Files
BT412/context/steamification.md
T
CydandClaude Opus 4.8 101562978d Terminology: BattleTech missions, not races
"Race" is Red Planet's term (it's a racing game) and rode in with the
RPL4LOBBY port. In BattleTech the gameplay unit is a MISSION. Sweep it out
of the front-end / lobby / marshal code + the roadmap and steamification
digest: rename the public API BTLobby_Push/PullRaceResults ->
Push/PullMissionResults, the internal PrimeHostedRace -> PrimeHostedMission,
the deferred marshal InstallNetworkRace -> InstallNetworkMission, and the
user-facing strings ("STEAM MISSION LOBBY", "L A U N C H  M I S S I O N",
"HOST/JOIN STEAM MISSION"). Comments follow. No behavior change; both gates
(default + BT412_STEAM) still build + link clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:29:38 -05:00

18 KiB
Raw Blame History

id, title, status, source_sections, related_topics, key_terms, open_questions
id title status source_sections related_topics key_terms open_questions
steamification BT412 Steamification — status, seams, and merge notes living docs/BT412-ROADMAP.md; RP412 repo (C:\vwe\RP412) git history + docs
pod-hardware
multiplayer
gauges-hud
build-and-run
wintesla-port
egg
RIO
console
BT cockpit button-bank geometry source (no vRIO CockpitLayout exists for BT): l4gauge.cfg / pod photos / Nick
Does BT's rebuilt L4D3D still have RP's NULL-texture AddRef crash pattern?
Does L4CONTROLS=PAD set primaryControlType=PrimaryRIO so MechRIOMapper engages unchanged?

BT412 Steamification — status, seams, and merge notes

BT412 = BT411 made distributable on Steam and playable over the internet with no pod hardware, following the recipe RP412 proved end-to-end (RP411 → RP412). The full plan and phase order: docs/BT412-ROADMAP.md. RP412 (C:\vwe\RP412) is the reference implementation — its git history from commit 467934c onward is the steamification, grouped roughly: toolchain → PadRIO input → MFD-split cockpit → front end/LocalConsole → NetTransport/Steam sockets → lobby → bindings/KeyLight → packaging.

Repo rule [T0]: origin = VWE/BT412.git only. This repo must never touch the BT411 remote (the gitea URL) — no remote, no fetch/push to it. Forked at BT411 4e72f0c (2026-07-14). Bringing BT411 work forward is allowed via a local-path merge (git fetch C:/vwe/BT411 master) — that never contacts the gitea BT411 remote; see the merge note below.

BT411 catch-up merge (2026-07-14)

Merged BT411's post-fork line (4e72f0c..abed41e, 91 commits) into BT412 via a local-path merge — brought the whole audio-fidelity system (engine AUD* + L4AUD* + audiopresets.cpp + ~600 content .wavs, docs/AUDIO_FIDELITY.md), missiles/rear-fire/HUD/gyro/gait tasks (#6668), FOGDAY.EGG, and refreshed context docs. Only 5 files overlapped the steamification; resolutions:

  • .gitignore, CMakeLists.txt, mech4.cpp — trivial / auto-merged (the deviceOwnsInput gating survived).
  • L4NET.CPP — took BT411's task-#50 fix (don't close the game listener on console loss) over the seam's adaptation of the old buggy close; the point-to-point send stays on NetTransport_Get()->Connect().
  • L4NETTRANSPORT.cpp — folded BT411's TCP_NODELAY latency fix into WinsockNetTransport::Connect (the seam already had BT411's retry-while-refused + non-blocking). Accept-side game-socket NODELAY stayed at the L4NET call site.
  • mechmppr.cpp — combined the device_owns_input gating with BT411's task-#68 look-behind ('V' rear-view), gating the lookBehind write too (a live RIO device feeds it through its mapped button). Verified after merge: clean build; solo front-end mode; loopback MP through the seam (mesh completes, both tick, replication, no NODELAY warnings). [T2]

