How I Built a 3D Mars Rally Game With AI Coding Agents

Mars Rally Championship, a free browser rally game, is 291 commits old. It started on 26 April 2026 from a game-jam starter pack, and most of the typing was done by AI coding agents working on branches: 54 merged pull requests came off agent branches, and 83 of the 291 commits carry an agent as the author rather than me. The thing that made it survivable was not prompting technique. It was a small set of machine-checkable invariants that fail CI the moment an agent writes code that is plausible and quietly wrong.

Diagram: a grid of 290 squares, one per commit from 26 April to 30 August 2026, coloured by author: 207 by mlsmi, 55 by Claude and 28 by Cursor Agent.

Diagram: 290 commits from 26 April to 30 August 2026: 207 by mlsmi, 55 by Claude and 28 by Cursor Agent. Authorship shows who committed, not who typed.

It started as a game-jam starter pack

The first commit is the Vibe Jam Starter Pack, dropped in whole: four standalone game starters and a bundle of shared agent skills. I kept the 3D one and deleted nothing, which is why projects/ is still in the repo as dead weight. Late April 2026, no plan beyond "make a rally car drive on Mars".

What it is now: a no-build browser game — 44 vanilla ES modules under public/js/, a self-hosted copy of Three.js resolved through an import map, zero bundler — plus a Next.js App Router site wrapped around it. The file I edit is the byte-for-byte file the browser runs. I wrote up that architecture separately in building a 3D rally game in Three.js with no build step; it turned out to matter for agents too, because a diff against a source file is a diff against production.

The honest scale

291 commits between 26 April and 6 September 2026. That is 19 weeks, roughly 2.2 commits a day, mostly evenings. About 38,000 lines across the game modules, the site, and the shared helpers, excluding the vendored engine.

The authorship split: 208 commits under my name, 83 under two agent identities. The repo's commit trailers show more than one agent was used — I moved between them over the summer, and I am not going to pretend I ran a controlled comparison.

Treat that 29% as a crude proxy and nothing more. Commit authorship measures who pressed the button, not who wrote the code. A large share of "my" commits are me fixing or trimming agent output and committing it locally; a share of the agent commits are mechanical renames across 44 files. If you measured characters typed, the agents wrote the large majority of this repo. If you measured decisions, they wrote almost none of it.

Guardrails are the actual work

This is the part people get wrong, so it is the part worth being precise about.

Agents are extremely fast at producing plausible code. The failure mode is almost never garbage. It is code that reads well, passes a skim, parses, type-checks, and violates an invariant nobody ever wrote down — because the invariant lived in my head, or in one comment, or in the shape of a file the agent never opened. Human review does not scale against that. You get three good reviews and then you get a tired one, and the tired one is the one that ships.

So the leverage is entirely in invariants a machine can check. Not "be careful", not a longer instructions file — a script that exits non-zero.

Diagram: a pipeline of four checks an agent's change must pass before merging, each with the failure it exits non-zero on: an orphan module, constants that drifted apart, a run that no longer reproduces, and a game that no longer drives.

Diagram: an agent's diff must pass the import-graph check, the drift guard, a determinism test and Playwright; each exits non-zero on its own kind of mistake.

The import-graph check

./test.sh is a zero-dependency Node script, 25 structural checks, no npm install, runs in about a second. One check does something almost embarrassingly simple:

check('every js module is in the import graph', () => {
  for (const f of JS_FILES) {
    const base = path.basename(f);
    if (!combined.includes(`./${base}`) && !combined.includes(`js/${base}`))
      return `orphan module: public/js/${base}`;
  }
});

With no bundler, an orphan module is invisible. Nothing errors — the file just never loads, and the feature looks complete in the diff and does nothing in the browser. Agents create orphan modules constantly: a new subsystem file, beautifully written, never wired into boot.js. This check has caught it every single time.

The drift guard

The garage, the pilot dossier and the achievement list live on the site, not in the game, and they operate on the game's own localStorage profile. That means lib/game-data.ts is a deliberate second copy of the game's cosmetics catalog, crate economy and medal thresholds. Two sources of truth by design, which is normally a smell.

