2.5 Calculus & Numerical Methods

Phase 2 · Game Math · Study time: 30–50 h

Just enough calculus to integrate motion stably (Euler, semi-implicit Euler, Verlet, RK4) with a fixed timestep, plus the floating-point pitfalls every programmer eventually hits.

Every frame, a game moves things: a character falls, a ball bounces, a rope sways, a bullet arcs through the air. Under all of that is one repeating question: given where something is right now and how it is moving, where should it be a tiny fraction of a second from now? Answering that question, over and over, sixty or more times a second, is what this chapter is about. You already know structs, pointers, and loops from earlier chapters — this chapter adds a small amount of math (a light touch of calculus, the branch of math about change) and shows how a game engine turns that math into a loop that runs every frame.

Do not worry if calculus sounds intimidating. You will not need to solve equations by hand. You need two ideas — the derivative and the integral — and one skill: turning them into a small loop of code. Everything else in this chapter builds on those two ideas.

1. The derivative: how fast is it changing right now

A derivative answers "how fast is this quantity changing, right at this instant?" It is a rate of change. If x is an object's position, its derivative dx/dt ("dx by dt", the change in x per tiny change in time t) is its velocity. If v is velocity, its derivative dv/dt is acceleration — how fast the velocity itself is changing.

DERIVATIVE = rate of change (look at the slope, at one instant) position (x) --d/dt--> velocity (v) --d/dt--> acceleration (a) x = where something is v = how fast x changes a = how fast v changes

You do not need calculus notation to feel this. If a car's odometer reads 100 km at 1:00pm and 160 km at 2:00pm, its average speed over that hour was 60 km/h — that is (distance change) / (time change). A derivative is the same idea, but shrunk down: instead of a whole hour, you shrink the time gap smaller and smaller, until it is asking about a single instant. Let's see that shrinking happen in code, using a position function x(t) = t^2 (an object accelerating steadily, like something speeding up under constant thrust):

#include <cstdio>

double position(double t) { return t * t; }   // example: x(t) = t^2

int main() {
    double t = 2.0;                 // check the rate of change AT t = 2
    double steps[] = {1.0, 0.1, 0.01, 0.0001};

    for (double dt : steps) {
        double avgV = (position(t + dt) - position(t)) / dt;  // slope of a nearby secant
        printf("dt = %8.4f  average velocity = %.6f\n", dt, avgV);
    }
}

Output:

dt =   1.0000  average velocity = 5.000000
dt =   0.1000  average velocity = 4.100000
dt =   0.0100  average velocity = 4.010000
dt =   0.0001  average velocity = 4.000100

As dt (the tiny time gap) shrinks toward zero, the average velocity settles down toward 4.0. That limit — the value it is heading toward — is the true derivative at t = 2. For x(t) = t^2, calculus gives a shortcut formula for the derivative: dx/dt = 2t. Plug in t = 2 and you get exactly 4, matching what the shrinking dt converged to. In a game you almost never need the shortcut formula — physics gives you forces and accelerations directly — but this is the idea underneath every "velocity is the derivative of position" sentence you will read.

Tip Whenever someone says "the derivative of X is Y", read it as "Y tells you how fast X is changing, at each moment." Velocity is the derivative of position. Acceleration is the derivative of velocity. That chain is the entire idea; everything else in this chapter is about computing it with small steps instead of a formula.

2. The integral: velocity accumulates into position

An integral is the reverse move: instead of asking "how fast is this changing," it asks "if I know the rate of change at every moment, how much did the quantity build up in total?" It is accumulation. If you know an object's velocity at every instant, integrating that velocity over time tells you how far it travelled — its position. If you know acceleration at every instant, integrating it tells you velocity.

INTEGRAL = accumulation (add up all the tiny changes over a stretch of time) acceleration (a) --integrate--> velocity (v) --integrate--> position (x) derivative and integral are opposites: one takes something apart into a rate, the other builds a total back up from a rate.

Picture velocity plotted against time. The distance travelled between two moments is the area under that curve. You can approximate that area by slicing time into thin strips, and for each strip drawing a thin rectangle of height v and width dt — its area, v * dt, is roughly the distance covered in that sliver of time. Add up all the rectangles and you have approximated the integral. This method is called a Riemann sum, and it is exactly what a game does every frame, just with one rectangle at a time, one frame at a time.

