blog/the-milo-engine-saga.md
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 why the best verification tool turned out to be the thing that runs the code.
In 2010, Harmonix shipped Rock Band 3 and Dance Central on the same in-house C++ engine, known internally as “Milo.” Different consoles, different compilers — but underneath, one codebase. That has a strange consequence: reverse-engineer one of these games and 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, directing fleets of Claude Code agents through structured tooling, I built five interlocking projects: three decompilations of the Milo engine across three games and two consoles, a native re-port that runs it on modern hardware and in a browser tab, and a tracing harness that replays the originals under emulation to prove the reconstructions correct. Along the way the work 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, 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, at 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. I picked the three legs 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. 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 sits precisely at the intersection of the other two: same engine as Dance Central 3, same game as the Wii build. It inherits DC3’s engine source tree and compiler setup, and 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 BSim similarity scoring from my fork of Ghidra, 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: an experiment in how far AI agents can push a clean-room decompilation. The honest answer is remarkably far — but only under a 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 44,000 functions in about ten 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.
Running the game was the best bug-finder
A byte-match verifies one function against one shipped binary. It says nothing about the glue, the stubs, and the headers you had to write to get there — and nothing at all about whether the result actually runs. Static matching cannot see behavior. Running the code can.
That 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 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 midway 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 one engine compiled many ways is many translations of one text; and build the thing that runs the code — because running it is how you find what static matching cannot see. The same pattern has since carried into 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.
