The Milo engine decompilation saga
How one engineer and a fleet of AI agents reverse-engineered a shared game engine across three games and two consoles — and discovered that a perfect match score can still be wrong.
In 2010, Harmonix shipped Rock Band 3 and Dance Central using the same in-house C++ engine, known internally as “Milo.” The engine was compiled by different toolchains for different consoles, but underneath, it was one codebase. That fact has a strange consequence: if you can reverse-engineer one of these games, you are, in a very real sense, reverse-engineering all of them at once.
Over roughly six months in 2026, I turned that observation into a method. Working solo, but directing fleets of Claude Code agents through structured tooling, I built five interlocking projects that decompile the Milo engine across three games and two consoles, re-port it to run natively on modern hardware and in the browser, and dynamically trace the originals under emulation to prove the reconstructions correct. Along the way, the project surfaced bugs Harmonix shipped sixteen years ago — and taught me that the most important tool in a decompilation project is not the diff score. It’s the thing that runs the code.
One engine, three translations
Matching decompilation is the discipline of writing original C++ that, when compiled with the era-correct toolchain, produces byte-for-byte the same machine code as a shipped binary. It’s preservation work: the output is a buildable, readable source tree for a game whose real source will likely never be public. It’s also brutally hard, because you’re not just reconstructing logic — you’re reconstructing the exact code a specific compiler, with specific flags, would have emitted.
The saga’s core insight is that the same engine, compiled three ways, is three parallel translations of one text. The three decompilations were chosen to occupy deliberately different corners:
- Rock Band 3 (Wii) — compiled with Metrowerks CodeWarrior. The binary I work from is a debug build carrying full DWARF symbols: real function names, real types, real assert strings. This is the ground-truth leg.
- Dance Central 3 (Xbox 360) — compiled with Microsoft’s PowerPC toolchain. Symbol names are recoverable here too, making it the oracle for the engine and the Xbox toolchain.
- Rock Band 3 (Xbox 360, retail) — stripped. No symbols at all. Every function starts life as an anonymous address.
That last binary is the interesting one, because it sits precisely at the intersection of the other two. It runs the same engine as Dance Central 3 and is the same game as Rock Band 3 on Wii. It inherits DC3’s entire engine source tree and compiler setup, and it inherits the Wii build’s game logic and — crucially — its names. A stripped retail binary that would be nearly intractable on its own becomes a triangulation problem instead.
The cross-referencing isn’t hand-wavy inspiration; it’s industrialized. A fingerprinting pipeline indexes every anonymous function by its referenced strings, callees, and constants, then cross-references the two symbol-bearing siblings to propose identities — one run of that stack named 1,871 functions in a single push. Transferring symbols across compilers turned out to be a small research result of its own: exact-instruction matching works within one toolchain but is useless across two, and the only discriminative signal was Ghidra’s BSim similarity scoring, gated by a simple rule — a proposed identity is only accepted if the sibling project still compiles and byte-matches with it applied. Governed that way, a batch of 978 vetted symbol transfers landed with zero false identities.
Agents that never guess
Every repository in this saga opens with the same framing: this is an experiment in how far AI agents can push a clean-room decompilation. The honest answer is remarkably far — but only under a foundational rule I wrote into the first project and never relaxed: agents never look at assembly and guess.
Instead, agents call typed tools. Each decompilation exposes an orchestrator service over a database seeded from the diff report, plus a couple dozen purpose-built commands that return match percentages, struct offsets, cross-references, and behavioral verdicts as structured data. An agent proposing a function match gets a number back, not a vibe.
Running fleets of agents against a shared build tree required real infrastructure: copy-on-write worktree pools so each agent gets an isolated checkout that shares a warm build cache, lock-serialized build wrappers so concurrent writers can’t corrupt state, and a content-addressed compiler cache that cut a cold full rebuild from about five minutes to 3.5 seconds. Every candidate win lands through a throwaway integration worktree that re-diffs the entire binary first — a gate that once caught a shared header “improvement” scoring 97.4% on its own function while silently regressing six other functions that inlined it.
The throughput this bought is the concrete case for the whole thesis. On the Wii project, a governed fleet of roughly eight parallel agents produced 1,757 commits in May 2026 — nearly three times the entire two-and-a-half-year volunteer history that preceded my fork. On the stripped retail binary, I byte-matched more than 18,000 functions in about eight weeks, including 1,176 strict matches in a single day that fell out of one insight about linker behavior. As of mid-July 2026, Dance Central 3 stands at 92.44% of its 32,252 authorable functions matched exactly.
When a perfect score lies
Here is the most counterintuitive result of the whole effort, and it’s a critique of the project’s own success metric.
A function can be byte-matched at a perfect score and still be wrong. The match metric necessarily forgives relocation-target differences — so a function can call the wrong symbol entirely and still score 100%. I ended up naming this class “reloc-masked” bugs, and one July sweep fixed 65 of them in Dance Central 3 alone. Deeper still, a decompilation can be genuinely byte-exact and still encode a subtle semantic misreading of what the code means, invisible until something executes it.
Static matching is blind to all of this. Running the code is not. That realization turned what began as a bonus deliverable — a from-scratch re-port of the engine to C++17 and WebGPU, so the decompiled code can boot, render, and score dance moves on x86_64 and in a browser tab — into the single best bug-finding oracle in the stack. Held to a strict discipline of running the real scripted game flow with no bypass hacks, the running port caught bugs that three other verification layers missed.
Alongside it sits a differential tracing harness: it captures real per-function call records from the original games running under emulation, then replays each call against the decompiled and ported code and compares outputs. Its most instructive moment was an audit of itself — proving that 96.7% of its 25,466-candidate hunting pool had zero bug-finding power, because it was comparing byte-identical code against itself. Reframing around the 845 genuinely “powered” functions immediately produced two real, independently confirmed bugs, including an EndianSwapEq<int> that had silently linked to a no-op stub and a random-number seed function emitting an arithmetic shift where the original used a logical one.
My favorite miniature of the whole lesson is the “melted-face” incident: a wave of near-match mesh rewrites kept every diff gate green while rendering spike-exploded band characters on the native path. It was caught only because someone was looking at the running game. The fix was cultural, not technical — no geometry rewrite lands without a pinned-screenshot visual check.
There’s a satisfying inversion at the end of this chain: because a faithful decompilation must reproduce the original’s defects exactly, the work surfaced genuine bugs Harmonix shipped in 2010 — an inverted receiver and argument in a scoring routine, a fully inverted login-retry clause, transposed difficulty branches, a destructor bounds-checking against the wrong container.
Where it stands
I want to be honest about status, because the shape of each plateau is itself part of the story. Dance Central 3’s static matching has plateaued by its own written admission; the remaining lever is runtime correctness in the native port. The Wii decompilation has been ruled mature, with the frontier moved to visual parity in the browser build. The retail Xbox 360 project hit a declared strategic pivot in July 2026: every cheap matching vein classified as exhausted, with evidence, and the resourcing decision escalated back to me rather than ground through unilaterally. The shared native engine is mid-way through earning its compatibility fixes one quantified experiment at a time. The tracing harness is stalled on a data blocker, having already banked its methodology as the result that outlives its bug count.
The lasting contribution isn’t any single percentage. It’s a repeatable method: point a governed, measured fleet of AI agents at a mechanically verifiable problem; give them structured tools instead of raw disassembly; treat their output as a production system with regression gates; exploit the fact that a shared engine compiled many ways is many parallel translations of one text; and build the thing that runs the code — because running it is how you learn the score was lying. The same pattern has since carried over to other reverse-engineering work, including responsible-disclosure security research on embedded firmware, where the discipline of structured tooling and honest metrics matters even more.
One developer, in roughly six months, got further across three games than a volunteer community had managed in two and a half years on one — and got a pair of 2010-era console games running in a browser tab as a side effect.