Goal: Explain how the entire CPS-2 body of work on this blog -- Dark Sakura, Shuma-Gorath, the stages, the crash fixes -- was actually produced: by directing fleets of Claude agents through an encrypted arcade ROM, with me as the editor who decides what counts as true.
Constraints: No source code, encrypted 68000 program ROM, and agents that will confidently report success on things that do not work. The last constraint is the one that shaped everything.
Approach: Fan out parallel hypothesis agents; send independent adversarial agents to refute what they found; accept nothing until it runs live in the emulator and the model reads the screenshot itself. Keep a memory index that carries retractions as prominently as facts.
Result: An 18-fix live-gated build chain, two secret characters fighting natively, and -- the part this post is about -- four documented cases where the AI was confidently wrong and the workflow caught it, including one where an entire working subsystem was retracted in favor of a two-byte nop.
Proof: Every claim below traces to a shipped build, a screenshot gate, or a retraction stamped into the project memory. The war stories are told with their real numbers.
Artifacts: The Dark Sakura and Shuma-Gorath posts are the "what." This is the "how."
The other posts in this series end with a note that the reverse engineering was AI-directed. Readers keep asking what that actually means, and I don't want to answer with a disclaimer. I want to show you the machine.
The instrument was Claude Code -- Anthropic's agentic CLI -- across several model generations, the Opus and Sonnet families, over months of sessions. I did not write the disassembly. I did not hand-trace the 68000. What I built and ran was the thing around the agents: the architecture that decides which of their claims survive.
Because here is the honest core of it: these agents are genuinely capable of reverse-engineering an encrypted arcade ROM, and they are also capable of building an entire subsystem on a wrong premise and reporting it as done. Both happened. The interesting engineering is not the prompt. It is the refutation pipeline.
The Machine Has Three Stages, and the Middle One Is Hostile
Every hard problem in this project ran through the same shape:
Fan out. Multiple hypothesis agents attack the same question in parallel, isolated from each other so they can't converge on a shared mistake. One recorded workflow ran five agents and three independently converged on the same answer -- that convergence is signal. Another ran eight with zero errors; another ran four mappers into a synthesis and then two verifiers.
Refute. Then independent adversarial agents get the findings with one instruction: refute this. Not review, not confirm -- refute. A finding only counts after surviving that pass. The refuters earn their keep: one shipped design died because its premise that $01326A was the sole reader of a variable was false -- an immediate scan found 9 hits. Another correctly predicted that static signature-matching would never find the object allocator and that only a live trace would (it was right; the allocator at $3FAE fell out of tracing a live Ryu Hadouken).
Live gate. Whatever survives refutation still has to run. Agents drive FBNeo -- the custom 64MB core -- through Lua harnesses that auto-navigate the menus, commit the secret characters, execute inputs, and screenshot. The screenshots come out as gd files, get converted by a small gd2png tool the agents wrote, and then the model reads the images itself. Not the logs. The pixels.
The One Rule Was Born From Being Burned
Before any of this existed, an earlier project taught me the failure mode the hard way: I was repeatedly told a fix was in, and each time live play was still broken -- garbled audio, a bot walking one direction forever. It destroyed trust and wasted sessions. Out of that came the rule that governs everything here, written verbatim into the project handoff:
A fix is not fixed until you (1) run it in the emulator, (2) read the screenshot yourself -- read the .png, do not infer from logs -- and (3) confirm the match survived: $FF4000 == 0x0800.
On top of that sit standing regression gates that every build must pass, forever: Mystic Stare's ring still fires. Ryu's Hadouken still fires (proving the rerouted object dispatch is stock-safe). Wolverine's superjump is unchanged, frame-for-frame. New capability never gets to cost old truth.
The builds themselves follow a six-step protocol: assert the base ROM's md5; disassemble every new instruction with capstone and assert the bytes; assert the target arena is untouched FF; assert the old bytes at every edit site before writing; re-verify that all prior fixes are still present in the output; assert the changed-byte list is exactly what was intended. Step five exists because a build once silently clobbered an earlier fix. The final spikes fix asserts that exactly 2 bytes changed in exactly one ROM member.
Now the war stories -- the four times this machinery earned its existence.
War Story 1: The Sprites Were Never Compressed. Our Own Tool Had Scrambled Them.
Early analysis of Shuma-Gorath's sprite data measured entropy at 7.95 bits per byte -- essentially random, the signature of compression. The plan absorbed it as a 30-50 hour codec-archaeology line item, the highest-risk item on the board.
It was false. The analysis artifact had run CPS-2 decryption over the entire first megabyte of the ROM wholesale. That recovers opcodes -- code is what the encryption protects -- but it scrambles data that was never encrypted in the first place. Same address, two truths, depending on which view you read. The real entropy of the sprite data was 4.43, and 11,587 distinct tiles walked out through the existing extraction pipeline with no codec work at all.
The 30-50 hour line item dropped to roughly zero. That is the mirage failure: the AI didn't misread the ROM, it misread its own artifact, and it would have confidently spent weeks on the consequence.
War Story 2: We Built a Whole Satellite Subsystem. The Real Fix Was Two Bytes.
Mystic Smash -- Shuma's rolling-ball attack -- was missing its spikes. An agent investigation concluded the spikes were six orbiting satellite objects that a previous patch had skipped spawning. On that model, a full satellite subsystem was built: six objects spawned, seated in formation, tracked the ball, despawned cleanly on recovery with no pool leak. The mechanism was live-verified. The objects really did all of that.
And the model was still wrong.
The spiky ball was an animation already sitting in the ROM -- group 22, sub 5, at relocated address $3F84A8 -- that the move's selector at $3FB744 never picked, because an earlier remap had left its test reading the opponent's state, making it permanently false. Worse, the smooth variant it picked instead carries attack-box-set $56, which resolves to atk $0000: no hitbox at all, and that's authentic MSHvSF data. The spiky variant carries box-set $57, a real box, atk $0045.
What made the truth findable was a positive control: before believing "this move has no hitbox," an agent proved the box pipeline healthy on a move that has one -- Shuma's Strong Punch shows atk $0014 with reach 71 for 5 frames. So "no box" was a data fact, not broken plumbing.
The final fix: two bytes. $3FB75A: 6602 becomes 4e71 -- a branch nopped out. Live-verified the only way that counts: the atk box live on 37 ball frames where there had been zero, the "FIRST ATTACK" banner on screen, score 0 to 10200, and the ring regression still passing.
The satellite build is recorded in the lineage as Discredited -- do not ship, do not extend, a labeled dead branch. But retraction is not deletion: three independently-measured facts from the dead branch were explicitly carried forward (the owner latch is $b0, not $a0; a handler ending in rts never draws -- it must end in the draw tail; copying the animation pointer does not install sprite state). An entire subsystem, retracted in favor of a nop -- and the salvage itemized.
War Story 3: I Asked Why Native Felt Worse. The Comfortable Answer Failed the Test.
Native Dark Sakura played worse than the old donor-chassis build, and the comfortable explanation was "the native port lost move completeness." Instead of accepting it, we ran an identical 29-move battery (ds_battery.lua) on both builds.
The comfortable answer was refuted. Moves the donor has that the native lost: zero confirmed. (28 of 29 passed on both sides; the single native failure was a contaminated test window, and one move -- back-jump -- actually works only natively.) The real regressions were elsewhere: specials degrading into normals -- landing in state $28 instead of $2A/$10, a Dragon Punch with vertical displacement 0 versus the donor's 24 -- and movement speed roughly doubled on walk: walk forward 152 versus 74, walk back 111 versus 55, superjump rise 362 versus 281.
The caveat stays attached wherever this is cited: it is not a pure A/B. The donor build commits character id $12 -- Ryu's engine -- so the donor side measures Sakura's sprites on Ryu's physics.
This is the workflow pointed at my own beliefs. The question was mine; the machinery treated it exactly like an agent's hypothesis.
War Story 4: The Premise Everyone Repeated for Weeks Was Wrong, and the Truth Was Stronger
For weeks, every writeup -- mine included -- said Capcom left two unfinished secret character slots in the ROM. One skeptical question sent an agent back to the tables, and the ROM answered plainly: every per-character table has exactly 23 entries, ids $00 through $2C. There is no 24th row anywhere. Id $2E borrows Spider-Man's $0E entry through sheer indexing arithmetic; $30 was never provisioned at all. The crashes I'd been fixing exist because nothing was there.
The published posts were corrected the same day, propagated across six of them in one commit. And the corrected claim is the stronger one: these are not unfinished characters finished. They are ids the engine was never built to hold, taught to hold them.
The Memory Carries Retractions, or the Dead Models Come Back
Sessions end. Context evaporates. What persists is a curated memory index of verified facts -- and, just as deliberately, retractions. The satellite entry is not deleted; it is stamped in place: the whole model below is wrong, do not build on it. The retraction sits exactly where the next session will trip over it. Without that, a discredited model is just a well-written document waiting to convince a fresh agent.
The agents also built their own tooling mid-project, which I want on the record because it is the part that still surprises me: the mvsc-text MCP server that renders text in fonts ripped from the game ROMs (it made the YouTube thumbnails); the gd2png converter; dozens of Lua capture and probe harnesses; the PC ring buffer and wild-jump logger compiled into the emulator core itself. The verification machine was partially self-manufactured.
What Still Doesn't Work -- Including the Workflow Itself
Here is the part I'd want a reviewer to read first.
The project bounds: everything runs in an emulator on a custom 64MB FBNeo core, not stock CPS-2 hardware, and that core breaks Fightcade netplay -- offline only. The native characters still run Ryu-clamped physics at roughly 22 dispatch sites (the engine literally shows a mini "RYU" tag under their lifebars). Supers are unported. The wrong sound effects were silenced, not replaced.
The workflow bounds: it catches wrong models, but only after they're built -- the satellite subsystem cost real effort before the refutation landed. It is also not immune to its own operational errors: a cwd-relative ROM path once meant a full test cycle ran against a stale zip; a careless grep filter once ate a build error and a stale build got tested as new. The gates caught both, which is the point -- but the machine's most reliable output is not correctness. It is caught mistakes.
What separated the useful output from the confident nonsense was never the model. It was refusing to count anything that hadn't run.