Skip to content

Race recordings ​

Ground truth for Un-POD's race core: races of the original WINPOD.EXE, recorded from its memory in the emulated PC (run/vm). Recorder: run/vm/racerec.py (inside the VM, as root). Reader: run/vm/racelog.py (on the host). Recordings stay in derived/race/ (ignored by git). What each value means is in docs/systems/race.md.

Format pod-race-1 ​

JSON Lines. Every number is the game's own word: 16.16 values are integers (divide by 65536), nothing is rounded.

Line 1, the header:

KeyWhat
format"pod-race-1"
rate_hz10: samples a second, on the VM's wall clock
startUnix time of the first sample

Then one line per sample:

KeyAddressWhat
tseconds since the first sample (wall clock)
clock0x502740race clock, 16.16 s
dt, dt_sum0x60be0c, 0x60be10the last frame's time and the running sum, 16.16 s
step0x60bdfcthe step unit
phase, countdown0x5920a4, 0x5920a0countdown phase (0 racing) and number shown (-1 after "GO!")
ending, end_timer, end0x50274c, 0x502754, 0x502750the end of the race
finished, humans_finished0x502748, 0x502749counters
humans, computers, laps, checkpoints0x61d5bc, 0x61d5c0, 0x61d5c8, 0x61d5d4the race's settings
pos0x61aad0 + n × 0x5588 × [x, y, z], 16.16 units; z is the height
rec0x502140 + n × 0xbc8 × [byte +4, byte +8, +0x10, +0x14]: place and flags, checkpoint and lap-line crossings, clock at the last lap line, clock at the last checkpoint
order0x5027208 racer numbers, first place first
chassis0x61aad0 + n × 0x558 + 0x3608 × [x, y, z]: the chassis point the beacon test uses (added 2026-10-24; not yet recorded)
track_pos+0x4988 × [x, y, z]: the point the path tracker uses (added 2026-10-24)
path0x61f870 + n × 0xb08 × [link from 0 (or -1), point, flags +0x38, lane +0x44]: each driver's path tracker (added 2026-10-24)

The last line, read three seconds after the race loop ended (after FUN_0047831c has driven any unfinished car to the line):

KeyWhat
final_recordsthe 8 race records, 8 × 0xBC bytes as hex: every lap and split
final_orderthe final order

Screenshots (--shots DIR) are taken whenever a lap count changes and at the end, for the times as the game prints them.

On POD every car's lap times add up to its race time and the HUD shows the same times (measured), so the race record keeps its layout and 0xbc stride; only the addresses moved (run/vm/podaddr.py). From 2026-10-24 the header (and racefinal.py's last line) also holds the drivers' names, read from the menus' entries (POD 0x6338f4, stride 0xdc).

Use ​

bash
run/vm/vm.sh ssh 'sudo python3 ~/share/racerec.py /tmp/race.jsonl 900 --shots /tmp/race-shots'
bash
python3 run/vm/racelog.py derived/race/pod-beltane-easy.jsonl
bash
python3 run/vm/racelog.py derived/race/pod-beltane-easy.jsonl --csv 1

Screenshots (--shots) block the recorder while import runs: each costs about 0.7 s without samples (measured: gaps in beltane-3 as the cars cross the lap line after "GO!"). dt is read whenever the sample lands, so it can hold a sub-step's length or the physics step's scaled value instead of the frame's (measured: 842 and 1383, not multiples of 65); dt_sum is the frame times' own sum.

A sample is not one instant: the words are read one after another while the game runs, so a sample can straddle a frame (measured: one sample shows phase 0 with the clock not yet zeroed). Sampling at 10 Hz from outside the process sees about one frame in eight: positions and counters are snapshots, not every frame. The exact per-frame inputs a port needs (each frame's dt and the human's keys) need a hook inside the game (re-observe); dt_sum bounds them between samples.

The order of the reads matters for a replay (measured while replaying beltane-3 through crates/pod-race): the globals (the clock first) are read before the cars' positions, and the positions before the records. A sample's records can therefore hold a crossing counted after its clock (in one sample of beltane-3, 0.57 s of game time later, while the VM was busy), and its positions can be a frame behind its records. Two samples can show the same clock. The positions are car+0; the beacon test uses the chassis point car+0x360, which a future recording should add.

Recordings ​

