LLM assisted source lifting and game porting (2): Reconstruction, fidelity, and an LLM-assisted workflow
This post is the second one in a series of me sharing my knowledge and learnings so far on leveraging LLMs in reverse engineering (arcade) video games. This is a significantly longer and more technical deep dive into how things were done and why.
The first post is shorter and stays on a fairly high level. Depending on what you are interested, you might want to read the shorter one first.
A word on “LLM-assisted writing”
This post was written with the support of an LLM. However, this here, is actually me, the human writing this section to be transparent about it. I did review this post prior publishing and also tweaked several things by hand.
My goal is to share the ideas, principles and concepts that I applied in these projects. I believe this knowledge can benefit similar use-cases, and in general help others in figuring out how to make hopefully better use of LLMs in doing great and meaningful things.
LLMs didn’t make writing this post possible, but rather lowered the bar for me to spend that extra time and effort in getting this out. I hope the result, no matter how and who/what wrote it, is what counts and achieves on the knowledge sharing aspect. Because in the end, that’s what I believe does actually matter.
Motivation
I want to port these games, but I don’t have their source code. The missing step is to reconstruct it from the original executable. I call that step source lifting, and the larger project source lifting and game porting. The goal is compiled, modifiable game code derived from the original program, with its platform dependencies adapted to a defined runtime—not a new game designed to resemble it, or an executable that simply bundles the original machine code with an emulator.
The specific games serving as the use-cases are:
- Dance Dance Revolution EXTREME that runs on the System 573 Digital arcade platform
- beatmania IIDX 9th style that runs on PC-based arcade hardware
In short, the rough high-level steps of the approach I applied:
- Analyze the binary
- Extract a searchable decompilation corpus
- Reconstruct compilable source
- Connect paths through the application
- Compare the result against the original
- Static checks and controlled runtime comparisons give the agent feedback
- Hands-on testing remains part of the plan.
My acceptance target is faithful game behavior, not a requirement to reproduce the original binary byte for byte. That is a deliberate tradeoff, not an equivalent form of proof. This post explains the checks I use, what they can miss, and how the development environment and agent workflow support them. It does not claim that either game is complete or that hardware comparison has already been established.
Terms used in this article
I realized that creating this article forced me to think about terminology a lot. It helped me with coming up with, I hope, good explanations for the differences that I see and understand regarding the different approaches to this type of problem. These terms overlap in community usage. The definitions here describe the distinctions relevant to this work, not a universal classification:
- Behavioral reimplementation or fan recreation: a new implementation built from an understanding of the game’s behavior, potentially informed by reverse engineering, with freedom to choose a different architecture. The important distinction is the implementation’s relationship to the original code, not whether someone calls the result a fan project.
- Matching decompilation: source reconstruction whose build targets the original machine code within a stated matching scope. A function match and a complete executable match are different claims.
- Static recompilation or binary translation: translation of machine code into another executable representation, potentially for another processor. This does not necessarily reconstruct a maintainable source-level expression of the original game’s logic. The word “recompilation” alone is ambiguous: it can also just mean compiling reconstructed source again.
- Emulator-based execution: execution of original machine code against an emulated platform. Packaging an emulator and game data as an executable is not, by itself, reconstruction of the application source.
- Source lifting: reconstruction of compilable application source from binary evidence, retaining a traceable relationship to the original logic.
- Game porting: adaptation of that application and its platform facilities to another supported environment.
These are not mutually exclusive. A source lift can target matching output; reconstructed software can run under an emulator. Using an emulator as the reference for a port is also different from using it to execute the port’s game logic. Here the intended game logic executes as compiled reconstructed source, while platform adapters supply the selected target facilities and original game data remains input. The exact boundary and remaining substitutes must be stated for each implemented slice.
Existing SDKs and game-specific abstraction layers can help locate those boundaries. Their presence does not guarantee that every device access is abstracted, or remove the need to inspect memory-mapped hardware calls.
What accuracy means for these ports
The target is high behavioral fidelity: the game must play as expected, with the graphics, sound, input response, and timing that make it the same game to its players. This is not a requirement for universal instruction-level or cycle-level equivalence. Exact internal behavior still matters wherever it affects those outcomes: arithmetic, game asset/data interpretation, or a one-frame timing difference can be central rather than incidental.
“The player cannot tell the difference” expresses the intended experience, not a proof or a license to ignore subtle defects. It needs specific tests and experienced playtesting. Quirks that look like bugs may be part of the expected game; silently correcting them would change the reconstruction.
Functional convergence versus source-lifting completion
My first major milestone is functional convergence: a connected game that works end-to-end, with as much agent autonomy as practical. The agent exercises the available runtime controls and fixes detected functional defects and original-fidelity differences; I contribute broader playtesting and feed issues back for diagnosis and correction. I call this “90/10 convergence” as a working description of that division of effort, not measured code coverage, fidelity, or a quota of defects we can leave behind. It does not require exhaustive bug discovery, and it does not make a known unresolved fidelity claim pass.
Three questions remain distinct:
- Functional convergence: do the intended connected application flows work?
- Code completeness: has all original code in the identified executable and code-bearing library scope actually been reconstructed, including cold paths and code normal runtime scenarios never reach?
- Behavioral fidelity: does that reconstruction preserve the original behavior, supported by evidence with explicit limits?
None answers the other two by itself. The ultimate technical goal is all code lifted, without dropping code just because a playthrough does not reach it. Functional convergence is a major state on that journey, not a claim that either game is complete. How to close the remaining gap effectively—and what evidence should justify declaring the source lift finished—is still open.
Longer term, I want this foundation to support a DDR remaster covering the System 573 games in one Linux/Windows application using the user’s CHD data. For IIDX, the aim is a consistent CLI across versions, with options for cabinet types and native BIO2 support, rather than necessarily one binary. Those are future directions, not delivered capabilities claimed here. First establish the reconstructed baseline; record deliberate remaster changes separately so they do not hide porting mistakes.
Why not require a matching binary first?
A matching target provides a strong objective check on the resulting machine code. It is a valid choice, and it can catch differences that selected runtime tests miss. My selected target is a faithful, modifiable port without requiring that matching step first. That allows work to concentrate on a connected target-runtime application and the behavior I want to preserve, but gives up the broad machine-code correspondence that a verified match could establish.
Cross-compiling a reconstructed function to MIPS does not by itself make its bytes match the original. Compiler version, code-generation and optimization settings, ABI, and address-dependent linking details may all affect comparison. A mismatch need not mean a behavioral difference; a match for one function does not prove correct callers, data, or platform integration. Matching output also does not uniquely recover the original source text.
Similarly, a decompiler emitting C-like code does not establish that the original game was written in C. The reconstruction language and the historical source language are separate facts. This method does not depend on assuming we have identified the latter.
The guiding question is: is the additional complexity necessary, or is there another way to achieve the required result? I apply that to matching work, hardware test automation, and agent tooling alike. It does not remove the obligation to investigate a concrete behavioral difference. It means choosing checks proportionate to the fidelity claim rather than treating every possible internal difference as an equally important porting defect.
What backs up the reconstructed source?
There are two related questions: did the source preserve the original logic, and does the connected application behave like the original under the tested conditions? Neither source inspection nor a successful runtime comparison answers both completely. The fidelity ledger records the evidence and gaps; recording a claim does not make it true.
For a function or connected path, the useful record is small: the binary/code scope, the A0 fidelity checks and A1 fidelity scenarios actually run, where their results can be inspected, and any remaining stubs or unknowns. Untested, blocked, and passing are different states. A fidelity tier label should never stand alone as a claim about the whole game, and this does not require a new database or elaborate tracking framework.
Ordinary implementation review comes first, outside the fidelity tiers. Check for invented APIs, omitted branches, unsupported shortcuts, and disconnected code. An agent reviewing its own work can help, but is not independent evidence of correctness.
Fidelity tier A0: static fidelity, identity-grounded reconstruction
A0 combines identity-checked reconstruction, independent review, maintained checks, native execution, and explicit runtime/platform limitations. Its tests and inspection encode invariants and constraints derived from analysis of the original program: data dependencies, branch behavior, types, integration assumptions, and details that need checking in disassembly because they are unclear or absent in the decompiled pseudo-C. A test should have a grounded reason for its expected result, not merely confirm the implementation the agent just wrote.
A0 can still encode a mistaken interpretation. When decompiled expressions, signedness, memory accesses, or control flow are doubtful, inspect the original instructions. Passing unit tests are not a claim that all original semantics have been recovered.
Fidelity tier A1: runtime fidelity in the development environment
A1 compares the reconstructed build against an original-game reference. Both expose game-loop debugging controls so the agent can inject inputs, advance execution, and compare frame and audio buffers. The evidence belongs to the scenario, builds, inputs, and reference environment actually exercised—not to every untested function merely linked into that build.
A screenshot checks selected visual output. A scripted input sequence checks more transitions. Audio comparisons add another observable interface. None automatically covers unseen branches, internal state, or every timing boundary. A1 is useful precisely because it supplies concrete feedback, not because it makes that feedback complete.
Fidelity tier A2: Human QA and real hardware playtesting
Short and focused human-driven QA sessions in a development environment allow for uncovering bugs and defects the agent missed for likely tooling limitation reasons. After that, a human playtest in a “production environment”, i.e. on the target production hardware/cabinet, must confirm that gameplay behavior, performance and timing accuracy are holding up to the actual player’s expectations.
Experienced-player QA and a controlled hardware comparison are related but not interchangeable. A blind A/B test that uses the same cabinet/player interface visible to the players, but swaps out the original hardware and software with the newer hardware and the reconstructed game without the player seeing that, might be a final test to establish human-indistinguishability.
Example: a timing window that looks right but is wrong
Suppose an agent hardcodes a judgment window where the original reads the value from chart data. This is an illustrative failure mode, not a claim about a particular game’s chart format. Ordinary gameplay may look identical if the chosen data happens to contain the same value as the constant.
The investigation would work in two directions:
- Follow the original data read and its use in the judgment calculation. Check the disassembly where necessary and retain that dependency in the reconstruction. Encode grounded constraints in A0 tests.
- Find supported data and input timings that distinguish the implementations: for example, different observed window values and inputs just inside, at, and outside the relevant boundaries. Compare judgments in the controlled original and reconstructed runs where the harness exposes them.
If we cannot observe or exercise those distinctions yet, mark that part unverified. A matching screenshot is not an answer. An extended playthrough is useful integration coverage, but may also miss the distinction. Manual reconstruction can make the same mistake; AI makes a repeatable feedback loop especially valuable, not the problem unique.
The game-loop debugger
The central runtime tool is a game-loop debugger: an application-level interface for controlling execution and inspecting user-facing output, rather than an instruction-level CPU debugger. Its controls include frame stepping, advancing execution, frame-buffer recording or dumping, audio-buffer dumping, and gameplay input injection. They can be scripted to create something reflecting repeatable intergration tests.
The reconstructed runtime exposes these controls. On the DDR reference side, MAME runs the original game and Lua scripting supplies corresponding controls through the same CLI interface for the agent. The PC reference setup uses the original game with bemanitools and a hook library to expose runtime control; it is not simply the same emulator arrangement as DDR.
This makes a replay script a practical test input: drive the game through menus, select songs, supply gameplay inputs, and collect outputs at selected points. A full three-song session is the kind of sequence the interface can express; this is not a claim that such a replay has already passed end-to-end for both projects. Comparisons need identifiable builds, starting data and configuration, input schedules, and defined capture points. Nominally stepping one frame on each side does not itself guarantee aligned simulation time or audio boundaries.
Randomness limits repeatability as well as unmanaged game-side (background) concurrency. The same input script may diverge because random state or initialization differs, rather than because the lift is wrong. Where state cannot be controlled or the relevant outputs aligned reliably, the comparison must report that limitation instead of treating every mismatch as a defect—or silently ignoring it. There is no universal deterministic replay claim here.
How much can the reference runtime tell us?
MAME is a useful reference, not infallible original hardware. Its usefulness must be qualified for the particular game revision, emulator revision, and behavior being tested. Agreement in menu navigation does not establish cabinet audio/input timing, while a device limitation does not invalidate every visual observation. A shared substitute on both sides can also conceal an error.
For an A1 comparison, identify relevant reference limitations and substitutions. If original hardware or another independent source has not checked an aspect, do not imply that it has. The same caution applies to bemanitools: substitution makes development possible without all the physical devices, but does not constitute proof of those devices’ behavior.
In my experience, this feedback loop has caught most of the defects I have encountered so far. That is an observation, not a measured bug-detection rate. Hands-on QA may be a better next investment than automating every last hardware comparison. If a concrete remaining timing defect needs a more precise check, though, the existing harness’s success is not a reason to waive it.
Source-code lifting life-cycle: From evidence to a running path
The following covers the life-cycle states the lifted source code is going through in the lifting process. Several things that sound like “progress” are different claims:
| Source code state | What it demonstrates | What it does not demonstrate |
|---|---|---|
| Raw corpus | A searchable rendering of functions and binary addresses exists. | That the decompiler is correct or the source compiles. |
| Mechanical source | Reconstructed functions can be compiled for the target. | That they are reachable from the original program entry point. |
| Connected path | The application reaches an observable result through lifted code. | That every branch or device behaves like the original. |
| Compared behavior | Selected observations match an original-runtime oracle. | Fidelity outside the tested inputs, frames, and boundary behavior. |
Keep those claims separate in code reviews and status reports. A platform stub can make a connected path run, but it does not turn that device check into a verified reconstruction.
The source lifting workflow
The preceding sections document in a very mechanical style the steps of the workflow, the evidence and tools, from an agents perspective. The rest of the walkthrough of the workflow follows this map: select a numbered box to jump to its detailed explanation, then use the return link beneath that section’s heading to find your place again. Read the rows left to right, then down; on a phone, follow the boxes from top to bottom.
1. Before code: describe the game and the platform
Two short, revisable documents give the agent a useful frame. They need not be encyclopedias or a rigid architecture specification. The goal is to give the agent enough context to “understand” the domain with its specific terminology, the domain boundaries, and the game’s design/architecture.
The domain description (or game design) explains what the game is: the player’s controls, screen flow, rules, important mechanics, and timing constraints. For DDR, the four directions on the dance panel, the on-screen step targets, music timing, and input judgments are not incidental details. Even a source lift focused on startup needs to know that later work must compare the synchronization of audio, video, and input—not just whether an arrow can be drawn.
The platform description specifies the intended source target: processor, operating or console environment, compiler, language, libraries, binary interfaces, storage, memory map, and application-visible hardware. On an x86 Windows-to-x86 Windows lift, much may come directly from the original platform, but the exact supported build and substitute-runtime assumptions still belong here. On System 573 Digital, describe separately the PlayStation-derived facilities and the extra arcade components. Record how the application reaches them—SDK calls, mapped addresses, device registers, or other observed interfaces—rather than dumping an emulator’s entire device implementation into a design document.
Reference source for an emulator or hardware substitute can reveal addresses, call conventions, and expected responses. Label which statements are observed in the binary, which come from reference implementations, and which remain hypotheses. If the original application’s calls contradict the proposed boundary, revise the boundary. Avoid promising that an interface supports more than the lifted path has shown.
An empty platform/ module or a brief boundary note can act as a signpost
before lifting. Do not fill it with speculative device APIs. When a lifted
call site first reaches a mapped register or library facility, preserve the
evidence from that call site and give it a narrow interface. Keep ordinary
game logic on one side and substitute device behavior on the other.
2. Prove the development runtime
Do this before lifting an application-sized body of source. Effectively, it provides the foundation and sets up the agent for success.
- Inventory the original executable and code-bearing libraries, game data, symbols if available, runtime tools, documentation, and available reference implementations. Keep original inputs identifiable and unchanged.
- Choose the actual compiler and target ABI, language mode, libraries, link arrangement, and run environment. Record versions and where they came from. A guessed “close enough” compiler can create problems later at every platform boundary.
- Compile and run an application-shaped “hello world” through the intended entry point and platform setup. Exercise one minimal visible result, not merely a successful object-file compilation.
- Make it clear where lifted game source, platform adapters, raw binary analysis, assets, and reference source belong. The agent should be able to find each without treating reference code as the original program.
That environment is the source-lifting sandbox. Its restrictions are practical: use the selected platform facilities; don’t pull in an unrelated graphics, audio, or device stack because it seems convenient. If the observed program needs a facility not accounted for, surface that gap and determine the smallest real boundary, rather than silently expanding the target. The sandbox is a known starting point, not a claim that every dependency is already understood.
For IIDX for example, the substitute runtime is supposed to stand in for hardware that is not physically connected. A call to download firmware to an I/O board might still be part of the lifted startup path; it need not trigger a new USB device implementation before the first screen can run. This is a slice-specific choice to model the response at a platform seam, and must be marked as such. Likewise, using a System 573 emulator as a runtime oracle or a source of interface evidence does not require importing its FPGA emulation into the reconstructed game.
3. Authenticate, analyze, and build a searchable raw corpus
Identify the exact release and hash the executable, data, and analysis database so evidence cannot silently drift between revisions. Keep original inputs unchanged. Analyze the executable and code-bearing dependencies; extract a function-level decompilation corpus with stable links back to binary identity (addresses, module, or other identifiers). Preserve uncertain names and decompiler artifacts as uncertain. Store the corpus next to the work so an agent can search it cheaply, but do not build or link it as game code. Repeatedly requesting the same decompilation through a tool is slow, can eat up your token budget faster, and makes it harder to inspect the larger call graph. A key optimization here was to make it as cheap/easy as possible for the agent to read and browse this data.
Account for recognized functions, relevant data, and executable ranges: each needs one source owner or an explicit unresolved classification. That map prevents silent omissions and duplicate implementations; it is not a fidelity score or a demand to translate the whole binary before choosing a slice. An unresolved classification keeps a gap visible; it does not satisfy the eventual goal of lifting all code. The tracer determines the order of work, not which original functions can be permanently omitted. Return to the original binary and disassembly when the decompiler output is ambiguous; a pretty C rendering is not itself evidence of semantics.
4. Choose the first connected slices
A tracer bullet (see Hunt & Thomas’ “The Pragmatic Programmer”) here means a narrow path through the real program from a defined entry to an observable result. It goes through the necessary code and platform seams; it is not a horizontal sweep of every renderer function or every device driver. A workable order for these arcade games is:
- Startup to the first warning or visible screen. Establish entry, initialization, essential asset loading, hardware/device initialization and checks, and rendering. This is commonly referred to as “application boot”. Include required failures on that path; exclude unrelated devices and sound playback unless the visible outcome really depends on them.
- Operator/test menu. Render it and navigate it with input. Lift the encountered calls into storage, sound, I/O, or other device boundaries, while deciding explicitly which device checks may provisionally pass. Text, textures, or 3D tests belong in this slice only to the extent needed for its stated observable end state.
- Normal game flow, split again. Attract loop; credits and player’s game session start; selecting different game modes and song selection; starting a song; gameplay; results, game credits and game over. Each slice gets its own entry, end observation, required behavior, exclusions, and comparison against the original where possible.
“Operator/test menu” is not automatically one small task. If a menu contains more tests than a single bounded slice can exercise honestly, select a reachable screen and navigation path first and extend it incrementally.
Here, all the domain and design documents that were created prior provide the agent with context to make it “understand” these given path well enough to make execute meaningfully on the tracer bullet. This part contributes significantly to enabling the agent to also understand and detect runtime defects on the A1 fidelity tier.
Example: an operator-menu slice brief
Outcome: From the established startup path, reach the operator menu, render its initial screen, navigate between defined entries using the intended input source, and return to the initial screen.
Include: Lift the code actually traversed; retain calls into sound, storage, and device-check interfaces at explicit platform seams. Implement only enough boundary behavior for the selected checks to complete.
Exclude for this slice: Sound playback, a full storage implementation, FPGA or I/O-board emulation, and tests on menu branches not being exercised. “No playback” does not mean dropping the sound submenu call path.
Check: Build and run the connected application. Record the menu screens, input transitions, which responses were stubbed, and which observations were compared with the original runtime. A passing stubbed hardware test is not evidence of device fidelity.
Stop and ask: If the lifted calls expose a material contradiction in the platform boundary or require a different target architecture, report the exact call and observation, alternatives, and impact. Do not stop merely because physical hardware is absent when its substitution was agreed.
This is a structural template for a decision, not a license to hard-code success for every hardware test. This is not a fully fledged out prompt to establish an entire tracer bullet session context. Choose responses based on the slice and make the substitution inspectable. Where an actual check is necessary to the claimed outcome, implement or verify it instead of bypassing it.
Give the agent a bounded job and a useful way to stop
Before sending a slice to an agent, write a concrete brief: starting state, end observation, code evidence to follow, allowed platform interfaces, runtime oracle, verification, and explicit out-of-scope behavior. A plan can refer to the lifting workflow and its stages without turning every action into a new persistent status file.
The feedback is what makes autonomy useful. Binary-grounded source limits invention, platform boundaries limit where substitution is allowed, and tests that distinguish plausible implementations make shortcuts harder to hide. Where no available check settles a question, more agent iterations are not evidence that it has been solved.
One way I have worked is to send that brief to a fresh OpenChamber session running OpenCode, then use OpenChamber’s goal feature to keep it working toward the same bounded outcome. This is an operator choice, not a requirement of source lifting. A goal must not reinterpret “keep going” as permission to cross a security boundary, change the target, fabricate fidelity evidence, or implement an entire device emulator. If the slice is done, stop and report the result rather than inventing more work.
When a human decision really is needed, state it in (software) engineering terms to drive problem solving based on rational and trade-offs:
The lifted card-reader call reaches address/interface X, but the proposed platform adapter assumes Y. With the original-runtime input Z, the menu remains blocked after the check. Option A changes the adapter and these call sites; option B narrows the slice. Which boundary should we use?
Replace X/Y/Z with observed specifics. Say what was attempted, why the agent cannot safely choose, the smallest options and consequences, and a recommended choice when evidence supports one. A structured question with selectable answers is easier to resolve remotely (on the phone) and keeps my own cognitive load lower than an unexplained “blocked on evidence” message. Do not present routine missing-hardware calls as blockers if the agreed platform substitute already covers them.
5. Lift and connect the reached code
Starting from the agreed entry, inspect the minimum sufficient original instructions, decompilation, callers, callees, and data to write provisional compilable source. Preserve widths, signedness, layout, calling conventions, ordering, failures, and original quirks. Follow the control-and-data dependencies, not just one observed call stack: an indirect dispatch table, callback, resource, or shared-state producer may be essential to the path.
Connect the reached functions through real calls and shared state in the target application. Merely linking their object files together does not establish that integration. Reuse existing source owners rather than adding a second implementation of the same original address. Build, test, and run small increments; return to analysis when the next dependency is uncertain.
Mechanical translation and semantic understanding develop together along the slice. Compilable provisional source is useful progress, not an A0 claim. Recover meaningful structures and names when the evidence supports them, but do not replace unfamiliar behavior with a plausible new game design.
6. Resolve only the platform contacts the slice reaches
A reached library, operating-system, or device call is a question about a specific boundary—not a requirement to implement the entire old platform. First inspect the original caller and service: inputs, outputs, side effects, timing, ownership, ordering, and failure behavior. Reuse a reviewed contract if it already covers that contact. Otherwise realize the smallest supported native service while retaining game rules and sequencing in the lifted code.
The IIDX Win32-based lift must preserve its reached DLL/Win32/DirectX and ABI contracts; the System 573 port needs native replacements for reached platform services that are form to the System 573 hardware. Neither calls for a generic device emulator. Deliberate substitutions need an explicit agreed purpose and limitations. A temporary response for an unsupported cabinet check is not proof of that device’s behavior, nor blanket permission to return success elsewhere.
If essential behavior remains unknown, stop at the exact unsupported contact and report what evidence is needed. Keep that diagnostic seam distinct from a working platform implementation; do not silently discard writes or invent values merely to reach the next screen.
7. Run the connected path and diagnose its frontier
Run through the intended application entry with known inputs and starting state. Use the game-loop debugger to exercise the currently scoped source lifting slice and inspect its visible result, state transitions, video, and audio where available. Identify the first precise frontier: a successful exit, unsupported contact, attributable defect, or divergence from the original reference.
Observe early, while lifting—not only after all reconstruction is complete. A startup failure or wrong output can reveal a mistaken ABI, state producer, resource choice, or platform assumption more cheaply than further speculative implementation. Feed the observation back into the relevant source and original analysis. An exact stop is useful evidence, but moving between stops is not a substitute for reaching the agreed visible outcome.
Pre-review comparisons are diagnostic. Apparently matching images do not establish passing A1 fidelity, and an unaligned or non-repeatable run must remain inconclusive rather than being treated as either a pass or a proven reconstruction defect.
8. Review and compare a bounded result
At a coherent connected boundary, obtain independent review against the identified original instructions and data, maintained tests, source, and native observations. Record limitations and unresolved behavior. This is the bounded A0 evidence described earlier; it does not have to gate every provisional source increment.
Then compare a controlled original/rebuilt scenario for A1. Pin the builds, reference environment, inputs, starting state, and observation points. Prefer a useful connected sequence with meaningful checkpoints, narrowing to a smaller probe when reachability, determinism, or attribution requires it. Reuse unaffected evidence rather than rerunning unrelated checks ceremonially.
State which transitions and outputs were actually observed. Static agreement does not prove runtime timing; runtime agreement does not prove unseen branches. Further documentary or physical-hardware evidence answers selected remaining questions—it is not an automatic next rung for every function.
9. Correct, reverify, and advance along the tracer
For an attributable mismatch, form a concrete hypothesis, make a bounded probe, apply the supported correction, and rerun affected checks. A claim-relevant source or platform change needs the relevant review and comparison again; an old passing result cannot cover a new implementation automatically.
Do not turn every minor residual into an unlimited investigation. If a gap is bounded and does not compromise safety, essential semantics, shared contracts, or the next path, retain its exact limitation and a reason to revisit it. Deferral does not make the affected claim pass. A blocking unknown requires evidence or a scope decision, not an invented implementation.
Then choose the next coherent slice toward the same tracer destination. Extend the connected runtime rather than starting an unrelated collection of easy functions. Once the tracer’s slices are covered, check their combined flow: passing local checkpoints alone does not establish a complete playthrough or whole-game fidelity.
After functional convergence: closing the remaining gaps
Following the connected paths is how I want to reach the first major milestone, not my final definition of a completed source lift. Human QA is useful now, but I do not want the remaining work to depend only on me finding problems by playing the game over and over again. Several possible approaches deserve investigation:
- Reconcile code completeness against the original. Check executable code regions and function discovery as well as the source-owner map. An initial decompiler inventory may miss functions; an accounted-for function may still be unresolved or only provisionally reconstructed. Include error handling, alternate modes, indirect calls, and code unreachable through ordinary play. Compiling a function or assigning it an owner does not establish its semantics.
- Resolve explicit uncertainty. Review TODOs, accuracy-unverified notes, hypotheses, known defects, provisional substitutes, and guarded stops. These markers are leads for investigation, not a complete inventory of possible mistakes. Removing a marker is not a correction: retain the supporting original evidence, affected checks, and any limitation that remains.
- Develop a golden set of scripted runtime integration runs. Reuse the current test setup where useful to specify scenarios, starting state, settings, input schedules, checkpoints, and expected observations. A future direction is to run such a set with Ludaro when it is ready; that is not a capability established here. Expectations need grounding in the original, not just snapshots that bless whatever the rebuilt game currently does. Randomness and timing alignment still limit which comparisons are conclusive.
- Turn QA findings into targeted regressions. Where practical, reproduce a reported defect in a supported runtime scenario or a focused test. For code application runs seldom reach, targeted original/rebuilt comparisons and boundary or failure cases may reveal differences that another full playthrough would miss. Their applicability and cost still need evaluation.
These are candidate closure methods, not an established tenth workflow step or a claim that a golden test set proves the entire game correct. I have not yet determined their effectiveness, the final completion criteria, or how to combine complete code reconstruction with sufficient behavioral evidence to justify moving into the next major phase. “100% done” needs a defensible scope and evidence standard; it cannot simply mean no TODOs, a full function count, or one successful playthrough.
Audit output, not activity
Long unattended runs can burn time on agent-only progress machinery. Status documents, confidence levels, and complex tracking schemes may look useful, but ask what decision they enable. For this work, a small lookup such as “was function F lifted?” and “was it exercised in the running path?” can be more valuable than a detailed, constantly updated ledger.
When progress feels slow, sample a bounded session window: what did the repository and runtime evidence show at the start and end? Classify the work in between by an explicit scheme—for example, source lifting, integration, runtime checking, documentation, and agent-only machinery. Inspect what was actually done inside the largest category, not just its label. That is how to distinguish a useful boundary tool from repeated bookkeeping. My concern about machinery came from inspecting sessions, not from the assumption that all tracking is waste. A percentage quoted without a window, category rules, and source records would be misleading.
My own setup used OpenChamber/OpenCode on a remote machine so a run could continue when my laptop was closed and I could handle decisions by phone. Lean-CTX reduced repository-context overhead and saved a lot of tokens; REkit supplied binary-analysis tools. I disabled shared Lean-CTX knowledge in my multi-project configuration after observing context drift between unrelated projects. This is a configuration-specific experience, not a necessary feature of the method.
Time, model choice, and honest unknowns
The exploratory phase cost time and tokens: building the environment, defining the lifting workflow, debugging runtimes, and correcting agent behavior all happened alongside real lifting. It is worth separating that cost from later execution after the sandbox and slices were defined. A fair retrospective would inspect dated session records for the first experiments, choose an explicit cutover for each project, and report model usage, tokens and elapsed time on both sides. The recent operator-menu work on DDR and startup rework on IIDX are possible cutover candidates, not verified dates in this draft. Subscription limits and parallel work on other projects make a simple “cost per game” claim unreliable.
The work discussed here primarily used OpenAI models (sol-5.5, sol-6.0, sol-6.1) on medium (majority) or high reasoning settings; the exact model IDs, settings, and distribution of work need to be checked against session records before publication. Stronger reasoning did not obviously solve the environment and scoping mistakes for me. I have not established how smaller or local models compare on equivalent slices. Better context handoffs, an orchestration tool, and worktree-based parallelism are possibilities, not demonstrated speedups; concurrency also adds integration costs and can make the first game harder to finish.
Summary: Adapting the method
For another game or application, if you want to adapt this method, I would recommend the following steps:
- Describe what it does and which behaviors must remain faithful.
- Inventory binaries, assets, reference material, legal access, and a controllable original runtime if one exists.
- Document the intended compiler, SDK, libraries, and application-visible hardware seams. Distinguish observations from hypotheses.
- Prove a small real application builds and runs on the intended target.
- Extract an indexed raw corpus without linking it as source; lift functions into a separate compiling and then connected body of code.
- Select one visible path and explicitly bound its allowed stubs and excluded subsystems.
- Run both sides where possible and report what matched, what was stubbed, and what remains unknown.
- Review an agent run’s actual outcomes before adding more tracking or automation.
The goal is not to copy my System 573 boundary or Bemani PC substitute-runtime setup. It is to give an agent the specific platform its code must inhabit, a narrow connected outcome to pursue, and an honest way to know what it has proven.
Next in the series: Part 3: Two games, one workflow, and what runs today.