What
#601 asked for zero-touch subtypes and named two asks. The tag manifest shipped and answers the first: an author writes [WProtoSubtype(typeof(Base))], never picks a number, never edits the base, never reads a sibling, and add / remove / re-add preserves the wire.
The second ask is unanswered, and no tag scheme can answer it:
Let a client slap this attribute on a type in their assembly.
A subtype in a consumer's assembly deriving from a base in this package. A per-assembly generator provably cannot see it: the base's dispatch chain was emitted when the base's assembly compiled. It is refused today with WPROTO040, and #601's research recorded why the manifest deliberately does not extend to it.
Why it is a different mechanism, not a bigger manifest
A manifest cannot coordinate across repositories that never see each other. This needs a runtime registry, and its hazards are the durable-wire kind:
- Unity's registrars run unordered. A serialize that happens before every registrar has run writes under the wrong tag, or none. The registry needs freeze semantics that refuse late registration after first use.
- Two unrelated packages picking the same tag on a shared base is undetectable at build time and silently type-confusing at read time. Arbitration has to be a runtime error at registration.
- Dispatch is currently branch-only static code. A registry lookup has to stay IL2CPP-safe — no
MakeGenericType — and stripping has to be told to keep formatters that nothing statically references.
Each of those failure modes is silent data corruption rather than a build error, which is why #601 refused to fold this in.
Acceptance criteria
- A consumer assembly can declare a subtype of a package base and have it round-trip, or the refusal stays and
WPROTO040's message explains the permanent reason rather than implying a future.
- Registration after first use is refused loudly, not honoured.
- Two registrations claiming one tag on one base fail at registration with both type names.
- The dispatch path is IL2CPP-safe and survives managed stripping, proven on a standalone player.
- Round-trip against protobuf-net 2.4.9 and 3.2.56 is preserved in both directions, since that is the package's central promise.
Notes
WPROTO040 currently refuses this and should keep refusing it until the above holds — a build error is strictly better than a player that writes an unreadable save.
Split from #601, which shipped the same-assembly half.
What
#601 asked for zero-touch subtypes and named two asks. The tag manifest shipped and answers the first: an author writes
[WProtoSubtype(typeof(Base))], never picks a number, never edits the base, never reads a sibling, and add / remove / re-add preserves the wire.The second ask is unanswered, and no tag scheme can answer it:
A subtype in a consumer's assembly deriving from a base in this package. A per-assembly generator provably cannot see it: the base's dispatch chain was emitted when the base's assembly compiled. It is refused today with
WPROTO040, and #601's research recorded why the manifest deliberately does not extend to it.Why it is a different mechanism, not a bigger manifest
A manifest cannot coordinate across repositories that never see each other. This needs a runtime registry, and its hazards are the durable-wire kind:
MakeGenericType— and stripping has to be told to keep formatters that nothing statically references.Each of those failure modes is silent data corruption rather than a build error, which is why #601 refused to fold this in.
Acceptance criteria
WPROTO040's message explains the permanent reason rather than implying a future.Notes
WPROTO040currently refuses this and should keep refusing it until the above holds — a build error is strictly better than a player that writes an unreadable save.Split from #601, which shipped the same-assembly half.