Three structural fixes to flatten mode, found bringing up games on-device
(Epic A1 Phase 5):
1. Nested classes: processFlatten now recurses through getDeclaredClasses()
(not gated on the outer class's eligibility), so nested Streamables like
BangConfig.Round get deterministic methods too. All detection/substitution/
insertion is scoped to each class's own body region (brace-matched,
comment/string-aware, member-class bodies masked during detection) so an
instrumented outer class can't hide — or poison — a nested one.
2. Ancestor-aware generation: a class whose ancestor declares read/writeObject
now generates `super.readObject(ins)` + only the fields declared below that
ancestor, instead of a self-contained full-set method. A self-contained
subclass method silently dropped the ancestor's side effects for every
subclass that previously inherited it: Prop.init() (server NPE cloning
board props at startRound), Item's _nondb identity extras (itemId=0 on
every wire-streamed item), InvocationMarshaller's _invdir rebind. Byte
layout is unchanged: the ancestor's method streams exactly the flattened
prefix. When the subclass adds no fields, nothing is generated — plain
inheritance is already complete (the field-free marshaller/unit case).
3. Abstract classes: substitution-only. Their default*Object() pass-throughs
are replaced in place with the class's own flattened prefix (making them
safe super-call targets — the reflective pass-through reads the full
dynamic-class set and would double-read the subclass tail), but whole
methods are never generated for them. Requires every Streamable ancestor
to be covered by the task's filesets; see findStreamMethodAncestor doc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhfHCXYp2ctWi76Z91y9Ut
The flatten-mode codegen gave each *Marshaller subclass a self-contained
read/writeObject, which overrode InvocationMarshaller.readObject and thus
skipped its side-effect of binding the transient _invdir from the stream's
client -> null _invdir -> NPE on the first invocation request (hit by ART
AND HotSpot clients). Fix: give InvocationMarshaller explicit read/writeObject
(GenStreamUtil, deterministic order, byte-identical to the old reflective
default) that keeps the _invdir binding; field-free subclasses inherit it.
narya 1.22.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhfHCXYp2ctWi76Z91y9Ut
Pre-crash safety checkpoint (box rebooted mid-implementation; NOT verified
complete). GenStreamableTask flatten-mode + GenStreamUtil runtime helper +
ObjectInputStream changes; explicit read/writeObject instrumentation across
presents net + dobj event classes so ART matches HotSpot's ClassUtil.getFields
order without the global sort flag (no data migration). narya 1.21.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhfHCXYp2ctWi76Z91y9Ut
A clean minor-version bump (retiring the Epic 2 -bangN patched-fork suffix —
that scheme is superseded; we now own these forks outright and version them
normally). GitHub Packages won't let us re-publish 1.19, and 1.20 makes the
TLS-capable build obvious in dependency listings.