Skip to content

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.EXE in 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_video FUN_00460570, indeo_installed FUN_00460120, docs/formats/video.md); Indeo 4.1 specified from Intel's IR41_32.DLL (clean room) and decoded by pod_video::indeo, all 13,663 pictures byte-identical to Intel's emulated codec; GameFrame's codec ir41_32.dll (PR 20) plays them through Video for Windows: in the emulated PC (POD_INDEO=1) WINPOD.EXE plays 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) tries Directory, then the CD; folder_path (FUN_0047aeb4) has the 14 folders (docs/modding.md); FUN_004ae91c builds 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); .BV3 palette open
  • [x] Images: MIMG/*.IMG decoded completely, 212 files (docs/formats/images.md)
  • [x] Stills: VIDEO/*.BBM, CREDITS.BVB decoded completely and round-trip, 6 files (docs/formats/stills.md, still::File::encode)
  • [x] Text: DICOS/*/*.BMD decoded 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.BI2 open
  • [x] Best times and lists: RECORDS/*.BRT, CIRCUITS.BIN, BETAMNU.BIN, HABI.BIN, RESSON/*.TXT decoded and round-trip, shown as tables (docs/formats/records.md, lists.md)
  • [x] Project viewer: viewer/build.py and 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-host when 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/viewer window and pod-viewer render; wheels placed on all 9 cars; parameter table beside the model and in pod-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-image and game-source crates (docs/sources.md): the current POD2_0.CUE reads 951 files byte-identical to target/pod/disc/; pod-viewer --image lists the same 423 DATA files as the folder; --url downloads with resume and MD5; the browser reads a dropped .cue + .bin or ?image=URL in 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.EXE found (25) and grouped: mixer, effects, coverage mask, clears, spans (docs/hardware/mmx.md)
  • [x] Coverage mask FUN_004dde10 measured identical to WINPOD.EXE FUN_004dcd70 (300 cases; measure/mmx_coverage.py)
  • [x] MMX mixer FUN_004d1460 measured against the instruction model (500 cases; measure/mmx_mixer.py)
  • [ ] Textured spans FUN_004de580 against FUN_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] .BL4 16 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.EXE and its arguments (docs/hardware/3d-cards.md)
  • [x] Wrapper layer 1, 3dfx: wrappers/glide (glide.dll on 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/ddraw for WINPOD.EXE and PODMMX.EXE (replacing patch_winpod.py's display patches); when the S3 build uses its textured face kinds
  • [x] mmx, gpu-1997 and the wrapper DLLs moved to GameFrame (2026-10-24; POD keeps run/wrappers/)
  • [x] Track editor (apps/viewer, feature editor): props, a sector's or face's texture, vertices, sectors, a smooth raise across sectors, lights, undo and save; the same Edit from the window and pod-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 NEW with its CIRCUITS.BIN row) and raced with unpod --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).

FormatFilesDecodedRound-tripWriterModded
Block container143yesyespod_container::encodeyes (every mod below)
Name strings144 namesyesyesencode_name
Text .BMD15yesyestext::Tableyes: the lap counter (2026-10-23)
Menu images .IMG212yesyesimage::write, Images::putyes: BELTANE.IMG (2026-10-23)
Stills .BBM, .BVB6yesyesstill::File::encode
Texture blocks707 pagesyesyestexture::Blockyes: Beltane page 0 (2026-10-23)
Tracks .BL3, .BL466yesyestrack::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, .BV432yesyescar::write (parameters, garage settings), texture blocksyes: GAMMA's grip setting (2026-10-23)
Chrome .CH3, .CH42texturesyestexture blocks
Fonts .BP37yesyesFont::encode
Effects .BI23yesyesEffect::encode
Sounds .MEG50yesyesbank::write
Best times .BRT16yesyesrecords::write
Lists CIRCUITS, BETAMNU, HABI, .TXT7yesyescircuits, menus, dressing, music
Car lists VOITURES, VOITURE2, VOIRESET3yesyescartable::write
Videos .AVI6RIFF 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_PATCHES in run/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 in derived/ui/pod/seq)
  • [ ] The DOS-extender builds (POD*X.EXE, PODMAX64, PODFXM64) exported; PODMMX and 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.exe 2.0: folders, .bl3 or .bl4 by DAT_0043569c, the lists); pod-extras install does 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 DATA folder (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.BIN cars 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 .BL3 and a car whose .BV3 does 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.BIN cars, 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 .BL4 face-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_0042b6c0 is not ported (app and render sessions)
  • [ ] CanyOEM and PompOEM in the 3dfx build: what the overrun of 0x463c8c does

The race (Un-POD's core) ​

  • [x] Race flow traced (docs/systems/race.md, 2026-10-23): session FUN_0049fba4, loop FUN_0049d4c0, frame clock FUN_004ae894 (ms × 65, clamped), steps of at most 3 × DAT_0060bdfc, countdown FUN_004815e8 on the wall clock, beacons FUN_0042a3ac and the track macro actions (FUN_00486004), lap line FUN_0042c684, checkpoints FUN_0042c3a0, order FUN_0042c264, end FUN_0042c120, FUN_00479114, FUN_0047831c
  • [x] Random draws found: Watcom rand FUN_004d1877; srand(1000) at race load, smoke only; none in the AI, whose only chance is the summed step time DAT_00572458 mod 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, format docs/systems/race-recording.md; POD races in derived/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's FUN_004221d8 (neighbours FUN_0046dc04, lane following FUN_00422028, the 16 behaviour types and their rule draw FUN_0045f1f8, steering FUN_0046d990, throttle, reset), its lane choice at "Troncon" FUN_004559dc, and the player's FUN_00415f9c (keyboard and three joystick kinds). Measured: match POD's WINPOD.EXE under 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 car FUN_00422408 (the reset's FUN_004aa8a0 is pod-race's Paths::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 test FUN_0042a1f0, lap line, checkpoints, order at gates, end timer, results, format_time. Measured: the beacon test, the order update and format_time match 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 update FUN_004afd3c, "Troncon" FUN_00459680 (humans) and its node assignment FUN_004592b4, in crates/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-race check uses POD. Beacon test, order at gates, format_time, tracker and order between gates measured on POD's WINPOD.EXE under the game's control word 0x027f, 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_004ac298 and the order update for a car moving back FUN_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 search FUN_004aa8a0, the fold search FUN_004c6854 and FUN_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 through pod-race with 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-up FUN_0049c800 (every car loaded by FUN_0049bcd0), a race chosen FUN_004a0414, load_track_select, the racers FUN_0049c13c, and the order of a pass of FUN_0049d4c0 (steps, clock, sound, keys, next frame time, drawing, HUD)
  • [x] The core assembled (crates/pod-game, 2026-10-24): Game with commands and queries, Cars for 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 step FUN_0041b66c and 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 scale FUN_00422be8, FUN_004159d8 and 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 is 0x027f (53-bit, round to nearest)
  • [x] Car-to-car collisions on POD (cars.rs, 2026-10-24): FUN_0042196c, FUN_0042127c and the hull test FUN_00420548, parts FUN_004209c8, impulse FUN_00420adc/FUN_00420b4c, object frames FUN_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_00420e54 from 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 prop FUN_004215a8, impulse FUN_004214b4, prop against prop FUN_00421750, faces FUN_004547a4, motion FUN_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_scale alone 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 --live drives 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 by FUN_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::props lays 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 record FUN_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-ai and the player's keys; pod-sound and pod-ui hosted; 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 poll FUN_004a2364, the menu loop FUN_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), Player1DefaultCircuit and Player1DefaultCar; the pause menu without its confirmation
  • [x] Ui::race_over and POD's single race end (4): the ranking and best races pages match pod-results-azthec and pod-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.BIN and VOITURE2.BIN (byte-checked for no change); the added cars of pod-extras reach 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 into RaceSetup::settings; the field editor 0x42eb80 (tests/flow.rs); not checked against a game-written .prb yet (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: 0x431eb8 hides 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' sort 0x4b39c8, the rows 0x4a5d70), 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 .ght reader (docs/formats/ghost.md, round-trip on the extras' two); the end pages 0 (0x4c3264, 0x4c3a9c, 0x4c40ec with 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 in apps/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, .BL4 light 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.BL3 page 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 car edits parameters, garage settings, torque curve, a tint (the .BV3 through 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/.CH4 textures 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: loader FUN_004c1e48 traced; 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::encode remakes 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, SHADOTIN included (fonts.md)
  • [x] Fonts POLICES/*.BP3: glyph tables (FUN_0046e0e0); FNT_MENU pages were drawn 0x300 bytes early, fixed
  • [x] Files the game writes: Params\<car>.prb, Com\modem.ann traced 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_004df88a and gain FUN_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 length FUN_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 in docs/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, feature playback); 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_views FUN_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_004965b4 ported; measured, 800 cases
  • [x] The coverage mask FUN_004d59e0, FUN_004d5a60, FUN_004d5b2c (tests row i + 1), FUN_004d5ba2 ported; measured, 800 cases
  • [x] Textured spans kind 0x60 FUN_004d7120 and kind 0x61 FUN_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 .BV3 carries its own, and the 16-bit software build remaps the pages to CHROME.CH3's palette in slot 4 (FUN_00465774, FUN_0049d2cc, measured); apps/unpod draws 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_004d81ba and pass FUN_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_00490544 and filler FUN_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_horizon FUN_00495804 / span_horizon FUN_004d81ba over the measured build_horizon block; inferred frame wiring from draw_frame_views FUN_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 .BL3 tracks (docs/systems/render.md, "The clouds")
  • [x] span_cloud FUN_004d6900 measured against POD 2.8 and ported to pod_render::span::cloud; 2,081 cases, 8,681 rows, byte for byte (measure/render_cloud_spans.py, tests/cloud.rs)
  • [x] fill_cloud_spans FUN_00494ee8 with fill_cloud_triangle FUN_00494c00 measured 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_spans FUN_00494ee8 / fill_cloud_triangle FUN_00494c00 ported to pod_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_layer 787 cases, cloud_clip_edge 1,317 cases, and draw_cloud_spans on 46 generated blocks; two runs byte-identical (measure/render_cloud_layer.py, derived/measure/cloud-layer/)
  • [x] draw_cloud_layer FUN_0048c8f4, cloud_clip_edge FUN_0048dd80 and draw_cloud_spans FUN_004950d4 ported to pod_render::sky::cloud (16-bit build, one view); tests/cloud_layer.rs replays 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 .BL3 only; Ubi.ini override remains open.
  • [x] Sun flare traced in its machine instructions (POD 2.8 WINPOD.EXE): marker FUN_0048c100, two spike passes and the octagon in FUN_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_polygon bl 0x20, fill_polygon_spans ebx 0x20 ecx 1, draw_shade_spans edx 0x80000000 to the whiten drawers (docs/systems/render.md, "The sun")
  • [x] place_sun_marker / draw_sun_flare measured 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, flare FUN_0048c248, the whiten/dither drawers and whiten table rows 0..31 ported to pod_render::sun (16-bit, one view, formats 0..3); tests/sun_flare.rs replays 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 (with FUN_00492e34 and the edge helpers FUN_004d7064, FUN_004d6f20, FUN_004d6f86, FUN_004d6ff8); measured, 1,500 polygons
  • [x] The polygon clipper FUN_00491b84 (four passes against the view's rectangle 0x62138c..0x621398), the perspective cut FUN_00491738 with its reciprocal table 0x57af5c, the clamp FUN_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-ai uses 53-bit vector length)
  • [x] The fan filler FUN_0049493c (with FUN_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_spans FUN_00492f9c with span_solid and span_checker, and the ordinary Gouraud path through draw_shade_spans FUN_00492fec / span_shade FUN_004d4f00; 700 fixture cases replay byte for byte (crates/pod-render/tests/color_faces.rs)
  • [x] draw_gradient_spans FUN_00493070, span_gradient FUN_004d6af6, and keyed span_gradient_keyed FUN_004d6cee ported to pod_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_00458cc8 and 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

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