In short: I migrated the backend of my game Galactic Reigns from Laravel to NestJS, handing most of the porting to a chain of AI agents. What worked wasn't better instructions, but automated barriers the agents can't get around, a mandatory adversarial review, and the old code used as a test oracle.
The context
Galactic Reigns is a real-time space strategy MMO that I've been building solo since August 2025. The first version of the backend was written in Laravel. I wanted to move to TypeScript with a strict modular architecture: each business domain (fleets, buildings, research, planets…) split into domain, application, infrastructure and presentation layers.
Two early decisions made everything else simpler:
- A new database, not shared with the old one. Differences from the PHP code are therefore documented choices, not bugs to align at all costs.
- The PHP code as a behavioural oracle, not a compatibility target: the goal isn't to run both side by side, it's to reproduce what the old code computes.
The agent chain, one domain at a time
For each business domain, the same chain of sub-agents (Claude Sonnet), under my supervision:
- Scout: an agent reads the domain's PHP code and writes a specification.
- Port: an agent writes the domain in TypeScript, layer by layer, with its tests.
- Verify: types, tests, architecture rules and conventions, with a bounded repair loop.
- Adversarial review: a reviewer hunts for mistakes from three angles, behaviour, module boundaries and fidelity to the original.
- Synthesis: a summary, and the list of decisions that are mine to make.
What the agents got wrong
Agents write plausible code fast. The problem is the plausible part:
- The planets' resource taxonomy had been invented, with values off by a factor of about 100.
- The list of game settings contained around forty invented keys, and 31 keys from the original were missing.
- Instructions such as "don't invent" or "write independent tests" drifted as soon as nothing enforced them.
Without an external reference, these mistakes slip through: the code compiles, and the tests written by the same agent pass too.
The old code as an oracle
The answer was the golden master. Small extractors read the PHP code and produce reference datasets: planet taxonomy, game settings, star types, research, building costs. Each file is used twice:
- as the source of truth: the agent reads the reference instead of inventing;
- as the test oracle: tests check the new code against these references, which are never generated by the code they check.
Mechanics beat prompts
The main lesson fits in one sentence: every recurring agent mistake was turned into an automated check, not into another instruction.
| Lever | Type | What we saw |
|---|---|---|
| Types, architecture rules, conventions, golden master | Automated | Holds. Once the porting template was hardened, the games domain passed on the first attempt. |
| Three-angle adversarial review | Semi-automated | Essential: about 8 divergences per domain that the automated checks let through. |
| Instructions in the prompt | Text | Drifts as long as no check enforces it. |
| Gameplay decisions | Human | Irreducible: I make the call. |
In practice, the barriers are:
- strict typing;
- dependency rules between layers, checked by a tool (the domain can't import infrastructure);
- a conventions script that forbids, for example, creating dates and random numbers directly in business code, outside the adapters built for that (which keeps everything deterministic and testable);
- the golden master.
What the migration fixed along the way
Rereading the old code this rigorously surfaced real bugs in the PHP, fixed in the new version:
- a role check that let an unknown role through instead of denying it;
- a missing membership check on the current game;
- a piece of session state shared across the whole process, so one player could inherit the game chosen by another.
The trap: green tests say nothing about startup
At one point, more than 4,000 tests were passing while the server had refused to start for five commits: a misdeclared dependency injection, invisible to unit tests. Since then, the rule after any module change is simple: build, restart, and make one real call to the API.
The outcome
The migration is complete: all 24 business domains of the game run in production on the new NestJS backend, in closed beta. The method was developed and proven on the first six domains.
What I take away for client projects:
- An agent doesn't replace a method. It speeds up writing; reliability comes from whatever checks its work.
- Investing in automated checks first pays off more than any prompt refinement.
- Keep a human as the business oracle, for the decisions code can't make on its own.