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.
- 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)
- ImportAnnotations.java: the S86 OSGi-bundle blocker was 3 javac errors (Long->int unboxing x2, a nonexistent
LocalVariableImpl ctor -> VariableStorage); "/undefined" resolves to DataType.DEFAULT (it lives in neither type
manager — main's first proof passed the cmp with failed=13 because the plate-comment rows had set the same function
comments); ghidra_rebuild.sh now dies unless the import printed failed=0 (R49), writes .proof markers
- ghidra_annotations_delta.py: analysis drift measured and encoded as three counted classes — Error/Analysis bookmarks;
auto-named DEFAULT functions the rebuild did not create (29 in main's LINKED regions); auto-named rows lagging the curated
symbol file (10 sep8 + 5 aug31, R15). Result: main 38 hand-authored rows (13 annotated fns incl. 3 the ELF does not
define, 22 comments, 3 labels); resident/overlays/protos container rows only; the DB holds no hand-authored types
- controls (R39): mutated block row -> PROOF FAIL; synthetic comment/bookmark/label/signature round-trip -> PROOF PASS
twice (idempotent); the filter keeps the synthetic rows and a hand-renamed name-only diff; fake failed=2 refused,
resident re-proven; roster --check controls both ways
- proofs, all PASS failed=0: resident 65s, ov_SC01_077 169s, ov_SC06_018 173s, SLUS_007.26 210s, sep8 202s, aug31 206s
- tools/ghidra_roster.py -> config/ghidra/ROSTER.md (--check in tools-health, ignores the per-machine proof column)
- .claude/settings.json hooks $CLAUDE_PROJECT_DIR-relative; ghidra_mcp_start.sh is a silent exit 0 without Ghidra or
the project (both controlled); SETUP P33 B5 section + 5 inventory rows + §2.8 (R21); CURRENT_PHASE log + checkpoint
- main:func_80032A74 (422 ins, closeness 1): the residual is ONE reload-time slot at sp+0x48 (u16 draft = 422/422 code, DIFF 22 frame
rows; s16 draft = DIFF 1 at idx 244 lh vs lhu). Every post-parameter slot producer enumerated from gcc-2.7.2 (reload1.c:658 ghost
alter_reg / caller-save.c:249 area / reload1.c:879 invalid-equiv / reload1.c:3499 spill_stack_slot) and refuted on the bytes: combine's
newi2pat ghosts re-derive a narrow load and the site is lhu (no lb, no double load); a save area without sw/lw needs sched.c:4962
staleness and no register-only insn shares a block with a call; $t0 holds no pseudo; LO mult results retry into GR_REGS. Verdict
PROVED at 1 (pin kept with the verdict; ledger WALL-PROVED).
- NEW mechanism measured: local-alloc.c optimize_reg_copy_2 (tmp = x; tmp op= c; x = tmp) mints a ghost with stale refs, but after
regclass -> GR_REGS, allocated, no slot (P13/P14). 18 isolated reproducers, 0 draft variants; cookbook §501-M; accelerators (12).
- tools/ghost_census.py (new): ghosts in a .lreg dump with their class (ST_REGS => slot). tools/cc1_dumps.sh: prints the .frame line,
ins count, spill lines and the census; the under-counting standalone-(use) grep is gone. SETUP rows (R21).
- tools/backlog.py: load_best kept the EARLIEST record at equal closeness (docstring said latest) — the S84 row never rendered; fixed.
- CURRENT_PHASE.md: S84 log + refreshed 🛑 checkpoint (rows (b)(c)(d) next, then T5). No src/config/carve change; fleet check-all
218/218 rc 0 at the S84 preflight.