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).
From main/CdReadStateMachine (MATCH 385/385, opus). One root: gcc-2.7.2 canonicalises
(mem (reg)) back to a symbol when the pseudo has a single reachable set, so reusing a
pointer local changes the addressing mode downstream. p[-0x10] re-folds to
lui %hi(sym-0x10) unless the offset pointer gets its own single-set local; and a
multi-set pseudo defeats the canonicalisation for every use, costing a load-delay nop.
Same mechanism as §421 read from the source side.
Also confirms the §333 frame dial and the merged-tail label pin (gcc's cross_jump picks
the other end of a merge than you expect) as the cheap alternative to a §5a fence.
A volatile __asm__ barrier is a scheduling AND allocation event, so on a function whose
residual is allocation-shaped every fence that kills the double-hop re-enables a
cross-jump over-merge. Two residuals in tension, which is why ~20 variants across two
attempts never converged. §428's UID-based barrier is the predicted resolution because
it changes no liveness; the escalation now running is the test.
Flagged explicitly as one agent's report, not a byte-proof (R14).
Sharpens §5a/§336, supplies the missing precondition to §162. Writing the cursor
advance inside each switch arm instead of a shared temp makes the converging addu a
label CREATED by cross_jump (get_label_before), so its INSN_UID >= max_uid and
jump.c:1988's guard stops the minimum=2 jump-to-jump search from ever running — only
minimum=1 survives, which is exactly the target's single merge. Killed a -23
LENGTH-DRIFT (5 spurious tail merges) in one edit, with no volatile asm.
Found by the S72 main wave on func_80026D64 (MATCH in 2 compiles). The agent also
verified the .rodata table against jtbl_80072BFC past match_one's .text-only blind
spot — because the pack carried the §426 carve note telling it to.
A wave verdict is a claim about what the AGENTS returned, not what is on disk.
claude_wave_draft.js has no filesystem access, so its .catch() reports NO-DRAFT for
any agent that dies — including one that already wrote a finished body.
S72: a Fable agent on main/func_80024448 spent 133k tokens / 17 tool calls / 10 min,
wrote its draft, then died on 'You've reached your Fable limit'. The workflow returned
NO-DRAFT with an empty draft_path; match_one on that same file returns
{status: match, closeness: 0, nins: 362}. A complete byte-perfect body reported as no
work at all.
typesig() keeps only tokens in TYPES, which DISCARDS every typedef name — so
'Ent30D80 *' and 'Rec14 *' both reduce to '*' and the model has always been blind to
what a pointer points at. But 'struct' was in TYPES, so 'struct Ent30D80 *' reduced to
'struct*' and conflicted with 'Ent30D80 *', its own typedef.
src/800.c declares func_80031988 BOTH ways and compiles today — gcc, the arbiter,
agrees they are one type — yet the checker DROPPED the byte-verified draft, and S71
recorded it among the eleven 'PROVEN gate-rejects'. Dropping struct/union/enum from
the param token set loosens nothing the typedef path had not already loosened.
R39 control over the already-succeeded population (gate_main.typesig is imported by
pregate_check and four other tools, so this reaches overlay slates too): 452 drafts
across 54 binaries, 178 drops before / 177 after — ZERO new refusals, exactly one
removed: main:func_80031988.
§426 — main's switch functions were never a codegen wall: one .rodata carve had been
missing since Phase 7, so a drafted switch double-emitted its jump table (+28/+52/+76/+84
image growth, 238 symbols shifted, first mover jtbl_80072A4C every time). Includes the
derived name-address overlay that names a layout shift with no reference build, the span
table, and why spans B-D need src/800.c split at the original TU boundaries the spans reveal.
§427 — a hash is a correctness oracle with zero diagnostic content; preserve the red
artifact before anything rebuilds over it, attribute per byte, and negative-control the
localizer in both directions.
playbook 1c — which main jtbl functions are drawable (R45), and the gate note: main is not
in the parallel lane, read the BODY/PLUMBING/MIXED verdict before recording a main reject.
The idempotency check was 'heading present -> skip', and claude_wave_packs.py calls
this tool at pack-build time, so EVERY pack that had any history was sealed against
evidence recorded later. Measured on wave S72m_1: 4 of 5 targets took a
freshly-recorded carve finding and the fifth silently did not, because it alone
carried a prior note. Now idempotent by REPLACEMENT — the generated block is always
the tail of the file, so truncate at the heading and re-render the current row set.
Re-running is byte-identical (verified) and never duplicates the heading.
Also records the S72 main carve unlock in .run/journal_notes_local.jsonl so it
reaches every future main switch-function pack through the same one code path.
main's gate could only ever say "got X want Y". S71 read 7 such verdicts as body
rejects and recorded 11 functions as "PROVEN gate-rejects, §376 in its purest form".
They are not: all 11 are switch functions, and the blocker is that main has had
exactly ONE rodata carve since Phase 7 (LZSS's jtbl_80072A38). Every other main jump
table stayed raw in the tail data, so a drafted switch DOUBLE-EMITTED its table, the
image grew (+28/+52/+76/+84 measured), and all 238 symbols above 0x80072A4C shifted.
* tools/main_diff_locate.py (NEW) — turns a red image into a named list of divergent
symbols via the linker map; per-byte attribution, self-test flips a byte at a known
address and asserts the containing symbol (plus the identical-pair direction).
* gate_main.py — PRESERVES the red image + map before the R40 baseline control
rebuilds over it, and auto-localizes: BODY REJECT vs PLUMBING REJECT vs MIXED. Also
-j on the build (was single-threaded) and the §376 drop list written to
.run/gate_main_dropped.json with the reconciliation chain.
* splat.us.exe.yaml — the .rodata carve extends from the LZSS table alone to the whole
contiguous game-jtbl span 0x80072A38-0x80072C70 (12 tables, one 800.o run).
Byte-neutral with no drafts substituted (probed first).
* jtbl_rodata_pads.py — --derive now works for main: one file-0-vram expression makes
both address->bytes and yaml-piece->address correct for the EXE's 0x800 header and
leaves flat overlays unchanged. Makefile arms it for BINARY=main.
Banked byte-identical: func_8001A114, func_8001AAD0, func_8001AF34 — three of the
eleven. 25 of main's 59 frontier functions (6,215 of 12,912 instructions) are in this
class; the remaining spans need src/800.c split at the TU boundaries the spans reveal.
Audit found real gaps rather than assumed coverage:
* SETUP.md (R21) had NONE of the five tools written this session. Added a table for
journal_notes / launch_check / gate_triage / restage_matching / weave_sweep, each with
when you need it, plus the two gating rules now enforced in code (parallel_gate refuses
main; gate_main refuses a no-op draft and counts banks from the source).
* wave-playbook: launch_check as step 4c (payloads go stale while gates run - 3 of 27
wave-2 targets were already banked) and gate_triage as step 6b with the measured
blocker census.
* decision-log (R31) held only the §406 pivot. Added the two strategic entries this
session actually turned on: gating main with a tool documented as unable to gate it
(false PASS, caught only by R22), and the drafting pool running dry while the lever
was an exclude list nobody re-probed after a tool fix.
* CURRENT_PHASE: the per-gate ledger for all 14 cycles plus the carve/rebase/main gates.
* Two memories: gate-main-only-with-gate-main, reprobe-exclude-lists-after-tool-fixes.
Four byte-verified findings from md_MAIN_003/func_800CF3E8 (467/469 ins), read off
-dS/-dR/-dl/-dr:
1. sched.c true_dependence guards on GET_MODE(mem) != QImode, so a `sb` store aliases
scalar global loads while sh/sw struct stores are exempt - that is what pins the
colour lbu after `sb 0xb` and gives it a mandatory filler slot. A property of the
store's MODE, not of what it points at.
2. An OT-index re-read must be a MEM_IN_STRUCT_P FIXED-address lhu to depend on its
store; expand's memory_address() forces the constant into a reg (la+lhu 0(reg))
unless you copy the whole struct into a 2-byte register local. This was the 468/469
wall for every prior attempt.
3. A `la` above an idx*24 chain needs a single-set base pseudo emitted before the
multiply: block-scope `{ Sprt24 *b_ = ARR; p = &b_[idx]; }`.
4. Recorded as NOT reachable from C: $v1-vs-$a0 for the last prim pointer is
local-alloc's pointer-vs-temp priority tie. Permuter or a §419 density manoeuvre.
Byte-proven on ov_SC07_006/func_801890FC (387 ins). For two stores at equal priority,
sched1's LUID tie picks the LAST statement's store first; regalloc then gives the other
load $v0, where it sits behind its own $v0 predecessors, and sched2 pins that chain last.
Each equal-priority pair therefore appears reversed: source FE;E-=0x40;A-=0xEE;88;8A;8C
emits as E,88,A,8C,8A.
This is why "statement order is inert" is such a common wrong conclusion here - the
mapping is not identity, so trying orders at random explores the wrong space. Compute the
intended emission order, then invert each equal-priority pair.
Companion (§193-E): the prior draft left no different-address store between `sh 0xA` and
its re-read, so cse forwarded it as `andi 0xffff`. A store to a different address between
the two re-seeds cse - §416's "re-read the store" seen from the failure side.
A §332 delay-slot wall the pinned triple cannot emit from C; banked the verbatim-asm way,
verified by one clean rebuild at 143dbb89f34491258bbc27810d0a12ec8b43a8dd. Bank confirmed
from the source (stub gone), not from the tool's own count.
Substituting such a draft puts the stub straight back: nothing changes, the clean build
is trivially byte-identical, and the function is reported banked while its stub is still
in src/. That is how func_8002B0B4 was counted in this morning's "BANKED 5 of 6" when
only 4 had applied.
Refused at slate load so it fires in every mode including dry run. Negative-controlled
both directions: the no-op slate is refused by name, a real verbatim draft still passes.
Scope measured before generalising: 5 of 2,749 stored drafts, all one of two functions -
rare, but silent, which is why it is a refusal and not a warning.
len(good) is "what we decided to keep", not "what was substituted". A draft whose stub
pattern does not match is a SILENT NO-OP: nothing changes, the build is trivially
byte-identical, the batch passes, and the function is reported banked while its
INCLUDE_ASM is still in src/.
Measured here: the bisect printed "BANKED 5 of 6" and func_8002B0B4's stub was still in
src/800.c - four real banks. The stub's absence is the bank oracle everywhere else in
this project; gate_main now uses it too, and names any accepted draft that never applied
instead of counting it.
The main lane, run the ONLY way main can be gated (§414): substitute the whole slate ->
make extract -> make build -> compare SHA1, with a bisect when the batch fails.
Baseline asserted green first (143dbb89...), batch of 6 failed, bisect isolated
func_8002C410 in 7 rebuilds, and the remaining 5 banked BYTE-IDENTICAL.
Four of the five are §265 verbatim-asm bodies for functions on the §332/§188 toolchain
wall list - the accepted route for a function the pinned triple cannot emit from C, the
same way func_800D0B1C banked overnight. Candidates came from scoring every stored draft
for main's 53 non-rejected open functions with match_one: 6 of 53 at closeness 0.
Drew, 2026-09-02: "use opus mainly and only escalate the difficult ones to fable".
The S69 table that set the old 150 line actually puts opus's cliff at ~350:
m1 opus 191-347 ins 10/15 MATCH 1,291 tok/matched-ins <-- best measured
m2 opus 347-670 ins 1/9 MATCH 7,158 <-- the cliff
So 150 was handing opus's STRONGEST band to Fable. Fable is now reserved for >340
instructions and for arm_from_history's compiler-internal residual signal (§413) at any
size. On the current pool that moves three functions back to opus while two stay on
Fable because their history names a scheduling/regalloc residual - escalation by
difficulty rather than length, which is the point.
Retries default back to opus: one failure is not evidence of a wall, and the history
signal lifts a target on its own if the notes justify it.
§422 (resident/func_800D06E8, 344 ins): mips.h has no PROMOTE_MODE, so a u8 local is
really QImode - `(u8)(c-3) < 2` gives widen_operand a paradoxical subreg (raw $a0) and
combine.c:9246 drops the truncation before the sltiu, while `(u32)(c-3)` re-widens and
loses it. And `s1 = (cmp) ^ 1` puts the comparison in the destination pseudo; the flag
needs its own temp.
§423 (ov_SC03_092/func_8017FA74): match_one closeness 0 + gate reject + "cause not
determined" has a checkable cause. harvest_verify splices the draft where the
INCLUDE_ASM stub was; a file-scope typedef the TU also defines (five lines below, here)
makes gcc-2.7.2 exit 33 on a C89 redefinition, the TU fails, the gate reverts, and
nothing names the typedef. Fix is block scope, proven with a three-way control. Mirror
of §409 law 2 - together they cover collisions above and below the splice point.
Tool trap: a scratch TU copy needs -Isrc/<overlay> or cpp exits 1 on the relative
engine_core.h include and EVERY variant passes identically.
Byte-proven on md_SC07_003/func_801A293C (313 ins, 6 -> 0 in 23 compiles). $t0 there is
reload's scratch (regalloc.md L80: first wholly-unused call-clobbered reg), not an
allocation: a launder output goes to $v0 via local-alloc, and any $8 pin evicts every
mfhi/reload to $t1 (measured 3x). The reach is §385's single-instruction non-volatile
asm with the bare symbol as an INPUT.
Also: spelling an addPrim RMW as a libgpu P_TAG 24-bit addr bitfield makes
store_fixed_bit_field mask the value first, hoisting 0xFFFFFF before 0xFF000000 and
closing both the lui/ori and lw-swap clusters at once (6 -> 3); and an in-place `and $s0`
needs `s0 = s0 & 0xFFFFFF` written out, not the compound form.
The law: before spending iterations on a register you cannot get, ask whether it belongs
to regalloc at all - reload runs after every dial C gives you.
aprop_symfix deduped its slate by BARE FUNCTION NAME, so a four-row slate for
func_8016AB6C across ov_SC03_107/ov_SC07_007/010/011 reported "1 drafts audited" - and
the three dropped rows each needed a DIFFERENT rebase, because each overlay has its own
target symbols. Same root as reloc_filter's binof and gate_lane's homonym staging: three
tools, one R48/§238 defect.
With all four visible, the structure is two uniform delta clusters of two, identical in
shape across all four overlays - one seed body's two data clusters each moving as a
block. STALE-DELTA only admits ONE cluster, so it refused all four as AMBIGUOUS. §420
records the safe generalisation (runs of constant delta, every run >= 2 members, D_
symbols only) and the verification step.
Rebased by hand under that rule, all four still MATCH at closeness 0, and the gate
banked 4/4 in 57 seconds with no drafting (commit:3629). Frontier 165, 45 banked.
Wave 3 drew 1 target - 0 left in pool. Of 174 open: 64 main, and of 110 non-main, 41
drafted this session, 68 excluded, 2 walls, ZERO undrawn. Re-probing the 68 with
jtbl_carve --probe found 17 now reporting `tail`, because tonight's jr_isolate_all
fixes changed their overlays. All 17 already had drafts; 10 scored closeness 0 with no
drafting. The gate banked 5, and they are exactly the five overlays jr-isolated tonight.
An exclude list is a snapshot of what the TOOLING could not do and goes stale the moment
the tooling improves - re-probe it after every tool fix.
Byte-proven on ov_SC01_000/func_8017DD04 (297 ins). Pinning the 0x80 constant to $7
loses its sched1 birthing boost, because $a3 and $a2 are ALSO set by the function's own
call-argument copies (reg_n_sets == 2) - a hard-register pin on an argument register is
unavailable to any function that passes arguments in it.
So win the ranking instead. local-alloc ranks by refs/live_length: 0x80 scored
13/319 = 4890 and took $6 (a pure $a2<->$a3 swap, 24 wrong). One six-input zero-byte
asm on mlo at the blk3/blk4 boundary buys 6 references, 5217 > 4890, mlo takes $6 and
0x80 falls to $7 with its boost intact. The boundary must be the one cut no hoisted
constant crosses, or you move the live lengths you are exploiting.
Measured inert: pinning mlo to $6 (cse never substitutes a hard reg for a bitfield
constant), and a dead hard-reg copy as a register suggestion (flow deletes it).
Two functions recorded as walls with their refutation lists rather than redrafted:
ov_SC03_105/func_801834A4 (loop.c movable ordering, closeness 6) and
ov_SC06_022/func_8017DF28 (expand_block_move's copy_addr_to_reg pseudo reused by cse,
closeness 2, seven levers measured inert). One MATCH blocked purely on carve state with
its exact prescription queued in .run/S71_carve_todo.txt.
Byte-proven on ov_SC04_016/func_8017DF8C (184 ins, 32 -> 0 in seven compiles).
1. Writing the stride as `off = j * 0x50` (a giv of biv j) rather than a second biv
fixes 14 preheader rows and 8 latch rows at once: loop.c chains givs LIFO
(loop.c:4421), so with `j = 0` spelled first the `move $s0,$zero` emits last and the
arg-3 giv lands in its natural record slot. When the PREHEADER ORDER is wrong, check
which variables are bivs and which are givs before touching anything else.
2. To emit la + addu + lh 0(reg) instead of a folded %hi/%lo(sym+K), hoist the table
pointer to a function-scope local set ONCE outside both loops. No LOG_LINK crosses
basic blocks so combine cannot fold it, and with every $s taken reload rematerialises
it from reg_equiv_constant at the use and deletes the init - so the hoist is free.
Spell the sum `idx + (s32)tbl`; the reverse costs one operand swap.
Measured inert here: §328's volatile cast on a reg+sym address.
Blocker census read off the 37 gate verdicts on disk: DIFF 18, CARVE 7, PARSE 3,
NO-DIAG 3, CONFLICT 2, ARITY 2, UNDEF 2. I had called carve the dominant remaining
class mid-session on the strength of the last two agents I'd read; it is not. What
remains is mostly genuine codegen, the opposite of the integration-dominated picture
this session opened with.
Also recorded rather than redrafted: ov_SC03_105/func_801834A4 as a proven loop.c
movable-ordering wall (closeness 6, two measured-inert levers), and
ov_SC01_004/func_8017EB30 as MATCH-279/279 blocked purely on §8e carve state.
Byte-proven on ov_SC03_013/func_8017E6F4 (182 ins). A pinned select arm expands as
ior+copy (two insns), so jump.c:728's `x=b; if(c) x=a;` collapse cannot fire. Unpinning
lets it fire - and then cse_end_of_basic_block's skip_blocks path walks between the two
D_80184D2C[idx] reads and CSEs the symbol_ref into $s3. The earlier agent read that as
"unpinning costs +2" and restored the pin, which re-blocked jump.c and hid the cse
behaviour: the function plateaued for three attempts on that misattribution.
The law: when removing a lever MOVES the residual instead of shrinking it, the new
residual is a different pass. Attribute it with a pass dump before re-adding the lever.
Four new byte-proven levers from the overnight lane, none previously in the cookbook:
re-read the store instead of passing the value (CSE store-forwarding), (&SYM)[3] vs a
pointer local as an ADDRESSING choice, one biv with +0/+2/+4 for combine_givs, and a
local's width choosing lh vs lhu+sll/sra.
Also recorded: the same-address twin hint was false three times tonight (ov_SC06_000,
ov_SC01_080, ov_SC03_030) while the same-TU neighbour was the real fuel in every case.
Three of the night's five post-limit MATCHes recovered a body off disk rather than
re-deriving it - func_80181A60 in 2 minutes instead of 16.
All five in-flight agents died on the 5-hour limit and returned NO-DRAFT; that is a
harness kill, not a verdict about the targets (R40), so they relaunch unchanged.
Gate 5 banked 2 (commit:3614). launch_check.py added after a stale card burned an agent.
gcc-2.7.2 pushdecl/different_binding_level: adding a file-scope extern for a callee that
the TU also declares at BLOCK scope merges those later externs into the global, which
then conflicts with the TU's own definition - and the error names the definition, not the
decl you added. Match the TU's binding LEVEL, not just its type. Byte-proven on
ov_SC04_011/func_80180B24 (215 ins), which also needed the house __asm__ alias for a
file-scope vs block-scope D_ spelling collision.
wave_args asserts a target is open AT DRAW TIME, then the payload sits on disk while
gates run. S71 launched ov_SC01_006/func_8017F9F8 from a payload built before the gate
that banked it; the agent spent a full run to report "STALE CARD - already banked
today", with no .s left to score against. Filtering the wave-2 payload found 3 such
targets of 27.
launch_check.py re-asks the same oracle everything else uses (a bank REMOVES the
INCLUDE_ASM stub, so corpus.stubs not containing the symbol IS the bank), either for one
target or by filtering a {wave,targets} payload in place. An unreadable binary is treated
as OPEN - a tool fault is not a verdict about the subject (R40).