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.
An exclude list records what the TOOLING could not do, then gets treated as a property of
the FUNCTIONS. Nothing re-examined it, so every tool fix left behind a population that is
now tractable and still marked impossible — invisible, because the draw filters it out
before anything measures it.
MEASURED one day after .run/S71_exclude.txt was written: 88 of its 107 entries were
stale — 28 already banked, 14 linked PsyQ symbols that were never targets, and 46 whose
blocker had since been fixed. Those 46 are 12,750 instructions of open, drawable work
including main:SaveLoadRoutine (1,165), the largest function left in main.
* tools/exclude_audit.py (NEW) — classifies each entry by its CURRENT blocker
(BANKED / LINKED / RE-PROBE / CARVE-BLOCKED / WALL), regenerates keeping only the
still-valid classes, and --assert-fresh exits 3 on staleness.
* draw_waves --exclude-file — runs that audit and REFUSES to draw on a stale list, naming
the counts and the regenerate command. --exclude-stale-ok still draws but prints what it
ignores: skipping is possible, never silent. Also fixes the old --exclude parsing, which
could not survive a '#' comment.
* .run/S72_exclude.txt — the regenerated list: 19 entries (16 CARVE-BLOCKED + 3 WALL),
each carrying its reason, down from 107.
Verified in all three directions: stale refuses rc=1, fresh proceeds rc=0, override
proceeds and announces. The parser's own report-don't-drop design caught a bug I
introduced in it (comma-splitting before comment-stripping).
Informational only while 4 known violations exist; a permanently-red gate trains people
to ignore it (R54). When the last overlay is split, drop the '|| echo' so a regression
fails the gate.
18% of the non-main frontier, all already in the exclude list as if unmatchable rather
than 'needs a subseg split'. Plus: 28 of that list's 107 entries are already banked, so
regenerate it before the next draw.
tools-health --check caught it stale (1,099 sections). My own bookkeeping — the index is
DERIVED and self-asserts coverage (R33/R32), which is exactly why the gate found it and I
did not.
A code object contributes exactly ONE contiguous .rodata run, so a subseg owning raw
jump tables in >=2 non-adjacent island spans makes every switch function outside the one
carveable span unbankable at any effort. main sat in that state from Phase 7 to Phase 31
and eleven functions were written off as 'PROVEN gate-rejects' because of it. The
evidence is derivable from the raw image on day one; nothing was comparing it.
FIRST FLEET RUN: 209/213 OK, 4 overlays flagged — ov_SC01_084, ov_SC02_005, ov_SC02_011,
ov_SC03_105 — holding 16 open functions / 3,613 instructions (18% of the non-main
frontier). All 16 were already in the S71 exclude list, i.e. recorded as if unmatchable
rather than as 'needs a subseg split'. 3.7s fleet-wide.
Self-test covers all three directions: fires on main's pre-S72 island (fed
synthetically, because the real tree no longer holds that state), stays silent on main
today, and does not over-fire on a one-span subseg. Linked-library subsegs are excluded
on principle — their code comes from a .a so cc1 emits no table for them; without that
filter main reports NEEDS SPLIT on libgs6, which the self-test caught.
Wired into make tools-health. accelerators #20 gains the when-to-split rule: split where
the BUILD forces a boundary (decidable at 0% matched), at the span-owner boundaries and
nowhere else, never on TU archaeology.
The carve + the src/800.c split at the original TU boundaries. 7 of the 14 banks were
span B/C — impossible before the split. Next session starts with 11 functions /
4,479 instructions that are now merely undrafted rather than unbankable.
Found P31 S72; COULD have been found 2026-06-15 (Phase 7, commit:0025), which wrote the
island's contents by hand and named loadDestPtrTable as the divider. The signal needs no
matching progress — it is a property of the retail image.
The number that matters is the cost curve: at Phase 6 src/800.c had 13 externs and 0
typedefs and the split was a yaml edit; at S72 it had 2,378 externs and 175 typedefs and
cost 57 crossing declarations, a shared header and 4 stale consumers. Plus a session of
wrong conclusions (11 'PROVEN gate-rejects', 10 of which banked once the carve existed).
PREREQ recorded honestly: the binding constraint came from the OVERLAY work two phases
later, so this is knowledge that never got carried back to main — not carelessness.
General principle: segmentation is the exception to probe-before-investing. When a
decision is evidenced at t=0, cheap now, and strictly more expensive later, make it early
even though its payoff is unproven.
The split created two new TUs and a shared header; four consumers still described main's
game code as one file:
* tools/reconcile_slate.py — HARDCODED open('src/800.c'), so after the split it saw a
THIRD of main's typedefs while reporting success (silently-narrowed scope, R32).
Measured: 133 visible before the fix, 187 after, 0 lost. Now globs
corpus.src_files('main') + src/800_shared.h, so a future split is already handled.
* docs/wave-playbook.md 1c — still said spans B/C/D were NOT drawable and that drawing
one is an R45 violation. That is now false and would have STOPPED a future session
from drawing the very targets this work unlocked.
* cookbook §426 — its 'the remaining spans need src/800.c split' paragraph now records
that it was done the same session, and points at §431 for the method.
* config/dedup.us.yaml + src/shared/clearTbl40.h — both said dedup group I0 is
instantiated 'at both sites in src/800.c'; both sites are above 0x80035270 and are now
in src/800_c.c.
Byte-neutral: dedup.us.yaml parses, gate_main --assert-baseline BYTE-IDENTICAL.
SETUP.md gains a row describing the layout and the rule it implies: never hardcode
src/800.c, glob corpus.src_files('main').
I wrote the rule for declaration edits and then lost two byte-proven banks to the same
mechanism an hour later. gate_main's opening 'git checkout -- src/*.c' destroys anything
uncommitted in src/, banks included. R42 is 'commit before the next command that can
touch src/', not 'commit at a good stopping point' — and count banks from the SOURCE,
which is the only oracle that caught it.
Both banked byte-identical earlier this session ('BANKED 2 of 3'), then sat UNCOMMITTED
while my very next action was another gate. gate_main.try_batch's first step is
`git checkout -- src/*.c`, which reverted them; the following commit captured only the
third function. I reported 14 banks; the source said 12.
Caught by counting banks from the SOURCE (the INCLUDE_ASM stub's absence) rather than
from my own account of what I had done — the oracle the project already mandates.
I had written this exact hazard into cookbook §431 an hour earlier, for DECLARATION
edits, and did not apply the same reasoning to BANKS. R42 is not 'commit at a good
stopping point', it is 'commit before the next command that can touch src/'.
Quoted '2,318 scattered externs' as measured-not-guessed. It was measured, and it
measured the wrong quantity: what a split costs is declarations used OUTSIDE their
region (57 of 1,247), not the total. R41's denominator discipline applied to an EFFORT
estimate. Also records the correction to the '1998' premise: the spans prove at least
three TU boundaries, not that the devs' files were exactly these three.
Where to split: the jtbl spans (tables pack tight within a TU, separated across TUs), and
that is also the MINIMUM — a TU with no switch emits no table and is invisible, so what
you recover is a lower bound on the original structure, not the structure.
What crosses: ask the compiler. 2,318 externs is the scary number and the wrong one; only
57 of 1,247 declared names cross a boundary, 19 of them typedefs with one definition each
and zero shape conflicts. Fix TYPES first — a missing typedef cascades into dozens of
parse errors that all evaporate at once.
Plus the general defect it exposed (a typedef stripper must read the destination's
includes) and the operational rule it cost twice (gate_main reverts src/*.c first, so
commit alignment edits before gating).
Its blocker was an extern the DRAFT carried for a different symbol
(func_8004355C declared (s32, void*) against the TU's (s32, u8*)). Adopting the TU's
spelling verbatim — the documented §376 recovery — banked it byte-identical in 10s.
Only func_800316F8 of the eleven remains, and it is a TABLE REJECT: .text
byte-identical, 18 bytes wrong in its own jump table (§405-A).
func_800316F8's .text was BYTE-IDENTICAL and all 18 differing bytes were its own jump
table; the tool called it a PLUMBING REJECT and routed it to the §376 declaration chain,
advice that would never have fixed it. classify() now separates a .rodata-only
divergence and names it §405-A: match_one compares .text ONLY, so a draft sits at
closeness 0 while emitting a wrong table — gcc emits case BODIES in source order while
entry i points at case i, so case value and case order are independent and only the
order is pinned by .text.
Attribution reads one symbol LOW for a cc1-emitted table (once the function is C its
table is a $L label, not a jtbl_ data symbol, so the bytes land in the PRECEDING table's
extent) — the OBJECT name is what identifies it, not the symbol name. Shared by
gate_main so both report the same four verdicts.
Controls: self-test PASS, green build IDENTICAL, and the known func_800316F8 case now
classifies TABLE REJECT.
Byte-identical. 13 main functions banked this session.
func_800316F8 rejected with a precise, new-class verdict: its .text is BYTE-IDENTICAL
and all 18 differing bytes are its own jump table at 0x800730C4 (attributed to the
preceding jtbl_800730AC because the table is now a cc1 $L label, not a data symbol).
That is §405-A in the flesh — match_one compares .text ONLY, so a draft can sit at
closeness 0 while emitting a wrong table. Not a body reject and not plumbing either.
Each of these five had a TU forward declaration that contradicted the real signature,
which is what made gate_main drop their byte-correct drafts:
func_800316F8 void f(void*) -> void f(s32) + cast at the one call
func_8003602C void f(void) -> void f(s32) (use is address-taken)
func_80036260 void f(void) -> int f(void) (use is address-taken)
func_80038FFC void f(u8**) -> s32 f(s32*) + cast at the one call
func_80039C70 void f(void*,s16,u8) -> void f(u8*,s16,s16)
Verified byte-identical with NO draft substituted, so any later gate failure is
attributable to the draft and not to this edit.
OPERATIONAL NOTE: gate_main's first action is , so an
UNCOMMITTED declaration edit is silently discarded and the gate then judges the drafts
against the old declarations. Commit alignment work before gating (R42's shape, seen
from the tool's side).
func_8002EED8 · func_8002F248 · func_80031988 — byte-identical, the first banks that
span B's carve made possible.
gate_main defect the split exposed: defs_above scans the destination .c ALONE, so a
typedef the TU gets through #include is invisible to strip_dup_typedefs and every draft
carrying its own copy dies with 'redefinition of X'. Latent until src/800.c's split moved
19 shared typedefs into src/800_shared.h, at which point func_80031988 — byte-correct,
and one of the eleven — failed to compile for that reason alone. header_defs() now walks
the destination file's quoted includes transitively and seeds defs_above with what they
provide, so an identical copy is stripped and a different shape is renamed, exactly as
for in-file definitions.
func_80031988 had TWO stacked blockers: this one, and the struct-tag false conflict in
typesig fixed earlier today. Neither was a property of the function.
BYTE-IDENTICAL with NO function banked (gate_main --assert-baseline, clean rebuild),
which is the whole point: the structure lands first and proves neutral, then drafts bank
against it.
One code object contributes exactly ONE contiguous .rodata run, and 800.o's is span A,
so spans B and C each needed their own object:
800 vram 0x800123F0-0x8002B0B4 -> .rodata span A (0x80072A38-0x80072C70)
800_b vram 0x8002B0B4-0x80035270 -> .rodata span B (0x80072E44-0x80073140)
800_c vram 0x80035270-0x8003A444 -> .rodata span C (0x800732A0-0x8007344C)
The span owners' address ranges are disjoint and ordered — tables pack tight WITHIN a
TU and are separated by other data ACROSS TUs — so these are (at least some of) the
original translation-unit boundaries. Splitting here is both the fix and the minimum;
any extra split would be speculation.
main's island is now a 7-piece data->rodata sandwich, so ld_interleave moves from
--front/--tail to --order.
THE SPLIT WAS CHEAP, AND MY FIRST ESTIMATE WAS WRONG. I costed it at '2,318 scattered
extern lines' — that is the TOTAL; what matters is how many CROSS a boundary, and that
is 57 of 1,247 declared names (4.6%), of which 19 are typedefs with exactly one
definition each and zero shape conflicts. Zero file-local statics. src/800_shared.h
carries exactly those, derived from the COMPILER's own errors rather than a regex model
of C (R33), and each typedef was MOVED, never copied.
Unlocks 17 functions / 4,471 instructions = 39% of what is left in main, incl.
SaveLoadRoutine (1139) and func_8003388C (663).
The 11 'PROVEN gate-rejects' were one missing rodata carve, not bad bodies. Next
session starts at the span B/C carve: 18 jtbl functions left in main's frontier holding
most of its remaining instruction mass, blocked on splitting src/800.c at 0x8002B0B4
and 0x80035270 — the original TU boundaries the jtbl spans reveal.
Counterweight to §3-B, with a precise discriminator. When two arms converge on a shared
block, that block is usually a late cross_jump merge of per-arm DUPLICATED statements
(§298). Spelling it as a real goto is not equivalent when the label sits INSIDE a loop:
the goto becomes a jump into the loop body, jump.c's mark_loop_jump marks it
loop_invalid, cc1 -dL prints 'Loop at N ignored due to multiple entry points', and
loop.c silently drops invariant hoisting — measured: the 1/0x80 constant hoist into
$a0/$a1 vanished, 2 insns plus a spurious andi.
Discriminator: shared tail outside every loop -> fold it (§3-B, and you may free a hard
ABI register). Shared tail inside a loop body -> duplicate per arm and let cross_jump
merge. A -dL line is a free oracle for this.
Also records CdReadSectorReadyCB's three remaining residual clusters as pack fuel so the
next attempt starts from the draft, not from the .s.
CdReadStateMachine (385 ins) · func_80024448 (362) · func_80026D64 (189) ·
func_8001B0D4 (86). One clean rebuild, 10.8 s, 4 of 4 accepted.
Wave shape: 5 targets, one agent per workflow, 5 concurrent. 4 MATCH / 1 NEAR.
Every agent verified its jump table and reloc stream past match_one's .text-only blind
spot (§405-A) because the packs carried the §426 carve note.
Three new laws banked from their notes: §428 (zero-byte cross-jump barrier), §429
(every held pointer needs its own local), §428a (two residuals moving in opposite
directions share one starved resource — which refuted my own prediction).
func_80024448 was recovered from disk after its Fable agent was killed by a rate limit
and the workflow reported NO-DRAFT; match_one on that file: closeness 0 (playbook §5b).
I predicted §428's UID barrier would resolve func_8001B0D4's fence<->over-merge
coupling, reasoning that it changes no liveness. The escalation that tested it did not
use §428 at all. §3-B did it: seven in-block 'return 0;' -> 'goto L_ret0;' to one shared
tail removed the priority-1 hard-$v0 sets, freeing $v0 for the D_800747E4 reload and
$v1 for CdQueueBusy's result, AND fired all three cross-jumps (92->86). One edit, both
residuals.
The real law is the opposite of my framing: two residuals moving in opposite directions
under every lever are usually not in tension — they are two symptoms of ONE starved
resource, and every lever so far was paying for one with the other. Ask what they are
both competing for, and inventory the hard register sets the source forces. A repeated
'return <const>;' in switch arms is the commonest way to pin $v0 many times over.
The wrong prediction is kept in the section as the refutation (R14).