The third agent produced no bank and a precise refutation plus a generator, which is the deliverable the brief asks for.
- it refuted the a2 hypothesis on its own body: git grep returns 1,770 declarations in two forms, both (s32 a0), no (void)
anywhere, and the pin is on a local rather than the parameter. Three lever-free spellings reach the target's complete
register assignment, so the class is reachable from plain C.
- its best is score 2, class ORDER, 89/89 instructions, every register correct, one displaced bystander store. The target's
sw sits inside the lbu->addiu window and that position is forced: anti_dependence and true_dependence (sched.c:817/845)
both hold, so the store can neither hoist nor sink. Post-sched1 stream and reg_live_length then match ours, leaving
reg_n_refs — computed by flow on the pre-combine RTL — as the only remaining input.
- R18 moves one simple statement to each other position in its own block, up to six away. R9's adjacent swap is the special
case; the distance is the point. It costs no instruction where R7's LOOP notes are a full sched1 barrier and always cost
one displaced insn, so R18 is ranked ahead of R7 in every class.
- two wrong spellings before the known-true check passed: identifier-disjointness as a requirement offered three candidates
and none of them the agent's (it is only an ordering preference now — byte-identical output is the same program, so the
oracle is the whole correctness proof, which is R9's own footing), and a blank line counted as an obstacle, so the
generator never offered the very move it was written from. It now reproduces that body exactly: bystander @21->17.
- delever_pack.py now writes each trace candidate's residual class beside its score, from the agent's method note: a bare
number hid that a move had already turned this body's residual from REG into ORDER.
- delever --selftest OK (3 new controls incl. the nested-block refusal); delever_search --selftest OK; tool_census OK.
- R16 writes a local whose only assignment is one integer literal at every use and deletes it. R6 stops at a temp read
exactly once, so a holder read four times was invisible to the search and its whole family with it. Deleting it is
byte-neutral alone but removes a quantity from the block, which is what lets the next move reach the allocator.
- R17 splits a run of consecutive same-literal assignments by moving the nearest differently-valued one into it, at each
interior split point. find_free_reg's live-range scan (local-alloc.c:2109-2110): while the two constants' ranges are
disjoint they share a caller-saved register; splitting makes the first live across the second and it takes another colour.
- known-true check: on the seed that keeps func_80168828's semantically-forced $4 pin, R16 then R17 reaches
score 0 (OTHER; mine 108 ins, target 108) — MATCH at three of the six offered split points, in ten compiles where the
blind search needed 2,271.
- the engine selftest's caller-saved assertion is now the ordering invariant (every targeted lever before every blind
family) rather than a fixed window widened once per new generator.
- delever --selftest OK (4 new controls); delever_search --selftest OK; tool_census --check OK; SETUP row (R21).
The harvest half of the one-at-a-time loop (R16): agent a1's reading of func_80156044 is now a move the engine can make
on any body, so the remaining head classes get it for free.
- R15 sinks the statement AFTER an if/else chain into every arm and deletes the variables it consumed:
`if (c) { v = e1; } else { v = e2; } w = f(v);` -> `if (c) { w = f(e1); } else { w = f(e2); }`.
- it is a REGISTER move, not a scheduling one. A value set in every arm and read after the merge is a cross-block pseudo
local-alloc never gives a quantity (local-alloc.c:472, next_qty reset at :517), so the arm holds two quantities and
takes block_alloc's unrolled case 2 (:1499-1502, qty_compare :1578-1596). Sinking makes it a third block-local
quantity, and case 3 (:1491-1496) falls through into case 2 and applies that comparison a second time, undoing its own
exchange — the two caller-saved colours swap. It also takes the value out of global.c, where set_preference
(global.c:1535+) had given it a copy preference through the merge result's argument copy.
- applicability is checked, never assumed: each consumed variable must be assigned exactly once in every arm by a simple
statement, appear in the merge statement, and occur nowhere else in the function.
- if_chains() counts a line's CLOSING braces before its opening ones. On a `} else if (...) {` line the two net to zero
and the first version's depth counter never closed the arm — the generator found 0 candidates on the very body it was
written from. Caught by running it on that known-true case before believing it.
- ranked third in REG-caller / REG-mixed / COUNT; the engine selftest's "R5 in the first three" assertion widened to
"R5 and R15 in the first four" rather than de-ranking the new move.
- verified: delever --selftest OK (3 new controls: a variable read after the merge, a variable one arm does not set, the
brace walk's three arms); delever_search --selftest OK; and the known-true check — R15 run on func_80156044's
pre-bank text emits the agent's crack and `--try` scores it
`score 0 (OTHER; mine 74 ins, target 74) — MATCH`.
- SETUP row rewritten (R21), kit corpus regenerated, tool_census --check OK (371 copies + 30 pointers, 0 gaps).
- no src/ change in this commit; the sweep of the other 56 head classes follows.
The pin/hint removal's COUNT OVER TIME is a deliverable in its own right: the post-100% chart, the story's spine, a wiki
page, and the day-one rule the decomp-architect kit should carry. It is only ever counted by the phase that removes it, so
it has to be captured while the work happens — a census is a moment.
- docs/levers.md: the taxonomy (A pins · B1-B4 barriers/launders/keep-alives/hand-placed instructions · B5 GTE, not a
lever · C volatile · D bare register · E asm-label aliases · F builtins · G attributes); a GENERATED §2; how the levers
got in phase by phase (P18's pin toolkit took the wave close-rate 33% -> 56% -> 90%; the family engine then copied every
lever across up to 134 overlays, which is why 1,758 distinct bodies are 12,578); the ladder with each rung's measured
yield; §5 the prevent-vs-defer argument; §6 what the wiki, the kit and the story each take.
- tools/lever_progress.py: --snapshot appends a milestone row (the census's totals by class + HEAD) to
docs/lever-progress.tsv and re-renders the document; the campaign table is derived from the de-lever ledger on every
render and scored as state TRANSITIONS, so the rung that FINISHES a body gets the credit (counting first-rows-only
reported rung R's 134-body batch as zero); --check refuses a series that is not this tree's.
- the numbers, generated: 21,061 sites removed or rewritten across 17,119 bodies against 33,957 still standing — 38% of
the class A/B population came off with no understanding at all, which is the evidence behind §5's rule:
BAN THE SILENCE, NOT THE LEVER (a lever is allowed and is a marked, ledgered, published debt from the first bank, with a
one-compile bank-time trial; never block the byte gate on it).
- Reference-index row (doc_links OK), SETUP row, tool dictionary row, CURRENT_PHASE entry for the story.
THE INSTRUMENT (R40). Two campaigns returned "0 of 16" with a straight face; both were the harness. The target object had
been assembled from a DISASSEMBLY LISTING, which is a second toolchain with its own answers:
- objdump prints the pseudo-instruction `move` for `addu rX,rY,$zero`; gas assembles `move` as `or` — 24 wrong words in
one 234-instruction function, silently;
- a listing's %hi/%lo pairs come back RESOLVED with no relocation, while every candidate carries one, and the masked
scorer compares reloc operands.
The permuter therefore scored 28 for a body that IS byte-identical: score 0 was unreachable and every NO-MATCH was its own.
- tools/delever_permute.py: the target is now the tree's OWN (levered) body compiled by the build's tail into a
one-function object — the candidates' relocations by construction — and `match_one` must call that body a MATCH against
the ROM listing before the search starts (R34 keeps it from being circular). Base score for the tree's own body: 0.
- tools/p16_permute.py `setup(target_o=)` + tools/permuter_ils.py `--target-o` (defaults unchanged).
- `--positive-control TU FN`: perturb a matching body by one commutative swap, require the permuter back to 0.
- tools/verbatim_target_s.py --gas now VERIFIES itself: assemble, disassemble, compare word by word with the image,
`.word 0x…`-patch what does not reproduce (24 in that function), REFUSE what still disagrees. The listing is a public
artifact (decomp.me) and was wrong for every function containing a `move`.
THE PROFILE. The weight profile now comes from the register a needed pin names, not just the site kind: callee-saved
($16-$23) is an allocation-order residual -> regalloc; caller-saved ($2/$3/$4-$7) is not -> cse. Read from the bytes: the
residual on func_80163EC8 (`register … __asm__("$2")`) is `and v0,v1,v0` against `and v0,v0,v1` — the operand order of one
`&` — and the regalloc profile weights perm_commutative 2.0 while cse weights it 40.0.
RUNG R (tools/delever.py --recipes): the cookbook's byte-neutral shape recipes, mechanically, seeded with the body's
lever-free text — R2 the formerly-pinned declarations permuted, R4 one moved through the whole declaration run, R3 an
initializer split placed after the run (C89), R5 the operand order of one commutative operator (the caller-saved lever, and
the only recipe needing no pinned declaration). Identity control on both the splice and the oracle before any verdict;
markers scrubbed within the banked body's own span only; selftest cases on a fixture whose answers are known by hand.
Also: a threading race in the site cache published the empty dict before filling it and made a whole batch report
"no site in this TU"; the file-scope asm dropper took an asm-LABEL clause for a statement. SETUP + dictionary rows.
- tools/delever_permute.py: one exemplar per RESIDUE text class from the ledger (copies desc, needed asc) prepared as a
single-function TU (delever's rung-A rewrite; other definitions -> prototypes; shared-header includes -> their prototypes;
INCLUDE_ASM and file-scope asm dropped; the build's own CPPFLAGS through cpp -P), the target regenerated from the ROM image in
BOTH forms (--gas for target.o, splat for match_one), permuter_ils with the profile from the NEEDED kinds, a winner banked only
through delever --apply-body + the GTE re-fold. Scratch/winners keyed alias+fn (R48).
- the control (R39/R56, new): every attempt first requires the LEVERED body to be match_one MATCH against the regenerated target,
then records the lever-free body's distance. --calibrate --limit 12: 12 of 12 MATCH; starting distance min 8 / median 78 / max 276.
- delever: ("B","gte-lever") joins REMOVABLE — a direct statement's clobbers reset to its canonical set, a variant-macro use pointed
at the canonical macro whose name comes from the variant definition's SIGNATURE (gte_rt_m -> gte_rtv0tr, not Sony's gte_rt);
462 of 462 gte-lever sites now offered to the ladder, 0 before. gte_consolidate.canonical_match() is the one reader of the
canonical table (R33), direct_rewrite refactored onto it; both selftests green.
- p16_permute.setup(outdir=) + permuter_ils --pd: a scratch dir keyed by the caller, defaults unchanged.
- four harness defects found by running it: the splat listing is not assemblable (R98 in a second place); pycparser rejects
__attribute__ and the permuter then silently permutes nothing; include_asm.h injects a file-scope .include "labels.inc" that
collides with the permuter's own macro.inc; an asm-LABEL clause is not an asm statement (a bare scan ate one and left a headless
K&R body). SETUP row (R21), dictionary row (R87), .gitignore allowlist for the outcomes ledger.
- run 5 under BRIEF-run5.md (the D6 guardrails restated): ProjectArchitect 2.0 §1–§9 then the kit Steps 0–10, unattended from
answers.md; 4 trailer-free commits; stopped_at null; manifest 57 == 57 (docs/inherited-record.md included); G 67; seeds 34/34;
check-ignore 9/9 + 5/5; placeholder audit clean; the audit exits 0 in the throwaway; PhaseEnd_Phase0.5 + the archived log; tree
clean; settings/statusline/memory/ghidra/dumps/~/.claude guardrails unchanged
- the agent's notes, acted on: the installer named pa-overlays.md without its templates/ folder at Steps 6 and 8 → `$KIT/templates/…`;
its own write-scope slip (two staging files under /tmp, deleted) recorded; the manifest rule's silence on a file the installer
creates and deletes itself noted
- the judge: my run-5 BEFORE snapshot stripped the first porcelain line's status space and so failed to exclude a deletion under the
dry-run's own directory (the false flag of the first judge pass — re-filtered, not re-snapshotted); the judge's reader crashed on an
empty dirty set (a one-token line) → tolerated; run-4 evidence kept under the .run4 suffix
- wiki page (five dry-runs, the run-5 row), Home/Tools/README/SETUP rows: five runs
- 21 read-only Opus slices over the 26 worklogs (30,540 lines read; the three giants by line range), briefed by
.run/P33.5/log-mining/BRIEF.md with the "already banked?" grep protocol: 777 candidates, 634 already banked, 143 NEW;
every cited log line verified to exist by harvest.py and read; 142 banked + 1 dropped (the miner's own low-value verdict)
- decomp-kernels.md: twelve kernels DK-69–DK-80 (instrument blind spots; verdict staleness + the health suite; what earns
belief; denominators/units/labels; leverage vs tractability + campaign scoping; models and prompts; the unattended run;
agents and the tree; edits that keep proofs; the search harness + the compiler as evidence; maintaining the knowledge base;
hosts and services), each provenance line generated from the slices' cited worklog lines (bank.py); section 8 retitled;
Coverage "In all: DK-1 … DK-80"; every count mention → 80 (kit README, methodology, SETUP row, wiki page)
- docs/accelerators.md "P33.5 S92" (two accelerators: a distillation ships with a coverage check; the end-of-project worklog
pass recovers the fixed-but-never-generalised lessons — 1 in 5 here) → cited by the twelve kernels (kit_coverage: 59 entries,
42 cited + 15 dispositioned, 0 UNCOVERED; the matcher now requires an un-numbered entry's FULL group text);
docs/decision-log.md "P33.5 S92" (R31: the question, the measurement, the pivot, the 82%/18% why, the hindsight path)
- evidence tracked under .run/P33.5/log-mining/ (BRIEF, 21 slice reports, HARVEST.md, HARVEST_TABLE.md, harvest.py, bank.py;
a dated .gitignore block); expected-manifest +1 (docs/inherited-record.md); make kit-corpus regenerated the record copies
- verify: tool_census --check OK (358 copies); kit_coverage OK; kit_lint OK (0 leaks over 436 files); doc_links --strict OK;
wiki_render --selftest 32/0; audit_public OK on the new files; 80 DK ids cited, 0 dangling
- decomp-architect/corpus/record/: the how-to (13), decision-log, accelerators, retrospective, story, wave-playbook, effort-map,
gen3-standards, gen3-handoff, DIGEST and every PhaseEnd (34) verbatim behind an authored front page (what each is, how to
read it, what is NOT there — the phase logs, R19 — and that the mining pass is their distillation); tool_census: RECORD_SOURCES
+ record_dest + the third corpus in plan/write/check (358 copies + 28 pointers, --check OK); kit_lint exempts corpus/record;
SETUP Step 6 gains 2c docs/inherited-record.md (+ the verify line; expected-manifest +1); ops-setup/README/tree/methodology/
wiki page/Home/Tools page/README bullet/SETUP row: "two dictionaries" → three
- tools/kit_coverage.py (+ config/kit_coverage_map.tsv): derives R1..R83 from DIGEST §3 (asserted contiguous) and the 58
accelerator entries (headings + numbered items), asserts each is cited by a provenance line of the registry seed / the
kernels or dispositioned (G / DK / FOLDED:G / ENV / PA / SEED: / KIT: / RECORD / COOKBOOK / NOT-PORTABLE; unknown ids
refused); first run: 26 uncited rules + 21 uncited entries → DK-66 (a ledger's tie-break, a checker's widening and a blanket
commit are part of the instrument — R70/R80/R52), DK-67 (the ignore file's directory-form wall — S91 (1)), DK-68 (a
summarised signal is a claim, not ground truth — R14/R66) in a new kernels section 8 (the museum is 9; "In all" 68) + 41
dispositions (15 PA, 3 ENV, folds into G6/G18/G38/G66/DK-12/19/20/22/25/26/31/35/44/45/46/57/61, 1 KIT template, 1 COOKBOOK);
now 0 UNCOVERED on both populations; wired into tools-health after tool_census --check; SETUP row + dictionary row
- verify: tool_census --check OK; kit_coverage OK (rules 57 cited + 26 dispositioned / 83; accelerators 41 + 15 / 58);
kit_lint OK; doc_links --strict OK; wiki_render --selftest 32 pages / 0 unlisted
- kit: DK-65 "types are a banking lever and a width lever, not a byte lever" (kernel, when, cost, calibration fence, provenance;
the kernels file now states its own total "In all: DK-1 … DK-65" and SETUP Step 6 compares grep -c against it instead of a typed
64); intake row 6's milestone gains the canonical type layer (one definition per shape, widths proven by the bytes at bank time,
a bank refused for a duplicate definition or a raw address cast) + the type tools moved forward + G62/DK-65 in its columns; row 10
reads "short if Part C and Phase 6's type layer held"; G62 extended with the bank-time clause (G1–G67 kept); tool_dictionary:
lift_types + canon_sig_reconcile P10 → P6 and the five type tools' need-keys name the type layer at Phase 6 (the phase column is
one token — the corpus dir derives from it); the cookbook front page's type-verdict sentence (a NAME never moves a byte, a WIDTH
or SIGNEDNESS is the one place a type does, the permuter cannot reach it); the methodology's "Types — the two-sided verdict"
paragraph; the kit README's table + tree now name the two dictionaries and layout-contract.md; make kit-corpus regenerated the
tool index, the MANIFEST and the corpora (302 copies + 28 pointers)
- wiki: docs/wiki/Start-a-new-decomp-project.md — the three steps, what it installs / does not (the two dictionaries), the ladder
(11 rows), the five AI-use rules, the six inversions (raw casts → declared symbols; DK-65), the compiler question, the accelerators
one line each (58 rows), the four dry-runs, how it is kept honest; sidebar + Home rows; Tools-from-this-project rows for the kit
and the tool index; README bullets for both; SETUP row for decomp-architect/ (R21); Where-the-project-goes-next links the page and
records the probe PASS (2026-09-07); phase34-seed: task 0 PASSED, the kit carried whole through the flip and split later
- verify: wiki_render --selftest 12/12 + reachability 32 pages / 0 unlisted; doc_links --strict 58 documents / 451 links / 0 pending /
0 broken / coverage 65 of 65 (one BROKEN on the first draft — a foreign project's docs/ path cited in backticks — reworded);
kit_lint OK (leak 0 / placeholders 22 == 22 / syntax 0 / gitignore 75 identical); tool_census --check OK; 65 DK ids cited, 0
dangling; audit_public OK on the touched files; no build input changed
- log + checkpoint (NEXT = task 15, Max, Tier 1 — prompt Drew and wait for gate 2)