Repository navigation
How to AI decomp 11 publishing
Keep ROM-derived bytes out of git from the first commit, even while private. BFM relaxed that rule while the
repository was private ("accidental inclusion is not a violation; a scrub before going public") and paid for it with a
full-history rewrite: about two sessions of tooling, two rehearsals, an archive repository, a force-push, a GitHub
Support ticket and a daily probe. "ROM-derived" is wider than the executable: the extracted payloads, the disassembly,
memory dumps, the reverse-engineering database (a Ghidra project embeds the program's bytes under a page XOR mask),
the vendor SDK, session transcripts that quote disassembly, re-downloadable extension archives, and compiled tool
binaries. Decide the public/private boundary on day one, and cite commits by date and subject in long-lived documents,
never by hash alone — a rewrite changes every hash. The firewall as this project finally installed it — the nine
classes with the tracked form each takes instead, a copyable .gitignore, the audit with its negative control and its
content check, the CI — is the wiki page The ROM firewall.
BFM's procedure (docs/public-flip-runbook.md; the tools under tools/public_rewrite/)
is the worked example. What made it safe:
- One purge set, one file, read by the rewrite and by the public-clean audit, so the two can never disagree.
- A scan of every blob reachable from every ref — by path, by content hash against the derived ROM-hash set (the extraction manifest + every per-binary contract), by byte signature, by size — with an expected-offenders fixture as the negative control: the scan passes only when it fails naming exactly the known set.
-
Inert tokens in history, real hashes at the tip. A new hash can never be written into a historical blob (it
would change every descendant hash), so every old hash cited in history becomes
commit:NNNN(the commit's ordinal), a public ordinal→new-hash map is committed, and one tip commit resolves the tokens in the current documents. Prefixes are asserted unambiguous; content-hash collisions are excluded. - The archive is created EMPTY — never a fork or an import, which share the host's object store — and receives the unrewritten history before the rewrite; a local bundle holds it too.
- Rehearse on a scratch copy, then prove pair by pair with POSITIVE assertions. The first trial caught a defect a single run would have shipped: the empty blob's id was in the strip list, so every "file emptied" change in 4,000 commits was silently undone. The second caught that a byte-identical commit keeps its hash and trips an old-hash assertion. The pairwise proof checks, for every (old, new) commit: identities, both timestamps, message == scrub(old), parents with the pruned commit spliced out, every changed blob re-derived as hash(scrub(old)), every removal justified, no purge path survives in any new tree, and the pruned set equals the derived purge-only set.
- Adopt, then verify the contract again — a content-preserving rewrite changes no tracked byte, and the clean fleet run on the adopted tree proves it.
-
Before the gc, audit
git worktree list. Twelve forgotten gate worktrees (12 GB, each a full old-history checkout) kept 3,729 old commits reachable; a linked worktree's HEAD is a ref. - The host keeps serving force-pushed-away objects until it purges them. Gate the visibility flip on a probe that asks the API and a fetch for sampled old hashes and expects both to fail — and run that fetch in a throwaway scratch repository: a successful fetch of an old hash imports its whole closure back into whatever repository runs it (found the hard way, 5.9 GB; R57).
-
Afterwards: the purged paths are ignored-but-present on the maintainer's disk — never
git clean -x; other clones reset to the new history and nevergit pull(an 8,000-commit merge of two lineages).
Publish the proof, not the claim: one script that runs the whole verification (toolchain preflight → the clean fleet
rebuild → the with/without-vendor-objects dual → the health check with both boundary oracles → the disc audit → the
report) writing one log per step ending in EXIT=<rc> and a timestamp, aborting on the first non-zero, with a summary
table that is pasted into the verification page — never typed. Re-run it after every banked batch and on the published
tree.
The progress JSON (every metric with numerator and denominator, the counts, a row per binary; no run date, so a regeneration never churns), the README's progress block rewritten between two markers, the badge endpoints, the objdiff report for a progress site, the timeline reconstructed from every commit that touched the digests — all written by tools, all asserted fresh by the health check. A wrong number in a README is a small lie that lasts for years; a generator cannot tell one.
The README's "verify it yourself" criterion is literal: a fresh clone, a bootstrap script (apt presence check that
prints the install line, the venv, the submodules, the checksummed vintage-compiler tarballs), the disc extraction
compared against the committed manifest (a Track-1-only dump gets an explicit PARTIAL verdict, never a silent pass), and
the clean fleet run — proven on a fresh clone without the vendor SDK (218/218 in 4 min 18 s). The vendor objects are
optional, user-supplied and checksummed, and a dual target proves the executable is byte-identical with and without
them. Absolute include paths were the one thing that would have broken a stranger's build; an audit class now refuses
them.
Audits (the public-clean scan on every tracked file; every source text with portable includes; the verbatim-assembly manifest without drift; derived indexes fresh; the decoder's unit tests; no stale symbol references) and compile-only (every eligible translation unit through the pinned compiler with the build's exact flags — a PR scope of a few representative binaries in a minute, the whole fleet weekly). CI cannot compare bytes to the originals; say so in the workflow header and the README.
The RE database's hand-authored part as text (a baseline-subtracted export), with a rebuild script that regenerates the program from the disc + the symbol files + that text and proves it equal (a mutated row must FAIL; a synthetic row must round-trip). The memory dumps stay local with a checksum file recording their identity. What is tracked is what a stranger can regenerate; what is not is what only a disc can produce.
The project's own work (tools, docs) under a copyleft license; the reimplemented game code under a NOTICE that asserts no license and acknowledges the copyright; every third-party component's license read from its source of truth (its LICENSE file or the host's license metadata — two upstreams published none, and the table says so); the vendor SDK never distributed. One phrase never used: "clean-room" — the source was derived from the binary.
A README whose numbers are generated; a verification page; the story and the retrospective assembled from the records
with file:line anchors; a wiki authored in the repository (link-checked, covered by the rewrite) and pushed by a
script that rewrites relative links into page names and refuses a dead one; a relative-link checker in the health check
with a pending list — a document may promise a page a later task of the same phase writes, but the promise is
tracked, and the phase cannot close while any entry remains.
The codegen map with its citation audit; a cross-project signature tool with a fixture that contains no game bytes; the masked-scorer improvement offered upstream to the permuter (declined: the maintainer keeps the wildcard rule and prefers symbol renames in the asm, and he read the model-drafted text as LLM-written — write outward text the way a developer writes an issue, short and plain, rewritten from the facts rather than a model draft with the tells removed; failure museum row 37); the drafter-pipeline write-up (the paired dataset of disassembly and matched C is ROM-derived and stays private); a decomp.me preset for the pinned compiler; a note to the neighbouring community project whose RAM map cited yours.
Rules this chapter proposes (recorded for the PhaseEnd; ratified as R74–R80 at the Phase-33.5 gate, 2026-09-07)
- No ROM-derived bytes in any published artifact — test fixtures, JSON, badges and reports included.
- Published numbers are generated, never typed (R51 applied to documents).
- Rehearse every irreversible repository operation on a scratch copy and prove it pair by pair with positive assertions.
- A linked worktree's HEAD is a ref — audit
git worktree listbefore any gc or purge. - A checker that widens its document set must classify a missing promised page as pending, never broken.
Using the project
- Build from your own disc
- Toolchain setup
- Repository layout
- The matching workflow
- The dedup engine
- Overlays and modules
- Ghidra rebuild from text
- Verification and progress
- Contributing and the no-ROM policy
- Tools from this project
- Start a new decomp project
Working conventions
Reference
How to AI-decomp
- Index
- 00 Read me first
- 01 Governance
- 02 The byte gate
- 03 Bootstrap order
- 04 Oracles and instruments
- 05 Cards, lanes, waves
- 06 The knowledge base
- 07 The compiler's source
- 08 Models and budgets
- 09 Economics
- 10 Integration and propagation
- 11 Publishing
- 12 The failure museum
Where the project goes next
History