Files
RP412/tools/mapview
Cyd 86bf6a934b Map instance records carry their own length
They are not a fixed 76 bytes. The first int of each record is its length,
and while most placements are 76, every track also has eight of 140, two
of 80 and one of 336 - 560 on Paingod's. Striding a fixed 76 landed
mid-record on those, and hunting forward for the next plausible
quaternion then locked onto arbitrary bytes: that is where the impossible
class ids came from, and the "resource id" 1065353216, which is
0x3F800000 - float 1.0.

Reading the length instead, all eighteen tracks parse to exactly their
declared instance count with no bytes left over. That is the check that
was missing before.

It does not change what gets drawn, because the extra records were never
drawable anyway. It does mean the parse is no longer guessing.
2026-08-07 13:23:37 -05:00
..

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:

  • NavDisplaynav(A,ModeAlwaysActive,(448,416),0x00,0x3C,...). The 448×416 radar screen, LOD following the zoom.
  • GPSgps(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.