4.2 KiB
§84 — The DERIVED-OFFSET remap bug: a hand-computed literal that encodes the DISTANCE between two per-overlay symbols, and why match_one is structurally blind to it (Phase 29 SESSION-20, func_8013D53C)
A family_remap member draft reached match_one MATCH (240 ins) and failed the whole-binary
gate. The whole binary differed by ONE BYTE.
The construct
Cracking the exemplar produced a deliberate matching idiom — reach a symbol via a different symbol
plus a literal offset, so gcc cannot CSE the two %hi/%lo pairs into one:
/* EXEMPLAR (ov_SC01_077): 0x801DA998 + 0x20 == 0x801DA9B8 ✓ */
(*(S9 *)&D_801DAA78) = *(S9 *)(&D_801DA998 + 0x20); /* same addr as &D_801DA9B8;
distinct sym defeats cse, keeps %hi/%lo */
family_remap substitutes symbol NAMES correctly — D_801DA998→D_801A5778, D_801DA9B8→D_801A5790
— and leaves the literal 0x20 alone. But 0x20 is not a constant of the algorithm: it is the
distance between two per-overlay symbols, and that distance is per-overlay.
exemplar: 0x801DA998 + 0x20 = 0x801DA9B8 ✓
member: 0x801A5778 + 0x20 = 0x801A5798 ✗ (the real symbol is 0x801A5790)
member: 0x801A5778 + 0x18 = 0x801A5790 ✓ correct offset is 0x18, not 0x20
Changing 0x20→0x18 + running the full gate_stage ladder banked it byte-identical.
Why it survived every candidate gate
match_one masks HI16/LO16, so a wrong %lo is invisible to it — it reports a clean MATCH.
Only the whole-binary gate sees the byte. This is the §81 blindness in its DATA form: §81 is
match_one blind to a jump table; §84 is match_one blind to a mis-derived data address.
A match_one MATCH that fails the whole-binary gate by ONE BYTE is a masked-field bug —
diff the built image against the payload and read the differing word before assuming plumbing.
The diagnostic that found it in one step:
python3 - <<'PY' # built vs extracted payload, byte-diff, map file offset -> vram
a=open('build/<ov>/<ov>','rb').read(); b=open('<target_path>','rb').read()
d=[i for i in range(min(len(a),len(b))) if a[i]!=b[i]]
print(len(d), [hex(BASE+i) for i in d[:8]])
PY
THE FIX IS MECHANICAL — the tool already holds the answer
The remap knows both mappings, and the exemplar's own comment even names the aliased symbol (which
the remap has already substituted: "same addr as &D_801A5790"). So:
correct_literal = mapped(aliased_sym) − mapped(base_sym)Detect&SYM + LITERALwhereSYM_exemplar + LITERALequals another mapped symbol's exemplar address, and recompute the literal from the member's own addresses. Never carry the exemplar's literal through a symbol substitution.
Scope, measured (do not over-generalise — §80)
The idiom is rare: only 5 sites across the whole matched corpus
(ov_SC01_077.c ×1, ov_SC01_077_jr_8017AE2C.c ×2, engine_core.h ×2). But one of those five
gates an entire 123-member family — 133 staged member drafts all carry the un-recomputed + 0x20
with different per-overlay base symbols (D_801F3058, D_801A5778, D_8018F9B8, …), so the single
tool fix is worth ≈ 240 ins × 123 members ≈ 29,520 ins.
It does NOT explain the sibling pool generally: the other three families probed the same day
(func_80144090, func_8012CC88, func_8014D12C) have zero derived-offset sites and fail for a
different, still-undiagnosed cause.
Two ladder lessons banked with it
- Bare
harvest_verifyis the LAST rung, not the ladder.gate_stage.pyrunscanon_resident_calls → cast_call_sites → reconcile_tu → ARITY pre-pass → sig_unify → harvest_verify. A probe that callsharvest_verifydirectly measures the un-recovered rate and will report PLUMBING for everything the ladder would have cleared. The byte fix AND the ladder were each individually insufficient here; only together did it bank. reconcile_decls.pyis RETIRED (Phase 26-A, R33) — superseded byreconcile_tu.py, because asking "what does the FLEET call this symbol?" is wrong by construction in a loosely-typed engine (548 of its answers conflicted; it was rewriting 60 of 196 live drafts). Reach forreconcile_tu, neverreconcile_decls.