Debugging Matter.js Instability and Explosions
Numerical instability and constraint explosions in Matter.js occur when physics solver calculations produce extreme forces, causing bodies to jitter wildly, tunnel through barriers, or fling across the canvas at infinite velocities. This article outlines systematic debugging workflows to diagnose, isolate, and resolve these divergence issues. By methodically auditing simulation time steps, visual telemetry, mass ratios, constraint parameters, solver iterations, and velocity thresholds, developers can stabilize chaotic physics simulations.
1. Enable Visual Debugging and State Logging
The first step in resolving instability is visualizing the hidden vectors that drive the simulation. Configure the default Matter.js renderer to show internal solver states:
const render = Render.create({
element: document.body,
engine: engine,
options: {
wireframes: true,
showVelocity: true,
showCollisions: true,
showPositions: true,
showAngleIndicator: true
}
});Pair this visual feedback with state logging. Listen to the
beforeUpdate and afterUpdate events to track
when bodies exceed realistic operational bounds:
Events.on(engine, 'afterUpdate', () => {
for (const body of Composite.allBodies(engine.world)) {
const speed = Body.getSpeed(body);
if (speed > 50 || Number.isNaN(speed)) {
console.warn(`Instability detected on Body ID ${body.id}: Speed=${speed}`);
console.log('Position:', body.position, 'Forces:', body.force);
// Optional: Pause runner to inspect state
Runner.stop(runner);
break;
}
}
});2. Standardize the Time Step
Variable frame rates cause the iterative solver to experience sudden delta spikes, which inject artificial energy into rigid body systems. Ensure the simulation relies on a deterministic, fixed time step:
- Explicitly set
engine.timing.isFixed = true. - Avoid feeding raw, unconstrained
requestAnimationFramedeltas directly intoEngine.update(). - When using a custom loop, cap delta inputs or use an accumulator to
advance the engine in fixed increments (e.g., exactly
16.666msper tick).
3. Normalize Extreme Mass and Size Ratios
Matter.js employs a sequential impulse solver. When two bodies with massively disparate masses collide or are linked via a constraint, the solver produces unstable impulses that blow the smaller body away:
- Keep mass ratios within a 1:10 range: Avoid placing
a body of mass
0.1directly against a dynamic body of mass1000. - Avoid extremely small or thin geometries: Thin polygons (less than 10–15 pixels wide) allow fast-moving bodies to overlap too deeply within a single frame, resulting in massive separation impulses.
- Audit custom densities: If setting density manually
via
Body.setDensity(), verify it does not inadvertently produce zero or near-infinite mass or moment of inertia.
4. Tune Constraint Stiffness and Damping
Constraint explosions are frequently caused by stiff springs operating in under-sampled environments. When a constraint tries to correct a displacement faster than the engine can solve for it, it overshoots, feeding perpetual energy into the system.
- Lower constraint stiffness: Reduce
constraint.stiffnessfrom1to0.1–0.5. - Increase damping: Set
constraint.dampingbetween0.05and0.2to absorb resonant vibrations. - Inspect constraint length: Setting
length: 0can lead to divide-by-zero or undefined directional vectors when both anchor points align precisely. Use a nominal length or offset anchor points.
5. Increase Solver Iterations
When bodies penetrate deeply or complex chains of constraints pull against one another, the default solver iteration count may fail to converge on a valid mathematical equilibrium:
// Default is typically 6 position and 4 velocity iterations
engine.positionIterations = 12;
engine.velocityIterations = 10;Increasing position and velocity iterations forces the constraint solver to calculate multiple stabilization passes per step. This dampens jitter and resolves constraint conflicts before they amplify into explosive impulses.
6. Isolate Components Iteratively
If the root cause remains ambiguous, systematically disable simulation components using an elimination strategy:
- Static Pinning: Set suspected bodies to
Body.setStatic(body, true)one by one to see if fixing them eliminates the explosion. - Remove Collision Filters: Set
body.collisionFilter.mask = 0to disable collision resolution entirely while leaving constraints active. If the explosion stops, the issue stems from body overlap or collision penetration rather than the constraints. - Strip Constraints: Temporarily clear all
constraints via
Composite.clear(engine.world, false, true)to confirm whether collisions alone remain stable.
7. Enforce Defensive Clamping
As a fail-safe against runaway numerical drift, place explicit safety clamps on forces and velocities during simulation updates:
Events.on(engine, 'beforeUpdate', () => {
const MAX_VELOCITY = 40;
const bodies = Composite.allBodies(engine.world);
for (const body of bodies) {
if (body.isStatic) continue;
const speed = Body.getSpeed(body);
if (speed > MAX_VELOCITY) {
const ratio = MAX_VELOCITY / speed;
Body.setVelocity(body, {
x: body.velocity.x * ratio,
y: body.velocity.y * ratio
});
}
// Clear runaway forces or NaNs
if (Number.isNaN(body.force.x) || Number.isNaN(body.force.y)) {
Body.set(body, 'force', { x: 0, y: 0 });
Body.setVelocity(body, { x: 0, y: 0 });
}
}
});This clamping logic acts as an emergency circuit breaker, preventing temporary numerical spikes from permanently ejecting bodies outside the playable bounds.