The Canonical Game Loop, Fixed Timesteps, and State Interpolation

At the core of every interactive game engine (Unreal, Unity, Godot, custom C++) lies the Game Loop. Running physics calculations with a variable delta time ($\Delta t$) introduces non-deterministic physics glitches, tunneling, and frame-rate dependent mechanics. This guide covers the "Fix Your Timestep" algorithm, accumulator loops, and rendering interpolation.


⚡ Quick Dive

Timestep Models Comparison

Model Physics Behavior Frame Rate Dependency Glitch / Tunneling Risk
Variable Timestep (dt = frame_time) ❌ Non-deterministic 💥 Speed changes with FPS drops ⚠️ High (Fast objects tunnel through walls)
Fixed Timestep (Lockstep) 🔒 Deterministic Slows down simulation on lag Low
Semi-Fixed Accumulator Loop 🔒 100% Deterministic Physics Smooth rendering at any Hz 🔒 Zero (Standard production engine model)

The Canonical Accumulator Game Loop (C++)

double t = 0.0;
const double dt = 1.0 / 60.0; // Fixed 60Hz physics tick (16.66ms)

double currentTime = getCurrentTime();
double accumulator = 0.0;

while (!quit) {
    double newTime = getCurrentTime();
    double frameTime = newTime - currentTime;
    if (frameTime > 0.25) frameTime = 0.25; // Prevent "Spiral of Death"
    currentTime = newTime;

    accumulator += frameTime;

    while (accumulator >= dt) {
        previousState = currentState;
        integrate(currentState, t, dt); // Fixed physics step
        t += dt;
        accumulator -= dt;
    }

    // Alpha interpolation factor between physics states [0.0, 1.0]
    const double alpha = accumulator / dt;
    State renderState = interpolate(previousState, currentState, alpha);

    render(renderState);
}

📖 Extended Guide

1. State Interpolation for High-Refresh Displays (144Hz+)

When the monitor refresh rate (e.g. 144 FPS / 6.94ms) does not match the physics update rate (e.g. 60 FPS / 16.66ms), rendering the raw latest physics state produces visual stutter (micro-jitter).

$$\text{RenderPosition} = \text{Position}{\text{prev}} \times (1 - \alpha) + \text{Position}{\text{curr}} \times \alpha \quad \text{where } \alpha = \frac{\text{accumulator}}{\Delta t}$$