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.
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).
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.
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.
§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.
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.
§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.
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).
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.
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.
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.
S71 ran main through parallel_gate, got "11 banked", committed it, and the R22
clean-fleet verify came back 212/213. main did not compile from clean; once the two
declaration conflicts were reconciled it built and was STILL not byte-identical. All 11
were then re-gated one at a time against a clean build — 11 of 11 REJECT.
The rule was already written down in ox_campaign.gate_main_batch: "main is gated by ONE
CLEAN REBUILD of the whole EXE, never incrementally … main's extract rewrites the linker
script, so an incremental main gate returns a FALSE DIFF." parallel_gate's worker IS
gate_stage, so it inherits that — and S58 recorded the false-DIFF direction while this is
the false-PASS one, which is worse: a false diff wastes drafts, a false pass commits wrong
bytes and reads green until the next clean fleet check (R53's signature — a failed build
leaves the previous object on disk and the SHA check downstream reads it).
Now a refusal naming tools/gate_main.py, not a docstring in the callee (R43).
Cookbook §414, including the two instrument errors made while recovering.
Measured on S71's own wave: wall-clock tracks iteration count, and iteration count
tracks the residual class, not size. A 26-instruction function took 18 min / 31 tool
calls (regalloc, finished NEAR); a 122-instruction one took 80 s / 10. The 20-33 min
runs were all compiler-internal residuals — scheduling ties, birthing boost, register
colouring, LUID order — where every hypothesis costs a compile-and-measure cycle.
arm_for keys on nins alone, so a 47-instruction regalloc wall could not be drawn at
the higher tier and nothing escalates mid-run. arm_from_history() now reads the
function's own journal notes at draw time and returns fable when they name one of
those classes; it never downgrades the size ladder's choice.
R39 control over 3,147 functions with history x 3 bands = 9,441 decisions:
4,020 upgrades (43%), 0 downgrades.
The control's FIRST form passed over an empty set — it keyed on journal rows carrying
a binary, and there are none: the agent verdict schema never had that field, so every
historical note is name-keyed and the same name is a different function in another
overlay (§238). claude_wave_draft.js's VERDICT now requires `binary`, so new rows are
exact. Cookbook §413.
tools/journal_notes.py mines the agent journals per (binary, fn) and appends a
PAST ATTEMPTS section to the pack; claude_wave_packs.py calls it automatically, so
it is the default rather than a step to remember. Idempotent, and R48-safe (a note
stamped with a different binary is never served — §238 homonyms).
Measured before adopting (S71 wave 1, 50 one-agent workflows over the 210-function
real frontier where every target had already refused an earlier wave):
* 38/39 MATCH at closeness 0 (97.4%) vs S70's 124/131 (94.7%) on an EASIER pool
* 29/39 agents cite a prior attempt as what they used
* 4/39 banked by RECOVERING a body that already matched, from a path a note named
* 11/39 matched on the first compile
The two costs it removes are re-testing a measured-inert lever (§406 lists twelve,
§407 fifteen, §410 four — each paid for by an agent and never seen again) and
re-deriving a body that already exists on disk.
Also: jr_isolate_all places file-local `static` definitions with the region that uses
them instead of refusing the whole file. A `static inline` helper (§82.1) has no
address by construction, which is not a defect; the R32 guard was refusing these and
blocking the isolate on 4 of the 6 overlays whose CARVE-REFUSED functions it is the
named remedy for. Two regions using one static is still a hard refusal (duplicating a
used static is a byte change, R43).
docs: cookbook §411, wave-playbook step 3b, accelerators entry.
* Every pack carried PAST ATTEMPTS ON THIS EXACT FUNCTION, mined per-function from the
historical agent journals (52 of 60 targets, 131 notes). Every landed agent returned
MATCH at closeness 0 on the hardest frontier we have.
* §409 — the wave and the nine laws it produced. Law 1: a relocation-stream
TRANSPOSITION is invisible to match_one, the permuter scorer and every similarity
tier (HI16/LO16 masking; the §195-D blind spot for a different reloc class), and it
retroactively explains "MATCH but the gate rejected it" verdicts.
* §410 — COPY THEN ACCUMULATE ON THE COPY: satisfies the $s2 in-place destination and
the sched1 birthing boost at once, with the agent's measured refutation list.
* `binof = {c["fn"]: c["binary"]}` was last-writer-wins, and `status`, `det` and `subof`
had the same shape — a draft of a name carried by two binaries was stamped with
whichever card came last and then reloc-checked against the OTHER binary's symbols.
* Resolve per draft instead: the shard's own target list first
(`.run/wave_<tag>_targets.<i>.json` = `targets[i::workers]`, each row carrying its
binary), a unique-name card second, a counted refusal when neither can answer (R43).
* R39 negative control over every historical wave: 42,655 drafts, 0 regressions,
2,317 (5.4%) previously mis-stamped; 2,107 homonym card names fleet-wide.
Intra-shard ambiguity: 0 of 50,684 (shard, name) pairs over 302,370 shard files.
docs: §408 — §406 refuted as a sweep (0 MATCH / 14 applied, 0 / 210). The 134-member
census counted main's 960 LINKED library stubs and matched a symmetric SHAPE; derived
from the mine-vs-target residual the addressable set is 15 / 210. Decision-log entry
records the pivot: 64 of 210 (30.5%) already match standalone, so the frontier's
largest lane is §376 integration, not codegen.
tools/weave_sweep.py — the derived-selector sweep (R32 coverage, R41 denominators,
--lever-all ablation control).
§405 — the generalisable residue of three waves, grouped by lever family:
A. match_one compares .text ONLY, so a switch's jump table is invisible to it —
a draft can score 110/110 with a PERMUTED table emitted as identity
(resident/func_800D02D0, byte-witnessed). Some historical 'MATCH but gate
rejected' verdicts were the ORACLE being wrong (R34 in our most-trusted tool).
B. the scheduler dials, incl. the birthing-boost re-tie's PLACEMENT rule (must be
a LATER basic block) and reorg's stop_search_p halting at any asm.
C. regalloc from C without pins: variable identity picks global- vs local-alloc;
a cross-arm join value loses first-fit and should be duplicated per arm for
cross_jump to refund; pass-through params reserve arg regs at zero cost.
D. integration is still the bottleneck — ~1/3 had byte-correct bodies blocked
only by declarations; the TU is the authority.
E. what the agents REFUTED: §137 invariance is false for CONFLICT-driven ties;
§153's 'cse2 puts it back' fails for the dead-def case; §257-8 volatility
polarity is per-site, not portable.
§406 — the prologue-weave class: 134 of 1,237 open stubs (11%) share the
sw / move , / sw shape, cause traced in cc1's .i.sched2 dump
(memrefs_conflict_p finds no dependence, potential_hazard picks sw $ra early), 12
variants measured inert, and ONE working lever (non-volatile memory-clobber asm
after the param copy). One lever x 134 known targets = a sweep, not an idiom.
§400 — a baseline check that conflates "absent everywhere" with "changed under
us" silently drops new files. The general law: when a comparison uses two
different sentinels for "nothing" ("" from a failed command, None from a missing
file), it reports a difference that does not exist — and in a GUARD, a phantom
difference becomes a refusal, which looks exactly like the guard working.
Corollary recorded in both §400 and the carve-state memory: "never blanket-add"
covers SHARED carve state (overlays.mk, splat yamls). It does NOT cover a carve's
own new per-binary source file, which is named by a committed yaml and whose 31
siblings are tracked — that one must be adopted with the bank that created it.
Docstring correction: parallel_gate does NOT use `git add -u src/` (that is
gate_stage's form); it adds exactly the adopted paths. My first diagnosis of this
bug blamed `-u` on the strength of that stale line and was WRONG — the cause was
the baseline comparison. Noted in the docstring so the next reader is not
misdirected the same way.
Caught by Drew asking whether the last waves were harvested. They were not: I
banked 1 of 5 (§398b) and left four lever sets in the notifications. Also found
two paid-for MATCHes that were never staged or gated.
(a) a fence BETWEEN two prologue loads, where source reorder does nothing —
the order is fixed before statement order matters (md_MAIN_013/func_800CB56C)
(b) SINK a call into BOTH arms and let cross_jump keep only the jal suffix;
88ins/close86 -> 92/13, then §3-T2 field order let each sh $zero fill an lhu
load-delay. Duplicate in source so the compiler merges, rather than writing
the merged form yourself (ov_SC07_001/func_8017EDC0)
(c) a $v0->$a0->$s3 DOUBLE COPY is a two-pseudo tell: SImode temp for the compare
+ separate HImode var for the tail (70->37); plus §195-N precondition 5 —
nesting `return 1` with ONE trailing `return 0` blocks jump.c's store-flag
transform so reorg fills both delay slots (18->0) (ov_SC02_017/func_8018347C)
(d) the re-tie as a BIV KILLER: a second set makes n_times_set>1 so loop.c
refuses the pseudo as a biv, killing the combined address giv. volatile was
worse, a dead read did nothing (ov_SC07_001/func_8017E4DC)
(d) makes THREE distinct uses of the zero-byte re-tie in one session — §380
un-hoists a move_movables invariant, §393 kills the scheduler's birthing boost,
§399d denies a biv. One line, three passes: when a single-set pseudo is being
treated specially, give it a second set.
Measured: 22 twin remaps gated as a batch -> 3 banked, 11 CC1-FAIL/PLUMBING, 8
DIFF. The eleven integration failures read 'syntax error before', 'undeclared',
'conflicting types', 'parse error' — the signature of a decl block written for
another TU.
family_remap rewrites the BODY correctly (per-overlay symbols, reloc targets) but
carries the source TU's typedefs/externs/callee prototypes verbatim into a
destination that already owns those names — §378c's fifth variant, at scale and by
construction. The 8 DIFFs are the h_norm class being 80%, not 100%.
Planning consequence (R41): the remap lane's realistic yield is ~15% straight
through and ~50% after the integration pass, NOT the 76-88% PURE-class rate.
Quote the straight-through number.
Tooling gap named: family_remap should emit the body with the DESTINATION TU's
decl environment; decl_prior already computes it for cards, and
cast_self_callers/fix_arity_callers already edit it.
Both rules were already written down (§384, §397) and both were violated anyway,
which is the argument for a tool: a habit you must remember at the moment you are
impatient is not a control.
tools/verify_binary.py — ALWAYS re-extracts before building, because a carve
rewrites splat inputs and a build over stale extract state produces a meaningless
SHA. S69 read three binaries as red on build-only checks; all three were
BYTE-IDENTICAL after extract+build, and two false reds cost legitimate work that
had to be restored (a 96-line match, and 23 declaration edits). --all-touched
sweeps everything with uncommitted src/ or config/ changes.
tools/twin_rescan.py — the twin oracle answers "is there a BANKED body like
this?", so an OPEN-OPEN cluster correctly reports "no banked twin" for every
member and that verdict is stale the instant one banks. Diffs the scan against
the previous snapshot so it reports what JUST became free, not the whole board,
with the ready-to-run family_remap command per row. Baseline: 318 open stubs, 37
already carry a banked twin at d<=5.
Memories added: rescan-twins-after-every-bank, check-against-a-known-true-case.
Measured the expensive way. A reach-6 cluster showed open-open, so seed_ref
correctly reported 'no banked twin' for all six. I cracked the exemplar (203k
tokens, five new levers) and then drafted four siblings at ~60k each — including
one that had already burned 257k plateauing at permuter-class NEAR.
They were EXACT clones. The agents' own diffs said so: 'label-stripped .s diff vs
the twin is EMPTY', 'an EXACT clone (asm diff = labels only)'. The moment the
exemplar banked, seed_ref returned it as a banked twin for every sibling, and
family_remap + the §378 chain banks them for ~0 tokens.
The law: a bank CHANGES THE TWIN GRAPH. The twin oracle answers 'is there a
BANKED body like this?', so its verdict for every sibling is stale the instant the
exemplar lands. crack-wave-sweep-map-regen applied one level down — the family map
is not the only stale artifact, and the twin oracle is the one the cards read.
Also: never draft two members of one cluster in parallel; if either cracks the
other is free.
§322b — the carve class is COMPLETABLE, and every worktree CARVE-REFUSED was an
instrument verdict (.run/sig.<b>.jsonl is gitignored, absent from worktrees, so
jr_inventory read every carve as UNOWNED). build_carve's refusal is EXACT, not
conservative — one object emits one contiguous .rodata — and the real fix
(isolate into its own subseg) already exists and harvest_verify already runs it.
Live census: 71 non-contiguous of 123 jtbl stubs; 21 of those are twins of
already-banked bodies (3,852 ins) free at ~25s each. End-to-end byte-proven in
23 seconds. Remaining blockers are 18 overlay_src_split plumbing defects (<=30
lines each) plus a jr_isolate_all port for main.
§332b — the §332 "walls" are a per-OBJECT assembler mode, not a C limit. A 3-line
maspsx reorder-passthrough + as -O2 is byte-INERT across the whole 800c3/800c2
objects and yields 0 diffs for SIX walls whose drafts already exist. That turns
"permanently unbankable" into a per-object Makefile switch and retires
oracle_reorder.py. Only 13 of the 15 listed walls are even reachable.
§378c — a FIFTH decl-blocker variant: the DRAFT redeclares a type/data/callee the
TU or a header already owns. Fix the draft to the TU's spelling (§367), never the
reverse. Two "integration-blocked" rows were phantoms, one of them my own
--any-proto pre-pass breaking a sibling TU (variant 4, second bite).
accelerators #19 — a verdict recorded inside an isolated environment describes the
ENVIRONMENT. Isolation exists so the worker sees less; every gitignored input is a
difference it cannot distinguish from a genuine rejection, and it writes that
difference down once per function. Negative-control the environment with a
known-good item; assert the worker's inputs; report a missing input as MISSING,
never as a verdict.
Correcting my own guidance from earlier today. §378 gave the self-caller chain;
three more variants appeared within hours and two of them BREAK the chain.
Variant 3 (NEW, byte-proven ov_SC04_018/func_8017F35C, banked): conflicting
RETURN type on a decl that is ALREADY no-proto, where the symbol is
ADDRESS-TAKEN rather than called. --any-proto has nothing to relax and
cast_self_callers has no call site to cast; --sync-decls ALONE fixes it, and is
safe precisely because an address-taken site has no arguments to convert.
Variant 4 (REFUTATION of what I wrote in the playbook this morning): "run the
same chain on the callee the diagnostic names" is wrong at scale. Applied to
func_8012AD44 in ov_SC07_000 it no-protoed 60 caller decls and the binary went
RED (265b24bb vs 9dbe4241); reverted via journal. The self case is safe because
step 2 casts the call sites so the decl change cannot alter argument conversion;
for a callee, cast_self_callers correctly refuses and the decl change runs
unprotected. It banked main/func_80021D38 only because that callee had ONE decl,
not sixty.
Rule added: never --any-proto a symbol whose call sites you are not also casting;
count the sites first. The chain is a DECISION TABLE, not a sequence to run
blindly.
The hard gate caught me: m1/m2 (§379-§383) and the fable escalation (§385-§388)
were harvested, but o1-o4 and p1-p3 were not — 57 MATCH notes sat unbanked while
I was about to draw new waves.
§392 — seven byte-proven spelling levers, each of which closed a match on its own:
(a) a same-address dual-sign read is fixed by ORDER (emit the unsigned
store-source read first); cse merges lh/lhu for every cast spelling tried
(b) a narrow temp picks the narrow load — s16 vs s32 decides lh vs lhu, and a
signed decrement temp yields "sll 16" where unsigned yields "andi 0xFFFF"
(c) tbl[idx-2] folds -8 into the lw offset; hoisting the subtract forces addiu
(d) identical switch arms must be SEPARATE case blocks — the target duplicates
arg setup per case and cross-jump-merges only the shared tail
(e) distinct pseudos per repeated inline copy — one shared pair biases sched1's
tie-break for the first copy only
(f) split the widen into two statements to move the sll off a pinned register
(g) the RETURN TYPE alone closed a 7-ins schedule residual (s32 -> void)
§393 — the BIRTHING BOOST: a single-set local gets max scheduling priority and
sched2's backward pass pushes it LATE; a zero-byte re-tie gives it a second set
and kills the boost. The scheduler-side sibling of §380 — same trick, different
pass, opposite symptom.
§394 — two align-1 accesses in one function reserve a phantom 8-byte stack slot;
a frame 8 bytes too large with no spill to account for it is the tell.
tools/seed_ref.py gains --contained/--contained-control: an open stub that is a
banked body plus or minus WHOLE BLOCKS — the class edit distance ranks badly.
Branch-offset masking was required (unmasked offsets veto exactly the target
pairs) and a min-side-25 floor (89% of raw hits were prologue/epilogue vacuity).
Ranks by (substitutions+regions, cover), not by d. Controls: planted-deletion
positive 60/60, random-pair base rate 0/397, R32 population 346/346, and a
post-refactor --near regression reproducing the stored slice exactly.
Banked on first use: ov_SC01_077/func_80184D50 = banked ov_SC03_007/func_8018283C
minus its trailing `&= 0x7FFFFFFF;` — MATCH, closeness 0, 98/98.
* cookbook §390: minimum distance is not minimum work (rank by effort; a deletion
is free, a substitution is thought), the lookalike filter r = d/min(nins) ~ 0.3
(17 of 30 "cousins" were boilerplate coincidence), and the three fleet-wide
nulls that close the scanner question — 0 new / 9 / 2. Spend integration
effort, not scanner effort.
* cookbook §391: a byte-aligned struct copies in FOUR instructions (lwl/lwr/swl/
swr), a word-aligned one in TWO. Never invent an aggregate type to make a draft
compile — an invented word-aligned Blk8 lost exactly 8 ins across two copies and
read as a believable "near, closeness 70" codegen residual.
* accelerators #18: a claim derived from BYTES is not a claim verified by a
COMPILER. Every similarity/correctness claim must name the tier it reached
(stream containment / compiled standalone / whole-binary gate / clean fleet);
a report that says "verified" without one invites the strongest reading.
Non-reproduction is a finding — say so rather than assuming your own setup.
* playbook §2a-2: the twin ladder (exact -> RELOC-ONLY -> CONTAINED -> cousin ->
cold), take the cheapest tier available, widen only when the tier above is empty.
* SETUP inventory row; generic-decomp-package: rank by work, and stop building
scanners once the well is dry.
The exact-hash twin tier found 22 of 352 reachable open stubs (6%). The
edit-distance band added by `seed_ref --near` finds 75 of 352 (21%) — 3.4x — on a
corpus we believed fully mined. 31 of the new rows were PURE reloc-only twins of
already-banked bodies; 8 banked the same day at ~0 agent tokens, one 94-ins
exemplar serving five open copies.
* cookbook §389: the h_norm hole (norm_stream drops its pending lui-hi on an
intervening R-type, so indexed-global reloc twins hash differently and vanish
from seed_ref/twin_sweep/dedup/family-maps at once). Do NOT fix h_norm — every
stored calibration keys on it; the near tier reads through it.
* accelerators #17: the generalisable law. A similarity hash built for DEDUP
under-matches by design, which is correct for dedup and silently lossy as a
FRONTIER join — the two questions want opposite error directions, and the
frontier failure looks exactly like "this function is unique".
* generic-decomp-package §2b: build the near band at the same time as the exact
tier, with the three verifications. It pays from the first bank for a new
project, where we paid a session to recover the debt.
* SETUP inventory row + playbook §2a (run it before believing any "no twin"
verdict; never send a RELOC-ONLY row to a drafting agent).
The 'false bank' in the S69 checkpoint was not one. Both instances verify
byte-identical after 'make extract BINARY=<b>'. §384 states the law (verification
must regenerate whatever the gate changed the inputs to), the trap inside it (a
src-only revert of a carve commit produces 'table-count drift vs the carve', which
reads like progress), and the give-away I ignored — the commit diffstat showed
config/overlays.mk and a splat yaml sitting next to the .c.
The lever existed but nothing downstream applied it. Proof it mattered: a wave
agent this session diagnosed its own blocker as "§378 THE SELF-CALLER CAST, a
TU-level fix (cast_self_callers.py) that requires editing src/, which I'm not
permitted to touch" — the knowledge propagated, the automation did not.
* recover_integration.py: NEW "self-cast" stage (tier=binary), so the driver can
run the whole chain as --stages arity,self-cast. The docstring states WHY the
order is not arbitrary: self-cast answers the error that "arity" CREATES.
* residual_rules_b.py: both decl-conflict tiers now prescribe the full chain
instead of "route to integration / budget for banking", and
NOCOMPILE-UNDECLARED-FIXED now says outright NOT to gate the autodecl arm (it
is a second conflicting declaration in the real TU).
* wave-playbook §4b: replaced the stale two-step recipe with the three-step
chain, the one-driver form, the callee variant, and the MANDATORY
--undo-journal.
* SETUP.md: full inventory row (R21) — it had zero mentions.
Not wired, deliberately: gate_stage's ladder rewrites DRAFTS via _xform, while
this edits the TU; a src-side edit inside the automatic gate needs
revert-on-failure, which recover_integration already owns.
Still open: a draft_prechecks rule to catch the self-decl conflict statically,
before a build is spent. The new stage's plumbing is verified (CLI + candidate
selection); its functional end-to-end run is NOT — gate12 held the tree.
tools/triage_ladder.py — the zero-token pre-agent pass, split PRE (target-side:
BANKED/WALL-332/PARKED, no build) from POST (residual_rules_b, needs a draft).
--escalate refuses a walled or banked target; --acceptance is the R39/R32 harness.
Refuses on a non-quiescent tree: a merging gate makes the stub oracle wrong in
both directions (measured, ov_SC01_004:func_8017EB30).
Acceptance, on the whole corpus: false-skip 0/1367 open stubs, recall 426/426
matched, wall tier fires on exactly the 10 enumerated walls (0 extra, 0 missing).
The first wall control asked for evidence that CANNOT exist — it scanned banked
functions' .s, which splat never writes — and printed '0 scanned / 0 tripped',
indistinguishable from a pass. The R32 empty-denominator assertion caught it on
its first run; replaced with a two-sided sweep over all open stubs.
tools/cast_self_callers.py — the §378 lever + --sync-decls for the narrow-param
case C89 forbids no-proto from reaching (§378a).
Wiring: wave_args drops walled/parked targets at draw time via pre_classify (one
implementation, R33); escalate_fable.js refuses any target without triage:'DRAFT'.
Tool fixes found by measurement:
* fix_arity_callers was blind to main entirely (globbed src/main/main*.c; main is
src/*.c) — reported success over an empty file set through three gates. Now
refuses when --binary selects no files.
* parallel_gate records each worker's 'failed by class' line (was truncated out of
the 200-char tail); gater_lane retries in-tree ONLY on the diagnostic-free
blind-worktree signature — S69 ran 22 serial retries against real cc1 errors.
docs: cookbook §376/§377/§378 (index 1033), SETUP.md, wave-playbook §4b.
§374 a register __asm__($30) reservation is NOT honoured by move_movables under
pressure, and the corruption is SILENT (the build succeeds) -- audit the raw
objdump register uses before trusting a pinned build that compiles.
§375 an $a0-$a3 pin used LATE relocates an EARLIER outgoing-call use of that same
register ~26 slots early, identically across three structural variants; argument
pins are not local the way $s pins are.
With §368 and §373 these now form a usable four-way rule for when a pin helps,
when it fights the allocator, when it is ignored entirely, and when it acts at a
distance.
From the fable escalation that closed ov_SC06_010/func_8017E764 (8 -> 0, BOTH
clusters), and it is three findings not one:
1. DEAD-RESET CSE-BREAKER. To stop cse merging two computations of the same
expression WITHOUT an asm's scheduling footprint: name it, use it, then
'p = 0;' immediately after. cse invalidates at the second set and flow deletes
the dead set BEFORE sched1 -- zero bytes, zero LUID disturbance. An empty-asm
re-tie by contrast is a REAL pre-call insn whose def->asm->arg chain fronts that
argument's addiu, and on this function that WAS the second residual cluster
(§361 confirmed: the lever caused the bug it was later blamed on). Removing the
dead-reset costs +2 ins / +8 frame bytes, so it is load-bearing.
2. A REGISTER PIN THAT DELETES A sched2 ANTI-DEP. sched1's birthing boost sinks a
single-set 'la' to its consumer, local-alloc reuses the freed scratch, and
sched2 is then walled by store-reads-$v0 -> la-writes-$v0. A pin on the address
pointer deletes the anti-dep. Note this is where a pin is RIGHT, against §368
where pins measured worse -- the discriminator is breaking a false
anti-dependence (works) vs out-arguing local-alloc about an allocation (fails).
3. HARD FACT: gcc-2.7.2 insn_cost (sched.c:1363) sets LINK_COST_FREE on any dep
whose consumer is unrecognizable (INSN_CODE<0 = every inline asm), so
pri(asm)=1 ALWAYS. An asm can never inherit a load's latency into its priority.
That closes off a whole family of plausible levers.
Also cross-referenced §370: this run was briefed to test that bound FIRST and
reported it did NOT explain the residual. §370's claim is unchanged and still
narrow; the transferable habit is checking whether a recorded bound covers your
case before declaring a residual unreachable.
tools/r22_verify.sh (NEW, promoted from .run so it survives the session):
'make clean' deletes asm/ AND build/, and THREE times this session that raced a
live lane -- a subagent authorised to splice src/800.c produced a FALSE
'212 passed, 1 failed' red, and two drafting agents reported their target's asm/
tree MISSING mid-draft (one survived only by finding an old snapshot). Drafting
agents never WRITE src/, which is exactly why 'check for a dirty tree' does not
catch them: they DEPEND on state this operation destroys. The guard refuses when
any wave scratch dir was touched in the last 6 minutes, names the live agents, and
offers R22_FORCE for a drained lane. R54 -- a guard that is not running is not a
guard, so this refuses instead of relying on me remembering.
Negative-controlled BOTH directions: refuses with 5 live agents named; passes on an
idle lane AND on a lane whose scratch is 30 minutes stale (no false positives).
fix(gater): the in-tree main commit message said '0 fn(s)' for a commit that
contained a real bank. corpus memoizes, so querying corpus.stubs immediately after
the bank returns the STALE pre-bank set. Derive the list from harvest_verify's own
verified-out file instead (R33: derive from the invariant the tool already wrote).
§372 ★★★ THE COPY-CAPTURE PAIR. Tell: a REGALLOC-PERM residual whose wrong-register
rows READ the destination of a nearby MATCHING copy insn. Two passes re-base uses
onto a copy's destination -- cse.c make_regs_eqv (canonical-reg rewrite of later
same-EBB uses) and local-alloc.c optimize_reg_copy_1 (forward-substitution when the
copy's src does not die in it) -- and BOTH die to one zero-byte edit: spell the copy
'P = X + zr' so SET_SRC is a PLUS, which is not a reg-reg copy and records no reg
equivalence, while emitting the byte-identical 'addu $rd,$rs,$zero'.
Notably the escalation was told to CHECK whether §368's tell applied rather than
assume it; it reported that it did NOT (pure shift/slti rows, no commutative
operands) and found the real cause from RTL dumps. That is §361's procedure working.