menu_bookDocumentation
FixedUpdate Internals: Step Counting, Spiral of Death Prevention, Time.Scope, and ApplyForce vs ApplyImpulse
FixedUpdate Internals: How the Fixed Timestep Works in s&box
The s&box fixed update system uses a step-counting approach rather than accumulating time. This is important to understand when debugging physics or timing issues.
How It Works
The FixedUpdate class tracks a _step counter. Each frame:
- curStep = floor( time / delta ) — compute the expected step count at the current time
- _step is clamped to [curStep - maxSteps, curStep] — prevents spiral of death
- While _step < curStep, increment _step and call fixedUpdate() with the correct time scope
CSHARP
internal void Run( Action fixedUpdate, double time, int maxSteps )
{
var delta = Delta;
long curStep = (long)Math.Floor( time / delta );
// Clamp to prevent spiral of death
_step = long.Clamp( _step, curStep - maxSteps, curStep );
while ( _step < curStep )
{
_step++;
using var timeScope = Time.Scope( (_step * delta), delta );
fixedUpdate();
}
}Key Behaviors
- Time.Delta inside OnFixedUpdate() is always exactly 1 / FixedUpdateFrequency — it is set via Time.Scope.
- Time.Now inside OnFixedUpdate() is the simulation time for that step, not the real frame time.
- If the game falls behind (e.g., a frame takes too long), maxSteps caps how many fixed updates run in one frame. Excess steps are dropped.
- The default maxSteps is ProjectSettings.Physics.MaxFixedUpdates.
- The default frequency is ProjectSettings.Physics.FixedUpdateFrequency.
Time.Scope
Time.Scope is used throughout the engine to temporarily override Time.Now and Time.Delta:CSHARP
using var timeScope = Time.Scope( simulationTime, deltaTime );
// Inside this scope, Time.Now == simulationTime, Time.Delta == deltaTimeThis is also used by Scene.Push() to restore the previous scene's time when switching between scenes.
Practical Implications
- TimeSince and TimeUntil use Time.NowDouble, so they work correctly inside OnFixedUpdate().
- Physics forces applied with ApplyForce() are scaled by the physics timestep automatically — you don't need to multiply by Time.Delta.
- ApplyImpulse() is NOT scaled by timestep — it's an instantaneous change.
Was this helpful?