mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-29 23:24:32 -04:00
3464a25cd0
Banked: func_80136334 (126, all four levers), func_8016B234 (129), func_8015FBE0 (125), func_80143D28 (131), func_8014D820 (126 of 128). 27,984 -> 26,714 sites. lever_census --check: 26,714 pin/asm sites, 26,714 marked !FAKE, 0 UNMARKED — OK THE MISSING CALL ARGUMENT CLASS IS NOW CONFIRMED SIX TIMES, independently, by six agents that never saw each other's work: a7, a8, a11, a12, a13, a25. In every case the source declares a call with fewer arguments than the callee really takes — m2c drops arguments at unprototyped and indirect call sites — and the register pin was hired to fake the instruction the missing argument would have produced. Mechanisms differ and were each proven on bytes: combine.c:1458's added_sets_2 gate; set_preference (global.c:1535/1589) applied ahead of first-fit at :997-1030, the argument copy degenerating to a self-move deleted at toplev.c:3142 / jump.c:424-443 so it costs zero instructions; and reorg.c:3374's liveness half, where restoring the argument adds a use to CALL_INSN_FUNCTION_USAGE (reorg.c:428) so a delay-slot steal is refused. No generator can reach any of it: every family rewrites statements and declarations, none edits a call's argument list. Two concurrency defects, both found by agents rather than by me: - the includers cache wrote through a FIXED temp name, so concurrent processes clobbered each other's os.replace and the loser saw FileNotFoundError, which reads like a compiler crash on the candidate. Now a unique tempfile per process. - the agent brief now mandates PACK/scratch/ for helper scripts and dumps, and says to retry once when a --try failure names something that is not your own text. Three agents had scripts overwritten mid-run by another agent. One valuable negative: func_80178970 does not close, and the agent proved why by construction rather than by exhaustion — only a call or a return writes $v0 in plain C, and a return's hard write is always emitted after its guarding branch, so combine deletes the call-result copy (combine.c:914-917, use_crosses_set_p at :10127-10130; the SMALL_REGISTER_CLASSES arm at :944-957 is not defined for MIPS). Its early-return rewrite still improves the source from 6 to 2 and reads better than the pinned original.
283 lines
22 KiB
Markdown
283 lines
22 KiB
Markdown
# Levers — the register pins and compiler hints, what they cost, and what to do about them
|
||
|
||
> **What this is.** The living record of every compiler-forcing construct in this decompilation: how many there are, how they
|
||
> got in, how they come out, and — the part that outlives the project — whether a decomp should refuse them from its first
|
||
> bank or clean them up at the end. The numbers below are **generated** (R75); the argument is written from them.
|
||
>
|
||
> **Maintained:** updated at the close of every Phase-36 task (`tools/lever_progress.py --snapshot "<task>"`, which also
|
||
> re-renders the tables here). Sources: `tools/lever_census.py` for the counts, `.run/P36/delever/ledger.jsonl` for the
|
||
> campaign, `docs/lever-progress.tsv` for the series a chart is drawn from.
|
||
>
|
||
> **Audiences:** the post-100% chart and the project story · the wiki (a "Levers" page) · `decomp-architect/` (the day-one
|
||
> kit: the rule a new project should adopt at its first bank, §"What we would do from day one").
|
||
|
||
---
|
||
|
||
## 1. What a lever is
|
||
|
||
A **lever** is a construct that exists only to force the compiler's output — it says nothing about what the program does.
|
||
This project's C is full of them because bytes were the only thing being optimised:
|
||
|
||
| class | what it looks like | why a drafter reached for it |
|
||
|---|---|---|
|
||
| **A** register pin | `register s32 x __asm__("$16");` | name the register the allocator must use |
|
||
| **B1** barrier | `__asm__ __volatile__("" ::: "memory");` | stop the scheduler moving code across a point |
|
||
| **B2** launder | `__asm__("" : "=r"(x) : "0"(x));` | break a value's history so cse/combine cannot see through it |
|
||
| **B3** keep-alive | `__asm__("" : : "r"(x));` | keep a dead value live |
|
||
| **B4** hand-placed instruction | `__asm__("addu %0,%1,$zero" …)` | emit exactly this instruction |
|
||
| **B5** GTE op | `__asm__("lwc2 …")` behind a per-TU macro | *not a lever* — Sony's coprocessor idiom; consolidated, not removed |
|
||
| **C** volatile | `*(volatile s32 *)(p + 4)` | force a reload |
|
||
| **D** bare `register` | `register int i;` | a hint gcc 2.7.2 at `-O2` ignores |
|
||
| **E** asm-label alias | `extern u16 aD800B9A02 __asm__("D_800B9A02");` | a second typed view of one symbol |
|
||
| **F** builtin | `__builtin_memcpy(&a, &b, N)` | a struct assignment in disguise |
|
||
| **G** attribute | `__attribute__((packed))` | a layout the type layer should carry |
|
||
|
||
Classes **A and B are the ones this phase drives to zero.** C and D stay if the bytes need them (ordinary C, ledgered and
|
||
counted, not marked). E, F and G belong to the canonical type layer and are the **types phase's** work (R95).
|
||
|
||
---
|
||
|
||
## 2. The numbers
|
||
|
||
<!-- BEGIN GENERATED: lever_progress.py -->
|
||
|
||
### Milestones — the count, at each moment a census ran
|
||
|
||
| date | milestone | pins | asm | **class A+B sites** | bodies | distinct | GTE levers | per-TU asm macros | C | D | E | F | G | HEAD |
|
||
|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|
|
||
| 2026-09-09 | T6 r1 (rung R's first class) | 20512 | 13445 | **33957** | 12578 | 1758 | 462 | 314 | 1722 | 50 | 7428 | 445 | 76 | `1761a3125` |
|
||
| 2026-09-09 | T6 d1 (rung D's four exemplars) | 20508 | 13445 | **33953** | 12574 | 1758 | 462 | 314 | 1722 | 50 | 7428 | 445 | 76 | `8fac96651` |
|
||
| 2026-09-09 | T6 p1 (four classes propagated) | 19982 | 13445 | **33427** | 12048 | 1755 | 462 | 314 | 1722 | 50 | 7428 | 445 | 76 | `a2da99b13` |
|
||
| 2026-09-09 | S101 rung G g1 (the guided search's first run: 1 of 16 exemplars, 132 bodies) | 19982 | 13313 | **33295** | 11916 | 1754 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `623e55340` |
|
||
| 2026-09-09 | S101 rung G g2 (wider beam + R10/R12/R13/R8-cse: 1 of 16, 132 bodies) | 19718 | 13313 | **33031** | 11784 | 1753 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `584031c71` |
|
||
| 2026-09-09 | S101 rung G g3 (the next 64 classes: 13 of 64, 1,516 bodies) | 18442 | 12583 | **31025** | 10268 | 1740 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `5ae0b5ceb` |
|
||
| 2026-09-09 | S101 rung G g4s (the 13 explained names, 38 classes: 3 of 38, 133 bodies) | 18309 | 12583 | **30892** | 10135 | 1737 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `068781ae0` |
|
||
| 2026-09-09 | S101 rung G g5 (the head re-drawn wide: 5 of 70, 62 bodies) | 18261 | 12545 | **30806** | 10073 | 1732 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `1ec67f667` |
|
||
| 2026-09-09 | S101 rung G g6 + g6b (the tail: 47 + 63 of 400 small classes, 93 + 186 + 47 bodies) | 17954 | 12404 | **30358** | 9747 | 1624 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `2f3ce92a1` |
|
||
| 2026-09-10 | S102 T7 a1 — func_80156044 closed by an agent (merge-point expression sunk into the if/else arms, both $2/$3 pins gone); 130 bodies banked (1 + 129 propagated) | 17694 | 12404 | **30098** | 9617 | 1623 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `5a8bd1021` |
|
||
| 2026-09-10 | S102 T7 s1 — the R15 sweep of the head: 6 exemplars closed by the sink alone (267 bodies), no drafting tokens | 17427 | 12270 | **29697** | 9350 | 1617 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `861dd0651` |
|
||
| 2026-09-10 | S102 T7 a2 — func_80168828: the constant-holder pin deleted and one adjacent swap closes it (125 bodies, 1 of 2 pins off; the $4 pin is forced by the header's (void) declaration) | 17302 | 12270 | **29572** | 9350 | 1617 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `c00ffe5d4` |
|
||
| 2026-09-10 | S102 T7 s3 (partial, the run crashed in propagate) — the constant-holder draw: 10 bodies closed | 17266 | 12267 | **29533** | 9326 | 1594 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `975850ff8` |
|
||
| 2026-09-10 | S102 T7 s3b — the constant-holder draw re-run after the propagate fix: 1 of 80, 6 bodies; 59 of 80 hit the compile budget | 17260 | 12267 | **29527** | 9320 | 1593 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `5603a114c` |
|
||
| 2026-09-10 | S102 T7 s5 — the first broad TAIL sweep with R15/R16/R17/R18: 285 of 800 bodies closed lever-free, no drafting tokens | 17029 | 12175 | **29204** | 9035 | 1308 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `449acd8cb` |
|
||
| 2026-09-10 | S102 T7 s6 — the last never-attempted exemplars: 25 of 337; the fresh pool is now empty | 16996 | 12152 | **29148** | 9010 | 1283 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `67e91ad4d` |
|
||
| 2026-09-10 | S102 T7 s7 (stopped early to restore agent cadence) — 5 of 207 judged at the wider width | 16989 | 12151 | **29140** | 9005 | 1280 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `724c68859` |
|
||
| 2026-09-10 | S102 T7 a7 — func_801287B8: a truncated local extern was faking a pin; the missing call argument restored through a function-pointer cast (127 bodies) | 16862 | 12151 | **29013** | 8878 | 1279 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `bb0e95c7c` |
|
||
| 2026-09-10 | S102 T7 a4 — func_8016C49C: one do-while on the function's LAST statement doubles a loop-note reference weight and flips allocno_compare (126 bodies) | 16736 | 12151 | **28887** | 8752 | 1278 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `9f5b22176` |
|
||
| 2026-09-10 | S102 T7 burst — a8/a12/a18/a19 banked (132+118+119+134 bodies): the call-arity class twice, a MEM_IN_STRUCT_P alias flip, and a sequence-point split | 15965 | 12019 | **27984** | 8249 | 1275 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `fb6c857f7` |
|
||
| 2026-09-10 | S102 T7 burst — a5/a9/a11/a16/a25 banked; the MISSING CALL ARGUMENT class confirmed six times independently (m2c dropped arguments at unprototyped and indirect call sites) | 15324 | 11390 | **26714** | 7612 | 1271 | 462 | 314 | 1590 | 50 | 7428 | 445 | 76 | `c6b380fcd` |
|
||
|
||
### The campaign — every batch, from the ledger (derived on every render)
|
||
|
||
| batch | date | rungs | bodies first judged | sites removed | rewritten | sites closed later | bodies made lever-free |
|
||
|---|---|---|---:|---:|---:|---:|---:|
|
||
| `t3_tus1` | 2026-09-09 | A,B | 176 | 211 | 4 | 0 | 0 |
|
||
| `t3_scrub` | 2026-09-09 | scrub | 1 | 0 | 0 | 0 | 0 |
|
||
| `tus1` | 2026-09-09 | A,B,replay | 1,392 | 3,241 | 101 | 0 | 0 |
|
||
| `tus2` | 2026-09-09 | A,B,replay | 2,752 | 3,501 | 4 | 0 | 0 |
|
||
| `tus3` | 2026-09-09 | A,B,replay | 2,513 | 2,910 | 15 | 0 | 0 |
|
||
| `tus4` | 2026-09-09 | A,B,replay | 2,224 | 2,012 | 131 | 0 | 0 |
|
||
| `tus5` | 2026-09-09 | A,B,replay | 1,225 | 1,811 | 10 | 0 | 0 |
|
||
| `tus6` | 2026-09-09 | A,B,replay | 1,409 | 1,667 | 0 | 0 | 0 |
|
||
| `tus7` | 2026-09-09 | A,B,replay | 1,407 | 1,853 | 2 | 0 | 0 |
|
||
| `tus8` | 2026-09-09 | A,B,replay | 602 | 432 | 0 | 0 | 0 |
|
||
| `tus9` | 2026-09-09 | A,B,replay | 1,901 | 2,234 | 10 | 0 | 0 |
|
||
| `tus10` | 2026-09-09 | A,B,replay | 362 | 408 | 3 | 0 | 0 |
|
||
| `headers1` | 2026-09-09 | A,B,replay | 200 | 301 | 1 | 0 | 0 |
|
||
| `headers2` | 2026-09-09 | A,B,replay | 171 | 197 | 2 | 0 | 0 |
|
||
| `gte1` | 2026-09-09 | gte | 399 | 0 | 0 | 0 | 0 |
|
||
| `gte2` | 2026-09-09 | gte | 244 | 0 | 0 | 0 | 0 |
|
||
| `sweep1` | 2026-09-09 | sweep | 4 | 0 | 0 | 0 | 0 |
|
||
| `sweep2` | 2026-09-09 | sweep | 3 | 0 | 0 | 0 | 0 |
|
||
| `sweep3` | 2026-09-09 | sweep | 134 | 0 | 0 | 0 | 0 |
|
||
| `r1` | 2026-09-09 | R | 0 | 0 | 0 | 134 | 134 |
|
||
| `d1` | 2026-09-09 | D | 0 | 0 | 0 | 4 | 4 |
|
||
| `p1` | 2026-09-09 | D | 0 | 0 | 0 | 394 | 394 |
|
||
| `p1b` | 2026-09-09 | D | 0 | 0 | 0 | 132 | 132 |
|
||
| `g1` | 2026-09-09 | G | 0 | 0 | 0 | 2 | 1 |
|
||
| `g1p` | 2026-09-09 | G | 0 | 0 | 0 | 262 | 131 |
|
||
| `g2` | 2026-09-09 | G | 0 | 0 | 0 | 2 | 1 |
|
||
| `g2p` | 2026-09-09 | G | 0 | 0 | 0 | 262 | 131 |
|
||
| `g3` | 2026-09-09 | G | 0 | 0 | 0 | 17 | 13 |
|
||
| `g3p` | 2026-09-09 | G | 0 | 0 | 0 | 1,989 | 1,503 |
|
||
| `g4s` | 2026-09-09 | G | 0 | 0 | 0 | 3 | 3 |
|
||
| `g4sp` | 2026-09-09 | G | 0 | 0 | 0 | 130 | 130 |
|
||
| `g5` | 2026-09-09 | G | 0 | 0 | 0 | 7 | 5 |
|
||
| `g5p` | 2026-09-09 | G | 0 | 0 | 0 | 79 | 57 |
|
||
| `g6` | 2026-09-09 | G | 0 | 0 | 0 | 66 | 47 |
|
||
| `g6b` | 2026-09-09 | G | 0 | 0 | 0 | 72 | 63 |
|
||
| `g6bp` | 2026-09-09 | G | 0 | 0 | 0 | 39 | 30 |
|
||
| `g6p` | 2026-09-09 | G | 0 | 0 | 0 | 275 | 186 |
|
||
| `a1` | 2026-09-10 | E | 0 | 0 | 0 | 2 | 1 |
|
||
| `a1p` | 2026-09-10 | E | 0 | 0 | 0 | 258 | 129 |
|
||
| `s1` | 2026-09-10 | G | 0 | 0 | 0 | 10 | 6 |
|
||
| `s1p` | 2026-09-10 | G | 0 | 0 | 0 | 391 | 261 |
|
||
| `a2` | 2026-09-10 | E | 0 | 0 | 0 | 1 | 0 |
|
||
| `a2p` | 2026-09-10 | E | 0 | 0 | 0 | 124 | 0 |
|
||
| `s3` | 2026-09-10 | G | 0 | 0 | 0 | 39 | 24 |
|
||
| `s3b` | 2026-09-10 | G | 0 | 0 | 0 | 1 | 1 |
|
||
| `s3bp` | 2026-09-10 | G | 0 | 0 | 0 | 5 | 5 |
|
||
| `s4` | 2026-09-10 | G | 0 | 0 | 0 | 18 | 3 |
|
||
| `s5` | 2026-09-10 | G | 0 | 0 | 0 | 325 | 285 |
|
||
| `s6` | 2026-09-10 | G | 0 | 0 | 0 | 56 | 25 |
|
||
| `s7` | 2026-09-10 | G | 0 | 0 | 0 | 8 | 5 |
|
||
| `a7` | 2026-09-10 | E | 0 | 0 | 0 | 1 | 1 |
|
||
| `a7p` | 2026-09-10 | E | 0 | 0 | 0 | 126 | 126 |
|
||
| `a4` | 2026-09-10 | E | 0 | 0 | 0 | 1 | 1 |
|
||
| `a4p` | 2026-09-10 | E | 0 | 0 | 0 | 125 | 125 |
|
||
| `a8` | 2026-09-10 | E | 0 | 0 | 0 | 2 | 1 |
|
||
| `a8p` | 2026-09-10 | E | 0 | 0 | 0 | 262 | 131 |
|
||
| `a12` | 2026-09-10 | E | 0 | 0 | 0 | 1 | 1 |
|
||
| `a19` | 2026-09-10 | E | 0 | 0 | 0 | 3 | 1 |
|
||
| `a18` | 2026-09-10 | E | 0 | 0 | 0 | 1 | 1 |
|
||
| `a12p` | 2026-09-10 | E | 0 | 0 | 0 | 117 | 117 |
|
||
| `a19p` | 2026-09-10 | E | 0 | 0 | 0 | 399 | 133 |
|
||
| `a18p` | 2026-09-10 | E | 0 | 0 | 0 | 124 | 124 |
|
||
| `a5` | 2026-09-10 | E | 0 | 0 | 0 | 4 | 1 |
|
||
| `a5p` | 2026-09-10 | E | 0 | 0 | 0 | 500 | 125 |
|
||
| `a9` | 2026-09-10 | E | 0 | 0 | 0 | 2 | 1 |
|
||
| `a9p` | 2026-09-10 | E | 0 | 0 | 0 | 256 | 128 |
|
||
| `a25` | 2026-09-10 | E | 0 | 0 | 0 | 2 | 1 |
|
||
| `a16` | 2026-09-10 | E | 0 | 0 | 0 | 1 | 1 |
|
||
| `a25p` | 2026-09-10 | E | 0 | 0 | 0 | 250 | 125 |
|
||
| `a16p` | 2026-09-10 | E | 0 | 0 | 0 | 130 | 130 |
|
||
| `a11` | 2026-09-10 | E | 0 | 0 | 0 | 1 | 1 |
|
||
| `a11p` | 2026-09-10 | E | 0 | 0 | 0 | 124 | 124 |
|
||
| **total** | | | **17,119** | **20,778** | **283** | **7,539** | **5,109** |
|
||
|
||
*Generated by `tools/lever_progress.py --render` from `.run/P36/census/lever_census.json` and `.run/P36/delever/ledger.jsonl`; the series lives in `docs/lever-progress.tsv` (R75: published numbers are generated, never typed).*
|
||
|
||
<!-- END GENERATED -->
|
||
|
||
---
|
||
|
||
## 3. How they got in
|
||
|
||
Nobody sat down to write 34,000 levers. They arrived one bank at a time, over five phases, each time as the cheapest way
|
||
past a real wall:
|
||
|
||
- **Phase 18** researched gcc 2.7.2's register allocator and produced *the pin toolkit* (cookbook §17). It worked: the wave
|
||
close-rate went 33% → 56% → 90% in one phase. Pins were the difference between a wave that banked and a wave that did not.
|
||
- **Phases 19–29**, the family campaign, scaled that: one cracked exemplar remapped across up to 134 overlays, and every
|
||
lever in the exemplar was copied with it. **This is why the count is in the tens of thousands and the distinct count is in
|
||
the hundreds** — 12,578 bodies carry class A/B sites, but only 1,758 distinct texts.
|
||
- **Phase 23's** local-model tier and **Phase 30–31's** autonomous lanes drafted at a rate no human reviewed line by line;
|
||
a pin that made the byte gate go green was, correctly for those phases, a win.
|
||
- **§501-E** (Phase 32) then replaced pins with *launders* on some functions — trading one lever for another. That recipe is
|
||
explicitly out of bounds in Phase 36.
|
||
|
||
Two facts about that history matter for the argument below. First, **the knowledge needed to remove a lever did not exist
|
||
when the lever was added** — the §31 codegen map (Phase 23), the allocation-order reading (`alloc_table.py`, Phase 32) and
|
||
the §501-P/Q/R producer censuses all came later. Second, **Phase 32 measured the thing that makes this a debt rather than a
|
||
cost**: on the last four functions of the frontier, *every* pin came off byte-identical once the source shape was right
|
||
(R73). A pin was rarely load-bearing. It was a shortcut around finding the shape.
|
||
|
||
---
|
||
|
||
## 4. How they come off — the ladder, and what each rung is worth
|
||
|
||
| rung | what it does | cost | measured yield |
|
||
|---|---|---|---|
|
||
| **A** strip-all | remove every lever in a body at once, compile, compare | 1 compile | see the campaign table above |
|
||
| **B** greedy | re-add sites one at a time to find the minimal needed set | ≤ sites+1 compiles | the residue after A |
|
||
| **C** exemplar/replay | a multi-copy class judged once, replayed to every copy | 1 compile per copy | most of the fleet's copies |
|
||
| **R** recipes | the cookbook's byte-neutral **shape** recipes, mechanically (R2 declaration order · R3 initializer split · R4 one declaration moved through the run · R5 operand order of one commutative operator · R6 a single-set temp inlined at its use · R7 one statement wrapped in a block) | 1 compile per candidate | **134 of 134 bodies in 6.0 min, 3,243 compiles** (batch `r1`) |
|
||
| **D** permuter | decomp-permuter seeded with the **lever-free** body against the target bytes | ~12 min per exemplar | **5 of 16 exemplars, 665 of 2,131 bodies behind them, 0.69 h** |
|
||
| **E** agents | one agent per surviving class exemplar, with the pack | tokens | Phase 36 T7 |
|
||
|
||
**RUNG R IS A REPLICATION ENGINE, NOT A DISCOVERY ENGINE — measured, S99.** On a class whose shape rung D had already found it
|
||
closed **134 of 134 bodies in 6 minutes**. On 300 residue bodies where nobody had found the shape yet it closed **0 of 300 at
|
||
`--cap 40` (12,110 compiles, 4.6 min) and 0 of 300 at `--cap 400` (17,140 compiles, 6.0 min)`**. Two things make that a real
|
||
result rather than a budget problem: the instrument was checked on one body by hand (every candidate COMPILES and returns
|
||
DIFFERS — nothing was erroring out silently), and 17,140 compiles over 300 bodies is ~57 candidates each, so **the cap was
|
||
never the binding constraint** — ten times the depth produced 42% more candidates and closed nothing. The recipe rung banks a
|
||
KNOWN shape across a class for free; it has nothing to say about a shape that is not in its list. Growing it means finding
|
||
more shapes (rung D, then agents), and harvesting each one back into the rung so it sweeps the population for free.
|
||
|
||
**The two rungs feed each other.** Rung D's wins are not exotic: each is a small nameable source shape — a statement wrapped
|
||
in a block (`flag = 0;` → `do { flag = 0; } while (0);`), a single-set temp inlined at its use. Every one of them became a
|
||
rung-R recipe the same session, and rung R then banks the whole class **mechanically, as a one-line diff in the real
|
||
source**, where banking the permuter's own output would paste a pycparser-reprinted body into a phase whose entire purpose
|
||
is readability. Rung D is the *scout*; rung R is the *harvest*.
|
||
|
||
**What the residue looks like.** After rungs A–C the survivors split by the *starting distance* of the lever-free body from
|
||
the target, not by how many sites they carry: exemplars 8–29 mismatched instructions away closed (5.9 s to 651 s); those
|
||
70–276 away did not close in 12 minutes, and every one of those is a body where removing a hand-placed **instruction**
|
||
lever changed the instruction *count* and shifted everything after it. None is a wall — they improved 78→9, 52→3, 50→3,
|
||
37→2 — they are seeds for a longer run or for an agent.
|
||
|
||
**An instrument warning that cost two campaigns** (cookbook §454): a search harness whose target is *assembled from a
|
||
disassembly listing* is not measuring your code. `objdump` prints the pseudo-instruction `move` for `addu rX,rY,$zero` and
|
||
GAS assembles `move` as `or`; a listing's `%hi`/`%lo` pairs come back resolved with no relocation. The scorer read 28 for a
|
||
body that was byte-identical, so score 0 was unreachable and two campaigns reported "0 of 16" about a population that was
|
||
fine. **Seed any byte search with a body that already matches and require base score 0 before believing a yield.**
|
||
|
||
---
|
||
|
||
## 5. The question that outlives the project: prevent, or clean up at the end?
|
||
|
||
The honest answer is **neither extreme**, and the evidence points at a specific middle.
|
||
|
||
**What the numbers say.** The mechanical rungs (A/B/C) removed or rewrote **21,061 sites across 17,119 bodies** — the
|
||
campaign table's own totals — against **33,957** still standing: **38% of the class A/B population came off with no
|
||
understanding at all**, by strip it, compile, compare. Those levers were never load-bearing. They were speculative, added by a drafter that had no
|
||
cost signal for adding one, and a *single compile at bank time* would have refused them. That is the strongest measured
|
||
claim in this document: **more than a third of the debt would never have existed if banking a function had asked "does it
|
||
still match without this?" once.**
|
||
|
||
**What the history says against blanket prevention.** The pin toolkit is why Phase 18's waves closed at all, and the
|
||
knowledge to *replace* a pin with the right source shape did not exist until Phases 23–32. A rule of "no levers, ever"
|
||
adopted in Phase 18 would have stalled the project at ~56% for months, in exchange for a debt that a later phase paid off in
|
||
weeks with better tools. Deferring was not obviously wrong; **deferring silently was.**
|
||
|
||
**The rule we would actually adopt from day one** — and the one the day-one kit should carry:
|
||
|
||
1. **A lever is allowed, and it is a marked, ledgered debt from the moment it is banked.** One comment at the bank site
|
||
naming the class and what it forces (`// !FAKE: pin $16 — without it gcc allocates x to $17`), and a census that counts
|
||
them. Phase 36 had to *re-derive* that reason for 34,091 sites, function by function, years after the fact; writing it
|
||
once at bank time is nearly free and is the difference between a debt and an archaeology project.
|
||
2. **The bank-time trial: compile the body once without the lever before accepting it.** One compile. It would have refused
|
||
~37% of the population at the moment of creation, when the context was hot.
|
||
3. **Publish the count as a first-class metric next to the match percentage.** A number nobody publishes is a number nobody
|
||
pays down. This project's lever count was invisible until the phase that removed them.
|
||
4. **Levers get the harvest → toolify treatment, exactly like idioms.** When a wave learns the shape that replaces a lever
|
||
class, that shape becomes a mechanical recipe and sweeps the banked population — instead of the lever propagating with
|
||
every family remap (which is how 1,758 distinct bodies became 12,578).
|
||
5. **Do NOT block a phase on lever-freedom.** Keep the byte gate as the only gate; let the marker and the count carry the
|
||
debt forward. A cleanup phase with mature tooling is genuinely cheap — rung A/B is a few hours of compute for a third of
|
||
the population, and rung R banks a 134-copy class in six minutes.
|
||
|
||
**In one line for the kit:** *ban the silence, not the lever.*
|
||
|
||
---
|
||
|
||
## 6. For the wiki and the kit
|
||
|
||
- **Wiki page ("Levers")** — sections 1, 2 and 5 of this document, plus the current chart. The count belongs beside the
|
||
match percentage on the progress page: a project that is 100% matched and 34,091 levers deep is not finished, and saying
|
||
so plainly is the difference between this decomp and the ones the community complains about (`docs/gen3-standards.md`).
|
||
- **`decomp-architect/`** — section 5's five rules, as a Phase-1 kernel (a lever is a marked debt; the bank-time trial; the
|
||
published count; harvest→toolify for levers; never block the byte gate on it) with this document's measured numbers as
|
||
the provenance. The taxonomy in section 1 transfers unchanged to any gcc-era PS1 decomp.
|
||
- **Parked for the endgame (Drew, 2026-09-09):** train a **gcc 2.7.2 LoRA on the final clean asm/C pairs** and release it for other
|
||
decompilations to use when cracking. Not now, and not on today's corpus: our 363,214 matched pairs are (asm → C-*with*-levers), so
|
||
training on them teaches a model to PRODUCE pins. The corpus worth releasing is the one this phase creates — lever-free C beside its
|
||
bytes — which is another reason to finish the removal before the project ships its dataset.
|
||
- **The story** — the arc is: a toolkit that unlocked the project (P18) → a family engine that multiplied it 134× (P19–29) →
|
||
a phase that measured the debt for the first time (P36 T1) → mechanical removal of a third of it (T4) → a scout-and-harvest
|
||
loop for the rest (T6) → zero (T7). The interesting beat is not the cleanup; it is that **the shortcut that made the
|
||
project possible was also the thing it had to spend a whole phase undoing**, and that both were the right call at the time.
|
||
|
||
---
|
||
|
||
## 7. Maintenance
|
||
|
||
Run at the close of every task that changes the count, and at every phase close:
|
||
|
||
```
|
||
.venv/bin/python tools/lever_census.py --sites --check -j 16 # the count, and 0 UNMARKED
|
||
.venv/bin/python tools/lever_progress.py --snapshot "T6 r1" # appends the milestone row, re-renders §2
|
||
```
|
||
|
||
`tools/lever_progress.py --check` fails when the series' last row is not this tree — a stale series is a wrong chart.
|