Root cause of the keymap whack-a-mole: three input layers exist (PadRIO/ bindings.txt under glass; btinput/CONTROLS.MAP under DEV; the bring-up drive bridge also under DEV, which clobbers e.g. Alt-reverse every frame), and DEV was the DEFAULT -- so every launcher that forgot BT_PLATFORM=glass booted the old stack: the play bats, then play_steam, then the operator console's local instances, each fixed with the same one line. Omission can't resurrect it now: with no BT_PLATFORM, desktop boots GLASS (and the menu on a zero-arg launch -- bare double-click of btl4.exe opens the mission menu). BT_PLATFORM=dev explicitly opts into the legacy DEV profile for debugging. BT_PLATFORM=pod is the cabinet profile; the real pod's SETENV.BAT must set it (contract recorded in glass-cockpit.md -- the old "bare boot == cabinet shape" menu-guard assumption is retired). Verified: no-env mission launch boots GLASS; bare no-env launch opens the MENU; BT_PLATFORM=dev boots DEV. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
16 KiB
id, title, status, source_sections, related_topics, key_terms, open_questions
| id | title | status | source_sections | related_topics | key_terms | open_questions | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| glass-cockpit | Glass Cockpit — the desktop developer layer (BT_GLASS / BT_STEAM) | living | plan 2026-07-17; CMakeLists.txt gates; BT412 prior art (reference only) |
|
|
Glass Cockpit — the desktop developer layer
Purpose: developer tooling so game developers can test their work WITHOUT booting a Tesla II pod — and stay far enough out of the game that nothing a real pod requires can break. NOT a consumer product (GDI-grade UI is the ceiling). Pod fidelity outranks desktop convenience in every trade-off.
ONE unified build (2026-07-21; the compile-gate split is RETIRED)
The pod/glass/steam compile split (three build dirs, three exes) is gone — it caused
version skew (a stale steam exe shipped 2 days behind), tripled build time, and the
"clicks silently dead in the wrong build" incident. Now everything compiles into the one
build/ exe and behavior is selected at RUNTIME:
| Runtime gate | Arms |
|---|---|
BT_PLATFORM=glass |
the desktop-cockpit profile: L4CONTROLS=PAD → PadRIO (clickable cockpit buttons, bindings.txt keymap), plasma window, and the miniconsole menu on a zero-arg launch |
BT_STEAM_NET=1 |
the Steam transport (FakeIP/SDR; degrades to Winsock without the Steam client). steam_api.dll is DELAY-LOADED (/DELAYLOAD + delayimp) — machines without the DLL run everything else |
| (nothing) | GLASS is the desktop DEFAULT (2026-07-22) — a bare boot gets PadRIO/bindings.txt (and the menu on zero args). The old accidental-DEV trap ("the old keymap again") is closed: every launcher that forgot the env used to boot DEV (play bats, steam bat, the operator console — each needed the same one-line fix). |
BT_PLATFORM=dev |
the legacy DEV profile (keyboard-only, the bring-up drive bridge) — explicit opt-in for debugging |
BT_PLATFORM=pod |
the cabinet profile. CONTRACT: the real pod's SETENV.BAT must set this — the old "bare boot == cabinet shape" assumption no longer holds. |
- The
BT_GLASSmacro is now ALWAYS defined (a seam marker only — the#ifdefsites remain as documentation of the layer boundary). Every gated code path was audited: all are instance-guarded (PadRIO::activeInstance,steamActive) or env-gated at runtime. BT_STEAMstays a COMPILE gate (the LICENSE gate, default OFF, restored 2026-07-21): the Steamworks SDK terms bar a public release with Steam inside, so a release configure must be able to exclude it entirely — no SDK headers, nosteam_api.lib, no DLL. The dev checkout configuresbuild/once with-DBT_STEAM=ON.mkdist.pyreads the answer frombuild/CMakeCache.txt: steam-OFF zips carry noplay_steam.bat, nosteam_api.dll(including a STALE one from an earlier ON build), a README with the steam section stripped, and a-nosteamname suffix so the two flavors never clobber. Verified both flavors: OFF exe has no steam bits +BT_STEAM_NET=1is inert; ON exe degrades to Winsock.- Menu guard (cabinet protection): the miniconsole previously claimed any zero-arg launch;
unified, it requires
BT_PLATFORM=glass(orBT_FE_MENU=1) — a bare cabinet boot can never land in the menu (btl4main.cpp, platform parse moved above the fe_menu_mode computation). - Verified matrix (2026-07-21): bare boot ≡ old pod exe (same DEV profile, same egg-load path);
glass + scripted click → ConfigureMappables end-to-end; exe runs WITHOUT steam_api.dll
present; glass zero-arg → menu; 2-node loopback MP (145 ticks each);
BT_STEAM_NET=1without Steam → "staying on Winsock" + game runs. - Convention: every
BT_*name is a RUNTIME env gate (BTEnvOn) — with the single exception ofBT_STEAM, the license compile gate above. Do not add another compile gate without updating this file. - No weak-linkage/optional symbols —
/FORCEturns unresolved externals into runtime AVs (reconstruction-gotchas); everything links always, so this class of trap no longer varies by build flavor.
Plan of record (2026-07-17)
Working branch glass-cockpit (from master e2c21c4). Steps: (1) gates+scaffolding [THIS],
(2) PadRIO glass cockpit, (3) miniconsole self-launch, (4) Steam transport+lobby. Full plan:
the approved plan file; running detail: docs/GLASS_COCKPIT.md. The retired BT412 trees
(C:\VWE\BT412; local branch local-abandoned-20260717) are read-only reference — no code is
copied from them.
Status
-
Step 1 (gates + scaffolding): DONE 2026-07-17. CMake options + defines; 3-config configure matrix + negative test + defaults build verified.
-
Step 2a (.CTL id audit): DONE 2026-07-17 [T2]. The streamed
.CTLpositional ids landed everyMechControlsMapperrecord ONE MEMBER LATE (our chain starts at 2, the binary's at 3) — a latent REAL-POD bug (RIO throttle would drive pedalsPosition), masked on dev boxes by the keyboard bridge. Fixed ungated: ids pinned to binary numbering + id-2 pad + static_assert locks (mechmppr.hpp/.cpp); torso/weapon chains were already aligned. Permanent env-gated diagnostic:BT_CTRLMAP_LOG=1dumps every streamed record's resolved member offset (L4CTRL.cpp CreateStreamedMappings). Full evidence:docs/GLASS_COCKPIT.md§2a. -
Step 2 (PadRIO glass cockpit): DONE 2026-07-17 [T2].
RIOBaseseam (L4RIO.h, unconditional) + gatedL4CONTROLS=PAD→PadRIO(L4PADRIO.*XInput+keyboard,L4PADBINDINGS.*content\bindings.txt); bridge stand-downs auto-yield to a live device (BTRIODevicePresent,BT_KEY_BRIDGEunset=auto/0=off/1=on); on-screen panelL4PADPANEL.*follows vRIO (C:\VWE\vrioCockpitLayout/PanelCanvas — 104 controls incl. the two hex keypads → real KeyEvents; right-click latch); desktop plasmaL4PLASMAWIN.*(L4PLASMA=SCREEN, buffer is top-down);-platform glasspreset + run.cmd token. Secondary displays reuse the EXISTING dev-gauge modes (dock /BT_DEV_GAUGES_WINDOW=1window /_DOCK=1overlay — gauges-hud); no new MFD code, noL4VB16.cppexposure. Button-trap guard: baseConfigureMappableMessageHandlerno longerabort()s by default (BT_BUTTON_TRAP=1restores) — each[FAIL]log = a missing L4 override to reconstruct. Verified: authentic-path input (engine push → streamed.CTL→ mapper), panel-click survival, 2-node loopback MP un-regressed on the pod build. Detail:docs/GLASS_COCKPIT.md. -
Trigger-config + one-keymap (2026-07-21, HUMAN-VERIFIED live). The cockpit-surround clicks only work when a
PadRIOexists, and PadRIO only exists under the GLASS platform profile (BT_PLATFORM=glass→L4CONTROLS=PAD); a plain launch falls to the DEV profile (KEYBOARD) andPadRIO::SetScreenButtonno-ops — every cockpit click silently discarded (the "held PROGRAM, nothing happened" incident:[cockpit] CLICK addr=0x8logged but ConfigureMappables id 9 never dispatched).play_solo.batnow setsBT_PLATFORM=glasswith the glass exe. The full regroup loop (hold PROGRAM → Mapping-mode flip suppresses fire → fire-key taps toggle the binding → joystick-display dot moves → release commits) is verified live. Diagnostics:[cfgmap]ENTER/EXIT (BT_FIRE_LOG, mechweap.cpp) +BT_BTNTEST="addr,on,off"scripted screen-button harness (L4PADRIO.cpp Poll). Keymap: which binding engine runs follows the same profile split — GLASS = PadRIO/bindings.txt, DEV/pod = btinput/CONTROLS.MAP— which made the felt keymap flip with the boot flavor. Settled 2026-07-21 (commitbabf5b1): CONTROLS.MAP + the compiled-in default now mirror the bindings.txt layout (numpad aim cluster, arrows=throttle/pedals, Alt=reverse 0x3F, NumPad5=AllStop, Shift=throttle) so every boot flavor feels identical. -
Step 3 (miniconsole): DONE 2026-07-17 [T2].
game/glass/btl4fe.*(menu + console-shape egg writer, golden-tested vs MP.EGG) +btl4console.*(the marshal: a worker-thread console CLIENT speaking the btconsole.py wire verbatim;content\marshal.log). ZERO engine hooks — the engine runs the stock console ladder. Per-mission process relaunch;BT_FE_*env arms the marshal (menu relaunches clear it). Verified: solo cycle (menu→mission→60s clock→stop→menu) and menu-hosted 2-node LAN. -
Step 4 (Steam): CODE COMPLETE 2026-07-18 [T2 single-machine]. See steam-networking — the wire seam +
L4STEAMNETidentity-token transport +btl4lobby. Live 2-account cross-machine session pending (docs/STEAM_TEST.md). -
Input-coverage audit + keyboard reconciliation: DONE 2026-07-20 [T2]. Root cause of the dead panel:
MechRIOMapper's message ids were chained one enum too high (0x19-0x2a) — the binary RIO table @0051dd30 re-registers the BASE aux/zoom ids 3..0x13 (Hotbox 0x1a); fixed ungated + static_assert-locked (btl4mppr.hpp) → all 24 MFD-bank + 2 zoom buttons live (52/72 panel buttons now work; 8 dead await handler reconstruction — Gitea backlog issue; 12 have no streamed mapping authored, authentically inert). Keyboard = the ~20 core gameplay actions on the CONTROLS.MAP keys; the PANEL covers every pod address by click (right-click = hold latch) — the merged default profile lives inL4PADBINDINGS.cpp(auto-writescontent\bindings.txt; W/S/A/D/Q/E/R/F/X drive, 1-4/Space/Ctrl fire, M/N/H/C = pod buttons 0x18/0x15/0x2C/0x2F,/V view toggle + J/K/L preset cycles hardcoded in PadRIO). Bound keys are SUPPRESSED from the typed 1995 channel (PadRIO::SuppressKey, the btinput pattern — ended the glass double-dispatch: 'w'=pilot-select, numpad/F-key key-up aliases); unbound keys keep their authentic meanings (5/z presets, t-o pilot select, +/- zoom). In BAS control mode the pod turns with the STICK (Q/E) and pedals are inactive — authentic; A/D turn in MID/ADV. Arrow keys mirror the drive cluster (UP/DOWN = the W/S throttle lever, LEFT/RIGHT = the A/D pedals). Full audit + census:docs/GLASS_COCKPIT.md` §2026-07-20. -
2026-07-20 live "keymap regression" report: STALE EXE [T2]. The human-reported glass regressions (arrows/lever/detent dead, V/J/K/L/N inert, ground-fire) were a
build-glass/Release/btl4.exebuilt BEFORE5dd3536landed — relaunched without rebuilding. No input-code change was needed; a clean rebuild + full SendInput re-verification (lever holds + zero stop, MID turns, V toggle shot-pair, level boresight, pod smoke) closed every symptom, and V's edge is now [T2].BT_MPPR_TRACEnow prints mode/pedals/stick/turn/elev. Procedural rule: rebuild every live build dir after landing an input/engine commit. Detail:docs/GLASS_COCKPIT.md§2026-07-20 (later). -
2026-07-20 human re-test: GAIT DETENT ported to the PadRIO lever [T2]. The remaining "our detent is missing" (mixed walk/run mode + repeated accel sample) was the gait DEAD BAND (locomotion): the pod-build keyboard lever snaps out of the walk/run hysteresis band at key-rest (mech4.cpp GAIT DETENT), and the PadRIO throttle slew lacked it. Ported faithfully: mech4.cpp publishes the band in lever units (
gBTGaitDetentLo/Hiseam, +0.002 engage margin),L4PADRIO.cppapplies the identical nearer-edge snap when no throttle slew key/pad axis is active. Verified: parks at 0.414/0.465 snap to 0.358/0.504 (band = demand 22.02-30.87), no at-rest sample in-band, exactly ONE up-shift + ONE down-shift clip across a full graduated sweep, zero-stop + pod build unaffected. Detail:docs/GLASS_COCKPIT.md§2026-07-20 (later still). A/D turn in MID/ADV. Full audit + census:docs/GLASS_COCKPIT.md§2026-07-20. -
Per-display cockpit windows (
BT_GLASS_PANELS): DONE 2026-07-20 [T2 startup-verified]. The-platform glasspreset now breaks the secondary displays out of the single combined panel/strip into ONE desktop window PER pod display, each carrying that display's surface WITH its RIO button bank placed around it, arranged pod-faithfully around the main view (engine/MUNGA_L4/L4GLASSWIN.*, new TU). Windows: Heat (UL) / Mfd2 (UC) / Comm (UR) / Mfd1 (LL) / Mfd3 (LR) each with 8 RED MFD buttons split 4 above / 4 below the surface; Secondary/Radar (portraitsec) with the 12 YELLOW Secondary buttons split left/right; a Flight Controls window for the no-display banks (throttle 0x38-3F, joystick/fire 0x40-47, panic). Each window is a GDI window (like gauges-hud's pad panel + the plasma window) — the surface is CPU-expanded from the shared gauge buffer (SVGA16::ExpandPlaneToBGRA, no D3D) andStretchDIBits'd in, so the whole D3D dev-composite path (dock / separate window / overlay) stands down while these are up (BTDrawGaugeInset/BTGaugeWindowRenderAndPresentearly-return; nogBTGaugeDockBottomstrip). Runtime gateBT_GLASS_PANELS(default ON under-platform glass,=0falls back to the single pad panel + docked gauges) — read byL4PADRIO(which panel to create),L4VB16(suppress compositing), andbtl4main(skip the world-window dock strip). The display↔bank map (spine of the feature): Heat 0x28-2F, Mfd2 0x20-27, Comm 0x30-37, Mfd1 0x08-0F, Mfd3 0x00-07, sec 0x10-1B (pod-hardware §Bank→MFD). Verified: build + bounded glass launch —[glasswin] per-display cockpit up (7 windows…), no crash, full mission loop un-regressed; surfaces carry LIVE gauge content (BT_GLASS_LOG= per-surface resolve/pixel diagnostic). Layout constants (surface sizes, exact button sub-positions, yellow left/right split) are marked tunable inL4GLASSWIN.cpp. Detail:docs/GLASS_COCKPIT.md. ⚠ Bug found + fixed during bring-up: surfaces were blank becauseapplication/ghWndare duplicate-defined globals that bind non-deterministically under/FORCE— the freshL4GLASSWINTU readapplication == NULL→ gauge renderer NULL. Fix: reach the renderer/window viaBTResolveGaugeRenderer()/BTResolveMainWindow(), DEFINED ingame/btl4main.cppoff its realbtl4App/hWndpointers (never the globals directly). See reconstruction-gotchas §6 duplicate-GLOBAL corollary. -
Glass per-display panel button click-crash (Gitea #18): FIXED 2026-07-20 [T2]. Clicking a per-display Engineering panel button (0x21 reported) on an Eng page hard-crashed — cdb: a wild call through an uninitialised pointer (
eip=cdcdcdcddebug /0x01048748release) with zero btl4 frames aboveReceiver::Receive. Root cause was NOT a/FORCEunresolved external (the glass link log carried only the known-benignmech3.objCreateStreamedSubsystemset) but a dense message-handler-table GAP slot (reconstruction-gotchas §11): the streamed Eng-page.CTLdispatches subsystem msg id 3 / 0xb to whatever subsystem is shown, and a weapon (Emitter) registers only PoweredSubsystem 4-8 + MechWeapon 9-10 — so id 3 is a gap belowentryCount, andReceiver::MessageHandlerSet::Build'snew HandlerEntry[]left it as heap garbage →(this->*garbage)(msg). The 1995 binary has the identical non-zeroingnew[]and survived only on fresh-heap-zero luck (id 3 on a weapon was always meant to be IGNORED — it's a Condenser/Reservoir action in a different subsystem branch; the uniform Eng-page button template makes buttons for unimplemented actions authentically inert). The new per-display windows just made the id reachable live. Fix (engine, class-wide, faithful):Buildnull-inits every slot (entryID=0/entryName=""/entryHandler=NullHandler) before copying inherited / placing supplied, so a gap is deterministically NullHandler →Receivedrops it — the authentic "Receiver ignores unhandled messages", not a glass-path guard. Fixes the entire dead-button class (#14) at once. Verified: click-soaked every MFD bank (Engineering/L+R Weapons/Heat/Comm, ~130 clicks through Quad↔Eng page cycles) under cdb — ZERO AVs, and the reconstructed mapper preset selects still fire ([mode] preset (1,1)/(2,1)…); pod build + solo un-regressed. The[glasswin] CLICKforensics log (names the button before dispatch) landed with this. Detail:engine/MUNGA/RECEIVER.cppBuild.
Key Relationships
- Extends: pod-hardware (RIO input path) · Uses: gauges-hud dev-gauge modes · Feeds: multiplayer (miniconsole hosting, Steam transport) · Build: build-and-run