DOM ENGINEV0.21.3FIZLET

07 DOCS · DETERMINISM

Same seed,
same game.

A Fizlet sim that steps on a fixed dt, draws every random number from engine.random and keeps its state in typed arrays replays exactly from a seed. That is what makes a replay test possible, and what lets two players run the same game in lockstep with only their inputs crossing the wire.

The rules for a deterministic step

  • Seeded random only. engine.random() is seeded by createEngine({ seed }) and allocation-free; engine.random.state snapshots and restores it. No Math.random in the step.
  • No clock in the step. Use dt and engine.steps (an integer count of fixed steps), never performance.now() or Date.
  • No layout reads. A sim that reads the page depends on the screen. In net play, fix the arena size and scale the drawing.
  • Inputs as bits. Pack what a player does into one 32-bit word per frame; the step reads only the words.
  • Nothing else moves on engine steps. Anything that advances with the step (a drop timer, a random draw in an effect) must advance on the same frames on every peer.
SYNC CHECK · sync.test.ts
// The sim: typed arrays, e.random, inputs as bits. No Math.random, no clock, no DOM.
function sim(e: Engine, players: number) {
  const pos = new Float64Array(players * 2);
  for (let i = 0; i < pos.length; i++) pos[i] = e.random() * 100;
  return {
    state: new Uint8Array(pos.buffer), // a byte view, made once
    step(e: Engine, inputs: Uint32Array) {
      for (let p = 0; p < players; p++) {
        const bits = inputs[p] ?? 0;
        pos[p * 2] = (pos[p * 2] ?? 0) + ((bits & 2 ? 1 : 0) - (bits & 1 ? 1 : 0)) * 2;
        if (bits & 4) pos[p * 2 + 1] = (pos[p * 2 + 1] ?? 0) + e.random() * 8;
      }
    },
  };
}

// In any test (Node or a browser): two peers, 600 frames, a late and jittery link.
const r = syncCheck((e, slot, players) => {
  const s = sim(e, players);
  return { ...s, input: () => (e.steps * 7 + slot) & 7 }; // scripted presses
}, { players: 2, frames: 600, latency: 3, jitter: 2 });
if (!r.ok) throw new Error(`peers diverged at frame ${r.diverged}`); // -1 when none did

syncCheck: find the first frame that differs

syncCheck(setup, { players, frames, latency, jitter }) from @fzlt/core/presence/check runs your sim as lockstep clients over an in-memory link with the latency and jitter you ask for, hashes each client's state every frame and returns { ok, frames, diverged, stalls }. diverged is the first frame whose state differed, or -1. It is test-only code (4,870 B gzip) and never ships to players.

The presence brick's own tests run two clients byte-identical for 3,000 frames with no latency, stay identical with delayed, jittered and reordered inputs at unequal frame rates, run four players at latency 3, and catch a forced desync at the next hash check.