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 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.
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.
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.
- waves C-N: ~448 banked, stubs 12,059 -> 11,549, fleet 95.4% instr, 213/213 after every
batch, main 0 -> 175 matched.
- records the three campaign-changing findings (main is open and was never hard; gate-group
packing is the throughput lever; match_one verifies SHAPE not SYMBOL IDENTITY), the tools
built/fixed, the prioritized open work, and the methodological lesson.
- cron be8fb48c cancelled; nothing in flight at handoff.
- records the night's totals and, more importantly, HOW to run the main lane: gate_main.py
batch clean-rebuild, never gate_lane/gate_stage (incremental + .ld rewrite = false diff).
- notes the one mechanical improvement left (strip duplicate typedefs on substitution) and
that the ~10 conflict-dropped main drafts are verified-correct and recoverable.
- 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.
- first full wave against the main EXE: 40 atlas mass cards -> 39/40 standalone (98%), ALL haiku.
main drafts exactly like an overlay; the only special handling is the gate path.
- gated via the clean-rebuild batch path: substitute -> make extract BINARY=main -> make build
BINARY=main -> 143dbb89 BYTE-IDENTICAL; then full-fleet R22 213/213. main stubs 1030 -> 996.
- NEW CLASS: in-TU cross-draft declaration conflicts. Batching N drafts into ONE .c means their
externs must agree with EACH OTHER (D_800A4ED4 s16-vs-u16; func_8001C9D0 void/void*/s32).
Resolved greedily (keep-in-order, drop incompatible) at a cost of 5 recoverable drafts.
- the recovery lever, proven on func_80037368: adopt the shared header's decl VERBATIM
(extern u8 D_80076251;) and adapt at the USE site ((&D_80076251)[i]) instead of redeclaring.
- NOTE on my own tooling: my first conflict detector compared parameter NAMES and wrongly
dropped 2 good drafts ((s32 *_) vs (s32 *)); comparing type signatures only recovered them.
Second time tonight a refusal check of mine discarded good work (R39).
- I reported 'main is blocked on a link-resolution defect' with confidence. REFUTED by the
control I should have run first: with src/ fully reverted and NO draft, make build BINARY=main
still produced the same c4546248 and the identical 2-byte jal diff. A defect that reproduces
with zero drafts is not caused by drafts.
- byte-proven both ways: 'make extract BINARY=main && make build BINARY=main' -> 143dbb89
BYTE-IDENTICAL (twice); 'make build' alone -> c4546248 + the 2-byte diff, deterministically.
- cause: main's extract runs the EXE-only psyq_integrate + ld_interleave steps that REWRITE the
linker script; gate_stage/harvest_verify build without re-extracting, so main gates against a
stale .ld. Overlays have no such step and are unaffected (all overlay banks are R22-clean).
- implication: main's 79,510 ins @ 0.5% are behind a TOOLING gap, not a wall. Fix the main gate
path to re-extract, then re-gate the 4 preserved drafts in .run/wave_p31e/main/.
- the earlier 'archive-member selection' evidence is a downstream symptom of the stale .ld.
- wave H: 40 atlas mass cards on ov_SC02_005 -> 38/40 standalone (95%), 34 banked, ONE gate
group. R22 213/213. stubs 11,788 -> 11,751. fleet 95.4%.
- §175 (NEW): a register pin to a CALLER-SAVED reg is not a scheduling hint, it changes program
meaning. func_80182EB0: value written before a jal and read after it; the $2 pin let gcc treat
the pre-call store as dead across the call and SILENTLY DELETE addiu v0,zero,-1 (49 vs 50 ins),
post-call read = garbage. Fix was to DROP the pin and kill the cross-call live range in C.
Rule: never pin a caller-saved reg to a value whose live range crosses a jal — use a
callee-saved $s0-$s7 (safe by ABI, the §17 lever) or restructure. A one-instruction count
mismatch on a caller-saved-pinned draft is this bug until proven otherwise.
- 2 NEARs enqueued with full diagnoses (prologue $ra-save scheduling; S3 chain-priority).
- call site is libmcrd1/func_80060D9C.s +0xD8, referencing the callee BY NAME (jal func_80061FA8)
- our build resolves that name 0x2A0 later, which is EXACTLY the .text size of build/src/800c2.o,
the object starting at 0x80061FA8 -> the name binds to the END of the object (the next
section's first symbol, firstfile in psyq/apicard/A66.o) instead of its start
- 3 ordered hypotheses recorded (duplicate func_80061FA8/.NON_MATCHING pair; object dropped or
reordered when src/800.o gains an undefined ref; an absolute symbol shadowing the object's)
- everything to test it is in the map + the 4 preserved main drafts
- I withheld 3 drafts on symfix STALE/AMBIGUOUS flags; all 3 already used the CORRECT PsyQ
names (RotMatrixY, RotTransSV). What symfix called 'draft-only symbols' were LOCAL
identifiers: a typedef, two inline-asm macro names, two local struct typedefs.
- gated unchanged -> 3/3 banked. wave G closes 35/36.
- two symfix defects behind it: (a) local typedef/macro names counted as symbol references;
(b) draft-symbol extraction misses some extern forms, so a name the draft DOES declare
still reports as asm-only.
- OPERATING RULE: symfix is advisory, the whole-binary gate is the arbiter. Never withhold a
standalone-MATCH draft on a symfix flag alone (R39 applied to my own judgement).
- wave G: 36 atlas mass cards on ov_SC03_006 -> 36/36 standalone (100%, independently
re-verified), 32 banked of 33 gated, ONE gate group. R22 213/213. stubs -> 11,788.
- two TU-packed waves now confirm the shape: ~1 rebuild per wave instead of 23.
- law 1b added to the wave prompt: agents reconstruct CODE at 91-100% but INVENT PsyQ symbol
names (S80131E00->Square0, Blk20_...->RotMatrixY, SRM_...->RotTransSV). Dangerous because
match_one MASKS relocations -> a wrong callee name still reports MATCH; only the
whole-binary gate + symbol audit catch it, after a wasted rebuild.
- 3 wave-G drafts held by that audit (would previously have crashed it pre-commit:2330).
- wave F: 60 atlas 'mass' cards (fresh crack, NO seed body) -> 55/60 standalone pre-repair
(59/60 post-repair), 50 BANKED of 53 gated, in a SINGLE gate group. R22 213/213.
- the throughput thesis is confirmed: wave D banked 45 across 23 whole-binary rebuilds;
wave F banked 50 across 1. gate cost scales with (binary,TU) groups, not drafts.
- the fresh-crack lane converts like the seeded lanes (~91%) => the atlas's ~7k draftable
candidates are all reachable, not just those resembling a prior match.
- grinder: 0 banked across 8 seeds / 11 ILS cycles (band exhausted); re-exposed the Phase-22
split-file blindness (no .s under md_MAIN_027).
- KNOWN DEFECT logged: aprop_symfix crashes on non-hex symbols (int('Square0',16)) — one
PsyQ-named callee aborts the whole audit; needs 1:1-rename handling + per-pair isolation.
- stubs 11,876 -> 11,826
- 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).
- 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)
- PhaseEnd_Phase30.md written; CURRENT_PHASE.md archived to phase-ends/logs/Phase30.md (R19)
- 25 sessions (S26-S50), 947 commits, stubs 28,296 -> 12,059, dedup 2,061/0
- milestone met on both clauses: >=95% instr AND every remaining stub on a named ledger
- v1.28.0 -> v1.29.0
The §172a/§172b tells + repaired instruments swept over all 892 open near-misses:
- 33/95 stored drafts re-verified MATCH and banked through the whole-binary gate
(aprop_symfix caught 40/108 carrying stale seed symbols before gating — §171 at scale)
- 19/20 hand/mech fixes banked: four pure lhu<->lh s16 flips; the lhu+sltiu->lh+slti
shared-global quadruplet (D_80126B5E/B66/CB0, D_80126CB0 are s16 FLEET-WIDE); one xor-eq
rewrite; 11 per-location literal swaps (mask/threshold constants from sibling binaries)
- 1 refusal (func_8017EE78) stays as redraft fuel
Stubs 12,111 -> 12,059. Fleet 95.3% instr / 90.0% distinct / 96.68% fn-count.
Veins mapped for next waves: ~400 LEN+N drafts, 13 ambiguous-symbol, 7 multi-literal.
Audit ledger: .run/c294/audit_results.json (classifier derives from match_one's own sig).
Final S50 state: 307 instances banked, stubs 12,468 -> 12,161, fleet 95.3% instr / 90.0%
distinct / 96.65% fn-count. R22 clean rebuild 4x, check-all 213/213 every time.
- tools/aprop_autodraft.py + tools/draft_prechecks.py: seed body + symbol_map + a MINIMAL
synthesized preamble. The seed's decl layer never travels — that layer is family_sweep's
dominant failure (331 of 458 S49 verdicts). 256 banked at zero agent tokens, against the
~20M the same work would have cost as a wave.
- Macro seeds (567 of 1196 members, all 3737 de-macroize) take the DEFINITION only; the block
stays the decl source. Pasting it whole measured 28% vs inline's 68% — func_8016AB6C's macro
is 1,891 lines of which 108 are the function.
- IMM is a second engine, not a wall: T2a's imm_map_tier1 resolves a per-location LITERAL like
symbol_map resolves a per-location SYMBOL. 131 of 275 IMM members resolve.
- draft_prechecks negative-controlled against ALL 205 banked drafts: zero false positives,
catches 39 of 67 known failures. That control found two bugs in the checks themselves —
C89 `f()` declares UNSPECIFIED parameters (not zero), and a member's own definition read as
a call to itself. Conservative by design: a pre-check that discards good drafts is worse
than one that lets a few builds fail.
- The A-prop pool is now priced exactly: PURE 437/37,376 ins, IMM 275/8,849, STRUCT 238/4,259.
- Cookbook §171a; SETUP rows; CURRENT_PHASE S50 FINAL checkpoint.
- The blocker was carried as "one missing file-scope extern gates 83 PURE members". Both
halves were wrong (R14): corpus.stubs says 4 open members, and D_801ED98C is a DEFINED
const Blk8 whose rodata lives inside the member's own nonmatchings .s — replacing the stub
deletes the data with it. gather_externs can carry an extern DECL, never a DEFINITION,
which is why it reported "no file-scope decl" for a symbol md_SC05_023 defines on line 114.
- Fix: paste typedef + const definition + body per sibling (data bytes verified identical
across md_SC05_024/025/028/029). 4/4 banked.
- aprop_symfix: new `local-only` class — draft-DEFINED identifiers that merely carry a
vram-looking suffix (Blk8_…, S8_…, L_call_…) are not stale symbols. Measured: that is every
non-clean case in the whole wave-7a/7b stored-draft residue, which holds ZERO stale-symbol
recoveries (a clean negative result — the defect was A-prop-specific).
- cookbook index regenerated (tools-health fails closed on a stale index — it caught §171).
- R22 clean rebuild: check-all 213 passed, 0 failed of 213. Stubs 12,445 -> 12,441.
- REFUTES §170's open hypothesis (batched cards concentrate members into one TU ⇒ §169
collision): 5-draft groups banked 5/5; 11 of 35 unbanked drafts were already one-per-TU;
and the two "concentrated" groups banked 12/12 and 10/10 once the real defect was fixed.
- The cause: a per-location data symbol carried out of the seed body unrebased. match_one
compares instruction ENCODINGS and is blind to a relocation's target NAME, so it scores
MATCH standalone and dies at link in the host TU. 24 of 24 concentrated failures, all 1:1
rewritable at one constant vram delta (0x4128).
- tools/aprop_symfix.py: audit + --fix, emits a gate_lane-shaped slate; deterministic and
build-free, so it runs BEFORE the gate. The R34 second oracle for the class match_one
cannot see.
- family_cousins.py --aprop-cards: members now carry sym_map, the explicit {seed -> member}
renames, read from the seed's C BODY (a matched seed has no .s of its own) vs the member's
.s. Two case-mismatch defects fixed while wiring it (sig lowercase vs splat uppercase).
- 23/24 banked. Stubs 12,468 -> 12,445. Fleet 95.2% instr / 89.9% distinct / 96.57% fn.
R22 clean rebuild: check-all 213 passed, 0 failed of 213. dedup 2,043/0.
- A-prop's true conversion is 87% (79/91); the 320 batched members are unblocked.
- Cookbook §171 + §170 struck in place; SETUP row; decision-log (R31).