DEV Community

Build a deterministic browser game engine

I built an open-source browser precision game called Path Protocol - 100 levels, a Theme Workshop for authoring your own courses, MIT licensed. Along the way, one constraint shaped every architectural decision: levels must replay identically so scores are comparable. That single requirement turns determinism from a nice-to-have into the product itself. If two players run the same level and the physics drifts, the leaderboard is meaningless. Here's how I structured the engine to make that work, and what I'd do again.

The game loop: two clocks, not one

The most important decision was separating the simulation clock from the render clock. requestAnimationFrame fires when the browser is ready to paint - which varies with monitor refresh rate, background tabs, and system load. If the simulation runs on that clock, the same level plays differently on a 60 Hz vs 144 Hz display. Instead, the engine runs a fixed 60 Hz step with an accumulator:

let accumulator = 0;
const STEP = 1000 / 60;

function frame(now) {
  const dt = Math.min(now - last, 250); // clamp to avoid spiral of death
  last = now;
  accumulator += dt;

  while (accumulator >= STEP) {
    update(STEP); // simulation advances in fixed increments
    accumulator -= STEP;
  }

  render(); // rendering can happen at any rate
  requestAnimationFrame(frame);
}

The simulation only ever advances in fixed STEP increments. Rendering happens as often as the browser allows, but it never changes the game state. This is what makes replays deterministic and scores comparable. The level layout is also seeded: the same seed produces the same start position, targets, pickups, and hazards. No randomness leaks into the simulation.

Ownership: the engine never imports React, Three.js, or the DOM

The cleanest boundary I drew was that the engine is framework-neutral. It doesn't know React exists, doesn't touch Three.js, and never reads the DOM.

  • React owns the menus, HUD, and authoring UI.
  • Three.js owns presentation - rendering the arena.
  • The engine owns movement, collision, scoring, and targets.

The renderer exposes a thin surface to the engine: update, resize, screenToWorld, and a few others. The engine calls into it, never the other way around. This separation made the engine trivially testable. I could run the entire simulation headless in Vitest - no browser, no WebGL - and assert that a given input produces a given outcome. That's the property that makes deterministic gameplay trustworthy.

Collision and scoring live in contracts, not visual assets

A common trap is deriving collision geometry from visual asset bounds. I avoided that by keeping collision geometry in JSON, separate from the rendered meshes.

  • Collision uses token shapes with swept tests to avoid tunneling at high speed.
  • Penalty and restart rules are explicit.
  • The scoring formula lives in exactly one module - there's no second copy to drift.

This matters for the Theme Workshop too: the same JSON contracts that power the player-facing levels also power community-authored courses. Before any level is accepted, it passes schema validation. That's how an open authoring system stays safe and consistent.

What AI-assisted development actually changed

I want to be honest here, because it's easy to overstate. AI helped me move quickly between idea โ†’ code โ†’ tests โ†’ docs. That iteration speed was real and valuable. But the hard part stayed hard: defining the rules. Determinism, collision boundaries, scoring ownership, persistence, security, and what the Theme Workshop should allow - those were decisions I had to make deliberately. The AI was a fast pair-programmer, not a substitute for the architecture.

Try it and contribute

The project is open source under MIT. If you're curious about the fixed-step engine, the collision contracts, or the authoring system, the code is all there.

One question to end with: what's the first thing you'd want to see in a deterministic game engine's test suite? I'd love to hear how others approach replayability.

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.