Wave Y's harvest found a defect in the card field shipped EARLIER THE SAME SESSION (§196). Overlay
functions are named by VRAM address and 134 overlays load at the same window, so build()'s bare
defs[name] counter mixed N unrelated functions -- and §196 ranked that row ABOVE the destination TU.
Measured over the tree: 3,911 of 9,861 symbols with a definition are defined in >1 binary; 1,219
disagree on ARITY, 1,204 of them in the overlay window; 818 of the disagreements were a top-two TIE
that Counter.most_common broke by sorted-file order, so the lowest-numbered defining overlay
silently won. On wave Y's five binaries, 26 of 65 overlay-window DEF rows (40%) named another
overlay's function. Byte-proven cost: applying one row's arity to func_8017E83C took it from
MATCH (114 ins) to 113 ins / 83 mismatched.
Fix: defs are keyed by BINARY; a DEF row is emitted only from the target's own binary for
overlay-window symbols, and otherwise withheld with a stated reason ('N other binaries define this
address; none is yours') rather than silently omitted. Resident/shared/main symbols are fleet-unique
and were always correct (0 of 43 wrong). build_wave_atlas passes the target binary through.
Wave Z was ALREADY STAGED with 24 poisoned rows across its 292; redrawn with the fixed tool -> 0
poisoned, 24 withheld with a reason. This is the fourth same-session self-correction (§194-E ->
§193-A, §199-A -> §189-A, §197-A -> §136-9, §201-A -> §196), and the first one caught before the
bad data reached agents.
Measured the top-candidate score distribution on wave Y's 67 gaps: min 0.009 / p50 0.08 / p90 0.26 /
max 0.53. Hand-checking the low half showed generic co-occurrence -- 'delay slot' matching every
section that mentions a delay slot -- not real answers. Emitting those is worse than emitting
nothing: an empty list costs a reader nothing, a plausible-but-wrong section costs them a read. The
tool now reports 14 confident candidates instead of 51 mostly-noise ones.
Every harvest so far spent most of its agents rediscovering that a gap was already answered: 61/71
(T), 44/64 (U), 76/67 (V), 41/68 (W), 56/63 (X). That majority verdict is the flywheel working, but
paying an adversarial verifier to reach it is waste -- the check is a text search, and a text search
does not need a model. gap_triage scores each gap against all 441 cookbook sections by IDF-weighted
distinctive-term overlap (file:line cites, section refs, register names, MIPS mnemonics, pass names)
and hands the reader its top candidates to open FIRST.
Deliberately dumb and deliberately non-authoritative: no embeddings, no model, and a candidate is a
starting point rather than a verdict -- its failure mode is a suggestion the reader ignores, not a
refusal that discards work (R39). R32: every gap appears in the output exactly once, asserted.
On wave Y's 67 gaps: 51 get a candidate section, 16 get none. Built during a four-round API outage
that blocked the harvest itself -- the retrieval half of the harvest needed no agents all along.
Second instance of the §192 defect class, found by wave Y's ov_SC02_017 slate. The project's own
§37/§124 idiom spells a renamed symbol as `extern s32 gVecX __asm__("D_80126B5C");`, and sym_of's
generic branch matched `__asm__` -- an identifier followed by '(' -- before reaching the real one.
Every aliased declaration therefore collided with every other one under the name `__asm__`.
Measured cost on one slate: 1 byte-verified draft DROPPED and 2 phantom CONFLICTING-EXTERN
failures, on an idiom this same session used to RECOVER work.
NC over src/ plus wave Y's drafts: 1,210 changed verdicts, every one `__asm__` -> the real alias
identifier (899 of them one symbol, aD800B9A02 -- the idiom is fleet-wide), 0 regressions.
HARVEST (10 agents, 68 gap reports): 4 CONFIRMED, 3 REJECTED, 41 already-covered. Yield fell from
14 because the readers were seeded with §193 AND §194 AND §195 -- three sessions of laws off the
table before they started, which is the flywheel working.
§197-A IS BANKED AS ONE ENTRY WITH ITS ATTRIBUTION FLAGGED CONTESTED. Two verifiers independently
confirmed the same phenomenon (lhu;sll 16;sra 16+K where the target has lh;sra K) and attributed it
to DIFFERENT passes -- one to cse's fold_rtx associative block with -da dumps showing the middle insn
already gone in x.i.cse, one to combine preferring the count-merge over forming lh. R34 forbids
treating agreement between disagreeing oracles as corroboration, so the observable, the tell and the
cure (a zero-byte asm re-tie; a fresh temp works with no second SET at all) are banked, and the pass
question is recorded as open. Both verifiers independently byte-refuted §136 type-form rule 9's cure:
u16 v[4] and SVECTOR v compile BYTE-IDENTICALLY in rule 9's own context. Banner added at rule 9.
§198 (in the tool's own help text): UNKNOWN was excluded from the default --levers while the wave
PROMPT told every agent "UNKNOWN does NOT mean hard -- it means the atlas could not name a lever."
The tool was silently overriding the doctrine. Measured: UNKNOWN held 607 of 1,179 in-band members,
more than every other lane combined; wave W drew 73 cards from it into 3 gate groups (24.3 drafts per
rebuild vs wave V's 7.8) for 71/71 drafted, 68 banked. Recorded cost: UNKNOWN groups are mostly
singletons, so the free sibling remap yielded ZERO where waves T/U/V got 49/34/25.
Also releases func_80185480 and func_8017EC98 from wave W's spent-card file -- their agents died on
server rate-limiting and no draft exists, so they must not be marked already-waved.
Wave-V token accounting picked this lever: drafting is 88.3% of a wave's output tokens (repair 8.4%,
reconcile 3.2%) at 9.3 match_one compiles per agent, so the leverage is in what a drafter must GUESS
before its first compile. Two such guesses are answered in the tree and were on nobody's card:
callee arity/return type (§195-A: NO positive tell exists in the asm, the procedure is a two-arity
A/B) and global type (every CONFLICTING-EXTERN drop). tools/decl_prior.py indexes 4,162 files ->
67,094 symbols (9,739 with a banked DEFINITION); cards now carry the rows for their own target's
symbols, ranked DEF > TU (authoritative, wave law 2) > FLEET modal + rivals with counts.
Control draw: 63 rows over 10 cards, 83% already settled by the destination TU (still a saving --
the agent was grepping for them by hand), 17% with no local answer at all.
Third instance in one session of the same law (§193-A seed_ref, §194-E tu_ref, §196): the answer was
already computed and the card did not carry it.
32 agents over wave V's 67 index_gap reports: 14 CONFIRMED, 9 REJECTED, 76 already-covered (the
third harvest this session; readers seeded with §193 AND §194 so neither could be re-derived).
THE HEADLINE IS A DEFECT IN OUR OWN VERIFIER (§195-D). masked_diff.mask_for short-circuited on the
OPCODE -- `if (word >> 26) in (2, 3): return 0` -- ahead of the reloc dispatch, so every `j` to a
LOCAL label was dropped from the comparison. The assembler resolves those itself and emits no
relocation, so there was nothing link-time about them. For a loop or switch arm, which label a `j`
targets is the difference between `break` (fall into the shared tail and execute its calls) and
`return` (skip them): byte-proven on ov_SC03_118:func_801825EC, where the banked `break;` and a
call-skipping `return;` variant differ in exactly one word (0800003e vs 08000041) and BOTH reported
MATCH. The blindness reached match_one, the permuter's MaskedScorer, family_cousins.tok and the
atlas similarity tiers at once -- nothing between a draft and the whole-binary gate could see it.
Now the 26-bit field is masked only when reloc_kind == "26" (the linker really does fill it).
R39 control: 35/35 already-banked wave-V drafts still MATCH against their snapshot .s.
Other confirmations of note: §195-A bounds §167-08 with a byte-proven false-negative class (an
argument that DIES at the call is allocated straight into $aN, so its only def is a plain load and
every use reads $aN -- there is no positive tell in either direction, only the two-arity A/B);
§195-B a CALL_INSN does not start a basic block in gcc-2.7.2, so a call-crossing temp can be a
LOCAL-alloc quantity; §195-M frame `vars` is a sequential bump-allocation, unifying §193-I's
CEIL(aggregate,8) term with §165-03/§167-06's 8x-orphan term as one frame_offset walk.
26 agents over wave U's 64 index_gap reports (7 cluster readers, one adversarial verifier per
candidate defaulting to REJECT, seeded with §193 so it could not be re-derived): 14 CONFIRMED,
5 REJECTED, 44 already answered by an existing section (wave T: 9/5/61).
TWO OF THE 14 CORRECT WORK BANKED THE SAME DAY, and both are now cross-banner'd:
* §194-E — `exemplar` is not merely un-banked (§193-A): it names the card's OWN target on 42/73
wave-U and 36/71 wave-T cards, and the `seed_ref` §193-A shipped is same-binary 0/51, so the card
still carried ZERO destination-TU locality. Fixed both ways: a self-pointing exemplar is now
emitted as null, and cards carry `tu_ref` — banked functions in the card's OWN .c ranked by
symbols shared with the TARGET's .s relocations (62% of wave-T targets had such a neighbour vs
19% for the cross-overlay literal grep). Operand-only extraction: a naive uppercase-word regex
read the .s comment column's hex words as symbol names (34 "symbols", 31 of them hex).
* §194-N — §193-D's C dial is misstated: the lever is a SURVIVING CODE_LABEL, not "a label between
the block and the call". jump_optimize deletes any label with LABEL_NUSES == 0 long before sched1
and rewrites a C user label into NOTE_INSN_DELETED_LABEL, which is not a basic-block boundary.
Highlights of the rest: §194-A a zero-byte fence is a one-way wall RELATIVE to the statement being
steered (after = emit-first), and the barrier predicate is volatile-or-colon-less, not the "memory"
clobber; §194-J back-to-back identical stores are deleted by flow.c's last_mem_set unless volatile;
§194-K blinding sched1's alias oracle with a second SET is the first zero-byte dependence-CREATING
lever; §194-M a store in a conditional branch's delay slot proves its C statement DOMINATES the
branch.
19 agents over wave T's 71 index_gap reports: 5 cluster readers, then one adversarial verifier per
candidate defaulting to REJECT. Result: 9 CONFIRMED, 5 REJECTED, and 61 of 71 gaps answered by a
section that ALREADY EXISTS — a retrieval problem, not a knowledge problem.
§193-A is the fix for that retrieval problem, and it is a construction invariant, not drainage: a
wave card's "exemplar"/"sibs" come from the atlas's OPEN set (atlas.py:96 load_open -> corpus.stubs,
:657 max over open members), so they are stubs 0/34 and 0/146 measured on wave T — and 0% at any
project maturity. Meanwhile atlas.py:505-536 already computes a MATCHED-pool twin (pool = sig -
stubs) and build_wave_atlas kept only its similarity score while discarding the identity. Cards now
carry seed_ref + the "M:" matched kNN neighbours; verified on a fresh draw, seed_ref resolves BANKED
4/4 where exemplar resolves banked 0/12.
Two of the nine refute existing sections in place: §193-B byte-refutes §43's "the (s16)param_of_s32
cast form CANNOT reproduce this" (the decider is combine.c:929's cross-call guard, not regalloc),
and §193-G shows §164-54's "scope to >=4 arms" bound is byte-wrong (balance_case_nodes splits at
i > 2). §193-C bounds §8/§48-A1's cross-jump refund to TAILS only — gcc-2.7.2 has no prefix merge.
Each one refused byte-verified work; each fix is probed, not reasoned:
* built-in redeclaration: a cc1 probe shows two conflicting "memcpy" declarations give
"warning: conflicting types for built-in function" + exit 0, while the same pair on a
non-builtin name errors. Every overlay TU in the fleet declares memcpy twice and compiles
today -> CONFLICTING-EXTERN on a builtin is now WARN.
* driver mismatch: overlays bank via gate_lane -> gate_stage -> harvest_verify, which strips
every typedef the target TU provides; pregate_check modelled gate_main's hoist/strip instead
and reported DUPLICATE-TYPEDEF for exactly the duplicates the real gate removes.
substitute() now takes an optional per-draft transform; pregate passes the overlay one.
* block-scope typedefs: two functions may each declare their own typedef inside their bodies
(that is how a draft stays self-contained for match_one). _typedefs now honours the brace
depth map the caller already computed.
* project scalar aliases: include/common.h's "typedef s32 M2C_UNK;" makes "extern s32 D_x" and
"extern M2C_UNK D_x" the same declaration; _ALIASES now DERIVES those from common.h (R33).
Measured on the 5 leftover slates: 28 drafts, all re-verified MATCH by match_one, went from
"0 kept / phantom FAILs" to main 2 clean, ov_SC04_011 15 clean, ov_SC03_028 1 clean,
ov_SC06_029 4 + 1 named TU edit, ov_SC02_005 2 real TYPEDEF-USED-ABOVE-DEFINITION.
build_wave_atlas: --one-per-gid collapses same-skeleton siblings to one card and defers
them to <out>.siblings.json for the post-bank family_sweep remap (R32 accounting asserted);
--rank total ranks gate groups by DELIVERED mass (card + deferred siblings). Measured on the
wave-T draw: 6,557 drafted ins carrying 12,709 sibling ins behind 69 of 71 gids = 19,266
instructions of potential for 71 agents, vs 9,985 behind 57 under --rank mass. R39 NC: the
flag is byte-inert on a pool whose gids are unique.
gate_main/pregate_check (§192): three defects that made the pre-gate ladder main-only while
reporting "clean" on overlay slates — (1) resolve_conflicts/substitute hardcoded
corpus.stubs('main') -> per-binary _stubs_for(); (2) sym_of returned the keyword `void` for
every `extern void (*D_x[])(...)`, manufacturing 192 phantom CONFLICTING-EXTERNs (NC over
5,526,100 declarations: 189,301 changed verdicts, 0 regressions); (3) `void f()` and
`void f(void)` were normalized together, costing 40 more phantoms — C89's unspecified-
parameter rule is now gate_main.sig_conflict. §192b: the tool refuses when it substituted 0
files, and prints the per-draft [DROP] reasons it used to compute and discard.
Same overlay slate now reports 2 failures, both real (duplicate typedef; memcpy declared two
ways). Cookbook §192/§192b + index regenerated (585 sections).
Wave Q (90 cards / 6,249 ins) was stopped mid-flight, then a repair-only pass recovered 12 of its
39 unfinished drafts (+579 ins, 51 -> 64 verified MATCH). Banking took two new tools, because the
gate failed three times on causes no C-level check can see.
tools/fragment_check.py (NEW) — the enclosing-function trap, both halves, in milliseconds:
* a draft whose [addr, addr+4*nins) SUBSUMES another symbol. splat labels addresses that are not
functions (a bare epilogue tail, a jump-only branch target, an interior label); convert the
enclosing function and a neighbour's `jal .L80050F24` loses its target.
* a draft that DEFINES another stub's symbol in inline asm. MoveImage's repair agent correctly
worked out that a trailing epilogue belongs to MoveImage (splat attributes those bytes to the
NEXT symbol) and emitted `.globl SYS_OBJ_8F4` itself -- right bytes, match_one passes, and the
assembler then sees the symbol defined twice because the fragment still has its own .s.
My first hypothesis was the subsumption case; the tool REFUTED it (clean) and the redefinition
case was the real one. Recording that order, because I built the tool before verifying the
premise -- R35 inverted.
tools/bisect_slate.py (NEW) — gate_main's built-in bisect ran THREE HOURS on a 45-draft slate and
printed nothing. Three defects: it never checked whether the BASELINE builds (if no subset can
pass, the search is doomed from step 1), it logs only when it isolates a single draft, and its
re-split rule degrades toward one rebuild per draft. The replacement runs the NULL CONTROL first
(the same control that refuted this phase's "main is link-blocked" misdiagnosis), logs every step,
and does a true binary search: it found the culprit (SYS_OBJ_1790) in 7 steps and 176 SECONDS.
Result: 40 banked, main stubs 1,803 -> 1,763.
§176i — WHAT A STATIC PRE-GATE CHECK CAN AND CANNOT PROVE. pregate_check validated wave Q's slate
as clean in 0.7s (the first slate all session to reach the gate pre-validated) and the build then
failed twice, both times outside what any text-only check can see:
1. LINK-time undefined reference: `.L80050F24` lives INSIDE gfx2D_BG0_OBJ_698 and another
function's .s branches to it -- converting a function to C deletes the local labels its
neighbours jump to. Statically checkable, but from the .s files, not the .c: scan every other
.s for label references landing inside a candidate's address range. Worth building.
2. BYTE mismatch: with the link fixed the binary BUILT and the SHA differed -- the §174 law 1c
class, which reloc_identity had already named six suspects for.
The division of labour to rely on: text checker for SHAPE, reloc oracle for IDENTITY, gate for
TRUTH. A clean pre-gate is a licence to build, not a prediction of success -- and when the binary
builds but the hash differs, BISECT, because it costs wall-clock and zero tokens.
§176j — STOPPING A WAVE MID-FLIGHT COSTS THE IN-FLIGHT TAIL. Wave Q stopped early: 51/90 verified
(3,631 of 6,249 ins) versus the 96-97% the same pipeline yields when allowed to finish. The loss is
SUSPENDED, not destroyed -- every draft persists on disk, 15 of the 39 unfinished sit at closeness
<=10. Do NOT resume the workflow to recover it (resume re-runs unfinished agents from scratch at
full cost); use a REPAIR-ONLY pass over the <=30 band instead. Decision rule: before killing a long
agent run, price the tail -- stopping converts near-matches into "needs a cheaper second pass",
which is a deferral, not a saving.
The 68%->95% lever from §176h.C2, mechanized. Reconciliation belongs INSIDE the wave: a banked
draft's declarations become the TU's, so a sibling clash hardens into a file clash and post-bank
recovery is measurably worse (18 parked drafts still MATCH, only 1 survived after their wave banked
vs 5 before).
AUTO-FIXES, each re-verified with match_one and REVERTED if a byte moves (a declaration change is
a codegen change, §176f):
* COSMETIC-TYPEDEF two names for a structurally identical struct -> adopt the other. Compared by
BODY, never by name (OtBlk_80015498 == OtBlk_80016450; Elem12 != B12). This body comparison is
also the answer to §176h.C's spelled-name limit.
* SIGNEDNESS / ALIAS / ARRAY-VS-SCALAR -> adopt the TU's spelling, fixing the use site.
* DEFPARAMS (NEW LEVER) -> adopt the TU's parameter types on the DEFINITION and re-narrow with a
shadowing local: `void f(s32 a0_p) { s16 a0 = (s16)a0_p; <body unchanged> }`. One textual
insertion instead of rewriting every use site, and the cast emits the same sll/sra pair.
Byte-identical on both cases tried.
REFUSES, with named reasons, because these are decisions and not edits: DIFFERENT-STRUCT (two real
layouts for one symbol), IMMOVABLE-TU-DECL (gate_main reverts src/, so it needs its own commit +
rebuild + R22), DEF-SIDE-RETURN (adopting the TU's return type usually costs the match -- measured
on func_8001ABBC), and BROKE-MATCH for anything its own verification rejects.
Measured on wave P's leftover slate: 6 -> 9 compatible, 3 auto-reconciled, 2 repairs reverted by
the tool's own byte check, 7 named for a human.
build_wave_atlas: --rank mass (main's gate cost is per SLATE, so ranking groups by member count
silently collapses a wide band to the smallest functions -- measured 60 cards/2,604 ins where 46
cards/4,829 ins were available), and the selector no longer counts ITS OWN OUTPUT as already-waved
(re-running with identical filters had been shrinking the pool 60 -> 46).
MEASURED, and it inverts the obvious plan. "Bank the clean drafts now, recover the conflicted ones
later" is backwards: of 18 wave-O/P drafts parked and re-verified still MATCH, only 1 survived
resolve_conflicts once their wave had banked -- versus 5 before it.
The mechanism: a banked draft's declarations BECOME the TU's, so every parked draft that merely
disagreed with a SIBLING now disagrees with the FILE, which is the stricter arbiter (a sibling
clash can be settled by editing either side; a file clash only by editing the draft, and some
cannot be settled at all because gate_main reverts src/ before every build). Worse, the auto-rename
that reconciles a cosmetic clash pre-bank becomes a DUPLICATE TYPEDEF post-bank, because the name
it renames to is now defined in the file.
So iterate the dry run to `N -> N compatible, 0 dropped` BEFORE spending the first rebuild, and
budget reconciliation into the wave rather than after it.
pregate_check: DUPLICATE-TYPEDEF now fires on ANY redefinition, not only differing bodies. C89 has
no compatible-redefinition allowance for typedefs, and my first version missed exactly the case the
tool exists for -- two identical `OtBlk_80016450` definitions, which the compiler rejected on the
next rebuild. Measured, not reasoned.
Also banks the auto-reconciler's rule: tell a COSMETIC clash from a REAL one by comparing struct
BODIES, not names. OtBlk_80015498 vs OtBlk_80016450 are the same {s32 a; s32 b[4];} and rename
byte-identically (both re-verified MATCH); Elem12 vs B12 genuinely differ and were refused.
Wave P drafted at 97% and cost A DOZEN clean rebuilds to bank, and not one of those rebuilds
failed on a matching problem -- every one failed on a TEXTUAL property of the substituted file
that a grep could have reported instantly. This is that grep.
gate_main's resolve_conflicts cannot answer it, and not from carelessness: it inspects the DRAFTS
while the compiler sees the FILE THEY LAND IN -- after typedef stripping and renaming, at each
draft's own insertion offset, interleaved with declarations the file already had. Those
transformations run AFTER the conflict check passes. So substitute() gained write=False and this
tool checks the artifact itself.
Five checks, each earned by a rebuild lost this session (§176h): typedef used above its
definition; type never defined anywhere; duplicate typedef with different bodies; one symbol
declared two incompatible ways; definition contradicting a visible prototype.
CALIBRATED AGAINST THE COMPILER, NOT AGAINST C89 PEDANTRY -- and this mattered. The first version
reported 4 hard FAILUREs on the slate that had just built BYTE-IDENTICAL:
- it ignored SCOPE, but the project deliberately uses block-scope extern blocks, and a declaration
inside one function cannot conflict with a definition elsewhere. Now brace-depth aware.
- it split `void f()` from `void f(void)`, which gcc-2.7.2 accepts. Normalized.
- it called every def-vs-decl mismatch fatal, but gcc-2.7.2 accepted `void f(void*,s32)` against a
`void f(s8*,s32)` definition and even `G3P *f(...)` against `G4P *f(...)`. What it REJECTED was
a void/non-void RETURN split (func_8001ABBC). That split alone is FAIL; the rest are WARN.
Comments are masked via cdecl before any use-site scan (an unmasked scan reported 7 phantom hits).
R39 controls: the slate that banked is FAIL-free (exit 0, 3 informative warnings); four synthetic
defects each reported at FAIL; a clean text reports nothing; a block-scope extern does not
conflict; `short` vs `s16` does not conflict; array-vs-scalar does.
Wave P was the first full run of the 6k-ins doctrine: 60 cards / 6,589 ins in 2 gate groups,
59/60 claimed and 58/60 independently re-verified MATCH (6,372 ins), reloc_identity 58/58 AGREE
with ZERO symbol errors -- the second consecutive clean wave on symbol identity.
Banking cost a dozen rebuilds and exposed four more gate_main defects plus three regressions of
my own. The tool fixes, all NC'd:
- resolve_conflicts never read a draft's OWN DEFINITION, so the DEF-side wall (a draft defining
s32 func_X against a TU prototyping void func_X) reached the compiler. Now definition-aware:
it caught 13 conflicts up front where the build had been finding them one rebuild at a time.
- DECL and both typedef patterns anchored on end-of-line, so a TRAILING COMMENT hid a declaration
or typedef entirely -- and agents comment nearly everything they declare. Seventh instance of
one root cause: a scanner that looks green while reading less than it claims (R32).
- typedef handling is now BODY-AWARE and POSITION-AWARE, in a single pass:
* identical definition visible ABOVE the insertion point -> strip and reuse;
* same name, different shape -> rename (private to the draft);
* definition below the insertion point -> never reuse (it is not in scope there).
Three wrong strategies preceded this, each costing a rebuild: blanket strip (the file's copy can
sit BELOW the draft -> implicit-int, then a collision), blanket rename (breaks drafts that share
an IDENTICAL typedef, because their externs stop agreeing -- my regression, three drafts at
once), and a rescan loop that found the definition it had just renamed and stripped it as a
self-duplicate -> 'parse error before *'.
KNOWN LIMIT, recorded not fixed: the conflict check compares spelled type NAMES, so three drafts
each defining their own Slot54 with different layouts all declared func_80032A74(Slot54*) and
looked compatible. Comparing struct LAYOUTS for locally-defined types is the real fix.
13 + 4 verified-correct drafts are parked in two named buckets (competing local type models;
immovable TU declarations that gate_main reverts before every build).
A wave is now sized by INSTRUCTION MASS, not card count. The metric is instruction-weighted, so a
wave is worth what its instructions are worth: the 12-42-ins card lanes carried ~1,400 ins/wave
(~0.011pp, ~440 waves to finish) while wave O carried 6,266 ins at the same gate cost and the same
draft rate.
build_wave_atlas --target-ins draws cards until the instruction budget is met (still capped by n)
and refuses to under-fill silently. Standard recipe: --target-ins 6500 --min-ins 60 --max-ins 200
--max-bins 4, levers now including UNKNOWN.
THE MEASUREMENT BEHIND IT: draft rate barely decays with size -- wave M 98% at avg 51 ins, wave N
92% at avg 65, wave O 96% at avg 128. Mass is nearly free.
THE UNKNOWN UNLOCK: UNKNOWN is not a difficulty label, it means the atlas could not name a lever,
and it had been routed as needing its own bespoke lane. Wave O's 22-card R37 probe drafted it like
any other lane -- reclassifying ~138k ins (a quarter of everything open) as ordinary wave fuel.
With UNKNOWN in, 9,224 fns / 417,325 ins = 70% of all open instructions are agent-draftable; the
60-200-ins mass band alone is 164,357 ins ~= 27 waves, and is the band to work first.
Also banks the five-step PRE-GATE PROTOCOL (independent re-verify -> reloc_identity -> dry-run to
0-dropped -> reconcile declarations toward the form the match needs -> gate), cookbook §176g.
36 fresh wave-O cracks + 10 recovered wave-J/K/L drafts, verified in ONE clean rebuild:
143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL. main stubs 1881 -> 1835.
Wave O was a 3-arm 49-card wave (6,266 ins): main head-crack, main UNKNOWN, overlay UNKNOWN.
47/49 standalone MATCH, independently re-verified by me (R14) at 47/49 -- exact agreement --
and reloc_identity reported 46 AGREE / 0 MISMATCH, the first wave of the campaign with zero
symbol errors. THE UNKNOWN LEVER DRAFTS LIKE ANY OTHER LANE, which matters strategically: it is
~138k ins fleet-wide (a quarter of everything open) and was routed as "needs its own lane".
FOUR gate_main defects fixed here, each of which had been silently costing prior waves drafts:
- typedef stripping walked drafts in SLATE order while substitution happens at ADDRESS order, so
the surviving typedef could land BELOW a draft using it -> "syntax error before D_800A651C".
Verified the two orders genuinely diverge for both destination files in this slate.
- a BUILD failure (sha None) fell through to a silent bisect -- a full clean rebuild per step to
rediscover what the compiler had already printed and discarded. Now the error lines are shown
and the offending drafts named for undefined-reference/redefinition/conflicting-types.
(My first version of that printer TAILED a stderr+stdout concatenation and faithfully showed 25
lines of make progress chatter instead of the error -- selecting by position, not by content.)
- typesig treated "short" and "s16" as different types (R39 over-refusal). Aliases now normalize;
11/11 NC cases pass, with signedness, volatile and array-vs-scalar still conflicting correctly.
- conflict detection ignored shared headers: engine_core.h's DEFINE_ macros declare symbols in
their own bodies, so a draft's file-scope array decl of D_800A651C was illegal. Block-scoping
the draft's extern fixes it byte-identically.
DECLARATION RECONCILIATION took the slate from 5 dropped to 0, and three of the four conflicts
were load-bearing CODEGEN, not style: the array form of D_80078D88 blocks a sched1 hoist (scalar
users adopt [0] for free); "volatile" on D_800B9A02 is required by one draft and fatal to two
others (plain u16 loses 1 bank, volatile loses 2); D_800A651C needs block scope. Cookbook §176f.
R37 probe: 20 shape-verified AND symbol-verified stored drafts, 5 gate groups -> 1 banked (5%),
statistically the same as the project's A10 stored-verdict law (~0-8%; T1 measured 0/23 on the same
kind of pile earlier this phase). The null is the finding: a stored draft's rejection is almost
never symbol identity, it is TU plumbing (§176d) or staleness. reloc_identity's real home is a
PRE-GATE check on FRESH drafts, not a backlog resurrection tool. The remaining 30 groups are not
worth 30 rebuilds -- lane closed rather than pursued because the tooling was new and interesting.
Also names the SYMBOL-COLLAPSE class in the fixer's refusal (one draft extern standing in for N
distinct globals; a textual rename moves every occurrence together, so it needs one extern per
site) and records the R38 self-note: the 0/23 prior was already in the phase log.
§174 law 1c recorded match_one's relocation blindness as a caution to the reader ('check every
symbol by hand after MATCH'). It is a computation, not a human's job. Banks the arithmetic, the two
failure shapes it separates (uniform-delta stale seed symbols vs wrong field offset), the four
traps that bit me building it (splat-derived names absent from the symbol files; MIPS o32 REL keeps
the addend in the instruction; index alignment is a precondition; a nearest-symbol label needs a
tight window), and the honest limit measured the same session: symbol-verified + shape-verified is
still NOT sufficient for a bank -- the first re-gate group of five such drafts banked 0/5, because
what remains is TU plumbing (§176d), not identity.
--fix rewrites only unambiguously-wrong symbols (every mismatch naming a symbol must imply the SAME
corrected base) and refuses otherwise: 10 of 12 repaired, 2 correctly refused.
match_one masks relocations (26-bit jal field, HI16/LO16 immediates), so it verifies instruction
SHAPE and is structurally blind to WHICH symbol each relocation names: a draft calling the wrong
function or touching the wrong global reports a clean MATCH (§174 law 1c; wave K burned 5 gate
attempts on two swapped globals). Until now only the whole-binary gate caught it, and it reports a
hash, not a cause.
But the target .s comment column carries the FINAL LINKED WORD, so the true address behind every
masked field is recoverable arithmetically, and config/symbols*.txt maps it back to a name. This
tool resolves what the DRAFT names, computes what the TARGET references, and compares -- /bin/bash, no
rebuild, and it names the fix instead of reporting a mismatch.
match_one --emit-streams additionally carries mine_relocs (kind/operand per index); existing
consumers read mine/tgt only.
THREE INSTRUMENT BUGS FOUND BY ITS OWN NEGATIVE CONTROLS, each fixed before any verdict was
believed (R35/R39):
- splat-derived func_/D_/jtbl_ names are not in the symbol FILES (their address IS their name), so
the first run checked ZERO relocations on a draft whose every callee was correct -- a checker
that looks clean while checking nothing (R32).
- the nearest-symbol fallback used a 0x4000 window and labelled func_8001C9D0 as 'SsGetMute+0xC50';
a wrong label is worse than none. Tightened to 0x200 with an explicit splat-derived fallback.
- MIPS o32 uses REL relocations: THE ADDEND IS IN THE INSTRUCTION, not the reloc entry. Reading it
off the operand string reported 0 for every struct-field/array access and fabricated mismatches
(the +1/+2/+3 signature on func_801F0734 was a byte-array walk, not three symbol errors).
Also refuses to answer confidently when the streams are not index-aligned (shape differs), since a
single inserted instruction shifts every later index -- my own NC produced exactly that phantom.
NC: known-good drafts AGREE (3 and 5 relocs actually checked); a synthetic callee 4 bytes off is
caught while match_one still says MATCH; a misaligned mutant is downgraded to advisory.
resolve_conflicts() had two defects, both found by the wave-J/K/L draft recovery:
(a) THE SYMBOL TABLE STARTED EMPTY -- only draft-vs-draft was compared, so a draft contradicting
a declaration ALREADY IN the .c reached the rebuild and surfaced only as a compile error and
a bisect. src/800.c carries 'extern void func_8001C9D0(void);' (from banked func_8001C2C4)
while three wave-J drafts declared it (s32)/(void *). The TU now seeds the table, and the
drop report names whether the clash is with the TU itself or an earlier draft.
(b) ONE NAMESPACE FOR ALL FILES -- 'seen' was global across the slate, so two drafts landing in
DIFFERENT .c files could not legally disagree about a symbol. Separate TUs are separate
namespaces; the table is now keyed per destination file (R39: over-refusal discards good work).
On the 11 recovered drafts the new check named 7 real TU conflicts that the old one missed
entirely. All 7 were repaired by adopting the TU's declaration verbatim and casting at the use
site -- including a NEW variant: when the TU's prototype takes no argument and the call must pass
one, cast through a function pointer, ((void (*)(s32))func_8001C9D0)(a0). All 11 re-verified
MATCH afterwards, so the cast is byte-identical in every case.
R39 NC: a synthetic draft re-contradicting the TU is still dropped; the repaired slate is 11/11.
build_wave_atlas.py:
- taken-set was a hardcoded 'abcdefghijkl' wave-letter literal -> glob('.run/wave_*_cards.json')
(R33 derive, don't re-derive). NC: 634 -> 726 taken, strict superset, +92 cards from waves m/n
that the literal would have silently re-issued.
- --exclude-bins defaulted to 'main' on the REFUTED link-defect rationale; default now empty and
the help states the real reason (main cannot be gated incrementally; use tools/gate_main.py).
- new --only-bins allow-list: main waves need it, since gate_main rebuilds once per SLATE.
gate_lane.py: an unhandled gate_stage exception produced no JSON line -> v={} -> '0 banked',
indistinguishable from an honest empty gate (cost 2 cycles in P31). Now a non-zero rc or a missing
JSON line is labelled CRASH with the stderr tail, recorded in the results JSON, listed in the
summary as NEVER GATED, and exits non-zero. R39 NC both directions.
- each draft compiles STANDALONE so it carries its own 'typedef struct {...} SVECTOR;'. Once
one such function banks, that typedef lives in src/800.c forever and every later draft
defining its own collides — a C89 duplicate-typedef error, not a byte miss. harvest_verify
already handles this; gate_main did not, and wave L lost a verified-correct draft to it.
- strip_dup_typedefs() drops typedefs the destination file (or an earlier body in the same
batch) already defines, and keeps novel ones.
- R39 NC: drops the duplicate, keeps the novel one, leaves the function body untouched, and is
a strict no-op when there are no duplicates.
- clean_build() ran 'make build' and then sha()'d build/us/SLUS_007.26 off disk. If the build
FAILED (compile error), the PREVIOUS successful binary was still there, so sha() returned the
good hash and the tool reported BYTE-IDENTICAL for a build that never ran.
- that is exactly how it claimed '43 banked' for wave K on a TU that did not compile; the
clean-fleet R22 caught it ([FAIL] main). A verifier that can pass without building is worse
than no verifier.
- fix: rm the output before building, and treat a non-zero make return as no-hash/never-pass.
- also: unbuffered print (a 16-min run looked hung with an empty log) and read the compile
error to name the culprit instead of bisecting at a full clean rebuild per step.
- gate_main reported wave K BYTE-IDENTICAL; the clean fleet R22 then failed [FAIL] main.
Three drafts declared D_80078D98 inconsistently (1 scalar, 2 array) and my conflict checker
could not see the difference: typesig() split on the symbol and kept only the prefix.
- fixed to retain the declarator suffix ('' vs '[]'); NC'd both directions — the wave-K
conflict is now caught, wave J's known answer (34/5) is unchanged.
- BOTH failure modes now documented in the tool: v1 too STRICT (compared parameter names,
discarded 2 good drafts), v2 too COARSE (ignored [], passed a real conflict). R22 caught
what the tool missed, which is exactly why the clean-rebuild rule exists.
- phase log: recorded the night's methodological lesson — every serious stall was an
instrument trusted without a control, never the compiler.
- main CANNOT be gated incrementally: its extract runs the EXE-only psyq_integrate +
ld_interleave steps that REWRITE the .ld, so gate_lane/gate_stage's incremental build
re-runs that on an already-rewritten script and yields a FALSE diff (R22's own rationale).
That cost a night: 4 byte-correct drafts gated 0/4 and I wrote up a nonexistent linker
defect before the null-draft control refuted it.
- gate_main substitutes the whole batch -> make extract BINARY=main -> make build -> compare
SHA. ONE clean build verifies the WHOLE batch (34 banked in one rebuild); bisects on failure
so a single bad draft can't sink the rest.
- handles both main-specific hazards: (1) in-TU cross-draft decl conflicts, resolved greedily
on TYPE SIGNATURES ONLY (comparing parameter names wrongly discards good drafts, R39 — my
own first version did exactly that); (2) stale .s after a revert (extract before resolving).
- NC'd against wave J's known answer: 34 compatible / 5 dropped, matching the hand result.
- deltas[int(new[-8:],16) - int(old[-8:],16)] assumed every symbol is func_XXXXXXXX/D_XXXXXXXX.
A draft whose target calls a PsyQ symbol by name (Square0, RotMatrixY) raised
ValueError and aborted the WHOLE audit — in wave F one such callee took down a 55-draft
batch, and the rename itself (re.subn) had already succeeded.
- a curated pair is a plain 1:1 rename with no address delta: count it as 'named-1:1' and
carry on (R32 — one unparseable pair must never answer for the batch). Delta print made
mixed-key safe.
- verified on the literal incident values: old aborts on 'Square0'; new completes, counting
2 named renames + still bucketing the hex pair by its 0x484c delta
- wave D (48 adapt cards): 47/48 standalone, 45 BANKED across two gates (40 + 5 late-repair).
Operational lesson: build the gate slate AFTER the repair stage lands — the first slate was
built early and 5 rescued drafts needed a second gate.
- wave C (35: 11 tell + 24 weak): 32 banked, 91% gate. Weak lane proven 24/24 on haiku.
- MAIN BLOCKER DIAGNOSED (the night's most valuable finding): main drafts are byte-correct yet
gate 0/4. Byte-diff of the built EXE = exactly 2 bytes in 413,696, NOT in the drafted fn: a
jal at 0x80060E74 retargets func_80061FA8 (game code, 800c2.o) -> firstfile (PsyQ libapi
A66.o). Adding one C fn perturbs symbol resolution between game code and the LINKED PsyQ
archives. main is an INTEGRATION wall, not a matching wall -> its own lane; excluded from
build_wave_atlas by default.
- build_wave_atlas.py now packs by (binary, TU) — the REAL gate-group key, since each group is
one whole-binary rebuild (wave D: 42 drafts / 23 groups = the throughput ceiling).
- 3 NEARs enqueued as grinder fuel incl. func_80183578 at close=1 DELAY-SLOT (§60a precedent).
- the old glob('src/<binary>/*.c') found nothing for main (sources live at src/*.c), so every
main draft grouped under src=None: an R36 consumer-blindness, latent because main has never
been wave-gated (main is 0.5% matched, the largest coherent mass left)
- derive from corpus.stubs()[..].path instead; NC'd 3 ways: still-open wave-C drafts 3/3 agree,
overlay sample 96/96 agree (no regression), main now resolves None -> src/800.c
- tools/build_wave_atlas.py: wave selection off the frontier atlas, optimized for GATE
THROUGHPUT (gate cost scales with (binary,TU) groups, not drafts: wave C was 1.3 drafts per
rebuild; atlas selection concentrates to ~96) and weighted toward instruction mass
- wave C: 35 cards (11 tell + 24 weak) -> 35/35 standalone (re-verified independently, R14)
-> 32 banked / 3 near, 91% gate, 0 symbol failures (Law 4 prevention worked)
- weak lane proven for the first time: 24/24 on haiku; 890 candidates remain
- reach measured: 32 exemplars, 8 with sharers, x2 each => ~1.25x effective (the x134
era ended in P25/29/30) -> throughput, not leverage, is now the lever
- tools/build_wave.py (pool=adapt|weak, corpus-derived open-stub filter, R35 gate guard)
- 3 self-inflicted instrument defects found+fixed+NC'd (P9, recorded not buried):
pgrep self-match via shell=True; corpus.stubs() is addr->Stub not names (nearly
declared both card pools spent); a wave fired on hand-typed placeholder cards (stopped)
- STRATEGIC: card lanes are ~0.23% of open ins/wave; the Atlas's head-crack bucket is
1,276 groups / 186k ins with high-reach groups up to 265 instances -> retarget waves
at atlas groups next
- tools/warmstart.py: --from-banked walks a banked exemplar's family's open
members, builds remapped proven-body drafts (symbol_map + build_draft), and
STREAM-classifies member-vs-seed with zero compiles; enqueues ONLY permuter-
shaped work (bucket==permuter or LENGTH-DRIFT |delta|<=2) as backlog records;
--lenmiss ingests T8's 49-route. Armed live: 59 enqueued, 120 refused by the
stream filter (the anti-92%-wasted-CPU discipline)
- grinder patch NOT needed: candidates() deliberately keeps unclassified
records ('unknown is not a reason to skip'), so pre-filtered enqueues flow
as-is — documented in the feeder docstring (YAGNI honored)
- family_cousins --weak-cards: 954 seeded-crack cards from the never-consumed
0.70-0.85 band, ins-ranked, §168 laws embedded, model-routed haiku 804 /
v3 43 / sonnet 86 / opus 21 (cheap tiers dominate), 0 unresolved .s
- match_one --emit-streams (additive; stdout-identity NC'd): word streams for the
len lane
- family_align.addr_true_rel: reloc-vs-constant range discriminator — FULL
conservative set kept for pair semantics (NC-1 157/157 regression), address-
true subset for indel eligibility only (a constant li-cluster must not read as
reloc-in-indel); synthetic probes green both directions
- tools/len_tells.py: aligned classification + §172b tell tagging (EXTPAIR/
SELECT/NOP) on target-side indels; detectors imported from atlas_features
(R33); cookbook text embedded in cards
- tools/lenmiss_route.py: pool-parallel (A8) — 587 audit LEN rows re-verified
live + routed in 24s: redraft 345 (frac>0.35, APPEND-ONLY backlog
reclassification — near-miss metrics stop lying) / permuter-length 49 (grinder
fuel) / cards 192 incl 14 tell-tagged (the audit's own detectors had emitted
ZERO) / mechanical 0 — an HONEST NULL: stored drafts rarely get constants
wrong; LEN drift is shape, family_align's value here is classifier/detector
- R32 accounting 587/587
- tools/family_align.py (NEW module — classify_member's return contract untouched,
the remap_hseq silent-pass trap avoided by design): SequenceMatcher alignment
over FC.tok streams; li-cluster reconstructor (lui/lui+addiu/lui+ori/li-from-$0
chains, split-cluster absorb for the rs-changed addiu partner); verdicts
LEN-LI/LEN-NOP/LEN-JTBL/LEN-STRUCT/STRUCT-ALIGNED/PURE/IMM; aligned imm engine
mirroring imm_map_tier1 (ordinal deliberately out in v1)
- NC-1 verdict-equivalence 157/157 banked pairs — the NC caught two real gaps:
R-type non-shift sa diffs are STRUCT; registers tested BEFORE the reloc skip
(a reloc-slot word with a different register is STRUCT). NC-2 parity 21/21
- R37 PROBE REFUTED the planned mechanical driver before it was built: 0/26
LI-ONLY cards classify mechanically (regfields x19) — cousins are 0.85-similar
DIFFERENT functions; §168 law 1 re-derived by measurement; no driver written
- family_align re-scoped: its consumer is T8's LEN+N near-miss pile (draft vs
its OWN target = same function); reloc-vs-constant range discriminator parked
for T8. decision-log entry (R31)
- tools/plumbing_groups.py: derives the honest still-open pool from the classified
ledgers (R38) — '1,217 PLUMBING' collapsed to 237 (SELF 109 / CALLEE 48 / OTHER
48 / DATA 32)
- recover_integration: PER-GROUP ISOLATION (git-checkout binary TUs between groups
— one TU-stage edit was poisoning every other group's whole-binary gate with a
phantom shared error; per-group banked_from_source capture) + new stages
'macro-externs' (§121 draft-tier, via family_sweep.macro_def_sig_map, R33) and
'tu-scope' (§103 STU binary-tier, the sweep-only lever)
- the probe (ov_SC03_107): raw 0/14 -> root-caused (poisoning + stale seed
symbols; rtu_match MATCHes them — blind to reloc names, R34) -> symfix-first
-> 9/14 BANKED (64%)
- sweep finding (Law 3): the no-draft majority (ov_SC02_037 44/44, most of
ov_MAIN_012) had verdicts from transient sweep remaps never persisted — family-
lane fuel, not recovery fuel; the stored-draft class is consumed
- cookbook §173 (symfix-first / per-group isolation / verdicts-without-drafts);
index 518 green; R22 clean fleet 213/213; phase total 17 banked @ 0 agent tokens
- decision-log: the P31 re-charter entry (organize-before-grind; R37/R38/R39
ratified at gate-1) per R31
- harvest_verify.py: import guard — a bare import now RAISES loud instead of
running a full gate (CLI unchanged, verified both directions)
- sig-resident: bootstrap boundary artifacts fixed (fused +0 data word with
func_800CEDFC; func_800D33E0 dropped past a glued tail) -> ELF-seeded per the
S45 pattern, exactly 145 fns; true denominator confirmed 145 (progress was
right); audit-corpus 0 PHANTOM + 0 TRUNCATED; all three oracles agree
- family maps regenerated at HEAD commit:2161: 11,025 open non-main members
reconciles EXACTLY with 12,059 - main 1,034 (102 stale phantoms cleared);
adapt cards 704, aprop cards 204 (full emission)
- main fuel-gap finding: 2,001/2,002 main stubs already have cached Ghidra-C
(only func_80049600 missing) — the roadmap '0/2,096' note was stale
- tools-health OK (dedup 2,063/0; C1 254,521/254,521; audit-digest green)
- tools/cc1_dumps.sh: run the pinned cc1 with -dr/-ds/-dj/-dc/-dl/-dg and count standalone
(use (reg)) insns in the .combine dump — each is one 8-byte never-referenced reload slot.
The NEAR-2 residual = 12 such orphans vs the target's 16, now ENUMERATED pseudo-by-pseudo
instead of inferred from ablations.
- The orphan mechanism, exact (combine.c:10835 + mips.md force_not_mem): every short-mem read
is a movhi+shift-pair triple; the fold orphans its ashift temp iff the HI reg carries an
extra HImode use and the death-note walk hits a label/jump. Single-use loads never orphan;
the head can never orphan (walk reaches insn 0).
- The zero-code +4 factory is byte-refuted: 18 new probe families x 3 placements (s16
respellings, cast truncations, <<16>>16 factories, placement sweeps, loop t/n, dossier-file
re-sweep) all land at vars=224-same-bytes or drift. p_optr reproduces the target's exact
16-orphan frame at +7 insns — opacity that defeats cse equally blinds num_sign_bit_copies;
fold-const closes the tree level. Three canonicalizers, one wall.
- Dossier NOTES.md updated with the S50 ledger; backlog klass -> WALL(P32) for both names.
- NOT banked: no 0-closeness draft exists. The dial draft (NEAR 2) remains the floor.