Track elapsed time each frame with requestAnimationFrame, add it to an accumulator, and run your update function in a while loop at a fixed dt (e.g. 1/60s) until the accumulator drops below dt. Render once per frame using the remaining fraction to interpolate positions, so physics stays deterministic regardless of frame rate.
At a glance
| Fact | Value | Source |
|---|---|---|
| Fixed timestep decouples physics from frame rate | deterministic sim | developer.mozilla.org |
| Accumulator pattern runs update in a while loop | 0 or more steps/frame | developer.mozilla.org |
| Leftover accumulator fraction used for render interpolation | smooths visuals |
Use a time accumulator: measure real elapsed time each frame, add it to the accumulator, then drain the accumulator by calling your update function at a fixed dt until less than one step remains. Render once per frame, optionally interpolating with the leftover fraction (accumulator / dt) so movement looks smooth even though physics only advances in fixed steps. This keeps collision and physics deterministic across devices with different frame rates, which matters for replays, networked games and anything sensitive to floating-point drift.
If you are targeting multiple web platforms, Playgama Bridge adapts one HTML5 build to 25+ platforms while letting you maintain your own custom loop.
let accumulator = 0;
const dt = 1/60;
let last = performance.now();
function frame(now) {
let elapsed = (now - last) / 1000;
last = now;
accumulator += Math.min(elapsed, 0.25); // clamp to avoid spiral of death
while (accumulator >= dt) {
update(dt);
accumulator -= dt;
}
render(accumulator / dt);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
Clamp the elapsed-time value each frame (as above) so a dropped tab or debugger pause does not cause a huge catch-up burst – this is the “spiral of death” problem. On the web, drive the loop from requestAnimationFrame rather than setInterval, since it aligns with the browser’s paint cycle and pauses automatically in background tabs. See MDN’s anatomy of a video game for how this loop fits the rest of the engine.
Does this differ by engine?
In Unity WebGL and Godot web exports the engine’s own fixed-update step (Unity’s FixedUpdate, Godot’s physics process) already implements this pattern internally, so you generally hook into it rather than writing your own loop; both engines expose JavaScript interop (Unity’s .jslib plugins, Godot 4’s JavaScriptBridge) if you need to call browser code from inside that loop, for example to request an ad break between levels.
Sources
- MDN: Game development (anatomy of a video game)
- Unity manual: interacting with browser scripting
- Godot docs: exporting for the Web
- Playgama Ad getting started
Related questions
Why not just multiply everything by delta time each frame?
Variable-dt movement is simple but not deterministic – physics results change with frame rate, causing tunneling, inconsistent collisions and desync in networked or replay-based games.
What is the spiral of death in a fixed timestep loop?
When a frame takes too long, the accumulator grows faster than it drains, forcing ever more catch-up steps; clamp the elapsed time added per frame to prevent this.
Should rendering use the fixed timestep too?
No – render once per real frame, using the accumulator’s leftover fraction to interpolate between the previous and current simulation state for smooth visuals.
Last updated: 24 September 2026