3.1 KiB
§450 ★★★ — REGENERATING A TARGET .s FOR A FUNCTION THAT IS NO LONGER A STUB (P31 S75; 146 functions unblocked, two silent defects caught by ONE cross-check)
§448 found 147 game functions carried as verbatim __asm__ bodies. Every one was unworkable, and
not for want of information: splat emits asm/nonmatchings/<subseg>/<fn>.s only for functions that
are still INCLUDE_ASM stubs, so once a function stops being a stub its target disappears — while
match_one and rtu_match BOTH consume a .s. Measured: 1 of 147 had a target on disk. The
information was in the wrong FORM.
tools/verbatim_target_s.py regenerates it from the extracted ROM image. That source is not an
implementation detail — it is the whole point. Never regenerate a target from the __asm__ block
in our own source: the block is the thing under test. A target derived from it agrees with the
candidate by construction, and a "decompile" verified against it proves only that we transcribed our
own transcription (R34: the oracle must be able to DISAGREE).
TWO DEFECTS, BOTH SILENT, BOTH CAUGHT BY ONE KNOWN-TRUE CROSS-CHECK — regenerating a target for a
function that still had a splat .s, and comparing word for word:
- BYTE ORDER. splat writes the four bytes AS THEY SIT IN THE IMAGE (
C8FFBD27for the instruction0x27BDFFC8) andmasked_diff.insns_from_sreads that column withstruct.unpack("<I", bytes.fromhex(...)).objdumpprints the VALUE. Reversing to "fix" it double-swaps: 91 of 1139 words agreed. The LENGTH was perfect, so nothing but a word-level comparison could have caught it — a length check would have passed it straight through. objdumpELIDES RUNS OF ZERO BYTES as..., and a MIPSnopIS0x00000000. So every nop and every nop-padded tail silently vanished:func_80049610(three nops) disassembled to ZERO instructions,func_80047D3Cto 31 of 36.-z/--disassemble-zeroesis load-bearing. Here the length assertion DID catch it — which is why the tool refuses on a count mismatch instead of emitting a short target (R32/R43): ~30 quietly-truncated targets would otherwise have shipped.
Final state: 1139/1139 words identical to splat's own .s, 146 of 147 emitted, the single refusal
reported by name.
And the companion move, tools/verbatim_to_stub.py: to GATE one of these, do not write a parallel
gate (R33). Convert the verbatim body back to INCLUDE_ASM and it becomes a first-class citizen of
every existing tool — splat re-emits its .s, gate_main/gate_stage/harvest_verify all splice it
normally. INCLUDE_ASM pastes the same assembly the block transcribes, so the bytes are unchanged;
what changes is the ACCOUNTING, and in the honest direction — a stub counts as outstanding work while
a verbatim body counted as banked. Two refusals guard it: the block is located by brace/paren
MATCHING (never regex-sliced — these blocks are full of braces and parens inside string literals), and
the new stub's asm subdir is copied from a sibling stub IN THE SAME FILE, because subsegs are per-file
and a neighbour's spelling silently includes ANOTHER FUNCTION'S ASSEMBLY.