12 KiB
GLASS_COCKPIT — running ledger (desktop developer layer)
Detailed running log for the glass-cockpit branch work: PadRIO, on-screen panel/plasma,
miniconsole, Steam transport/lobby. Curated digest: context/glass-cockpit.md (read that
first); this file carries the step-by-step detail, evidence, and verification transcripts.
2026-07-17 — Step 1: branch + compile gates + scaffolding
- Branch
glass-cockpitcreated from master @e2c21c4. CMakeLists.txt:option(BT_GLASS OFF)+option(BT_STEAM OFF);BT_STEAMwithoutBT_GLASSis a configure-time FATAL_ERROR; gates append toBT_DEFS(flows to all three targets);STATUSline prints gate state at configure. Gatedtarget_sourcesblocks will be added per-TU as they land (CMake would reject nonexistent source files now)..gitignore:build-pod/,build-steam/,content/bindings.txt,content/frontend.egg,content/steam_appid.txt.- Verification (this step): defaults configure prints
BT_GLASS=OFF BT_STEAM=OFFand builds (purity build);-DBT_GLASS=ONand ON/ON configures print correct gate state; the BT_STEAM-without-BT_GLASS negative test FATAL_ERRORs. Full 3-config BUILD matrix becomes meaningful from step 2 (first gated code) and is re-run there.
2026-07-17 — Step 2a: the .CTL positional-attribute off-by-one — CONFIRMED and FIXED (ungated)
The bug [T2, live-verified]: the streamed .CTL "L4" mapping resource carries the 1995
binary's positional attribute ids (stick=3, throttle=4, ...), resolved at install time by
Simulation::GetAttributePointer(id) → AttributeIndexSet::Find(id) = attributeIndex[id-1]
(positional). MechControlsMapper chained its enum on Subsystem::NextAttributeID == 2, but
the binary's chain reaches 3 (one base attribute we don't publish) — so EVERY streamed mapper
record landed one member late.
Evidence (new BT_CTRLMAP_LOG=1 diagnostic in LBE4ControlsManager::CreateStreamedMappings,
engine/MUNGA_L4/L4CTRL.cpp — permanent, env-gated, default off). Before the fix
(build-pod Debug, -egg DEV.EGG, dev platform):
rec 0 subsys 0 group 1(Joy) elem 0 attrID 3 -> +0xe4 (= throttlePosition: WRONG, stick intended)
rec 1 subsys 0 group 0(Scalar) elem 0 attrID 4 -> +0xe8 (= pedalsPosition: WRONG, throttle intended)
rec 2 subsys 0 group 3(Button) elem 63 attrID 6 -> +0xf0 (= speedDemand: WRONG, reverse intended)
rec 4-6 attrID 10/11/12 -> lookRight/lookBehind/lookDown (one late; LookLeft/Right/Behind intended)
Spacing attr3→attr4 was 4 bytes (Scalar) where stick→throttle must be 8 (ControlsJoystick).
The weapons chain (attrID 19 → +0x31c TriggerState) was already correct — mechweap was
pad-aligned in task #16 (gotcha #11). Torso (subsys 17) also aligned (MechSubsystem publishes
binary id 2). ONLY the mapper chain (subsys 0) was off. On the dev box the keyboard INPUT
BRIDGE (writes the mapper members directly, after the engine push) masked it; on a REAL POD the
serial RIO throttle scalar would have driven pedalsPosition and the stick x/y would have
driven throttle+pedals — the Phase-8 landmine prior art predicted.
The fix (ungated, pod-correctness — user-approved 2026-07-17): mechmppr.hpp pins the enum
to the binary numbering (MechControlsMapperPadFirstAttributeID = 2, stick=3 ... PilotArray=
0x16); mechmppr.cpp pads id 2 with a valid never-bound entry (the mech.cpp attrPad idiom) and
locks the base + pinned ids with static_asserts. L4MechControlsMapper::TargetRangeExponent
(chained) moves 0x16→0x17, matching the binary's own arithmetic; it is bound by name (audio
zoom-blip), so no consumer shifts.
After the fix: attrID 3 -> +0xdc (stick), attrID 4 -> +0xe4 (throttle, 8-byte spacing
✓), attrID 6 -> +0xec (reverse), 10/11/12 -> +0xfc/+0x100/+0x104 (the look trio); torso +
weapons unchanged. Regression: headless forced-walk run (BT_FORCE_THROTTLE=1) drives normally
— [mppr] thr=1 -> speedDemand=61.501, gait cycles, position advances, 31/31 subsystems
simulated, no faults.
2026-07-17 — Steps 2b-2e: PadRIO, stand-downs, displays, preset — DONE + verified
2b (RIOBase + PadRIO, commit ac57a47): L4RIO.h splits abstract RIOBase (enums, the five
analog Scalars, pure GetNextEvent/SetLamp, no-op serial ops, NEW IsOperational()) out of
serial RIO (: PCSerialPacket, RIOBase — ctor assigns as before, zero behavioral delta);
L4CTRL.h rioPointer retyped RIOBase*; gated L4CONTROLS=PAD factory arm. NEW gated TUs
L4PADRIO.* (XInput+keyboard → the surface; 3s hot-plug; focus-guarded keys; per-poll
AnalogEvent heartbeat time-gated so the manager's drain loop terminates; lampState[];
static SetScreenButton — button addresses AND the keypad space 0x50-0x6F → KeyEvents) and
L4PADBINDINGS.* (content\bindings.txt, default written on first run; deflect/slew/set axis
model; addresses validated vs ButtonCount).
2c (stand-downs, commit 9f35a80): engine query BTRIODevicePresent() (ungated —
pod-correct); both dev bridges (mech4.cpp attr writes, mechmppr.cpp BT_KEY_BRIDGE) gate
their WRITES: unset=auto / "0"=off / else on; forced harness always rides. Definitive live
test: OS-injected LSHIFT hold slewed PadRIO throttle 0→0.33→1.0 with pre==thr every frame —
the value arrives via the ENGINE push through the streamed .CTL binding (bridge stood down),
speedDemand=61.501 from authentic InterpretControls.
2d/2e (displays + preset, commits db9ac94 + 418acf0): the on-screen panel follows vRIO
(C:\VWE\vrio — CockpitLayout.cs/PanelCanvas.cs, per Cyd): five 4x2 MFD clusters
(descending addresses), Throttle/Secondary/Screen/Joystick 1x8 columns, two 4x4 hex keypads
(0x50 internal→pilot unit, 0x60 external→operator unit), physical names, red/yellow/blue
coloration, 500/250/125ms flash, right-click latch — 104 controls. PlasmaWindow : Video8BitBuffered renders the 128x32 plasma via the SAME gauge-renderer path as serial
(buffer is TOP-DOWN — the flip rendered it upside down, user-reported + fixed). MFD surfaces =
the EXISTING dev-gauge modes (no new code, no L4VB16 exposure). -platform glass preset +
run.cmd token. Button-trap guard (e2c21c4 pattern): the authentic base
ConfigureMappableMessageHandler FAIL trap is abort() under DEBUGOFF — any aux/zoom button
without its reconstructed L4 override KILLED the game on press (panel makes all reachable).
Default now log+ignore; BT_BUTTON_TRAP=1 restores the hard trap. Verified: synthetic panel
click → 2 [FAIL] (press+release through panel→PadRIO→ProcessRIOEvent→streamed mapping), game
survives. NOTE: each [FAIL] marks a missing L4 override — reconstruction backlog, see
open-questions.
Step-2 verification matrix (all passed 2026-07-17):
- Purity: defaults
build-pod/compiles (known mech3 /FORCE externals only); seam audit —BT_GLASS/BT_STEAMmentions ONLY in CMakeLists, btl4main.cpp preset region, L4CTRL.cpp (include+PAD arm), L4GREND.cpp (include+SCREEN branch), gated TUs, run.cmd docs. - Pod solo forced-walk clean (§2a).
- 2-node loopback MP on the pod build: A
-egg MP.EGG -net 1501+ B-net 1601+btconsole.pyrelay — both nodes launch into the running mission (77/76 ticks, mechs at their drop zones, gait/target loops live). - Glass build: PadRIO drives through the authentic mapper path; panel click path live; plasma
window; all boot lines present.
Deferred to the user (visual-only): panel/plasma appearance vs vRIO on screen, XInput stick
feel,
L4PADFLIPpolarity.
2026-07-17 — Step 3: the miniconsole — DONE + verified (commit c4ca87b)
Architecture: the console stays a wire peer. game/glass/btl4fe.* (GDI catalog menu + the
console-shape egg writer) and btl4console.* (the LocalConsole marshal: a worker-thread console
CLIENT speaking the tools/btconsole.py protocol verbatim — 1040-byte chunked
ReceiveEggFileMessage, 20s settle, RunMission×2, stay connected, own the clock,
StopMission(id 6)@expiry; logs content\marshal.log). The engine runs the 100% stock console
ladder — ZERO engine hooks; the planned L4APP.H setters proved unnecessary (launches pass real
argv on relaunch). WinMain: one gated block — zero-mission-arg glass launch → menu → relaunch
self with mission argv (BT_FE_EGG/PODS/SECS arm the marshal; BT_FE_LOOP returns exits to the
menu; menu relaunches CLEAR the handoff — the leak re-armed a marshal in cycle test 1).
Verified (synthetic menu drives, all autonomous):
- Golden egg:
frontend.eggsection sequence + line offsets IDENTICAL toMP.EGG; ordinal pages verbatim; GDI name-glyph pages in MP.EGG framing (32/8 rows, 16 bytes/line). - Solo cycle: menu → egg → relaunch → marshal → stock ladder → mission RUNS (58+ ticks) → 60s clock → stop → menu returns with clean env.
- HOST LAN 2-node: peer started manually (
-net 1601); menu-hosted launch → marshal feeds BOTH pods (2-pilot egg, 10 chunks each) → both in-mission (65/18 ticks) → stop both → host returns to the menu, peer idles at console-wait.
2026-07-17 — Step 4 pre: Steamworks SDK 1.64 vendored (commit 23c0019)
Official Valve zip (provided by Cyd at the repo root), extracted to
extern/steamworks_sdk_164/ and trimmed to public/ + redistributable_bin/ + Readme
(12.5 MB); zip removed. Consumed only under -DBT_STEAM=ON.
2026-07-17/18 — Step 4: the Steam wire — CODE COMPLETE (commits e79e8fa, 48ede2f, f703bb1)
4a (e79e8fa) — the wire seam: file-local BTNetSend/Recv/Accept/Close wrappers around the
9 raw Winsock sites in L4NET.CPP; OFF builds are pure code motion. Regression: 2-node loopback
MP through the seam — mesh forms, full 31/31 mission on both nodes.
4b (48ede2f) — the transport: gated TU L4STEAMNET — pseudo-SOCKETs (0x5EA0xxxx) behind the
seam; the engine's TCP byte stream rides reliable-NoNagle Steam messages re-assembled into
per-connection rings (recv semantics preserved: empty→WSAEWOULDBLOCK, closed→0); seam
completions: CheckSocket (peer = token address), OpenConnection TCP_OPEN delegate,
GetMyAddress self-token. Install on BT_STEAM_NET=1; graceful degrade to Winsock at every
step. BOTH pumps needed (SteamAPI_RunCallbacks + sockets RunCallbacks).
4c (f703bb1) — the lobby + THE FAKEIP LESSON [T2]: prior art assumed FakeIP survives the
per-mission process relaunch — DISPROVEN live (menu process got 169.254.124.110, the mission
process got 169.254.54.234; the pod timed out connecting to its own stale address). Redesign:
roster addresses are opaque ipv4-shaped TOKENS (169.254.77.N, ports 1501 console / 1502 game)
minted by the host at GO and mapped to Steam IDENTITIES; connections ride
ConnectP2P(identity, channel) (console=0, game=1); the map hands off via env
(BT_FE_MYFAKE/BT_FE_STEAMMAP). btl4lobby: ISteamMatchmaking room (btl4=1 filter),
pilots publish name/mech/color, host GO mints tokens + writes btl4map + btl4go.
Verified single-machine (ON/ON, live Steam client): menu → HOST STEAM LOBBY → room → GO →
map 169.254.77.1=<steamID64> → egg roster carries the token → mission process
[steamnet] up ... 1 roster token(s) incl. self → pod self-matches → stock console ladder →
mission RUNS (46 ticks). 3-config build matrix green throughout. Remaining (user hardware):
the live 2-account cross-machine session — procedure in docs/STEAM_TEST.md.
2026-07-18 — Merge origin/master (commit f889e24): btinput meets PadRIO
Master's 33-commit run landed (D1 relay/operator-console line, CONTROLS.MAP+XInput binding
engine btinput.cpp, camera seats, torso pitch, sign fixes, the 1995 manual, version stamping).
The reconciliation: btinput (ungated, dev/pod desktops) joined the stand-down convention —
BTInputPoll yields and BTInputSuppressKey claims nothing when an operational cockpit device
(serial RIO / PadRIO) owns the input path (BTRIODevicePresent; BT_KEY_BRIDGE override;
forced harness exempt). One input system per mode: CONTROLS.MAP/btinput on dev+pod
desktops, bindings.txt/PadRIO on glass. The D1 relay keeps its own raw sockets by design
(relay and Steam are alternative wires; the BTNet seam covers the arcade mesh both need).
Verified: 3-config build, glass stand-down live, pod forced-walk with btinput active, 2-node
loopback MP 31/31.