Fixed Time Steps and Accumulator in Matter.js

This article explains how the accumulator variable and fixed time steps interact within Matter.js to create stable, deterministic physics simulations. By decoupling the rendering frame rate from the physics update cycle, developers can prevent common simulation glitches, such as tunneling or erratic collision responses. Understanding this relationship is fundamental for maintaining consistent gameplay physics across devices with differing refresh rates and hardware performance.

The Role of Fixed Time Steps

Physics engines like Matter.js rely on discrete time integrations to calculate velocities, positions, and collision resolutions. When an engine uses a variable time step—advancing the simulation by the exact duration of the last rendered frame—inconsistencies arise. A sudden drop in frame rate produces a large time delta, which can cause objects to pass completely through barriers (tunneling) or experience extreme forces.

To maintain numerical stability, Matter.js performs best when updated using a fixed time step. A fixed time step ensures that the physics world always advances by a consistent, predictable duration (typically 16.66 milliseconds, corresponding to 60 updates per second) regardless of how fast or slow the user's display refreshes.

The Accumulator as a Temporal Buffer

Because the browser's render loop via requestAnimationFrame delivers variable intervals depending on display hardware (e.g., 60Hz, 120Hz, or 144Hz monitors) and CPU/GPU load, you cannot directly pass the render delta into a fixed physics step without distorting simulation speed.

The accumulator serves as a temporal bank that reconciles this mismatch:

  1. Time Accumulation: In each execution of the animation loop, the actual real-world time elapsed since the previous frame is calculated and added to the accumulator variable.
  2. Step Consumption: A loop checks whether the value in the accumulator is greater than or equal to the defined fixed time step.
  3. Execution: For every chunk of time that meets or exceeds the fixed step, Matter.Engine.update(engine, fixedDelta) is called, and the fixed delta value is subtracted from the accumulator.
  4. Remainder Preservation: Any leftover time that is smaller than the fixed time step remains in the accumulator to be consumed during the next rendering cycle.

Implementation Mechanics

The practical implementation of this relationship in JavaScript follows this structure:

const fixedDelta = 1000 / 60; // 16.66ms fixed time step
let accumulator = 0;
let lastTime = performance.now();

function gameLoop(currentTime) {
    let frameTime = currentTime - lastTime;
    lastTime = currentTime;

    // Prevent spiral of death on long pauses or lag spikes
    if (frameTime > 250) {
        frameTime = 250;
    }

    accumulator += frameTime;

    while (accumulator >= fixedDelta) {
        Matter.Engine.update(engine, fixedDelta);
        accumulator -= fixedDelta;
    }

    // Render the scene using Matter.Render or custom renderer
    requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);

Why the Relationship Matters

The relationship between the accumulator and the fixed time step produces three critical outcomes: