How to Sync Matter.js Composites Over a Network
Synchronizing complex multi-body composites in Matter.js across a network requires managing parent-child hierarchies, maintaining rigid constraint stability, and minimizing bandwidth consumption. This guide covers how to architect an authoritative network model for Matter.js composites—such as ragdolls, vehicles, or chained mechanisms—by implementing unique identifier mapping, payload serialization, state reconciliation, and client-side interpolation without breaking physics constraints.
1. Structure Composites with Deterministic Network IDs
Matter.js generates internal IDs (body.id,
constraint.id) incrementally at runtime. Because client and
server instances instantiate objects at different times, these default
IDs will not match across the network.
To synchronize a composite reliably:
- Assign a persistent, globally unique
networkIdto the composite container. - Assign deterministic local identifiers to each nested child body and
constraint (e.g.,
chassis,wheel_front,wheel_rear). - Maintain a lookup map on both client and server to reference
specific composite parts by their hierarchical keys (e.g.,
vehicle_42:wheel_front).
function tagComposite(composite, netId) {
composite.networkId = netId;
composite.bodies.forEach((body, index) => {
body.networkSubId = `${netId}:b_${index}`;
});
composite.constraints.forEach((constraint, index) => {
constraint.networkSubId = `${netId}:c_${index}`;
});
}2. Implement an Authoritative Server Architecture
Running full, uncoordinated simulations on both client and server causes rapid divergence due to floating-point nondeterminism and constraint solver variances.
- Server: Runs the canonical physics loop at a fixed tick rate (typically 30Hz to 60Hz). It processes client inputs, steps the Matter.js engine, and periodically broadcasts state snapshots.
- Client: Sends player inputs (e.g., applied forces, torques, or control flags), receives snapshot data, and reconciles local visual representations.
3. Serialize Minimal State Payloads
Transmitting every property of every body in a composite creates excessive bandwidth overhead. To optimize transmission:
- Serialize Root and Key Dynamic Parts: For rigid
assemblies connected by stiff constraints, you often only need to send
the root body's
position,angle,velocity, andangularVelocity. The client can compute the positions of strictly attached parts locally. - Deformable Composites (Ragdolls/Ropes): For
assemblies where constraints allow movement, transmit
(x, y, angle)for each child body. You can omit velocities if using snapshot interpolation on the client. - Quantize Numerical Values: Convert floating-point
numbers to fixed-precision integers or binary buffers
(
Float32ArrayorInt16Array) rather than sending raw JSON.
Example serialization schema:
function serializeComposite(composite) {
return {
netId: composite.networkId,
bodies: composite.bodies.map(b => ({
id: b.networkSubId,
x: Math.round(b.position.x * 100) / 100,
y: Math.round(b.position.y * 100) / 100,
a: Math.round(b.angle * 1000) / 1000,
vx: Math.round(b.velocity.x * 100) / 100,
vy: Math.round(b.velocity.y * 100) / 100
}))
};
}4. Apply Updates Without Destabilizing Constraints
Directly overwriting the position and angle
of connected bodies causes Matter.js's constraint solver
(Matter.Constraint) to register extreme displacement. This
results in erratic physics explosions or snapped joints.
To apply server snapshots safely:
- Temporarily Disable Sleeping: Ensure bodies are
awake using
Matter.Sleeping.set(body, false). - Use
Body.setPositionandBody.setAngle: Avoid modifying properties directly; use Matter.js mutator functions to ensure internal transformation matrices, bounds, and axes update correctly. - Sync Velocities Simultaneously: Set
body.velocityandbody.angularVelocityto match the target frame so the constraint solver anticipates the velocity vector on the subsequent tick. - Let Constraints Settle: If snapping occurs, temporarily relax constraint stiffness during the tick an update is applied, then restore it once positions align.
5. Client-Side Snapshot Interpolation
To ensure smooth visuals between server updates, do not snap the physics bodies directly on every received network packet. Instead, use an interpolation buffer:
- Maintain a buffer of past server snapshots on the client (typically 50–100ms of history).
- Render the composite at a continuous timestamp slightly behind the
server (
currentTime - interpolationDelay). - For each sub-body in the composite, interpolate between the two
surrounding snapshots:
- Position: Linear interpolation
(
lerp). - Angle: Spherical or shortest-path angle interpolation to prevent wrapping artifacts around \(\pi\) and \(-\pi\).
- Position: Linear interpolation
(
- Apply the interpolated transforms to visual sprites directly, or update kinematic representations of the bodies if the client is not running local prediction.
6. Client Prediction and Reconciliation (Player-Controlled)
If a client controls the composite (e.g., driving a multi-body vehicle):
- Predict Locally: Apply inputs immediately to the client-side composite and simulate the physics step ahead of the server.
- Tag Inputs: Send inputs to the server with an incrementing sequence number.
- Reconcile Errors: When the server snapshot arrives with the last processed input sequence number, compare the server's root body transform with the client's historical transform for that tick.
- Soft Correct: If the discrepancy exceeds a threshold, smoothly blend (decay) the positional difference into the current predicted state over several frames rather than instantly hard-resetting the entire composite.