FileWhat
derived/race/beltane-1.jsonlBeltane, three laps, one human (scripted, follow.py) and seven computer cars, 2026-10-23. Recorded passively during another session's run, which had a text mod loaded (the lap label reads "MOD"; text only). Starts at race clock 1'13"88, 3,990 samples to the end; frames of 10 to 148 ms (the VM was busy). Final records: Raze (racer 3) 5'33"18, then Shane, Maverick, Ken-ichi, Marco, Slate, Diamond, the player +2'32"87; best lap Raze 1'46"40. Every time matches the results screen (measured). Screenshots at each lap change in beltane-1-shots/
derived/race/beltane-2.jsonlThe same race set-up, the same text mod, recorded from the countdown (racewait.sh starts racerec.py when the phase becomes 2): 4,713 samples over 497.5 s, frames of 11 to 261 ms. Maverick (racer 1) 5'34"18, then Marco +16"94, Ken-ichi +17"14, Slate, Shane, Diamond, Raze, the player +2'31"32; best lap Maverick 1'47"35
derived/race/beltane-3.jsonlThe stock reference race: no mod files (the game reads the disc only), GAMMA at the disc's settings, difficulty FACILE, Beltane, three laps, one human (follow.py on the recorded line) and seven computer cars, 2026-10-23. From countdown "4" to the results: 4,535 samples over 479.4 s, frames of 12 to 210 ms. Maverick 5'36"17, Shane +05"22, Marco +19"63, Ken-ichi +21"32, Diamond +23"48, Slate +38"27, Raze +38"49, the player +2'11"55; best lap Shane 1'48"63, the player's 2'29"09. Every line matches the results screen (measured)
derived/race/pod-beltane-easy.jsonlPOD 2.8, the reference from 2026-10-24: Beltane, EASY, three laps, English, run/vm/pod.sh on display :4, recorded from the countdown (racewait.sh) for 1,200 s. The seven computer cars finish: racer 2 in 5'34"96, racer 1 +0"92, racer 3, 6, 7, 4, 5; best lap racer 1, 1'48"16. The player (follow.py) wedged against a wall and did not finish. Frames of 12 to 462 ms (a loaded VM). Final records at the 1,200 s limit
derived/race/pod-beltane-medium.jsonlPOD, Beltane, MEDIUM (POD_DIFFICULTY=Normal), from the countdown. The player's car stays on the grid; the race is left when the seven computer cars are home, and racefinal.py adds the final records. Racer 7 in 5'10"37, then 6, 4, 3, 1, 5, 2; best lap racer 5, 1'37"32
derived/race/pod-azthec-medium.jsonlPOD, Azthec (a track new in POD), MEDIUM, five laps, as above; driver names in the file. Lazer in 5'26"15, then Luigi +12"29, Chris, Blaze, Warp, Viper, Jackal; best lap Lazer, 1'04"17. Three checkpoints a lap
derived/race/pod-azthec-easy.jsonlA whole POD race to the results screen: Azthec, EASY, five laps. The recorder was started 23 s after "GO!". The scripted driver wedged on a ledge in lap 1, so at race clock 291 s run/vm/autopilot.py gave the player's car the game's own AI controller (Deviation: the writes POD's driver_set_ai makes, into racer 0's driver: +0x0c = 0x4221d8, flags bit 1, +0x40 = 2, +0x68 = 0x45f1a0). From there the car drove itself to the line. The player finished at race clock 1080.4 s; ending became 1 with end_timer 3.0 s, and the race loop ended 3.5 s later (measured). Results: Maverick 5'56"83, Shane +5"85, Ken-ichi +18"44, Raze +21"41, Marco +25"11, Slate +32"92, Diamond +40"50, Player 1 +12'03"57; best lap Maverick 1'09"89, the player's best 2'10"23. Every line matches the results screen (derived/ui/pod/ref/pod-results-azthec.png). POD's results screen is followed by "ALL TIME BEST 5 LAPS" for the track

beltane-1 and beltane-2 were the mods session's runs: text, image and texture mods loaded, the car's grip at the disc's value. Measured: the player's best laps, 2'33"38 and 2'37"39, are the two grip-95 runs in docs/modding.md ("Grip"). The physics and the rules were the disc's.

Racers in the first edition's races: 0 the player, 1 Maverick, 2 Shane, 3 Raze, 4 Marco, 5 Diamond, 6 Ken-ichi, 7 Slate (measured: four results screens).

Prose CC0 1.0, code Apache 2.0. POD: Planet of Death is © Ubi Soft; none of its files are on this site.