Checklist: POD: Planet of Death
Updated: 2026-10-26
Each line: the item, its main original function, and its state: traced (read and documented), decoded (reader consumes every byte of every file), round-trip (writer gives byte-identical files), modded (a changed file loads in the game). Keep it short; details belong in the topic documents.
Format coverage (catalogue, 2026-10-24, all Python decoders): POD, 322 of 423 DATA files decode completely (69% of 423,256,559 bytes); 0 fail; 101 have no Python decoder: track, car and chrome data (.BL3/.BL4 66, .BV3/.BV4 32, CH3/CH4) and NUKE___1.BCH. The Rust readers cover all track and car files.
Inventory
- [x] Every binary identified (
docs/FINDINGS.md, "Binaries") - [x] Data containers identified and listed (
docs/FINDINGS.md, "Data files") - [x] Catalogue running (
./gameframe run catalog target/pod/disc/DATA derived/catalog/pod) - [x] Ghidra export of
WINPOD.EXE(1,725 functions) - [x] Original running where it can be watched:
WINPOD.EXEin a window under Wine, menu and demo race (run/wine/run.sh,docs/running.md, 2026-10-23) - [x] A whole race, menu to results, in the emulated PC (
run/vm, 2026-10-23) - [ ] Sound, CD audio, Indeo videos; record file reads and frames (
re-observe). Videos: WINPOD's player and its Indeo check traced (play_videoFUN_00460570,indeo_installedFUN_00460120,docs/formats/video.md); Indeo 4.1 specified from Intel'sIR41_32.DLL(clean room) and decoded bypod_video::indeo, all 13,663 pictures byte-identical to Intel's emulated codec; GameFrame's codecir41_32.dll(PR 20) plays them through Video for Windows: in the emulated PC (POD_INDEO=1)WINPOD.EXEplays the intro, and the two Direct3D builds play it and reach the menus. Un-POD's video player to wire in (pod_video::player, the app session). Sound traced and its engine measured under the emulator (docs/systems/sound.md); the running game's sound recorded in the emulated PC through PulseAudio - [x] Load a modded file: four mods seen in the game, from
C:\POD\Data\…(docs/modding.md, 2026-10-23)
Foundations
- [x] Block container (
FUN_004694b8): traced / decoded / round-trip, 143 files (docs/formats/container.md) - [x] Entry offsets: raw file positions (measured on all 143 current POD files)
- [x] Name strings (
FUN_00468958): decoded / round-trip, 144 names (docs/formats/strings.md) - [x] The file-open path:
open_data_file(FUN_0047ba70) triesDirectory, then the CD;folder_path(FUN_0047aeb4) has the 14 folders (docs/modding.md);FUN_004ae91cbuilds names inside them
Milestone 1: asset viewer
Order: the shared texture block, images, text, sounds, fonts, then the rest. Each item is done when its content shows in the viewer (viewer/build.py). The catalogue's coverage line counts only files decoded to the last byte, so it grows later, as whole formats are decoded.
- [x] Texture block (
read_texture_block,FUN_0046aca0): round-trip, 707 pages (Block::encode,Block::put);.BV3palette open - [x] Images:
MIMG/*.IMGdecoded completely, 212 files (docs/formats/images.md) - [x] Stills:
VIDEO/*.BBM,CREDITS.BVBdecoded completely and round-trip, 6 files (docs/formats/stills.md,still::File::encode) - [x] Text:
DICOS/*/*.BMDdecoded completely, 15 files (docs/formats/text.md) - [x] Sounds:
RESSON/*.MEG, 133 sounds from 50 banks decode (docs/formats/sounds.md); music is CD audio (CDAREF.TXT,lists.md) - [x] Fonts and effects:
POLICES/*.BP3(19 tables, 691 glyphs),PVRSMOKE/PVRSHADO.BI2(docs/formats/fonts.md);SHADOTIN.BI2open - [x] Best times and lists:
RECORDS/*.BRT,CIRCUITS.BIN,BETAMNU.BIN,HABI.BIN,RESSON/*.TXTdecoded and round-trip, shown as tables (docs/formats/records.md,lists.md) - [x] Project viewer:
viewer/build.pyand the Rust native + wasm viewer show textures, images, stills, text, sounds and fonts (apps/viewer)
Milestone 2: libraries and the native + wasm viewer
See docs/PLAN.md.
- [x] Rust workspace:
pod-container,pixels,pod-formats,apps/viewer(pod-hostwhen a second app needs it) - [x] Port the finished decoders, tested on every file of the player's disc (
crates/pod-formats/tests/disc.rs) - [x] Viewer 2D views, native and wasm (
apps/viewer); sound plays natively only; dropping a disc image tested in the browser (docs/sources.md) - [x] Car viewer (3D model):
apps/viewerwindow andpod-viewer render; wheels placed on all 9 cars; parameter table beside the model and inpod-viewer info(docs/formats/cars.md) - [x] Viewer command line for agents:
list,info,export,render; browser links?data=&file=&set=&yaw=&pitch= - [ ] Track viewer (3D track, sky, racing line): geometry, textures and props drawn (
apps/viewer,pod-viewer render --zoom --focus); sky, racing line and a fly camera to come - [x] Track viewer: sky panorama, clouds and light sprites as pictures; texture animations and animated objects play (
animate,pod-viewer render --time S); racing line and a fly camera to come - [x]
disc-imageandgame-sourcecrates (docs/sources.md): the currentPOD2_0.CUEreads 951 files byte-identical totarget/pod/disc/;pod-viewer --imagelists the same 423 DATA files as the folder;--urldownloads with resume and MD5; the browser reads a dropped.cue+.binor?image=URLin ranges, cached in OPFS. archive.org downloads send no CORS header, so the browser needs a local server or the native download
Hardware: MMX and the 3D cards
- [x] MMX routines of
PODMMX.EXEfound (25) and grouped: mixer, effects, coverage mask, clears, spans (docs/hardware/mmx.md) - [x] Coverage mask
FUN_004dde10measured identical toWINPOD.EXEFUN_004dcd70(300 cases;measure/mmx_coverage.py) - [x] MMX mixer
FUN_004d1460measured against the instruction model (500 cases;measure/mmx_mixer.py) - [ ] Textured spans
FUN_004de580againstFUN_004de3f0, pixel for pixel; the two mixers sample for sample - [x] 3dfx, ATI, S3 builds: imports, which track files, texture handling (
docs/hardware/3d-cards.md) - [x]
.BL416 flags (page halving,FUN_004a422c,FUN_004a4414) and face word (mirror group,FUN_004aa718,FUN_004a54c4) - [ ] S3 texture surface format; how the 3dfx build draws mirrors
- [x] Every Glide call of
WPOD3DFX.EXEand its arguments (docs/hardware/3d-cards.md) - [x] Wrapper layer 1, 3dfx:
wrappers/glide(glide.dllon OpenGL, any output size); the game reaches its menus and a race in the VM - [ ] Wrapper: compare frames with a real Voodoo or a reference Glide (nGlide, dgVoodoo) for the combiner, fog and chroma key; the race resolution setting (
DAT_00651f3c) - [x] Wrapper, ATI and S3 builds:
wrappers/ddraw,wrappers/ati3dcif,wrappers/s3dtkw; both race unchanged at 1280 × 960 (the frame scaled: their chips draw only shadows and beams) - [x] Wrapper: whole-multiple, Scale2x and CRT-style scaling for the DirectDraw builds (
wrappers/scalers) - [ ] Wrapper on POD: the 3dfx (
glide2x.dll) and Direct3D builds run by race-loop in the VM; the PowerVR build (SGL, a scene graph) in a session of its own - [ ] Wrapper:
wrappers/ddrawforWINPOD.EXEandPODMMX.EXE(replacingpatch_winpod.py's display patches); when the S3 build uses its textured face kinds - [x]
mmx,gpu-1997and the wrapper DLLs moved to GameFrame (2026-10-24; POD keepsrun/wrappers/) - [x] Track editor (
apps/viewer, featureeditor): props, a sector's or face's texture, vertices, sectors, a smooth raise across sectors, lights, undo and save; the sameEditfrom the window andpod-viewer edit; byte-identical saves on all 66 current POD track files (docs/modding.md, 2026-10-24) - [x] Track editor on POD: all 66 track files (the 15 new tracks included) save back byte for byte; tests on POD; edited tracks installed into a game folder (
--install,--as NEWwith itsCIRCUITS.BINrow) and raced withunpod --content: Beltane (texture, raised road) and Magnetic as the new track MAGVOLT, the car riding the raised road (docs/modding.md, 2026-10-24) - [x] Track editor: props added and removed (the environment changes size; add then remove gives the original on all 44 POD files with props), beacons and race paths moved as fields (EASY in entry 0, MEDIUM and HARD in entries 1, 2); raced in Un-POD: a lifted lap line counts no lap, a moved racing line moves the computer's cars, added props are drawn (
docs/modding.md, 2026-10-24) - [ ] Track editor next: the animation library's instances, saving in the browser, props for tracks without an environment, the reverse race's beacons and paths
- [x] The viewer's axis convention: tracks are drawn with axis 2 up (measured in the race), car models with axis 2 down (
scene.rs,Projection::up); before 2026-10-24 tracks were drawn mirrored in height, with tunnel roofs under the road - [ ] Renderer profiles in the viewer: show a track as each build drew it (software 8-bit, 3dfx RGB565 with halved pages and mirrors, ATI, S3)
Writers and mods
Every format against the definition of done (AGENTS.md): decoded, round-trip on every file of the disc (crates/pod-formats/tests/writers.rs, disc.rs), modded (a changed file seen in the game, docs/modding.md).
| Format | Files | Decoded | Round-trip | Writer | Modded |
|---|---|---|---|---|---|
| Block container | 143 | yes | yes | pod_container::encode | yes (every mod below) |
| Name strings | 144 names | yes | yes | encode_name | |
Text .BMD | 15 | yes | yes | text::Table | yes: the lap counter (2026-10-23) |
Menu images .IMG | 212 | yes | yes | image::write, Images::put | yes: BELTANE.IMG (2026-10-23) |
Stills .BBM, .BVB | 6 | yes | yes | still::File::encode | |
| Texture blocks | 707 pages | yes | yes | texture::Block | yes: Beltane page 0 (2026-10-23) |
Tracks .BL3, .BL4 | 66 | yes | yes | track::layout (parts kept as bytes, see Formats; Layout::patch for the editor) | yes: BELTANE.BL3 (2026-10-23); a sector's pages, a raised road (BELTANE.BL3) and moved props (ALDERON.BL3) from the track editor (2026-10-24) |
Cars .BV3, .BV4 | 32 | yes | yes | car::write (parameters, garage settings), texture blocks | yes: GAMMA's grip setting (2026-10-23) |
Chrome .CH3, .CH4 | 2 | textures | yes | texture blocks | |
Fonts .BP3 | 7 | yes | yes | Font::encode | |
Effects .BI2 | 3 | yes | yes | Effect::encode | |
Sounds .MEG | 50 | yes | yes | bank::write | |
Best times .BRT | 16 | yes | yes | records::write | |
Lists CIRCUITS, BETAMNU, HABI, .TXT | 7 | yes | yes | circuits, menus, dressing, music | |
Car lists VOITURES, VOITURE2, VOIRESET | 3 | yes | yes | cartable::write | |
Videos .AVI | 6 | RIFF walk, sound and Indeo 4.1 pictures decoded (pod-video): every picture byte-identical to Intel's codec (video.md) | the codec in the game: ir41_32.dll, the intro played by WINPOD.EXE and the Direct3D builds in the emulated PC |
POD 2.8 (current reference since 2026-10-24)
- [x] POD's EXEs exported; the label map to POD (
labels/pod/: 273 of 309 labels; map of 2,258 functions), what changed in the code (docs/versions.md) - [x] POD runs in the emulated PC in English (
run/vm/pod.sh;POD_PATCHESinrun/wine/patch_winpod.py: CPU default, fullscreen flag in code) - [x] Readers on POD: catalogue 322 of 423 DATA files decode completely, 0 fail; Rust disc tests cover the current image's 951 files and all 423 DATA files
- [x] Reference races on POD: Beltane EASY and MEDIUM, Azthec MEDIUM, and Azthec EASY to the results screen (the player's car on the game's own AI after lap 1;
derived/race/pod-*.jsonl) - [x] English menus captured (
derived/ui/pod/ref, client crop at +5+31, steps inderived/ui/pod/seq) - [ ] The DOS-extender builds (
POD*X.EXE,PODMAX64,PODFXM64) exported;PODMMXand the 3D builds checked against POD - [ ] A POD race driven to the results screen by our own scripted driver (it wedged on POD's Beltane and Azthec in a loaded VM; the finished race used the game's AI)
The extra cars and tracks (docs/extras.md, crates/pod-extras)
- [x] Every file of the download's 101 zips read as the game reads it and written back byte for byte (482 files,
crates/pod-formats/tests/extras.rs); the eight the game cannot load listed with the reason - [x] CD Patcher's install traced (
CDPatcher.exe2.0: folders,.bl3or.bl4byDAT_0043569c, the lists);pod-extras installdoes the same, with POD's limits (96 circuits, 32 spare cars) - [x] A manifest of every extra: kind, author, which build loads it, what is installed
- [x] Un-POD races an extra track from a merged
DATAfolder (Hellway, 2026-10-24) - [x] The installed extras in POD's software build, in the emulated PC (race-loop session, 2026-10-24): the track screen's third page lists Beach, Fallen, Hellway, Iceberg and Night with pictures; "Load new car" pages the
VOITURE2.BINcars and swapped in LORIS; a race on Beach with LORIS runs, lap 1 of 6, no crash - [ ] The installed extras in POD's 3dfx build (Tron, CanyOEM), with the wrapper's POD 3dfx run
- [x] Un-POD:
--content DIR(and?content=) reads the installed extras over the disc (game_source::Layered); a track with no.BL3and a car whose.BV3does not load are read from their.BL4/.BV4; Arcade, Tron and the car ED raced live (2026-10-24) - [x] Un-POD's car swap (POD's "Load new car", menu 25) offers the
VOITURE2.BINcars, the extras among them (front-end session, 2026-10-24) - [x] Fan tracks in Un-POD's live race (2026-10-24): every start record sits on a track face (within 3.5 units, as Beltane's within 0.4); with the app session's
.BL4face-word fix Tron's cars stand on the grid and Arcade races past three minutes; Tron, Forest and ECocoon list opponents for HARD only (EASY and MEDIUM are NEANT), so they race alone below HARD, as the data says - [ ] Beach in Un-POD: the grid is right (POD's capture shows the player in a narrow corridor), but Un-POD's own chase camera, 14 units behind the car, ends inside the scenery; the game's camera
FUN_0042b6c0is not ported (app and render sessions) - [ ] CanyOEM and PompOEM in the 3dfx build: what the overrun of
0x463c8cdoes
The race (Un-POD's core)
- [x] Race flow traced (
docs/systems/race.md, 2026-10-23): sessionFUN_0049fba4, loopFUN_0049d4c0, frame clockFUN_004ae894(ms × 65, clamped), steps of at most 3 ×DAT_0060bdfc, countdownFUN_004815e8on the wall clock, beaconsFUN_0042a3acand the track macro actions (FUN_00486004), lap lineFUN_0042c684, checkpointsFUN_0042c3a0, orderFUN_0042c264, endFUN_0042c120,FUN_00479114,FUN_0047831c - [x] Random draws found: Watcom
randFUN_004d1877;srand(1000)at race load, smoke only; none in the AI, whose only chance is the summed step timeDAT_00572458mod 100 (FUN_00462f40,docs/systems/controllers.md); "? Proba Unique" unused on the disc - [x] Race records (
0x502140, 0xBC each) measured against the results screen - [x] Recorder and reader:
run/vm/racerec.py,run/vm/racelog.py, formatdocs/systems/race-recording.md; POD races inderived/race/pod-*.jsonl; the clock's 0.99182 rate and the countdown measured - [x] The controllers ported (
crates/pod-ai;docs/systems/controllers.md): the computer'sFUN_004221d8(neighboursFUN_0046dc04, lane followingFUN_00422028, the 16 behaviour types and their rule drawFUN_0045f1f8, steeringFUN_0046d990, throttle, reset), its lane choice at "Troncon"FUN_004559dc, and the player'sFUN_00415f9c(keyboard and three joystick kinds). Measured: match POD'sWINPOD.EXEunder Unicorn on 2,000 cases per routine and 15,000 steps of eight-car races (measure/controllers.py). - [x] Controllers on POD's running game: EASY and MEDIUM races on Azthec read with
run/vm/ctlsnap.py, 2,100 controller steps each with 51 rule draws, all match the port - [x] Controllers against POD's own results: POD step logs of EASY and MEDIUM races on Beltane, 19,194 controller calls and the draw clock at every step, all match (
measure/controllers_steplog.py) - [ ] Rust live championship: investigate Galleria's lane-following out-of-bounds panic at point 1824 (
docs/systems/controllers.md, "Open") before claiming the championship runs end to end. - [ ] Controllers: the file POD reads the opponents' rules from (
FUN_0045f404, "Fich_Conc") - [ ] Controllers next: "Decision PIT"
FUN_004570ec, the network carFUN_00422408(the reset'sFUN_004aa8a0is pod-race'sPaths::place, wired in 2026-10-24: 137 reset cases match) - [ ] Per-frame inputs (each frame's
dt, the keys) recorded inside the game, then a race replayed twice to show determinism - [x] Race rules ported (
crates/pod-race, 2026-10-24): frame clock and steps, countdown, beacon testFUN_0042a1f0, lap line, checkpoints, order at gates, end timer, results,format_time. Measured: the beacon test, the order update andformat_timematch the original under Unicorn on 20,000 inputs each (measure/race_rules.py); the reference race replayed from its positions matches every sample's gate state and every word of the final records, the order and the results (tests/replay.rs) - [x] The order between gates (2026-10-24): race paths, tracker
FUN_004b0ec0, order updateFUN_004afd3c, "Troncon"FUN_00459680(humans) and its node assignmentFUN_004592b4, incrates/pod-race. Measured: tracker and order update match the original under Unicorn on 20,000 cases each (measure/race_paths.py); the replayed race's order is the game's in 99.4% of samples - [x] The computer cars' lane choice (
FUN_004593fc, driver +0x44):pod_ai::computer::troncon(2026-10-24) - [ ] Placing a car on the paths (
FUN_004af830); the order update's branch for a human in a view - [x] Race rules on POD (2026-10-24): every
pod-racecheck uses POD. Beacon test, order at gates,format_time, tracker and order between gates measured on POD'sWINPOD.EXEunder the game's control word0x027f, 20,000 cases each (derived/measure/race-pod/); the dot product's model corrected to 53-bit rounding. The three POD races (pod-beltane-easy,-medium,pod-azthec-medium) replayed: every car's record at every sample, the final records and order; the mid-lap order check (order_between_gates) on the recorded path state gives the game's order in every pair of samples it can decide. - [x] Going the wrong way on the paths (POD, 2026-10-24): the tracker run backwards
FUN_004ac298and the order update for a car moving backFUN_004ab418, with POD's previous and next path points (FUN_004abc1c,FUN_004abae8). Measured under Unicorn, 20,000 cases each (measure/race_paths.py) - [x] The human's per-frame tracker
FUN_004c688c(POD) and its wrong-way bit (record byte +4, bit 3), with the place searchFUN_004aa8a0, the fold searchFUN_004c6854andFUN_00423bc0(2026-10-24): ported in pod-ai (pod_ai::human_path), measured under Unicorn on 20,000 Beltane and 5,000 Azthec cases (measure/race_paths.py,human) - [x] A POD recording that reaches the results screen (
derived/race/pod-beltane-e2e.jsonl, 2026-10-25); 6,666 samples after "GO!", replayed throughpod-racewith every sampled car record and final records and order matching - [ ] Per-frame human inputs and positions, to replay the human's follower
- [x] Main loop and loading a race traced (
docs/systems/main-loop.md, 2026-10-24): start-upFUN_0049c800(every car loaded byFUN_0049bcd0), a race chosenFUN_004a0414,load_track_select, the racersFUN_0049c13c, and the order of a pass ofFUN_0049d4c0(steps, clock, sound, keys, next frame time, drawing, HUD) - [x] The core assembled (
crates/pod-game, 2026-10-24):Gamewith commands and queries,Carsfor what moves the cars; the reference race replayed through it gives every word of the original's records, the order and the results (tests/reference.rs) - [x] The game app (
apps/unpod, 2026-10-24): the reference race from the countdown to the results screen, natively and in the browser, chase camera, HUD in the game's race font, probe,--vanilla, headless frames; tests run the program - [x] Car physics ported on POD (
crates/pod-physics, 2026-10-24;docs/systems/physics.md): the stepFUN_0041b66cand its callees (gearbox, engine, torque, brakes, air, grip, tyres, springs and sub-steps, the body and wall impacts, track contact by sectors and faces, wheel spin, travel, push-out, stuck timer, sound state), the catch-up scaleFUN_00422be8,FUN_004159d8and the wheels' roll. Measured on a POD race step-logged in the VM (pod-beltane): every function against its calls captured under Unicorn (measure/physics_capture.py, 39 functions); 8,328 of 8,328 car steps match the game word for word, the put-back at step 870 included (FUN_00415730,putback.rs); each car followed on its own until another car touched it (the player and one computer car the whole race). - [x] The step log (
run/vm/steplog.s, 2026-10-24; on POD): every simulation step recorded from inside the game, each car after its controller and after its physics, with the chassis search's counter; the game thread's FPU control word is0x027f(53-bit, round to nearest) - [x] Car-to-car collisions on POD (
cars.rs, 2026-10-24):FUN_0042196c,FUN_0042127cand the hull testFUN_00420548, partsFUN_004209c8, impulseFUN_00420adc/FUN_00420b4c, object framesFUN_004155b8. Measured: every call under Unicorn over the whole POD race (29,148 pairs, 21 contacts, 1,041 passes); the port follows the whole race on its own, all eight cars, word for word - [x] Damage on POD (
damage.rs, 2026-10-24):FUN_00420e54from contacts and wall impacts, the parts' macro chains (FUN_004545a8), the wheels a broken part hurts (FUN_00415318,FUN_00415270,FUN_004152ac). Measured under Unicorn with the option forced on (both modes, parts' health set low): every call, 12 parts broken - [x] Canyon on POD (2026-10-24, step log from race-loop, damage on): all 18,600 car steps match; the port follows the whole race on its own (2,325 steps, jumps, 60 contacts); the several-point impact damage branch confirmed
- [x] The track's props (
props.rs, 2026-10-24): car against propFUN_004215a8, impulseFUN_004214b4, prop against propFUN_00421750, facesFUN_004547a4, motionFUN_00454a98. Measured under Unicorn with Canyon's props placed in the cars' path: every call, 77 hits, 208 whole passes - [x] Props against walls and each other and falling out of the track, and the damage record for the race display (
damage::Record,FUN_0047ed1c,FUN_0047ea74,FUN_0047ec6c), 2026-10-24. Measured under Unicorn on scenes set on Canyon and Beltane: 143 prop motions, 214 passes, 25 record updates - [ ] Physics next: the zones (
FUN_00456dd4); a car put back after 2.5 s upside down; a point with no sector (FUN_0045ccd8);time_scalealone on POD (the whole simulation step already reproduces POD's steps under Unicorn, 104 of 104) - [x] A live race from the disc (2026-10-24): the track world (
crates/pod-world,docs/systems/world.md), each race car from its car file with its garage set-up, on its grid slot, settled (pod_game::start). Measured: every word of the eight race cars after the settling matches the first step of POD's Beltane step log;apps/unpod --livedrives on any track, natively and in the browser - [x] Collisions and damage in the live race (2026-10-24): each car's drawn object built from its car file (
FUN_0046aabc,FUN_004203c8,FUN_00420300;pod_world::object). Measured: every field the collisions and the damage read matches POD's running game for all eight Beltane cars (tests/object.rs); a minute on Beltane gives 78 contacts and 37 damaged parts - [x] The race paths in the disc's world and the trackers placed (2026-10-24):
pod_world::paths(nodes, links, points as POD keeps them), the live race's put-back on them, the cars' race trackers on the path point nearest byFUN_004aa8a0's distance. Measured: the put-back's step matches the running game at the eight drivers' trackers; the eight trackers are the original's at the first step - [x] A gamepad in Un-POD (2026-10-24): gilrs natively, the Gamepad API in the browser, on the keyboard's slots (
apps/unpod/src/pad.rs) - [x] The track's props in the disc's world (2026-10-24):
pod_world::propslays out the.env's global list as POD does (FUN_00468cfc,FUN_0046a780,FUN_00454654); every field the physics reads matches POD's running game on Canyon's 20 props; the live race runs the props' collision pass - [x] A live race to the results (2026-10-24):
--autopilot(an addition) on one lap of Beltane, every car finishing (apps/unpod/tests/app.rs); the driver's +0x7c shared both ways; damage sets and props through the drawing seam - [ ] Live race next: the autopilot's wander on Beltane's last lap;
FUN_004aa8a0's graph search (FUN_004aa5ac,FUN_004aa7a4) for the tracker after a put-back; the damage recordFUN_0047ed1c; the grid draw of the menus; the gamepad's stick as the original's analog joystick (pod-ai's device path) - [ ] The game app with
pod-physics,pod-aiand the player's keys;pod-soundandpod-uihosted; the game's chase camera (FUN_0042b6c0) and HUD (FUN_0047f56c,FUN_0047c470) traced
The front end (Un-POD's menus)
- [x] Menu engine ported (
crates/pod-ui, 2026-10-24): the twelve-button pollFUN_004a2364, the menu loopFUN_00436658(hover, Tab, arrows and their 999 rules, Select press and release, Esc, F1),goto_menu,activate_item,menu_back, picture and list items, the outline (corrections measured for all 503 items,measure/menu_outline.py), the menu fonts, text items and the game's pointer (docs/systems/front-end.md) - [x] Screens matching the original's frames pixel for pixel (
tests/frames.rs, 97 captures): main menu (0), quit confirmation (1), help, race mode (16), track choice (17), car choice (24), pause and its confirmation (32), records (35, without reading the records) - [x] The single race's path to
Effect::StartRace(tests/flow.rs): race mode, track choice (digits and names match; title and map reveal not ported), car choice (OK, arrows),Player1DefaultCircuitandPlayer1DefaultCar; the pause menu without its confirmation - [x]
Ui::race_overand POD's single race end (4): the ranking and best races pages matchpod-results-azthecandpod-best-5-laps-azthec(19 captures checked); the records merge inferred - [x] Against POD's English captures: all 10 captures of ported screens match exactly with the pointer (main menu, one player, options, track pages 1 and 2, car, pause, quit warning, records;
tests/frames.rs), once the captures' presentation (row 478, columns 636 to 638) is applied (check::presented) - [x] POD's car swap (25): handler 44 and actions 220 to 225 traced and ported; OK writes
VOITURES.BINandVOITURE2.BIN(byte-checked for no change); the added cars ofpod-extrasreach the car screen through it (tests/flow.rs) - [x] POD's options (10): handler and actions traced and ported (difficulty, damage, volumes, Dolby, intro, stereo, opponents, credits, OK) with their Ubi.ini writes (
tests/flow.rs); the typed fields not ported - [x] POD's car swap, garage (with its star chart,
0x435e54) and options after a press match their POD captures (14 captures checked; the garage's item 28 left out as open) - [x] POD's garage (26) for one player: settings, typed values, load and save of
Params\<car>.prb(pod_formats::prb, POD's encoded format), the kept set-up intoRaceSetup::settings; the field editor0x42eb80(tests/flow.rs); not checked against a game-written.prbyet (asked of race-loop) - [x] POD's controls (11, 13 for the keyboard) and graphics options (14) match their captures (17 captures checked, the garage's item 28 included:
0x431eb8hides it); key choosing and its Ubi.ini writes not ported - [x] The end-to-end path in pod-ui (2026-10-24): start-up videos, a single race to its end pages and back, the standard championship to its ending videos and back (
tests/flow.rs); championship screens' drawing not ported - [x] POD: championship saves for one player: menus 40 and 45, actions 175, 176, 188,
Data\ChShip\POD<n>.<kind>read and written (docs/formats/chship.md,pod_formats::chship,tests/championship_saves.rs); menus 42 and 43 unreachable - [x] POD's time attack: menu 18 (handler 193, the three types 15, 14, 77, go 256, back 192, choice 68, records 247), the race set-up's laps (3, or 1 for one lap), the race ends 7/3, 6, 5 with their handlers and actions (
tests/time_attack.rs); the "Top 5" ghost panel (155, 156, the ghost slots' sort0x4b39c8, the rows0x4a5d70), action 68's race writes and the racer table's save, restore and players' turns (tests/time_attack/panel.rs,racers.rs); the ghost bundle.ghtreader (docs/formats/ghost.md, round-trip on the extras' two); the end pages 0 (0x4c3264,0x4c3a9c,0x4c40ecwith action 196) and 10 (0x4c5414, the hall of fame) drawn and checked against the code's strings and positions (tests/time_attack_pages.rs, no capture yet). Not ported: the ghost import and export (194, 195), the pages' pictures and reading the track's records - [x] POD: championship menus 21 (the first track's view, from the car screen's OK), 41 (unreachable) and 44 (not in the menu file), menu 60's team pages and the team sorts (
screens/first_track.rs,screens/championship_teams.rs;tests/championships.rs,tests/championship_end.rs); the teams' set-up belongs to the multi-player menus - [ ] POD: multi-player (51, 53, 54)
- [ ] Files the screens write:
Ubi.ini,RECORDS\*.BRT,Params\<car>.prb, byte for byte with the original's - [ ] Menu sounds (
Effect::Sound) with pod-sound; the hosting inapps/unpod
Formats
- [x] Tracks:
CIRCUITS/*.BL3,*.BL4: all 36 files read to the end of every entry, sky, animations, race paths and opponents included (docs/formats/tracks.md); some records kept as bytes - [x] Tracks, the rest (2026-10-23): entry 0 reads to the start of entry 1 and entries 1 to 7 to the next, on all 36 files: animation library, positioned records, sky (horizon panorama, clouds,
.BL4light sprites), texture animations, zones, beacons, start records, race paths per difficulty, the reverse track, opponents (decoders/track.py,crates/pod-formats/src/track/) - [x] Tracks: writer (
track/layout.rs): all 66 write back byte-identical; a part that grows moves entries 1 to 8 and the track still reads; texture pages redrawn and sector vertices moved; modded (BELTANE.BL3page 0) - [ ] Tracks, for the editor: the fields of the parts kept as bytes (zones, beacons, start records, positioned records, props, lights, race paths) written from their fields
- [x] Vehicle creator (
docs/vehicle-creator.md, 2026-10-24):pod-viewer caredits parameters, garage settings, torque curve, a tint (the.BV3through a palette inferred from the.BV4) and the model's scale in both files; unedited saves byte-identical on POD's 16 cars; raced in Un-POD (measure/unpod_follow.py) - [ ] Cars:
VOITURES/*.BV3,*.BV4: decoded to the last read in 32 files; every parameter byte a named field or zero, write-back byte-identical (docs/formats/cars.md); modded: the grip setting driven in the emulated PC (docs/modding.md); the garage settings are the car screen's five figures; the sound slots and the two extra objects open;CHROME.CH3/.CH4textures only - [x] Car lists:
SCRIPTS/VOITURES.BIN,VOITURE2.BIN,VOIRESET.BIN: XOR 0x68, 0x80-byte records, round-trip (cartable.rs) - [x] Images:
MIMG/*.IMG: decoded / round-trip (212 files,image::write) / modded (BELTANE.IMG) - [x] Sounds:
RESSON/*.MEG: loaderFUN_004c1e48traced; all 50 decoded / round-trip (sounds.md); some fields open - [x] Fonts and effects:
POLICES/*.BP3,*.BI2: all 7 decoded to the last byte and round-trip (Font::encoderemakes the leftover buffer,Effect::encode); glyph tables shown in the viewer (fonts.md) - [x] Text:
DICOS/*/*.BMD: decoded / round-trip (15 files,text::Table::encode) / modded (DIC_GAM.BMD, the lap counter) - [x] Lists:
CIRCUITS.BIN,BETAMNU.BIN,HABI.BIN: decoded / round-trip (lists.md); car tables in Cars; menu item fields and display numbers partly unknown - [ ] Best times:
RECORDS/*.BRT(load_records,save_records): traced / decoded / round-trip, 17 files; modded and written in the game open (records.md) - [x] Music lists:
RESSON/*.TXT: traced / decoded / round-trip (FUN_004c4170,FUN_004c45d8,FUN_004c49e0) - [x] Effects
POLICES/*.BI2: all 3 decoded to the last byte,SHADOTINincluded (fonts.md) - [x] Fonts
POLICES/*.BP3: glyph tables (FUN_0046e0e0);FNT_MENUpages were drawn 0x300 bytes early, fixed - [x] Files the game writes:
Params\<car>.prb,Com\modem.anntraced from the code (lists.md); none on the disc - [x] Other files:
VIDEO/*.AVI(RIFF walk),LOADER.EXE(PE layout) for the catalogue
Sound (crates/pod-sound, docs/systems/sound.md)
- [x] Output: waveOut 22,050 Hz 16-bit stereo, 100 blocks of 880 bytes, 25 ahead (
FUN_004c5bf0,FUN_004c7605): traced - [x] Mixer
FUN_004df88aand gainFUN_004df859: traced, measured, ported sample for sample (400 cases,measure/sound_mixer.py) - [x] Channels: play, update (pitch glide), stop, pause, swap, the ten mixed (
FUN_004c70d8,FUN_004c6db8,FUN_004c5974…): measured, ported (300 sequences,measure/sound_channels.py) - [x] Sounds in space
FUN_004bf5c8(distance, Doppler, pan, send) and the lengthFUN_004db73d: measured, ported (5,000 cases each,measure/sound_space.py) - [x] Events, the active list, the frame update and the engine groups (
FUN_004bfa5c,FUN_004c00f0,FUN_004bfd2c): measured on FIX.MEG, ported (150 cases,measure/sound_events.py) - [x] Missing POD car events 21/22 distinguished from zero-filled event 0; Un-POD skips the stale car slots that otherwise start the looped tyre squeal (
FUN_00418010,FUN_004bfa5c,tests/events.rs; intentional difference indocs/DEVIATIONS.md) - [x] Music: scripts and CD calls (
FUN_004c50a8,FUN_004c51ec,FUN_004c31e0); track 2 read from the player's image (cdda.rs) - [x] PODMMX's mixer compared with WINPOD's (
measure/sound_mmx.py,docs/hardware/mmx.md) - [x] Playback outside the core: cpal natively and Web Audio (
playback.rs, featureplayback);examples/scene.rs - [ ] The triggers in the code that fires them: the countdown (
FUN_004815e8, unpod-app), the car and collision sounds (pod-physics), the menus and the CD sequence (pod-frontend), the zone sounds (FUN_0049dc98) - [x] Compared with the running game: the menu sound recorded in the emulated PC through a PulseAudio null sink (
inside.sh sound,measure/sound_recorded.py): gain 1.00006, 94% of samples within ±1 - [x] A race recorded the same way, with the step log, compared with pod-sound's replay: all six countdown voices at one delay and gain 0.96–1.00; the engines' spectra follow the step log (
examples/race.rs,examples/compare.rs) - [ ] A race sample for sample: frame boundaries in the step log, the car code's events
- [x] CD audio in the emulated PC: CDEmu plays the image's track 2 (
inside.sh cdemu); the original starts it with the countdown of a race, never in the menus - [x] Where the CD music starts and stops: the track's loudspeakers (
.hp,+0x34) hold event 20; "Depart" (FUN_0049f75c) plays them through "Son Localisé"FUN_00475b18, "FinCourse" (FUN_0049fba4) stops them through "Tue Sons Local"FUN_00475bbc; on 29 of POD's 32 tracks (pod_sound::speakers,tests/speakers.rs); Un-POD runs both
Renderer (crates/pod-render, docs/systems/render.md)
- [x] The frame's draw order traced (
draw_frame_viewsFUN_0049af38): views, world, cars, sky,clear_uncovered, overlays - [x] The tables of
FUN_00495b14(perspective, reciprocal, multiply) ported; measured identical under Unicorn at control words 0x37f and 0x27f - [x] The shade table row
FUN_004965b4ported; measured, 800 cases - [x] The coverage mask
FUN_004d59e0,FUN_004d5a60,FUN_004d5b2c(tests row i + 1),FUN_004d5ba2ported; measured, 800 cases - [x] Textured spans kind 0x60
FUN_004d7120and kind 0x61FUN_004d7c3d, perspective and affine, with the x87 rounding; measured byte for byte, 4,000 span lists each - [x] Where the 8-bit car textures get their palette: each
.BV3carries its own, and the 16-bit software build remaps the pages toCHROME.CH3's palette in slot 4 (FUN_00465774,FUN_0049d2cc, measured);apps/unpoddraws its cars that way - [x] The drawing thread's FPU control word: 0x27f (measured in a recorded POD frame)
- [x] A recorded frame's span calls replayed through the port against the frame itself: 271,467 of 272,446 pixels the same; the other 979 are overwritten after the spans (704 black by
clear_uncovered; 275 of 0x0820 by something not yet found, not the horizon) - [x] The horizon: span routine
FUN_004d81baand passFUN_00495804(the BkGround panorama, drawn after the clear and under the world); measured byte for byte, 4,000 span lists and 400 list blocks - [x] Horizon builder
FUN_00490544and fillerFUN_004951ec; measured on 1,500 camera and band cases - [x] Both Un-POD renderers draw the enabled track horizon behind the world using measured
draw_horizonFUN_00495804/span_horizonFUN_004d81baover the measuredbuild_horizonblock; inferred frame wiring fromdraw_frame_viewsFUN_0049af38; the app camera is still a stand-in - [x] POD's 16-bit cloud pass traced from track setup through
span_cloud; cloud words measured on all 33.BL3tracks (docs/systems/render.md, "The clouds") - [x]
span_cloudFUN_004d6900measured against POD 2.8 and ported topod_render::span::cloud; 2,081 cases, 8,681 rows, byte for byte (measure/render_cloud_spans.py,tests/cloud.rs) - [x]
fill_cloud_spansFUN_00494ee8withfill_cloud_triangleFUN_00494c00measured against POD 2.8 at control word 0x27f: 4,264 synthetic cases, counts 3..8, every y ordering × winding, return, arena, group bytes and mask (measure/render_cloud_fill.py,derived/measure/cloud-fill/) - [x]
fill_cloud_spansFUN_00494ee8/fill_cloud_triangleFUN_00494c00ported topod_render::fill::cloud; Rust test replays all 4,264 POD 2.8 cases, including the whole arena, mapped vertices and coverage mask (tests/cloud_fill.rs;derived/measure/cloud-fill/cases.bin) - [x] Measure cloud geometry and span lists:
draw_cloud_layer787 cases,cloud_clip_edge1,317 cases, anddraw_cloud_spanson 46 generated blocks; two runs byte-identical (measure/render_cloud_layer.py,derived/measure/cloud-layer/) - [x]
draw_cloud_layerFUN_0048c8f4,cloud_clip_edgeFUN_0048dd80anddraw_cloud_spansFUN_004950d4ported topod_render::sky::cloud(16-bit build, one view);tests/cloud_layer.rsreplays all 787 layer cases (34 divide faults at 0x48d240 returned as faults, 102 carried sequence frames), 1,317 clip cases (12 faults at 0x48de30) and 46 draw-pass frames byte for byte: block, whole arena, mapped vertices, mask, globals, strips and slices - [x] Wire the measured 16-bit cloud layer into both Un-POD backgrounds: clouds over the clear, transparent horizon over clouds, world over both; track words/page/palette and persistent scroll supplied. Inferred frame wiring with the stand-in camera and empty coverage mask; known divide faults skip that cloud frame. Indexed
.BL3only;Ubi.inioverride remains open. - [x] Sun flare traced in its machine instructions (POD 2.8
WINPOD.EXE): markerFUN_0048c100, two spike passes and the octagon inFUN_0048c248(R = 25,000 or 50,000, turns π/2 and π/4, pass-1 scale 20,000/65,536, light scales 64,000 and 62,500),clip_polygonbl 0x20,fill_polygon_spansebx 0x20 ecx 1,draw_shade_spansedx 0x80000000 to the whiten drawers (docs/systems/render.md, "The sun") - [x]
place_sun_marker/draw_sun_flaremeasured at control word 0x27f: 94 synthetic cases, 80 valid markers with 24 triangles each, 14 failures unchanged; both screen widths, four 16-bit formats, dither off/on, complete frames and per-triangle traces (measure/render_sun_flare.py,derived/measure/sun-flare/) - [x] Sun marker
FUN_0048c100, flareFUN_0048c248, the whiten/dither drawers and whiten table rows 0..31 ported topod_render::sun(16-bit, one view, formats 0..3);tests/sun_flare.rsreplays all 94 cases byte for byte (frames, marker, 24 triangles, span lists, tables) - [x] Place the measured sun marker in the cloud pass before horizon drawing, then draw the dither-off F0/RGB565 flare after world faces in
pod-render; focused tests cover marker placement and world occlusion. The stand-in flare, dithered flare, split screen and 8/32-bit marker offsets remain not ported; full-frame parity is unverified. - [x] The polygon filler
FUN_004931ec(withFUN_00492e34and the edge helpersFUN_004d7064,FUN_004d6f20,FUN_004d6f86,FUN_004d6ff8); measured, 1,500 polygons - [x] The polygon clipper
FUN_00491b84(four passes against the view's rectangle0x62138c..0x621398), the perspective cutFUN_00491738with its reciprocal table0x57af5c, the clampFUN_00491b14; measured, 10,000 polygons - [x] Transform, projection, sphere cull, z-plane clip, quad face loop and flat vertex light; measured on 3,000 objects and 61,543 vertices (five distance keys differ at 0x37f because
pod-aiuses 53-bit vector length) - [x] The fan filler
FUN_0049493c(withFUN_0049461c,FUN_00494498,FUN_004945a0) for faces of kind 0x40/0x60 without flag 4, textured affine triangles with one set of steps each; measured, 1,500 polygons (pod_render::fill::flat) - [x] POD's flat-colour and Gouraud drawer paths measured through their wrappers: 700 synthetic cases across 320/640 widths, frame alignments, edge shapes, colours, lights and reciprocal widths; complete frame outputs and shade globals agree with the instruction-based model (
measure/render_color_faces.py,derived/measure/color-faces/) - [x] Port
draw_flat_spansFUN_00492f9cwithspan_solidandspan_checker, and the ordinary Gouraud path throughdraw_shade_spansFUN_00492fec/span_shadeFUN_004d4f00; 700 fixture cases replay byte for byte (crates/pod-render/tests/color_faces.rs) - [x]
draw_gradient_spansFUN_00493070,span_gradientFUN_004d6af6, and keyedspan_gradient_keyedFUN_004d6ceeported topod_render::span::gradient; 240 full-frame POD 2.8 fixtures match byte for byte (120 ordinary, 120 keyed). - [x] Connect the measured affine fan and clipped row-span fill paths to Un-POD's face flags; preserve source visibility, keyed-texel and two-sided bits. The app still uses split triangles and float camera math (
docs/systems/render.md, "In Un-POD"). - [ ] Compare both routes against a matched POD track, camera and frame to determine whether the observed roof gaps are fixed or have another cause.
- [ ] Triangle face loop
FUN_00458cc8and lit vertex sources (FUN_004262c8,FUN_00425ccc,FUN_004d4770,FUN_004d465d) - [ ] Connect the measured transform and clipper to
apps/unpod, then compare complete 640 × 480 frames with POD