v(t) ^ each thin bar ~ v * dt | ______ one sliver of distance | _| |____ | _| | | _| stack every bar up | _| = the total distance travelled +----------------------------> t 0 dt 2dt 3dt 4dt ...

Here is that idea as code: an object with constant acceleration a, starting from rest, accumulated in tiny time slices dt. We compare the accumulated result against the closed-form formula from physics, x = 0.5 * a * t^2:

#include <cstdio>

int main() {
    double a = 2.0;      // constant acceleration
    double T = 3.0;      // total time
    double dt = 0.001;   // tiny time slices

    double v = 0.0, x = 0.0;
    for (double t = 0.0; t < T; t += dt) {
        x += v * dt;      // accumulate position from velocity (one thin slice)
        v += a * dt;      // accumulate velocity from acceleration
    }

    double exactX = 0.5 * a * T * T;   // closed-form: x = 1/2 a t^2
    printf("numeric x  = %.4f\n", x);
    printf("exact x    = %.4f\n", exactX);
}

Output:

numeric x  = 9.0030
exact x    = 9.0000

Slicing time into 3000 tiny pieces and adding them all up gets you within 0.003 of the exact answer — close, but not perfect; a tiny error creeps in because each rectangle is only an approximation of the true curve. That small gap is not a bug. It is the central fact this whole chapter is about: a game never solves the exact equation. It always approximates by taking small steps. The next sections are all about which stepping recipe to use, and why the recipe matters far more than how small you make dt.

3. From continuous math to a game loop

The examples above knew the acceleration in advance as a clean formula. Real games do not get that luxury. A character's acceleration depends on player input, gravity, whatever they are colliding with, and whatever spring or force is currently pulling on them — it can change from one frame to the next in ways no formula captures. So instead of solving for an exact equation, a game engine repeats a small recipe every single frame. This repeating recipe is called numerical integration (numerical = using approximate step-by-step arithmetic, instead of an exact formula).

