Ammo.js Step Simulation and Server Tick Rate
In multiplayer game development, synchronizing physics across the
client and server is a critical challenge. This article examines whether
the step simulation in Ammo.js—the JavaScript port of the Bullet physics
engine—should be strictly tied to the server tick rate. We will explore
the mechanics of the Ammo.js stepSimulation function, the
consequences of direct coupling, and the best practices for maintaining
a stable, synchronized multiplayer physics environment.
The Mechanics of
stepSimulation
To understand the relationship between Ammo.js and server ticks, you
must first understand how Ammo.js processes time. The
stepSimulation method typically takes three parameters:
physicsWorld.stepSimulation(timeElapsed, maxSubSteps, fixedTimeStep);timeElapsed: The actual time that has passed since the last update.maxSubSteps: The maximum number of steps Ammo.js is allowed to take in a single update to catch up totimeElapsed.fixedTimeStep: The internal size of each physics step (defaults to 1/60th of a second, or ~16.66ms).
Ammo.js uses these parameters to run a fixed-timestep simulation. If
timeElapsed is larger than fixedTimeStep, the
engine will run multiple sub-steps (up to maxSubSteps) to
ensure the simulation remains accurate and consistent.
Why Strict Coupling is a Bad Idea
Strictly tying the physics step to the server tick rate—meaning you force the physics engine to calculate exactly one step per server tick using a variable delta time—is generally bad practice for several reasons.
1. Physics Instability
Physics engines rely on a constant, fixed timestep to calculate
forces, gravity, and collisions accurately. If your server tick rate
dips due to CPU spikes, the delta time (timeElapsed)
increases. If you force Ammo.js to simulate a single, large step (e.g.,
100ms instead of 16ms), the collision detection will fail. Fast-moving
objects will “tunnel” through walls, and constraint calculations will
explode, causing objects to fly apart.
2. Variable Network Rates
Servers often run at lower tick rates than physics engines to save bandwidth. For example, a server might tick at 20Hz or 30Hz, while physics engines require at least 60Hz to remain stable. Forcing the physics engine to run at 20Hz directly will result in a sluggish, inaccurate simulation.
The Recommended Approach: Sub-stepping
Instead of strictly tying the simulation to the tick rate, you should use Ammo.js’s built-in sub-stepping. This decouples the server’s update loop from the internal physics calculations.
On every server tick, you pass the actual elapsed time since the last
tick to stepSimulation:
// Server tick rate: 30Hz (approx. 33.33ms)
// Internal physics step: 60Hz (approx. 16.66ms)
physicsWorld.stepSimulation(deltaTimeSeconds, 10, 1 / 60);In this scenario, if the server ticks exactly every 33.3ms, Ammo.js will automatically perform exactly two internal sub-steps of 16.6ms. If a network tick is delayed and takes 50ms, Ammo.js will perform three sub-steps. This keeps the physics simulation stable and accurate, regardless of fluctuations in the server tick rate.
The Exception: Deterministic Rollback Architecture
There is one major exception where physics steps must be strictly tied to the game tick: lockstep or rollback multiplayer systems (such as fighting games or competitive RTS games).
In a deterministic rollback system, the client and server must simulate the exact same state at the exact same tick. If you use sub-stepping with variable delta times, slight floating-point differences will accumulate, leading to desynchronization (desyncs).
To implement determinism with Ammo.js: 1. Disable variable
delta times: Do not pass the real-world elapsed time to
stepSimulation. 2. Force a single step:
Pass the exact same fixed timestep on every tick (e.g.,
physicsWorld.stepSimulation(1/60, 0, 1/60)). 3.
Strict tick-by-tick execution: Run exactly one physics
step per game tick. If the server ticks at 60Hz, the physics engine
steps exactly once per tick. If the server ticks at 30Hz, the physics
steps exactly once per tick (though you must design your game assets to
be stable at 30Hz).
Conclusion
For most multiplayer games, you should not strictly tie the Ammo.js step simulation to the server tick rate. Instead, use a fixed internal physics timestep (usually 60Hz) and allow Ammo.js to sub-step using the server’s elapsed delta time. This preserves collision integrity and simulation stability. Only enforce a strict 1:1 relationship between ticks and physics steps if you are building a fully deterministic, input-synchronized rollback system.