16 KiB
The retrospective — what we believed, what failed and why it looked right, what it cost, what we would do sooner
What this is. The analytical companion to
docs/story.md, written at the public flip (Phase 33) for the next project — ours or anyone's. It is assembled from three records the project kept as it happened, never from memory: the decision log's hindsight entries (docs/decision-log.md, cited by dated heading and line), the two PhaseEnds that carry a "What we believed, what failed, and what we would do sooner" section (PhaseEnd_Phase31.md,PhaseEnd_Phase32.md), and the Deviations table of every PhaseEnd (237 rows across 32 phases — what was planned against what happened).tools/mine_hindsight.pygathers all three withfile:lineanchors; nothing here is backfilled into the log (R31). Two earlier syntheses are folded in by reference rather than rewritten:docs/hindsight-study.md(the optimal ORDER for an AI-driven matching decompilation, with its seven meta-principles) anddocs/generic-decomp-package.md(what the next project should inherit on day one). Every cost carries its denominator (R41).
1. What we believed, turn by turn — and what was actually true
| When | What we believed | What was true | Record |
|---|---|---|---|
| June (the brief) | A recompilation first would force the memory map into the open and bootstrap the decomp | A matching decomp needs no recompiler; recomp output feeds no matching work. Decided before the first commit | docs/history/claude-bfm-starting-point.md (banner), PROJECT_CONTEXT.md "Key Decisions" |
| Phase 5 | The link would be a fight; a 2.35 binutils downgrade might be needed | The link was clean; binutils 2.42 was byte-clean; the only fights were 15 leading-digit symbol names | PhaseEnd_Phase5.md Deviations |
| Phase 7 | ~350 PsyQ SDK functions would have to be hand-decompiled | The real Sony objects link byte-identical; hand-decompiling library code was never necessary | PhaseEnd_Phase7.md Deviations ("Library code") |
| Phase 12 | The resident engine holds a bytecode script VM to match | There is no VM — the "script VM" is compiled-MIPS dispatch tables; a written determination replaced a phase of work | PhaseEnd_Phase12.md Deviations |
| Phases 16–17 | The remaining engine core was blocked by loose typing and missing signatures | The wall was the compiler's own code generation; types and signatures were a red herring | PhaseEnd_Phase16.md, PhaseEnd_Phase17.md |
| Phases 18–22 | Residuals that resisted every source rewrite were "unsteerable" | Reading the gcc 2.7.2 source explained them pass by pass (the codegen map); "unsteerable" meant "not yet read" | PhaseEnd_Phase23.md, docs/gcc-2.7.2-map/ |
| Phase 25 | A "mechanical" family sweep would remap whole families for free | The sweep had a hard ceiling at translation-unit type collisions; the mechanical tier was a third over-counted by its probe | decision-log.md:58, :167 ("the mechanical tier was ⅓ probe-over-counted") |
| Phase 25 | The GIANT def-side wall was a matching wall | 71% of it was tool-shaped: a declaration conflict hidden behind "0 banked" | decision-log.md:102, :134 |
| Phase 26 | The endgame plan's numbers described the remaining work | "The audit found the endgame plan was majority-fiction": broken scanners had been measuring the frontier | decision-log.md:780 (2026-07-14, A2) |
| Phases 26–28 | The "~3% swing number" was a wall class | It was an -O0 compile-flag artifact of the tooling |
decision-log.md:1241 (2026-07-16, Phase 29 Task 1) |
| Phase 29 | The waves were limited by idioms the drafters lacked | The bottleneck was INTEGRATION — banking a byte-correct draft past declarations, placement and symbol spellings (~92% of drafts byte-correct, ~27% banked at one measurement) | decision-log.md:1932 (2026-07-24, SESSION-15); PhaseEnd_Phase31.md "Cost" |
| Phase 30 | The disc had been fully counted | It held 73 more code-bearing payloads; the denominator grew 140 → 213 while the percentage still rose | decision-log.md:2151, :2208, :2243; PhaseEnd_Phase30.md |
| Phase 31 | A band of the main executable was a compiler wall | It was Sony's libpad 4.2.1; the exact February-1998 archive was on the internet and linked in — twelve "walls" at once | decision-log.md:3067 (S78); PhaseEnd_Phase31.md |
| Phase 31 | The permuter had exhausted the pinned near-misses | It had never run on one | PhaseEnd_Phase31.md "What we believed" |
| Phase 32 | Two functions were PROVED unmatchable by producer censuses | Each census was one producer short; five-line reproducers found the missing behaviours in minutes | decision-log.md:3404, :3424 (S85); cookbook §501-Q/§501-R |
| Phase 33 | Main's game-code denominator was settled | Ghidra's boundaries under-counted it by 3,616 instructions; the build-derived sig replaced it | decision-log.md:3451 (P33 S86) |
| Phase 33 | The history rewrite was a mechanical filter run | Two defects only a rehearsal could show: an empty blob in the strip list silently undid every "file emptied" change in history, and a byte-identical commit keeps its hash | phase-ends/CURRENT_PHASE.md (P33 C1), docs/public-flip-runbook.md §5 |
The pattern is not subtle. Almost every wall the project recorded was one of its own instruments — a scanner that skipped, a gate that compared the wrong hash, a probe that sized what it should only have filtered, a census one producer short, a boundary from the wrong tool. The Phase-31 close says it in one line: roughly a third of that phase's "compiler walls" were tooling. Phase 26 made it a doctrine (R35: fix the instrument before trusting its measurement; R32: assert your coverage; R34: a second, disagreeing oracle) and Phase 32 sharpened it to R65: a wall verdict must name the compiler pass and quote the dump line.
2. What failed, and why it looked right at the time
- The silent skip. A corpus scanner that could not answer returned an empty set, and an empty set read as "nothing
left" — the tool reported a 100%-complete decomp from a swallowed error (
tools/progress.py, the comment block inweighted_metrics). It looked right because green is what success looks like. Seven of these were found in Phase 26 (decision-log.md:608, "the silent-skip class"). Fix: R32/R43 — a tool refuses rather than guesses, and every scanner compares what it found against an over-approximating candidate set. - The wrong-hash gate. For a month the shared byte gate compared one binary against another binary's hash
(
decision-log.md:1499). It looked right because the gate was green — for the wrong reason. Fix: per-binary contracts (config/check.*.sha), R50 periodic whole-fleet verification, R56 (a gate verdict measures the draft only while the baseline is green). - The proof that was a census. "PROVED unmatchable" meant "every producer on my list is refuted on the bytes"; the
list was the assumption (
decision-log.md:3424). It looked right because each refutation was real. Fix: R69 — a verdict names the list it was proved against; R73 — build the one-second reproducer battery before probing the real function. - The summary that hid a compile error. "0 banked" across a whole class read as an intractable wall; the raw
compiler error on ONE placed candidate showed a declaration conflict (
decision-log.md:124). Fix: R37 — probe before costing; ground every estimate on one instance. - The register pins. Every pin on the last four functions came off byte-identical once the source shape was right
(
PhaseEnd_Phase32.md"What we believed"). Pins looked like levers because they moved the diff; they were symptoms. Fix: R73 — read the allocation order before any register lever; remove every pin after a structural change. - The atlas score. A 0.55 "weak cousin" was the exact same-shape sibling; the whole crack was one grep and one port
after ~1M tokens of dial work (
decision-log.md:3399). A similarity score looked like a shape oracle. Fix: R72 — a hand pass opens with a grep for the idiom's literal constants. - The fresh session that nearly closed an open phase. A compressed hand-off was misread as "done"
(
decision-log.md:51). It looked right because the summary said so. Fix: the replayable 🛑 checkpoint (R64) that distinguishes not this session from not this phase, and a plan of record that outlives any hand-off. - The rewrite that would have corrupted history.
--strip-blobs-with-idswith the empty blob's id in the list dropped every "file emptied" change across 4,000 commits; the pairwise proof caught it, the trial caught the pairwise proof's own gap (P33 C1). It looked right because the filter exited 0 and the purged paths were gone. Fix: rehearse an irreversible operation on a scratch copy and verify pair by pair, with positive assertions (no purge path survives, the pruned set equals the derived set), not only absences.
3. What it cost — with denominators
| Item | Cost | Of |
|---|---|---|
| Duration | 12 weeks (2026-06-10 → 09-07), ≈87 sessions, ≈4,040 commits | the whole project |
| Re-drafting functions that were already byte-correct and failed on plumbing | the dominant token spend of the campaign phases; ≈92% of drafts byte-correct, ≈27% banked at one measurement | PhaseEnd_Phase31.md "Cost"; the recovery tooling of P31 closed the gap |
| Re-drafting functions whose banked twin was two words away | ≈250k tokens before tools/twin_rescan.py existed |
PhaseEnd_Phase31.md (§397) |
| The last four functions of Phase 32 | a 655k-token Fable agent on one row, 3,360-variant sweeps, ≈100 hand variants, ≈1M tokens of dial work on a row whose answer was a grep | PhaseEnd_Phase32.md "Cost"; decision-log.md:3399 |
| One wave of 31 drafters (P32 T3) | ≈16 Opus + 4 Sonnet + 11 Haiku agents, 30–70 min each on Opus, for 20 MATCH (2,111 instructions) + 9 exact-length seeds; ≈1 hour of a Max session to recover from the coordinator's death | decision-log.md:3291 (S82) |
| Sessions lost to instrument diagnosis | "the cost was the diagnosis" — three zero-token banks and five onboardings whose only expense was finding which tool lied | decision-log.md:3245 (S81) |
| The private-era convenience (R1) | a full-history rewrite: ≈2 sessions of tooling, two rehearsals, 311 s of filter time, and a Support ticket | decision-log.md:3447; docs/public-flip-runbook.md |
| Stale scratch | 12 GB of forgotten gate worktrees, each a full old-history checkout, discovered only when they pinned the old lineage against the gc | phase-ends/CURRENT_PHASE.md (P33 C9) |
| Phase length vs. plan | P31: 30 sessions against "10–18" — the ceiling kept moving because every instrument repair re-opened a class that had been called a wall | PhaseEnd_Phase31.md Deviations |
What did not cost much: the mechanical banks. Once a tool existed for a class — twins, families, siblings, -O0
carves, propagation, stranded boundaries — its members cost ≈0 tokens each (docs/generic-decomp-package.md, "The
claim"). The economics of the project are the ratio between those two rows.
4. What we would do sooner
In the order docs/hindsight-study.md §0 argues for — byte-match baseline → pin the compiler by evidence → honest corpus
census with self-asserting tools → propagation and reconcile tooling → the codegen map and the permuter → then crack,
leverage-first — and with the specific lessons this project paid for:
- Keep ROM-derived bytes out of git from the first commit, even while private. R1's relaxation bought a few weeks
of convenience and cost a full-history rewrite, a rehearsal harness and a Support ticket (
decision-log.md:3447). Decide the public/private boundary on day one; cite commits by date and subject in long-lived documents, never by hash alone. - Read the compiler's source before the first "unsteerable" verdict. Phase 23's breakthrough was available in
Phase 16 — the source was public all along (
PhaseEnd_Phase23.md;docs/gcc-2.7.2-map/). - Build the integration layer before the drafting layer. Banking, not cracking, was the bottleneck from the day
cracking became systematic (
decision-log.md:1932); the reconcile/propagation/declaration tools of P19–P31 should have preceded the waves. - Assert coverage in every scanner from its first version, and control every refusal against the population that
already passed (R32, R39). Seven silent-skip defects and several "walls" were nothing but tools that could not
answer and said "none" (
decision-log.md:608,:647). - Ask "whose object is this?" before "which pass emitted this?" Twelve walls were Sony's library; provenance →
archive → link → compiler is the order (
PhaseEnd_Phase31.md"Doctrine changes"; cookbook §487–§490). - Build the one-second reproducer battery for a mechanism before probing the 500-instruction function, and read
the register allocation ORDER before any register lever (R73;
decision-log.md:3424). - Rehearse anything irreversible on a scratch copy, and make the proof assert presences, not only absences (P33 C1: two rehearsals, two defects that a single run would have shipped).
- Write the checkpoint to be replayed, not summarised (R64). The number-one failure mode of a long multi-session AI
project is a fresh session misreading a compressed hand-off (
decision-log.md:51); the 🛑 block that carries paths, commands, hashes and the exact next step is what made the last thirty sessions resume without a question. - Measure the steady state, not the launch; report every lane, not the loud one; quote the denominator (R41,
the
measure-the-steady-stateandreport-every-lanelessons of the autonomous-lane phase). Half the false ceilings of P31 were launch bursts read as limits. - Give every scanner and every gate a known-true case to check against before reading its output — five of one
session's biggest "findings" were artifacts of the instrument that produced them, each caught only that way
(
decision-log.md:2938, S75).
5. What stayed genuinely hard
Not everything was tooling. docs/hindsight-study.md §5 names the classes that remain hard for any matching
decompilation of this compiler, and this project's last phases confirm them:
- The novel unique tail — functions with no duplicate, no structural sibling and a scaffold too far from byte-exact for the permuter. They need reasoning; they were the last and most expensive fraction (the 21 of Phase 32).
- Compiler-internal residuals — scheduling ties, register-allocation drift, the delay-slot knife-edges. They were steerable in the end (every pin came off), but only by understanding the pass (§501-Q's combine bookkeeping gap, §501-R's user-variable rule) — knowledge that had to be won from the source, once, and written down.
- Build-integration walls that masquerade as compiler walls — jump-table carves, mixed optimization levels,
scattered
.bssin Sony's objects (cookbook §489). They are ours to fix, and they never announce themselves. - The human gate. ~99% of the work was the agent's, but every wrong direction was caught at a plan or milestone
gate by a person — the re-charter of P31, the "everything short of Sony's objects and the original hand-asm must be C"
directive of P32, the in-place-flip decision of P33. Automation filled between the gates; it did not replace them
(
docs/hindsight-study.md§3, principle 7).
6. For the wiki, in one paragraph
Pin the compiler by evidence and make a whole-binary byte gate the only judge; treat every tool as a suspect before every function; build the things that compound (dedup, propagation, the codegen map, the reproducer battery) before the things that pay once; keep the perishable why in a decision log written during the session that learned it; write hand-offs to be replayed; and keep the game's bytes out of the repository from the first commit. The rest — the 218 binaries, the 363,214 functions, the 4,000 commits — follows from those, at a cost that is mostly the diagnosis of one's own instruments.