The first version of these plans was a scatter of scenery positions - an
impression of a track rather than a picture of one. The game already draws
the real thing: the map screen in the pod renders the track from above every
race, so the plans now reconstruct that instead of approximating it.
NavDisplay::DrawStatic walks the static entities, looks up each one's
L4GaugeImage by resource id, and draws it through localToWorld x
worldToView. navmap.py does the same offline. The pieces that made it
possible:
- a map instance carries its model's GaugeImage id at +44, beside the
position at +48 and the quaternion at +60;
- a GaugeImage is a vertex array plus per-LOD polylines through it, in
world units - cn3 is an 89x5 wall segment, pit1 a 500x300 pit;
- a placement whose model has no gauge image is skipped here exactly as
DrawStatic skips it, which is why a card can report fewer placements
carrying map art than the track contains.
The difference is not subtle. Wiseguy's Wake and Paingod's Passage resolve
into twin canyon walls running their length, Brewer's Bane into an L-shaped
route through junction chambers, and both arenas into a lattice of obstacles
inside a boundary wall. What is still missing is the driving surface: the
map draws what lines the route, never the tarmac.
tools/pages/navmap.py carries the reader, and the README documents both the
instance record and the gauge image stream. Regenerating from the committed
generators reproduces the committed page byte for byte.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Regenerating the reference pages
docs/vtv-presets.html and docs/tracks.html are generated from
assets/RP411/RPL4.RES, the gauge config, and the console's RPConfig.xml.
Both are committed, so this is only needed when the resource file changes.
Both need a resource listing from the original tool, which is the authority for resource ids — the directory can be walked in file order, but the ids have holes (this file leaves 53 and 56 unassigned) so the walk has to be aligned against the listing:
Release\RPL4TOOL.exe -l assets\RP411\RPL4.RES > listing.txt # ends in _getch(); close it
python tools\pages\extract_presets.py vtv.json listing.txt
python tools\pages\build_page.py vtv.json docs\vtv-presets.html
python tools\pages\build_tracks.py listing.txt docs\tracks.html
RPL4TOOL finishes with _getch() (RP_L4/RPL4TOOL.cpp), so with stdout
redirected it writes the whole listing and then waits for a keypress —
close it once the file stops growing.
What each reads
| script | reads | writes |
|---|---|---|
extract_presets.py |
RES, L4GAUGE.CFG, tools/console-config/RPConfig.xml, listing |
vtv.json |
build_page.py |
vtv.json, GAUGE/s*.pcc silhouettes |
docs/vtv-presets.html |
build_tracks.py |
RES, listing, RPConfig.xml, RPL4FE.cpp catalogs, tracks.css/js/tpl |
docs/tracks.html |
build_tracks.py reads the front end's own kMaps / kFootballMaps arrays
so the page cannot claim a track is offered when the menu does not offer it.
Two formats worth knowing
Control mappings. A vehicle's ControlsMappings List holds the resource
ids of its L4 and Thrustmaster streams. Streams are named plainly, so
there is nothing in a stream saying whose it is — resolve by id, never by
which vehicle name sits nearest in the file. Records are 24 bytes and carry
their own mode mask; bits 3–8 are ModePreset1..6.
Map instances. 76-byte records: class id at +0, position at +48, unit quaternion at +60. A few records are longer, so resync on an unexpected class id rather than trusting the stride. The quaternion doubles as a checksum — a mis-read almost never produces a unit one.