every frame, for every moving object: 1. add up the forces acting on it right now (gravity, springs, input, collisions...) 2. a = force / mass (Newton's second law) 3. step velocity and position forward by a small dt <-- numerical integration 4. draw it at its new position repeat, forever, 30 to 240 times per second

Every object a game simulates keeps a tiny bit of state: at minimum, a position x and a velocity v. Each frame, the engine works out the current acceleration a from whatever forces apply, then uses a stepping rule to turn (x, v, a) at "now" into (x, v) at "now + dt". The rest of this chapter is entirely about that one stepping rule — there turn out to be several different recipes, and they do not all behave the same way, even though they all look almost identical in code.

To compare recipes fairly, every example from here on uses the same test case: a mass on a spring, no friction. A spring pulls back toward the center with a force proportional to how far it is stretched — Hooke's law, F = -k * x, where k is the spring's stiffness. Acceleration is a = F / m = -(k / m) * x. A frictionless spring should oscillate forever at the same height, never gaining or losing energy — which makes it a perfect way to catch a stepping rule that quietly leaks energy in or out.

4. Explicit (Forward) Euler: the simplest stepping rule

The most direct way to turn "position and velocity now" into "position and velocity a moment later" is called explicit Euler (also called forward Euler, named after the mathematician Leonhard Euler). Each step, compute the acceleration from the current state, then move both velocity and position forward using the values you had before the step:

explicit (forward) Euler, one step of size dt: a = force(x) / m v_next = v + a * dt x_next = x + v * dt <-- uses the OLD v, not v_next

Here it is stepping our frictionless spring (k = 4, m = 1, dt = 0.1, starting stretched to x = 1, at rest). Alongside position and velocity we print the spring's total energy (0.5*k*x^2 + 0.5*m*v^2, the sum of stored spring energy and kinetic energy) — physics says this number should stay exactly constant, since nothing is removing or adding energy:

#include <cstdio>

int main() {
    const double k = 4.0, m = 1.0, dt = 0.1;
    double x = 1.0, v = 0.0;

    printf("step     x        v      energy\n");
    for (int i = 0; i <= 6; i++) {
        double energy = 0.5*k*x*x + 0.5*m*v*v;
        printf("%3d   %7.4f  %7.4f  %6.4f\n", i, x, v, energy);
        double a = -k/m * x;
        double xNew = x + v*dt;   // explicit Euler: uses OLD v
        double vNew = v + a*dt;
        x = xNew;
        v = vNew;
    }
}

Output:

step     x        v      energy
  0    1.0000   0.0000  2.0000
  1    1.0000  -0.4000  2.0800
  2    0.9600  -0.8000  2.1632
  3    0.8800  -1.1840  2.2497
  4    0.7616  -1.5360  2.3397
  5    0.6080  -1.8406  2.4333
  6    0.4239  -2.0838  2.5306

The position and velocity numbers look completely reasonable — the spring is swinging back, exactly as you would expect. But look at the energy column: it starts at 2.0000 and climbs every single step, with nothing feeding it. That is not supposed to happen, and it is the subject of the next section.

5. Why forward Euler explodes

Explicit Euler always uses the position or velocity from before the step to move things forward. That one detail means it is always looking slightly backward, using stale information. For an oscillating system like a spring, that lag has a very specific, ugly consequence: each step overshoots very slightly, and every overshoot adds a tiny bit of energy that was never there. If you keep stepping, the energy keeps piling up. This is often called the simulation "exploding" or "gaining energy," and given enough steps it will send positions flying off to absurd values.

The cleanest way to see it is to plot velocity against position — this is called phase space. For a real frictionless spring, that plot is a closed loop, retraced forever, because the energy never changes. Explicit Euler instead draws a slowly widening spiral:

explicit (forward) Euler in phase space -- spirals OUTWARD (energy keeps growing) v ^ | . . | . . | . . -----+---------------------> x | . . | . . | . . each loop is drawn slightly BIGGER than the last one -- energy keeps leaking in
Common mistake Making dt smaller "to fix the explosion." A smaller dt slows down how fast explicit Euler leaks energy, but it does not stop the leak — it is still explicit Euler, still always slightly wrong in the same direction. Given enough steps (and a game runs millions of steps over a play session) it will still drift. A smaller dt buys time; it does not fix the underlying problem. Section 6 fixes the underlying problem with almost no extra cost.

6. Semi-implicit (symplectic) Euler: the one-line fix

Semi-implicit Euler (also called symplectic Euler) changes exactly one thing: it updates velocity first, then uses the new velocity to update position, instead of the old one.

semi-implicit (symplectic) Euler, one step of size dt: a = force(x) / m v_next = v + a * dt x_next = x + v_next * dt <-- uses the NEW v this time

That is the entire change — swap the order of two lines. Here is the exact same spring, same numbers, with only that swap:

#include <cstdio>

int main() {
    const double k = 4.0, m = 1.0, dt = 0.1;
    double x = 1.0, v = 0.0;

    printf("step     x        v      energy\n");
    for (int i = 0; i <= 6; i++) {
        double energy = 0.5*k*x*x + 0.5*m*v*v;
        printf("%3d   %7.4f  %7.4f  %6.4f\n", i, x, v, energy);
        double a = -k/m * x;
        v = v + a*dt;      // update velocity FIRST
        x = x + v*dt;      // then use the NEW velocity
    }
}

Output:

step     x        v      energy
  0    1.0000   0.0000  2.0000
  1    0.9600  -0.4000  1.9232
  2    0.8816  -0.7840  1.8618
  3    0.7679  -1.1366  1.8254
  4    0.6236  -1.4438  1.8199
  5    0.4542  -1.6932  1.8462
  6    0.2667  -1.8749  1.9000

The energy now wobbles slightly (2.00 -> 1.92 -> 1.86 -> 1.83 -> 1.82 -> 1.85 -> 1.90) but it does not run away — it stays in a tight band around the true value and would keep oscillating around it forever rather than climbing. In phase space it stays on (almost) the same loop instead of spiraling outward:

semi-implicit Euler in phase space -- stays on (about) the same loop v ^ | . . . | . . | . . -----+---------------> x | . . | . . | . . . loops stay close to the same size -- energy stays bounded, never runs away

Methods with this "energy stays bounded, never runs away" property are called symplectic. This one-line change is why semi-implicit Euler, not explicit Euler, is what real engines use for physics: Unity's Rigidbody, Box2D, and most game physics engines step velocity first and position second, specifically because of this stability. It costs exactly the same amount of math per step as explicit Euler — you get a much more stable simulation for free, just by picking the order of two lines correctly.

Tip If you ever write your own simple physics update by hand — a projectile, a custom spring, a camera follow — always update velocity from acceleration first, then update position from the new velocity. That habit alone avoids most of the "why does my simulation blow up" bugs.

7. Verlet integration: skip storing velocity at all

Verlet integration (from Loup Verlet, a physicist who used it for molecule simulations) takes a different approach: instead of storing a velocity, it remembers an object's previous position and its current position, and steps forward using those two plus the current acceleration:

Verlet integration, one step of size dt: a = force(x) / m x_next = 2*x - x_prev + a * dt * dt ( velocity is never stored -- it is implied by how far x moved since last step: v ~ (x - x_prev) / dt, only computed if you need it )
#include <cstdio>

int main() {
    const double k = 4.0, m = 1.0, dt = 0.1;
    double x = 1.0, v = 0.0;              // start the same as the earlier examples
    double xPrev = x - v*dt;              // reconstruct a starting "previous position"

    printf("step     x      energy(approx)\n");
    for (int i = 0; i <= 6; i++) {
        double vApprox = (x - xPrev) / dt;  // velocity is IMPLIED, not stored
        double energy = 0.5*k*x*x + 0.5*m*vApprox*vApprox;
        printf("%3d   %7.4f   %6.4f\n", i, x, energy);
        double a = -k/m * x;
        double xNext = 2*x - xPrev + a*dt*dt;
        xPrev = x;
        x = xNext;
    }
}

Output:

step     x      energy(approx)
  0    1.0000   2.0000
  1    0.9600   1.9232
  2    0.8816   1.8618
  3    0.7679   1.8254
  4    0.6236   1.8199
  5    0.4542   1.8462
  6    0.2667   1.9000

Notice these numbers are identical to semi-implicit Euler's. That is not a coincidence — if you expand the Verlet formula using x_prev = x - v*dt, it reduces to exactly the same two lines as semi-implicit Euler. They are close cousins: both belong to the same "symplectic" family, both keep energy bounded instead of leaking it. Verlet's real advantage shows up somewhere else: constraints.

Why Verlet is the go-to for cloth and ragdolls

Cloth, rope, and ragdolls are usually modelled as a grid or chain of points connected by rigid "sticks" of a fixed rest length. Because Verlet only ever touches positions (no velocity to keep in sync), you can move every point forward with the formula above, and then directly nudge points to satisfy distance constraints ("these two points must be exactly 1 unit apart") without worrying about updating a separate velocity — the next step's implied velocity just falls out of wherever the point ends up. This is sometimes called position-based dynamics.

#include <cstdio>
#include <cmath>

struct Point { float x, y; };

void satisfyDistance(Point& a, Point& b, float restLength) {
    float dx = b.x - a.x, dy = b.y - a.y;
    float dist = std::sqrt(dx*dx + dy*dy);
    float diff = (dist - restLength) / dist;
    a.x += dx * 0.5f * diff;   // pull a toward (or push away from) b
    a.y += dy * 0.5f * diff;
    b.x -= dx * 0.5f * diff;   // and the same for b, in the opposite direction
    b.y -= dy * 0.5f * diff;
}

int main() {
    Point a{0.0f, 0.0f};
    Point b{3.0f, 0.0f};    // stretched too far apart
    float rest = 1.0f;      // the "stick" between them should be length 1

    float before = b.x - a.x;
    satisfyDistance(a, b, rest);
    float after = b.x - a.x;

    printf("distance before = %.2f\n", before);
    printf("a = (%.2f, %.2f)   b = (%.2f, %.2f)\n", a.x, a.y, b.x, b.y);
    printf("distance after  = %.2f\n", after);
}

Output:

distance before = 3.00
a = (1.00, 0.00)   b = (2.00, 0.00)
distance after  = 1.00

The two points started 3 units apart and got pulled halfway each, landing exactly 1 unit apart — the rest length. A real cloth simulation runs a Verlet position step, then loops over every stick constraint and applies this nudge a handful of times (once is rarely enough when many sticks share points), which is why cloth in games often looks slightly "loose" or takes a frame or two to settle — it is iteratively solving many of these tiny nudges.

Tip Use Verlet (with constraints) for cloth, rope, hair, and ragdolls, where you have many connected points and want simple, stable behavior. Use semi-implicit Euler for everything else — normal rigid bodies, characters, projectiles — since that is what off-the-shelf physics engines already do for you.

8. RK4: much more accurate, at a real cost

RK4 (fourth-order Runge-Kutta, named after two mathematicians) is a more accurate stepping rule. Instead of sampling the slope (the acceleration and velocity) once per step, it samples it four times — at the start, twice near the middle, and at the end — and blends the four samples together with fixed weights:

RK4 samples the slope FOUR times per step, then blends them: k1 = slope at the START of the step k2 = slope at the MIDPOINT, using k1 to guess where you would be k3 = slope at the MIDPOINT again, using k2's better guess k4 = slope at the END, using k3's guess final step = weighted average: (k1 + 2*k2 + 2*k3 + k4) / 6 the two middle guesses count double, because the midpoint matters most
#include <cstdio>

int main() {
    const double k = 4.0, m = 1.0, dt = 0.1;
    double x = 1.0, v = 0.0;

    auto accel = [&](double xx) { return -k/m * xx; };

    printf("step     x        v      energy\n");
    for (int i = 0; i <= 6; i++) {
        double energy = 0.5*k*x*x + 0.5*m*v*v;
        printf("%3d   %7.4f  %7.4f  %6.4f\n", i, x, v, energy);

        double x1 = x,            v1 = v;
        double a1 = accel(x1);
        double x2 = x + v1*dt/2,  v2 = v + a1*dt/2;
        double a2 = accel(x2);
        double x3 = x + v2*dt/2,  v3 = v + a2*dt/2;
        double a3 = accel(x3);
        double x4 = x + v3*dt,    v4 = v + a3*dt;
        double a4 = accel(x4);

        x = x + (dt/6.0)*(v1 + 2*v2 + 2*v3 + v4);
        v = v + (dt/6.0)*(a1 + 2*a2 + 2*a3 + a4);
    }
}

Output:

step     x        v      energy
  0    1.0000   0.0000  2.0000
  1    0.9801  -0.3973  2.0000
  2    0.9211  -0.7788  2.0000
  3    0.8253  -1.1293  2.0000
  4    0.6967  -1.4347  2.0000
  5    0.5403  -1.6829  2.0000
  6    0.3624  -1.8641  2.0000

The energy column reads 2.0000 the entire way through — at this precision, RK4 does not visibly leak or wobble at all. It is far more accurate per step than either Euler variant. So why does almost nobody use RK4 for real-time game physics? Look at the code: every step calls accel four times, not once. For a scene with a few objects that is nothing, but for thousands of physics bodies, four times the acceleration work per body per frame is a real cost, and games need that cost paid every single frame forever, not just once. Semi-implicit Euler's small, steady energy wobble (section 6) is a price nearly every game is happy to pay in exchange for a quarter of the work.

Tip RK4 earns its cost in places that are not running every frame for thousands of objects: an offline cutscene simulation, a rocket trajectory tool, a rare "must be exact" calculation. For the physics that runs continuously under gameplay, semi-implicit Euler or Verlet almost always wins once you weigh in the real cost per frame.

9. All four side by side, on the same spring

Every example above used the exact same spring (k = 4, m = 1, dt = 0.1, starting at x = 1, v = 0). Lining up the energy column from each one tells the whole story in a single table. Remember: the true energy should stay exactly 2.0000 forever, since nothing removes or adds energy to a frictionless spring.

step explicit Euler semi-implicit Euler Verlet RK4 0 2.0000 2.0000 2.0000 2.0000 1 2.0800 1.9232 1.9232 2.0000 2 2.1632 1.8618 1.8618 2.0000 3 2.2497 1.8254 1.8254 2.0000 4 2.3397 1.8199 1.8199 2.0000 5 2.4333 1.8462 1.8462 2.0000 6 2.5306 1.9000 1.9000 2.0000 explicit Euler: climbs every step -- unstable, will eventually explode semi-implicit / Verlet: wobbles but stays bounded -- stable, cheap, what games use RK4: barely moves at all -- most accurate, but 4x the work per step

Three lessons sit inside this one table. First, "more accurate-looking code" is not the same as "stable code" — explicit Euler and semi-implicit Euler do the exact same amount of arithmetic per step, yet one explodes and the other does not; the order of two lines is the entire difference. Second, Verlet and semi-implicit Euler behave the same for this simple case, which is why either is a reasonable default depending on whether you find it easier to think in velocities or in positions. Third, RK4 is genuinely more accurate, but that accuracy is not free — you are trading CPU time you will spend every frame, forever, for precision most gameplay never notices.

Tip If you remember nothing else from this chapter: never use plain explicit (forward) Euler for anything that oscillates or is driven by a spring-like force. Use semi-implicit Euler as your default.

10. Fixed timestep + accumulator: physics must not depend on frame rate

Every stepping rule above takes a dt as input. The obvious thing to do is measure how long the last frame took and hand that number straight in as dt. This is a trap. Frame time is never perfectly steady — it jitters with scene complexity, background OS work, and outright lag spikes. If dt is the raw frame time, then the exact same gameplay produces different physics results on a fast machine versus a slow one, and even different results from one run to the next on the same machine. Worse, section 5 showed that a bigger dt makes explicit-style stepping error grow faster — so a single lag spike can make physics behave very differently for one frame, or in bad cases let a fast-moving object tunnel straight through a thin wall because it took one huge leap instead of several small ones.

NAIVE (frame-rate dependent -- avoid this): dt = timeThisFrameTook <-- different every frame, every machine updatePhysics(dt) <-- same code, different results every run FIXED TIMESTEP + ACCUMULATOR (frame-rate independent): accumulator += timeThisFrameTook while (accumulator >= fixedDt) { updatePhysics(fixedDt) <-- ALWAYS the same dt, ALWAYS the same result accumulator -= fixedDt }

The fix is to decouple physics time from render time entirely. Pick one fixed dt for physics (commonly 1/60 second) and keep an accumulator — a running total of "real time we owe the simulation." Every frame, add the measured frame time to the accumulator, then run the physics step in a loop, consuming one fixedDt at a time, until there is not enough left for another full step. A slow frame simply causes more physics steps to run that frame, catching the simulation back up — but every individual physics step always used the exact same dt, so the simulation itself is completely deterministic and frame-rate independent, even though rendering is not.

#include <cstdio>

void stepPhysics() {
    // moves every body forward by exactly fixedDt -- always the same dt
}

int main() {
    const double fixedDt = 1.0 / 60.0;     // ~0.016667s, ALWAYS this value
    double accumulator = 0.0;

    double frameTimes[] = {0.021, 0.018, 0.052, 0.017}; // "measured" real frame time

    for (double frameTime : frameTimes) {
        if (frameTime > 0.25) frameTime = 0.25;   // clamp a huge spike (see below)
        accumulator += frameTime;
        printf("frame took %.3fs -> accumulator = %.5f\n", frameTime, accumulator);

        int steps = 0;
        while (accumulator >= fixedDt) {
            stepPhysics();
            accumulator -= fixedDt;
            steps++;
        }
        double alpha = accumulator / fixedDt;   // 0..1, for render interpolation
        printf("  ran %d physics step(s), leftover = %.5f, alpha = %.2f\n",
               steps, accumulator, alpha);
    }
}

Output:

frame took 0.021s -> accumulator = 0.02100
  ran 1 physics step(s), leftover = 0.00433, alpha = 0.26
frame took 0.018s -> accumulator = 0.02233
  ran 1 physics step(s), leftover = 0.00567, alpha = 0.34
frame took 0.052s -> accumulator = 0.05767
  ran 3 physics step(s), leftover = 0.00767, alpha = 0.46
frame took 0.017s -> accumulator = 0.02467
  ran 1 physics step(s), leftover = 0.00800, alpha = 0.48

Watch the third frame: it took an unusually long 0.052 seconds (a lag spike), so the loop runs three physics steps back to back to catch up, instead of one huge step with a blown-up dt. Physics stays exactly as stable as it was on a steady frame rate, just running more frequently that one frame. The leftover accumulator value after the loop (never a full fixedDt) is used as alpha, a fraction between rendering positions to smooth the visuals between two physics steps, so the picture on screen does not look jerky even though physics itself only updates in fixed chunks.

Common mistake Forgetting the clamp on frame time (the if (frameTime > 0.25) line). If the game genuinely stalls for a full second — a texture load, a debugger breakpoint, a phone getting a phone call — the accumulator can balloon, and the catch-up loop tries to run dozens of physics steps in a row. That takes real time itself, which makes the next frame's measured time even bigger, queuing up even more steps. This runaway feedback loop is called the spiral of death. Clamping the maximum frame time you ever feed into the accumulator (accepting that physics will visibly lag behind for one bad frame) is what stops it.

11. Floating-point pitfalls in physics code

Every number in every example above has been a float or double — a computer's approximation of a real number, not an exact one. Physics code runs these approximations millions of times over a play session, so two habits matter far more here than in most other code.

Never compare floats with ==

The same real-valued math, done via different sequences of floating-point operations, very often lands on a value that is off in the last few binary digits. That means an equality check like t == 0.3 can silently fail even when t is, for all practical purposes, exactly 0.3:

#include <cstdio>

int main() {
    double t = 0.0;
    const double dt = 0.1;
    for (int i = 0; i < 3; i++) t += dt;

    printf("t after 3 steps of 0.1  = %.17f\n", t);
    printf("t == 0.3 ?               %s\n", (t == 0.3) ? "true" : "false");

    const double eps = 1e-9;
    printf("fabs(t - 0.3) < eps ?    %s\n", (t > 0.3 - eps && t < 0.3 + eps) ? "true" : "false");
}

Output:

t after 3 steps of 0.1  = 0.30000000000000004
t == 0.3 ?               false
fabs(t - 0.3) < eps ?    true

Adding 0.1 three times gives 0.30000000000000004, not 0.30.1 itself cannot be stored exactly in binary floating point, so the tiny rounding error is baked in from the very first addition. The direct == check reports false, even though the values are equal for any practical purpose. The fix is an epsilon comparison: instead of asking "are these exactly equal," ask "are these within a tiny tolerance (eps, short for "epsilon") of each other." Use this for any comparison involving physics values — checking if a ball "has stopped" (speed < eps) rather than speed == 0, or if two positions "match" rather than are bit-for-bit identical.

Precision gets worse the farther you are from zero

Floating-point numbers do not spread their precision evenly across all values — they pack more precision near zero and less precision far from it. That means adding the same small number to a position has a very different effect depending on how large that position already is:

#include <cstdio>

int main() {
    float smallStep = 0.01f;
    float positions[] = {0.0f, 1000.0f, 100000.0f, 10000000.0f};
    for (float start : positions) {
        float p = start;
        for (int i = 0; i < 100; i++) p += smallStep;   // should move by 1.0 total
        printf("start = %10.1f  moved by %.6f (expected 1.000000)\n", start, p - start);
    }
}

Output:

start =        0.0  moved by 0.999999 (expected 1.000000)
start =     1000.0  moved by 1.000977 (expected 1.000000)
start =   100000.0  moved by 0.781250 (expected 1.000000)
start = 10000000.0  moved by 0.000000 (expected 1.000000)

Near the origin, adding 0.01f a hundred times moves the object almost exactly 1.0, as expected. Ten million units from the origin, it does not move at all0.01 is smaller than the gap between two representable float values out there, so every single addition gets silently rounded away to nothing. This is exactly the bug behind reports like "my open-world character starts jittering, or stops responding to small movements, once I walk far enough from the map's center." Large, open-world games deal with this using a floating-origin technique: periodically re-centering the whole world (or at least everything near the camera) back around (0, 0, 0), or storing world position in double and only converting to float relative to the camera right before rendering.

Tip This is also why the fixed timestep from section 10 uses a double for the accumulator, not a float. The accumulator keeps adding small frame times together over an entire play session — potentially millions of additions — and a double gives it far more room before rounding error becomes visible.

12. Glossary

13. Exercises

Exercise 1 An object's recorded positions are x(0)=0, x(1)=1, x(2)=4, x(3)=9 (it is following x(t) = t^2). Using the central difference formula v(t) ~ (x(t+1) - x(t-1)) / 2, estimate the velocity at t = 1 and at t = 2. Then compare both to the exact derivative formula dx/dt = 2t.
Show answer

At t = 1: v(1) ~ (x(2) - x(0)) / 2 = (4 - 0) / 2 = 2. The exact formula gives 2t = 2*1 = 2 — an exact match.

At t = 2: v(2) ~ (x(3) - x(1)) / 2 = (9 - 1) / 2 = 4. The exact formula gives 2t = 2*2 = 4 — again an exact match.

The central difference happens to be exact here because x(t) = t^2 is a simple quadratic curve (the error in this kind of finite-difference estimate depends on the curve's third derivative, which is zero for a quadratic). For messier motion the estimate would only be approximate, but it still gets closer to the true velocity the closer together your sample points are — exactly the shrinking-dt idea from section 1.

Exercise 2 A spring has k = 2, m = 1, dt = 0.5, starting at x = 1, v = 0. By hand, compute x, v, and energy (0.5*k*x^2 + 0.5*m*v^2) at steps 0, 1, and 2 using (a) explicit Euler and (b) semi-implicit Euler. Which one shows energy climbing, and which one shows it dipping and recovering?
Show answer

(a) Explicit Euler (a = -k/m*x = -2x, uses OLD v for the position update):

step 0: x=1.0000  v=0.0000  a=-2.0000  E=1.0000
step 1: x=1.0000  v=-1.0000            E=1.5000
step 2: x=0.5000  v=-2.0000            E=2.2500

(b) Semi-implicit Euler (velocity updates first, then position uses the NEW v):

step 0: x=1.0000  v=0.0000   E=1.0000
step 1: x=0.5000  v=-1.0000  E=0.7500
step 2: x=-0.2500 v=-1.5000  E=1.1875

Explicit Euler's energy only ever climbs: 1.00 -> 1.50 -> 2.25, already growing fast with this fairly large dt. Semi-implicit Euler's energy dips below the true value first and then comes back up (1.00 -> 0.75 -> 1.19) — it wobbles around the correct value instead of running away from it, exactly the bounded behavior from section 6.

Exercise 3 Using the fixed-timestep accumulator pattern with fixedDt = 1/30 (about 0.03333), and a starting accumulator of 0, trace what happens across three frames whose measured frame times are 0.05, 0.02, and 0.08 seconds. For each frame, give the accumulator value after adding the frame time, how many physics steps run, and the leftover accumulator afterward.
Show answer

fixedDt ~ 0.03333.

Frame 1 (0.05s): accumulator = 0 + 0.05 = 0.05. One step fits (0.05 >= 0.03333), leaving 0.05 - 0.03333 = 0.01667, which is less than fixedDt, so the loop stops. 1 step, leftover 0.01667.

Frame 2 (0.02s): accumulator = 0.01667 + 0.02 = 0.03667. One step fits, leaving 0.03667 - 0.03333 = 0.00333. 1 step, leftover 0.00333.

Frame 3 (0.08s): accumulator = 0.00333 + 0.08 = 0.08333. First step leaves 0.05, which still fits, so a second step leaves 0.01667, which does not fit a third. 2 steps, leftover 0.01667.

Every physics step used exactly the same fixedDt, no matter how the frame times jittered — the accumulator just carried the leftover fraction of a step forward to the next frame, which is exactly what keeps the simulation frame-rate independent.

That is the numerical-methods toolbox a game programmer actually reaches for: derivatives and integrals give you the vocabulary (rate of change, accumulation), explicit Euler shows the naive approach and why it fails, semi-implicit Euler and Verlet are the stable, cheap defaults that ship in real engines, RK4 is there for the rare case that needs real accuracy, and the fixed-timestep accumulator makes sure all of it runs the same way no matter how fast or slow a frame renders. Keep the two habits from section 11 — never compare floats with ==, and watch precision far from the origin — and you have everything you need to step motion forward safely, frame after frame.

← Back to all chapters