Skip to content

Cars (VOITURES/*.BV3, *.BV4) ​

Nine cars, each in two files: .BV3 with 8-bit textures and .BV4 with 16-bit ones. The models are the same in both (measured: the objects end at stream offsets exactly 0x10300 apart, the difference in texture size).

Reader: crates/pod-formats/src/car.rs, with carparams.rs for the parameters (prototypes decoders/car.py, decoders/carparams.py). Viewer: the 3D car view in apps/viewer, with the parameter table beside the model; pod-viewer info NAME prints the same table, and pod-viewer render NAME OUT.png draws the car. State: decoded in all 18 files (measured, 2026-10-23): the walk follows load_car_file to its last read, every parameter byte is a named field or zero, and the parameters write back byte-identical through the container. Modded (2026-10-23, docs/modding.md): a changed grip setting (the third garage setting) shows on the car screen and changes the race car's grip. A changed side_grip/traction_grip loads into the car slot but does not reach the race: the garage setup replaces it (setup_defaults, FUN_004436c8; car_from_setup, FUN_00442f04).

The game's reader is load_car_file (FUN_0046ae1c). It opens the file with key 0x0F2E (container.md) and reads, in order (observed in the disassembly; the 0x24-byte and light reads are not in Ghidra's pseudocode, which drops the register arguments):

StepReadFunction
1name: the car's name (arc)load_car_file
20x558 bytes: the parameters (below)load_car_file
3u32 n (64 in every car), n × u32: the torque curveload_car_file
4name, texture block (textures.md), u32load_car_parts (FUN_0046aabc)
518 objects, each followed by 0x1C bytes; 4 objects, each followed by 0x1C bytes; 4 objectsload_car_parts
6two animated objects: name, u32, object, 0x34 bytes, u32 frames (0 in every car), frames × 64 bytesread_animated_object (FUN_0046a780)
70x24 bytes: a word the game overwrites, then 16 × u16load_car_file
85 × u32: the garage settingsload_car_file
9a light: u32 kind, 0x30 bytes, 4 × u32load_car_file, FUN_00434e50

After step 9 the stream holds 2,048 to 13,741 more bytes, mostly not zero. The game never reads them (observed: load_car_file closes the file next): inferred, leftover memory the original tool wrote out as block padding. A writer must keep them to stay byte-identical.

Unlike the tracks, the car's animated objects have no texture block (measured: the object starts right after the u32; observed: FUN_0046a780 reads a block only on one branch of a test of ECX after the name). What the two objects are is open.

The .BV3 texture block carries the car's palette in its palette slot (measured, 2026-10-24, in all of POD's and the first edition's car files; an earlier note here said the slot was zero). The 16-bit software build remaps the pages to CHROME.CH3's palette at load (docs/systems/render.md, "Palettes and texture blocks").

Objects ​

Read by FUN_00466898. Coordinates are signed 16.16 fixed point.

ReadContent
u32Vertex count n
n × 12Vertices: x, y, z
u32, u32, u32Face count, and two counts that size run-time arrays of 0x1C and 0x38 bytes
facesOne after another (below)
n × 12Vertex normals
u32Not used by the viewer

Face ​

Read by read_face (FUN_00465d54), which branches on the corner count (observed).

ReadContent
nameMaterial (T_G_CHUB3_A)
u32Corners: 3 or 4 (any other count stops the game with an error)
4 × u32Vertex indices; unused slots hold leftovers
3 × i32Face normal, 16.16
nameRender type: TEXGOU (textured, Gouraud), GOURAUD, FLAT
0x28 bytesu32 texture page, four (u, v) pairs on the 256 grid, u32 leftover
12 bytesQuads only (read at 0x46b25d); meaning unknown
u32Face flags. The reader copies its low three bytes into runtime face offsets +0x36, +0x35, and +0x37 respectively (observed at FUN_00465d54); the render loop uses these bytes for visibility, face sidedness, and other face behavior.

The bytes after an object (0x1C) ​

Seven words, 16.16: half-extents in x, y and z, a radius, and the centre x, y, z (inferred from the values: a bounding box and sphere; the wheels have centre 0).

What the objects are ​

Inferred from drawing them:

ObjectsWhat they look like
18 (step 3)The body, as three sets of six pieces; sets 1 and 2 differ from set 0 in their panels (damage states or detail levels: open)
4 (step 4)Wheels, each modelled around its own centre
4 (step 5)A flat outline of the body at height 0, flat-shaded: a shadow or collision hull

Axis 0 runs along the car, axis 1 across, axis 2 up (measured by drawing the vertices from three sides).

Parameters ​

The 0x558 bytes go straight into the car's slot (0x6174ec + car × 0x63c, block at +0x90). At the start of a race FUN_00476aa0 copies the fields below into the race car (0x61aad0 + racer × 0x558), which has the same layout; the physics reads them there at the same offsets (observed). Everything else in the block is the car's run-time state (engine speed, current gear, velocities, the curve pointer at +0x43c), and it is zero in all nine cars (measured).

Not scrambled. An earlier reading took the wheels from stream offset 0x58. That fits only Arc and AV1, whose names have 3 letters: the block starts after the name, so its offset moves with the name's length (measured: with the name read first, all nine cars read as numbers). The .BV3 and .BV4 of a car hold the same block (measured).

Values are signed 16.16 fixed point unless marked. The units are inferred from the formulas: the game multiplies weight by 9.81 (0x9cf5c) and turns engine speed into rpm with 60/2π (0x98c9e), so lengths are metres, masses kilograms, angles radians and speeds per second. Each value below is observed in the function named.

Point records ​

Five records of 0xD8 bytes: wheels 0 to 3 at 0x000, 0x0D8, 0x1B0, 0x288, then the chassis point at 0x360. Wheels 0 and 2 are the front pair (positive x), 1 and 3 the rear; 0 and 1 are on the negative-y side. Offsets are within a record.

OffsetFieldRecordsRead byWhat the game does with it
+0x40mass, kgallFUN_00429bf0Inertia m·r²/2 into +0xa0; weight −m·g·9.81 into +0x44. 100 for wheels, 50 for the chassis point in every car
+0x48x, mallFUN_004231f4Position along the car
+0x4cy, mallFUN_004231f4Position across the car
+0x50z, mallFUN_004231f4Height. Zero in the files; for wheels FUN_00429cb4 sets it to the rest height at reset. The chassis point's x, y, z is subtracted from every wheel's position: it is the centre of mass
+0x54radius, mallFUN_00429bf0Wheel radius (inertia; the garage averages the four, FUN_00442dd8). For the chassis point: the radius of its contact with the ground (FUN_00424478)
+0x94side gripwheelsFUN_004284ccTimes the surface's grip and a quarter of the car's weight: the most sideways force the tyre holds before it slides
+0x98traction gripwheelsFUN_004284ccTimes the surface's grip and a quarter of the weight: the force along the wheel before it spins or locks
+0xacspringwheelsFUN_00426f18Suspension force −(z − rest)·spring − speed·damper, limited to ±7.63
+0xb0damperwheelsFUN_00426f18The speed term of the same force
+0xb4rest, mwheelsFUN_00429cb4Height the wheel starts at
+0xb8travel max, mwheelsFUN_004272dcHighest z; the wheel stops there
+0xbctravel min, mwheelsFUN_004272dcLowest z

FUN_00429cb4 overwrites the chassis point's +0x94 and +0x98 with 0x9c40 (0.61) at reset; they are zero in the files.

The car ​

OffsetFieldRead byWhat the game does with it
0x44bu8: forward gears (6 in every car)FUN_0042672cTop gear; the garage copies this many ratios (FUN_00442dd8)
0x44c9 × gear ratioFUN_00426184Engine speed per wheel speed, indexed by the current gear (u8 at 0x44a): 0 neutral (0), 1–6 forward, 7 unused (0), 8 reverse (negative). In every car gear n is gear 1 ÷ n
0x484steering: largest angle, radFUN_0042386cLimit of the front wheels' angle (+0x58 of wheels 0 and 2)
0x488steering speed, rad/sFUN_0042386cAdded per second while left or right is held
0x48csteering return, rad/sFUN_0042386cSpeed back to centre, times the steering speed, when no key is held
0x518rev up, rad/s²FUN_00425c50, FUN_00426a40Engine speed gained per second with the throttle out of gear
0x51crev down, rad/s²FUN_00425c50Engine speed lost per second, out of gear
0x520body mass, kgFUN_00429bf0, FUN_0042a07cWeight into 0x524; the body's inertia from its bounding box (m/12 · (a² + b²))
0x528gravity scaleFUN_00429bf0Weight = mass × this × 9.81 (4.36359 to 4.36362 in the nine cars)
0x538front brake shareFUN_0042653cWheels 0 and 2 brake with this share, 1 and 3 with 1 − share (0.5 in every car)
0x53cstep scaleFUN_00427514Multiplies the time step when the car's velocity moves its position (1.1942 in every car); inferred: a world-units-per-metre factor
0x544brake rate, /sFUN_0042653cBraking grows by this per second while the key is held
0x548brake maxFUN_0042653cBraking stops growing here; a joystick sets a share of it
0x54cu32: drivecode at 0x428d4bWhich wheels the engine drives: 1 all four (front and rear shares by wheel radius), 2 the front pair, any other value the rear pair. 1 in every car

At reset FUN_00429cb4 sets the largest engine speed (0x514) to 1000 rad/s (9,549 rpm) and the shift points from it, whatever the file holds there.

Torque curve ​

64 u32 values, one per 256 rpm of engine speed (observed: FUN_00426184 converts the engine speed in rad/s to rpm and indexes the curve by rpm ÷ 256). The value times the throttle and the gear ratio is the torque on the driven wheels. Every car's curve rises to a flat top and falls to 0 at entry 40 (10,240 rpm). The garage rebuilds the curve from its settings (FUN_00443048), so a changed curve may last only until the player changes the setup.

After the objects ​

ReadContent
0x24 bytesA u32 the game replaces with a handle (load_car_file stores the result of FUN_004c1e48 there; the files hold leftover text such as rog\), then 16 u16 numbers, 0xFFFF for none. Inferred: the car's sound effects, by number. The 16th differs between a car's .BV3 and .BV4, so it is leftover too
5 × u32The garage's five settings, 0 to 100 (Arc: 86, 37, 61, 55, 61). FUN_004436c8 copies them into the setup, and FUN_00443048 builds the torque curve from them. In order: acceleration, brakes, grip ("adhérence"), steering, speed (measured: the car screen shows GAMMA's 49, 25, 95, 82, 49; a changed third value shows as the grip). Grip in the race = 1.2 + 0.026 × setting on all four wheels (measured at 95, 50 and 5)
lightThe record the track lights use (tracks.md). FUN_00434e50 picks a shading routine from the kind (2 in every car) and the second tail word. The race car points at it (+0x554). The meaning of the 48 bytes is open, as for the tracks

Wheel positions ​

The viewer places each wheel at (x, y, rest height) from its point record. Measured: in all nine cars the wheels land at the body's corners.

Car lists (SCRIPTS/VOITURES.BIN, VOITURE2.BIN, VOIRESET.BIN) ​

Reader: crates/pod-formats/src/cartable.rs (prototype decoders/cartable.py). State: decoded and round-trip for all three files (measured, 2026-10-23).

FUN_00477400 reads a list: a plain u32 count, then count records of 0x80 bytes, each byte XORed with 0x68. FUN_0047747c writes one the same way (observed).

POD's VOITURE2.BIN holds 23 records with a count of 8 (measured). POD's reader (FUN_00472020) reads the counted 8: its seven new cars and ALIEN. The 15 after them name cars that are not on the disc (Breton, IRX, Akir7, Nocte, Kaizer, Pirate, Eagle, Razor, Xira four times, DarkSmil, Bat, Witch); inferred: the cars released on POD's web site, as the README says. They are never read; cartable::read_all keeps them for writing back.

Record offsetSizeContent
0x00char[0x40]Car file without extension (GAMMA → VOITURES/GAMMA.BV3)
0x40char[0x14]Name shown in the menus (gamma); FUN_0048d110 and FUN_00436658 look cars up by it
0x54char[13]Picture name (24manta.tga)
0x61char[13]Picture name (Pmanta_E.tga)
0x6echar[13]Picture name (Pmanta_A.tga)
0x7b5 bytesZero

No shipped program reads the picture names (measured: the strings appear in no executable or DLL, and no .tga files ship). POD2_0.DLL, the online client, names the three lists.

FileCountHoldsWhere it goes
VOITURES.BIN8The race list: Gamma, Saber, Scorp, Shivan, AV1, Dopple, Bakar, Arccount 0x6174c0, records 0x61f424
VOITURE2.BIN1Spare cars: Aliencount 0x505c50, records 0x505c54
VOIRESET.BIN8The race list as shipped (equal to VOITURES.BIN on the disc)read into the stack

The menu code around 0x43ee1c–0x43f6be (inside FUN_0043e9cc as Ghidra cuts it) reads the spares, and writes both VOITURES.BIN and VOITURE2.BIN back (observed). The routine at 0x43f404 replaces the race list with VOIRESET.BIN's eight records and moves every car not in it to the spares (observed). Inferred: the menu swaps a spare car, such as the Alien, into the eight-car race list, and "reset" undoes it. These two files are save state: the game writes them. When a race-list entry changes, the game clears that car's "setup ready" flag (0x50911c + car).

Open questions ​

  • How FUN_00443048 turns the five settings into the curve.
  • What the two animated objects after the parts are.
  • The sound numbers: which effect each slot plays.
  • The light record's 48 bytes (shared with the tracks).
  • The units of grip, springs, brakes and torque: they are ratios inside the game's own scale.
  • What the three body sets are, and why Bakar's front piece sits apart from its body.
  • The quad-only 12 bytes and the face flags.
  • Checking a changed value by driving under Wine (docs/running.md).

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