Filed, not started. Proton already carries Linux players and the Windows build reaches a running mission under it; this is the answer to the separate question of what one source tree building natively on both would take, so that the number exists before anyone needs it. Three sweeps of the tree, and the measurements are the part worth keeping. Two of them make the job smaller than the earlier assessment feared, and one makes it different. /Zp1 was called an entanglement with no manifest of which structs mattered. There is a manifest now: 31 distinct whole-object stream types, of which 29 are enums, scalar typedefs or all-float aggregates. Modelled at both packings and compared by sizeof, exactly one struct changes - the anonymous .met reader in L4D3D.cpp, 14 bytes packed against 16 default. Dropping /Zp1 costs one pragma. That also retires the packing divergence between RPL4TOOL and the game that FORCE:MULTIPLE has been hiding. The include mess is not the blocker. 172 of 172 core translation units fail under GCC, and 171 still fail with every 64-bit error removed, because MUNGA/SCHAIN.h uses a friend-declared name as a type and sits in the precompiled block. One line breaks the entire core, and 155 friend sites over 117 names share the shape. The whole conformance list is about thirty sites across twenty headers - small, and it gates everything. And the windows.h leak into engine core is NETWORK.h, not MATRIX.h. Winsock2.h reaches APP.h and 157 translation units, which is how APPMGR.h's bare 'extern HWND ghWnd' has been compiling all along. The D3DX9 include in MATRIX.h is real but narrow: ten files, two members. docs/RP412-LINUX.md said otherwise and now says so. What the sweeps found in our favour: RP is clean outright, 190 of 241 units touch no platform symbol, the renderer's distinct surface is nineteen render states and one mesh draw call with no shaders, all 224 .X files are text using eight templates, and the entire gauge production path - including four thousand seven hundred lines of L4VB16 - is already portable C++. RIOBase, the whole input seam, is two pure virtuals and five scalars. DivLoader is referenced by nothing and linked by nothing; the lobby room screen has no callers at all. The plan is phased so that almost everything happens on Windows first, verified by the existing build and the two-pod harness, with Linux arriving only at phase four. A port that spends six months unable to run is a port that gets abandoned. The document ends by saying when this would not be worth doing, which is most of the time: Proton already delivers the player experience, and only the first three phases have standalone value. It becomes worth starting when the goal changes to retiring the 2010 SDK dependency or removing Wine as a support variable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
307 lines
21 KiB
Markdown
307 lines
21 KiB
Markdown
# RP 4.12 on Linux — compatibility assessment
|
||
|
||
Requirements exploration for making RP412 playable on Linux (desktop and Steam
|
||
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
|
||
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.
|
||
|
||
## 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).
|
||
- **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
|
||
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)
|
||
|
||
> 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.**~~ **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 (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. **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. **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
|
||
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.
|