Files
BFM-decomp/cookbook/C0177.md
T

5.0 KiB
Raw Blame History

§171 — THE STALE SEED SYMBOL (P30 S50, 2026-08-13): why §170's 91%→57% was never codegen

§170 left one hypothesis open — that family-batched A-prop cards CONCENTRATE members into one destination TU and die of the §169 collision. It is REFUTED, by data that already existed and by a direct test. Refuted three ways: (1) the recorded S49 verdicts show 5-draft single-TU groups banking 5/5 twice, and 4/4 twice — batch size was never the discriminator; (2) 11 of the 35 unbanked drafts were single-draft groups, i.e. already gated one-per-TU, and all 11 classify DIFF; (3) after the real fix below, the two "concentrated" groups §170 blamed — 12 drafts and 10 drafts into ONE .c each — banked 12/12 and 10/10.

The real defect, and it is one line per draft. A per-location data symbol (D_8018xxxx — a function-pointer table, a jump table, a state array) is part of the seed's ENVIRONMENT, not its logic. The adapt/A-prop lanes hand an agent a proven seed body plus a word-diff; the agent edits the logic and carries the seed's symbol across unrebased. Then:

  • match_one scores it MATCH. It compiles standalone and compares instruction ENCODINGS. %hi(D_seed) and %hi(D_target) are the same instruction with a different relocation TARGET NAME, and the scorer is blind to the name.
  • The whole-binary gate kills it at LINK: undefined reference to 'D_80181900'.

Standalone-MATCH / host-TU-link-failure. That class was 24 of the 24 concentrated A-prop failures — the entire conversion gap. In the word-diff card the rename is visible only as an opaque IMM site (the low half of a lui/lw %hi/%lo pair), which is exactly the site class an agent reads as "an immediate to copy", not "a symbol to rebase".

Measured: all 24 were 1:1 rewritable, every one at the same seed→target vram delta (0x4128 — one binary's data section offset from the seed's). Rebased mechanically: 23/24 banked (the 24th is a genuine DIFF). A-prop's real conversion is 56 → 79 of 91 = 87%, not 57%; the agents' 91% MATCH claim was very nearly right and the gap was ours.

Two tools, one primitive (tools/aprop_symfix.py, imported by family_cousins.py):

  • Post-hoc guard — aprop_symfix.py <slate.json> --fix audits every draft's vram-suffixed symbols against the symbols the TARGET's own .s relocates, rewrites the 1:1 cases, and emits a gate_lane-shaped slate. Deterministic, no build, so it belongs BEFORE the gate, never after.
  • Pre-hoc annotation — --aprop-cards members now carry sym_map: the explicit {seed → member} renames, computed from the seed's C BODY (a matched seed has no .s of its own — it is compiled from C) versus the member's .s. It turns a puzzle into an instruction.

This is R34 in one line: match_one is a perfect codegen oracle and a NULL linkage oracle. The symbol audit is the second oracle that can disagree with it, and it costs no build.

Two spellings, one bug, twice: sig_image writes hex lowercase, splat writes it uppercase, and seed_body_ref builds func_%08X. Both defects found while wiring this (a NO_SEED_BODY on every macro seed, an AMBIGUOUS on every 1:1 rename) were case mismatches — the same class §128/R35 keeps naming. Compare function identities case-insensitively, and strip the DEFINE_ prefix, or the seed's own name reads as a stale symbol.

§171-D — STALE-DELTA: the 1:1 rule generalized to n:n (P31 T1, 2026-08-14). The S50 fix handled exactly one stale symbol ↔ one target symbol; everything n:m was AMBIGUOUS-refused. But a seed's data cluster moves to the target overlay AS A BLOCK, so when the counts are EQUAL, every symbol on both sides is vram-addressed, and the sorted-by-address zip has exactly ONE uniform (target − draft) delta, the pairing is forced and aprop_symfix now rewrites it as STALE-DELTA (n pairs, per-pair substitution proved, sequential re.subn safe because stale ∩ asm_only = ∅). Anything else — count mismatch, a non-addressed name (MoveImage) on either side, per-pair deltas that disagree by even 4 — stays AMBIGUOUS. Measured on its first live batch (the 11 undefined reference to D_* failures from the match108 never-gated set): 4 classified STALE-DELTA, 4/4 banked (func_8016BCC0 Δ−0x65450, func_8017F1C8 Δ+0x13FC0, func_80186BD8 Δ+0x165A0, func_80186BF8 Δ+0x16590); the uniform-delta test correctly refused func_80186C1C, whose two deltas differ by 4 — a hand-check had wrongly called it fixable, the rule was right. R39 negative controls: synthetic probes (1:1 unchanged, uniform→DELTA, non-uniform/non-addressed/count-mismatch →AMBIGUOUS) plus a classification-equality re-audit of the S50 snapshot over still-stub rows (zero non-world-motion changes). Note the world-motion classes when re-auditing old snapshots: banked fns read NO_ASM (stub gone), and STALE can decay to clean when a sibling's bank DEFINES the once-missing symbol — 9 such drafts became gateable for free here (0 banked on re-gate, though: stored drafts still re-gate at the ~8% A10 law, 0/23 this batch).