Physics

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.

accumulate the real frame timeenough for a fixed step?advance physics by one fixed steprender the frame

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, not update. Anything that affects the physics result — applying forces, spawning bodies — belongs on the fixed clock. update runs 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 feed Date.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