Texture blocks
Every track, car and chrome file holds its textures in the same kind of block, read by read_texture_block (FUN_0046aca0) in WINPOD.EXE. A block holds a few texture pages; each page is an atlas of named sub-textures.
Reader: decoders/textures.py. Viewer: viewer/build.py. State: decoded for viewing on all 56 files that hold textures, 345 pages (measured, 2026-10-23: every page drawn and looked at in the viewer). Not yet round-trip: the bytes around the blocks are not decoded.
Layout
Observed in FUN_0046aca0; the sizes are register arguments, read from the disassembly at 0x46aca0–0x46ad51.
| Read | Size | Content |
|---|---|---|
N | u32 | Number of pages |
| — | u32 | Read and not used by the software build |
per page: k | u32 | Number of sub-textures on the page |
| per page: records | k × 0x34 | One record per sub-texture, below |
| palette | 0x300 | 256 × RGB, 8-bit values; only in the 8-bit files (below) |
| pixels | N × page size | The pages, one after the other, rows top to bottom |
The software build reads the palette only when the load mode is 0 or 4, and 0x10000 bytes per page (EDI at 0x46accc).
Sub-texture record (0x34 bytes)
A page can also hold records of all zero bytes beside its real ones: two on page 7 of POD POD's MINE.BL3 and MINE.BL4 (measured). The readers accept them on a page that has a real record too, so the block search, which looks for real records, is unchanged.
| Offset | Type | Meaning |
|---|---|---|
+0x00 | char[32] | Name, zero-terminated (route2l2.TGA). Latin-1: coté.TGA in SCORP. A few names keep a stray byte after .TGA (winnight2.TGA@, mapcar5aa.TGA followed by 0x80) |
+0x20 | i32 × 4 | x0, y0, x1, y1: inclusive corners on a 256 × 256 grid |
+0x30 | u32 | Flags: 0 on most; 1 on the sky (meaning unknown) |
The corners count to 255 even on the 128 × 128 pages: the grid is the same, the page is smaller (measured: the outlines fit the tiles at half scale).
Page formats
Measured by drawing the pages and by the spacing of blocks in the stream (for example the sky block in BELTANE.BL3 takes 0x300 + 0x10000 bytes, and in BELTANE.BL4 0x20000).
| Files | Page | Pixels | Palette in the block |
|---|---|---|---|
.BL3, .CH3 | 256 × 256 | 8-bit index | yes |
.BV3 | 256 × 256 | 8-bit index | yes, the car's own; the game remaps the pages to CHROME.CH3's palette (docs/systems/render.md) |
.BL4 | 256 × 256 | RGB565 | no |
.BV4, .CH4 | 128 × 128 | RGB565 | no |
Inferred: the "3" files serve the 8-bit software renderer and the "4" files the 16-bit 3D cards, which hold car textures at half size.
Where the blocks are
- Tracks: three blocks. The main textures (5 pages in
BELTANE), then two one-page blocks that each hold a singlesky.tga. Each is preceded by a name (strings.md). The loader reads them at steps 10 and 12 (tracks.md). - Cars: one block of 2 pages after the car's parameters (at stream offset
0x670–0x674). - Chrome: one one-page block right after the header.
The scan (texture::blocks, for the viewer) finds blocks by their records: a run of .tga names with corners in range, preceded by k, N and the unused word. The car and track readers read each block where the file puts it (texture::block_at), as read_texture_block does, without checking the records. Fan files need that (measured, 2026-10-24, docs/extras.md). The fan cars F1, F2 and F3 name their first texture Pneu-pluie.TG` . The fan tracks Parc and Down name their prop textures .bmp, and Parc's sky record runs to the corner (63, 1023).
The sky
The sky page is not drawn as a 256 × 256 picture. Drawn 64 pixels wide and 1,024 tall it is a skyline turned on its side: inferred, a 1024 × 64 panorama stored column by column. The viewer shows it as a 256 × 256 page for now.
Open questions
- What the unused second word holds (
0,1, or values like0x328b740). - What the record flags mean.
- How the sky page is mapped when drawn.
Writer
crates/pod-formats/src/texture.rs: a Block keeps its stored bytes (the word after the page count, each record's whole name field, the palette slot, each page's pixels). Block::encode gives them back: round-trip on all 371 pages (measured, tests/writers.rs), except one scan result in TEST.BL4 whose pages would pass the end (the scan's guess at the .BL4 sky; the track writer reads that file from the reader's own walk). Block::put draws a picture into a page: the nearest palette colour for 8-bit pages, RGB565 for 16-bit ones. Modded: Beltane's page 0 in the game (docs/modding.md).