Advanced

A fixed-step machine for deterministic rules

Time-based updates are right for feel and wrong for rules that must agree across machines. The fixed-step machine runs the rules in equal slices and carries the leftover time, so the same inputs produce the same states on any machine.

What you will see

Five steps, then the accumulator reset. Without the reset, the next frame runs five more and the game freezes.

About 1 min of reading

Try It

Convert a loop to fixed steps

1

Write the accumulator

printf 'const SLICE = 1 / 60; let acc = 0; function frame(dt) { acc += dt; } ' | tee game/fixed.js

Before you run it, say out loud what it will print. Then open the answer.

What it prints
The gap is added, not used directly. Nothing steps the rules yet.
2

Run whole slices

printf ' let n = 0; while (acc >= SLICE && n < 5) { step(w, SLICE); acc -= SLICE; n++; } if (acc > SLICE * 5) acc = 0; ' | tee -a game/fixed.js

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Equal slices, capped at five per frame, with the leftover kept.
3

Prove the same states

run the loop with frame gaps of 0.016 and with gaps of 0.033 and print the state after one second of total time

Before you run it, say out loud what it will print. Then open the answer.

What it prints
The same state both times. A time-based update would differ, which is the bug this fixes.
4

Test the cap

call frame with a gap of 3 seconds and count the steps run

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Five steps, then the accumulator reset. Without the reset, the next frame runs five more and the game freezes.
Do not let the accumulator grow unbounded. A long stall then becomes an unbounded catch-up loop that hangs the tab.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

Elapsed time to whole steps
1 Add the frame gap to an accumulator
↓
2 While a whole slice remains, step the rules
↓
3 Subtract the slice each time
↓
4 Leave the remainder for the next frame
Why this works
Where the slice size matters

A slice shorter than the fastest movement still catches collisions. A slice longer than a wall thickness tunnels, so the slice is a property of the level geometry.

What stays time-based

Presentation only: particles, camera smoothing, animation. Those read the leftover time as a fraction and never affect the state.

Examples

Equal slices

The rules always receive the same dt. The number of calls varies with the machine, the values per call do not.

Carry the remainder

Accumulate the elapsed time and run steps while there is a whole slice left. The leftover stays for the next frame, so time is never lost.

Bound the catch-up

A long stall would run hundreds of steps to catch up. Cap the steps per frame and drop the rest, so a stall is a hitch and not a freeze.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Tuning numbers so the game feels right
Previous Lesson
Tuning numbers so the game feels right
Next Lesson
Finding collisions without testing every pair
Finding collisions without testing every pair →