The smoke test reads both files and diffs them: cosmetic ids must match exactly, and literals like the 500-credit crate cost and the gold/silver/bronze medal speeds must be present on both sides. Drift means either un-equippable items or wrong medals shown to players. Before the guard existed, an agent updating one side and not the other was routine.

The design-system gate

Any visual change to the site has to load a design-system skill first — tokens, type registers, imagery rules, verification steps. Without it, every agent invents its own spacing scale and the site stops looking like one artifact after about four sessions.

Rules about files that must never exist

Two of the sharpest guardrails are prohibitions. public/index.html must never be created, because it would silently shadow the Next.js landing page. Deploy-time placeholders live in exactly one document and nowhere else. Both are named checks that fail loudly, because both are the kind of "helpful" file an agent adds without being asked.

Tests that run the real game

52 Playwright tests across 11 spec files boot the real game at /play, drive a stage, cross checkpoints, exercise the tutorial and the multiplayer lobby, and assert things about the site. This is the expensive guardrail — the job runs about 37 minutes and eats most of my CI budget — and the only one that catches "the code is fine, the game is broken".

GuardrailWhat it catchesWhat it costs to maintain
Import-graph checkNew modules nothing imports — invisible in a no-build gameOne line in boot.js per module; near zero
Catalog drift guardSite's mirror of the game's constants diverging from the gameBoth files must change in one commit, or CI fails
Design-system skillAd-hoc styling that fragments the site's lookThe agent reads the skill before any visual edit; slower first pass
Never-create file rulesFiles that would shadow real routes or leak deploy placeholdersZero — two string checks
Lint for undefined globalsBare-called handlers that were never registered anywhereNew globals must be declared in the ESLint config
Playwright runtime suiteCode that parses, lints and type-checks but does not driveThe big one: ~37 min of CI per run, plus flake triage
Content validatorPublished claims the product cannot back; dead internal linksKeeping a facts sheet current as the game changes

The general point is dull and I think it is the whole lesson: do not spend your budget on reviewing agent output, spend it on making agent output falsifiable. Every hour I put into test.sh paid back more than every hour I put into reading diffs.

What the agents were genuinely good at

Bounded, specified, verifiable work, and they were better at it than I am. The per-wheel tire model came out of a spec, a set of target numbers and three iterations. The deterministic replay pipeline — a fixed physics step, a seeded world, recorded inputs, runs re-simulated bit-exact on the server — is the most intricate thing in the repo and an agent did most of it, because "the same inputs must produce the same output" is a property a test can assert.

They were also good at the work I avoid: the test specs themselves, SQL migrations, accessibility passes, mechanical refactors across 40 files. And they read the whole repo before answering, which I stopped doing months ago.

What did not work

The findings below are the top of docs/ROADMAP.md, which I keep in the repo and public on purpose.

The engineering ran miles ahead of the product. After three months I had a deterministic simulator with real Mars constants, a per-wheel tire model, a ghost format, mobile touch controls and a genuine accessibility layer — and almost nothing wrapping it. No share surface anywhere. No way for a finished run to become a link someone else clicked. The sim was excellent and unencountered.

Content authoring, not code, became the bottleneck. For most of the project exactly one stage was authored. Stages exist as terrain squares; a stage becomes drivable only when someone hand-places a start position and a checkpoint route in the level editor. Three are drivable today. Everything downstream is gated on that manifest, deliberately — an unauthored stage is hidden from the stage list, its leaderboard, its guide page and the sitemap — so a missing route does not strand a broken page, it strands a whole surface that would have existed. No agent can fix this for you. It is taste and hours in an editor.

Mars Rally Championship in-game screenshot: the dev-only level editor on Stage 3 with the MID CP tool selected, the spawn arrow and numbered gate rings joined by route legs across the terrain, the gate list and inspector at right

In-game screenshot: the level editor, a dev-only tool that runs on localhost, placing a stage's gates. Every drivable route was hand-placed here.

