Files
RP412/BUILD.md
T
CydandClaude Opus 5 ef4a5e501d The wireframe stands against black
Alt+W held the sky dome solid so the edges had something to read
against. Looking at it, the dome was the problem: lit, fogged and
filling the upper half, it washed out everything behind the near
geometry. A whole structure over the track in the middle distance was
invisible until the sky came away.

So the view now clears to BLACK under wireframe and the sky pass is
skipped entirely. Skipping costs nothing - the dome only ever covers
pixels the clear already owns - and it removes the two fill-mode
brackets that used to wrap the pass, so the frame is simpler than it
was. Both changes are conditional on gWireframe; the solid path clears
to the fog colour and draws its sky exactly as before.

Fog still applies to the edges, which is why the middle distance tints
toward the fog colour instead of staying bright. That is depth
information, so it stays.

environ.ini's template and BUILD.md both said the sky stayed solid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 12:50:44 -05:00

17 KiB
Raw Blame History

Building Red Planet 4.12 (Win32)

This is the Win32 source for the pod-racing game Red Planet, built on the in-house MUNGA engine and its L4 (Win32 / DirectX 9) platform layer. As of RP 4.12 the build targets Visual Studio 2022 (v143); the legacy VS 2005/2008 projects are retained for reference (see §5).

Verified build (2026-07-12): all 4 projects build clean (Release|Win32) with VS2022 Build Tools 17.14 (MSVC 14.44, v143) + Windows 11 SDK (10.0.26100) + DirectX SDK (June 2010). Outputs: Release\rpl4opt.exe (the game), Release\RPL4TOOL.exe, lib\Munga_l4.lib, lib\DivLoader.lib. Runtime-verified against the VC9 baseline built from the same tree: identical log output and behavior at every checkpoint, including RIO init against vRIO and mission load (see §4 for the pre-existing crash both builds share).


1. Requirements

Component Version Notes
VS2022 Build Tools (or any VS2022 with C++ workload) 17.x, MSVC v143 Installed at C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools. Install via winget install Microsoft.VisualStudio.2022.BuildTools --override "--quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended".
Windows 10/11 SDK any recent (26100 verified) Comes with the VCTools workload.
DirectX SDK (June 2010) Still needed for d3dx9 and dxerr only; everything else now comes from the Windows SDK. The projects reference it via $(DXSDK_DIR), which the SDK installer sets machine-wide. The DXSDK include/lib paths are appended after the Windows SDK paths (see the IncludePath/LibraryPath properties in the projects) so modern headers win — do not move them to AdditionalIncludeDirectories.

Bundled third-party libraries (already in the repo)

Committed under lib/, no install needed: OpenAL32.lib, libsndfile-1.lib (import libs). At run time the game needs OpenAL32.dll (system-wide install — assets/RP411/oalinst.exe — or beside the exe) and libsndfile-1.dll (beside the exe; a copy ships in assets/RP411/).

2. Building

