Linux is Proton's problem to solve, not ours to port to

Cyd asked what it would take to run on Linux. The answer is filed rather
than acted on: we are mid-playtest, and this is a decision for the end of
it.

Proton is the recommendation. The build is 32-bit fixed-function D3D9,
which is the most-travelled path Proton has, and Valve keeps i386 alive
in the Steam runtime containers specifically to carry it; Wine 10 fixed
child-window Vulkan rendering, which is the one thing that would have
stopped the cockpit dead, since the 3D scene presents into a child STATIC
with GDI panes clipped over it. Deck Verified is achievable from a
Windows-only build and is what comparable Win32 titles ship.

The native port is scoped in the doc rather than dismissed, because the
engine is more portable than it looks: RP is clean C++, MUNGA is ~90%
clean, single-threaded, with one timing seam and one file seam already
named. What stops it is mechanical and total - 1,800 of 1,844 include
lines cite a filename in a case no case-sensitive filesystem will find -
and then strategic: the wire format and .RES carry size_t under /Zp1, so
a 64-bit build silently changes the protocol while a 32-bit one targets a
runtime Valve is walking away from.

Five things need doing to the Windows build either way, and they are all
improvements to it on their own: the Dynamic Lighting mirror imports
windowsapp.lib statically, so its careful try/catch degradation never
runs under Wine - the image fails to load first; the plasma window and
the exploded MFD view are extra top-level windows, which is what gamescope
handles worst; the viewscreen relies on the stock STATIC proc returning
HTTRANSPARENT for mouse fall-through rather than saying so; the 1080
canvas downscales 1.5x on the Deck, against a 9px legibility floor; and
the front end is mouse-driven, which Deck Verified will not pass.

