The renderer's bottom layer, byte for byte
2026-10-24.

The same 279 spans, drawn by crates/pod-render (left) and by Gold's WINPOD.EXE itself under the emulator (right), with Beltane's first texture page (BELTANE.BL3). The spans and light levels are made up for the picture; the pixels are identical.
Un-POD's game app draws the race with the viewer's renderer, which only looks roughly like POD. This session started on POD Gold's own software renderer, from the bottom: the routines that put pixels into the 640 × 480 frame. The full write-up is in docs/systems/render.md.
What was traced
FUN_0049af38 draws each view of a frame: the world, the cars, the sky, then a clear of whatever nothing covered, then the overlays. The renderer draws front to back and keeps one bit per pixel of what is already drawn, so hidden spans are dropped before they are drawn.
A polygon becomes one span per screen row, and a span is drawn by one of two hand-written assembly routines: kind 0x60 (textured and shaded) and kind 0x61 (the same with texel 0 transparent). Both draw one texel per pixel up to the first 8-byte boundary of the row, and after it each texel covers two pixels, so POD's software image is half resolution across. In perspective they take a sample every four pixels and fill the pixels between with the midpoint of two samples.
The FPU trick
Kind 0x60's perspective path packs u, v and the light level into one 64-bit integer, with a high bit set so every value has the same exponent, and steps it with the x87 FPU. Storing the result as a double lines up the bytes: byte 0 is u, byte 3 is v, and the high dword, shifted, is an index straight into the shade table, through an address that wraps round past 4 GB. The midpoint of two samples is the sum of the two doubles, which has one more bit of exponent, so its bytes come out halved.
The port keeps this as integers, rounded where the FPU rounds. It is a translation of the assembly instruction by instruction, with the overlapping globals kept as bytes.
How it is known
Every piece runs against the original under Unicorn (measure/render_*.py):
| Piece | Original | Cases | Result |
|---|---|---|---|
| Perspective, reciprocal and multiply tables | FUN_00495b14 | every entry | identical |
| Shade table rows | FUN_004965b4 | 800 | identical |
| Coverage mask: mark, mark at, test, clear | FUN_004d59e0, FUN_004d5a60, FUN_004d5b2c, FUN_004d5ba2 | 800 | identical |
| Textured span, kind 0x60 | FUN_004d7120 | 4,000 span lists | every byte identical |
| Textured span, kind 0x61 | FUN_004d7c3d | 4,000 span lists | every byte identical |
Two things the emulator showed that reading did not:
- Unicorn starts with FPU control word 0, single precision, which no Windows program runs with. The game's table builder makes a slightly different perspective table under it. Three of the first 2,000 span cases failed for that reason before the harness set 0x37f.
- The test routine reads the next row.
FUN_004d5b2csteps its mask pointer a row before it reads, unlike the routines that mark. So it tests each span against the mask of the row below. The port does the same.
Open
The FPU's precision changes the picture: with 64-bit or 53-bit precision, half of the random span lists come out different. Gold sets 64-bit precision at start-up and then runs the game on a thread of its own, which Windows starts at 53-bit. Which one the drawing code runs under is asked of the session that owns the emulated PC. So is a recorded frame's span calls, to check the port against a real frame of Beltane.
A frame of the game
Then the race-loop session recorded a real frame of Gold in the emulated PC: Beltane, fourteen seconds after GO!, with every call that hands spans to the span routines. The drawing thread runs at 53-bit precision (control word 0x27f), which settles that question. The port replays the 55 calls and their 2,577 spans with the game's own textures and shade table. 271,467 of the 272,446 pixels they write are the same as Gold's.

All 979 others are on the player's car. Gold drew over them after the spans, black from the clear of uncovered pixels and a dark colour from a later pass with a third span routine (FUN_004d81ba). The black areas at the top of ours are the sky and the far scenery, which the game draws without the dispatcher. With 64-bit precision, 5,404 pixels would differ.
The cars' palette
Un-POD's cars drew nearly white: their .BV3 pages are 8-bit, and the reader took their palette slot for empty. It is not empty. Every .BV3 carries its car's palette, in both editions. The game loads CHROME.CH3 first into palette slot 4 and then every car into the same slot. FUN_00465774 rewrites each later block's pages to the slot's palette through a nearest-colour map (FUN_0049d2cc). The port of that map matches the original on 500 random palettes. The eight cars of the first edition already share the chrome's palette; the cars Gold added are remapped to it. Un-POD now draws its cars that way, from the .BV3, with no .BV4.

The polygon filler
One layer up, FUN_004931ec turns a clipped polygon into the span records the routines above draw. It walks each edge from top to bottom, x first, then through the coverage mask, then the light, the depth term and the texture coordinates. Edges whose ends differ in depth go through the perspective table and a small FPU routine that leaves its rounded result in a double's low dword. Ghidra's pseudocode lost a call and inverted a branch here, so the port follows the disassembly. On 1,500 random polygons under the emulator, with and without the coverage mask, it writes the same records, mask and result as the original, byte for byte. Then on a real frame: the race-loop session logged every polygon Gold filled in one Beltane frame. 83 of its 84 polygons come out the same, to the last word. The last two differ by one column of the coverage mask, which something else marked first.
Next
Above the filler: the transform and clipping, the sort and the sky. Then pod-render takes the place of the stand-in behind apps/unpod's drawing seam, and its frames are compared with Gold's at 640 × 480.