& "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe" `
    WinTesla.sln /p:Configuration=Release /p:Platform=Win32 /m

Debug|Win32 also builds. x64 is not configured — this is a 32-bit build. The solution is WinTesla.sln with four v143 projects:

Project Type Output
MUNGA_L4/Munga_L4.vcxproj Static lib lib\Munga_l4.lib
RP_L4/RP_L4.vcxproj App (Windows) Release\rpl4opt.exethe game
RP_L4/RPL4TOOL.vcxproj App (Console) Release\RPL4TOOL.exe
DivLoader/DivLoader.vcxproj Static lib lib\DivLoader.lib (Release)

Build order is resolved by ProjectReference (RP_L4 and RPL4TOOL both reference Munga_L4).

Versioning: the patch number is the repository's commit count, so a build always names the commit it came from and there is no question about which changes a given binary contains. stamp-version.ps1 runs as RP_L4's pre-build step and writes the generated, uncommitted RP_L4\rpl4build.h:

#define RP412_VERSION       "4.12.96"
#define RP412_VERSION_LONG  "4.12.96 (a1b2c3d)"

The game logs the long form on its first line. A trailing + on the hash means the tree had uncommitted changes to tracked files when it was built — useful when a test machine reports something a clean build cannot reproduce. Only the 4.12 product line is set by hand, at the top of the script.

Test builds expire. $expireDays at the top of the same script is the shelf life in days (currently 14, counted from the day it was built, not the day the code was written — rebuilding an old commit gives a usable binary rather than one born stale). An expired build says so in a dialog, names its version and expiry date, points at the releases page, and exits without running. It stops a tester spending an afternoon on something that was fixed a week ago.

⚠️ Set $expireDays = 0 for a real release. A shipped build that expires is a catastrophe, and that one line is what decides it.

RP412NOEXPIRY=1 waives the check when an old build has to be run on purpose, and says so in the log so a waived build is never mistaken for a current one. It is deliberately not listed in environ.ini — a bypass every tester can see is a bypass every tester will use. Negative $expireDays backdates the expiry, which is how the refusal gets tested without touching the machine's clock.

The header is deliberately not committed: the commit that recorded a hardcoded number would itself change the count, so the file would be stale the moment it landed. It is rewritten only when the stamp actually changes, so ordinary rebuilds do not recompile RPL4.CPP for nothing. Building outside a git checkout stamps 4.12.x (no repository) rather than inventing a number that would sort against real ones.

Packaging: pack-dist.ps1 assembles a runnable game into dist\ (exe + PDB, game data, OpenAL/libsndfile runtimes, launch scripts, HANDBOOK.html, README). It deliberately does not write environ.ini — the exe carries that template and writes it on first run (RP_L4/RPL4ENVIRON.cpp), so a tester can drop a new build over an old folder without losing their settings. Pass -Zip to also produce RedPlanet-<version>.zip for handing to someone else. It reads the version from rpl4build.h rather than asking git again, so the package and the binary inside it cannot disagree, and it warns if the build it is packing came from a modified tree.

3. VS2022 migration notes (what changed and why)

Settings preserved from the VC9 projects: /Zp1 struct packing in Munga_L4 and RP_L4 (the engine's on-disk/on-wire binary formats depend on it — RPL4TOOL and DivLoader never had it), Unicode, subsystems, x86, /DYNAMICBASE:NO, optimization levels, and /FORCE:MULTIPLE (see below). Deliberate changes:

  • CRT unified to /MD (Release) / /MDd (Debug). The VC9 build mixed /MT (Munga_L4, RPL4TOOL) with /MD (RP_L4) and forced the link.
  • Import libs are no longer merged into Munga_L4.lib by the librarian; the exes link OpenAL32/d3d9/d3dx9/dinput8/dxguid/ws2_32 directly.
  • WINDOWS_IGNORE_PACKING_MISMATCH is defined: the modern Windows SDK static_asserts against /Zp1; the VC9 build always compiled Windows headers at /Zp1, so this preserves those exact semantics. Un-/Zp1-ing the engine (pragma-pack only the serialized structs) is future work.
  • _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS is defined: the code still uses stdext::hash_map; porting to std::unordered_map is future work.
  • legacy_stdio_definitions.lib is linked: the June-2010 dxerr.lib references pre-UCRT stdio symbols.
  • Source fixes (behavior-preserving): standard copy-ctor/assignment overloads added to Time (rvalues can't bind to volatile& in standard C++); operator==(SOCKADDR_IN&,...) in NETWORK.h made inline; the L4DINPUT callbacks renamed DIEnum* (they collided with L4CTRL's under LTCG); std::ios.instd::ios::in in CAMMGR.cpp.
  • /FORCE:MULTIPLE is still required: gOpNames, gReplacementData (hash_maps defined in a header) and GlobalEggFileName are genuinely defined in multiple TUs (LNK4006 warnings, same as the VC9 build). Moving them to a single TU is future cleanup.

4. Runtime notes

Fixed (2026-07-12): standalone mission load used to crash with an access violation in d3d_OBJECT::LoadTexture (MUNGA_L4/L4D3D.cpp): D3DXCreateTextureFromFileA failures were never checked and the NULL texture was cached and AddRef()ed. It crashed identically on VC9 — a latent defect in the 2007 DPL→D3D9 port, exposed by the pod-skin textures (VIDEO\player18) that a bare working copy doesn't contain (they normally come from the replacement-material/presets path, WTPresetsVIDEO\<name>.png). Missing textures now log L4D3D.cpp couldn't load texture … and render untextured; the game boots to a running window with -windowed -res 640 480 -egg TEST.EGG from a working copy like assets/RP411/.

steam_api.dll is optional. It is delay-loaded (DelayLoadDLLs in RP_L4/RP_L4.vcxproj, with delayimp.lib supplying the helper), so a working copy that has never seen Steam — like assets/RP411/, which predates the Steam work — boots and races normally. Only Steam itself is switched off, logged as Steam: steam_api.dll not found beside the exe - Steam features off, staying on TCP.

Delay loading alone would only move the failure: the first call into a delay-loaded DLL that cannot be found raises the helper's fatal exception rather than returning an error. So every path that reaches a Steam symbol first asks SteamNetTransport_ClientLibraryPresent() (MUNGA_L4/L4STEAMTRANSPORT.cpp) — a cached LoadLibrary probe using the same plain-name lookup the helper does. The gates are SteamNetTransport_Install and the two lobby entries RPL4Lobby_Host/_Join; everything else in the transport and lobby is downstream of one of those. Adding a new Steam call site means checking which gate covers it.

For Steam features you need the DLL from extern/steamworks_sdk_164/sdk/redistributable_bin/steam_api.dll (the 32-bit one, not win64\) beside the exe, plus steam_appid.txt — with the DLL but no appid file SteamAPI_Init fails and the game falls back to TCP.

environ.ini must be written without a BOM. The parser matches key names from the start of the line, so a leading UTF-8 BOM silently invalidates the first key in the file — put L4CONTROLS there and it reverts to the built-in KEYBOARD default, which then fail-fasts on 0xC0000409 with *****VTV has no controls mapping!***** in the log (there is no keyboard-only pod mapper). PowerShell's Set-Content -Encoding utf8 writes a BOM in 5.1; use [System.IO.File]::WriteAllText with an ASCIIEncoding. The log line Environ: environ.ini does not mention N option(s) naming a key that is plainly in the file is the tell.

For runtime debugging the v143 build produces full PDBs — run cdb -g -G -lines -y Release rpl4opt.exe ... from the working directory (cdb ships in this machine's Windows Kits).

Debug keys

RP412DEVKEYS=1 arms them, and they arrive through the engine keyboard handler, so L4CONTROLS must include KEYBOARD. That handler takes one key per frame off the front of the message queue and genuinely drops presses, so press again before concluding a key is broken.

Key State
Alt+W wireframe Live. Reimplemented on D3D9 as a per-frame D3DRS_FILLMODE (gWireframe, MUNGA_L4/L4VIDEO.cpp). The view clears to black and the sky pass is skipped, so the edges stand on their own — with the lit dome in place the far half of the scene is unreadable. The 2D pass (gunsight, cam-ship HUD) is held solid; particles are wireframed with everything else. Fog still applies, so distant edges tint toward the fog colour rather than staying white.
Alt+E event-queue dump Live. Reaches GeneralEventQueue::DumpEventQueue.
Alt+V predator vision Inert.
Alt+F frame dump Inert.
Alt+/ perf stats Inert.
Alt+K free memory Inert, silent.
Alt+R dither, Alt+P eyepoint Inert; they log "Function net yet enabled."
Alt+Q abort Always live, no RP412DEVKEYS needed.

The inert ones call DPLRenderer methods whose bodies were commented out with the rest of the DPL calls in the 2007 port (STUBBED: DPL RB 1/14/07). Reviving one means writing it against D3D9 rather than un-commenting anything: the dpl_* types those bodies used are empty placeholder classes now (DPLSTUB.h), and libDPL/ is reference headers that are not compiled. Alt+V is the worst of them — the DPL renderer implemented predator vision internally, reached by passing an out-of-band explosion effect type (-1 on, -2 off) with a NULL DCS, and nothing in this tree records what it actually looked like.

Running without the cockpit (Workstream A prototype)

Two new environment options remove the hardware dependency entirely:

  • L4CONTROLS=PAD;KEYBOARD — selects PadRIO (MUNGA_L4/L4PADRIO.cpp), an in-process RIO that speaks the full RIO control surface (buttons, axes, lamps) from an XInput controller + the PC keyboard. The stock VTVRIOMapper path runs unchanged. Hot-plugging works; with no controller it falls back to keyboard only.

  • L4PLASMA=SCREEN — renders the 128×32 plasma display as its own desktop window ("Plasma Display", plasma orange, L4PLASMASCALE sets the pixel size, default 4). It opens directly below the main view; L4PLASMAPOS=x,y overrides. No COM port. Closing the window hides it.

  • L4MFDSPLIT=1 — assembles the whole pod interior in one window. The pod hardware drove five monochrome MFDs from the color channels of two video outputs (window 3 = upper MFDs in R/G/B, window 4 = lower MFDs in R/G) and mounted the map display portrait. In split mode the main game window becomes the cockpit shell and every display is a chrome-less child pane, arranged as in the pod:

    [ MFD UL ] [ MFD UC ] [ MFD UR ]
    [ plasma (reduced) ][ viewscreen (centered) ]
    [ MFD LL ] [   Map  ] [ MFD LR ]
    

    The cockpit is a fixed 1920×1080 internal canvas (scaled down uniformly on smaller work areas): the viewscreen fills the whole canvas (launch with -res 1920 1080 for native 1:1 3D), with compact 320×240 MFD glasses in the four corners, the score glass top-center, and the portrait map bottom-center. The panes deliberately overlap the viewscreen and render over it — in the pod the MFD bezels partially occluded the viewport, and the same trick gives a full-screen 3D view with the cockpit floating over its edges. MFDs render green-screen and the map full-color/rotated, CPU-side from the shared gauge canvas (the packed D3D windows stay hidden); the 3D scene presents into the viewscreen pane via Present's hDestWindowOverride (gMainPresentWindow), clipped around the panes (viewscreen pinned to the bottom of the z-order). Mouse clicks over the viewscreen fall through to the game window (STATIC pane hit-test transparency). The plasma glass is currently hidden in cockpit mode. This replaces the external BitBlt-mirror launcher wrapper.

    Each split window also carries its display's physical button bank (geometry per vRIO's CockpitLayout): 4 red buttons above and below each MFD glass (RIO addresses descending from the cluster anchor — upper left 0x2F, upper center 0x27, upper right 0x37, lower left 0x0F, lower right 0x07), and 6 amber buttons down each side of the map (Secondary 0x100x15 left, Screen 0x180x1D right; the columns' other addresses are Tesla relays, not buttons). They light from the lamp states the game commands (dim/bright, flash modes animate) and press the corresponding RIO unit with the mouse — active when PadRIO is the control device, dark and inert with real serial hardware.

Bindings (vRIO's default profile, condensed):

Input Pod control
Left stick / WASD joystick X/Y
LT / RT, Q / E left / right pedal
Right stick Y, PgUp / PgDn throttle (rate; position holds)
A / Space joystick trigger
B / R reverse thrust (ButtonThrottle1)
DPad / arrows joystick hat (look)
X, Y, LB, RB pinky / thumb-low / thumb-low / thumb-high
Start / F1, Back / F2 config buttons (AuxUpperRight 1 / 2)

L4PADFLIP=XY (or X / Y) inverts the stick axes if the feel is wrong. Example desktop environ.ini:

L4CONTROLS=PAD;KEYBOARD
DPLARG=1
L4DPLCFG=RPDPL.INI
L4GAUGE=640x480x16
L4PLASMA=SCREEN
L4MFDSPLIT=1
TARGETFPS=60

Lamp commands land in PadRIO::lampState[] — the hook for the planned on-screen cockpit panel (vRIO buttons arranged around the displays).

Also note: Fail() compiles to abort() in release (MUNGA/DEBUGOFF.h) — e.g. running with L4CONTROLS=KEYBOARD alone aborts at VTV creation because no keyboard-only pod mapper exists (the old CRT showed a blocking R6010 dialog; the UCRT fail-fasts). Use L4CONTROLS=RIO;KEYBOARD with vRIO serving the RIO side, exactly like the arcade config.

5. Legacy VS2005/2008 build

The original .vcproj files and solution are kept as WinTesla_vc9.sln and still build with VC++ 2008 Express SP1 + DXSDK June 2010:

@echo off
call "C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\Tools\vsvars32.bat"
set "DXSDK_DIR=C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\"
vcbuild /nologo /rebuild WinTesla_vc9.sln "Release|Win32"

See docs/BUILD-NOTES.md for the original repository cleanup history and the findings behind the legacy build.