Nothing here has been run on Linux. The first step when this resumes is
an afternoon with a Linux box confirming the child-window Present, before
anything is promised.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-13 12:58:19 -05:00
co-authored by Claude Fable 5
parent 1e0c412102
commit 3d94ac7158
2 changed files with 193 additions and 0 deletions
+1
View File
@@ -124,6 +124,7 @@ mission. Full map with pad and keyboard diagrams:
| [docs/RP412-ROADMAP.md](docs/RP412-ROADMAP.md) | The original plan and workstreams |
| [docs/RP412-FRONTEND-DESIGN.md](docs/RP412-FRONTEND-DESIGN.md) | TeslaConsole analysis, the egg format, the console protocol, and the Steam mapping — with status notes as each layer landed |
| [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 |
| [BUILD.md](BUILD.md) | Toolchain and build steps |
Dev tooling: `tools/two-pod-test.ps1` races two pods on loopback,
+192
View File
@@ -0,0 +1,192 @@
# 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
<https://claude.ai/code/artifact/47e4cf5f-a9a3-416e-bd5e-25c810983551>.
**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
fixable inside the Windows build, and Valve's Deck Verified program accepts
Windows-only titles. A native port is a genuine second platform layer
(~77k lines of `MUNGA_L4` plus ~6,300 lines of raw Win32 UI in `RP_L4`) and it
forces the 32/64-bit question, which reaches into the wire protocol and the
`.RES` format. Nothing on the Proton path is throwaway: each fix is an
improvement to the shipping Windows build.
Provenance: code findings below are measured against this repo (four
exploration sweeps over `MUNGA`, `MUNGA_L4`, `RP`, `RP_L4`, `DivLoader` and
the build files, 2026-08-12, main @ 1e0c412). External claims — Proton/DXVK
behavior, Steam Runtime direction, Deck Verified criteria — are from Valve
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.
## 1. How the Windows dependency actually distributes
The architecture is far more portable than the mechanics. The engine is
single-threaded by construction, has exactly one timing seam (`SystemClock`,
implemented in [MUNGA_L4/L4TIME.cpp](../MUNGA_L4/L4TIME.cpp)), one file-I/O
seam ([MUNGA/FILESTUB.cpp](../MUNGA/FILESTUB.cpp), six syscalls), a formal
network transport interface with all real socket calls in one ~350-line class
([MUNGA_L4/L4NETTRANSPORT.cpp](../MUNGA_L4/L4NETTRANSPORT.cpp)), and audio
already speaks OpenAL. But the mechanical surface is total: **1,800 of 1,844
quoted `#include` lines cite a filename in a case that doesn't exist on disk,
and 334 use backslash separators** — on a case-sensitive filesystem, nothing
compiles until a repo-wide normalization pass is done.
| Tree | Lines | Portability |
|------|-------|-------------|
| `RP\` | 16,441 | **Clean.** Zero Win32 API, zero Win32 types, zero threads. Only 16 `stricmp` and 4 `getenv`. |
| `MUNGA\` | 116,695 | **~90% clean** (267 of 297 files carry no Win32/MSVC token). The leaks are few and named: `PostQuitMessage` as the abort path (32 sites in 9 files; the portable prototype still sits commented out at `MUNGA/STYLE.H:31`), [MUNGA/MATRIX.h](../MUNGA/MATRIX.h) line 4 including `<D3DX9.h>` (which transitively feeds `windows.h` to most of the engine), `HWND`/`SOCKADDR_IN` carried as opaque values in a handful of headers, and `SPOOLER`'s `CreateDirectoryA`/`CopyFileA`. |
| `RP_L4\` | 22,588 | **Mixed.** 20 of 34 files clean. The rest is `WinMain`/`WndProc` ([RP_L4/RPL4.CPP](../RP_L4/RPL4.CPP)) plus ~6,300 lines of hand-written Win32+GDI UI (front end, lobby, console board) behind no platform interface. |
| `MUNGA_L4\` | 76,575 | **The platform layer.** 41 of 100 files touch D3D9 / DirectInput / XInput / Winsock / serial / WinRT / Steam. This is what a native port rewrites. |
| `DivLoader\` | 1,714 | **No callers.** Entirely non-portable (D3D9 in its public API, an implementation-defined bitfield read off disk) — and nothing in this tree calls it. Delete, don't port. |
Inventories that came back smaller than expected:
- **Input**: the only XInput entry point used anywhere is `XInputGetState`
(3 call sites). The live DirectInput reader
([MUNGA_L4/L4JOY.cpp](../MUNGA_L4/L4JOY.cpp)) models 8 axes / 32 buttons /
4 hats — a direct match for SDL's joystick model.
- **Audio**: OpenAL (~91 calls) + libsndfile (3 calls), **zero**
winmm/mmsystem anywhere. EFX is already optional and self-degrading. One
engine-core leak: `MUNGA/AUDIO.cpp` calls three `al*` functions directly.
- **Networking**: TCP only, no UDP, no `select()`/`poll()` — a pure per-frame
nonblocking poll loop branching on exactly three error codes
(`WSAECONNREFUSED`/`WSAECONNRESET`/`WSAEWOULDBLOCK`).
- **Steam**: four interfaces total (NetworkingSockets, NetworkingUtils,
Matchmaking, Friends+User), all gated behind the
`SteamNetTransport_ClientLibraryPresent()` probe. The vendored SDK already
ships `linux32/linux64 libsteam_api.so`. Steam Input is not used.
- **Renderer**: 100% fixed-function D3D9 — no shaders, no D3D9Ex. All ~193
device calls sit in five `MUNGA_L4` files. D3DX usage is mesh loading
(`D3DXLoadMeshFromXA`), texture loading (PNG), and matrix helpers.
The leaky seam is **windowing**: of ~430 user32/gdi32 call sites, about
two-thirds live *outside* the platform layer, and the per-frame message pump
sits in engine core (`MUNGA/APPMGR.cpp:126`). The cockpit composition is
genuinely exotic Win32: up to 11 top-level windows, D3D `Present` into a child
`STATIC` control via `hDestWindowOverride`, GDI-painted panes clipped over the
3D viewscreen, and hit-testing that relies on the stock STATIC proc returning
`HTTRANSPARENT`.
## 2. Path A — Proton as the official Linux target (recommended)
External findings (August 2026): 32-bit D3D9 through DXVK is Proton's
most-traveled path and is deliberately preserved — Steam Runtime 4 dropped
most i386 libraries *except* the ones Proton needs. Wine 10 (in current
Proton) merged Vulkan child-window rendering, removing the one historical
blocker that would have hit the viewscreen pane directly. Valve's stated
position is neutral between native and Proton, Deck Verified is achievable
with a Windows-only build, and the prevailing small-studio practice for
legacy Win32 titles is to ship Proton officially. Wine resolves filenames
case-insensitively, so the repo's asset-case inconsistencies cost nothing on
this path.
One dated fact that matters: a 32-bit-specific Proton bug corrupted
`ISteamNetworkingSockets::ReceiveMessagesOnConnection` data — exactly RP412's
receive path — and was fixed only in **Proton 10.0-4b / 11.0-1b (July 2026)**.
The multiplayer test matrix should pin that as the minimum version.
### Fix in the Windows build (confirmed in code)
| 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. |
| **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)
- **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.
- **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.
- **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
model; the legacy DirectInput paths' `DISCL_EXCLUSIVE` claims.
- **The joyconfig wizard** (`AllocConsole` + `conio`, `L4JOY.cpp:811-1392`)
very likely misbehaves under Proton — it is optional, and pad/keyboard
defaults don't need it; document rather than block on it.
- **Multi-adapter gauge windows** (up to 4 D3D devices, one per adapter,
`L4VB16.cpp:207`) are thin ice under DXVK — but that's the arcade
multi-monitor config, not the consumer default. Confirm the default path
never creates them.
## 3. Path B — the native port, honestly scoped
Feasible — the clean core makes it a real option, not a rewrite of the game —
but it is a phased engineering project, not a compatibility fix:
0. **Mechanical compile pass — everything blocks on this.** Normalize include
case and separators repo-wide (267 of 289 distinct include names don't
match disk case; renaming files inside a case-insensitive checkout takes
care), shim the MSVC CRT tail (100 `stricmp`, the Annex K `_s` family, the
`_time64` family, `itoa`/`strlwr`, 9 `std::ostrstream` sites), fix
`Fail_To_Debugger`'s non-const `char*` signature (GCC/Clang reject it
outright, so the `DEBUG_LEVEL>0` build doesn't compile at all), and address
the `SKIPPY_CAST` strict-aliasing punning (`MUNGA/STYLE.H:108`).
1. **Drain the core leaks.** Replace `PostQuitMessage`-as-abort with the
portable hook whose prototype still sits commented out in `STYLE.H:31`
(32 sites); strip `<D3DX9.h>` out of `MATRIX.h` and give it a neutral
interop type; typedef-shim the `HWND`/`HINSTANCE`/`SOCKADDR_IN` values
carried in core headers; split `RIOBase` out of
[MUNGA_L4/L4RIO.h](../MUNGA_L4/L4RIO.h) so the controls stack stops
dragging `<windows.h>` through the serial-packet header; port `FILESTUB`
(six syscalls) and `L4TIME` (one file); add a path-separator/case layer to
`FILEUTIL` and the 19 hardcoded `"dir\\"` literals.
2. **The 32/64-bit fork — the strategic decision.** The wire protocol and
`.RES` format are `/Zp1`-packed host-order struct dumps carrying `size_t`
and `unsigned long` fields (`MUNGA/RECEIVER.h:172`,
`MUNGA/RESOURCE.h:208-210`). A **32-bit native build** keeps every format
byte-identical but targets Steam's legacy *scout* runtime — swimming
against the current. A **64-bit build** (what Steam Runtime 4 wants) means
retyping the serialized fields to fixed widths, discovering and
pack-annotating the serialized struct set (46 raw stream overloads, no
manifest exists), and versioning the wire so Linux and Windows builds can
still race each other. Dovetails with the un-`/Zp1` cleanup already on the
roadmap; if the native port ever happens, 64-bit with fixed-width formats
is the right side of the fork.
3. **The new platform layer.** SDL for window/events/input (XInput's one
function → `SDL_GameController`; `L4JOY`'s 8-axis/32-button model maps
directly to `SDL_Joystick`, and SDL makes the RawInput de-dup unnecessary);
OpenGL for the fixed-function renderer (~193 device calls in five files,
no shaders to translate — the real work is replacing D3DX mesh/texture
loading); the CPU-side gauge canvases become streaming textures instead of
GDI blits; OpenAL-soft is a near drop-in; sockets go BSD (mechanically
small: 3 errno branches, ~45 `SOCKADDR_IN` occurrences); Steam loads via
`dlopen` through the flat API (~30 symbols) since ELF has no `/DELAYLOAD`.
4. **Rebuild the desktop UI.** The front end, lobby, and console board —
~6,300 lines of GDI drawing and Win32 controls behind no interface — get
redrawn through the engine or SDL, and the ~580-line console-mode joyconfig
wizard gets a portable rewrite. This is also where the four separate
message pumps collapse into one event loop.
5. **Delete, don't port.** The legacy DirectInput paths (`L4DINPUT`,
`ThrustMaster`), DOS-era `L4KEYBD`/`L4MOUSE` stubs, the AWE32 audio layer
and `sos/`, `JOYSTICK.asm`, DivLoader (no call sites), and Windows Dynamic
Lighting (OS-exclusive by nature). The serial RIO path is optional hardware
support — stub it, or port it to termios only if pod preservation on Linux
ever matters.
## 4. Sequencing
The Path-A fixes (lamp-mirror delay-load, explicit hit-test, Deck window
policy, font audit, pad-driven front end) all improve the Windows build on
their own, and none are wasted if Path B ever runs. The reverse is not true.
When this resumes: an afternoon smoke test of the current build under Proton
on a Linux box — child-window Present first — before committing to anything.