Files
RP412/docs/RP412-LINUX.md
T
CydandClaude Fable 5 3d94ac7158 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>
2026-08-13 12:58:19 -05:00

193 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.