Testing a game with deterministic replays and fuzzing
Most game bug reports go like this: "I fell through the floor near the second door, I think I was holding left?" Nobody can reproduce it, so nobody fixes it.
The root problem is that most games aren't deterministic. Frame timing varies, Math.random() gives different numbers every run, and the same inputs produce different outcomes. Fix that, and a whole set of testing tools becomes possible.
Determinism: the foundation
debuff runs game logic on a fixed-step clock (60 steps a second, whatever the display's refresh rate) with a seeded RNG. Rendering interpolates between steps so motion stays smooth, but the simulation always advances in identical steps.
So a recording of the seed plus the inputs is a complete recording of a session. Play it back and you get the exact same game, every time.
Scenarios: tests for game feel
A scenario is a JSON script of inputs, waits and expectations, run headless:
{
"steps": [
{ "press": "ArrowRight" },
{ "wait": 40 },
{ "release": "ArrowRight" },
{ "expect": { "state.player.x": { "gt": 80 } } },
{ "capture": "after-run" }
]
}npx debuff scenario # run and compare against baselines
npx debuff scenario --update # accept intentional changesexpect checks game state, capture compares a screenshot with a stored baseline, and set changes a param mid-run, so you can test that a higher jumpSpeed actually clears a gap. Scenarios run in CI, and they give coding agents a way to prove their changes work.
Replays: bring the bug back exactly
The dev panel can record a session, then save or copy it as a replay. Attach the file to a bug report and anyone can watch the exact bug happen. Agents can load replays too, through the MCP server's game_replay tool.
A replay is just inputs plus a seed, so it can become a scenario as well. Today's bug becomes tomorrow's regression test.
Fuzzing: random hands, real bugs
Players do things you'd never script. debuff fuzz does them too. It plays the game headlessly with random input and reports three kinds of finding:
- errors: crashes, with the explained error (whose bug, which line)
- stuck states: the game's state stops changing for too long
- loops: the state flips between the same values with no new input
Every finding comes with a scenario file that replays the run up to that point.
npx debuff fuzz --seconds 120 --runs 5
npx debuff fuzz --seed 1234 # repeat a run exactlyBecause the inputs are seeded too, the same seed always gives the same inputs. Run it in CI, or ask your agent to fuzz the game after a big change.
debuff fuzzlands in the next release.
Get started
npm create debuff@latest my-gameNew projects ship with a scenario you can copy from.