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>
193 lines
14 KiB
Markdown
193 lines
14 KiB
Markdown
# 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.
|