Status ledger

  • Phase 0 bootstrap — DONE 2026-07-14. Tree copied (working tree + full history, build/ excluded), origin re-pointed, fast-forwarded to 4e72f0c, dev-state run/run.cmd working-tree edit discarded (committed version is the documented solo launcher), identity pass (README/CLAUDE.md/CMake project(bt412)), this topic + roadmap authored.
  • Phase 1 (VS2022 v143) — DONE 2026-07-14. Generator moved to Visual Studio 17 2022 (v143, Win32). One real change was forced: the v143 linker fails the image on unresolved externals even under /FORCE, so the dead offline-tool ladders in game/reconstructed/mech3.cpp are neutralized behind BT412_OFFLINE_TOOLS (default off) and the exe now links /FORCE:MULTIPLE (UNRESOLVED retired — real unresolved symbols are hard errors now). None of RP412's other v143 source fixes (TIME.h, NETWORK.h, L4DINPUT, CAMMGR) were needed — BT's tree was already v142-clean. Verified: clean build; solo DEV.EGG runs (gait/target/ 31-subsystem ticks); two-instance loopback MP via btconsole.py — mesh forms (console + 1502↔1602 game ports, "All connections completed!"), mission runs both sides, each world ticks its master + the remote replicant. [T2]
  • Phase 2 (PadRIO input layer) — DONE 2026-07-14. Ported from RP412: L4RIO.h (RIOBase split; serial RIO re-parented), L4CTRL.h (rioPointer is RIOBase*), L4CTRL.cpp PAD-token hook (→ primaryControlType=PrimaryRIO, so the stock MechRIOMapper engages — confirmed), new L4PADRIO.* / L4PADBINDINGS.* / L4KEYLIGHT.* (gates renamed BT412KEYLIGHT; KeyLight TU builds /std:c++17 /permissive- per-file). Default bindings.txt = the shared Tesla board layout (numpad flight, letter rows = MFD banks, F-keys = Secondary columns, joystick head 0x40-0x47 on Space/arrows/pad). content/bindings.txt is gitignored (written on first PAD run). BT-side changes PadRIO forced: (1) both keyboard input bridges (mech4.cpp write of mapper attrs; mechmppr.cpp BT_KEY_BRIDGE) now stand down when a RIO device exists (bridge_controls->rioPointer != 0) — they clobbered the engine push every frame; M mode-cycle / X recenter conveniences stay live. (2) the mapper attribute chain was off by one — see reconstruction-gotchas §11 (short-parent-chain positional trap): the .CTL stick mapping wrote throttlePosition. Fixed with a pad slot + binary-locked enum. This was a latent REAL-POD bug (Phase 8 would have hit it). Verified live: L4CONTROLS=PAD → throttle lever ramps (rate axis, sticky), stick deflects (turnDemand=1 + the authentic speed-vs-turn clamp: 61.5→22.0 u/s while turning), mech drives across the map, XInput pad detected + 3 s re-probe, bindings parse (59 key buttons/8 axes/12 pad buttons/5 pad axes). Keyboard fallback regression-verified headless (BT_FORCE_THROTTLE=1 walks). New diagnostics: BT_CTRLMAP_LOG=1 (streamed-mapping dump), BT_STICK_LOG=1 (RIO stick push trace).
  • Phase 3a (PlasmaScreen, L4PLASMA=SCREEN) — DONE 2026-07-14. L4PLASMASCREEN.* copied verbatim; L4GREND.cpp selector diff applied (BT's only divergence there is a comment at line 440 — no collision). Renders BT's 128×32 plasma into a desktop window. Verified: a window titled "Plasma Display" opens and the game ticks normally beside it (L4GAUGE=640x480x16 L4PLASMA=SCREEN). Note: the L4GAUGE gauge path binds a listen socket at startup → a first-run Windows Firewall prompt (expected; unrelated to plasma).
  • Phase 3b (single-window MFD-split cockpit, L4MFDSPLIT=1) — DEFERRED 2026-07-14. Two blockers make this the wrong thing to land now:
    1. No BT cockpit-layout geometry. RP412's L4MFDVIEW button-bank anchors/addresses come from vRIO's CockpitLayout for the Red Planet pod; vRIO has zero BattleTech data. The BT pod's panel geometry has no reference (needs pod photos / l4gauge.cfg mining / Nick) — the clickable lamp-lit banks can't be authentically placed.
    2. BT's MFD compositing is an unfinished upstream beachhead. Per docs/GAUGE_COMPOSITE.md, only the radar/secondary surface composites on a dev box; the five mono MFDs don't render yet (port-name mismatch, Step 2). RP412's split panes would be empty until that upstream work lands.
    3. Merge is HIGH-risk (quantified above): BT's 521-line L4VB16 divergence (dev-gauge docking + a 476-line block) overlaps RP412's 402-line split rewrite in the ctor and Update regions — a true hand-merge, not a drop-in. The 3-way diffs are staged in the scratchpad (rp412-L4VB16.diff, bt-L4VB16.diff). Prerequisite order when resumed: finish BT's MFD dev-composite (GAUGE_COMPOSITE.md Steps 23) → obtain BT cockpit geometry → then hand-merge MFDSplitView. Until then the game ships single-window with the dev-gauge dock (BT_DEV_GAUGES) as its cockpit composite.
  • Phase 4 (NetTransport seam + Steam transport) — DONE 2026-07-14. L4NET.CPP taken from RP412 post-seam (all ~24 Winsock call sites now route through NetTransport_Get()); BT's 3 BT_NET_TRACE blocks re-sited onto their code anchors (the drop path, the point-to-point send before NetTransport_Get()->Send, the pad-buffer receive) — they read message/packet metadata, not sockets, so no collision. L4NETTRANSPORT.* (seam + WinsockNetTransport default) and L4STEAMTRANSPORT.* (gate renamed BT412_STEAM, self-test env BT412STEAMSELFTEST) copied. extern/steamworks_sdk_164/ vendored (headers + win32 redistributable_bin). One engine global ported from RP412's APPMGR: gConsoleLossEndsMission (default False = arcade re-listen; the marshaled/lobby path sets it in Phase 5/6). CMake: seam always compiled; option(BT412_STEAM) (default OFF) gates the transport define + sdk/public include + steam_api.lib + post-build steam_api.dll copy. steam_appid.txt gitignored (Spacewar 480 by hand until a real AppID). Verified: (1) default TCP build — loopback MP through the seam is byte-for-byte the old behavior (console connects, egg msgID-3 chunks delivered, "All connections completed!", both instances tick, net-tx/net-rx traces fire through NetTransport_Get()); (2) BT412_STEAM=ON config + build (compile-check of the Steam transport TU against the vendored SDK; a live Steam session needs the Phase 6 lobby + 3 machines).
  • Phase 5 (front end) — DONE 2026-07-16 (5a5d). Interactive menu + local egg builder
    • in-process marshal + results + single-binary loop all landed and boot-verified. Landed the local mission-egg builder — the core "single-player without the operator console" capability:
    • game/reconstructed/btl4fe.{hpp,cpp}: catalogs (8 maps, 8 mechs + variants, colors), a BTFeMission config struct, and BTFeMission_WriteEgg that emits the exact console egg format (verified against content/MP.EGG): [mission], [ordinals] + the 4 static rank-place plasma bitmaps, [pilots], per-pilot loadout sections, [largebitmap]/[smallbitmap] with GDI-rendered 128×32 / 64×16 pilot-name plasma bitmaps, and the role models.
    • Two engine setters ported from RP412's L4APP.H (SetEggNotationFileName, SetNetworkCommonFlatAddress) so the front end feeds the standard -egg path.
    • game/btl4main.cpp WinMain: front-end mode — no -egg and no -net builds frontend.egg locally and points the engine at it (BTFrontEnd_Run). -egg/-net runs are untouched. Verified: zero-argument launch → [frontend] built frontend.egg → mission loads and runs (ticks, gait, targeting); the generated egg is structurally identical to MP.EGG (32×32/16×16 name bitmaps with real glyph pixels, all sections present). Completed since (2026-07-16):
    • Interactive on-screen catalog menu (green-on-black GDI, btl4fe.cpp): map / mech / color / time / weather / length groups + pilot-name edit, framed LAUNCH (and, under Steam, HOST / JOIN) buttons. Zero-arg launch shows it; a chosen LAUNCH builds the egg.
    • In-process LocalConsole marshal (btl4console.{hpp,cpp}): NOT a thread — it runs on the engine's per-frame hook (gPerFrameHook, added to engine/MUNGA/APPMGR.*, called on the game thread in RunMissions), so its application->Dispatch(StopMissionMessage) is safe. Owns the mission clock and stops it at the chosen length=; snapshots per-pilot kills/deaths at the stop (BTResolveRosterPilot/BTPilotKills/BTPilotDeaths).
    • Score intake + results screen: BT's own kills/deaths model (no gConsoleScoreSink — that is RP-layer; BT has no working mission-end wire flow, msgID 0x18 stays a stub). A GDI "MISSION COMPLETE" scoreboard (BTFrontEnd_ShowResults) reads the marshal snapshot (BTLocalConsole_GetResult/GetResultName).
    • Single-binary loop = RELAUNCH per mission (CreateProcessW after the marshal stop), NOT an in-process for(;;) — in-process re-init crashes on the stale reconstructed gBT* globals (cdb-traced to BTL4VideoRenderer::MakeEntityRenderablesgBTTerrainEntity). Verified: 3 distinct PIDs across the menu→mission→results→menu cycle.
    • WinMain launch-mode branch (BTFrontEnd_LastLaunchMode()): 0 solo, 1 lobby host, 2 lobby member (see Phase 6).
  • Phase 6 (Steam lobby) — CODE LANDED 2026-07-16; live 3-machine test DEFERRED. game/reconstructed/btl4lobby.{hpp,cpp} (ported from RP412 RPL4LOBBY) builds both gates — stubs without BT412_STEAM, full ISteamMatchmaking room under it (both configs link clean). What works: the room screen; member data (ip/cp/gp/nm/vh/cl/bd = FakeIP + fake console/game ports + persona + mech/color/badge); the nonced go launch roster → SteamNetTransport_RegisterPeer for every peer; Push/PullMissionResults over lobby data (res key) rebuilding the shared score sheet via BTLocalConsole_ClearResults/ InjectResult. The menu offers HOST/JOIN when BTLobby_Available() (Steam transport up, installed by WinMain on BT412STEAM env). A host launch bakes every member into the egg's [pilots] (mesh FakeIP:1502 addresses, round-robin drop zones) via BTFrontEnd_SetHostedPilots; a member enters as a network pod on :1501 (SetNetworkCommonFlatAddress + gConsoleLossEndsMission=True). Deferred / not built: the host→member wire egg-feed marshal (InstallNetworkMission — the owner streaming eggs + RunMission to remote pods, as tools/btconsole.py does). Without it a member waits for a console connection nothing supplies, so a live host+member mission is blocked on it. The lobby object does NOT survive the per-mission relaunch (the single-binary limitation — everyone returns to a fresh menu, not the room). docs/STEAM-3-MACHINE-TEST.md for BT is not yet authored.
  • Phase 7: packaging (pack-dist.ps1), KeyLight, release — remain.

The seams (what plugs in where) [T0 unless noted]

  • Input: LBE4ControlsManager PUSH model; L4CTRL.cpp parses L4CONTROLS tokens; RP412 adds token PADnew PadRIO() (a RIOBase implementation from XInput+keyboard), and the stock RIO mapper runs against it unchanged. BT's mapper selection: btl4app.cpp MakeViewpointEntity switch on controls->primaryControlType (+0xc98) → MechRIOMapper for PrimaryRIO(4).
  • Plasma: BT's plasma is identical hardware to RP's (128×32, L4PLASMA.h); RP412's PlasmaScreen renders the same Video8BitBuffered surface into a desktop window, selected in L4GREND.cpp on L4PLASMA=SCREEN.
  • Displays: BT pod = main 800×600 3D view + 640×480 radar + five mono MFDs on one spanned 1280×480 surface (+ plasma). All cockpit surfaces are bit-plane masks over one shared SVGA16 pixelBuffer (see gauges-hud, pod-hardware). RP412's MFDSplitView (L4MFDVIEW.* + L4VB16.cpp split mode, L4MFDSPLIT=1) composes child panes on a fixed 1920×1080 canvas; 3D presents into the viewscreen pane via Present(hDestWindowOverride).
  • Networking: arcade stack unchanged (see multiplayer); RP412 moved the wire behind NetTransport (L4NETTRANSPORT.*), default WinsockNetTransport, Steam implementation L4STEAMTRANSPORT.* (ISteamNetworkingSockets, FakeIP + SDR, degrade-to-TCP on any failure). Addresses stay SOCKADDR_IN-shaped so the egg [pilots] ip[:port] roster works over both wires.
  • Session: console protocol (egg msgID 3, 1040-byte chunks + ACK; RunMission msgID 5 ×2; console must stay connected) — tools/btconsole.py is the working feeder and the spec for the in-process LocalConsole marshal (Phase 5).

Known merge risks (from the 3-way BT411 / RP411-baseline / RP412 comparison)

Quantified 2026-07-14 [T1] — BT-vs-RP411-baseline divergence measured with diff --strip-trailing-cr (baseline extracted from RP412 history, git show 3b8b729:MUNGA_L4/<f>); RP412-side change sizes from git diff 3b8b729..HEAD:

File BT vs baseline RP412 change Treatment
L4RIO.h identical 125 lines (RIOBase split) RP412 verbatim
L4RIO.cpp identical 0 nothing to do
L4CTRL.h, L4NET.H, L4PLASMA.*, L4KEYBD.h identical small RP412 verbatim
L4CTRL.cpp 29 diff lines 17 (PadRIO hook) apply RP412 diff
L4GREND.cpp 6 diff lines 15 (PlasmaScreen selector) apply RP412 diff
L4VIDEO.cpp 2725 diff lines (BT render work) only 14 apply RP412 diff — small despite BT divergence
L4VIDEO.h 29 diff lines 7 apply RP412 diff
L4NET.CPP 23 diff lines (BT_NET_TRACE ×5) 345 (seam refactor) MEDIUM — apply seam refactor, re-site traces
L4VB16.cpp 514 diff lines (dev-gauge dock) 350 (split view) HIGH — true hand-merge
L4VB16.h 14 diff lines 32 hand-merge with L4VB16.cpp
  • L4VB16.cpp — HIGH. RP412 rebuilt ~38464046 (splitViews/canvas/present); BT rebuilt the same window-management region for dev-gauge docking (BT_DEV_GAUGES, BT_DEV_GAUGES_WINDOW, BT_DEV_GAUGES_DOCK, BT_GAUGE_SCALE, BT_GAUGE_SEC_ROT, BT_ALL — 15 sites). Hand-merge; write the 3-way diff notes HERE before editing.
  • L4NET.CPP — MEDIUM. RP412 rerouted all ~2226 Winsock call sites through NetTransport_Get(); BT's 5 BT_NET_TRACE diagnostic blocks sit exactly on those send/receive sites and must be re-sited onto the seam calls.
  • CMake: engine sources are explicit per-file lines (no globs) — every ported .cpp needs its own add_library line or it silently doesn't compile. New deps: xinput9_1_0.lib (PadRIO), C++/WinRT (KeyLight, per-file C++17), Steamworks SDK + steam_api.lib behind option(BT412_STEAM).
  • No /Zp1 in BT's build (RP's engine builds /Zp1): RP412's packing-mismatch workarounds (WINDOWS_IGNORE_PACKING_MISMATCH, KeyLight default-packing carve-out, pack(push,8) around Steam headers) are no-ops here — keep them for verbatim-ness, but eyeball any copied code for /Zp1-layout assumptions.

Key Relationships