Lane Dodger
A three lane endless runner, built the way this guide builds everything: no engine, no assets, nothing hiding the interesting part. The game is on this page and you can play it now. What the chapters are actually about is how to write a game you can test, because the usual answer is that you cannot, and the usual result is a game whose difficulty nobody can reason about. The simulation here imports nothing at all, so every rule is checked by a program that plays the game, and the graphics and the sound are both things that happen to it afterwards.
raylib is vendored from its C sources at a pinned commit rather than used as a Zig package, and the chapters explain why. The browser build needs the Emscripten SDK, which CI does not carry, so the playable artefacts on this page are built by hand and committed.
- A game loop that takes a
dtis a game that plays differently on every machine. A fixed timestep with an accumulator costs ten lines and buys reproducible runs. - Game logic that imports no graphics library can be tested by playing it. The rules here are exercised by a bot at every seed, in about a second, with no window open.
- A difficulty curve should be derived from what is physically possible, not tuned until it feels right. Tuned numbers stop agreeing with each other the first time one of them moves.
- Handles beat pointers for anything spawned and destroyed constantly: a stale index silently reads its slot's next occupant, and a generation counter turns that into a null.
- Sound effects can be a few hundred lines of arithmetic rather than a folder of files, and waveforms are as testable as anything else.
10 chapters.
- A Game You Can TestA three lane endless runner you can play here, and the one rule that makes the rest testable.
- Vendoring raylibHow to add raylib to a Zig project, why this one depends on the C source, and how the same game reaches a browser canvas.
- The WorldA field measured in its own units, a player that slides between lanes, and the order the tick does things in.
- The Loop Takes No dtA fixed timestep, an accumulator, interpolation for the leftover, and the split needed for the browser build.
- Handles, Not PointersA fixed-capacity pool with generational handles, plus the event stream used by particles and sound.
- Drawing ItField units mapped onto a window, the road, the entities, and a particle system that holds no raylib types.
- InputPresses latched once per frame and handed to exactly one tick, and why a held key must not cross the board.
- Difficulty You Can ProveRow spacing derived from what is physically possible, a bot that proves the course is solvable, and a reaction time model for playability.
- Sound Without Sound FilesFive effects synthesised at startup from oscillators and envelopes, with waveform tests that run without an audio device.
- Build It From ScratchThe order to write the files in, the two points where you can stop and check, and what to do when it does not build.