Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3229a82380 | ||
|
|
29ad567ffb | ||
|
|
9fae4406b5 | ||
|
|
b07e328553 | ||
|
|
879b76c6e1 |
@@ -30,6 +30,15 @@ Committed under [lib/](lib/), no install needed: `OpenAL32.lib`, `libsndfile-1.l
|
||||
`assets/RP411/oalinst.exe` — or beside the exe) and `libsndfile-1.dll` (beside the
|
||||
exe; a copy ships in `assets/RP411/`).
|
||||
|
||||
It also needs **`d3dx9_43.dll`**, which is a hard import and is *not* part of
|
||||
Windows — it ships only with the June 2010 DirectX End-User Runtime. A dev box
|
||||
with the SDK installed has it system-wide and never notices; a clean Windows
|
||||
10/11 fails to start the exe at all (0xC0000135, before `WinMain`).
|
||||
[pack-dist.ps1](pack-dist.ps1) therefore extracts it from the SDK's
|
||||
`Redist\Jun2010_d3dx9_43_x86.cab` into `dist\`. That same file is also what
|
||||
makes the game run under Wine/Proton, whose builtin `d3dx9_43` stubs
|
||||
`D3DXConcatenateMeshes` — see [docs/RP412-LINUX.md](docs/RP412-LINUX.md).
|
||||
|
||||
## 2. Building
|
||||
|
||||
```powershell
|
||||
|
||||
+42
-2
@@ -92,6 +92,46 @@ namespace
|
||||
const int buttonDepth = 240;
|
||||
const int indicatorStrip = 10;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Extra pixels of indicator strip, on top of the 10 above, so a
|
||||
// FLASHING lamp is easier to notice on desktop glass - the pod's
|
||||
// buttons were physical and backlit and never had that problem.
|
||||
// Only the strip moves, so the button keeps its width and reaches
|
||||
// the same distance behind the glass: this makes the lamp a
|
||||
// LONGER bar, not a fatter face. The client size, the display
|
||||
// offset and both banks all derive from strip_h/strip_w below, so
|
||||
// the window grows to fit and nothing needs to know about it.
|
||||
//
|
||||
// DEFAULT 0 - Red Planet keeps the pod's geometry unless a player
|
||||
// asks otherwise. (BattleTech 4.11 carries the same knob defaulted
|
||||
// to 10, where the players asked for it; the two games are tuned
|
||||
// separately on purpose.) Ported alongside RP412MFDLAYOUT, which
|
||||
// took the same route from BT411's BT_GLASS_LAYOUT.
|
||||
//---------------------------------------------------------------
|
||||
const int defaultProtrude = 0;
|
||||
|
||||
int ProtrudeBonus()
|
||||
{
|
||||
static int bonus = -1;
|
||||
if (bonus < 0)
|
||||
{
|
||||
const char *setting = getenv("RP412MFDPROTRUDE");
|
||||
bonus = (setting != NULL && setting[0] != '\0')
|
||||
? atoi(setting) // a word parses to 0, i.e. off
|
||||
: defaultProtrude;
|
||||
if (bonus < 0) bonus = 0;
|
||||
if (bonus > 200) bonus = 200;
|
||||
|
||||
if (bonus != 0)
|
||||
{
|
||||
DEBUG_STREAM << "MFDView: RP412MFDPROTRUDE=" << bonus
|
||||
<< " (indicator strip " << indicatorStrip << " -> "
|
||||
<< (indicatorStrip + bonus) << ")\n" << std::flush;
|
||||
}
|
||||
}
|
||||
return bonus;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// The map paints its own legend beside each side button, and that
|
||||
// grid is not the naive height/6: measured off the 640-tall map,
|
||||
@@ -649,7 +689,7 @@ void
|
||||
// and the picture above/below it is the click target. Paint
|
||||
// draws the buttons before the MFD image for that reason.
|
||||
//-----------------------------------------------------------
|
||||
int strip_h = indicatorStrip;
|
||||
int strip_h = indicatorStrip + ProtrudeBonus();
|
||||
int button_h = buttonDepth;
|
||||
|
||||
// on a scaled-down cockpit, keep the two banks from meeting
|
||||
@@ -692,7 +732,7 @@ void
|
||||
// indicatorStrip clearing the edge, so the lamp reads as a
|
||||
// slim column and the map itself is the click target.
|
||||
//-----------------------------------------------------------
|
||||
int strip_w = indicatorStrip;
|
||||
int strip_w = indicatorStrip + ProtrudeBonus();
|
||||
int button_w = buttonDepth;
|
||||
|
||||
// keep the two columns from meeting behind a narrow map
|
||||
|
||||
@@ -127,6 +127,7 @@ mission. Full map with pad and keyboard diagrams:
|
||||
| [docs/STEAM-3-MACHINE-TEST.md](docs/STEAM-3-MACHINE-TEST.md) | Multiplayer test procedure, Steam Input notes, the abort key |
|
||||
| [docs/RP412-LINUX.md](docs/RP412-LINUX.md) | Linux compatibility assessment — Proton path vs native port; parked until late playtesting |
|
||||
| [docs/NET-TEST.md](docs/NET-TEST.md) | Network test protocol — WAN emulation profiles, the NetLog/NetStats telemetry, the stall-recovery test, `-spoolstats` |
|
||||
| [docs/RP412-UNIFIED-BUILD.md](docs/RP412-UNIFIED-BUILD.md) | Proposal (not scheduled) — one source tree building native on Windows and Linux; the portability survey behind it |
|
||||
| [BUILD.md](BUILD.md) | Toolchain and build steps |
|
||||
|
||||
Dev tooling: `tools/two-pod-test.ps1` races two pods on loopback,
|
||||
|
||||
@@ -83,6 +83,49 @@ HWND hWnd;
|
||||
//
|
||||
static LONG WINAPI RPL4CrashDumpFilter(EXCEPTION_POINTERS *exception)
|
||||
{
|
||||
//
|
||||
// Wine raises 0x80000100 (EXCEPTION_WINE_STUB) when the game calls an
|
||||
// API Wine declares but has never implemented, and puts the module and
|
||||
// function names in the first two exception parameters as plain ANSI
|
||||
// strings. Wine prints that to its own stderr - which a player running
|
||||
// through Steam never sees - and then this filter caught the exception
|
||||
// and wrote a minidump, so a perfectly self-describing failure arrived
|
||||
// as a mystery crash. That is exactly how D3DXConcatenateMeshes cost a
|
||||
// full investigation on 4.12.233: one stubbed function out of the
|
||||
// twenty d3dx9 imports, and nothing in our log named it.
|
||||
//
|
||||
// Unlike an access violation this is an orderly raise from Wine with
|
||||
// the heap intact, so the log stream is safe to use here. Written
|
||||
// before the dump, in case the dump is what fails.
|
||||
//
|
||||
if (exception != NULL &&
|
||||
exception->ExceptionRecord != NULL &&
|
||||
exception->ExceptionRecord->ExceptionCode == 0x80000100 &&
|
||||
exception->ExceptionRecord->NumberParameters >= 2)
|
||||
{
|
||||
const char *stub_module =
|
||||
(const char *) exception->ExceptionRecord->ExceptionInformation[0];
|
||||
const char *stub_function =
|
||||
(const char *) exception->ExceptionRecord->ExceptionInformation[1];
|
||||
|
||||
if (stub_module != NULL && stub_function != NULL &&
|
||||
!IsBadStringPtrA(stub_module, 128) &&
|
||||
!IsBadStringPtrA(stub_function, 128))
|
||||
{
|
||||
DEBUG_STREAM << "\n*** WINE STUB: " << stub_module << "."
|
||||
<< stub_function << " is not implemented by this Wine.\n"
|
||||
<< "*** The game called an API Wine only declares. Shipping the"
|
||||
<< " real DLL beside the exe overrides Wine's builtin and is"
|
||||
<< " usually the whole fix; see docs/RP412-LINUX.md.\n"
|
||||
<< std::flush;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_STREAM << "\n*** WINE STUB: an unimplemented API was called"
|
||||
<< " (names unreadable)\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
HMODULE dbghelp = LoadLibraryA("dbghelp.dll");
|
||||
if (dbghelp != NULL)
|
||||
{
|
||||
@@ -225,6 +268,34 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
//
|
||||
DEBUG_STREAM << "Red Planet " << RP412_VERSION_LONG << std::endl << std::flush;
|
||||
|
||||
//
|
||||
// And whether this is Windows at all.
|
||||
//
|
||||
// Wine exports wine_get_version from ntdll and Windows does not, so
|
||||
// one GetProcAddress separates the two. It matters because the
|
||||
// failure modes differ: Wine implements Direct3D and the D3DX helpers
|
||||
// itself, and where a helper is only a stub the game dies at the call
|
||||
// with no hint in its own log (see the filter above). A tester's log
|
||||
// saying which platform it came from turns that from an investigation
|
||||
// into a lookup. Never true on Windows, where the lookup fails and
|
||||
// nothing is printed.
|
||||
//
|
||||
{
|
||||
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
|
||||
if (ntdll != NULL)
|
||||
{
|
||||
typedef const char * (__cdecl *WineGetVersionFn)(void);
|
||||
WineGetVersionFn wine_get_version =
|
||||
(WineGetVersionFn) GetProcAddress(ntdll, "wine_get_version");
|
||||
if (wine_get_version != NULL)
|
||||
{
|
||||
DEBUG_STREAM << "Platform: Wine " << wine_get_version()
|
||||
<< " (not Windows) - D3DX comes from Wine's own"
|
||||
<< " implementation\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load up our environment variables
|
||||
//controls
|
||||
if(getenv("L4CONTROLS") == NULL)
|
||||
|
||||
@@ -178,6 +178,20 @@ namespace
|
||||
"L4MAPPOS=LEFT\n"
|
||||
"L4MAPSCALE=100\n"
|
||||
"\n"
|
||||
"# How far the lit buttons around each display reach past its edge, in\n"
|
||||
"# EXTRA pixels. The pod's buttons were physical and backlit; on a\n"
|
||||
"# desktop panel they are a thin bar along the glass edge, and a\n"
|
||||
"# FLASHING one is easy to miss. This lengthens that bar - the button\n"
|
||||
"# keeps its width and still reaches the same distance behind the\n"
|
||||
"# picture, so the display and the click target are unchanged. The red\n"
|
||||
"# MFD strips and the amber map columns only.\n"
|
||||
"#\n"
|
||||
"# 0 - the default - is the pod's own geometry, exactly as it shipped.\n"
|
||||
"# 1-200 adds that many pixels. Give it a number: a word counts as 0.\n"
|
||||
"# (BattleTech 4.11 carries the same setting defaulted to 10, where its\n"
|
||||
"# players asked for it; the two games are tuned separately.)\n"
|
||||
"#RP412MFDPROTRUDE=10\n"
|
||||
"\n"
|
||||
"# The Winners Circle: at the end of a race the finishers are stood on\n"
|
||||
"# the award platform in finishing order, with each pilot's callsign on\n"
|
||||
"# the plate beside their spot, and held there for a few seconds before\n"
|
||||
|
||||
@@ -77,6 +77,23 @@
|
||||
helper. -->
|
||||
<DelayLoadDLLs>steam_api.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
|
||||
<RandomizedBaseAddress>false</RandomizedBaseAddress>
|
||||
<!-- /LARGEADDRESSAWARE: a 32-bit process gets 2GB of user ADDRESS
|
||||
SPACE by default, which is a separate and much scarcer resource
|
||||
than memory. Measured standalone mission: 166MB committed
|
||||
against 795MB of address space - the graphics driver maps VRAM
|
||||
apertures and its own bookkeeping into our map, and managed-pool
|
||||
resources are shadowed in system memory as well as VRAM, so the
|
||||
map runs about five times the memory actually used. Under Wine
|
||||
the same mission mapped 3193MB, because wined3d routes through
|
||||
OpenGL and the NVIDIA driver maps far more aggressively; on an
|
||||
older Wine that exhausted the space outright and the game died
|
||||
before video init with nothing useful in the log. This raises
|
||||
the ceiling to 4GB on any 64-bit host. It frees no memory and
|
||||
changes no behaviour - it stops the process running out of
|
||||
numbers. Equally worth having on Windows: 795MB of 2GB is
|
||||
already 40% before 1080p, eight pods, and the 100MB spool
|
||||
buffer RP412RECORDSIZE takes when recording is armed. -->
|
||||
<LargeAddressAware>true</LargeAddressAware>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<!-- rpl4build.h is generated, not committed: the patch number is the
|
||||
|
||||
+132
-11
@@ -1,12 +1,15 @@
|
||||
# RP 4.12 on Linux — compatibility assessment
|
||||
|
||||
Requirements exploration for making RP412 playable on Linux (desktop and Steam
|
||||
Deck). Status: assessment complete (2026-08-12), **parked until late
|
||||
playtesting** — nothing has been run on Linux yet; the Proton test matrix in
|
||||
§3 is the next concrete step when this resumes. A formatted copy of this
|
||||
report lives at
|
||||
Deck). A formatted copy of the original assessment lives at
|
||||
<https://claude.ai/code/artifact/47e4cf5f-a9a3-416e-bd5e-25c810983551>.
|
||||
|
||||
**Status: it runs.** The assessment below was written 2026-08-12 as prediction;
|
||||
on 2026-08-14 a field test on real hardware found and cleared the one blocker,
|
||||
and the game played a full mission on Linux. §0 is what the test found and what
|
||||
was done about it — read it first, it corrects several predictions in §2. The
|
||||
remaining work is the Deck-specific and DXVK items in §0's open list.
|
||||
|
||||
**Verdict: target Proton as the official Linux path; treat a native port as a
|
||||
separately-scoped future project.** The existing 32-bit Win32/D3D9 build is
|
||||
squarely on Proton's best-supported path, every Proton-blocking issue found is
|
||||
@@ -25,6 +28,120 @@ partner docs, the DXVK source, the steam-runtime repository, and Wine release
|
||||
notes as of August 2026, with unconfirmed items called out as needing live
|
||||
tests.
|
||||
|
||||
## 0. Field test, 2026-08-14 — one stubbed function, and a Windows bug behind it
|
||||
|
||||
Test environment: Linux Mint 22.3, RTX 5070 Ti (driver 595.84), Wine 10.0
|
||||
Staging from GE-Proton10-21, 32-bit prefix, **wined3d** renderer, windowed
|
||||
1920×1080, symbols from the shipped `rpl4opt.pdb`. Build 4.12.233.
|
||||
|
||||
**The blocker was `D3DXConcatenateMeshes`.** Wine has never implemented it.
|
||||
The game calls it exactly once per session, from `ConsolidateStaticObjects`
|
||||
([MUNGA_L4/L4VIDEO.cpp](../MUNGA_L4/L4VIDEO.cpp)) via
|
||||
[MUNGA/APP.cpp](../MUNGA/APP.cpp)'s first-mission-load latch, so every player
|
||||
hits it on their first race. Wine raised `EXCEPTION_WINE_STUB` (0x80000100)
|
||||
naming the function in plain text; our own crash filter caught it and wrote
|
||||
`rpl4crash.dmp`, so it presented as a mystery crash. Of Wine's 329 `d3dx9_43`
|
||||
exports, 63 are stubs; the game imports 20 of them; exactly one is stubbed.
|
||||
There is no second landmine on the current code path.
|
||||
|
||||
**The fix was already a Windows bug.** `d3dx9_43.dll` is a *hard* import of
|
||||
`rpl4opt.exe` — confirmed in the import table, and only `steam_api.dll` is
|
||||
delay-loaded — and it is **not part of Windows**. It ships solely with the
|
||||
June 2010 DirectX End-User Runtime. A customer on a clean Windows 10/11 who
|
||||
has never installed that redistributable cannot start the game at all: the
|
||||
loader fails at 0xC0000135 before `WinMain`, with no window and no log line.
|
||||
Every machine this was built or tested on had the SDK or some older game
|
||||
installed, which is why it went unnoticed. `pack-dist.ps1` now ships the DLL
|
||||
beside the exe, extracted from the SDK's redistributable cab. Wine prefers an
|
||||
application-directory DLL over its own builtin, so the same one file closes
|
||||
the Windows failure and the Linux blocker together — verified working on a
|
||||
stock prefix with no overrides and nothing installed.
|
||||
|
||||
> **Licensing:** the DirectX SDK terms nominate DXSETUP as the delivery
|
||||
> method for this redistributable. Shipping the loose DLL is near-universal
|
||||
> practice and is what is verified working on both platforms, but confirm the
|
||||
> terms before a public release.
|
||||
|
||||
**Address space, not memory.** The 32-bit process mapped **VmSize 3193 MB**
|
||||
under Wine, and on an older Wine that exhausted the address space outright —
|
||||
the game died before video init with nothing useful logged. It is not using
|
||||
that much memory: measured on Windows, a standalone mission peaks at **166 MB
|
||||
committed / 179 MB working set against 795 MB of address space**, already a
|
||||
~4.8× ratio. Address space is inflated by reservations, file mappings, and
|
||||
above all the graphics driver mapping VRAM apertures and its own bookkeeping
|
||||
into the process; under wined3d's OpenGL path with an NVIDIA driver that grows
|
||||
several-fold again. For a 32-bit process this is a hard 2 GB ceiling entirely
|
||||
separate from RAM, so it is possible to run out of *addresses* while using
|
||||
170 MB. `/LARGEADDRESSAWARE` is now set: it raises the ceiling to 4 GB on any
|
||||
64-bit host, frees no memory and changes no behaviour. Worth having on Windows
|
||||
too — 795 MB of 2 GB is already 40% before 1080p, eight pods, and the 100 MB
|
||||
buffer `RP412RECORDSIZE` takes when recording is armed.
|
||||
|
||||
**Diagnostics added, so the next gap names itself.** The crash filter now
|
||||
decodes 0x80000100 and writes the offending `module.function` to `rpl4.log`
|
||||
before the minidump, and startup probes `wine_get_version` in `ntdll` and logs
|
||||
the platform next to the version banner. Neither line can ever appear on
|
||||
Windows. Together they would have reduced this entire investigation to reading
|
||||
one line of a tester's log.
|
||||
|
||||
**Do NOT install the VC++ redistributable into a Wine prefix.** It *causes* a
|
||||
crash — a null dereference inside `std::mutex`, reached from
|
||||
`KeyLight_SetLogger` via `PadRIO::PadRIO`. Wine's builtin C++ runtime works
|
||||
correctly. This is a support-desk trap worth knowing, because installing the
|
||||
redistributable is a common first instinct. (On real Windows the VC++ runtime
|
||||
is genuinely required; Steam installs it as a depot prerequisite.)
|
||||
|
||||
### Predictions in §2 that the test corrected
|
||||
|
||||
- **The `windowsapp.lib` static-import worry was wrong.** §2 called it a
|
||||
load-time failure risk under Wine. The game boots, and the VC++ trace above
|
||||
runs *through* `KeyLight_SetLogger` — that code executes. Wine satisfies the
|
||||
WinRT API-set imports.
|
||||
- **`d3dx9` was the right area, the wrong function.** §2 named
|
||||
`D3DXLoadMeshFromXA` and `D3DXCreateTextureFromFile` as the heaviest leans on
|
||||
Wine's reimplementation. Both are fully implemented. The lesson is method:
|
||||
rank by *what Wine stubs*, not by what the game uses most — intersecting the
|
||||
import table against Wine's stub list yields the answer directly.
|
||||
- **Address-space exhaustion was not predicted at all**, and static analysis
|
||||
would not have found it.
|
||||
|
||||
### Still open after this test
|
||||
|
||||
- **DXVK is untested.** The test ran wined3d; Proton defaults to DXVK for
|
||||
D3D9. The §2 headline risk — `Present` into a child `STATIC` via
|
||||
`hDestWindowOverride` with GDI panes clipped over it — is specifically a
|
||||
DXVK question, and whether the run exercised cockpit split mode
|
||||
(`L4MFDSPLIT=1`) at all is not recorded. **This is the next test**, and the
|
||||
rig now exists to do it cheaply.
|
||||
- **Steam networking under Proton** — SDR/FakeIP through the lsteamclient
|
||||
bridge. The test's networking was almost certainly plain TCP.
|
||||
- **Everything Deck-specific**: gamescope with the plasma window and the
|
||||
exploded MFD view, the gamepad-complete front end, the 9 px font floor.
|
||||
- **Optional load-time polish, both platforms**: 303 `d3dx_load_pixels_from_pixels`
|
||||
calls (texture format conversion at load — shipping textures in device format
|
||||
skips it) and 150 `OptimizeInplace` calls (the `.X` loader; baking meshes to
|
||||
an in-house format at build time would retire the `.X` dependency).
|
||||
- **Swap Creative's OpenAL for OpenAL Soft** — the same shape of bug as the
|
||||
`d3dx9_43.dll` one above: `pack-dist.ps1` copies `OpenAL32.dll` out of the
|
||||
build machine's `SysWOW64`, and what ships is Creative's 2009 router +
|
||||
`wrap_oal.dll` + a 791 KB installer. Bundling OpenAL Soft instead means the
|
||||
same implementation runs on Windows and Linux. Scoped in
|
||||
[RP412-UNIFIED-BUILD.md](RP412-UNIFIED-BUILD.md) — near-term, independent of
|
||||
the port.
|
||||
- **The native port** is scoped separately in
|
||||
[RP412-UNIFIED-BUILD.md](RP412-UNIFIED-BUILD.md) (proposal, not scheduled).
|
||||
Its survey supersedes several figures in §1 and §3 below — notably that
|
||||
`/Zp1` affects exactly one struct, that the `<windows.h>` leak into engine
|
||||
core is `NETWORK.h` rather than `MATRIX.h`, and that ISO conformance rather
|
||||
than include hygiene is what actually blocks a GCC compile.
|
||||
- **The durable fix for the blocker**: replace `D3DXConcatenateMeshes` with a
|
||||
hand-written mesh merge, ~60–80 lines using only helpers Wine implements
|
||||
(`D3DXCreateMesh`, `D3DXGetDeclVertexSize`, `D3DXVec3TransformCoordArray`,
|
||||
`D3DXMatrixInverse`, …). Worth doing eventually because `D3DXSimplifyMesh`
|
||||
and `D3DXSplitMesh` are stubs too, so any future mesh work hits the same
|
||||
wall. Verify by comparing vertex/face counts and attribute ranges against
|
||||
the D3DX output on Windows.
|
||||
|
||||
## 1. How the Windows dependency actually distributes
|
||||
|
||||
The architecture is far more portable than the mechanics. The engine is
|
||||
@@ -94,28 +211,32 @@ The multiplayer test matrix should pin that as the minimum version.
|
||||
|
||||
### Fix in the Windows build (confirmed in code)
|
||||
|
||||
> Written 2026-08-12 as prediction. The first row is **disproven** by the
|
||||
> field test and is kept only so the reasoning is on the record; rows 2–5
|
||||
> stand and remain Deck work. See §0.
|
||||
|
||||
| Item | Evidence | Candidate fix |
|
||||
|------|----------|---------------|
|
||||
| **WinRT lamp mirror is a static import.** `windowsapp.lib` via `#pragma comment` — unlike `steam_api.dll`, not delay-loaded. Wine has no `windows.devices.lights` at all; the in-code try/catch guards only run if the image loads. | [MUNGA_L4/L4KEYLIGHT.cpp](../MUNGA_L4/L4KEYLIGHT.cpp) line 33 | Delay-load it the way `steam_api.dll` is handled, or move activation behind a `LoadLibrary` probe. Cheap, and hardens the Windows build too. |
|
||||
| ~~**WinRT lamp mirror is a static import.**~~ **WRONG — see §0.** The reasoning was that `windowsapp.lib` is not delay-loaded and Wine has no `windows.devices.lights`, so the image would fail to load before the in-code guards could run. In practice Wine satisfies the WinRT API-set imports and the game boots; `KeyLight` code demonstrably executes. | [MUNGA_L4/L4KEYLIGHT.cpp](../MUNGA_L4/L4KEYLIGHT.cpp) line 33 | None needed. |
|
||||
| **Second top-level window fights gamescope.** The "Plasma Display" window plus the exploded `L4MFDSPLIT=2` mode (6 top-level panes) hit gamescope's known multi-window weakness on Deck. | [MUNGA_L4/L4PLASMASCREEN.cpp](../MUNGA_L4/L4PLASMASCREEN.cpp) line 271, [MUNGA_L4/L4MFDVIEW.cpp](../MUNGA_L4/L4MFDVIEW.cpp) line 297 | On Deck/gamescope, default the plasma glass into the cockpit window (the `SetParent` path already exists at `L4PLASMASCREEN.cpp:91`) and treat exploded view as desktop-only. |
|
||||
| **Implicit hit-test transparency.** Clicks fall through the viewscreen only because the stock STATIC proc returns `HTTRANSPARENT`; no explicit `WM_NCHITTEST` handler exists. If Wine's STATIC differs, all mouse input over the 3D view dies silently. | [MUNGA_L4/L4VB16.cpp](../MUNGA_L4/L4VB16.cpp) line 4659 | Handle `WM_NCHITTEST` explicitly in the viewscreen subclass. Removes the dependency on an undocumented control behavior everywhere. |
|
||||
| **Deck legibility floor.** The 1920×1080 canvas lands on Deck at 1280×720 — a 1.5× shrink. Valve's gate is 9 px minimum font height, so anything under ~14 px on the canvas fails. | Fixed canvas: [MUNGA_L4/L4APP.cpp](../MUNGA_L4/L4APP.cpp) line 269 | Audit the smallest cockpit and front-end type; bump or provide a Deck-scale preset. |
|
||||
| **Gamepad-complete front end.** The menu/lobby is mouse-driven GDI; callsign entry is a Win32 `EDIT` control. Deck Verified requires the full flow on pad alone, with the Steam on-screen keyboard for text. | [RP_L4/RPL4FE.cpp](../RP_L4/RPL4FE.cpp) line 2467 | Add pad navigation to the front end and invoke `ShowFloatingGamepadTextInput` for the callsign field. The largest Path-A work item. |
|
||||
|
||||
### Verify under Proton (needs live testing; no code change assumed)
|
||||
### Verify under Proton (status as of the 2026-08-14 field test)
|
||||
|
||||
- **Child-window Present**: `Present(…, hDestWindowOverride)` into a child
|
||||
STATIC under DXVK — supported per the DXVK source (per-HWND presenter map)
|
||||
and Wine 10's child-window Vulkan work, but this exact composition (GDI
|
||||
siblings clipped over the presented pane) is the least-exercised path in any
|
||||
D3D9 stack. First thing to smoke-test.
|
||||
D3D9 stack. **STILL OPEN — the field test ran wined3d, not DXVK.** Next test.
|
||||
- **Steam networking end-to-end**: repeat the three-machine SDR/FakeIP race
|
||||
([STEAM-3-MACHINE-TEST.md](STEAM-3-MACHINE-TEST.md)) with one or more peers
|
||||
on Proton ≥ 10.0-4b. No Proton-specific FakeIP defects are on record, but
|
||||
absence of reports is not confirmation.
|
||||
- **Wine's d3dx9**: `D3DXLoadMeshFromXA` (.x meshes) and
|
||||
`D3DXCreateTextureFromFile` (PNG) are the two heaviest leans on Wine's
|
||||
reimplementation.
|
||||
absence of reports is not confirmation. **STILL OPEN.**
|
||||
- ~~**Wine's d3dx9**: `D3DXLoadMeshFromXA` and `D3DXCreateTextureFromFile`~~ —
|
||||
**DONE, and both are fine.** The stubbed import was `D3DXConcatenateMeshes`;
|
||||
see §0.
|
||||
- **Input odds and ends**: the `IG_` RawInput device-path heuristic for XInput
|
||||
de-duplication (`L4JOY.cpp:72-146`) assumes Windows-shaped HID paths;
|
||||
`GetAsyncKeyState` + foreground-window focus gating under gamescope's focus
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
# RP 4.12 — unified Linux/Windows native build (proposal)
|
||||
|
||||
**Status: proposal, not scheduled.** Written 2026-08-14 against HEAD `9fae440`.
|
||||
Nothing here is committed to. It is filed so the measurements survive — the
|
||||
survey behind it took three full sweeps of the tree, and the numbers are the
|
||||
durable part whether or not the plan is ever run.
|
||||
|
||||
The shipping Linux path today is **Proton**, and it works: see
|
||||
[RP412-LINUX.md](RP412-LINUX.md) §0 for the field test and the `d3dx9_43.dll`
|
||||
fix that unblocked it. This document describes the *other* thing — one source
|
||||
tree producing a native binary on both platforms — and exists because the
|
||||
question "what would that actually take?" deserved a real answer rather than a
|
||||
guess.
|
||||
|
||||
## Do this one now, independent of the plan
|
||||
|
||||
**Swap Creative's OpenAL for OpenAL Soft.** This is not port work. It is the
|
||||
same shape of problem as the `d3dx9_43.dll` bug found on 2026-08-14 — a runtime
|
||||
dependency the build machine quietly satisfies — and it is worth fixing on
|
||||
Windows whether or not any of the phases below ever run.
|
||||
|
||||
What ships today is Creative's 2009-era stack: `OpenAL32.dll` v6.14 is only a
|
||||
**router** that dispatches to `wrap_oal.dll` v2.2.0.5 (the real implementation),
|
||||
with `oalinst.exe` (791 KB) bundled as a fallback installer. Worse,
|
||||
`pack-dist.ps1` copies `OpenAL32.dll` **out of the build machine's `SysWOW64`**,
|
||||
so the audio stack that ships is whatever happened to be installed on the
|
||||
machine that packed it. The router architecture exists so multiple vendors'
|
||||
implementations could coexist, which stopped mattering fifteen years ago.
|
||||
|
||||
The work:
|
||||
|
||||
- Bundle **OpenAL Soft** — ship its `soft_oal.dll` renamed to `OpenAL32.dll`,
|
||||
vendored in the repo like `steam_api.dll` is, not copied from the system.
|
||||
- Delete `wrap_oal.dll` and `oalinst.exe` from `dist\` and from
|
||||
`pack-dist.ps1`; drop the "copy from `SysWOW64` if present" branch. Net
|
||||
~1.2 MB smaller.
|
||||
- Replace the vendored Creative headers in `MUNGA_L4\openal\` with OpenAL
|
||||
Soft's. Only `al.h`, `alc.h` and `efx.h` are ever included (11 sites across
|
||||
10 files) — `efx-creative.h`, `EFX-Util.h` and `xram.h` are referenced by
|
||||
**nothing** and can simply go.
|
||||
- Replace `lib\OpenAL32.lib` with OpenAL Soft's import library.
|
||||
|
||||
Why it is worth doing on its own merits:
|
||||
|
||||
- **The same implementation then runs on both platforms**, since Linux distros
|
||||
ship OpenAL Soft as `libopenal.so`. Audio bugs reproduce across platforms
|
||||
instead of being "works on my Windows."
|
||||
- It removes the **only `__declspec` in the entire tree**, which lives in
|
||||
Creative's `al.h`.
|
||||
- It ends the silent dependency on the packing machine's system state.
|
||||
|
||||
Licensing is unremarkable: OpenAL Soft is LGPL, and shipping it as a separate,
|
||||
dynamically-linked DLL is the standard compliant arrangement for a closed-source
|
||||
game.
|
||||
|
||||
*Verify: audio plays in a mission; EFX reverb still engages (`EFX_Available()`
|
||||
in `L4AUDEFX.cpp` — it probes `ALC_EXT_EFX` and degrades silently, so confirm
|
||||
the log does **not** say "filters and reverb inert"); the two-pod harness
|
||||
passes; `dist\` contains no `wrap_oal.dll` or `oalinst.exe`.*
|
||||
|
||||
**What this does *not* do: remove OpenAL.** The API is cross-platform and is
|
||||
precisely why audio barely features in the phases below — all ~91 call sites
|
||||
compile unchanged against `libopenal.so`. Dropping OpenAL entirely is possible
|
||||
in principle (the game disables OpenAL's own attenuation and Doppler, does its
|
||||
own 3D math, and never streams — it is using OpenAL as little more than a
|
||||
mixer) but would mean reimplementing EFX reverb and rewriting ~91 working call
|
||||
sites to remove a dependency that causes no portability problem. Not
|
||||
recommended.
|
||||
|
||||
## The four decisions this plan assumes
|
||||
|
||||
Taken 2026-08-14. Changing any of them changes the plan's shape.
|
||||
|
||||
| Question | Decision | Consequence |
|
||||
|----------|----------|-------------|
|
||||
| Cross-play Windows↔Linux? | **Yes, same race** | Serialized platform-sized types must be pinned to fixed width |
|
||||
| Renderer | **GL on Linux now, converge Windows later** | The GL path must be Windows-capable from day one — SDL context creation, portable GL loader, no GLX-specific code |
|
||||
| Build system | **One CMake for both** | The working MSBuild build gets migrated; highest-regression-risk step |
|
||||
| What ships meanwhile | **Proton stays the shipping path** | No deadline pressure; Deck work proceeds separately and benefits both |
|
||||
|
||||
## What was measured
|
||||
|
||||
The survey is the reason to keep this document. Three findings materially
|
||||
changed the risk picture from the earlier 2026-08-12 assessment.
|
||||
|
||||
### 1. `/Zp1` is nearly a non-issue
|
||||
|
||||
The prior audit flagged project-wide 1-byte struct packing as a serious
|
||||
entanglement with "no manifest of which structs matter." There is now a
|
||||
manifest. Of **31 distinct whole-object stream types**, 29 are enums, scalar
|
||||
typedefs, or all-`float` aggregates. Modelled at both packings and compared by
|
||||
`sizeof`, exactly **one** struct changes layout:
|
||||
|
||||
```c
|
||||
// MUNGA_L4/L4D3D.cpp:264-273 — read whole from every .met file at :282
|
||||
struct { unsigned char minify, magnify, alpha, wrap_u, wrap_v;
|
||||
bool doScroll; float scrollUDelta, scrollVDelta; } fileTexOp;
|
||||
// /Zp1 = 14 bytes; default packing = 16 (padding after the bool)
|
||||
```
|
||||
|
||||
Dropping `/Zp1` costs one `#pragma pack(1)`. It also retires
|
||||
`WINDOWS_IGNORE_PACKING_MISMATCH`, the Steamworks `pack(push,8)` guards, and
|
||||
the latent `RPL4TOOL`-vs-`rpl4opt` packing divergence that `/FORCE:MULTIPLE`
|
||||
currently hides.
|
||||
|
||||
### 2. ISO conformance is the real blocker, not includes
|
||||
|
||||
Under GCC 15 with `-fsyntax-only`, **172 of 172 `MUNGA`+`RP` translation units
|
||||
fail — and 171 still fail with every 64-bit-related error removed.** The
|
||||
keystone is one line:
|
||||
|
||||
```cpp
|
||||
// MUNGA/SCHAIN.h — friend-declared name used as a type
|
||||
class SChainLink : public Link {
|
||||
friend class SChain; // :10
|
||||
SChainLink(SChain *chain, Plug *plug, …); // :18 ← not a type under ISO
|
||||
};
|
||||
```
|
||||
|
||||
`schain.h` sits in `MUNGA.h`'s precompiled block, so that single line breaks
|
||||
the entire core. The same pattern appears at **155 `friend class` sites over
|
||||
117 names**. Other classes: iterator typedefs of friend-declared names (66
|
||||
TUs), `Now()`/`HiResNow()` declared friend-only inside `SystemClock` but called
|
||||
at 50+ sites (25 TUs), `new (Subsystem (*[n]))` array syntax GCC parses as a
|
||||
lambda (4 sites), `__try`/`__except`, `std::ios::_Openprot`, MSVC `<float.h>`
|
||||
`SW_*` constants, `goto` crossing initialization.
|
||||
|
||||
Roughly **30 distinct sites across ~20 headers** — small in absolute terms,
|
||||
but they gate all 241 TUs.
|
||||
|
||||
### 3. LP64 has two systemic breaks
|
||||
|
||||
- `MEMSTRM.H:20` `void Rewind() { SetPointer(0U); }` becomes ambiguous between
|
||||
`SetPointer(void*)` and `SetPointer(size_t)` the moment `size_t` ≠
|
||||
`unsigned int`. **Fails in 171 of 172 TUs on 64-bit, zero on 32-bit.**
|
||||
- On LP64, `size_t` matches **none** of the eight `MemoryStream_Read`
|
||||
overloads. On ILP32 it silently binds to the `unsigned int` one.
|
||||
|
||||
### The good news, quantified
|
||||
|
||||
- **`RP\` is 100% clean** — 0 of 20 TUs touch a platform API. Of 241 compiled
|
||||
TUs, **190 touch no Win32/D3D/Winsock symbol at all**.
|
||||
- **The renderer's distinct surface is tiny**: 19 render states, 7
|
||||
texture-stage states (stages 1–2 only ever `D3DTOP_DISABLE`, so effectively
|
||||
one texture unit), 5 sampler states hardcoded to linear, 5 FVFs, **one** mesh
|
||||
draw call, no shaders, no indexed draws, no render targets, no stencil, no
|
||||
scissor, one directional light.
|
||||
- **The assets are a small closed subset**: all 224 `.X` files are *text*
|
||||
format using 8 templates, no skinning, no vertex colours; 56,521 verts total,
|
||||
largest single mesh 2,195. All 104 PNGs are 8-bit RGBA.
|
||||
- **The gauge/MFD production path is already portable C++** — `GRAPH2D`,
|
||||
`GAUGREND`, `GAUGE`, `GAUGMAP`, `L4VB8`, `L4GAUGE`, `L4GREND`, `L4GAUIMA`,
|
||||
`RPL4GAUG` all measure zero Win32/D3D hits, plus **~4,700 of `L4VB16.cpp`'s
|
||||
7,230 lines** (the software rasterizer, the `L4GraphicsPort` channel packing
|
||||
that emulates the pod's five-MFDs-in-two-video-outputs trick, the palette
|
||||
fades). Only its *destination* is platform-bound.
|
||||
- **`RIOBase` — the whole input seam — is 2 pure virtuals** (`GetNextEvent`,
|
||||
`SetLamp`), 13 virtuals with working empty bodies, and 5 public `Scalar`s.
|
||||
`PadRIO` implements it in 6 methods.
|
||||
- **The GDI vocabulary is minimal**: no `Rectangle`, `Ellipse`, `LineTo` or
|
||||
`CreatePen` anywhere. The entire visual language is `FillRect`, `FrameRect`,
|
||||
`DrawTextA`, one `Polygon` (a combo arrow), `BitBlt`, `StretchDIBits`.
|
||||
|
||||
### Counts at HEAD
|
||||
|
||||
1856 quoted includes · 351 with backslashes · **1764 case-wrong** (only 30
|
||||
fully correct) across 468 files · 226 `#pragma hdrstop` · 129 `stricmp` in 28
|
||||
files · 12 `stdext::hash_map` in 5 files · **54 `PostQuitMessage`** in 17 files
|
||||
· 111 `getenv` in 38 files · 421 user32/gdi32 calls in 27 files · 54 backslash
|
||||
path literals in 17 files, of which 8 are directory-case and 3 file-case
|
||||
mismatches against disk.
|
||||
|
||||
### Confirmed dead — delete rather than port
|
||||
|
||||
`DivLoader` (zero references tree-wide; `DivLoader.lib` is 2.6 MB and linked by
|
||||
nothing) · `RPL4Lobby_Room`/`_Join` plus `RunRoom`/`PaintRoom`/`RoomWndProc`,
|
||||
~410 lines with no callers — the front end absorbed the room · `L4KEYBD` (only
|
||||
call site commented out) · `L4MOUSE` (fully stubbed) · `MUNGA_L4/sos/` and
|
||||
`libDPL/` · `LOGGER.cpp` (on disk, in no project) · the 44 `.tcp` test includes.
|
||||
|
||||
## The strategic shape
|
||||
|
||||
**Almost all the work happens on Windows, verified by the existing build and
|
||||
the two-pod harness, before a single Linux compile is attempted.** Phases 0–3
|
||||
make the *Windows* build ISO-conformant, platform-decoupled, fixed-width and
|
||||
CMake-built, each step green against a live race. Only Phase 4 adds Linux.
|
||||
|
||||
That ordering is the point. It is what stops a year-long project from having a
|
||||
six-month stretch where nothing runs and nothing can be tested.
|
||||
|
||||
## Phases
|
||||
|
||||
### Phase 0 — ISO conformance and hygiene *(Windows only)*
|
||||
|
||||
Fix the `friend class`-as-type family (keystone `SCHAIN.h:18`; same shape in
|
||||
`CAMINST.h`, `ENTITY.h`, `INTORGN.h`, `PLUG.h`, `TRACE.h`, `L4AUDHDW.h`, and
|
||||
the iterator typedefs in `HOSTMGR.h`, `HOST.h`, `COLLORGN.h`, `COLLASST.h`,
|
||||
`RNDORGN.h`, `MISSION.h`). Declare `Now()`/`HiResNow()`/
|
||||
`Get_Frame_Percent_Used()` at namespace scope. Clear the ~30 remaining
|
||||
conformance sites. Scripted include normalization across 468 files. A CRT
|
||||
compat header (`stricmp`, Annex K `_s`, `ostrstream`→`ostringstream`, `itoa`).
|
||||
Drop `#pragma hdrstop`. Delete the dead list.
|
||||
|
||||
*Gate: MSBuild Release clean · two-pod passes · `g++ -fsyntax-only` reaches
|
||||
zero errors over `MUNGA`+`RP` on a 32-bit target.*
|
||||
|
||||
### Phase 1 — Break platform coupling *(Windows only)*
|
||||
|
||||
- **`MUNGA/NETWORK.h:8-9` is the real `<windows.h>` leak** — `<Winsock2.h>`
|
||||
reaches `APP.h` and **157 TUs**, and is why `APPMGR.h:5`'s bare
|
||||
`extern HWND ghWnd;` compiles at all. (`MATRIX.h`'s `<D3DX9.h>` is the other
|
||||
route and is much narrower: 10 files, 2 members.) Replace `SOCKADDR_IN` in
|
||||
engine headers with a portable address struct.
|
||||
- `VIDREND.h:60` `std::vector<MONITORINFO>` → portable rect struct.
|
||||
`APPMGR.h`/`SPOOLER.h` `HWND`/`HINSTANCE` → opaque handle.
|
||||
- Split `RIOBase` out of `L4RIO.h` (lines 16–220 have no Win32 dependency; only
|
||||
`RIO` below needs `l4pcspak.h` → `<windows.h>`). Retype ~8 `RIO::` →
|
||||
`RIOBase::` in `L4CTRL.h`.
|
||||
- `PostQuitMessage` → the portable abort hook whose prototype is still
|
||||
commented out at `MUNGA/STYLE.H:31` (54 sites).
|
||||
- Remove `<dos.h>` from `MUNGAL4.H:6` (reaches 62 TUs). Fix the 3 OpenAL calls
|
||||
leaking into `MUNGA/AUDIO.cpp`.
|
||||
- One `ResolveAssetPath()` for separator, case, and an **exe-relative base** —
|
||||
`GetModuleFileName` is called *nowhere* today, so everything resolves against
|
||||
the working directory, which breaks read-only installs and XDG conventions.
|
||||
**Careful:** `RPL4VID.cpp:1099` does `TempFileName[8] = skeleton_type;`,
|
||||
hardcoding the `"video\\"` prefix length.
|
||||
|
||||
*Gate: MSBuild clean · two-pod passes · a race against an **unmodified** build
|
||||
still works, since nothing on the wire has changed yet.*
|
||||
|
||||
### Phase 2 — Fixed-width formats and LP64 readiness *(Windows only)*
|
||||
|
||||
Retype, don't redesign — the files already contain 32-bit values.
|
||||
|
||||
Pin to `uint32_t`: `RESOURCE.h:208-210` and the `.RES` offset table
|
||||
(`RESOURCE.cpp:658/807/867` — note `:867` already writes `sizeof(long)` against
|
||||
`:658`'s `sizeof(size_t)`, a latent inconsistency) · `RECEIVER.h:172`
|
||||
`messageLength`, which is in every wire packet and every spool byte ·
|
||||
`SIMULATE.h:57`, `DAMAGE.h:137`, `BOXSOLID.h:171`, `CAMINST.h:12` ·
|
||||
`TIME.h:53` `Time::ticks` · the spool headers in `SPOOLER.cpp` and
|
||||
`L4CTRL.cpp:2662` · `L4VIDEO.cpp:920/970` `fread(sizeof(size_t))`.
|
||||
|
||||
`L4NET.H:404/431` `unsigned long streamPointer` carries a `SOCKET` **and those
|
||||
classes are on the wire** — must become a fixed-width handle.
|
||||
|
||||
Fix the `MemoryStream` overload set for LP64. Handle `VDATA.h:9/12`
|
||||
(`RegisteredClassMemoryAddress` is a truncating pointer cast used as a
|
||||
container key) and `VERIFY.cpp:132/213` `*(int*)(0xFFFFFFFF)`, a hard 64-bit
|
||||
compile error.
|
||||
|
||||
Drop `/Zp1`; add `#pragma pack(1)` to `fileTexOp`.
|
||||
|
||||
**Egg text rasterization**: `AppendNameBitmap` (`RPL4FE.cpp:1809`) uses GDI to
|
||||
rasterize the callsign into 1bpp hex rows **inside the egg**. With cross-play
|
||||
on, both platforms must emit identical bytes — so this moves to a bundled
|
||||
bitmap font used on **both**, not a native text API on each.
|
||||
|
||||
*Gate: `.RES` loads unchanged (validated through
|
||||
`tools/resbuild/build-res.ps1`, the only regeneration path) · `.met` textures
|
||||
still read · a cross-version race proving the wire is byte-identical.*
|
||||
|
||||
### Phase 3 — CMake migration *(Windows first)*
|
||||
|
||||
Reproduce the settings exactly: Unicode charset, the four defines,
|
||||
`/FORCE:MULTIPLE`, `steam_api.dll` delay-load + `delayimp`, `/DYNAMICBASE:NO`,
|
||||
`LargeAddressAware`, LTCG/COMDAT in Release, `$(DXSDK_DIR)` paths appended
|
||||
*after* the Windows SDK, and the `stamp-version` pre-build step (~30 lines of
|
||||
CMake; the logic has nothing PowerShell-specific).
|
||||
|
||||
**Use `-iquote`, never `-I`** for the source directories: `MUNGA/TIME.h`,
|
||||
`SET.h`, `RANDOM.h`, `ITERATOR.h` collide with standard headers — verified to
|
||||
break `<pthread.h>` → `<time.h>` → `MUNGA/TIME.h`.
|
||||
|
||||
*Gate: the CMake-built Windows exe passes the two-pod harness and a smoke
|
||||
mission before MSBuild is retired.*
|
||||
|
||||
### Phase 4 — The Linux platform layer
|
||||
|
||||
- **SDL3** for window, events, timers, input. **Map SDL scancodes into the
|
||||
Windows VK namespace** — `bindings.txt` stores VK codes and is a shipped,
|
||||
user-editable file; changing the namespace breaks every existing profile.
|
||||
- **GL 2.1 compatibility profile** implementing `VideoRenderer` — a near-1:1
|
||||
mapping for fixed-function. `D3DZB_USEW` has no GL equivalent: use a normal
|
||||
depth buffer and `glPolygonOffset` for the decal pass, which is the correct
|
||||
GL tool and replaces the `_33 -= 5e-7` projection hack.
|
||||
- **Asset loaders**: a text `.X` parser for the 8 templates in use; `stb_image`
|
||||
for PNG; a hand-written mesh merge replacing `D3DXConcatenateMeshes`. **The
|
||||
merge must reproduce D3DX's subset-numbering order exactly** —
|
||||
`ConsolidateSingleObject` remaps attribute IDs against it and both
|
||||
lookup-failure branches are empty `//Freak out` blocks. It must also preserve
|
||||
one-texture-object-per-image, because `HashDrawOp` hashes the raw texture
|
||||
pointer (which is why the 1×1 per-plate marker textures exist).
|
||||
- **BSD sockets** behind the existing `NetTransport` seam (3 errno branches).
|
||||
**Steam via `dlopen` + `steam_api_flat.h`** — ELF has no delay-load
|
||||
equivalent and C++ vtable calls can't be resolved by `dlsym`. The
|
||||
`linux32`/`linux64` `.so` files are already on disk but `.gitignore`d.
|
||||
- **Shims**: `FILESTUB` (6 syscalls; `_lseek`'s `long` caps files at 2 GB) ·
|
||||
`L4TIME` → `clock_gettime` · the console thread → `std::thread` · the crash
|
||||
handler → `signal` + `backtrace`.
|
||||
- **Fix the particle VB lock length** (`L4PARTICLES.cpp:513` requests 1/6 of
|
||||
what it writes) — benign under `D3DLOCK_DISCARD`, not under a mapped GL range.
|
||||
|
||||
*Gate: a Linux binary reaching `LocalConsole: mission running` with no crash,
|
||||
then a Linux↔Windows two-machine race.*
|
||||
|
||||
### Phase 5 — The UI
|
||||
|
||||
Reimplement the front end's drawing against a portable primitive set: rect
|
||||
fill, rect frame, single-line text with ellipsis, a monospace font at a
|
||||
requested pixel height, a text-extent query, plus dropdown and single-line-entry
|
||||
widgets. `RPL4FE.cpp` is ~32% drawing/windowing and ~68% already portable, and
|
||||
`LayoutMenu` is 137 lines of pure rect arithmetic before it touches Win32.
|
||||
Candidate substrate: the engine's own `GraphicsDisplay` interface
|
||||
(`MUNGA/GRAPH2D.h:213`), which already provides `DrawFilledRectangle`,
|
||||
`DrawText` and `DrawBitMap`.
|
||||
|
||||
### Phase 6 — Convergence
|
||||
|
||||
Switch Windows to the GL renderer, retiring the D3D9 path, the June-2010 SDK
|
||||
dependency and `d3dx9_43.dll` shipping — but only after the GL path has field
|
||||
time on Linux.
|
||||
|
||||
## Risks
|
||||
|
||||
- **`.gitattributes` forces `eol=crlf`** on all source in the working tree, so a
|
||||
Linux checkout gets CRLF. Decide early: normalize to LF, or keep and ensure
|
||||
tooling tolerates it.
|
||||
- **Resource-embedded filenames** (`GRAPH2D.cpp:188/614/801`,
|
||||
`RPL4VID.cpp:1093`) concatenate a prefix with names read out of `RPL4.RES`;
|
||||
their case cannot be determined from source and needs a resource-table dump.
|
||||
- **Phase 3 is the highest-regression-risk step** — it replaces a working build
|
||||
system for a shipping product. It is deliberately placed after the code is
|
||||
already portable, so a rollback to MSBuild stays possible.
|
||||
- **`bindings.txt` and the egg name bitmaps are user- and wire-visible
|
||||
formats.** Both are handled above, but a mistake in either is a silent
|
||||
compatibility break.
|
||||
|
||||
## When this would *not* be worth doing
|
||||
|
||||
Stated plainly, because the honest answer matters more than the plan:
|
||||
|
||||
Proton already delivers the Linux player experience, and Phases 0–2 are the
|
||||
only parts with standalone value (ISO conformance, decoupled seams, fixed-width
|
||||
formats and an exe-relative path layer would each improve the Windows build on
|
||||
their own). If the goal is "Linux players can buy and play it," that goal is
|
||||
**already met** by the shipping path. This project is worth starting when the
|
||||
goal is different: retiring a 2010-era closed SDK dependency, owning the render
|
||||
path outright, or removing Wine as a variable in support. If none of those
|
||||
becomes pressing, Phases 0–2 can be cherry-picked as ordinary maintenance and
|
||||
the rest left on the shelf.
|
||||
@@ -1133,6 +1133,7 @@
|
||||
<tr><th>L4MFDSCALE_<span style="color:var(--phosphor)">xx</span></th><td>Any one on its own — <span class="addr" style="font-family:var(--mono)">UL UC UR LL LR</span>. Unset inherits the line above.</td></tr>
|
||||
<tr><th>L4RADARSCALE</th><td>The radar alone. It already sits at 1.35× the MFDs.</td></tr>
|
||||
<tr><th>L4RADARPOS</th><td class="mono">CENTER · LEFT · RIGHT · MIDLEFT · MIDRIGHT</td></tr>
|
||||
<tr><th>RP412MFDPROTRUDE</th><td>Extra pixels of lit button reaching past each display edge, so a flashing one is easier to catch. A longer bar, not a wider button — the picture and the click target are unchanged. <span style="color:var(--ink-quiet)">0–200, default 0 (the pod's own geometry)</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
# TEST.EGG) - but not the arcade launch scripts or the old 4.10 exe
|
||||
# - libsndfile-1.dll beside the exe; OpenAL32.dll copied from the system
|
||||
# when installed, with oalinst.exe included as the fallback installer
|
||||
# - d3dx9_43.dll from the DirectX SDK redist cab: a hard import that is
|
||||
# NOT part of Windows, and also what makes the game run under Proton
|
||||
# (environ.ini is NOT shipped - the exe writes it on first run)
|
||||
# - start/joyconfig scripts, HANDBOOK.html, VTV-PRESETS.html, TRACKS.html,
|
||||
# CONTROLS.txt and a README
|
||||
@@ -192,6 +194,53 @@ $handbookPage
|
||||
"$dist\TRACKS.html",
|
||||
[System.IO.File]::ReadAllBytes((Join-Path $root 'docs\tracks.html')))
|
||||
|
||||
# --- D3DX runtime ----------------------------------------------------------
|
||||
# d3dx9_43.dll is a HARD import of the exe, and it is NOT part of Windows.
|
||||
# It ships only with the June 2010 DirectX End-User Runtime, so a customer
|
||||
# who has never installed that redistributable - which is most of them, on a
|
||||
# clean Windows 10 or 11 - cannot start the game at all: the loader fails at
|
||||
# 0xC0000135 before WinMain, with no window and no log line. Every machine
|
||||
# this has ever been built or tested on had the SDK or some older game
|
||||
# installed, which is exactly why it went unnoticed.
|
||||
#
|
||||
# The same file is also what makes the game work under Wine/Proton. Wine
|
||||
# implements 63 of d3dx9_43's 329 exports as stubs, and the game imports
|
||||
# exactly one of them - D3DXConcatenateMeshes, called once per session from
|
||||
# ConsolidateStaticObjects on first mission load. Wine loads a DLL from the
|
||||
# application directory in preference to its own builtin, so shipping the
|
||||
# Microsoft one here means the stub is never reached. One file closes a
|
||||
# hard Windows failure and the entire Linux blocker at once.
|
||||
#
|
||||
# Taken from the SDK's redistributable cab rather than from SysWOW64: that
|
||||
# cab is the licensed redistribution source, and it is the same signed
|
||||
# Microsoft binary. NOTE: the DirectX SDK terms nominate DXSETUP as the
|
||||
# delivery method for this redistributable - shipping the loose DLL is
|
||||
# near-universal practice and is what is verified working on both
|
||||
# platforms, but confirm the terms before a public release.
|
||||
$d3dxCab = Join-Path $env:DXSDK_DIR 'Redist\Jun2010_d3dx9_43_x86.cab'
|
||||
$d3dxDll = Join-Path $dist 'd3dx9_43.dll'
|
||||
if ($env:DXSDK_DIR -and (Test-Path $d3dxCab)) {
|
||||
& expand.exe $d3dxCab -F:d3dx9_43.dll $dist | Out-Null
|
||||
if (Test-Path $d3dxDll) {
|
||||
Write-Host " d3dx9_43.dll extracted from the DirectX SDK redist cab"
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path $d3dxDll)) {
|
||||
# Fall back to an installed copy so a machine without the SDK can still
|
||||
# produce a working package.
|
||||
$d3dxSystem = "$env:WINDIR\SysWOW64\d3dx9_43.dll"
|
||||
if (-not (Test-Path $d3dxSystem)) { $d3dxSystem = "$env:WINDIR\System32\d3dx9_43.dll" }
|
||||
if (Test-Path $d3dxSystem) {
|
||||
Copy-Item $d3dxSystem $dist
|
||||
Write-Host " d3dx9_43.dll copied from $(Split-Path $d3dxSystem)"
|
||||
} else {
|
||||
Write-Warning ("d3dx9_43.dll NOT FOUND - this package will fail to start on any " +
|
||||
"machine without the June 2010 DirectX runtime, and will crash " +
|
||||
"under Wine/Proton on first mission load. Install the DirectX SDK " +
|
||||
"or the end-user runtime and repack.")
|
||||
}
|
||||
}
|
||||
|
||||
# --- OpenAL runtime --------------------------------------------------------
|
||||
# The exe links OpenAL32.dll (32-bit). Prefer shipping the already-installed
|
||||
# runtime beside the exe; oalinst.exe covers machines where that misses.
|
||||
|
||||
Reference in New Issue
Block a user