Un-POD plays a race
2026-10-24, the game app's session.

build/release/unpod: the reference race at four times speed, 40 seconds in. The player's GAMMA is last, as it was in the original.
Un-POD is the Rust rebuild of POD. Its pieces so far were libraries: the file formats, and the race rules that count laps and decide the order. This session made the program that ties them together, apps/unpod. It opens your own copy of the game and plays a race from the start lights to the results.
How the original runs a frame
Before building the loop, the session read the original's. When you pick a race, the game looks up the track's row in CIRCUITS.BIN, loads the track file and the race display's font, places each racer's car on the grid with its controller, settles the cars for two seconds and starts the countdown. Every car was loaded once at start-up, so a race only copies them.
Then one function runs the race, one pass per frame. Each pass reads typed keys, runs the simulation steps with the frame time measured on the pass before, adds that time to the race clock, plays the sound, reads the camera and pause keys, measures the next frame time, draws the world and then the HUD. The countdown and the end of the race live in the HUD's code. The order is in docs/systems/main-loop.md, with the address of each call.
The core and the app
Un-POD keeps the same order, split in two. The core (crates/pod-game) does what decides the race and nothing else: no window, no sound, no files. The app sends it commands (the player's controls, pause, quit), runs a pass, and reads back a snapshot: the clock, the countdown, the order, and each car's place, laps, times and position. The physics, opponents, sound and menu sessions agreed this interface, so their crates plug into the same place.
What moves the cars is one interface with a step per physics step. Today it is a recording: the reference race, sampled from the original's memory ten times a second. After "GO!" the rules see the same moves they saw in the race-rules test. The results screen comes out as the original's: Maverick first in 5'36"17, the player last, 2'11"55 behind, best lap Shane's 1'48"63.

The countdown runs on the wall clock, as in the original: five seconds and a half from the first pass, then "GO!" restarts the race clock from zero.
Drawing it
The track, its props and the cars are the viewer's geometry seen from a chase camera. The first window drew them as the viewer does, far faces first. From inside Beltane's tunnels that order breaks: long grey walls covered the road. The fix was to draw in software with a depth buffer at 640 pixels across, the original's width, and show the picture as one texture. The window and the headless frames now come from the same code.

A headless frame (--headless --frames DIR): lap 2, 4'36"79 into the race.
The HUD uses the game's own glyphs from HABILAGE.BP3, the font the race display loads with the track. The layout is Un-POD's for now: the game's HUD function is not traced yet. Each car is placed by its recorded position and faces the way it moves. Which way is a car's front? The steered wheels, 0 and 2, sit at +x in the car file, so +x is forward.

The results screen after the race loop ends: the original's order, times and gaps, and each racer's best lap.
The browser build plays the same race. It fetches the files from a server on your own machine; nothing is bundled.

The menus
Later in the day the front-end session landed the game's menus as a crate. The app hosts them with --menus: the main menu as the game draws it, at 640 × 480, driven by the keyboard and the mouse. By the evening the menus had their track and car screens, so a race starts the way it does in the game: UN JOUEUR, COURSE SIMPLE, a track, a car, OK. Until the physics lands, every race is Beltane's reference race, in the car you chose. The sound landed too: the countdown's beeps and the eight idling engines now play through the sound session's mixer.

A probe for agents
Agents and tests drive the app through a probe, as Un-Banished's are: a command a line on standard input or a local socket, a JSON line back. state, cars and results read the core; input and pause send it commands; run 400 runs passes; shot writes a frame. The probe is an addition the original lacks, so --vanilla turns it off. The tests run the program itself: headless to the original's results, the probe through a whole race.
A race that played too fast
The first window ran one pass per redraw. The countdown looked right, then the race went at six times speed. The rebuilt frames are a tenth of a second each, one per sample of the recording, and each pass imposed one. The countdown was right because it runs on the wall clock. The app now runs as many passes as the race clock owes for the time elapsed.
Next
- Plug in the physics and the opponents as they land, then the player's keys.
- Feed the engines their real input (+0x444) from the physics, and the menus their sounds.
- Trace the game's chase camera and HUD, and draw them as the original does.