How to Synchronize Two Matter.js Engines

Synchronizing the state between two independent Matter.js physics engines running side by side—such as in a client-server simulation, a Web Worker architecture, or a dual-view setup—requires an authoritative state pipeline, structured data serialization, and accurate state reconciliation. This article details how to extract physics parameters from a primary simulation, transfer the minimal necessary payload to a secondary engine, and apply state corrections without causing visual jitter or simulation instability.

1. Establish the Authority Model

Before sharing data, designate one Matter.js engine as the authoritative source (Master) and the other as the replica (Slave).

Never allow both engines to update each other bidirectionally without conflict-resolution logic; doing so introduces feedback loops and compounding floating-point drift.

2. Extract the Essential State Payload

Matter.js composite trees contain deep object graphs with circular references. Do not attempt to serialize the entire Engine or Composite instance. Instead, map each rigid body to a unique identifier (id) and extract only the dynamic properties required to reconstruct motion:

function extractEngineState(engine) {
  const bodies = Matter.Composite.allBodies(engine.world);
  return bodies
    .filter(body => !body.isStatic)
    .map(body => ({
      id: body.id,
      position: { x: body.position.x, y: body.position.y },
      angle: body.angle,
      velocity: { x: body.velocity.x, y: body.velocity.y },
      angularVelocity: body.angularVelocity
    }));
}

If bodies are frequently created or destroyed, also synchronize dynamic body creation and removal events rather than querying full hierarchies every tick.

3. Apply the State to the Target Engine

To synchronize the secondary engine, match incoming state records by id and apply the values using built-in Matter.Body manipulation functions rather than direct object mutations. Direct mutations bypass internal cache recalculations (such as axes, bounds, and inertia).

function applyEngineState(targetEngine, statePayload) {
  const bodies = Matter.Composite.allBodies(targetEngine.world);
  const bodyMap = new Map(bodies.map(b => [b.id, b]));

  for (const state of statePayload) {
    const targetBody = bodyMap.get(state.id);
    if (!targetBody) continue;

    Matter.Body.setPosition(targetBody, state.position);
    Matter.Body.setAngle(targetBody, state.angle);
    Matter.Body.setVelocity(targetBody, state.velocity);
    Matter.Body.setAngularVelocity(targetBody, state.angularVelocity);
  }
}

4. Prevent Simulation Drift and Jitter

Directly snapping properties each tick can lead to jarring visual artifacts and physics explosions if the secondary engine has active collision constraints. Use the following techniques to maintain stability:

Step Timing Alignment

Both engines must run on the exact same delta time. Avoid passing variable frame deltas (Engine.update(engine, delta)) where frame drops can cause divergent integrations. Run both engines using fixed time steps:

const FIXED_DELTA = 1000 / 60;
Matter.Engine.update(masterEngine, FIXED_DELTA);
Matter.Engine.update(slaveEngine, FIXED_DELTA);

Interpolation Over Hard Snapping

If updates between engines occur over a network or an asynchronous thread boundary (such as Web Workers), the secondary engine should interpolate between the current transform and the target transform:

const LERP_FACTOR = 0.2;

function smoothSyncBody(targetBody, targetState) {
  const newX = targetBody.position.x + (targetState.position.x - targetBody.position.x) * LERP_FACTOR;
  const newY = targetBody.position.y + (targetState.position.y - targetBody.position.y) * LERP_FACTOR;
  const newAngle = targetBody.angle + (targetState.angle - targetBody.angle) * LERP_FACTOR;

  Matter.Body.setPosition(targetBody, { x: newX, y: newY });
  Matter.Body.setAngle(targetBody, newAngle);
  Matter.Body.setVelocity(targetBody, targetState.velocity);
  Matter.Body.setAngularVelocity(targetBody, targetState.angularVelocity);
}

5. Synchronize Constraints and Sleeping Bodies