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.
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.
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.
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.
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.
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.
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 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.
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:
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.
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:
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.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.
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:
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.
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:
#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.
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.
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:
#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.
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.
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.
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.
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.
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.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.
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.3 — 0.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.
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 all — 0.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.
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.dt, regardless of how long a rendered frame actually took.(0,0,0).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.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.
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?(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.
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.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.