Determinism
The fixed-step contract that makes Meep's physics bit-exact across V8 runtimes — and what that buys you.
Meep’s physics is deterministic: the same inputs, applied in the same order, produce the same world — every run. And because the engine is pure JavaScript over IEEE-754 doubles, that result is bit-exact across V8 runtimes — Chrome, Edge, Node, Deno, and Electron all compute the identical answer, down to the last mantissa bit.
This isn’t a happy accident of the implementation. It’s a designed property, and a few rules keep it intact.
The fixed-step contract
The simulation never advances by a variable frame time. The EntityManager accumulates real elapsed time and steps the solver in fixed increments (fixedUpdateStepSize, 1/60 s by default), draining the accumulator one fixed tick at a time. A slow frame runs several physics steps to catch up; a fast frame may run none — but every step that runs is the same size.
Within each tick the solver runs a fixed pipeline: find the contacts, group the connected bodies, then run a fixed number of solver substeps (solverSubsteps, default 4), each doing a fixed number of iterations (solverItersPerSubstep). No step count depends on wall-clock time or measured load, so the arithmetic is identical regardless of how fast the machine is.
Why it’s bit-exact
Floating-point math is only “unpredictable” when something in the stack is. Meep removes those somethings:
- Pure JavaScript, IEEE-754 doubles. No WASM with its own float settings, no GPU readback, no native library compiled per platform. V8 evaluates double arithmetic to the bit-identical result on every host it runs on.
- Fixed iteration order. Bodies, contacts, and groups of touching bodies are processed in a stable, deterministic order, so the order-dependent sum of impulses lands the same way every time.
- No hidden entropy. The solver reads no wall-clock, no
Math.random, no uninitialized memory.
The scoping word is V8. Different JavaScript engines may evaluate transcendental functions (Math.sin, Math.sqrt edge cases) to different last-bit results, so bit-exactness is guaranteed across the V8 family — which is every realistic web and Node target — rather than across all engines everywhere.
What it buys you
- Lockstep multiplayer. Replicate the input stream, not world state. Every client runs the same simulation and stays in sync — a few bytes of input per tick instead of a firehose of positions. See Networking.
- Replays. Record inputs once; replay the exact match, every time, on any V8 host.
- Server reconciliation. An authoritative server re-runs a client’s steps and gets the client’s exact result — mispredictions are real divergences, not float noise.
- Reproducible tests. A physics regression is a clean diff against a recorded trace, not a flaky heisenbug.
Keeping it deterministic
The engine holds up its end; your gameplay code has to hold up the other:
- Drive simulation from
fixedUpdate, notupdate. Anything that affects the physics result — applying forces, spawning bodies — belongs on the fixed clock.updateruns once per rendered frame and is for presentation. - Keep entropy out of the sim. If you need randomness in simulated logic, use a seeded PRNG you control, not
Math.random. Don’t feedDate.now()or frame timing into physics decisions. - Order your writes. Create bodies and apply inputs in a consistent order across clients. Two clients that spawn the same bodies in a different order are simulating different worlds.
Follow those and the simulation is a pure function of its inputs.
Where to go next
- Physics overview — how determinism fits the rest of the engine.
- Rigid bodies — applying forces on the fixed clock.