The leaderboard was trivially fakeable. For months the table accepted anonymous inserts with a permissive policy, and the submitted time was not checked against the submitted ghost. Any player with devtools could have posted a one-second run. That is now a server route that takes the run's recorded inputs, checks the claimed time against a physics floor derived from route length, rate-limits per device, and then re-simulates the whole run headlessly through the same physics the browser ran — the time that goes on the board is the one the server's simulation produced, and a run that does not reproduce is refused. Worth noticing: agents built the leaderboard, and no agent ever mentioned that it was unauthenticated. I found it during an audit I asked for explicitly.

The site sat effectively unindexed while features kept shipping. A search for the domain returned essentially one URL, and it was a page that did not exist in the app.

That last one is the honest summary of the whole failure mode. An agent will happily build you the next feature forever. It will never interrupt to tell you that your problem is distribution, or that the thing you should do this week is author a stage rather than refactor a subsystem. Vibe coding a 3D game gets you a 3D game surprisingly fast. It does not get you players, and it will not warn you about the difference.

What I would tell someone starting now

Write the invariants before the features. The first day is the cheapest time to add a script that fails when the repo stops being the shape you want, and you will never again know the shape as clearly as you do on day one.

Prefer a check in CI over a rule in a document. A rule in a document binds only the agents that read it that day. A failing exit code binds everyone, including future you at 1 a.m.

Keep an honest findings file in the repo and let the agents read it. Mine is blunt to the point of being unflattering, and it is the single most useful context I hand over at the start of a session.

Budget explicitly for the work agents cannot do: content, taste, and deciding what not to build. On this project that turned out to be the majority of the remaining work, and I planned for none of it.

Where to try this

The game is free, runs in the browser, needs no download and no account, and works on a phone or a school Chromebook. Start driving — a bare link auto-starts a stage, because the game has no menu screen and this site is the menu. The stage list shows what is drivable, the level editor page explains the tool the authoring bottleneck runs through, and about covers the project. The engineering companions here are deterministic ghost replays and the no-build Three.js architecture.

Questions, answered


Can AI agents build a real game?

Yes, with supervision. Mars Rally Championship is 291 commits and roughly 38,000 lines, built over 19 weeks with coding agents doing most of the typing, and it ships a deterministic physics simulation, online multiplayer and a full site. What agents do not supply is the judgement about what to build, the content authoring, or the willingness to notice that a shipped feature is insecure.

How much of the code did the AI write?

By commit authorship, 83 of 291 commits, about 29 percent, plus 54 merged pull requests from agent branches. That number is a crude proxy and probably understates it badly: many commits under my name are me editing and committing agent output. By characters typed the agents wrote most of this repository. By decisions made they wrote almost none of it.

What guardrails do you need for AI-generated code?

Machine-checkable ones. This repo runs a zero-dependency structural test with 25 checks, including an import-graph check that fails when a module nobody imports appears, and a drift guard that fails when two deliberate copies of the same constants diverge. On top of that sit lint rules, explicit prohibitions on files that must never exist, and 52 end-to-end tests that boot and drive the real game.

What is AI bad at in game development?

Knowing when to stop, and everything that is not code. Agents never told me the leaderboard was unauthenticated, never told me the site was unindexed, and never suggested that hand-authoring a stage route mattered more than the next subsystem. They also cannot supply taste: track layout, difficulty pacing and feel are hours in an editor, driving the thing repeatedly.

Is the game free to play?

Yes. Mars Rally Championship runs in the browser with no download, no install and no account. Personal bests and your pilot profile are stored locally in the browser; the online leaderboard and multiplayer rooms work without signing up for anything.

Keep reading


LOG · October 14, 2026Browser Gaming

Best Racing Games With Leaderboards You Can Trust

Racing games with leaderboards, judged on trust: who checks a run before it posts, whether you can watch it or race its ghost, and if you need an account.

Read the dispatch
LOG · October 14, 2026Browser Gaming

Best Drift Simulator Games: Realistic Physics Compared

The best drifting simulator games compared on tire model, surfaces, assists, wheel support and scoring, from Assetto Corsa to free browser options.

Read the dispatch
LOG · September 27, 2026Browser Gaming

Racing Games for Low-End PCs and Old Laptops (All Free)

Racing games for low-end PCs and old laptops, all free: browser games that install nothing, downloads with official specs, and the settings that cost frames.

Read the dispatch