132 KiB
CURRENT_PHASE — Phase 33: 100% verification + the public flip + Gen2 exit
Gate 1 approved 2026-09-06 (Drew, plan mode, Max, Fable 5.1). Rules R65–R73 ratified at gate 1 (binding; full text in
phase-ends/DIGEST.md§3). The approved plan is reproduced VERBATIM at the end of this file (§"Approved plan") — its~/.claude/plans/copy is not part of the repo. Roadmap:docs/roadmap-to-100.mdv2 §3 P33 (all six items, Drew's "everything as written");docs/gen2-roadmap.mdPhase 14's two-repo model is SUPERSEDED by the in-place flip. Baseline HEAD at open:b5b79b3ac(the Phase-32 CLOSE commit; tree clean apart from the R23ghidra/churn).
Milestone (gate 2)
.run/P33/verify/SUMMARY.md: every step EXIT=0 (218/218 clean fleet;sdk-dualboth legs; oracles green incl.0 PAD-TAIL; disc residue 0; report 100.00 / 100.0 / 100.0 withINCLUDE_ASM 0, backlog 0) — and the C8 re-run on the adopted tree.gh api repos/Druthulu/BFM-decomp --jq .private→false;tools/public_rewrite/probe_github.shexit 0 (old hashes 404); rootLICENSE,src/NOTICE.md,THIRD_PARTY.md, the rewritten README (badges, Special thanks, the ProjectArchitect link);.github/workflows/no-rom.ymlgreen on the public repo; the fresh-clone criterion met with the disc.- Every deliverable file present (verification.md, commit-map.tsv, config/ghidra/*.jsonl + ROSTER.md, dumps/CHECKSUMS.sha1, decompme-preset.md, outreach/archipelago.md, gcc-2.7.2-map/README.md, tools/xsig/, the permuter PR branch + permuter-ils.md, matching-drafter-pipeline.md, story.md + timeline, retrospective.md, docs/wiki/ + how-to-ai-decomp/, gen3-handoff.md); outward actions done by Drew or explicitly recorded as pending (P9 — never claimed).
PhaseEnd_Phase33.mdv2.0.0 + DIGEST append +v2.0.0tag pushed.
Effort / model (R7/R26/R27 — every transition is PROMPTED, never assumed)
- Max is the session default. Max-mandatory tasks: A3 (link recipe), B1 (build input), B5 (Ghidra export design), B9/C3 (P5c-class), C1 design, C7 design, C10 decision, D1/D2 wording, E5, E6, F1 narrative, F2, F3, G1, G2 (Tier 1 — prompt and WAIT for gate 2). Everything else is xHigh-class execution — drop only for faster turns.
- No Ultracode / Workflow in this phase: no breadth stretch warrants it. Agents only for isolated research (read-only).
- Drew-only actions (R6): every
git push, the archive repo creation + mirror push, the force-push + remote tag deletion, the GitHub Support ticket, the visibility flip, the wiki push, the decomp.me preset save, the Archipelago message, the frogress key / decomp.dev registration, the permuter issue/PR, thev2.0.0tag push.
Owner decisions (binding; the full list with rationale is in the Approved plan §"Owner decisions")
- Scope = all six roadmap items. 2. Flip IN PLACE with the full rewritten history (every commit/date/message kept; hashes
change;
commit:1712drops as empty). 3+11. Purge set: the EXE (both historical paths),dumps/*.bin,ghidra/,tools/psyq/,session archive/,tools/ghidra-ext/*.zip,tools/brave-CUE/brave.exe; drop theS76-pre-scrub-backuptag. 4. In-history hash scrub →commit:NNNNtokens +docs/commit-map.tsv+ a tip resolution commit; the flip is gated on old hashes returning 404. 5. Everything else stays public (PhaseEnds AND logs, CLAUDE.md, PROJECT_CONTEXT.md, decision log, accelerators, campaign tooling); README links https://github.com/Druthulu/ProjectArchitect and carries a Special-thanks section. 6. License split: AGPL-3.0 for tools/ + docs/ (brave-CUE GPL-3.0, submodules theirs);src/NOTICE.mdno license asserted. 7. R65–R73 ratified. 8. A project GitHub wiki authored indocs/wiki/+docs/how-to-ai-decomp/, synced by script. 9.--mailmapboth Gmail identities →Drew T <50529377+Druthulu@users.noreply.github.com>. 10. Archive repoDruthulu/BFM-decomp-archive(private, created EMPTY, receives the unrewritten history before the rewrite). Defaults (from the rewrite design's measurements): strip all 60 remainingClaude-Session:trailer lines; literal-replace the Gmail strings in the 3 tracked files that mention them; droprefs/stash+refs/original/after the archive push; R20's new home = the Ghidra text export (config/ghidra/*.jsonl) +ghidra_rebuild.sh --proof, the archive as the one-time snapshot,CLAUDE.mdgains "nevergit clean -x" (R20 amendment proposed at the PhaseEnd).
Tasks (strict order; one commit per task after this file is updated; harness task list = the same 41 items, R28)
- A1 Governance commit (R65–R73 in DIGEST §3; this file; the harness list) — xHigh —
73068f39f - A2 Reporting-instrument fixes (pad-tail literal → derived; backlog LINKED-aware prune;
sig-mainbuild-derived +progress.pyloud-fail) — xHigh, R39 controls — see Log 2026-09-06 A2 - A3
NO_SDKknob +make sdk-dual(+ tools-health wiring,[skip]without SDK dirs) — Max — see Log 2026-09-06 A3 - A4
tools/family_hseq.pyregen — Low (became an instrument fix: the map now carries its own coverage) — see Log - B1
make disc-extract(extract.py--expect-manifest/--allow-missing-audio; check-env;.gitignorere-tightened; the 4 splat preset headers tracked;cleanfixed) — Max — see Log 2026-09-06 B1 - B2 34 absolute includes →
../shared/+ portable-include audit + 15-binary re-gate — xHigh — see Log 2026-09-06 B2 - B3
tools/bootstrap.sh/make bootstrap+ check-env extensions + fresh-clone proof 218/218 — xHigh — see Log 2026-09-06 B3 - B4
tools/fetch_psyq.sh+ CHECKSUMS rows (20 lib40 LIBs, psyq-obj-parser) BEFOREtools/psyq/leaves git — xHigh — see Log 2026-09-06 B4 - B5 Ghidra regenerability (ExportAnnotations/ImportAnnotations/ghidra_rebuild.sh
--proofon SLUS_007.26 + resident; roster; ExportSymbols R15 fix; path hardcodes; hooks) — Max (finished at medium, Drew's call) — see Log 2026-09-06 B5 - B6
dumps/CHECKSUMS.sha1+ INDEX.md rewrite + memory-map Source-index row — Low/xHigh — see Log 2026-09-06 B6 - B7 No-ROM CI (
no-rom.yml,audit_public.py,compile_only.py) — xHigh — see Log 2026-09-06 B7 - B8 SETUP.md rows/sections (R21) +
docs/verification.md— xHigh — see Log 2026-09-06 B8 - A5 THE RECORDED RUN (
tools/verify_contract.sh→.run/P33/verify/, SUMMARY all EXIT=0) — run Low, read Max — see Log 2026-09-06 A5 - B9/C3 The preparatory commit (
git rm --cachedpurge set; psyq CHECKSUMS moved; zip sha256s; runbook; decision-log entry) — Max (P5c-class) — see Log 2026-09-06 B9/C3 - C1 filter-repo 2.47.0 +
tools/public_rewrite/— design Max, execution xHigh — see Log 2026-09-07 C1 - C2 Negative control + dictionary + sample — xHigh — see Log 2026-09-07 C2
- C4 Bundle + Drew's archive mirror push + bare clone + the rewrite — xHigh — see Log 2026-09-07 C4
- C5 Verification suite on the rewritten clone — xHigh — see Log 2026-09-07 C5
- C6 Adoption in
~/bfm-decomp(no gc yet) — xHigh — see Log 2026-09-07 C6 - C7
docs/commit-map.tsv+ tip resolution commit — design Max, run xHigh — see Log 2026-09-07 C7 - C8 R22 clean rebuild on the adopted tree — xHigh — see Log 2026-09-07 C8
- C9 Final gate + Drew's force-push + local gc — xHigh — see Log 2026-09-07 C9
- C10 Support purge → probe gate → FLIP (Drew) — decision Max
- D1 README rewrite — Max — see Log 2026-09-07 D1
- D2 LICENSE +
src/NOTICE.md+THIRD_PARTY.md— Max wording — see Log 2026-09-07 D2 - D3 Progress publishing (progress.json, badges, frogress_upload, objdiff_report + progress.yml) — xHigh — see Log 2026-09-07 D3
- D4 SETUP.md public-clean pass — xHigh — see Log 2026-09-07 D4
- D5 Governing-docs consistency pass +
tools/doc_links.py— xHigh — see Log 2026-09-07 D5 - F1 timeline.py + story — xHigh tool / Max narrative — see Log 2026-09-07 F1
- F2 retrospective — Max — see Log 2026-09-07 F2
- F3 wiki + how-to-ai-decomp +
wiki_sync.sh— Max (FULL, Drew 2026-09-07) — see Log 2026-09-07 F3 - E3 gcc-2.7.2 map README +
gccmap_cites.py— xHigh — see Log 2026-09-07 E3 - E4 xsig packaging — xHigh — see Log 2026-09-07 E4
- E5 permuter upstream PR branch — Max (FULL, Drew 2026-09-07) — see Log 2026-09-07 E5
- E6 drafter write-up — Max — see Log 2026-09-07 E6
- E1 decomp.me preset (after the flip) — xHigh
- E2 Archipelago outreach (after the flip) — xHigh
- C11 Aftercare (other clones, archive remote removed,
.run/pruned, DIGEST/decision-log) — xHigh - G1
docs/gen3-handoff.md— Max — see Log 2026-09-07 G1 - G2 PhaseEnd v2.0.0 + DIGEST + archive this file + tag — Max, Tier 1, WAIT for gate 2
Mid-phase rules check after every 4 completed tasks (P6). Commit banked artifacts immediately (R42); count from the SOURCE.
Log
- 2026-09-06 (S86, session bd19e14a, Max, Fable 5.1) — gate 1. Session-start protocol (R64 digest); the history audit (every blob on every ref; the Ghidra DB proven to embed the EXE bytes under the page XOR mask; 28/28 RAM dumps carry the EXE's code; no secrets in tree, history diffs or the 1.27 GB transcript archive); Drew's decisions 1–11 (above); three read-only exploration agents + three design agents; the plan approved via plan mode. No repo change before A1.
- 2026-09-06 (S86) — A1
73068f39f: R65–R73 appended to DIGEST §3; this file opened with the plan verbatim; 41 harness tasks. - 2026-09-06 (S86) — A2 (reporting instruments).
make sig-mainREWRITTEN:tools/main_seed_ends.pyderives main's game-code function boundaries from the build (link-map.textsections × each object'snmsymbols; tiling asserted) andsig_imagehashes the original EXE bytes at them →.run/sig.main.jsonl(809 fns / 45,150 ins); wired intotools-healthandreport BINARY=main;progress.pyprefers it, falls back to the legacy Ghidra sig, and EXITS 1 with no sig (control: both hidden → rc=1 + the message); the digest's oracle clause is derived live (main_oracle_line: "0 phantom, 0 truncated, 0 pad-tail");backlog.linked_closedretires main addresses inside LINKED ranges (controls 4/4;backlog prune: 1 rows -> 0;docs/backlog.md0 open);dup_reportmain sig →.run/sig.main.jsonl. Measured correction (R14/R41): the derived sig tiles the 15 game-code objects' text EXACTLY (45,150 words, no overlaps, never shorter than Ghidra's), while the Ghidra sig covered 41,522 words and left 3,628 words of real game code owned by no function (switch tails after unresolved jump tables, 52 two-to-four-ins thunks, andSaveLoadRoutine= thecase 0:body insidefunc_8002b0b4) — so MAIN game-code weighted is 45,150 / 45,150, not 41,534 (P31 S79's +22 was one instance of this class); fleet instr 13,488,497 → 13,492,113, distinct 5,816,589 → 5,820,205, all still 100%.make report BINARY=mainEXIT=0 (.run/P33/a2_report.log: lint OK, dedup-check 2220/0);make audit-digestOK;docs/duplicates*.mdregenerated (main's table now game-code-only). SETUP §6.8 + a P33 A2 section (R21). - 2026-09-06 (S86) — A3 (the with/without-SDK dual). Makefile:
NO_SDK ?=knob — underNO_SDK=1the elevenpsyq_integraterewrites are skipped (nestedifeq) AND the-Texternals fragments are not collected (a stalebuild/psyq/*_externals.ldfrom a WITH build must not leak into the WITHOUT link);sdk-dualtarget (refuses unless all 11 SDK object dirs exist; leg 1 WITH → extract →rm -rf build/psyq→ leg 2NO_SDK=1→ extract → leg 3 WITH restore; map assertions each leg; both legs againstconfig/check.us.sha); wired intotools-healthaftersig-mainwith a[skip]when the SDK dirs are absent;.PHONY+=sig-main sdk-dual. Run:make -j$(nproc) sdk-dual→sdk-dual: OK — main 143dbb89… byte-identical WITH and WITHOUT the PsyQ objects, EXIT=0, 28 s wall (.run/P33/a3_sdk_dual.log); maps.run/P33/verify/main_with_sdk.map(1,288build/psyq/entries, 0libcd1stub) andmain_no_sdk.map(0 / 12); the live tree is back in the WITH state (map has 1,288 psyq entries;build/us/SLUS_007.26sha == check.us.sha). Control for A2's tool:main_seed_ends.pyon both maps → identical seed files (the NO-SDK map exercises the LINKED-subseg skip: 70 objects skipped by name, vs 268build/psyq/objects by path). Dry-run controls:make -n check BINARY=main NO_SDK=1shows 0psyq_integratelines andif [ -z "1" ]around the externals. SETUP: P33 A3 section (R21). - 2026-09-06 (S86) — A4 (family map regen → instrument fix, R35). Regenerating
.run/family_hseq.jsonmade theaudit-binarieswarning WORSE (6 → 217 "missing"): at 100% the map'sfamilieslist is empty and CHECK 4 inferred coverage from family members, so a complete map read as empty; the P32-close warning had been a stale pre-onboarding file. Fix:family_hseq.pywrites"binaries"(scanned: 217) +"open_instances"(0);audit_binaries.pyCHECK 4 reads that denominator and warns "predates the coverage field" on an old-format map. Controls: current map → 0[warn]; the same map with the keys removed → the predates warning; restored → 0. No other tool callsfamily_hseq.load()(grep; consumers read the json'sfamilieskey, unchanged).docs/family-hseq.mdregenerated (0 families). SETUP: P33 A4 section (R21). - 2026-09-06 (S86) — B1 (
make disc-extract).tools/bfm_extract/extract.py:--expect-manifest(compare against the committed oracle, never write it; mismatch →.run/extract/+ the first 20 diffs, exit 1; refuses an internally inconsistent oracle) and--allow-missing-audio(PARTIAL verdict for Track-1-only dumps, in both extract and--verify). Makefile:disc-extract(probe → disc presence →--verify-disc→ extract+compare → verify),extractruns it when$(EXE)is absent,extract-allruns it once first,check-envstep 6 WARN-on-absent + step 7 oracle self-consistency + disc INFO,helprewritten,cleankeeps the 4 preset headers (now tracked),.PHONY..gitignorere-tightened to H1 (see SETUP P33 B1). Controls: no disc → exit 2 + staging text; truncated 100 MB Track 1 →--verify-discFAIL, nothing written,git diff --exit-codeon the oracle clean; the real run withextracted/moved aside and only the two manifest files restored →disc-extract: OKin 15.7 s (EXIT=0,.run/P33/b1_disc_extract.log),diff -rqagainst the previous tree IDENTICAL for all 1,801 retail files; second run "up to date" in 0.7 s;make check-envOK with the new PASS lines. Slip, recorded: the previous tree also heldextracted/proto/(the two prototype EXEs from the prototype discs, not the retail one) and I removed the moved-aside copy in the same command as the diff — regenerated fromdisks/withtools/bfm_extract/extract_proto_exe.py(sep8 413,696 B sha143006a31…, aug31 415,744 B sha16150b0f8…). Commit026571291. - 2026-09-06 (S86) — B2 (portable includes).
tools/audit_text_sources.pygained the portable-include class (absolute / angle-bracket / unresolvable / outside-repo#include); positive control BEFORE the fix: 34 offenders in 19 files, all ABSOLUTE (4,299 sources scanned); the sed →../shared/engine_core.h×30 +../shared/engine_types.h×4;grep -rn /home/musashi src include→ 0; negative control → OK. Re-gate of the 15 binaries (derived from the diff, 5 in parallel): 15/15[ OK ]BYTE-IDENTICAL in 29 s wall (.run/P33/b2_gate/<bin>.log). SETUP: P33 B2 section (R21). Commit9be6364a5. - 2026-09-06 (S86) — B3 (bootstrap + the fresh-clone proof).
tools/bootstrap.sh(apt presence → printed install line; venv fromrequirements-python.txt; submodules; cc1 tarballs sha256-checked + extracted into their own dirs;make check-env),make bootstrap; check-env gained 4b (the other three submodules, WARN), 4c (the four tracked preset headers, FAIL) and 8 ([INFO] extracted payloads present: N / 218). Idempotent run here: 1.4 s, OK. The proof (.run/P33/ b3_fresh.log):git clone --no-localinto.run/P33/fresh(the uncommitted Makefile + bootstrap.sh copied in) →tools/bootstrap.shcreated its own.venv, extracted both cc1s, fetched the submodules,check-env: OK→diskssymlinked →disc-extract: OK→extract-all: 217 extracted, 0 failed of 217→check-all: 218 passed, 0 failed of 218, EXIT=0, 4 m 18 s wall (user 45 m 48 s) — with NO SDK objects in the clone (.run/obj40absent), i.e. every binary built the way a public user builds it. Clone deleted afterwards (3.1 GB). SETUP: P33 B3 section (R21). Commit89e087f52. - 2026-09-06 (S86) — B4 (the optional SDK, user-supplied and verified).
git mv tools/psyq/CHECKSUMS.sha256 tools/psyq_CHECKSUMS.sha256(done here rather than in C3 so the script's path is final) + rows for the parser tarball (353495f1…, decomp.me's release), the parser binary (4fba623a…, identical to our Phase-7 copy) and the 20 PsyQ 4.0 LIBs;docs/SETUP.md+.gitignorereferences repointed. Newtools/psyq_libs_from_disc.py(the 20PSX/LIB/*.LIBoff the DTL-S2002 redump disc via our own ISO walker; refuses a disc withoutPSX/LIB; asserts 20) andtools/fetch_psyq.sh [--disc|--from] [--no-build](parser → RTL 4.2 7z → lib421 → the 4.0 LIBs, each sha256-verified before use; thenpsyq_build_libs.sh×10,make_libgs.sh,make_snd_used.py, the lib421 ELF step,make_apicard_used.py,make sdk-dual). Controls: no 4.0 source → refuses naming the redump title (rc 1) after verifying the parser + 4.2 pieces;--froma dir with one corrupted LIB →sha256 MISMATCH for tools/psyq/lib40/LIBTAP.LIB, rc 1, nothing built. The run: withtools/psyq/lib40/moved aside,fetch_psyq.sh --disc "<our Track 1 .bin>"extracted the 20 LIBs from the disc (diff -rqagainst the tracked copies: identical), verified them, rebuilt.run/obj40/*,.run/obj42/*,tools/psyq/lib40_elf/, andsdk-dual: OK— 36.9 s wall, EXIT=0 (.run/P33/b4_fetch.log). SETUP: P33 B4 (R21). Commit707ec491e. - 2026-09-06 (S86) — B5 IN PROGRESS (checkpointed at 87% context; WIP commit). Done and proven: MCP stopped cleanly;
tools/ghidra_scripts/ExportAnnotations.java+tools/ghidra_export_annotations.sh— byte-stable JSONL export of a program's container facts, LOCAL types, every function signature (params/locals/storage/sources), defined data, the 5 comment kinds, bookmarks, equates, and labels the symbol files do not carry; all 129 programs exported in 18.5 s →.run/ghidra_export/<prog>.jsonl(139 MB; resident 2,573 rows in 5.7 s).tools/ghidra_annotations_delta.py(live − baseline; container rows always kept;--census). Makefileprint-%helper +GHIDRA_PROJrepo-relative; the sixtools/ghidra_*.shwrappers repo-relative (BFM_GHIDRA_PROJoverride;ghidra_mcp_verify.shtakes the program as arg 3 /BFM_GHIDRA_PROG);DefineFunctions.javatakes the list path as arg 1;ImportPsyqGdt.javafinds the gdt underApplication.getInstallationDirectory();ExportSymbols.javaR15 fix (output path arg, refuses to overwrite, refusesconfig/). Written, NOT yet proven:tools/ghidra_scripts/ImportAnnotations.java,tools/ghidra_rebuild.sh(its import step works: the scratch project must live underbuild/ghidra_rebuild/proj— Ghidra refuses a path component starting with '.';resident_funcs.txtfrom the ELF = 1,304 entries). Blocker found: in the resident rebuild the runs after the import failed withFailed to get OSGi bundle containing script: …/tools/ghidra_scripts/ApplySymbols.java(same for ExportAnnotations) — Ghidra compiles the script DIRECTORY as one bundle, so ONE file that does not compile breaks every script in it;ExportAnnotationsworked on 129 programs BEFOREImportAnnotations.javaexisted ⇒ the new file almost certainly has a compile error (one known:new LocalVariableImpl(name, first, dt, stackOffset, program)— no such ctor; the stack form is(String, DataType, int, Program)without first-use, or(String, int, DataType, VariableStorage/Address/ Register, Program)). Census of the live exports (heuristic, before baselines):types=0in EVERY program (no hand-authored structs in the DB at all); the 154/402 "USER_DEFINED" variables per program are the loader's GTEMAC functions (analysis-origin, will subtract); real user labels beyond the symbol files: aug31 382, sep8 15, SLUS_007.26 1, overlays 1–2. - 2026-09-06 (S87) — B6 (dumps).
dumps/CHECKSUMS.sha1(28 rows fromsha1sum ram_*.bin;sha1sum --check28/28 —ls dumps/*.bin | wc -l= 28,git ls-files dumps= 29 incl. INDEX; the.binare still TRACKED until C3, ignored for new adds since B1);dumps/INDEX.mdstatus bullets rewritten (LOCAL-ONLY from the flip; the archive repo holds the committed copy; a re-capture viatools/ram_probe.py snapshotis a new state snapshot, never byte-identical — the table of 28 states preserved verbatim);docs/memory-map.mdSource-index row for the corpus. Commit: see below. - 2026-09-06 (S87) — B7 (the ROM-free CI).
tools/public_rewrite/purge_set.txt(THE purge set, filter-repo syntax — created here soaudit_publicand C1 share one file);tools/audit_public.py(purge paths + a DERIVED ROM-hash set [1,801 manifest rows + 218 check.*.sha + redump] + 50 MiB cap; controls: the current tree FAILS with exactly the purge set — 255 offender rows — a clean subset OK, a renamed EXE copy caught by content; empty-file SHA1 collision with the zero-length SC04/SC05FILE_029/1.6payloads found and exempted);tools/compile_only.py(flags parsed from the Makefile, TUs from<alias>_SRC_DIRwith nested pruning, skips DERIVED: 70 LINKED + 47 INCLUDE_ASM, -O0 TUs compiled at -O0; measured PR scope 54/54 in 1.6 s, fleet 4,170/4,170 in 123 s at -j32, failed 0; unknown alias refused);.github/workflows/no-rom.yml(jobsaudits+compile-only; every audit command re-run under the SYSTEM python without the venv/obj40 → rc 0 ×8; apt needscpp-mipsel-linux-gnufor the MIPS cpp —dpkg -Smeasured;cdecl --audit --gccleft out: > 5 min exhaustive). Theauditsjob is RED until C3 by design (the purge set is still tracked). Gotcha, recorded:pkill -f '<pattern>'from a shell whose own command line contains the pattern kills that shell (exit 144) — match on the child's distinctive argv or usepgrep -f … | grep -v $$. SETUP: P33 B7 section + 4 inventory rows (R21). Commit: see below. - 2026-09-06 (S87) — B8 (SETUP cross-references +
docs/verification.md). Every P33 tool already had its SETUP section + inventory row (written with its task, R21); B8 added the as-built pointers the plan named — §4.4 →make disc-extract, §4.6 →make bootstrap, §4.8 →tools/fetch_psyq.sh, §6.3 →make help+ the new targets + the R22 line — and the "Backup & private-repo posture" P33 update (R20's new home:config/ghidra/+--proof,dumps/CHECKSUMS.sha1,tools/psyq_CHECKSUMS.sha256, the archive repo; "nevergit clean -x"). NEWdocs/verification.md: what you need (redump SHA1/CRC, apt, disk/time), the R22 recipe with every expected last line (frommake help, the P32/B3 logs and the current digest: 363,214 / 13,492,113 / 5,820,205 / main 45,150), what is NOT our C (1,256 LINKED + 5 verbatim), the last-recorded-run table (placeholder until A5 fills it fromSUMMARY.md), what CI proves without the disc, the regenerable RE artifacts.tools/verify_contract.shis A5's (next). Commit: see below. - 2026-09-06 (S87) — A5 THE RECORDED RUN: PASS.
tools/verify_contract.shon HEAD3e0ecfacc→.run/P33/verify/(tracked): 00 tree · 01check-env: OK· 02 family_hseq 217 scanned / 0 open · 03check-all: 218 passed, 0 failed of 218(157 s, clean fleet) · 04sdk-dual: OK — main 143dbb89… WITH and WITHOUT(28 s) · 05tools-health: OK(356 s;sig-main-oracle: 0 PHANTOM, 0 TRUNCATED, 0 PAD-TAIL; zero[warn]) · 06 audit-frontier (1,258 stubs = all in main's LINKED regions, 0 game-code) · 07UNCLAIMED code payloads: 0· 08 report363214/363214 · 13492113/13492113 · 5820205/5820205 · main 45150/45150,INCLUDE_ASM stubs : 0, backlog 0. Every step EXIT=0 with its contract line; SUMMARY.md pasted intodocs/verification.md§2. Total wall 14 min (32 CPUs, JOBS=16). Two false starts recorded: the porcelain check tripped on the run's own untracked logs (→-uno), and audit_frontier's derived LINKED count read 0 twice (the reference view keys addresses, not Stub records) — each fix gated on a control before its commit (R66). Gotcha ×2:pkill -f '<pattern>'kills the calling shell when its own command line contains the pattern (exit 144) — bracket the pattern ('verify_contrac[t]'). Commit: see below. - 2026-09-06 (S87, Max) — B9/C3 the preparatory (purge) commit. Pre-checks: census of the purge set (1 EXE at
extracted/retail/, 28 dumps, 27 Ghidra DB files, 189 undertools/psyq/— all Sony/third-party: 20 LIB, 61 h, 55 o, 37 a, 10 EXE, 3 zip, psyq.ini, psyq-obj-parser; 3 archive parts; 2 zips; brave.exe = 251 index entries; nothing project-authored inside a purged dir — CHECKSUMS had moved in B4); ignore coverage proven withgit check-ignore --no-indexon every path (the plain form is BLIND to tracked files — it reported nothing); control: main byte-identical withtools/psyq/+.run/obj40+.run/obj42moved aside (0build/psyq/map entries = the public WITHOUT leg; B3's fresh clone still HAD tools/psyq), WITH state restored (1,288). Thengit rm --cached×251 (files stay on disk: 37 sampled present);git ls-fileson every purge path → 0;??purge paths in status → 0;tools/audit_public.py→ OK, 0 offenders among 6,566 tracked paths (its first green; 255 offenders before);make check-env→ 0. Docs in the same commit:docs/public-flip-runbook.md(NEW — Block C operational: decisions, actor table, C3…C11 with exact commands/checks, the Support-ticket text, the probe, fallback, risk register, rollback; the mailmap is SCRATCH and the runbook names no personal address), SETUP §2.3/§2.4 (the two zip sha256s + byte sizes; download-only now),docs/verification.md(the--cachedremoval changes no tracked-content byte → A5 holds for this tip),CLAUDE.mdfail-safe line "nevergit clean -x" (the hazard begins at THIS commit: ignored-but-present dirs), the decision-log entry (R31: why the purge leaves the index before the rewrite). Re-measured repo state for C1/C2: main 4,029 commits before this commit,--all4,300; refs = main, origin/main, origin/HEAD, 1 stash, the tag (NOrefs/original/, 1 stash — not the S86 block's 3 + refs/original; re-measure in C2);origin/mainis still the P32 close commit — Drew has not pushed the P33 commits yet (R6);.git929 MB. Commit: see below. - 2026-09-07 (S87, Max) — C1 the rewrite tooling, PROVEN by two trial rewrites on a scratch bare clone.
git-filter-repo==2.47.0in the venv (+requirements-python.txt);tools/public_rewrite/:common.py,hash_dict.py(4,420 commit objects: 4,030 main / 339 twins / 51 orphans; 150,280 prefixes; 0 ambiguous, 0 collisions with 1,480 cited content hashes; the scratch mailmap: 2 personal identities → noreply),scrub.py(--test12/12;--sampleover HEAD: 397 MB in 7.9 s, 1,238 replacements in 98 files, 731 distinct tokens = EXACTLY the set git's own object lookup resolves; the 186 seven-char replacements read — all commit ranges in PhaseEnds/cookbook),gate_scan.py(+ the tracked fixtureexpected_offenders.txt; negative control PASS: 16.8 GB / 112,390 blobs in 2 m 25 s, every rule named, 0 strays),run_filter.py,verify_rewrite.py,build_commit_map.py,resolve_tokens.py,absent_scan.py(positive control on the current repo: FAIL with 82,362 offenders, 7 m 24 s),probe_github.sh. Trial #1 (filter 274 s) exposed two defects that would have corrupted the real run — (1) the EMPTY blob was in the strip list (an empty file once sat under a purge path) and--strip-blobs-with-idsdropped every "file emptied" change in history: 7 files kept their previous content and a restore commit was pruned as a second empty; (2) the byte-identical "Initial commit" (noreply-authored, no citations, no purge paths) keeps its hash and tripped the map's old-hash assertion — plus the post-rewrite gate's empty-list guard. Fixes: shared blobs are never stripped by id (content/signature hits always are);verify_rewriteasserts no purge path survives and that the pruned set == the DERIVED purge-only set; unchanged commits recorded (unchanged_commits.txt) and exempted in the map, absent_scan and the probe; the guard fires only with path offenders. Trial #2: everything PASS — gate fixture PASS (268 ids, the empty blob excluded); filter 269 s, 1 pruned (exactly "session archive update"), main 4,030 → 4,029;verify_rewrite4,029 pairs / 0 failures (241,534 changed blobs re-derived, 989,166 removals justified, the empty commit survived) in 430 s; map 4,030 rows / 1 pruned / 0 old hashes / 1 unchanged; absent_scan on the clone PASS (0/0/0/0/0/0; INFO 370 bare UUIDs) 409 s; gate on the clone PASS (0 offenders, 60 s); resolve_tokens on a trial checkout: 1,231 tokens in 97 files, residue 7 (commit:1712×3 = the pruned commit's ordinal, orphan-24 ×2, orphan-26, orphan-35 — cited commits that exist in no lineage),--check0 remaining; absent_scan --tree HEAD PASS. Pack size: filter-repo's gc leaves 500 MB;repack -adf --window=250 --depth=50→ 80 MB in 166 s (C9). SETUP P33 C1 section + rows; runbook §3/§5/§6/§10 updated with the measured facts and the strip-by-id lesson. Trial scratch deleted (C4 re-clones fresh). For Drew's decision (not in the plan's scope, reported by absent_scan as INFO): 370 bare session UUIDs across history / 68 at HEAD in checkpoint prose; noclaude.aiURL anywhere. Commit: see below. - 2026-09-07 (S87) — C2 the recorded controls on the final pre-rewrite tree (HEAD
fca383904, the C1 commit).hash_dict.py --write-mailmap: 4,421 commit objects (4,031 main, 339 twins, 51 orphans), 150,314 prefixes, 0 ambiguous, 0 collisions with 1,480 cited content hashes, 2 identities → noreply.scrub.py --test12/12;scrub.py --sample: 397 MB in 7.9 s, 1,239 replacements in 98 files, 732 distinct tokens == the 732 git's own lookup resolves (only-git 0 / only-ours 0), 186 seven-char replacements read (.run/public_rewrite/c2_sample.log, scratch — it prints old hashes).gate_scan.py --all --worktree --expect-fail: 112,405 reachable blobs / 16.79 GB in 3 m 13 s, every rule named with its count (ghidra/ 42 paths ever, tools/psyq/ 190, dumps 28, archive 3, zips 2, brave.exe 1, the EXE 1+1), 0 strays, 268 ids to strip (the shared EMPTY blob excluded),audit_publicOK 0 offenders among 6,578 tracked paths. One more R43 guard added torun_filter.py: it refuses a dictionary whose main count/HEAD differ from the clone's (a stale dictionary would drop rows from the public map; trial #2 matched by construction). Sequencing law for C4: the dictionary + ids are rebuilt from the FINAL tree right before the clone (4 s + 3 min) — any commit after that invalidates them (the guard enforces it). Commit: see below. - 2026-09-07 (S87) — C4 the backups and THE REWRITE. C4a:
.run/public_rewrite/pre-rewrite.bundle(556 MB,--all --reflog,git bundle verify: complete history, 6 refs) made after the C2 commit. C4b (Drew, R6): WSL had no GitHub credentials (password auth refused) →gh auth login -h github.com -p https -w(device flow, browser opened by hand — the "failed opening a web browser" line is cosmetic, Ctrl-C cancels the login) +gh auth setup-git;Druthulu/BFM-decomp-archivecreated (Private; it carried GitHub's own.gitattributesinitial commit — force-updated, harmless) andgit push --mirror archive(176,533 objects, 530 MiB; GH001 large-file warnings for the 3 archive parts, accepted). Verified viagh api: private, not a fork, no parent;git ls-remote archive== local refs exceptrefs/stash, which--mirrordoes not push (the bundle holds it; Drew may push it as a branch). C4c: FINAL dictionary on the committed tip (4,422 commit objects, 4,032 main, 0 ambiguous, 0 collisions) + FINAL ids (gate_scan --expect-failPASS; 268 ids, the shared empty blob excluded) → bare--no-localclone (1 pack, 0 loose) → tag deleted (its tip recorded in scratch) →run_filter.py: 311 s, commit-map 4,032 rows, exactly 1 pruned ("session archive update", ordinal 1712), main 4,032 → 4,031, new tip494fbd3c8, 1 pack / 0 loose / 500 MB, scrub stats: 72,568 hash replacements, 85 address replacements, 60 trailers dropped, 192 binary blobs untouched. Logs:.run/public_rewrite/c4c5.log,filter.log(scratch). - 2026-09-07 (S87) — C5 the verification suite on the rewritten clone — ALL PASS.
verify_rewrite.py: 4,031 pairs / 0 failures (identities post-mailmap, both timestamps, message == scrub(old), parents with the pruned commit spliced, 241,734 changed blobs re-derived as git-hash(scrub(old)), 989,166 path removals justified, no purge path survives in any new tree, pruned set == the derived purge-only set, the pre-existing empty commit survived) in 433 s.build_commit_map(scratch copy): 4,032 rows / 1 pruned / 0 old hashes / 1 unchanged (the noreply-authored "Initial commit", ordinal 1, byte-identical).absent_scan.pyon the clone: 0 offenders across 112,138 blobs (16.2 GB text) + 4,031 commits + refs (INFO 370 bare UUIDs) in 360 s.gate_scan.py --all --repo <clone>: PASS, 0 offenders over 16.22 GB in 56 s. - 2026-09-07 (S87) — C6 adoption in
~/bfm-decomp(NO gc).git fetch <clone> +refs/heads/main:refs/heads/main-rewritten;git diff --name-only main main-rewritten= 100 files, all text (0 binary), 0 purge paths, 0 added/deleted (the token-scrubbed docs/logs/tool comments at the tip);git reset --hard main-rewritten→ HEAD494fbd3c8; tracked 6,578 == the clone's tree;main-rewritten,refs/original/refs/heads/main, the tag and the 1 stash dropped (bundle + archive hold them); refs now:main,remotes/origin/{HEAD,main}(the P32 close, pinning the old lineage until Drew's push),remotes/archive/main(the pre-rewrite tip);rev-list --all --count8,143 (both lineages); the purged paths still on disk. - 2026-09-07 (S87) — C7 the commit map + the tip resolution. Repo-local identity →
Drew T <50529377+Druthulu@users.noreply.github.com>.build_commit_map.py→docs/commit-map.tsv(4,032 rows, 1 pruned row of zeros, 0 old hashes asserted) + privateold-to-new.tsv+unchanged_commits.txt(1).resolve_tokens.py: 1,238 tokens → shortest-unique ≥9-char new hashes in 98 files (phase-ends 24, logs 20, docs 23, tools 14 — comments/docstrings only, checked; 2src/files — C comments only, checked;.run/P33/verify3,.runnotes);--check0 remaining; residue 8 in 4 tokens:commit:1712×4 (the pruned commit),commit:orphan-24×2,commit:orphan-26,commit:orphan-35(cited commits that exist in no lineage — one is the dropped TEMP commit in tools/verify_worktree.py). Defect caught and fixed before the commit: the resolver had rewrittenscrub.py's self-test fixtures (commit:0012etc. are the grammar's own examples) — restored from HEAD, andtools/public_rewrite/is now skipped by rule.absent_scan --tree HEADPASS,audit_publicOK. The tip commit (this one) cites NEW hashes only. Commit: see below. - 2026-09-07 (S87) — C8 R22 on the ADOPTED tree: PASS.
tools/verify_contract.shon HEAD5bc4a5c0ab(the rewritten history + the C7 tip + one fix): 00 tree · 01check-env: OK· 02 family_hseq 217/0 · 03check-all: 218 passed, 0 failed of 218(176 s, clean fleet) · 04sdk-dual: OKboth legs143dbb89…(29 s) · 05tools-health: OK(358 s;0 PHANTOM, 0 TRUNCATED, 0 PAD-TAIL, zero warns) · 06 audit-frontier (1,258 stubs = main's LINKED regions, 0 game-code) · 07UNCLAIMED code payloads: 0· 08 report363214/363214 · 13492113/13492113 · 5820205/5820205 · main 45150/45150, stubs 0, backlog 0. Total ≈14 min. A content-preserving rewrite changed no tracked-content byte — proven. Two false starts recorded, both my own instrument: the A5 logs under.run/P33/verify/are TRACKED since A5 (I deleted them before the run → dirty tree), andrun_stepwrites00_tree.logbefore the porcelain check runs → step 00 failed on its own log; fixed in5bc4a5c0ab(the check now excludes.run/P33/verify/). SUMMARY pasted intodocs/verification.md§2 (the C8 run is now the recorded one; the A5 run is cited by date). Commit: see below. - 2026-09-07 (S87) — C9 the final gate, THE FORCE-PUSH (Drew) and the local gc. Gate on the final
main+ worktree:gate_scan --refs main --worktreePASS (0 offenders over 16.23 GB, 61 s;audit_publicOK 6,579 paths);origincarried no tags. Drew:git push --force origin main(176,052 objects, 491.5 MiB) →git fetch --prune origin→origin/main == main == e821833986. Local gc, with two discoveries: (1)refs/remotes/archive/mainpinned the old lineage →git remote remove archive(done here instead of C11); (2) 12 stale linked worktrees (.run/S74/wt_*×8,.run/pgate/wt8-9,.run/S69_fable3/pgate/wt0,~/bfm-verify— campaign gate scratch from 2026-08-24…09-02, ~12 GB, each a full old-history checkout with the SDK/EXE/Ghidra on disk, no process using them) kept 3,729 old commits reachable (a linked worktree's HEAD counts) →git worktree remove --force×12 + prune:rev-list --all8,146 → 7,763 (remote) → 4,034 = main. Then reflog expire +repack -adf --window=250 --depth=50+ prune + a fresh commit-graph (the stale one named pruned commits → fsck errors): one pack, 80 MB;.git1.5 GB → 93 MB; store == reachable (176,056 objects); fsck clean; disk 13 → 24 GB free. Scans on the working repo:absent_scanPASS 0 offenders (112,251 blobs / 16.2 GB text + 4,034 commits, 438 s; INFO 375 bare UUIDs),gate_scan --all --worktreePASS (94 s). Probe baseline (probe_github.sh, gh-authenticated): 33 old hashes sampled → 31 still ALIVE on GitHub, 2 already gone — the ticket's target is 0; the probe now treats the API's 422 ("no commit found") as gone (it had counted the vanished tag tip as alive). The rewritten history is the only one reachable locally and the only one reachable onorigin; the old objects remain SERVABLE on GitHub until Support purges — hence C10. Runbook §10/§12 updated with the measured procedure. Commit: see below. - 2026-09-07 (S87, Max) — D1 the README rewrite. The old README was Phase-19 era (136 binaries, 52 functions, "about
half the EXE is stubs") — every number stale, so a full rewrite. The numbers are GENERATED:
tools/progress.py --json→docs/progress.json(schema 1: the four metrics with numerator/denominator/pct + a one-line "what", the counts — real 360,737 / shared 255,632 / linked 1,256 / verbatim 5 / stubs 0 / non-matching 0 / matchable 363,214 / dedup 2,220 groups, 255,708 instances — and 218 per-binary rows; no run date, so it never churns) and--readmerewrites the block between<!-- progress:begin/end -->(refuses a README without the markers — control run;--checkasserts both are fresh); wired intomake report BINARY=mainafter--fleet. README (144 lines): the claim and the contract · the generated block · what is NOT our C (1,256 Sony objects + 5 verbatim) · build from your own disc (the verification.md recipe, redump SHA1/ CRC32, the EXE SHA1, the pinned triple, the optional SDK) · repository layout · how it was made (ProjectArchitect link, the governance files, the knowledge base) · About the history (the rewrite, thecommit:NNNNtokens,docs/commit-map.tsv) · the license split · contributing + the no-ROM policy · Special thanks (the plan's list, sotn-decomp style) · the Square trademark line · 3 badges. Forward references the README makes to files later tasks create (D5's link checker must see them by then):LICENSE,src/NOTICE.md,THIRD_PARTY.md(D2);docs/badges/fleet_instr.json,docs/badges/binaries.json(D3 — these exact names);docs/story.md(F1),docs/retrospective.md(F2). Commit: see below. - 2026-09-07 (S87, Max) — D2 LICENSE + NOTICE + THIRD_PARTY.
LICENSE= the AGPL-3.0 text fetched verbatim fromhttps://www.gnu.org/licenses/agpl-3.0.txt(661 lines, 34,523 bytes — never retyped).src/NOTICE.md: a reimplementation for study/interoperability/preservation, © 1998 Square Co., Ltd. acknowledged, no affiliation, no license asserted oversrc/, "distributing the compiled output is distributing the game's code — don't", what the repo does not contain; the phrase "clean-room" appears nowhere (grep = 0).THIRD_PARTY.md: 17 rows, every license read from the upstream source of truth — the submodules' on-disk LICENSE files (maspsx MIT, decomp-permuter MIT, asm-differ Unlicense, m2c GPL-3.0), brave-CUE'slicense.txt(GPL-3.0; the compiledbrave.exepurged from history), and the GitHub API's license metadata for the rest (splat/spimdisasm/rabbitizer MIT, Ghidra Apache-2.0, GhidrAssistMCP MIT, PCSX-Redux GPL-2.0 — psyq-obj-parser is part of it; old-gcc and ghidra_psx_ldr publish NO license file → stated as such, download-only; the SDK proprietary and never distributed; the AP world cited as facts).tools/README.md(new): the one AGPL statement fortools/, no per-file SPDX headers. For Drew:docs/history/project_architect_v1.3.0.mdis KEPT as the historical record of the framework version used (THIRD_PARTY row links the repo) — say if you prefer link-only. Commit: see below. - 2026-09-07 (S87) — D3 progress publishing.
progress.py:weighted_metricsnow returns per-binary instruction totals (per_bin, from the existing loop),--jsonrows carryinstr_matched/instr_total(main 45,150/45,150, resident 4,483/4,483 — known-true check), anddocs/badges/{fleet_instr,fleet_fn,distinct,binaries}.jsonare written in the shields endpoint format;--checkalso asserts the badges (control: a mutated badge → STALE).make audit-digestrunsprogress.py --json --readme --check.tools/objdiff_report.py: report.proto v2 (snake_case) — 218 units all complete, code 53,968,452/53,968,452 bytes (= 13,492,113 × 4, reconciles), functions 363,214/363,214, categoriesgame-code+linked-sony-objects; validated with the realobjdiff-cli3.8.1 (report changes a aparses and reports no change; a mutated unit is reported — control) — itschangestakes POSITIONAL previous/current.tools/frogress_upload.py: dry run prints the payload (git_hash, timestamp, the measures with denominators);--pushneedsFROGRESS_API_SECRET..github/workflows/progress.yml: converts the committed JSON, uploadsSLUS_007.26_report(no rebuild in CI). SETUP: 3 inventory rows + a P33 D1–D3 section (R21). Outward (Drew, after the flip): decomp.dev registration, frogress slug + key. Commit: see below. - 2026-09-07 (S87) — D4 the SETUP.md public-clean pass. Header refresh line (public from P33, H1 in force, the remote);
the
TBDremote sentence →origin= the public repo + the private archive; §4.4's/mnt/z→ a generic<dump-source>; §4.7 as-built note (the cc1 tarballs are TRACKED, sha256-verified,bootstrap.shextracts them — the wget lines are provenance, not a contributor step); §6.5 gains the project's decomp.me preset line (compilergcc2.7.2-psx, the exact flags incl.-Wa,--aspsx-version=2.56,--expand-div; E1 createsdocs/decompme-preset.md); the "Backup & private-repo posture" section rewritten as "Backup & repository posture — PUBLIC since Phase 33" (H1 in force, what is never committed, the private era as a dated historical note pointing at the archive repo + bundle; the two-repo mirror plan stated SUPERSEDED; Sony libs never distributed, cc1 tarballs tracked);requirements-python.txtpath fixed; ledger row 11 CLOSED (the remote), row 14 annotated to close at E1; the one R21 gap found by the census (git log --diff-filter=Aontools/since the P32 close vs the inventory: 25 P33 tools, 1 missing) filled — atools/verify_contract.shrow. Residual grep forcurated public mirror|two-repo|private repo|/mnt/z: only the new sentences that describe the change. Commit: see below. - 2026-09-07 (S87) — D5 the governing-docs consistency pass +
tools/doc_links.py.CLAUDE.mdfail-safe line now names the Ghidra project, the dumps, the SDK and the session archive as out of git (H1 in force again, enforced by audit_public + CI);phase-ends/DIGEST.md§1: the H1 bullet rewritten (R1 historical; H1 in force since P33 C3; R20's home = the text export + checksums + the private archive; nevergit clean -x) and the Roadmap bullet (Phase 14 became P33, executed IN PLACE, not the two-repo mirror);docs/roadmap-to-100.mdgains a dated "Status at Phase 33" block above §0 (the P33 text below it kept as the historical plan, marked superseded where it says two-repo);docs/gen2-roadmap.mdPhase 14 gets a SUPERSEDED banner (nothing deleted);.gitignoreheader anddumps/INDEX.mdre-read — already correct (B1/B6);docs/backlog.md0 rows. NEWtools/doc_links.py(12 documents, 18 relative links, 0 broken; 2 PENDING with their creating tasks —docs/story.mdF1,docs/retrospective.mdF2 — listed indocs/doc_links_pending.txt, which must be EMPTY before gate 2:--strictrefuses pending; negative control: a broken link → BROKEN rc 1) intools-health; SETUP row (R21). The grep review (gitignored|private repo|curated public mirror|two-repo|stays out of git) over CLAUDE.md, DIGEST, README, both roadmaps, dumps/INDEX, verification: every remaining hit is either the historical plan text now under a SUPERSEDED banner, a dated historical note, or a sentence describing the change. Commit: see below. - 2026-09-07 (S87, Max narrative) — F1 the timeline + the story.
tools/timeline.py: walks the 450 commits that toucheddocs/progress.fleet.mdand the 51 that toucheddocs/progress.md(both historical formats: the JuneFLEET byte-identicalsingle metric, the post-07-11 three metrics withMAIN game-code weightedseparate until 07-22), keyed by DATE (the rewrite changed every hash), joins the 32 PhaseEnd headers and commits-per-day →docs/story-timeline.md(72 dated rows, 2026-06-14 → 09-06) +docs/story-timeline.svg(three polylines, phase ticks, the 07-22 step); self-check: the last row ==docs/progress.json(OK);--checkfor staleness. R14 correction on my own prose: the generated note first asserted the 07-22 denominator step "stepped the percentages DOWN" — the rows show 78.0% → 78.6% instruction-weighted (43 commits of banking that day absorbed main's game code entering the denominator); the note is now computed from the two rows.docs/story.md: the premise (decomp-first over the brief's recomp-first; the constitution and the cadence), Gen1 in five days, the fleet + dedup + the first ceiling (P8–P20), the breakthrough of reading the compiler (P21–P24), families and the tooling-integrity audit (P25–P28), the family campaign (P29), recovery and concentration (P30), the atlas, lanes and the last twenty-one (P31), the frontier emptied (P32), making it public (P33), and a by-the-numbers table — every section names its PhaseEnd / decision-log headings / timeline rows; the lost first month is stated as reconstructed from the PhaseEnds, git anddocs/history/.docs/doc_links_pending.txtshrinks to 1 (docs/retrospective.md, F2); the checker's default set gains the story, the timeline and the retrospective; SETUP row (R21). Commit: see below. - 2026-09-07 (S87, Max) — F2 the retrospective.
tools/mine_hindsight.py(stdlib; the decision-log'sHindsightbullets AND### Hindsightsections — 19 over 79 entries after widening the pattern, the first cut found 11 —, the two "What we believed" sections, 237 deviation rows over 32 PhaseEnds →.run/P33/hindsight.md, scratch,file:lineanchored).docs/retrospective.md: §1 a turn-by-turn table of belief vs truth with its record (16 rows, from the brief's recomp-first to the rewrite rehearsal); §2 eight failures and why each looked right, each with the rule it produced; §3 costs with denominators (the 92%-byte-correct/27%-banked measurement, the 250k-token twin waste, the 655k-token agent, the 31-drafter wave's model mix, the private-era rewrite, the 12 GB of stale worktrees, P31's 30-vs-18 sessions); §4 ten "sooner" items in hindsight-study §0's order; §5 what stayed hard (foldingdocs/hindsight-study.md§3/§5 anddocs/generic-decomp-package.mdby reference, never rewritten); §6 the wiki paragraph. R31: every claim cites the log heading/line or the PhaseEnd.docs/doc_links_pending.txtis now EMPTY —tools/doc_links.py --strictPASSES (the gate-2 condition); SETUP row (R21). Commit: see below. S87 ends here at 87% context (Drew's call) — the checkpoint below is the successor's seed. - 2026-09-07 (S88, Max, Fable 5.1) — preflight + an R57 instrument defect in the probe. Session start per R64; the
harness task list rebuilt (40 items; A1–A5, B1–B9/C3, C1–C9, D1–D5, F1–F2 completed; C10 + F3 in progress). Preflight:
tree clean;
origin/main == main == 5e57e88de2— Drew pushed the post-C9 commits; the first-ever runs of BOTH workflows on GitHub are GREEN (no-rom1 m 35 s,progress15 s, run ids 34095194524 / 34095194479); repo still PRIVATE;doc_links --strictPASS; 18 GB free. Drew: F3 runs in FULL. The probe (run here, gh-authenticated): 31 of 33 old hashes still ALIVE — identical to the S87 baseline, no purge yet. Defect found (R57 — the instrument's own write path): the probe'sgit fetch origin <old-sha>succeeds for every ALIVE sha and thereby imports that commit's whole closure — the purged EXE, dumps, Ghidra DB, SDK — into the working repo as unreachable objects:git count-objectsread 30 packs / 5.97 GiB / 415,712 objects (C9 had left one 80 MB pack / 176,056 objects), two sampled old shascat-file -ePRESENT,fsck --unreachable13,403 heads; refs/reflogs all on the new lineage,rev-list --all== main (4,043). Fix:probe_github.shnow fetches into a throwaway bare repo (.run/public_rewrite/probe_scratch.git, remoteorigin,--filter=blob:none --depth=1— commit + trees only;trapremoves it) and ends with a self-check that names any sampled old commit the WORKING repo holds, printing the gc recipe. Control:git count-objects -videntical before and after the fixed run (101 loose / 415,712 in-pack / 30 packs both times), scratch removed, positive control OK, self-check "holds 31 of 33" (the pre-fix residue), verdict unchanged (31 ALIVE, rc 1). The repair itself —git reflog expire --expire-unreachable=now --all && git gc --prune=now— is REFUSED by the auto-mode classifier from a Claude shell; Drew runs it (expected: one pack ≈ 80 MB,.git≈ 93 MB, the two sampled shas absent). Runbook §11 + SETUP row (R21). Commit: see below. - 2026-09-07 (S88, Max) — F3 the wiki + how-to-AI-decomp, IN FULL (Drew's call at the S88 preflight). Authored in-repo
per decision 8:
docs/wiki/—Home.md,_Sidebar.md,Build-from-your-own-disc.md,Toolchain-setup.md,Repository-layout.md,The-matching-workflow.md,The-dedup-engine.md,Overlays-and-modules.md,Ghidra-rebuild-from-text.md,Verification-and-progress.md,Contributing-and-the-no-ROM-policy.md,How-to-AI-decomp.md(the chapter index) — anddocs/how-to-ai-decomp/00-README…12-failure-museum(the 13 chapters the plan named). 25 files, 1,896 lines / 163 KB; every page distilled from the records listed in SETUP's P33 F3 section (portable-decomp-workflow, hindsight-study, generic-decomp-package, accelerators, effort-map, retrospective, story, wave-playbook, runbook, verification, README, SETUP §4/B5/D1–D3, disc-completeness, memory-map's load slots, the core tools' docstrings) — no new facts, every number a recorded measurement with its denominator; the failure museum is 36 rows (what it looked like / what it was / why it looked right / what it produced), incl. the S88 probe defect. Tooling (same change, R21):tools/wiki_render.py OUT_DIR | --list | --selftest— a GitHub wiki addresses pages by NAME (no.md, no directories), so the in-repo pages carry ordinary RELATIVE links (checkable) and the renderer rewrites them deterministically (wiki→wiki = the page name; a chapter →How-to-AI-decomp-NN-name; anything else inside the repo →blob/main/tree/main/raw.githubusercontent.com; URLs/mailto/anchors untouched; a dead link is an ERROR — R43);--selftest= 12 cases incl. the dead-link negative control (12/12), wired intomake tools-healthafterdoc_links.tools/wiki_sync.sh [--push]— render into.run/wiki/render/→ clone/fast-forwardBFM-decomp.wiki.gitunder.run/wiki/→ REPLACE the wiki's pages (the repo is the source of truth) →git status --short;--pushis Drew's (R6). The wiki repo exists only after the flip AND the first page is created in the GitHub UI — until then the dry run renders + lists (exit 0; measured: 25 pages, 264 relative links rewritten, "not clonable yet … dry run OK") and--pushrefuses (exit 2).tools/doc_links.pygainedDEFAULT_GLOBS(docs/wiki/*.md,docs/how-to-ai-decomp/*.md, expanded at run time) —--strictPASS: 40 documents, 290 relative links, 0 pending, 0 broken;docs/doc_links_pending.txtcarried the 10 not-yet-written pages mid-task (the PENDING mechanism worked as designed: 24 pending hits, 0 broken at that point) and is EMPTY again. Runbook §11 gained the post-flip wiki step (first page in the UI, thenwiki_sync.sh --push); SETUP: 2 rows + the P33 F3 section.make tools-healthre-run with the new line:tools-health: OK — sigs fresh; corpus(+resident) + cdecl + binaries + report(lint+dedup) + cookbook-index all green.(EXIT=0). Commits:954362c81e(the pages + the tooling), then the wiring commit (Makefile · SETUP · runbook · this log · the checkpoint). What this session did NOT do: the local gc (classifier-refused; Drew's), the wiki push (post-flip; Drew's). - 2026-09-07 (S88, Max) — E3 the codegen map's README + every citation tagged with its source tree.
docs/gcc-2.7.2-map/README.md(NEW, indoc_links' default set): the five files and their pass groups, the condensed §31 triage table, the byte-proof method, the provenance legend (the vanilla 2.7.2 subset from the GNU tarball, sha2567cd8bce5…;pmret/gcc-papermario@a6afc2af…= gcc 2.8.1), the 2.8.1 line-number caveat (drift up to +611 lines), the S23 audit numbers (184 claims: 119 confirmed · 40 line-drift · 7 refuted of 21 raised · 4 unverifiable) and the tag census.tools/gccmap_cites.py: tags everyfile.c:NNNcite in the map (135 cites in 5 files) as[2.7.2]/[2.8.1 pm]/[repo], derived from the trees (R33): a quoted source snippet near the cite → an underscore identifier's function extent (^name (GNU-style headers) → the nearest occurrence within ±60 lines → the author's cues (papermario,2.7.2, the decisive→2.7.2 :NNNarrow) →docs/gcc-2.7.2-map/cite_overrides.tsv(20 rows, each with the construct and its line in the other tree; 2 of them resolve a tie) → a tie is a valid 2.7.2 line; a cue that contradicts the evidence leaves[?](R34); a line past the end of the 2.7.2 file is 2.8.1. Modes: write (idempotent; never silently changes an existing tag —--retagaccepts),--dry-run,--check(textual: every cite tagged, no stale override — inmake tools-health+ the CIauditsjob),--verify(re-derives every written tag),--controls(6 known-true cases from the map's own text, both drift directions),--explain(the undecided cites with the map's sentence and both trees' lines — the input to an override). Three instrument defects, found by its own controls/verify before any tag was written (R39/R57): (1) span pairing inside a ±160-char window inverted the backtick pairs and dropped the identifiers right next to a cite (must_and,QTY_CMP_PRI,CONSTANT_P…); (2) fenced code blocks inverted the pairing for the rest of a document; (3) after writing, a neighbouring cite's tag read as a2.8.1cue — the instrument reading its own output — caught by--verify. Bare ALL-CAPS prose words (NOT,AND,DEST) had also passed as evidence: identifiers now need an underscore, as every real gcc macro/function cited has. Final census:[2.7.2]79 ·[2.8.1 pm]55 ·[repo]1 · undecided 0;--verify0 disagreements;--controls6/6; second write 0 files. Wiring (R21): Makefiletools-health(--checkaftercookbook_index --check),.github/workflows/no-rom.ymlaudits step,doc_linksdefault set, SETUP row + the P33 E3 section.make tools-healthre-run with the new line:tools-health: OK — sigs fresh; corpus(+resident) + cdecl + binaries + report(lint+dedup) + cookbook-index all green.(EXIT=0) (.run/P33/e3_tools_health.log);doc_links --strictPASS (41 documents, 301 links, 0 broken). Gotcha, recorded: the first tools-health run was KILLED by the harness's low-memory guard during the report step (a transient spike; 29 GB available afterwards) — and the process table held 8 orphanedtools/permuter/run_masked.pyworkers from a closed phase, 49 h old (parent PID 18), stopped by PID (neverpkill -fwith a literal the calling shell carries); the foreground re-run passed. Commit: see below. - 2026-09-07 (S88, Max) — E4 xsig packaged:
tools/xsig/+ the standalone repo. The Phase-21 cross-project dedup probe's library (.run/xdedup/xsig.py, 2026-06-25) and its four driver scripts folded into ONE stdlib file,tools/xsig/xsig.py(library + CLIsign-s | sign-objdump | cross | verify | selftest;compare()= the instruction-by-instruction diff a score cannot replace — opcode/register/immediate/length, R63), withREADME.md(the recorded worked example: BFM × Xenogears + Vagrant Story = 103 hits, all PsyQ library/BIOS — libapi 34, libcd 14, libetc 14, libspu 11, libcard 7, largest_spu_setReverbAttr307 ins, 37 already named; BFM × Tomba = 126 hits, 124 library, one 19-ins non-library HIGH; the decision log's "clean negative — zero engine code"), an MITLICENSE, andtests/: a game-free fixture —tests/fixture.c(two functions of our own) compiled with the pinned triple bytests/make_fixtures.shand linked TWICE (ld -q=--emit-relocs,-Ttext0x80010000 vs 0x80200000,--defsymhelper/table at different addresses) →fixture_a.txt/fixture_b.txt(objdump listings) +fixture_a.s(the splat form,%hi/%lofrom the reloc records);tests/test_xsig.py8 checks (same function at two link addresses signs identically while the raw words differ; the 3 relocated fields are present and the only masked ones —verify: IDENTICAL up to relocation; a different function differs; a register flip changes the sig and is classifiedregister; the two front-ends agree;sign-objdump+crossfind exactly the two true pairs;verifyrejects the mismatch;selftest) — 8/8 OK. Two gotchas, recorded:objdump -drinterleaves relocation records only for OBJECT files — a linked ELF lists them separately (-r, section-relative offsets), so the generator merges them into the listing in the object-listing format; and a linked listing puts the address at column 0 (an object listing indents it) — the instruction regex is^\s*. Sibling-repo URLs in the README taken from the reference clones'git remote get-url origin, not from memory. Wiring (R21): the tests inmake tools-health(xsig tests: OK (8)) + the CIauditsjob;tools/xsig/README.mdindoc_links' default set (--strictPASS); SETUP row + the P33 E4 section. The standalone repo:.run/P33/xsig-repo/=xsig.py,README.md,LICENSE,tests/(+.gitignore),git init, the repo-local NOREPLY identity (the global identity DIFFERS and was never used), the tests run there with the SYSTEMpython3(self-contained, no venv), one initial commit — Drew createsDruthulu/xsigon GitHub (EMPTY) and pushes (git -C .run/P33/xsig-repo remote add origin … && git push -u origin main).make tools-healthre-run with the new line:tools-health: OK — sigs fresh; corpus(+resident) + cdecl + binaries + report(lint+dedup) + cookbook-index all green.(EXIT=0) (.run/P33/e4_tools_health.log). Commit: see below. - 2026-09-07 (S88, Max) — E5 the permuter upstream PR branch +
docs/permuter-ils.md— IN FULL (Drew's call). Scratch clone ofsimonlindholm/decomp-permuteratmain41bd0bfc(2026-09-05; 39 commits past our pinb44b0622) → branchreloc-masked-scorer, one commit under the noreply identity:src/reloc_scorer.py(RelocMaskedScorer, aScorersubclass — the in-treemasked_diffrule: mask from the TARGET's relocation records,R_MIPS_26→ opcode only, the 16-bit-immediate relocs → opcode+rs+rt, ajagainst.textcompared relative to the function start, everything else the full word, symbol+addend equality at masked slots,score = mismatches + |Δlength|, 0 iff link-identical; MIPS only),--score-mode {mnemonic,reloc-masked}+ thescore_modesettings key (default unchanged), a refusal with-J(remote evaluators build a stockScorer),--debugper-instruction diff, USAGE/README/example_settings notes,test/test_reloc_scorer.py(10 tests on an embeddedobjdump -drzlisting of the xsig fixture object — real gcc 2.7.2 output, no game bytes; the end-to-end test feedsobjdump_command="cat"a file with a 20-byte MIPS ELF header ahead of the listing, sinceget_archreads ident + e_machine). Verified at the commit: 10/10;black --checkclean;mypy= upstream's baseline (5 pre-existing:tomlstubs,Levenshtein,helpers.pyAny — none added, checked by stashing);./run-tests.sh: only upstream'stest_permfails (needsmips-linux-gnu-gcc); the real permuter end to end (.run/P33/permuter-e2e/: ourcompile.sh,settings.tomlwithobjdump_command = "mipsel-linux-gnu-objdump -drz -m mips:4300"because upstream looks formips-linux-gnu-objdump): base == target → base score 0 →--stop-on-zero; the floor in miniature —other()(5 ins) with itsxoroperands swapped: reloc-masked base 4 → score 0 at iteration 256 (<40 s,-j4; the winner restored(a * 3) ^ (b >> 2)), while the DEFAULT scorer read the same base as 3,585 and sat at 3,420 after 20 iterations. Two gotchas fixed on the way (both caught by the tests / mypy before the commit): aMatch-typed loop variable shadowed by the instruction loop (mypy union-attr ×3); the stand-in object needing a real ELF header. The tracked copy (R20):tools/permuter/upstream/ 0001-reloc-masked-scorer.patch(git format-patch; proven togit amcleanly onto upstreammain; the one whitespace warning — a doubled newline at USAGE.md's end — fixed and the commit amended). A dev venv (.venv-dev/: mypy 2.3.1, black 26.5.1, toml, pycparser) lives in the scratch clone, never in the project venv.docs/permuter-ils.md(NEW; indoc_links' default set,--strictPASS): §1 why the stock scorer floats (the"." in fieldwildcard), §2 the scorer + the PR + the four byte-bought rules (-drz, the internal-jtarget, keep-the-opcode, PC16) + Drew's push/PR commands, §3 the issue text for PR-2 (a configurablesymbol_regex), §4 the ILS warm-restart recipe and its five guards (func_8014809472 → 36 over ~8 restarts; hidden pins re-hidden per cycle; refused-cycle abort R61; comment strip; flushed stdout R55; winner ≠ bank), §5 related. SETUP: row + the P33 E5 section (R21). Drew: fork upstream,git -C .run/P33/permuter-upstream push -u fork reloc-masked-scorer, open the PR (the commit message is the description), file the issue. Commit: see below. - 2026-09-07 (S88, Max) — E6
docs/matching-drafter-pipeline.md, the drafter write-up. Distilled fromdocs/gen2-mips-matching-model.md(every measurement of the local-model tier, 2026-06-29 → 07-08) anddocs/community-matching-model-plan.md(the parked community release), with the drafting loops of cookbook §12 / §500: §1 why a specialist is safe under the byte gate (the model affects throughput, never correctness); §2 the toolchain-agnostic pipeline —export_pairs→format_finetune→train_lora→eval_lora→serve_local→api_draft/lora_grind/bulk_harvest→ the byte gate;ab_score+workflows/ab_match.js;grinder— every tool verified present; §3 the measured arc as ONE table with dates (the stock 35B floor ~0; v1 39/41 trivial and 0/34 non-trivial on 638 pairs; corpus-v2 with theexternblock: 85% on 6–15 ins; the 0/222 broad run that was two harness bugs, R40; 7/15 on open stubs; 0/15 on shared code → corpus-v3 with the 1,623 shared macro bodies + the "never an empty body" clause: 57.5% held-out, ~352 production banks, fleet +502 fns at $0;bulk_harvest65% with a 0.4 s/fn gate; GLM5.2 on the hard band: 10/18 bodies right, 3/18 banked — the def-side declaration wall caps every drafter; v4 discarded — the 7B is capacity-bound; the 4.8× Haiku A/B); §4 the portable lessons (data > size; self-contained completions; weight by regime; dense > MoE; drop over-length examples; train at the inference context; the gate makes quality a throughput question; exonerate the harness; route by measured difficulty); §5 hardware; §6 a five-step recipe for another project; §7 what is NOT published — the ROM-derived pair dataset (datasets/match_pairs/) and the adapter weights (models/), both gitignored (git check-ignoreconfirmed), a Gen3 licensing decision. Cites the decision-log entry "2026-07-08 · Phase 25 — the local-7B tier is capacity-bound and off the endgame critical path". Wiring:doc_linksdefault set (--strictPASS), SETUP row + the P33 E6 section (R21). Commit: see below. Block E is complete on the still-private repo (E3–E6 in full); E1/E2 and D3's outward actions wait for the flip. - 2026-09-07 (S88, Max) — G1
docs/gen3-handoff.md(pulled ahead of the probe-gated C10 → E1 → E2 → D3-outward → C11 chain: it depends on none of them — a sequencing deviation from the plan's C11 → G1 order, recorded). §1 where Gen2 ends (the contract, what is not C); §2 the owner's next intent (casts → structs, pins off, names); §3 the starter census DERIVED from the tree with its commands (R33/R41): 143 raw address casts, 61,898 distinctD_80xxxxxx, 16,335 distinctfunc_80xxxxxx— the plan's numbers reproduced exactly — and the numbers the plan did not have: 44,243 register-pin declarations (the "pins off" campaign's true size — batch by family), 1,258INCLUDE_ASMtiles (all Sony regions), 1,083 symbol-file entries (1,081 curated), 1,232 struct definitions inengine_types.h(drafter-invented variants included), 2,220 dedup groups, 5 verbatim bodies; §4 the one invariant (every edit byte-gated like a match; a shared body changes every member; types are a comprehension lever, not a byte lever — Phase 17's byte-neutral struct recovery); §5 the inherited levers (lift_types,cast_call_sites,canon_sig_reconcile, the declaration ladder,alloc_table+cc1_dumps_tufor pins, the propagation/registry machinery, the Ghidra text path, the atlas, the actor struct / idxtab map / memory map — every one checked to exist); §6 shiftability honestly scoped (position-locked slots, LZSS recompression not byte-stable → a rebuilt disc verifies by booting, the Sony regions relink); §7 the parked Gen3 ideas with their state (asset export survey, recomp, randomizer tooling, the community model's licensing gate, JP/protos, the preset, libs-from-source); §8 governance for a new generation and the three things to do first (measure the shape; a differential harness for "byte-neutral?"; batch by leverage). Wiring:doc_linksdefault (--strictPASS), SETUP row + the P33 G1 section (R21). Commit: see below.
🛑 SESSION CHECKPOINT — A1–A5 ✓, B1–B9/C3 ✓, C1–C9 ✓, D1–D5 ✓, F1–F3 ✓, E3–E6 ✓, G1 ✓ (34 of 41); C10 IN PROGRESS ON DREW'S SIDE; NEXT = THE PROBE-GATED CHAIN (Drew) then C11 then G2 (2026-09-07, written by session 4555f4e4 "S88" at the G1 close; SUPERSEDES the earlier blocks)
0. How to use this block
You are a FRESH SESSION that has read PROJECT_CONTEXT.md, phase-ends/DIGEST.md, PhaseEnd_Phase30/31/32.md and this file,
and nothing else (R64). Replay this block verbatim, state phase / done / NEXT / effort, list the rules from the digest
(R1–R73), then WAIT for Drew. Everything Claude can do before the flip is DONE (34 of 41; G1 was pulled ahead of the
plan's C11 → G1 order because it depends on nothing gated — recorded as a sequencing deviation). NEXT = the probe-gated
chain on Drew's side: tools/public_rewrite/probe_github.sh PASS → C10 the flip → E1 → E2 → D3 outward → the wiki push →
then Claude's C11 (aftercare) → G2 (the PhaseEnd, Tier 1, WAIT for gate 2). If the probe still fails, there is
nothing to draft: ask Drew about the Support ticket and stop. Rebuild the harness task list (40 items, R28) marking A1–A5,
B1–B9/C3, C1–C9, D1–D5, F1–F3, E3–E6, G1 completed and C10 in progress. Every commit cites NEW
hashes only (the history was rewritten; docs/commit-map.tsv maps ordinals → new hashes; the scratch .run/public_rewrite/
holds the old ones and stays until the probe passes). Never git clean -x (CLAUDE.md fail-safe).
1. Where we are
Phase 33 — 100% verification + the public flip + Gen2 exit. Gate 1 approved 2026-09-06 (plan mode, Max). The approved plan
is VERBATIM at the end of this file — its Blocks E–G paragraphs are the specs for what remains. Done (34): A1–A5, B1–B9/C3,
C1–C9 (the rewrite, adopted, force-pushed by Drew, gc'd), D1–D5 (README, LICENSE/NOTICE/THIRD_PARTY, badges/objdiff/frogress,
SETUP public-clean, governing docs + doc_links), F1 (timeline + story), F2 (retrospective), **F3 (S88: the wiki — 12 files
under docs/wiki/ + the 13 how-to chapters under docs/how-to-ai-decomp/, tools/wiki_render.py + tools/wiki_sync.sh,
doc_links DEFAULT_GLOBS; 954362c81e + 0cf971d1f4), **E3 (S88: docs/gcc-2.7.2-map/README.md + tools/gccmap_cites.py
cite_overrides.tsv; all 135 map citations tagged[2.7.2]/[2.8.1 pm]/[repo]— 79/55/1 — verified, controls 6/6;50c1b69e4d), **E4 (S88:tools/xsig/—xsig.pylibrary+CLI, README with the Phase-21 worked example, MIT LICENSE,tests/from a game-free fixture at two link addresses, 8/8; in tools-health + CI; the standalone copy at.run/P33/xsig-repo/with one commit under the noreply identity — Drew createsDruthulu/xsigEMPTY and pushes;9c4d32d651), **E5 (S88: the upstream PR branchreloc-masked-scorerin.run/P33/permuter-upstream/+ its tracked copytools/permuter/upstream/0001-reloc-masked-scorer.patch+docs/permuter-ils.md; 10/10 tests, black clean, mypy at upstream's baseline, the real permuter 4 → 0 in 256 iterations where the default scorer read 3,585 — Drew pushes the branch to his fork and opens the PR + files the issue;bf002f84d2), **E6 (S88:docs/matching-drafter-pipeline.md— the local-model tier's pipeline, its measured arc, the portable lessons, what is NOT published;98e5846662), G1 (S88:docs/gen3-handoff.md— the derived census incl. 44,243 register pins, the invariant, the levers, shiftability scoped, the parked ideas, the governance for Gen3; the commit that carries this block). In progress (Drew, C10): the GitHub Support ticket (text:docs/public-flip-runbook.md§11 — its filing was never confirmed to S88; ask) and the dailytools/public_rewrite/probe_github.shuntil it prints PASS (S88's run: 31 of 33 old hashes still ALIVE = the S87 baseline; no purge yet). Remaining (6): nothing more that Claude can do before the flip; then, gated on the probe PASS: C10 (the flip — Drew), E1 (decomp.me preset — Drew), E2 (Archipelago — Drew), D3's outward actions (decomp.dev registration, frogress slug/key — Drew), the wiki push (Drew: Wiki → "Create the first page" in the GitHub UI, thentools/wiki_sync.sh --push); then C11 (aftercare), G1 (docs/gen3-handoff.md), G2 (the PhaseEnd v2.0.0 + DIGEST +v2.0.0tag; Tier 1; WAIT for gate 2).
2. Facts the remaining tasks depend on (measured S88; verify if in doubt, R14)
- Repository state:
main= the rewritten history (4,031 commits) + the S87 tip commits (C7 → F2) + the S88 commits (214d0dd15bthe probe fix,954362c81eF3 pages + tooling,0cf971d1f4the F3 wiring,50c1b69e4dE3,9c4d32d651E4,bf002f84d2E5,98e5846662E6, the G1 commit = HEAD);origin/main== the F2 commit5e57e88de2— Drew pushed the S87 tip on 2026-09-07 07:23Z; the S88 commits are NOT pushed (a normal fast-forward push; R6). The first-ever GitHub runs of both workflows were GREEN on that push (no-rom1 m 35 s, run 34095194524;progress15 s, run 34095194479) — read the Actions tab again after the next push, fix red, never claim green unseen (P9). The repo is still PRIVATE (gh api repos/Druthulu/BFM-decomp --jq .private→ true). No linked worktrees; repo-local identity = the noreply address;ghauthenticated in WSL as Druthulu (gh auth logoutat C11); thearchiveremote is REMOVED. - ⚠ The object store holds the purged history again (unreachable): 30 packs / 5.97 GiB / 415,712 objects (C9 had left one
80 MB pack / 176,056). Cause (R57, fixed S88): the pre-fix probe's
git fetch origin <old-sha>succeeded for every ALIVE sha and imported each commit's closure. The auto-mode classifier refuses the repair from a Claude shell, so Drew runs:git reflog expire --expire-unreachable=now --all && git gc --prune=now(evidence it is safe: refs = main + origin/{HEAD,main} at one tip; all 21 reflog entries on the new lineage;rev-list --all --count== main; the bundle + the archive repo hold the old history). Expected after: one pack ≈ 80 MB,.git≈ 93 MB,git cat-file -e 296ff5551ba71269972e708031b52e9cf39bc782fails. Until then the fixed probe's self-check prints "the WORKING repo's object store holds 31 of 33 sampled OLD commits" — that is the residue, not a new import (the fixed run leftgit count-objects -vbyte-identical before/after). - Scratch that carries OUTWARD work for Drew (not regenerable from the repo without redoing it — keep until pushed):
.run/P33/xsig-repo/(E4: one commit; push toDruthulu/xsig) and.run/P33/permuter-upstream/(E5: branchreloc-masked-scorer, one commit; push to Drew's fork of decomp-permuter — regenerable from the tracked patch viagit am);.run/P33/permuter-e2e/is the demo dir (regenerable). - Scratch to keep until the probe passes, then delete (C11):
.run/public_rewrite/(dict.json, mailmap, rom_blob_ids, old-to-new.tsv, unchanged_commits.txt, old_tag_tip.txt, the rewritten bare clonerepo.git, the bundlepre-rewrite.bundle556 MB, the trial logs; the probe'sprobe_scratch.gitis created and deleted per run);.run/objdiff/(the objdiff-cli 3.8.1 validator);.run/P33/hindsight.md;.run/wiki/(the render dir; later the wiki clone — regenerable). Disk ≈ 18 GB free of 75 before Drew's gc (+6 GB after it). - Contract state: unchanged since C8 —
docs/verification.md§2 quotes the C8 run (218/218 clean fleet, sdk-dual OK, tools-health OK 0 PAD-TAIL, UNCLAIMED 0, report 100.00/100.0/100.0, stubs 0).make tools-healthat the F3 close (S88, after thewiki_render --selftestline was added):tools-health: OK — sigs fresh; corpus(+resident) + cdecl + binaries + report(lint+dedup) + cookbook-index all green.(EXIT=0) (.run/P33/f3_tools_health.log).tools/doc_links.py --strictPASS: 40 documents, 290 relative links, 0 pending, 0 broken;docs/doc_links_pending.txtis EMPTY — keep it so; any new forward link must be listed there with its creating task (gate 2 requires empty).tools/wiki_render.py --selftest12/12;tools/wiki_sync.shdry run: 25 pages, 264 links rewritten, "not clonable yet … dry run OK" (exit 0).tools/gccmap_cites.py --checkOK /--verify0 disagreements /--controls6/6 (needs bothtools/reference/trees — gitignored;--checkalone is textual and is what CI and tools-health run);tools/xsig/tests/test_xsig.py8/8 (in tools-health + CI);make tools-healthat the E4 close:tools-health: OK — sigs fresh; corpus(+resident) + cdecl + binaries + report(lint+dedup) + cookbook-index all green.(EXIT=0) (.run/P33/e4_tools_health.log); at the E3 close:tools-health: OK — sigs fresh; corpus(+resident) + cdecl + binaries + report(lint+dedup) + cookbook-index all green.(EXIT=0) (.run/P33/e3_tools_health.log). - Generated, never typed (R51):
docs/progress.json, the README block,docs/badges/*.json,docs/story-timeline.md/.svg,docs/commit-map.tsv,config/ghidra/ROSTER.md,docs/progress*.md,docs/cookbook-index.md— regenerate withmake report BINARY=main,tools/timeline.py,tools/ghidra_roster.py,tools/cookbook_index.py. The rendered wiki (.run/wiki/render/) is derived fromdocs/wiki/+docs/how-to-ai-decomp/bytools/wiki_render.py— edit the sources. - Open decisions for Drew (defaults in force): the local gc (above — please run it); the Support ticket status (S88 could
not confirm it was filed);
docs/history/project_architect_v1.3.0.mdKEPT (THIRD_PARTY links the repo); bare session UUIDs in checkpoint prose NOT scrubbed (375 across history, 68 at HEAD; noclaude.aiURL exists); the stash not mirrored to the archive (the bundle has it). F3's scope is RESOLVED (full, Drew 2026-09-07). - Rule candidates for the PhaseEnd (G2), recorded as they arose: (a) no ROM-derived bytes in ANY published artifact — test
fixtures, JSON, badges, reports included; (b) published numbers are generated, never typed (R51 applied to docs); (c) rehearse
every irreversible repository operation on a scratch copy and prove it pair by pair with POSITIVE assertions (the
strip-list/empty-blob defect; the unchanged-commit case); (d) a linked worktree's HEAD is a ref — audit
git worktree listbefore any gc/purge (12 GB pinned the old lineage); (e) an R20 amendment: the text export + checksums + the archive repo are the backup; nevergit clean -x(CLAUDE.md fail-safe line exists since C3); (f)pkill -fnever with a literal the calling shell's own command line contains (S87 killed its own shell twice); (g) a checker that widens its document set must classify a missing promised page as PENDING, never BROKEN (doc_links); (h) (S88) a probe or guard must never write into the repository it guards — a "read-only" check that fetches, clones or builds does so in a throwaway scratch (R57 exemplar: the purge probe re-imported 5.97 GiB of the purged history on every run).
3. NEXT — in order
- Preflight:
git status --short | grep -v ghidra/(empty) ·git log -1 --format='%h %s'·git fetch --prune origin && git rev-parse origin/main main(differ until Drew pushes; if equal, read the Actions tab:gh run list --repo Druthulu/BFM-decomp --limit 4) ·git count-objects -v(packs: 1 after Drew's gc; 30 = not yet run) ·df -h ~·.venv/bin/python tools/doc_links.py --strict(PASS) · ask Drew: pushed? gc run? ticket filed? latest probe result? (tools/public_rewrite/probe_github.sh— ~1 min, gh-authenticated, safe to run from Claude since S88). - E3, E4 and E5 are DONE (see the log). Outward actions pending on Drew: E4 — create
Druthulu/xsigEMPTY, thengit -C .run/P33/xsig-repo remote add origin https://github.com/Druthulu/xsig.git && git -C .run/P33/xsig-repo push -u origin main; E5 — forksimonlindholm/decomp-permuter,git -C .run/P33/permuter-upstream remote add fork <fork-url> && git -C .run/P33/permuter-upstream push -u fork reloc-masked-scorer, open the PR (the commit message is the description) and file the issue fromdocs/permuter-ils.md§3. E6 is DONE too (docs/matching-drafter-pipeline.md). The old E6 spec, for the record:docs/matching-drafter-pipeline.mdfromdocs/community-matching-model-plan.md,docs/gen2-mips-matching-model.md, cookbook §12/§500,docs/wave-playbook.md; the ROM-derived pair dataset is NOT published. One commit per task after this file is updated; log; refresh this block. - After the probe PASSES (Drew): C10 the flip (Settings → Change visibility → Public, only with D/E/F landed) → E1
(
docs/decompme-preset.md; Drew creates the preset in the browser: platform ps1,gcc2.7.2-psx, flags-O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker -Wa,--aspsx-version=2.56,--expand-div; prove onfunc_80018F20first; the manual scratch search closes SETUP ledger row 14) → E2 (docs/outreach/archipelago.md; Drew sends; on reply a G5 row) → D3 outward (decomp.devmanage/newwith theSLUS_007.26_reportartifact; frogress slugbfm+ key; thentools/ frogress_upload.py --push) → the wiki push (Drew: create the first page in the GitHub UI, thentools/wiki_sync.sh --push; verify the pages render and the sidebar links resolve) → C11 (other clones:git fetch origin && git reset --hard origin/main && git reflog expire --expire=now --all && git gc --prune=now, nevergit pull;gh auth logout; delete.run/public_rewrite/+.run/objdiff/; prune.run/; DIGEST §0/§1 + decision-log entries) → G1docs/gen3-handoff.md(Drew's next intent: readability/shiftability prep; the starter census 143 raw*)0x80xxxxxxcasts / 61,898D_80xxxxxx/ 16,335func_80xxxxxx/ 1,092 named; the byte-gate invariant; the existing levers) → G2 (Tier 1: prompt for Max, present the milestone evidence, WAIT for gate 2; thenPhaseEnd_Phase33.mdv2.0.0 with the rule candidates (a)–(h),CURRENT_PHASE.md→phase-ends/logs/Phase33.md, DIGEST §0/§2/§3 appended, the annotatedv2.0.0tag; Drew pushesmain --tags).
4. Files S88 touched (8 commits after the F2 tip)
Tools (new): tools/wiki_render.py, tools/wiki_sync.sh, tools/gccmap_cites.py, tools/xsig/ (xsig.py, README, LICENSE,
tests/: fixture.c, make_fixtures.sh, test_xsig.py, fixture_a.txt, fixture_b.txt, fixture_a.s),
tools/permuter/upstream/0001-reloc-masked-scorer.patch; changed: tools/public_rewrite/probe_github.sh (scratch-repo fetch +
self-check), tools/doc_links.py (DEFAULT_GLOBS + the map README), Makefile (wiki_render --selftest + gccmap_cites --check + the xsig tests in
tools-health), .github/workflows/no-rom.yml (the gccmap_cites + xsig steps). Docs (new): docs/wiki/*.md (12),
docs/how-to-ai-decomp/*.md (13), docs/gcc-2.7.2-map/README.md + cite_overrides.tsv, docs/permuter-ils.md, docs/matching-drafter-pipeline.md, docs/gen3-handoff.md; changed: the five map files (135
cites tagged in place), docs/SETUP.md (the probe clause in the public_rewrite row;
2 wiki rows; the P33 F3 section), docs/public-flip-runbook.md (§11: the R57 probe paragraph; the wiki push step),
docs/doc_links_pending.txt (10 entries mid-task → EMPTY), phase-ends/CURRENT_PHASE.md (F3 ticked; the S88 preflight + F3 log
entries; this block). Evidence: .run/P33/f3_tools_health.log, .run/P33/e3_tools_health.log, .run/P33/e4_tools_health.log, .run/wiki/render/
(regenerable), .run/P33/xsig-repo/ (the standalone copy, one commit), .run/P33/permuter-upstream/ (the upstream clone +
branch + dev venv), .run/P33/permuter-e2e/ (the demo dir). The plan file:
~/.claude/plans/max-effort-set-plan-twinkling-moonbeam.md (copied below).
Approved plan (gate 1, 2026-09-06) — VERBATIM copy of ~/.claude/plans/max-effort-set-plan-twinkling-moonbeam.md
Phase 33 — 100% Verification + the Public Flip + Gen2 Exit (plan, gate 1)
Planning effort: Max (confirmed set). Governing inputs:
docs/roadmap-to-100.md§3 P33 (lines 199–231),docs/gen2-roadmap.mdPhase 14 (lines 150–166, the two-repo model — SUPERSEDED by the decisions below),phase-ends/PhaseEnd_Phase32.md"Roadmap delta", and this session's history audit + three exploration reports.
Context
Phase 32 emptied the frontier: 218 binaries rebuild byte-identical from C from a clean tree (218/218, exits 0/0/0), fleet 100.00 / 100.0 / 100.0, main 2,091 / 2,091 (789 REAL C · 1,256 LINKED Sony PsyQ objects · 3 verbatim asm; 5 verbatim fleet-wide). What remains is not matching: it is proving the contract once more as one recorded run, making the repository publishable, publishing it with its full history, releasing the reusable tooling, writing the story / retrospective / how-to, and declaring Gen2 EXIT at v2.0.0.
The history audit (this session, every blob on every ref — main = 4,011 commits; --all = 4,282 because the
S76-pre-scrub-backup tag points at a PARALLEL 126-commit lineage left by the Phase-31 S76 trailer scrub, plus a
refs/original/ filter-branch leftover and 3 stashes; the object store holds 4,401 commit objects; 112,253 unique
blobs, ≈16.8 GB content, .git 925 MB): ROM-derived or proprietary content in history = extracted/retail/SLUS_007.26 (the EXE, 413 KB,
1 commit), dumps/*.bin (28 × 2 MB RAM images, 1 commit), ghidra/ (the Ghidra project — verified to embed the EXE's
bytes under Ghidra's page XOR mask; 174 MB across 15 commits; 6 programs tracked), tools/psyq/ (Sony PsyQ SDK,
40 MB, 5 commits), session archive/ (271 MB of session transcripts, 1 commit; ~260k lines of game disassembly inside).
Never committed: the disc, asm/, assets/, expected/, build/. No secrets in tree, history diffs or transcripts.
Tracked .run/, docs/, phase-ends/ carry no original-game disassembly blocks (0 objdump-style lines; 6 illustrative
lines in the cookbook). 700 old commit hashes are cited in docs/ + phase-ends/ (1,167 backticked citations at HEAD).
Author identities: 50529377+Druthulu@users.noreply.github.com ×3,816, 50529377+Druthulu@users.noreply.github.com ×162, noreply ×33. One tag
S76-pre-scrub-backup. git filter-repo is not installed.
Owner decisions (Drew, 2026-09-06 — binding for this plan)
-
Scope = everything in roadmap v2 §3 P33 (all six items), not a trimmed subset.
-
Flip IN PLACE with the full history:
Druthulu/BFM-decompis force-pushed with a rewritten history (every commit, date, message and order preserved; hashes change; one all-purged commitcommit:1712may drop as empty). No new repo. Before that, the current unrewritten history is pushed by Drew to a new private archive repo. -
Purge from all history: the EXE,
dumps/*.bin,ghidra/,tools/psyq/,session archive/— plus the two additions confirmed in decision 11 (tools/ghidra-ext/*.zip: 25.6 MB, one carries no license text, both re-downloadable with recorded sha256;tools/brave-CUE/brave.exe: a 7.6 KB compiled GPL tool binary). -
In-history hash scrub: every old commit hash in every historical blob and commit message is replaced by an inert token; a token→new-hash map is committed; a tip commit resolves tokens at HEAD to the new hashes so current docs stay navigable. The visibility flip is gated on old hashes no longer resolving on GitHub (Support purge / GC).
-
What stays public: everything else, explicitly including
phase-ends/(PhaseEnds ANDlogs/),CLAUDE.md,PROJECT_CONTEXT.md,docs/decision-log.md,docs/accelerators.md, the campaign tooling (no keys in tree). The README links https://github.com/Druthulu/ProjectArchitect as the guidance system used, and carries a Special thanks section (sotn-decomp style) crediting every tool/project we built on. -
License split:
tools/+docs/under AGPL-3.0 (tools/brave-CUE/keeps GPL-3.0; submodules keep theirs);src/ships a NOTICE — a reimplementation of copyrighted game code provided for study/preservation, no license asserted (sm64/oot convention). -
Rules R65–R73 ratified (PhaseEnd_Phase32 candidates (i)–(ix)) — recorded in
phase-ends/DIGEST.md§3 as the first governance commit of the phase. -
A GitHub wiki shaped to this project (sotn-decomp precedent): Home · Build from your own disc · Toolchain setup · Repository layout · The matching workflow (draft → gate → bank) · The dedup engine · Overlays & modules (the 218 binaries, the disc map) · Ghidra rebuild from text · Verification & progress · Contributing / no-ROM policy · the "how to AI-decomp" chapters. Authored in-repo under
docs/wiki/(versioned, CI-linted, covered by the rewrite) and pushed toBFM-decomp.wiki.gitby a sync script (Drew pushes, R6). -
Author identity: both Gmail identities are mapped to
Drew T <50529377+Druthulu@users.noreply.github.com>in the same rewrite pass (--mailmap); names and dates untouched. -
Archive repo:
Druthulu/BFM-decomp-archive(private) receives the current unrewritten history (all refs + the tag) before the rewrite. Drew creates it empty and pushes (R6). -
Purge set is final: the five confirmed path sets PLUS
tools/ghidra-ext/*.zipandtools/brave-CUE/brave.exe; theS76-pre-scrub-backuptag is dropped from the public history (the archive keeps it).
Task blocks (effort per task: Max = design / irreversible / synthesis; xHigh = mechanical execution)
Defaults set by this plan from the rewrite design's measurements (change them at approval if you disagree):
- The EXE is purged at BOTH historical paths (
extracted/SLUS_007.26, thenextracted/retail/SLUS_007.26) and by blob id, so no renamed copy survives. - All 60 remaining
Claude-Session:trailer lines in main's commit messages (the S76 scrub missed them) are stripped in the same pass; the Gmail addresses are also literal-replaced inside the 3 tracked files that mention them. refs/stash(3 Phase-26 WIP entries) and therefs/original/leftover are dropped locally after the archive push.- The archive repo is created EMPTY (never a fork or import — those share GitHub's object store).
- R20's new home after the flip: the Ghidra text export (
config/ghidra/*.jsonl, B5) becomes the tracked backup of the RE work andtools/ghidra_rebuild.sh --proofits check; the archive repo is the one-time snapshot of the binary DB, dumps, PsyQ and transcripts;CLAUDE.mdgains "nevergit clean -x" (the purged paths become ignored, sogit clean -fdxwould delete the RE database). Proposed as an R20 amendment at PhaseEnd_Phase33.
Block A — Tool fixes the contract run depends on, then THE recorded verification run
- A1 Governance commit (xHigh):
phase-ends/DIGEST.md§3 gains R65–R73 in full text (from PhaseEnd_Phase32 "Rules Added");phase-ends/CURRENT_PHASE.mdcreated with the approved plan + the harness task list (R28). - A2 Reporting-instrument fixes (xHigh; each with a known-true control, R39):
tools/progress.py:1032— the fleet digest's "0 phantom, 0 truncated, 1 explained pad-tail" is a HARDCODED f-string literal; derive it fromcorpus.audit('main')at render time (print "(run make sig-main-oracle)" when the oracle sig is absent — never a stale literal).tools/backlog.py prune— an address inside a LINKED range counts as closed (derive the ranges fromprogress._main_linked_ranges(), R33) so the legacyfunc_80062144row (insideapicard5) retires honestly; control: a synthetic near-miss at a game-code address must survive. R32 hole:tools/progress.py:944-947silently drops main (0/0, "217 binaries") when the Ghidra-derived.run/sig.SLUS_007.26.jsonlis absent — a public clone has no Ghidra DB. Fix:make sig-mainbecomes ELF-seeded (nmT symbols in main's text range minus the LINKED ranges — thesig-residentrecipe, Makefile:360-374;tools/sig_image.pywrites the identical schema) andprogress.pyreadssig.SLUS_007.26.jsonlelsesig.main.jsonlelse exits non-zero; control: hide the Ghidra sig,make sig-main && make report BINARY=mainmust print41534 / 41534and 218 binaries. - A3
NO_SDKknob +make sdk-dual(Max — touches the link recipe; a wrong knob is a false green):NO_SDK=1skips the 11 guardedpsyq_integrate.pycalls AND the-Texternals fragments (Makefile:861-920);sdk-dualrefuses to run unless every SDK ELF dir exists (R32 — never run one leg twice and call it a dual), runsextract BINARY=mainbetween legs (psyq_integrate rewrites the.ldin place), asserts WITH map containsbuild/psyq/libcd/and NO-SDK map containsbuild/src/libcd1.oand nobuild/psyq/, both legs ==config/check.us.sha. Added totools-healthaftersig-main-oracle,[skip]with a message when no SDK dirs exist (a public clone's every check IS the WITHOUT leg). This turns the once-regressed hand-only fallback (config/symbols.us.txt:248,docs/accelerators.md:654-656) into a first-class assertion. - A4
tools/family_hseq.pyregen (Low): clears the[warn] .run/family_hseq.json is missing 6 onboarded overlay(s)line in the P32 tools-health log. - A5 THE RECORDED RUN (Low to run, Max to read honestly; runs on the committed final tree AFTER Block B, since R22
means the verified tree is the published tree):
tools/verify_contract.sh→.run/P33/verify/NN_<step>.log(each endingEXIT=<rc>+ timestamp, the P32r22_check.logshape; aborts on the first non-zero EXIT, R53) +SUMMARY.md: 00git rev-parse HEAD+ porcelain (only R23 ghidra churn) · 01make check-env· 02 family_hseq · 03make clean && make extract-all JOBS=16 && make check-all JOBS=16(expect217 extracted, 0 failed (+ main)andcheck-all: 218 passed, 0 failed of 218) · 04make sdk-dual(+ both.mapfiles kept) · 05make tools-health(must containsig-main-oracle: … 0 PHANTOM, 0 TRUNCATED, 0 PAD-TAIL, corpus(+resident) green, zero[warn]) · 06make audit-frontier· 07make audit-disc(needs the 4-track disc; residue 0) · 08make report(three 100 lines,INCLUDE_ASM stubs : 0, backlogOpen near-misses: 0) · 09 SUMMARY..gitignoreallowlists.run/P33/verify/(*.log *.map *.md *.txt) exactly like.run/P32/t4e/. The evidence is cited bydocs/verification.md(a public "verify it yourself" page: the commands + expected last lines + this run's SUMMARY table) and by the PhaseEnd.
Block B — The public build path and the regenerability of everything that leaves git
- B1
make disc-extract(Max — a build-input step; wrong = a green build on the wrong bytes): promotestools/bfm_extract/extract.pyinto the Makefile. Facts that shape it:extract.py:379-381OVERWRITES the manifest on every run, so the wiring must COMPARE, never write; the committedextracted/retail/manifest.jsonl(1,801 rows) includes the 3.DAaudio files from Tracks 2–4, so the oracle needs the 4-track redump BIN/CUE (a Track-1-only dump gives 1,798 rows). New flags:--expect-manifest PATH(compute, compare to the committedmanifest.sha1, write nothing on match, print the first 20 differing rows and exit 1 on mismatch) and--allow-missing-audio(Track 2 absent → compare minus the 3.DArows, printPARTIAL: 1,798/1,798 code+data verified; 3 .DA unverified; without the flag a partial dump FAILS, R43). Target: idempotent probe viaextract.py --verify(≈5 s) → else requiredisks/…(Track 1).bin(exit 2 with the staging instruction) →extract_exe.py --verify-disc(redump SHA1b44f0f0a…/CRC32c238191b; refuses a non-canonical dump) →extract.py --expect-manifest→--verify.extract-allruns it once serially first;extractruns it only when$(EXE)is missing;check-envstep 6 → WARN "run make disc-extract" when the EXE is absent, plussha1(manifest.jsonl) == manifest.sha1;helptext refreshed..gitignore: delete the!/extracted/retail/SLUS_007.26re-include; new/dumps/*.bin,/ghidra/,/tools/psyq/,/session archive/,/tools/ghidra-ext/*.zip; header rewritten (H1 in force).make cleanstops deleting the four splat preset headers, which become TRACKED (include/include_asm.h,macro.inc,labels.inc,gte_macros.incare generic splat presets, identical for every binary — the ROM-free input CI needs). Verification:mv extracted .run/extracted.off && make disc-extract→ exit 0,cmpof the manifests, EXE sha1143dbb89…; second run prints "up to date"; negative controls:DISC_DIR=/nonexistent→ exit 2; a truncated Track-1 copy →--verify-discFAIL withgit diff --exit-code extracted/retail/manifest.*clean; the run time recorded in SETUP §4.4. - B2 Absolute includes (xHigh): 34 lines in 19
src/ov_*/…_jr_*.cfiles across 15 binaries (30engine_core.h, 4engine_types.h) →sed 's|#include "/home/musashi/bfm-decomp/src/shared/|#include "../shared/|'(the tree's own form, 3,978 uses; byte-neutral by construction — same header text — but still re-gated:make extract BINARY=<b> && make check BINARY=<b>for each of the 15; the fleet R22 in A5 is the recorded proof).tools/audit_text_sources.py(already in tools-health) gains a portability class: absolute, angle-bracket, or outside-repo#includes are offenders; controls: 34 offenders before the sed, 0 after. - B3
tools/bootstrap.sh/make bootstrap(xHigh): idempotent — apt presence check (prints the install line, no sudo) → venv +requirements-python.txt→git submodule update --init→sha256sum --check tools/bin/CHECKSUMS.sha256+ untar each cc1 tarball into its own subdir →make check-env.check-envgains: submodule state, the 4 tracked headers, disc/extracted state. Verification = the "stranger with their own dump" criterion:git clone --no-localinto.run/P33/fresh/→tools/bootstrap.sh(single WARN: run disc-extract) → disc symlinked in →make disc-extract && make extract-all && make check-all→ 218/218. - B4
tools/fetch_psyq.sh(xHigh; OPTIONAL enrichment, never required for byte-identity): what each LINKED region needs —.run/obj40/{libcd,libetc,libgpu,libmcrd,libc2,libgte}+libgs_used+snd_usedfrom PsyQ 4.0 LIBs (tools/psyq/lib40/, sourced from the DTL-S2002 disc — no public URL is recorded anywhere → the script REFUSES and accepts--from DIR, naming the redump title);.run/obj42/{apicard_used,libapi42,libpad421}from the RTL 4.2 7z (archive.org, sha256 recorded) viapsyq_lib_split.py;psyq-obj-parserfrom the decomp.me compilers release (sha256 to be ADDED).Psy-Q_46.zip,PSYQ_SDevTC_v4.5.zip,conv47/, the 4.0 Win32 tools feed NO region. Beforetools/psyq/leaves git: add sha256 rows for the 20lib40/*.LIBand thepsyq-obj-parsertarball totools/psyq/CHECKSUMS.sha256(kept tracked) so a user-supplied copy is verifiable. The script ends withmake sdk-dual. - B5 Ghidra regenerability (Max; runs with the MCP STOPPED — the SessionStart hook's server holds the lock; the
hook restarts it next session):
tools/ghidra_scripts/ExportAnnotations.java <out.jsonl> [symbols…](read-only; byte-stable JSON-Lines, fixed key order, sorted sections,0x%08xaddresses; rowsprogram,block,archive,type(only LOCAL-archive user types — the 2,599 psyq400 types re-import viaImportPsyqGdt),func(USER_DEFINED signature/params/locals/custom storage),data,comment×5 kinds,bookmark(notAnalysis),equate,label(USER_DEFINED, not already in the symbols files));ImportAnnotations.java <in.jsonl>(idempotent compare-before- write, two-pass types for self-references viaDataTypeParser, printsBFMANN … failed=0, refuses unknown row kinds / schema drift, R43);tools/ghidra_rebuild.sh <program> [--proof|--into-live]composing: stop MCP → import (ghidra_import.shfor PS-X EXEs incl. the two protos fromextracted/proto/,ghidra_import_raw.sh <blob> <vram>for raw programs with blob/vram DERIVED fromconfig/overlays.mk/modules.mk/the Makefile's resident block, R33) →DefineFunctionsfrom the built ELF'snmT symbols →ApplySymbolsin the yaml'ssymbol_addrs_pathorder →ImportAnnotations→--proof: re-export andcmpagainstconfig/ghidra/<program>.jsonl(exit 0 = regenerable) + an R9 read-only re-open. Analysis-noise trap: export a BASELINE from the rebuilt program BEFORE importing; every row present in the baseline is analysis-origin (PsyQ-signature plate comments,Analysisbookmarks) → tighten the filter untilcmppasses; keep.run/P33/ghidra/<prog>.baseline.diff; negative control: mutate one field in a copy → the proof must FAIL. Roster: export ALL 129 on-disk programs once, keep inconfig/ghidra/only those with hand-authored rows (config/ghidra/ROSTER.mdgenerated), proof REQUIRED onSLUS_007.26+resident, recommended onov_SC01_077; protos exported (cheap RE preservation) but marked "rebuild needs the prototype disc". Same change:ExportSymbols.javaR15 fix (output-path arg, refuses to overwrite, neverconfig/), and the path hardcodes: Makefile:165GHIDRA_PROJ := $(or $(BFM_GHIDRA_PROJ),$(CURDIR)/ghidra), the sixtools/ghidra_*.shPROJ_DIR,DefineFunctions.java:21,ImportPsyqGdt.java:20(→Application.getInstallationDirectory()),ghidra_mcp_verify.sh:19PROG arg;.claude/settings.jsonhooks →$CLAUDE_PROJECT_DIR-relative and the start script a silent no-op when Ghidra is not installed (a contributor's session must not try to launch Ghidra). - B6 Dumps (Low; wording xHigh):
sha1sum dumps/ram_*.bin > dumps/CHECKSUMS.sha1(28 rows, committed BEFORE the.binleave git);dumps/INDEX.mdrewritten (local-only from P33; hashes; re-capture recipetools/ram_probe.py snapshot <name>→.run/ram/, with the honest caveat that a re-capture is a new state snapshot, never byte-identical to the original);docs/memory-map.mdSource-index row for the corpus. Verify:git ls-files dumps= INDEX + CHECKSUMS;sha1sum --check28/28. - B7 No-ROM CI (xHigh):
.github/workflows/no-rom.yml, pinned actions,ubuntu-24.04, python 3.12, submodules. Jobaudits(~1 min, ROM-free by measurement:audit_text_sources.py,verbatim_check.py,cookbook_index.py --check,work_evidence.py --selftest,bfm_extract/test_lzss.py,lint_symbol_refs.py, and the first-push gate reused as CI —tools/audit_public.py: no tracked file's SHA1 appears inextracted/retail/manifest.jsonl(the manifest IS the list of ROM-derived hashes, R33), none equals the EXE SHA1, none > 50 MB, none under the purged paths). Jobcompile-only: cc1 from the tracked tarballs + maspsx +ason every eligible TU via newtools/compile_only.pywhose skip list is DERIVED (the 70 LINKED TUs fromprogress._main_linked_segs_from_makefile()- the 47 TUs with line-start
INCLUDE_ASM(/INCLUDE_RODATA(; -O0 TUs fromcorpus.o0_sources()), coverage linecompiled N of M; skipped …(R32); PR scopemain resident ov_SC01_077 md_MAIN_013(sized to ≤10 min — time one TU first, each includes the 8.8 MB engine_core.h),--allonworkflow_dispatch+ weekly;cdecl.py --audit --gccrides along. Excluded on purpose (need the ROM): audit-binaries, split_indicator, audit-digest, progress --audit, dedup check. The workflow header and README state that byte-identity is verified locally with the disc.
- the 47 TUs with line-start
- B8
docs/SETUP.md(R21) +docs/verification.md(xHigh): §4.4 disc-extract, §4.6 bootstrap, §4.8 fetch_psyq, §6.3 the new targets, tooling rows for every new tool;docs/verification.mdas in A5. - B9 The
git rm --cachedcommit (Max, P5c-class): after B4/B5/B6 are committed —git rm --cached extracted/retail/SLUS_007.26 dumps/*.bin tools/brave-CUE/brave.exe tools/ghidra-ext/*.zip && git rm -r --cached ghidra tools/psyq "session archive"; files stay on disk, now ignored by B1's.gitignore. A5's R22 logs remain valid (a--cachedremoval changes no tracked-content bytes — stated indocs/verification.md).
Ordering inside A+B: A1 → A2/A3/A4 → B1 → B2 → B3 → B4 → B5 (parallel with B1–B4 once the MCP is stopped) → B6 → B7 → B8 → A5 (the recorded run on the final tree) → B9 → Block C.
Block C — The history rewrite, the push, the purge gate, the flip
- C1 Install + tooling (design Max, execution xHigh):
.venv/bin/pip install git-filter-repo==2.47.0(SETUP row, R21). Newtools/public_rewrite/:purge_set.txt(the single source of truth, filter-repo--paths-from-filesyntax),gate_scan.py(the first-push gate — scans the working tree AND every blob reachable from the given refs for purge-path prefixes, content SHA1s in the known-ROM set (EXE, redump Track-1, everysha1inextracted/retail/manifest.jsonl, everyconfig/check.*.sha), byte signatures (PS-X EXEat 0; the 2,097,152-byte RAM image with the resident's first words at 0xCEDF8; the EXE entry code; PsyQLIB\x01/LNK\x02magics), any blob50 MiB;
--expect-fail EXPECTED.txt= the R39 NEGATIVE CONTROL, exit 0 only when the scan fails naming exactly the expected offender set; also emitsrom_blob_ids.txt),hash_dict.py(all 4,401 commit hashes → ordinal bygit rev-list --reverse main; off-main commits → the ordinal of their (tree, author-timestamp, subject) twin — measured 126/126 for the tag lineage — elseorphan-NNN; prefixes 7..40 = 149,634 entries; asserts 0 ambiguous prefixes and 0 collisions with the known content-hash set),scrub.py(the ONE scrub function:\b[0-9a-f]{7,40}\b— validated at HEAD: 711 resolving citations, 0 word-embedded false hits,func_800D128C/0x800d128cdo not match — replaced via dict lookup by the tokencommit:NNNN(non-hex inside the first 7 chars so it can never re-match; no Markdown/GitHub side effects;git grep -c 'commit:[0-9]'is 0 today); NUL-sniff binary skip; idempotent),run_filter.py(composes the filter-repo call, refuses to run outside a bare repo under.run/public_rewrite/, logs versions + wall time;--samplefirst, R37: must reproduce ≈1,202 hits / 93 files / ≈3 s over HEAD's 395 MB and the replaced-token set must equal the 711 citations thatgit cat-file -eresolves),build_commit_map.py(→ publicdocs/commit-map.tsv:ordinal new_hash author_date committer_date subject, no old hash anywhere — asserted by running scrub over its own output; private.run/public_rewrite/old-to-new.tsvfor the probe only),resolve_tokens.py(at HEAD of the adopted checkout:commit:NNNN→ the unique 9-char new abbreviation, asserted bygit cat-file --batch-check;--checkasserts zero resolvable tokens remain and lists the orphan residue),verify_rewrite.py(the pairwise proof, C5),absent_scan.py(every blob incl. binaries + every message → 0 dictionary prefixes, 0Claude-Session:, 0 Gmail strings),probe_github.sh(Drew's post-purge probe). All scratch under.run/public_rewrite/(never committed; it holds old hashes). Measured budget: regex+lookup 125 MB/s → ≈2.2 CPU-min over 16.8 GB; the filter-repo stream dominates (10–30 min). Disk: 13 GB free → bare--no-localclone (≈0.6 GB) + bundle (≈0.6 GB), no working-tree copy. - C2 Negative control + dictionary + sample (xHigh):
gate_scan.py --refs --all --worktree --expect-failon the CURRENT repo must FAIL naming exactly: the EXE at both paths, 28 dumps, allghidra/**(41 blobs), alltools/psyq/**(190 files at HEAD), the 3session archive/*parts (the only >50 MiB blobs), the 2 ghidra-ext zips,brave.exe; signature hits = EXE blob(s) + 28 RAM images. Thenhash_dict.py(expected: 4,401 commits, 149,634 prefixes, 126 twins, 0 ambiguous, 0 collisions) andrun_filter.py --sample. - C3 Preparatory commit in
~/bfm-decomp(content Max, mechanics xHigh) = Block B's B9 plus:git mv tools/psyq/CHECKSUMS.sha256 tools/psyq_CHECKSUMS.sha256BEFORE the rm (B4's added rows go there; SETUP's three references follow); record the ghidra-ext zip sha256s in SETUP §2.3/§2.4 (GhidrAssistMCP_2.8.0.zip983e2add…,ghidra_psx_ldr_2026.06.04.zipdc57cf1a…);docs/public-flip-runbook.md+tools/public_rewrite/; a forward-only decision-log entry (R31). Checks:git ls-files -- <purge paths>empty; every purge pathgit check-ignore -q; the files still on disk;make check-envexit 0. Commit immediately (R42). - C4 Backups, then the rewrite (xHigh; Drew pushes):
git bundle create .run/public_rewrite/pre-rewrite.bundle --all --reflog+git bundle verify. Drew: createDruthulu/BFM-decomp-archiveEMPTY + private,git remote add archive …,git push --mirror archive; check:git for-each-refvsgit ls-remote archiveidentical. Thengit clone --no-local --bare ~/bfm-decomp .run/public_rewrite/repo.git; delete the tag in the clone; assert the clone holds onlyrefs/heads/mainat the prep commit, one pack, zero loose objects;run_filter.py=git filter-repo --invert-paths --paths-from-file purge_set.txt --strip-blobs-with-ids rom_blob_ids.txt --blob-callback … --message-callback … --prune-empty auto --replace-refs delete-no-add --mailmap …(never--prune-empty always: main has one pre-existing empty commit133c1d45athat must survive;delete-no-addso norefs/replace/<old>names are minted). Checks: exit 0; commit-map has 4,012 rows (4,011 + prep) with exactly one mapped to zeros (the pruned archive upload); norefs/replace; one pack; size recorded (expect 150–250 MB). - C5 Verification suite on the rewritten clone (xHigh; every check an exit code):
verify_rewrite.py— for every (old,new) pair: names/emails (post-mailmap) and BOTH timestamps equal;new.message == scrub(old.message); new parents = map(old parents) with the pruned commit spliced out;git diff-tree -r --no-renames old new: everyDis a purge path or a ROM blob id, everyMsatisfieshash-object(scrub(old_blob)) == new_blob, anyAfails; prints 4,011 pairs checked.gate_scan.py --refs --all→ PASS (the same tool that failed in C2).absent_scan.py→ 0/0/0.git rev-list --count main= 4,011 and the%at %ctlists match with the pruned commit removed. The pre-existing empty commit's twin exists. - C6 Adoption in
~/bfm-decomp(xHigh):git fetch .run/public_rewrite/repo.git +refs/heads/main:refs/heads/ main-rewritten;git diff --stat main main-rewrittenlists ONLY text files (≈93) and no purge path;git reset --hard main-rewrittenonmain(the purged paths are untracked+ignored since C3, so they stay on disk);git status --porcelainempty;git ls-files | wc -lequals the clone's tree count; deletemain-rewritten,refs/original/…, the tag, the stashes. NO gc yet (origin/main still pins the old lineage until Drew pushes). - C7 Commit map + tip resolution (design Max, run xHigh):
git config user.email→ the noreply identity;build_commit_map.py→docs/commit-map.tsv;resolve_tokens.py; checks:git grep -c 'commit:[0-9]' HEAD= 0; the orphan residue (≥1:tools/verify_worktree.py:214cites a dropped TEMP commit) listed in the commit message; every inserted 9-char hash resolves uniquely;absent_scan.py --tree HEAD= 0. Commitdocs(phase-33): commit-map + citations resolved to the rewritten history. - C8 R22 clean rebuild on the adopted tree (xHigh, ~1 h):
make clean && make disc-extract && make extract-all && make check-all→ 218/218, exit codes 0 (R53);make reportstill 100.00 / 100.0 / 100.0. (Content-preserving rewrite ⇒ A5's evidence still describes this tree; this run proves it.) - C9 Final gate + force-push + gc (xHigh; Drew pushes):
gate_scan.py --refs main --worktreePASS. Drew:git push --force origin main;git push origin :refs/tags/S76-pre-scrub-backupif the tag is on origin (git ls-remote --tags origin);git fetch --prune origin;origin/main == main. Then locallygit reflog expire --expire=now --all && git gc --prune=now; checks:git rev-list --all --count= 4,012 (old main 4,011 + the prep commit − the pruned archive upload + the TIP commit; the script prints the expected number from the map),gate_scan.py --refs --allPASS,absent_scan.pyPASS,.gitsize recorded (expect < 400 MB). - C10 Support purge → probe gate → the flip (Drew; decision Max): open the GitHub Support ticket (text in the
runbook: private repo, no forks, force-pushed to remove proprietary game binaries and personal session links; please
GC unreachable objects and purge cached views; the repo goes public only after the old SHAs return 404). Probe:
tools/public_rewrite/probe_github.sh— for 30 sampled full old hashes + the pruned commit + the old tag tip:gh api repos/Druthulu/BFM-decomp/commits/<sha>must 404 andgit fetch origin <sha>must fail; positive control: the currentmainsha must succeed; after the flip also probe 7-char prefixes unauthenticated atgithub.com/Druthulu/BFM-decomp/commit/<7>. While any probe returns 200: wait and re-run daily; fallback if Support stalls = delete + recreate under the same name and push the same rewritten history (nothing else exists to lose). Flip = Settings → Change visibility → Public, only after the probe exits 0. During the wait: Blocks D, E, F land on the still-private repo. - C11 Aftercare (xHigh): every other clone of the old history (the Windows tree, other machines) must
git fetch origin && git reset --hard origin/main && git reflog expire --expire=now --all && git gc --prune=nowor re-clone — nevergit pull(an 8,000-commit merge);git remote remove archivefrom the working repo (habit guard);.run/at 38 GB → prune regenerables (the WSL disk is capped at 75 GB, 13 GB free); PhaseEnd/DIGEST entries.
Block D — Publishing surface (after the rewrite, during the GitHub-purge wait, BEFORE the visibility flip)
- D1 README rewrite (Max): what/why · the numbers table WITH denominators, generated (
tools/progress.py --readmewrites a<!-- progress:begin/end -->block fromdocs/progress.json;make audit-digestasserts it) · "what is not our C" (1,256 LINKED Sony objects; 5 PERMANENT verbatim bodies; the 74src/lib*.c/apicard*.cINCLUDE_ASM fallback tiles for the linked regions) · build-from-your-own-disc in N commands (uses Block B'smake extract) · verification evidence (R22 log path, EXE SHA1143dbb89…, redump Track-1 SHA1) · method (ProjectArchitect link, CLAUDE.md, phase-ends, decision-log, accelerators) · story/retrospective/wiki links · license split · contributing / no-ROM policy · Special thanks (sotn-decomp style): sotn-decomp (method precedent), splat/spimdisasm/rabbitizer, maspsx (mkst), asm-differ + decomp-permuter (simonlindholm), m2c (matt-kempster), old-gcc (decompals), Ghidra, ghidra_psx_ldr (lab313ru), GhidrAssistMCP, psyq-obj-parser (decomp.me), PCSX-Redux, brave-CUE (CUE), the xenogears/vagrant-story/tomba decomps (sibling idioms), redump, TCRF, gamehacking.org, the BFM Archipelago world (AegeusEvander), ProjectArchitect, Claude Code. Badges at top (D3). - D2 LICENSE + NOTICE + THIRD_PARTY (Max for wording, xHigh to place): root
LICENSE= AGPL-3.0 verbatim;src/NOTICE.md= reimplementation-for-study wording, no license asserted, © Square 1998 acknowledged, never the word "clean-room" (the source was derived from the binary);THIRD_PARTY.mdrows: brave-CUE GPL-3.0 (source only —brave.exepurged), submodules (maspsx MIT, decomp-permuter MIT, asm-differ Unlicense, m2c GPL-3.0), old-gcc 0.17 (GCC = GPL; download + sha256), GhidrAssistMCP MIT (download), ghidra_psx_ldr (no license detected upstream — download only, never redistributed), Ghidra Apache-2.0, binutils, splat/spimdisasm/rabbitizer (pip), PsyQ SDK (Sony; user- supplied, optional, never distributed;tools/psyq/CHECKSUMS.sha256stays so a user can verify their copy), gcc-2.7.2 source (GPL-2.0;ftp.gnu.org/old-gnu/gcc/gcc-2.7.2.tar.gz, sha2567cd8bce5…), the gitignored reference clones, ProjectArchitect (Drew's;docs/history/project_architect_v1.3.0.md— keep or link: Drew decides at approval). No per-file SPDX headers (sotn precedent); one statement intools/README.md. - D3 Progress publishing (xHigh + one Max pass on the metric→schema map):
tools/progress.py --json→docs/progress.json(fleet fn/instr/distinct + main REAL/LINKED/VERBATIM/empties + per-binary rows, every number with its denominator, R41) +docs/badges/*.jsonin shields.io endpoint format ({"schemaVersion":1,"label","message", "color"}; README usesimg.shields.io/endpoint?url=raw.githubusercontent.com/…/docs/badges/fleet_instr.json) — written bymake report, asserted bymake audit-digest(extended).tools/frogress_upload.py(stdlib,--dry-rundefault,FROGRESS_API_SECRETenv; frogress projects are admin-created — Drew requests slugbfm+ key from the frogress maintainers).tools/objdiff_report.pyconvertsdocs/progress.json→ objdiffreport.json(categoriesgame-codevslinked-sony-objects; metadata says "from a local clean rebuild; CI does not rebuild") +.github/workflows/progress.ymluploading artifactSLUS_007.26_reportfor decomp.dev (Drew registers atdecomp.dev/manage/newonce public; verify the JSON parses with a downloadedobjdiff-clifirst). - D4 SETUP.md public-clean pass (xHigh): header date; §2.3/§2.4 → download-only for the Ghidra extensions (drop the "vendored zip" lines 16-17/861-862); §4.4 generic dump path; §4.7 tarballs; §4.8 + §5.6 PsyQ = user-supplied; §6.5 → the decomp.me preset; the "Backup & private-repo posture" section → the public posture; ledger rows 11/14 closed; R21 rows for every P33 tool.
- D5 Governing-docs consistency pass (xHigh):
CLAUDE.md:61(Ghidra project + PsyQ SDK stay out of git — true again post-flip);.gitignoreheader (H1 in force; drop the!/extracted/retail/SLUS_007.26re-include);dumps/INDEX.md:8;tools/progress.py's pad-tail wording (fix the generator, never the file);docs/roadmap-to-100.md§1 → a dated "status at P33" block (218 / 0 / second oracle closed);docs/gen2-roadmap.md:150-166→ a "SUPERSEDED at P33" banner (delete nothing);phase-ends/DIGEST.md§0/§1/§3 (H1 re-tightened, R1/R20 historical, PROJECT_CONTEXT corrections, R65–R73 full text);docs/backlog.mdlegacy row retired viatools/backlog.py. Verification for D1–D5: newtools/doc_links.py(relative-link/path checker over README, THIRD_PARTY, SETUP, wiki; in tools-health) +make audit-digest+ a line-by-line grep review forgitignored|private|curated public mirror|two-repo.
Block E — Releases (after the rewrite; each is a directory in-repo first, standalone repo only where stated)
- E1 decomp.me preset (xHigh, 0.25 session):
docs/decompme-preset.md— platformps1, compilergcc2.7.2-psx, flags-O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker -Wa,--aspsx-version=2.56,--expand-div(decomp.me's image wrapsaswith maspsx and forwards-Wa,args; its cc1 is old-gcc 0.13 + maspsx86ccd7d8vs our 0.17 +874855c5— prove onfunc_80018F20before saving). Presets are created in-browser by any logged-in user (POST /api/preset/) — Drew creates it, runs the outstanding manual scratch search, and we close SETUP ledger row 14. - E2 Archipelago outreach (xHigh, 0.25):
docs/outreach/archipelago.md— the world isgithub.com/AegeusEvander/Brave-Fencer-Musashi-AP-World(theirclient.pyaddresses are already cited in memory-map §3.4; one live discrepancy to offer: day-of-week0x078ebavs our verified0x80078EAC). Drew sends (GitHub issue or Discord); on reply, a G5 provenance row indocs/memory-map.md. - E3 gcc-2.7.2 codegen map (xHigh, 1):
docs/gcc-2.7.2-map/README.md(cookbook §31's triage table + per-file scope- provenance legend + the gcc-source fetch note + the byte-proof method + the 2.8.1-line-number caveat) and
tools/gccmap_cites.pytagging everyfile.c:NNNcite[2.7.2]or[2.8.1 pm]againsttools/reference/gcc-2.7.2(--checkexits 0 when every cite is tagged). Not a standalone repo now (it cites in-repo proofs).
- provenance legend + the gcc-source fetch note + the byte-proof method + the 2.8.1-line-number caveat) and
- E4 xsig (xHigh, 0.5): move
.run/xdedup/xsig.py→tools/xsig/(library + CLIsign-s | sign-objdump | cross | verify, MITLICENSE, README with the Xenogears/Vagrant Story/Tomba PsyQ-body hits as the worked example,tests/with a fixture built from one 10-line C function compiled at two link addresses — no game bytes, noasm/dependency). Standalone repoDruthulu/xsigprepared under.run/P33/xsig-repo/; Drew creates and pushes. - E5 permuter upstream (Max, 1): upstream's
Scorer.__init__gainedign_branch_targets, objdump_commandafter our pin, sotools/permuter/run_masked.py's rebind breaks on HEAD; the MIPS symbol wildcard is"." in field(scorer.py:66-67) — the root cause of the nonzero floor for dotlessfunc_8…symbols. PR-1: aRelocMaskedScorerbehind a--score-mode reloc-masked/ settings key (objdump-drzwords + reloc records; MIPS-only; bottoms at 0 iff link-identical; decoupled from ourcdecl; fixture tests,mypy,black,./run-tests.sh). PR-2 (or an issue): configurablesymbol_regex. The ILS warm-restart stays ours (docs/permuter-ils.md+ script) and is offered as a USAGE recipe. Drew opens the issue, then the PR from his fork. - E6 drafter write-up (Max, 0.5–1):
docs/matching-drafter-pipeline.mdfromdocs/community-matching-model-plan.md,docs/gen2-mips-matching-model.md, cookbook §12/§500,docs/wave-playbook.md— the pipeline and the measured results; the ROM-derived pair dataset is NOT published.
Block F — Endgame writing (after the rewrite, during the GitHub-purge wait)
- F1 timeline + story (xHigh tool / Max narrative, 1.5):
tools/timeline.py(stdlib; walks every commit touchingdocs/progress.fleet.md— 449 commits over 70 dates since 2026-06-16 — anddocs/progress.md, parses both the June single-metric and the post-07-11 three-metric formats, joins PhaseEnd dates/versions and commits-per-day; keyed by date, never hash; self-check: last row ==docs/progress.json) →docs/story-timeline.md+ a hand-writtendocs/story-timeline.svg(three polylines, phase ticks, the 07-22 denominator step annotated).docs/story.md: the brief and the lost first month (reconstructed fromdocs/history/, P1–P7), Gen1 exit, the fleet + dedup, the pivots (P16/P17/P18/P23), the tooling-integrity audit (P26-A), the family campaign (P29), the atlas + wide-tolerance waves (P31), the last 21 (P32), the flip — each section citing its PhaseEnd and decision-log entries by path. - F2 retrospective (Max, 1.5–2):
tools/mine_hindsight.pypulls every decision-logHindsightfield, the PhaseEnds' "What we believed…" sections and all 32 Deviations tables into.run/P33/hindsight.mdwithfile:lineanchors;docs/retrospective.mdfolds in the existingdocs/hindsight-study.md(294 lines) anddocs/generic-decomp-package.mdrather than redoing them: what we believed at each turn · what failed and why it looked right · what it cost (with denominators) · what we would do sooner · what stayed genuinely hard. R31: cite, never backfill the log. - F3 wiki + how-to-AI-decomp (Max, 2–3):
docs/wiki/= the project wiki pages (decision 8) anddocs/how-to-ai-decomp/=00-README·01-governance(ProjectArchitect: constitution, two gates, PhaseEnds, R-rules, the digest, replayable checkpoints) ·02-byte-gate·03-bootstrap-order·04-oracles-and-instruments·05-cards-lanes-waves·06-knowledge-base·07-compiler-source·08-models-and-budgets·09-economics·10-integration-and-propagation·11-publishing·12-failure-museum; drawn fromdocs/portable-decomp-workflow.md§0–§11,docs/accelerators.md,docs/hindsight-study.md§3,docs/effort-map.md.tools/wiki_sync.shpushesdocs/wiki/toBFM-decomp.wiki.git(Drew runs the push). All pages link-checked bytools/doc_links.py.
Block G — Gen2 EXIT (Max, 0.5)
- G1
docs/gen3-handoff.md: Drew's next intent (readability / shiftability prep: casts → structs, pins off, names) with the starter census (143 raw*)0x80xxxxxxcasts, 61,898 distinctD_80xxxxxx, 16,335 distinctfunc_80xxxxxxinsrc/; 1,092 named symbols), the byte-gate invariant, and the existing levers (tools/lift_types.py,tools/cast_call_sites.py,tools/canon_sig_reconcile.py,docs/struct-core-pivot.md,docs/gen3-parking-lot.md). - G2
phase-ends/PhaseEnd_Phase33.md(PROJECT_CONTEXT format + R25 recap + "what we believed" + Roadmap delta = Gen2 EXIT),CURRENT_PHASE.md→phase-ends/logs/Phase33.md(R19), DIGEST §0/§2/§3 appended, version 1.31.0 → 2.0.0; P33 rule candidates recorded (e.g. "no ROM-derived bytes in any published artifact, test fixtures and JSON included"; "published numbers are generated, never typed"). Annotated tagv2.0.0created locally after Drew's milestone-close commit; Drew pushesmain --tags(R6).
Execution order and why
A1 governance ─► A2/A3/A4 instrument fixes ─► B1 disc-extract ─► B2 includes ─► B3 bootstrap ─► B4 fetch_psyq
─► B5 Ghidra text export + rebuild proof (MCP stopped; may overlap B1–B4) ─► B6 dumps ─► B7 CI ─► B8 SETUP/verification.md
─► A5 THE RECORDED RUN (final tree) ─► B9/C3 prep commit ─► C1/C2 tooling + negative control (can precede C3)
─► C4 archive push (Drew) + rewrite ─► C5 verify ─► C6 adopt ─► C7 map + tip ─► C8 R22 ─► C9 force-push (Drew) + gc
─► [Support purge wait: D1–D5 docs/license/README, F1–F3 story/retrospective/wiki, E3/E4/E5/E6 releases land on the
still-private repo] ─► C10 probe gate → FLIP (Drew) ─► D3 outward (badges/frogress/decomp.dev), E1 preset,
E2 Archipelago (Drew) ─► C11 aftercare ─► G1/G2 PhaseEnd v2.0.0 + tag (Drew pushes)
- The rewrite comes AFTER every build-facing change and the recorded run so that the published tree is the verified tree (R22) and no later commit needs rewriting; everything written afterwards cites new hashes only.
- The purge wait is used for the writing and the releases, so the flip is not delayed by them and the repo goes public with LICENSE, NOTICE, README, story, retrospective and wiki already in place.
- Mid-phase rules check after every 4 completed tasks (P6). One commit per task after
CURRENT_PHASE.mdis updated; banks/artifacts committed immediately (R42). Effort per task as annotated (Max = design/irreversible/synthesis, xHigh = execution); no breadth stretch in this phase warrants Ultracode.
Milestone (gate 2 — what Drew confirms)
.run/P33/verify/SUMMARY.md: every step EXIT=0 (218/218 clean fleet;sdk-dualboth legs; oracles green incl.0 PAD-TAIL; disc residue 0; report 100.00 / 100.0 / 100.0 withINCLUDE_ASM 0, backlog 0) — and C8's re-run on the adopted tree.gh api repos/Druthulu/BFM-decomp --jq .private→false;probe_github.shexit 0 (old hashes 404); rootLICENSE,src/NOTICE.md,THIRD_PARTY.md, the rewritten README with badges + Special thanks + the ProjectArchitect link;.github/workflows/no-rom.ymlgreen on the public repo; the fresh-clone criterion (B3) met with the disc.- Deliverables present:
docs/verification.md,docs/commit-map.tsv,config/ghidra/*.jsonl+ROSTER.md,dumps/CHECKSUMS.sha1,docs/decompme-preset.md,docs/outreach/archipelago.md,docs/gcc-2.7.2-map/README.md,tools/xsig/, the permuter PR branch +docs/permuter-ils.md,docs/matching-drafter-pipeline.md,docs/story.md+ timeline,docs/retrospective.md,docs/wiki/+docs/how-to-ai-decomp/,docs/gen3-handoff.md; outward actions (preset saved, Archipelago message sent, frogress/decomp.dev registered, wiki pushed, PR opened) done by Drew or explicitly recorded as pending in the PhaseEnd (P9 — never claimed). PhaseEnd_Phase33.mdv2.0.0 + DIGEST append +v2.0.0tag pushed.
Verification summary (machine-checkable, by block)
A: tools/verify_contract.sh logs + SUMMARY; the three instrument fixes each with a positive and a negative control.
B: make disc-extract idempotence + two negative controls; include audit 34→0; fresh-clone 218/218; sdk-dual;
ghidra_rebuild.sh --proof cmp exit 0 on SLUS_007.26 + resident and a mutated-copy FAIL; sha1sum --check 28/28;
CI green. C: the R39 negative control of gate_scan.py; the sample check (711/711 tokens); verify_rewrite.py 4,011
pairs; absent_scan.py 0/0/0; timestamps identical; git rev-list --count arithmetic; C8's R22; the probe gate.
D–G: tools/doc_links.py in tools-health; make audit-digest over README/progress.json/badges; timeline.py
self-check (last row == progress.json); mine_hindsight.py anchors resolve; markdown lint on the wiki.
Risk register (condensed; the full text is in the runbook)
- Fixed point: new hashes can never be written into historical docs (each insertion changes every descendant hash);
hence inert tokens in history, live hashes only in the tip commit. A forward-order fast-import driver with
get-markcould do it in one pass but is an unsafe hand-written importer — rejected. - Data-loss hazard after the flip: the purged paths become ignored;
git clean -fdxwould delete the RE database → the bundle, the archive repo, the text export, and the CLAUDE.md fail-safe line. - GitHub retention: force-pushed-away objects stay servable until GC/Support; the earlier S76 pre-scrub lineage is ALREADY sitting unreachable on GitHub — the same purge covers it. A fork/import-created archive would share the object store — the archive is created empty.
- Other clones:
git pullon an old clone = an 8,000-commit merge; reset or re-clone (runbook). - Scrub false positives: a binary-SHA1 abbreviation coinciding with a commit prefix (expected < 1 over 44k tokens)
is guarded by the content-hash exclusion set; ambiguous prefixes become
commit:amb-N, never a wrong hash. - Disk/time: 13 GB free suffices (clone 0.6 + rewrite 0.2 + bundle 0.6 GB); rewrite 10–30 min; R22 ≈ 1 h.
- Mailmap: cosmetic unless the 3 tracked files mentioning the addresses are also replaced (they are).
- The 4-track requirement: the committed manifest includes Tracks 2–4 audio; a Track-1-only user gets an explicit PARTIAL verdict, never a silent pass.
Honest scale (P9, R41)
| Block | Sessions |
|---|---|
| A verification + instrument fixes | 0.5–1 |
| B build path, Ghidra export/rebuild proof, dumps, CI, SETUP | 3–4 (B5 alone 1–1.5) |
| C rewrite, verification, push, gc | 3 + 1 calendar-gated on GitHub Support |
| D publishing surface | 1.5–2 |
| E releases (preset 0.25, Archipelago 0.25, gcc map 1, xsig 0.5, permuter PR 1, drafter write-up 0.5–1) | 3.5–4 |
| F story 1.5, retrospective 1.5–2, wiki + how-to 2–3 | 5–6.5 |
| G exit | 0.5 |
| Total | ≈17–21 sessions (roadmap v2 priced the narrow verify+flip P33 at 2–4; the difference is items 3–5) |
| Cut points that cost nothing later because the flip precedes them: F3 (wiki, 2–3), E5 (permuter PR, 1), E3 (gcc map, 1). |
After approval (R28)
Build the harness task list: one task per plan item A1–A5, B1–B9, C1–C11, D1–D5, E1–E6, F1–F3, G1–G2, in the order
above; create phase-ends/CURRENT_PHASE.md with the plan, the effort annotations and the first 🛑 checkpoint block;
commit as the A1 governance commit. Then one task at a time.