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:
- 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.
- Step Consumption: A loop checks whether the value in the accumulator is greater than or equal to the defined fixed time step.
- 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. - 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:
- Determinism: The physics engine processes the exact same mathematical increments on every machine, ensuring that object behavior, jumps, and trajectories are reproducible.
- Hardware Independence: High-refresh-rate monitors (such as 144Hz displays) do not speed up the simulation, nor do lower frame rates slow down the in-engine passage of time.
- Stability Safeguard: By capping the maximum elapsed time before adding it to the accumulator, developers prevent the "spiral of death," where the engine gets trapped attempting to catch up with an impossible backlog of computation.