A map record is an Entity::MakeMessage (MUNGA/ENTITY3.h): classToCreate, owningPlayerID, resourceID, instanceFlags, localOrigin. The origin ends the 76-byte case, which puts classToCreate at +28, resourceID at +40 and instanceFlags at +44. I had been reading +44. That is instanceFlags, and it is 524 on every scenery record - and 524 happens to be cn3's GaugeImage. So every track resolved to cn3 repeated a few hundred times, consistently and wrongly, and every conclusion drawn from that followed: the "one wall bar with a gate", the ticks, the claim that the LOD machinery is never exercised. The id at +40 varies per placement. The tracks use cn1, cn3, cn4, cn5, cn7, br1, br3, ft1, cq1, cq2, md3, md4, the pits, and the score zones sc50/sc50a/sc500a that gave the amber boxes at each end. A record names the model's Model List; the GaugeImage is filed under the same model name, so the name is the join - and models with no gauge image (oao, snAwork, pz1) are skipped here exactly as DrawStatic skips them. Found by following the map loader: InterestManager::LoadMapStream reads the stream as MakeMessages and names the map entity classes, one of which is 95 in these records - CulturalIconClassID.
mapview
A dev tool: every track drawn the way the pod's map screen draws it, with pan and zoom, so you can ask "what does the map actually show here?" without booting the game. Nothing here ships — pack-dist.ps1 does not look at it.
python tools\mapview\build_mapview.py <rpl4tool-listing.txt> tools\mapview\mapview.html
Then open mapview.html. It is self-contained: track data, palette and all.
| key | |
|---|---|
| ↑ ↓ ← → | pan north / south / west / east |
| + - | zoom in / out |
| [ ] | previous / next track |
| F / A | fit the course / fit everything |
| G | GPS or NavDisplay LOD behaviour |
Dragging pans and the wheel zooms.
What it reproduces
NavDisplay::CalculateBounds (RPL4GAUG.cpp:1587)
derives metersPerPixel from the zoom and sets LODIndex to it. L4GaugeImage::Draw
(L4GAUIMA.cpp:908) then walks the LOD scales:
for (lod_index = 0; lod_index < LODCount; ++lod_index)
if (LOD_value <= LODScales[lod_index]) break;
if (lod_index < LODCount) { ...Draw... } // else nothing is drawn
Scales ascend, so index 0 is the finest and running past the largest drops the
object entirely rather than simplifying it. The viewer does the same and reports
the count, though this content barely exercises it: every placement in every
track is cn3, which carries a single LOD at scale 1000, so nothing is dropped
until 1000 m/px and then all of it goes at once.
Two display modes, because the game has two map gauges and they disagree:
- NavDisplay —
nav(A,ModeAlwaysActive,(448,416),0x00,0x3C,...). The 448×416 radar screen, LOD following the zoom. - GPS —
gps(R,ModeAlwaysActive,(125,203),1.0). The 125×203 panel, LOD pinned at 1.0 by the config however far out it is scaled.
Colour
Not a phosphor screen. Primitives carry palette indices and the palette is
whichever the port was configured with; for the pod's secondary port, where the
nav display lives, L4GAUGE.CFG says
configure(0, sec, 270, 0x00ff, clut0, rgb, secpal.pcc). PCC is PCX, so the last
769 bytes are 0x0C and 256 RGB triples. The walls come out grey because index
51 is #4b4b4b; the background is index 0, black. A primitive whose own colour
is 0 keeps the colour already set, which is the display's staticColor, 0x3C.
Palettes are per-file, not global — 39 of 40 gauge PCCs differ from each other — so the port's configured palette is the one that matters, not any convenient nearby image.
Orientation
+X runs right, +Z runs up. That is the engine's projection rather than a
choice. L4GaugeImagePrimitive::Draw plots MoveToAbsolute(dest->x, dest->z),
so the screen axes are view-space X and Z; and the graphics view's origin is at
the bottom left with Y increasing upward — BackgroundLine draws
endpoints.bottomLeft to endpoints.topRight, and the port's zero-degree blit
is documented with the origin at the bottom-left corner.
Worth knowing: the setup console's pictures of the tracks are mirrored against
this. They are illustrations, not screenshots. The plans in TRACKS.html
deliberately follow the console pictures, because nine of those cards are the
console pictures and the reconstructed nine have to sit beside them consistently.
This viewer follows the game instead. The two disagree by a mirror, and both are
right for what they are.
The nav display also centres on the vehicle and turns with it, inverting the
viewer's transform and taking yaw only unless rockAndRoll is set. Heading 0
here is the north-up case; Q/E turn the map.