The S74 checkpoint's "one unfixed defect that is actively costing banks"
(reconcile_tu manufacturing declaration conflicts), run to ground — plus the
harness gap that produced a false carve-corruption verdict.
reconcile_tu.py — three defects, measured against the real gcc-2.7.2 front end
(cdecl._cc1_accepts, the oracle cdecl.compatible was validated with; R33):
* The premise "a decl BELOW still conflicts" is TRUE at file scope and FALSE
at block scope. cc1 ACCEPTS a block-scope extern against a TU decl below it
(pedwarn "type mismatch with previous external decl"); conforming it is
destructive, because the TU's decl names the TU's TYPE and a type declared
below the splice point is not in scope AT it -- the emitted result gets
"syntax error before 'D_x'". Byte-witnessed on resident:func_800D06E8 (344
ins), whose block-scoped `extern Blk80078E78` became `extern
Struct80078E78`, typedef 388 lines lower. That construct is what this
ladder's OWN scope_demote_drafts (§8d) rung emits on purpose, and three
already-banked functions in that TU use it: one rung undoing another.
* The cast pass rewrote COMMENT PROSE -- 8 rewrites inside one header comment,
including inside a quoted cc1 diagnostic. Now matches on cdecl._mask
(length-preserving, so a mask offset is a source offset) and splices into
the original.
* `&sym` emitted `&` applied to a cast: legal for the scalar arm, `invalid
lvalue in unary '&'` (measured) for the array/fnptr/fnptr_array arms. `&`
now selects a pointer form and consumes itself -- but ONLY with no trailing
subscript, because `&sym[i]` is the address of ELEMENT i and the old code
had that case right. That last clause exists because the R39 negative
control caught the fold as a regression in the first cut of this fix.
gate_stage.py — `--skip-stages` / `GATE_SKIP_STAGES` (loud when used). Stage 0
gates raw drafts first, so a broken rung can only cost a RECOVERY, which is
exactly what makes it invisible: the function it destroys was already failing,
so its DIFF reads as a fact about the function.
verify_worktree.py / jr_isolate_all.py / parallel_gate.py — provision() now
symlinks every .run/sig.*.jsonl (main clone 259, provisioned worktree 0), the
third member of the class holding extracted/ and .run/obj40. parallel_gate was
fixed for this identical bug in S69: two provisioners, no shared list, found
twice; they now cross-reference each other. jr_isolate_all no longer swallows
the resulting FileNotFoundError into `except: continue` -- that turned a missing
index into a confident carve-CORRUPTION verdict over 2,603 of 2,603 functions
(R54). Adds _assert_scan_covered: attempted == raised means the scan measured
nothing, so its zero is an artifact, not a finding (R32).
Verification:
* 4 cc1 probes (the table above), each run on the pinned front end.
* R39 negative control over the stored-draft corpus: 661 adjudicated, 652
IDENTICAL, 9 CHANGED and every one an intended class. 4,173 of 4,864 drafts
unadjudicable (filenames that are not func_<ADDR>) -- stated, not hidden.
* jr_isolate_all ov_SC03_105 --dry-run: unchanged in the main tree.
* make clean/extract/build BINARY=resident -> 8e17e02f... BYTE-IDENTICAL.
Docs ship with the change (R21): cookbook §442/§443, index regenerated (1,112
sections), 3 docs/SETUP.md rows, CURRENT_PHASE S75 log.
The checkpoint listed §435-§439 and was written before the documentation catch-up; a stale
checkpoint is worse than an absent one. Now records that every tool change this session carries a
SETUP row and a cookbook entry, and names the rule the gap taught.
I answered Drew's yes/no honestly — NO — and this closes it. Every gap had the same shape: a tool
change that came from a SUBAGENT arrived as a report, I merged the code and wrote it up in the
commit message, and a commit message is not the knowledge base. The six changes I made myself were
documented inline; these five were not.
SETUP.md tooling ledger:
* `ld_interleave` — the row still said "interleave linker inputs" and predated BOTH --order
(S72, main's 7-piece island) and --pre (S74, the resident's leading-rodata header).
* `harvest_verify` — the typedef strip-set is computed SCOPED (`above=fn`) now, and why.
* `jtbl_rodata_pads` — a new row for the three S74 measurement corrections, each of which ACCUSES
THE CARVE when it fires, plus why the trailing-.align one stayed latent (zero_gap self-corrects
an undershoot when the next item is an anchor, and a C jump table has no anchor).
* NEW row `jtbl_carve` — the `covered` / `covered-tpad` verdicts.
* NEW row `jr_isolate_all` — `_region_emit_start` and the empty-closing-region skip.
Cookbook:
* §440 — a carve piece binds to a SUBSEG, not a function, so §8b's "non-adjacent => ISOLATE" is
over-strict: EXTEND the carve across still-stubbed material instead. Four byte-proven
corollaries (migrated tables self-align by SPAN-RELATIVE offset; JTBL_PADS counts cc1 tables
only so a mixed span's spec grows as siblings bank; the zero-word rule is invalid across a
migrated boundary; a covered table at 4-mod-8 gains 4 bytes when it banks). Plus the resident's
rodata->text->data->rodata->data layout and why it needed --pre.
* §441 — three more instrument defects that each produced a confident, precise, WRONG verdict
about a correct draft, with the habit they share: when a gate rejects a body you have
byte-verified standalone, the first suspect is the gate.
Playbook: new step 2a-0 — the same-address lead is size-filtered now; read the `⚠ IGNORE` line, and
regenerate any pack built before S74 rather than trusting a bare address lead.
Every reject class this session was an instrument defect, not a codegen wall: 24 already-MATCHed
bodies were sitting behind seven tool bugs, six of which are now fixed. The checkpoint names the
seventh (reconcile_tu manufacturing declaration conflicts) as the first thing to fix next session,
and the harness gap (verify_worktree omits the sig files, and the scan that needs them swallows the
error and reports a false corruption) beside it.
Fleet verified from a clean rebuild AFTER the last bank: check-all 213 passed, 0 failed of 213.
split_indicator 213 OK and now a hard gate. INCLUDE_ASM lines in src/ 1,086 -> 1,036, measured at
both commits — the same 50 the gates reported, counted independently from the source.
THE CARD USED TO HAND AGENTS A WRONG TWIN ABOUT ONCE IN FIVE. `⭐ func X IS BANKED AT THIS ADDRESS`
never checked that the two functions were the same SIZE, and overlays share addresses between
unrelated functions as readily as they share code. Measured over this session's ~60 cards: about a
dozen agents reported discarding the lead themselves, and one card advertised a 72-instruction
namesake — with journal history claiming "already MATCH closeness 0" — to a 241-instruction target.
A confidently wrong lead costs more than no lead, because the agent believes it.
corpus.sig already carries `nins` and `h_seq`, so the fix is free: `_same_addr_banked` now returns
(binary, nins, h_seq); the card keeps a lead only at a MATCHING instruction count, marks it strong
when the mnemonic skeleton matches too, and prints an explicit `⚠ IGNORE` naming the binaries where
that address holds something else, with both sizes.
VERIFIED IN BOTH DIRECTIONS against known-true cases before being believed (never trust a filter you
have not tried to fool):
* the trap: ov_SC03_105:func_801806F8 (241) vs ov_SC03_013 (72) -> `⚠ IGNORE`.
* the positive: ov_SC02_003:func_80187B40 (158) -> strong lead to ov_SC02_000 (158, same h_seq,
banked this session) AND, in the same card, warned off ov_SC04_011's 138-ins homonym at that
same address. That is precisely the pair a wave agent sorted out by hand hours earlier.
Cookbook §438 (the law: a lead is fuel only if it carries the cheapest fact that can refute it —
size refutes a homonym for free and nobody had asked) and §439, the S74 lever set: MEM_IN_STRUCT_P
as a two-way alias-oracle dial (four agents converged on it independently); `goto`-into-a-shared-tail
vs longhand as a REGALLOC dial because gcc-2.7.2 cross-jumps after allocation; `for` -> do/while as a
length-changing scheduling dial; allocno PRIORITY via a non-volatile asm at a loop head, with the
measurement that register pins are actively harmful for that class; the -O0 global-RMW rule
(`x++` emits the copy-back quartet, `x = x+1` does not); why `sll 16; srl 16` survives only across a
CALL; `sltiu N` without `addiu -1` proving an empty `case 0` is mandatory; block-scoped temps in
duplicated bodies; two `register asm` vars cannot share a hard reg; and `x*32` vs `x<<5` emitting
lh vs lhu — which match_one's %lo mask HIDES, so it must be checked with objdump.
resident:func_800D00E4/func_800D02D0/func_800D0488 + ov_SC07_002:func_80180248, all byte-verified
from clean rebuilds (resident 8e17e02f, ov_SC07_002 fad71342) and counted from the SOURCE.
ov_SC06_029's two are re-gated separately against HEAD — this agent's worktree predated five banks
there, so its numbers for that binary no longer apply.
TWO OF THE SIX NEEDED NO CARVE WORK AT ALL, AND CARVE-REFUSED WAS AN INSTRUMENT VERDICT.
ov_SC07_002:func_80180248's table is ALREADY inside a carve bound to its own subseg: in stub state
spimdisasm migrates the table into the fn's .s and the object fills the piece exactly, so banking
just swaps that block for cc1's identical one. `island_probe` classified it `tail` on the table's
ADDRESS, `apply()` routed it to build_carve, which resolves spans out of the RAW data asm where a
carved table no longer is -> "not found in the raw data asm" -> harvest_verify booked CARVE-REFUSED.
A verdict about the route we chose, not about the function (R43). jtbl_carve now has a `covered`
verdict (table inside an existing carve bound to the fn's OWN subseg) and a `covered-tpad` wall (the
retail copy carries a trailing §8a pad the matched body won't emit — bankable, needs a `0t<n>`
entry); a fully-covered batch is a no-op before either route.
THE RESIDENT CAN CARVE LIKE AN OVERLAY. Its three tables are adjacent and lead the island
(0x450e0..0x451ac, one span, all in subseg `resident`). The genuinely new part: the resident opens
with `- [0x0, rodata, hdr]`, a 1-word .rodata header BEFORE the code, so its layout is
rodata -> text -> data -> rodata(carve) -> data, which `ld_interleave --order` cannot express (every
listed piece lands after TEXT_START, and hdr.rodata.o would fall into the unchecked `empties` bucket
and be parked after the text, moving every byte). New `--pre` places a leading-rodata piece ahead of
the text; resident_JTBL_INTERLEAVE uses it.
NEW LAW, BYTE-PROVEN (§8b was over-strict — EXTEND the carve, do not isolate): a .rodata carve piece
binds to a code SUBSEG, not a function, and the object's .rodata is the address-ordered
concatenation of cc1's tables for BANKED functions and still-stubbed functions' MIGRATED tables. So
a span may legitimately hold a MIX, and extending a carve across an align-pad word and two unrelated
STILL-STUBBED tables was byte-identical with nothing banked — where the tooling demanded a
jr-isolation. Corollaries, all measured: migrated tables self-align (spimdisasm emits `.align 3` iff
the table's SPAN-RELATIVE offset is 8-aligned), so stubbed tables need no spec; JTBL_PADS counts cc1
tables only, so a mixed span's spec GROWS as each sibling banks; and the zero-word rule is INVALID
across a migrated boundary, because that zero is supplied by the preceding migrated block.
ALSO REPORTED, NOT FIXED (harness gap worth its own change): verify_worktree.provision omits
`.run/sig.<bin>.jsonl` — main clone 259 files, provisioned worktree 0 — and jr_isolate_all's
carve-ownership scan swallows the resulting FileNotFoundError in a bare `except: continue`. Measured:
2603 of 2603 functions raised, the scan found 0 owners, and the run aborted with a CONFIDENT FALSE
verdict ("committed .rodata carve ownership is not 1:1 — stranded/duplicated carve"). Both resolve
instantly once the sigs are present. Any worktree-run isolation before that is fixed reports a
corruption that is not there.
Clean rebuild BYTE-IDENTICAL 87ac0de3; corpus.stubs('md_SC07_004') 10 -> 0, counted from the SOURCE.
THE §376/§378 CHAIN WAS NEVER NEEDED. Zero declaration edits: no fix_arity_callers, no
cast_self_callers, no --any-proto, no --sync-decls, no undo journal. `git diff -U0` on the TU removes
exactly the 10 INCLUDE_ASM stubs plus one hoisted typedef. Every recorded "declaration conflict" was
an INSTRUMENT defect. Four of them, all named, three patched here:
1. `CC1-FAIL(no-diagnostic)` was neither cc1 nor no-diagnostic. The failing stage was
`jtbl_rodata_pads --derive`, which prints to stderr AFTER cc1 exits 0 quietly. `_items` matched a
rodata anchor only as `D_xxxxxxxx:`, but a block written as inline `__asm__` in C arrives in the
labels.inc macro form `dlabel D_xxxxxxxx` — so an 8-byte hole opened in the walk and every C jump
table after it died with "island layout drift". The harness label was wrong twice: it said CC1
when the failure was a post-maspsx filter, and no-diagnostic when there was a precise one.
2. Same file, UNALIGNED ANCHOR: the ctable branch read `word(pos)` without first stepping the
sub-word zero gap, so a preceding `.asciz` ending at an odd address made it refuse a correct
layout. Now reuses the same zero_gap the anchor branches already use — a no-op wherever pos is
already aligned, i.e. everywhere that builds green today.
3. `harvest_verify` computed the typedef strip-set UNSCOPED: `cdecl.typedef_names(path)` without
`above=fn`, which that function supports for exactly this. A typedef declared BELOW the splice
point got stripped out of the draft that needed it -> `parse error`, logged as PLUMBING and
indistinguishable from a real conflict. One line.
4. NOT PATCHED, AND THE MOST IMPORTANT ONE: `reconcile_tu.py` (gate_stage's `-rc` stage) MANUFACTURED
both remaining "conflicts". The same drafts gate 9/9 byte-identical through harvest_verify and
7/9 through gate_stage. Isolated stage by stage, `-rc` (a) rewrites deliberately BLOCK-SCOPED
externs to a file-scope spelling whose typedef is declared ~2,800 lines lower — overwriting the
TU's own byte-proven house style, which three already-banked functions in that file use; and
(b) substitutes identifiers TEXTUALLY, including inside comments and inside `&`-expressions,
emitting `*(T *)&((s32 *)&D_800AE620)`. A correct draft using the block-scope-extern idiom
currently CANNOT survive gate_stage. Left for a deliberate fix: `--stages` should be able to skip
reconcile_tu, or a draft should be able to opt out.
The two surviving non-stub source edits are byte-neutral (proven by the SHA above): a §304
migrated-rodata re-emission (`D_801A01EC`, the exact form this TU already uses three times, needed
because banking the body deletes the .s that carried the island word), and one typedef moved up so a
function above it can see it (typedefs emit no bytes).
Also banked the 10th stub (func_801ADA10) that defect 3 had been silently blocking.
Regression-checked by the agent: main, md_MAIN_003, md_SC07_003, md_SC03_073, md_MAIN_011 all
rebuild BYTE-IDENTICAL. A full R22 follows before this session closes.
md_MAIN_011:func_800CF28C · md_MAIN_003:func_800D0268/func_800D0740/func_800D0C50, all byte-verified
from a clean rebuild and counted from the SOURCE: md_MAIN_011 is now FULLY MATCHED (0 open stubs),
md_MAIN_003 is down to 1 (func_800CF3E8).
THE PREMISE I HANDED THE AGENT WAS WRONG, AND IT SAID SO. md_MAIN_011 is already a whole-object -O0
module — no carve was needed. Its real blocker was tools/jtbl_rodata_pads._s_rodata_span ignoring a
trailing `.align`, the SAME defect this session fixed for md_SC07_003 from the other direction: two
agents converged on it independently. Adopted this agent's stricter form (only a TRULY trailing
align rounds `hi`; an interior one is followed by data that sets `hi` higher anyway).
Note WHY it stayed latent: `derive`'s zero_gap self-corrects a 1-3 byte undershoot whenever the next
stream item is an anchor. A C jump table has NO anchor — so the bug can only fire the moment someone
banks a switch function into such an object, and when it fires it accuses the CARVE ("island layout
drift"), not itself.
md_MAIN_003 needed one new -O0 object, and the boundary I proposed (0x1f74 -> 0x1e58) was both too
narrow and off by 0x2B8. The carve made is `md_MAIN_003_o0e` at 0x1308 (vram 0x800D0100) running to
the existing o0c boundary: everything in that span is a §265 verbatim __asm__ body or an INCLUDE_ASM
stub — zero optimizable C — so the whole tail flips with one cut. Proved byte-identical with NOTHING
banked first (§431 discipline), then the three drafts gated one at a time.
TWO MORE GENERAL DEFECTS FIXED IN jr_isolate_all, both of which silently mis-place a boundary:
* an item-less CLOSING region emitted a duplicate `- [off, c, …]` line and the validator refused;
the empty-region skip covered only region 0, and `_partition`'s empty `footer` made the closing
region look non-empty.
* A §265 VERBATIM __asm__ BODY IS PREAMBLE, AND PREAMBLE IS ASSUMED BYTE-NEUTRAL. It is not — it
emits bytes. `parse_overlay_c` has four addressed-anchor forms and a verbatim body is none of
them, so it attaches to the NEXT anchor: cutting at func_800D0268 would have moved 0x168 bytes of
other functions into the new object while the yaml claimed the region starts higher. New
`_region_emit_start()` derives the yaml offset from the region's CONTENT (item addresses + every
.globl/.ent the text names that resolves inside the object) and takes min(cut, emit), so a
boundary can only move DOWN. Where no verbatim asm is in play it equals the cut — every existing
isolate is unchanged.
BLAST RADIUS PROVEN, not argued: jtbl_rodata_pads is in the build path (`--derive` for md_*/main),
so the agent rebuilt main + all 70 md_* from scratch (71/71) and then ran the full fleet:
**make check-all 213/213 passed, 0 failed**, main 143dbb89 BYTE-IDENTICAL.
CENSUS, denominator asserted (1057 live stubs, 0 without a .s): exactly ONE -O0-prologue stub
remains stranded in an -O2 TU fleet-wide — main:func_8002C410 in src/800_b.c, 299 ins. Nothing more
should be built for this class; the general tool already existed and what was missing was
correctness, not coverage.
FIVE independently-MATCHed ov_SC06_029 bodies were rejected by a `parse error before '#'` in a file
the GATE ITSELF generates, at a line no draft contains. The isolation emitted, into the §8b carried
decl layer:
extern #define CALL_80185C6C ((void *(*)(s32, s32))func_80185C6C) extern void func_8012C218();
CAUSE. Every peeler in the TU-split chain asked `line.strip().startswith("/*")`, which is blind to a
comment a construct opens MID-LINE and wraps. The declaration ends at its `;` BEFORE the `/*`, so
the caller resumed on the comment's PROSE with in_block=False — and the prose is hostile: `(s32,s32)`
closes a depth-0 paren, `seen_header` latches, and every later `;` reads as a K&R parameter
declaration, so one "construct" swallowed the whole preamble. `parse_overlay_c` then anchored a
`def` on a pure declaration run and `def_proto` rendered it as that definition's implied prototype.
A SECOND defect rode along: `_file_scope_decls` hoisted such a col-0 line VERBATIM, unterminated
`/*` included, so the carried layer opened a comment that silently ate the next two declarations —
a dropped file-scope decl is a silent byte-changer. Building the guard exposed a THIRD: `_strip`
tested for `/*` before stripping `//`, so `// … src/*/*.c` (7 lines in 5 sources) opened a phantom
block comment and blanked everything to the next `*/`.
FIX: one derived comment-state oracle, `comment_open_at()` (R33) — per line, does it BEGIN inside a
block comment — consulted by parse_overlay_c, def_proto, split_src_region.parse and
jr_isolate_all._file_scope_decls (which also truncates a hoisted decl at an unterminated `/*`).
`_strip` now lexes left to right. `parse_overlay_c` RAISES (R43) when a wrapped comment closes with
code after the `*/`, because that construct could never anchor — 0 occurrences fleet-wide.
MEASURED, not assumed:
* the shape occurs 238 times across 193 tracked .c files; 153 are col-0 hoistable declarations in
150 files — every one a binary whose next isolation would have carried a broken decl layer.
* A/B over all 4,188 tracked sources, old parser vs new: round-trip identity 4188/4188 both ways;
exactly 2 files' item lists change, each losing one PHANTOM def and gaining nothing; malformed
implied prototypes 999 -> 984; 0 refusals.
* negative control BEFORE any edit: ov_SC06_029 extract+build -j+check BYTE-IDENTICAL b7b0d4ae.
* with the fix, gate_stage banked 5 of 6 drafts, counted from the SOURCE; the 6th
(func_80184084) is the separate CARVE-REFUSED class.
The 984 residual malformed prototypes are a DIFFERENT pre-existing trigger (col-0 lines gluing
declarations to DEFINE_func_*() invocations); 4 still carry a `#` and survive only because it lands
in a dropped segment. Named in §437, deliberately not fixed here.
Cookbook §437 + a SETUP.md tooling-ledger row for comment_open_at (parse_overlay_c may now raise).
The banks themselves are NOT in this commit: the agent's worktree predated func_8017F9C0's bank, so
adopting its TU verbatim would have destroyed one. They get re-gated against HEAD with these tools.
TWO DEFECTS, ONE INCIDENT. ov_SC03_105's own SUCCESSFUL gate committed an isolation's new TUs
(src/ov_SC03_105/ov_SC03_105_jr_801813BC.c, _jr_80181C84.c) whose `INCLUDE_ASM` lines name .s files
that do not exist until a re-extract. corpus.stubs then refused — correctly, "the tree and the
source disagree" — so the NEXT gate on that binary died before doing any work, and a matched body
(func_801818E8) sat unbankable behind it.
1. THE REASON NOW TRAVELS WITH THE REFUSAL. stubs_of() returned a bare None and the caller printed
"corpus refused in worktree": true, and naming nothing. It took a hand-built worktree to see that
corpus had said exactly what was wrong all along. It now returns the message and the result JSON
carries it. Verified against a TRUE reproduction (delete one .s in a scratch worktree):
verdict REFUSED + "1 stub(s) have NO .s on disk ... src/...:4214: asm/.../func_8017F018.s".
2. THE MERGE STEP REPAIRS WHAT IT BROKE. For every binary whose carve created a new source file,
assert corpus.stubs is satisfiable in the MAIN tree; if not, `make extract BINARY=<b>` and
re-assert; if it is STILL unreadable, say so loudly rather than leaving a tree no tool can read
(R32/R43). This is the R22 corollary — a config change needs a make extract, not just a make
check — firing inside a tool's own commit.
Repaired the live instance by hand first: rm -rf asm/ov_SC03_105 + extract + build -j + check ->
BYTE-IDENTICAL d305ff6d, corpus readable again, and func_801818E8 then banked (commit:3718).
Cookbook §436-D; wave-playbook §6 carries the hand-gating version of the same warning.
`_s_rodata_span` summed only the DATA an included `.s` emits, so a file ending `.asciz "r"` +
`.align 2` measured 0x801A00D8..DA instead of ..DC. The island walk then landed 2 bytes short and
`--derive` aborted with `C table entry 0 at 0x801A00DA ... island layout drift` — a true statement
about a span that was never the real one. Worse, the Makefile pipes md_*/main through `--derive`
without `set -o pipefail`, so the failure could yield a short object rather than stopping the build.
Three lines: round `hi` up to the trailing align, which is what the assembler actually emits.
CONTROLS (both on UNMODIFIED sources, so this is proven byte-neutral, not argued):
md_SC07_003 clean rm -rf + extract + build -j + check -> BYTE-IDENTICAL 46af79a1
gate_main --assert-baseline -> BASELINE GREEN 143dbb89
Found by a drafting agent (md_SC07_003/func_801A09C8) that ran its own gate reject to ground
instead of respelling its body, and proved the patch in scratch first: with the fix its draft's
.rodata is byte-identical to the green control and .text differs in exactly 1 of 6266 words — a
%lo(jtbl) carrying a section-symbol reloc that three already-green C-jtbl functions in the same
object already ship. Cookbook §436-C, with the habit that found it.
Three independent split agents hit both defects in one session, on the tools that CERTIFY and UNDO
the work they were doing. Each is fixed, negative-controlled against the exact failing case, wired
into its siblings, and documented in the same change (cookbook §436).
1. split_indicator attributed a jump table by the STUB'S DIRECTORY PATH. `make extract` does not
prune a re-homed subseg's `nonmatchings/<old>/` dir, so after a correct, byte-green §431 split
both the old and new dirs hold the moved stub — and the tool printed NEEDS SPLIT for a split that
was already correct. owners() now derives the owner from the CONFIG by address (R33), exactly as
jtbl_carve.func_subseg already does for the identical §8b hazard, and NAMES any leftover stub in
a `note:` line. Notes now print on an OK verdict too: hiding one behind `st != OK` is the same
defect in the other direction — a true verdict about a narrower world than the reader believes.
PROVEN by planting a stale stub for func_80182A00 under its old subseg: OK + the note, where the
old code would have seen one subseg owning two spans. --self-test still PASSes both directions.
2. jtbl_carve --revert did `git checkout --` on the WHOLE splat yaml. The carve owns only the
trailing data/.rodata region; the `c` pieces are source configuration it never writes. The blunt
form cannot tell "carve state I just added" from "the §431 split someone added to the same
uncommitted file", so --revert after a carve PROBE silently un-split the overlay — each agent
recovered only because they had backed the yaml up by hand. It now splices back only its own
region (parse_config gained an optional `lines=` so the SAME region derivation runs over the
committed text — one derivation, two callers), refuses loudly if the committed region carves onto
a subseg the current config no longer defines, and reports how many uncommitted `c` pieces it
preserved. PROVEN in the ov_SC01_084 worktree: carve → revert → the uncommitted split survived
("PRESERVED 30 uncommitted `c` piece(s)"), carve lines gone, diff back to the 6 split lines.
SIBLING: jtbl_family_bank.revert carried the same blunt checkout for the isolation's code pieces.
It now keeps whatever pre-dated the attempt (the `keep_regions` signal it already trusts for
src/) and NAMES anything it drops — an isolation region and a §431 split piece are both
`<ov>_jr_<addr>`, so no name test can tell them apart and only that signal can.
3. NOT A DEFECT, and recorded as such: a speculative carve fails the build with `jtbl_rodata_pads:
consumed 3 rodata jump table(s) but 9 pad spec(s) given`. That is R43 working — the pad spec is a
CONSEQUENCE of banking, not a prediction of it — and it reproduces identically on the pristine
unsplit config, so it is never evidence about a split.
make tools-health: split_indicator is a HARD GATE now, as its own comment promised it would become
once the last violation was split. 213 OK of 213; a new one fails the build instead of being echoed
past.
Cookbook §435 (an overlay TU split is near-free — 0/3,074, 1/2,679, 2/3,254 names crossed, because
the §8b carried decl layer re-emits externs per region so only typedefs can cross; and the gap test
between two rodata runs is "is this word a valid code address", not "is it zero") + §436 (the two
defects and the shape they share). Playbook + SETUP.md carry the emptied CARVE-BLOCKED class.
16 of 27 entries dropped as STALE, all of them the CARVE-BLOCKED set the four §431 splits
in commit:3709 just unblocked. 11 WALL entries kept (curated, pinned by their `# WALL:`
annotation, which exclude_audit re-reads as a pin so a wall survives regeneration).
An exclude list records what the TOOLING could not do, so it becomes a list of work you
decided not to do the moment the tooling improves — regenerating it is part of the fix,
not follow-up hygiene. draw_waves --exclude-file audits and REFUSES a stale list.
BYTE-IDENTICAL on all four, with NOTHING banked (clean rm -rf asm/<bin> + extract +
build -j + check), which is the whole point: the structure lands first and proves neutral,
then drafts bank against it. split_indicator: 213 OK, 0 needing attention, of 213 —
the CARVE-BLOCKED class is now EMPTY fleet-wide.
One code object contributes exactly ONE contiguous .rodata run, so a subseg owning raw
jump tables in two non-adjacent spans could carve only one of them and every switch
function in the other span was unbankable at any effort (cookbook §426/§431).
ov_SC01_084 2 pieces cut 0x80182A00 (0x5A8A8)
ov_SC02_005 3 PIECES cuts 0x80185060 (0x5CF08) + 0x80185E80 (0x5DD28)
ov_SC02_011 3 PIECES cuts 0x80183178 (0x5B020) + 0x80188E3C (0x60CE4)
ov_SC03_105 2 pieces cut 0x8018624C (0x5E0F4)
TWO OF THE FOUR NEEDED A CUT THE BRIEF DID NOT NAME, and the address evidence found it:
each already had a carve run that could not merge with span 1, separated by rodata that
is not padding — ov_SC02_005 by `0000F040 00000000` (8 bytes, twice the widest .align 3
pad the JTBL_PADS spec can emit), ov_SC02_011 by `FEBEF6AE 000002DC 0 0` (a TU's trailing
const data). A gap detector keyed on zero words would have merged them and produced an
unbuildable carve: the load-bearing test is "is this word a valid code address in this
overlay's text range", not "is it zero". ov_SC01_084's divider is real data too
(`0 FFFF0000 00080000 0 0`), while ITS span-1 gap word IS a zero .align 3 pad and merges.
OVERLAY SPLITS ARE NEAR-FREE, AND THE REASON IS STRUCTURAL — the opposite of main.
The Phase-26 §8b carried decl layer re-emits each region's externs locally, so only
typedefs cross a cut: 1 name of 2,679 (ov_SC01_084, 0 typedefs) · 5 typedefs of 44
(ov_SC02_005) · 2 names of 3,254, 0 typedefs (ov_SC02_011) · 0 of 3,074 with zero
compiler errors (ov_SC03_105). main's split moved 57 of 1,247. Every crossing typedef was
MOVED to a <bin>_shared.h, never copied, and every list came from the compiler (R33).
Carve probes (jtbl_carve --func, then reverted — carve state is added when a function
banks, never speculatively): all four subsegs now accept a carve with no fail-loud, and
jtbl_carve derived the §8e per-table pad specs the zero-word rule predicts.
Unlocks 17 open switch functions: ov_SC01_084 func_80182A00 · ov_SC02_005 func_80185060,
func_80185E80 · ov_SC02_011 func_80183178, func_80183630, func_8018418C, func_80188E3C ·
ov_SC03_105 func_801806F8, func_80180ABC, func_80180EC0, func_801813BC, func_801818E8,
func_80181C84, func_8018624C, func_801867D0 (+2 more span-1 owners).
CORRECTION, measured not assumed: config/wave_exclude.txt listed ov_SC01_084:func_80182328
as CARVE-BLOCKED and it never was — its table ABUTS the existing carve, so it always
merged into one run. Proven by control on the PRISTINE unsplit config: --func func_80182A00
exits 1 "would host NON-CONTIGUOUS .rodata carves", --func func_80182328 succeeds.
Supersedes S73 CLOSE and its addendum. Every number re-verified against the repo:
REAL 880/1,918, MAIN 56.5%, frontier 124 (main 36), main jtbl fns 25 -> 2.
Replaces the 'known-remaining' TODO list with what was actually fixed. The four that
were not doc typos: progress.py undercounting REAL by 7 (the #else half of a
NON_MATCHING block is live code and classify() swallowed it), the silently deleted §429,
the false §265 accusation in §434, and memory-map.md:309 claiming a 'verified' extent
that overlaps the new span-B carve.
* §426 listed three localizer verdicts; there are four, and the missing TABLE REJECT is
the dominant residual on main's switch functions (§433). Its span-B table also still
advertised SaveLoadRoutine as an unlockable owner — it is the §434 frame pair.
* §434 quoted SaveLoadRoutine at 1139 instructions; the .s has 1165.
* docs/memory-map.md:309 recorded saveHeaderTemplate @0x80072DF0 with 'handler code ptrs
@+0x54' at the ledger's HIGHEST confidence. 0x80072DF0+0x54 = 0x80072E44, which is
jtbl_80072E44 — func_8002B0B4's dispatch table and the first 12 bytes of the S72 span-B
carve. The row's extent is wrong past +0x54 and now says so; a 'verified' row that
overlaps a carve boundary is how a future resegmentation gets talked out of itself.
* Makefile's overlay --front/--tail comment sat directly under main's --order call with
nothing distinguishing them; now says which is which.
* Step-1's draw command still passed a superseded .run snapshot through the UN-AUDITED
--exclude flag. Running it verbatim bypassed the freshness prerequisite built this
session. Now --exclude-file config/wave_exclude.txt with a fresh --ledger.
* Both exclude populations were wrong: '96 jtbl functions build_carve refuses' is 16
across 4 overlays (split_indicator derives it), and the seven .run/S6*_walls.txt
ledgers are superseded by the WALL entries pinned in the canonical list.
* Entry count said 19; it is 26. Replaced with 'trust exclude_audit, never a number
written here' — a count in prose goes stale the moment anything is added.
* Model routing still had a Sonnet band Drew abolished, and no mention that Fable is
exhausted account-wide (three agents died on the limit in S73 at ~133k tokens each).
* Section 1c's census was pre-session: 25 of 59 main jtbl functions, 'every one now
drawable', and SaveLoadRoutine as the flagship drawable example. It is 2 of 36, and
those two are the §434 frame pair, excluded from draws.
* The gate step listed three gate_main verdicts; there are four, and the missing TABLE
REJECT is the DOMINANT residual on main's switch functions (§433).
* Triage still named jr_isolate_all as the usual CARVE unblock; it does not yet produce
an assemblable object, and §431 is the cheaper route.
* draw_waves Usage advertised [--no-main], which argparse never defined (the flags are
--main / --only-main, and main is excluded by default), and omitted --exclude-file,
which is now a PREREQUISITE that refuses a stale list.
* jr_isolate's STATUS block still declared the tool BLOCKED on split_src_region with the
blocker unbuilt. Five defects were fixed this session and it runs the full chain to
completion; what remains is a duplicate-definition class at assembly. Says so, and
points at §431 as the cheaper route than finishing the item model.
* ld_interleave's layout diagram — the first thing anyone reads — showed the pre-S72
three-piece island with 6324C.data.o. main's island is SEVEN pieces driven by --order;
--front/--tail is the overlay form now.
* jtbl_rodata_pads described a stored-spec-only filter and advertised guards that no
longer all exist; --derive serves main since S72.
classify() consumed everything from '#ifdef NON_MATCHING' through '#endif', swallowing
the #else half. But banking replaces the #else INCLUDE_ASM with the real body and leaves
the old attempt in the dead half — so every function banked that way landed in NO bucket:
not real, not a stub, invisible in both numerator and denominator.
Measured: CdReadStateMachine, CdReadSectorReadyCB and StreamLoadStateMachine are
byte-identical in the shipped build and counted as zero. REAL 873 -> 880, matchable
1911 -> 1918 (seven functions fleet-wide, not the three I first checked).
Now consumes only the DEAD half, then decides from the LIVE half: an INCLUDE_ASM there
still buckets as NON_MATCHING (accounting unchanged), anything else rewinds and is
classified normally.
THIRD coverage defect of this exact shape in this one function — the K&R-definition case
(~190k instructions erased) and the '#if 0' case are both documented in its own comments,
which is what pointed me at it. A scanner that walks preprocessor structure needs a test
per branch, not per directive.
Found by the S73 documentation audit, which I had written off as producing only doc typos.
Corrects three defects I introduced (deleted §429, a false accusation in §434, an
over-strong SaveLoadRoutine verdict), two wrong numbers in the block above, and records
the SETUP §6.6 gap that was the real answer to 'are the docs up to date'.
Also lists what the audit found and I did NOT fix, with file:line, so a fresh session
inherits the list instead of rediscovering it: jr_isolate/jtbl_rodata_pads/draw_waves
docstrings, several stale playbook census numbers and its step-1 command, memory-map:309,
and the pre-S72 --front/--tail descriptions in the Makefile and ld_interleave.
TWO REAL DEFECTS I INTRODUCED, both found by the audit:
1. §429 WAS SILENTLY DELETED. My §428a rewrite (commit:3659) wrote t[:start]+new instead of
t[:start]+new+t[end:], truncating everything below §428a. §429 ('every held pointer
needs its own local') was the casualty and had been gone for the rest of the session.
Restored verbatim from commit:3658, between §428a and §430. All of 426-434 now present;
index 1103 sections.
2. §434 ACCUSED AN AGENT OF INVENTING ITS CITATION OF §265. §265 exists and says exactly
what the agent said — 'THE VERBATIM-ASM BANK LANE: A FUNCTION NO -O2 C CAN EVER MATCH
BANKS AS A RAW __asm__ BODY' — with four named byte-banked precedents. I ran
cookbook_index --resolve 265, which resolves a LINE number not a section, and believed
it without opening §265. Retracted in the section itself.
The verdict also needed narrowing: gated, the §265 transcription of SaveLoadRoutine is
BYTE-IDENTICAL for the function itself and fails only because substituting one half of
the shared frame moves 3,989 bytes across 262 symbols. True statement: neither can bank
SEPARATELY; the route is to transcribe/resegment the PAIR together via §265. The
exclude entries now say 'excluded from DRAWS only' and name that route, instead of
reading as 'unmatchable'.
I also mis-read the draft as containing INCLUDE_ASM by grepping raw text — all three hits
were in comments. Sixth instance this session of reading prose as code.
I updated SETUP's tooling INVENTORY when each tool changed, but not the HUMAN-facing
procedure, and §6.6 is where a person learns the matching loop:
* :493 still said 'In src/800.c, replace the INCLUDE_ASM line with the C function body.'
main's game code is THREE TUs since S72, and WHICH one is load-bearing for any switch
function — one code object contributes exactly ONE contiguous .rodata run, so the TU
decides which jump-table span the body's table lands in. Following that line for a
span-B/C function re-creates the exact §426 double-emit this session existed to remove.
Replaced with the vram -> TU -> asm-path -> span table.
* :759 listed main_diff_locate's verdicts as an exhaustive three — BODY / PLUMBING /
MIXED. There are FOUR, and the missing TABLE REJECT is checked FIRST and covers
precisely the case the PLUMBING clause claimed ('byte-identical, everything differs
elsewhere'), routing the reader into the one chain the tool forbids for that class.
* config/wave_exclude.txt was named nowhere in SETUP despite being tracked config that
draw_waves now requires. Added, with both entry classes and the WALL pin.
* :537 described ld_interleave as --front/--tail only; main uses --order since S72.
The jump-table class on main is resolved: 25 -> 2, and both survivors are the §434 frame
pair, provably unmatchable as separate C functions (resegmentation, not drafting).
Wave S73m_1 banked 9 of 9 drafts (2,413 ins). Cookbook entries written this morning
cracked functions this afternoon; two of mine were refuted by later MATCHes and rewritten.
StreamLoadStateMachine (459 ins) byte-identical. That completes wave S73m_1: 9 of 9
drafts banked, 2,413 instructions.
gate_main defect this exposed, twice in one gate: resolve_conflicts scanned every 'extern'
line with no notion of the preprocessor, so a declaration parked in the DEAD half of an
'#ifdef NON_MATCHING / #else / #endif' pair read as a live constraint. It is never
compiled and constrains nothing. func_80018714 and func_800377D8 each carry a stale
'(void)' declaration in a dead branch while the LIVE definition takes a pointer and a u8
respectively; the first mis-blamed a draft, the second got a byte-verified draft DROPPED.
live_text() now blanks those branches before the scan.
The detour is instructive: I 'fixed' the draft twice to satisfy a constraint that did not
exist, and each fix made it worse — the draft's original (u8) declaration was correct all
along, because it matched the LIVE definition. Read which branch a declaration lives in
before believing it.
Both needed the §376 recovery in the DRAFT — adopt the TU's spelling for a symbol the
draft also declares:
* CdReadSectorReadyCB dropped its own 'extern void func_800599B8(void *rect, ...)';
the TU declares it (SpadRect_800184F0 *) at src/800.c:5480, above the insertion point.
* func_80035C4C adopted 'extern void func_8003D650(int,int,int)' — no caller anywhere
uses the return value, so the s32-vs-void difference was free to give up.
Also corrects src/800.c's dead-branch 'extern void func_80018714(void);' to '(void *)'.
That declaration lives inside #ifdef NON_MATCHING and is never compiled, but gate_main's
DECL scan has no notion of preprocessor guards and read it as a live conflict. The live
K&R definition at :5576 takes void *, so the correction makes the dead copy agree with
reality as well as clearing the false conflict.
Prepares the S73 wave's 9 byte-verified drafts for gating. Five definitions have
promotion-safe params so the declaration becomes K&R no-prototype — which also keeps
func_8003388C's 'Ent388C *' typedef out of scope at the declaration site, where it is not
yet defined. CdReadSectorReadyCB's u8 is NARROW so no-proto is unsafe (§17-stop); it gets
the exact prototype, safe because that symbol is only ever passed BY ADDRESS.
Verified BYTE-IDENTICAL with no draft substituted, via a DIRECT extract+build with the
binary deleted first — NOT via gate_main --assert-baseline, whose first action is
'git checkout -- src/*.c'. I used that first and it silently reverted these very edits,
then reported GREEN for a tree that no longer contained them: a verification of the
wrong thing. Same hazard as the two banks lost this morning, from the other direction.
Six of the twelve were found by checking every draft systematically rather than trusting
the agents' notes; two were never reported.
I wrote §430 this morning from a NEAR agent's report: 'a source goto into a loop kills
loop.c's invariant hoisting, so duplicate the statements per arm instead.' The MATCH on
CdReadSectorReadyCB (424/424) refutes it. The goto is what the original source had —
writing it took the residual 318 -> 28 instantly with length exact — and the lost hoist
is REPAIRABLE by hand-hoisting the constants into pre-loop locals (cse cannot fold them
back because MIPS bne/sb need registers): 28 -> 13. Declaration order matters.
The corrected law is better than the guess: a disabled optimizer pass is a job you can
take over, not a wall.
The general lesson, and it is the second instance today: a law derived from a NEAR is a
hypothesis about why something did NOT work; a law derived from a MATCH is evidence about
what does. §428a needed the same correction this morning.
Also banks two more laws this function paid for: cc1 -df's ';; regs to allocate' is a
free allocno-priority oracle (q 10refs/33live beat i 7/24 for $s2; six reshapes failed,
§17 merge + a register pin fixed it), and a stale card tu= cost the last 6 instructions
(func_80018714 is K&R 'void *', not '(void)').
StreamLoadStateMachine (MATCH 459/459) settled the general form of the law S72 found by
refutation. 'return 0' keeps the hard-$v0 set live inside that arm and EXCLUDES $v0 from
the allocator there; 'break' to a shared post-switch return frees it. Case 11 needs
return 0, every other zero-arm needs break — one dial, eleven positions, correct setting
is per-arm not global. func_80035C4C is the same pattern from the other side.
Completes the ladder: §3-B (fold returns) is the default because it frees the register,
§428a explains why the freed resource resolves coupled residuals, and the dial is how you
put the pin back where one arm needs it.
The previous commit's cookbook change landed but the exclude entry did not: the guard was
`assert 'SaveLoadRoutine' not in t` over the whole file, and that string already appeared
inside func_8002B0B4's WALL note. Fourth instance today of matching PROSE as if it were
structure. Now compares against parsed ENTRIES, not a substring.
SaveLoadRoutine (1139 ins) and func_8002B0B4 (76) are ONE 0x40 frame split across two
symbols, byte-verified: func_8002B0B4's jtbl_80072E44 points at SaveLoadRoutine and at
labels INSIDE its body, and SaveLoadRoutine has no prologue while owning the epilogue.
gcc-2.7.2 has no sibcall/tail-merge pass, so any C body for either gains a synthesized
prologue/epilogue the target lacks.
An agent reported SaveLoadRoutine as MATCH closeness 0; its own note says 'NOT a C
decompile' — it wrapped verbatim asm. gate_main would refuse it (contains its own
INCLUDE_ASM). NOT counted as a bank. Both now excluded.
This shrinks main's honest matchable frontier by 1,215 instructions (11%). The real fix
is a RESEGMENTATION merging the two symbols, not a draft.
Adds the 3-step frame check to run BEFORE drafting anything large; not running it cost
70k + 134k tokens this session. Also notes that the agent invented its §265 citation
while reaching a correct conclusion (R14: check both).
Measured across one wave: 4 of 5 consecutive main MATCHes turned on case source order
or the .rodata table. func_800316F8's .text was ALREADY exact and it still could not
bank — 18 bytes, all table. gcc emits case BODIES in source order while entry i points
at case i, so value and order are independent and only ORDER is pinned by .text, which
is the only thing match_one compares (§405-A).
Records the method every agent converged on independently: read the table order from the
.s, write bodies in that order, set values to the inverse permutation, then verify table
entries / reloc symbols / internal j destinations by hand before reporting. Pairs with
§427's TABLE REJECT verdict, which names the same class from the gate side.
From main/func_8002DC68 (MATCH 198/198). The target masks one value twice (a compare,
plus a second andi that reorg steals for a beqz delay slot). Written as param_2 & 0x7F on
both sides, cse merges the two (and:SI) and the delay slot comes out EMPTY. Spelling ONE
as (param_2 << 25) >> 25 hides it from cse — different RTX — and combine's
simplify_shift_const folds it back to andi. Two masks in the RTL, one instruction each
out. Byte-verified on either side.
The inverse of the usual advice: normally you make two expressions identical so cse
merges them; here you make them different to cse and identical to combine, exploiting
pass order. Any x & ((1<<n)-1) has a shift-pair twin with this property.
My sweep for '§179-C documented walls' grepped raw text, so it matched an INCLUDE_ASM
line quoted inside a 'BANKING: this block REPLACES the line ...' comment. corpus.stubs
said banked, my grep said stubbed, and corpus was right.
Third instance today of one bug class — reading PROSE as CODE. The other two were
split_src_region.item_name matching a parenthesised token inside a comment, and matching
a leading extern declaration as the definition. The exclude_audit caught this one
immediately by flagging my own addition as STALE.
R45 — never draw a card the pipeline cannot bank. src/800_b.c carries the explanation
directly above func_8002B0B4's stub (no epilogue; every exit is a raw j/jr into labels
inside SaveLoadRoutine's body, so gcc-2.7.2 always synthesizes an epilogue the target
lacks), and the wave drew it anyway: 70k tokens and 100s for an agent to re-derive that
paragraph and hand back the stub verbatim, reported as MATCH closeness 0.
A draft that IS its own INCLUDE_ASM is the silent-no-op class gate_main already refuses,
so nothing would have banked — but the agent slot was spent. Swept main for the class:
exactly 2 such stubs, both now excluded.
jr_isolate has been unusable since Phase 26 — its own docstring says "BLOCKED on
split_src_region". Five distinct defects, each found only after fixing the one above it:
1. split_src_region demanded an address for EVERY top-level item, but an overlay .c is
full of address-less constructs (hoisted typedef blocks, per-function extern runs,
comment banners). coalesce() now merges an address-less run FORWARD into the item
below it — they are a preamble belonging to that function, which is §431's model.
2. coalesce re-derived the name from the MERGED text, so item_name matched the preamble
instead of the function. It now carries (addr, name, text) captured before the merge.
3. item_name scanned COMMENTS as if they were code: a comment containing any
parenthesised token won over the real definition below it.
4. item_name matched a leading "extern void (*D_x[])(void);" and returned the name
"void" — the §192 class, which gate_main.sym_of fixed for itself and this tool never
got. A real function was then treated as a preamble and merged into its neighbour,
leaving its body inside another item while its own stub survived: 26 duplicate
symbols in one overlay. It now anchors on a DEFINITION (ends in an open brace, not a
semicolon) and refuses type keywords as names.
5. That definition anchor required column 0, so an INDENTED top-level body was invisible.
Also: jr_isolate's idempotency check keyed on the CONFIG, which it writes FIRST, so any
failure in between left a half-applied tree the tool believed was finished. It now
requires the source file too and refuses with recovery instructions. And inject accepts
"already present and textually IDENTICAL" — splat emits an empty function as C, not as a
stub — while still failing hard when the destination defines it DIFFERENTLY.
Progress on ov_SC02_005: trim went 78 kept / 231 moved -> 89 / 255; duplicate symbols
26 -> 0; the chain now runs to completion (rc=0).
NOT DONE: the object still fails to assemble on a remaining duplicate-definition class.
Tree restored, ov_SC02_005 BYTE-IDENTICAL.
My own regression from the same session: exclude_audit.parse now returns 4-tuples (it
carries the WALL pin and each entry's note), and draw_waves built `skip` straight from
them, so every membership test against a 2-tuple missed and the exclude list had no
effect at all — while the run reported success.
Caught by MEASURING the pool rather than trusting the run: it came back 88 non-main + 45
main = the full frontier, when a 25-entry list should have reduced it. Now 69 and 41,
which reconciles exactly (88 - 16 carve-blocked - 3 non-main walls; 45 - 4 open main
walls, PopMatrix/PushMatrix being linked and already refused).
The silently-narrowed-scope shape again, and the third time this session that counting
the RESULT rather than trusting the REPORT is what caught it.
Found by checking readiness rather than asserting it: S71's two PROVEN walls
(ov_SC03_105:func_801834A4, ov_SC06_022:func_8017DF28) were NOT in the canonical list —
they lived in a separate .run/S71_walls_found.txt the regeneration never saw. Drawing
would have spent agents re-proving them (playbook §1b: a full agent run each time).
Merging them in exposed a second defect: a WALL has no jump table, so the DERIVED logic
would classify it RE-PROBE and drop it. in the input is now read as a PIN that
survives regeneration, and the entry's ORIGINAL note is carried through — a wall's value
is its refutation list, and replacing that with boilerplate turns evidence into a bare
'do not try'. Round-trip verified idempotent: 9 walls survive a second pass unchanged.
Merged 7 walls ledgers (S67/S68/S68_332/S69/S70/S71/S71_found) into
config/wave_exclude.txt: 25 entries = 16 CARVE-BLOCKED (derived) + 9 WALL (curated).
The audit found 8 of the merged walls already BANKED — a wall that got matched is no
longer a wall.
DELIBERATELY NOT merged: .run/t3wall_list.txt, 99 BARE function names with no binary.
R48 — the same name is a different function in another overlay, so a bare-name exclude
over-excludes silently fleet-wide.
88 of 107 entries were stale one day after the list was written; 46 of them were
12,750 instructions of open drawable work including SaveLoadRoutine. Canonical list is
now config/wave_exclude.txt (19 entries), and draw_waves --exclude-file audits it as a
prerequisite.
There were NINE session-snapshot copies under .run/ and no way to tell which was
current — the accumulation smell behind the whole staleness problem. This is the one,
it is tracked, and it is regenerated rather than hand-edited.
.run/ is gitignored scratch, which is the wrong home for it: CARVE-BLOCKED entries are
derived and vanish when the subseg is split, but WALL entries are CURATED and cannot be
re-derived — that is precisely why the file needs to be tracked.