One window per pod display (Heat/Engineering/Comm/Weapons x2/radar + Flight Controls), each carrying its surface with its RIO button bank arranged pod-faithfully; CPU-expanded surfaces, buttons under the imagery. Runtime gate BT_GLASS_PANELS (=0 = the legacy single panel). Merged on top of today's landed fix pile (audio-crash, experience, parallax); the L4PADRIO overlap with the gait-detent commit resolved preserving BOTH (his window refactor + our at-rest band snap). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> # Conflicts: # context/glass-cockpit.md
441 lines
32 KiB
Markdown
441 lines
32 KiB
Markdown
# 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-cockpit` created from master @ `e2c21c4`.
|
||
- `CMakeLists.txt`: `option(BT_GLASS OFF)` + `option(BT_STEAM OFF)`; `BT_STEAM` without
|
||
`BT_GLASS` is a configure-time FATAL_ERROR; gates append to `BT_DEFS` (flows to all three
|
||
targets); `STATUS` line prints gate state at configure. Gated `target_sources` blocks 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=OFF` and builds
|
||
(purity build); `-DBT_GLASS=ON` and 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_assert`s. `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):**
|
||
1. Purity: defaults `build-pod/` compiles (known mech3 /FORCE externals only); seam audit —
|
||
`BT_GLASS`/`BT_STEAM` mentions ONLY in CMakeLists, btl4main.cpp preset region, L4CTRL.cpp
|
||
(include+PAD arm), L4GREND.cpp (include+SCREEN branch), gated TUs, run.cmd docs.
|
||
2. Pod solo forced-walk clean (§2a).
|
||
3. **2-node loopback MP on the pod build:** A `-egg MP.EGG -net 1501` + B `-net 1601` +
|
||
`btconsole.py` relay — both nodes launch into the running mission (77/76 ticks, mechs at
|
||
their drop zones, gait/target loops live).
|
||
4. 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, `L4PADFLIP` polarity.
|
||
|
||
## 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.egg` section sequence + line offsets IDENTICAL to `MP.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-20 — Glass input-coverage audit + keyboard reconciliation
|
||
|
||
**Trigger:** making BT_GLASS the permanent player experience — familiar desktop keys dead, "most
|
||
panel buttons do nothing". Full audit of every input surface (bindings.txt keys, the 72-button
|
||
panel, the streamed .CTL EventMapping records, the typed 1995 channel), then the fix set.
|
||
|
||
### Finding 1 — THE panel-dead-buttons root cause: MechRIOMapper message ids shifted [T1→T2 fixed]
|
||
`MechRIOMapper`'s enum chained `Aux1QuadMessageID = L4MechControlsMapper::NextMessageID` (=0x19),
|
||
registering all 18 RIO handlers at ids 0x19-0x2a. The binary RIO table @0051dd30 RE-REGISTERS the
|
||
base aux/zoom ids **3..0x13** with the override handlers (Hotbox = 0x1a @0051de98) — that
|
||
re-registration over the base FAIL-trap ids is the whole point of the derived table. Streamed
|
||
`.CTL` events carry the binary ids, so every MFD-bank/zoom press found only the base
|
||
`ConfigureMappableMessageHandler` → `[FAIL]` (26 of 72 panel buttons dead; on a real pod with the
|
||
hard trap, an abort()). Hotbox worked by accident (sender + entry both used the same wrong 0x2a).
|
||
Fixed: ids pinned to the binary numbering + static_assert locks (`btl4mppr.hpp`). Verified live:
|
||
panel 0x0F/0x08/0x27/0x20 → `[mode] preset` lines, 0x10/0x11 zoom no longer FAIL. (Residual
|
||
fidelity gap: the binary RIO table also carries its OWN Keypress id 0x19 → @004d2514,
|
||
unreconstructed; we still register the shared L4 Keypress 0x17 — works, logged in the backlog.)
|
||
|
||
### Finding 2 — the always-active msg-4 records IDENTIFIED [T1] (closes the #9 open question)
|
||
Binary message tables decoded from section_dump (16-byte {id,name,fn,0} entries): per-receiver-
|
||
class id spaces. Roster (ARENA1 MadCat): subs 3=Reservoir, 4-9=Condenser1-6, 10-13=GeneratorA-D.
|
||
So: **0x2C = the COOLANT FLUSH button** (Reservoir InjectCoolant, WORKS), **0x2F/0x2E/0x2D/0x2B/
|
||
0x2A/0x29 = per-condenser VALVE buttons** (MoveValve, WORK), **0x1A-0x1D = Generator A-D ON/OFF**
|
||
(`ToggleGeneratorOnOff` id 4, table @0050fb90 fn @004b1ed0 — DEAD, unreconstructed). Also new:
|
||
0x13 → Mech **DuckRequest** (0x1a @0049fa00 — the manual's CROUCH button!), 0x28 → Mech
|
||
**BalanceCoolant** (0x16 @0049f728), 0x12 → ThermalSight toggle (id 3), 0x14 → Searchlight +
|
||
Searchlight2 **ToggleLamp** (id 3 @004b860c — body reconstructed but `Searchlight::
|
||
MessageHandlers` is default-constructed EMPTY, so it never fires: the quick win). Weapon
|
||
Eng-page extras: msg 0x3 = **ToggleCooling** (HeatableSubsystem @0050e41c fn @004ad6f8, unwired),
|
||
msg 0xb = **ToggleSeekVoltage** (energy @004ba478) / **EjectAmmo** (ammo weapons @004bb9b8),
|
||
Myomers msg 9 = ToggleSeekVoltage (@004b8a48; body exists, unwired). Unhandled messages are
|
||
SILENT (Receiver::Receive ignores an unfound id) — the dead-button census cannot come from
|
||
[FAIL] logs alone; it comes from the BT_CTRLMAP_LOG record dump × the handler tables.
|
||
|
||
### Finding 3 — glass keyboard: stand-down + double dispatch [T2]
|
||
ALL btinput actions (drive keys, M/N/J/K/L/H/C/V/B/G/F5-F9) stand down when PadRIO owns the path
|
||
— nothing on the game side polls keys directly (BTInputPoll is the only GetAsyncKeyState
|
||
consumer). Meanwhile the typed 1995 channel stays LIVE (glass preset = `PAD,KEYBOARD`) with NO
|
||
suppression — so Cyd's old bindings double-dispatched: W (keypad row) also typed 'w' = pilot
|
||
select 0; C (fire middle) also typed 'c' = MFD3 Eng3; NUMPAD key-UPS alias to letter hotkeys
|
||
(VK_NUMPAD2=0x62='b' = MFD3 Quad, VK_F5=0x74='t' = pilot select 3). This is the historic
|
||
pre-btinput double-dispatch, back on the glass side.
|
||
|
||
### The fix set (landed)
|
||
1. `btl4mppr.hpp` — RIO id pinning (ungated, pod-correctness; the step-2a idiom).
|
||
2. `L4PADRIO.*` — typed-channel suppression tables built from the loaded bindings (+hardcoded
|
||
keys), `PadRIO::SuppressKey` consumed by a `#ifdef BT_GLASS` arm next to the btinput hook in
|
||
`L4CTRL.cpp:1506` block; V joins backtick on the view-toggle edge; J/K/L fire
|
||
`gBTPresetCycle[3]` (the same seam the dev poll feeds — no binary analog exists for a "cycle"
|
||
button; the body is the authentic SetPresetMode).
|
||
3. `L4PADBINDINGS.cpp` + `content\bindings.txt` — the MERGED default map (user decision:
|
||
keyboard = the ~20 core gameplay actions on the CONTROLS.MAP keys; the PANEL covers all 72
|
||
pod buttons by click/latch). W/S throttle, A/D pedals, Q/E twist, R/F elevation, X all-stop,
|
||
1-4/Space/Ctrl fire + LALT reverse + B look-behind, M=0x18, N=0x15, H=0x2C (flush hold),
|
||
C=0x2F (Condenser1 valve), G=0x0E (weapon-1 configure, Mfd1-Quad-gated), F5-F9=Mfd2 bank
|
||
0x27-0x24+0x22 (page-gated gen A-D/mode), numpad flight cluster kept. DISPLACED from Cyd's
|
||
map (each reachable on the panel or by editing bindings.txt): number row→secondary panel,
|
||
QWERTY row→pilot keypad (dropped), arrows→hat looks (arrows now drive), V/C/B→fire
|
||
0x47/0x46/0x45, LCTRL→throttle-down. XInput pad section untouched.
|
||
NOTE (control modes, authentic): in BAS mode the pod turns with the STICK (Q/E here) and the
|
||
pedals are inactive; A/D turn in MID/ADV — exactly the manual's behavior, unlike the desktop
|
||
bridge which re-routes A/D per mode.
|
||
4. Un-touched: CONTROLS.MAP, btinput defaults, pod serial path. `bindings.txt` is read only by
|
||
`PadBindingProfile` inside the gated PadRIO TU — the pod build never opens it.
|
||
|
||
### Verification (2026-07-20, build-glass2, ARENA1 solo, all logs in-session)
|
||
- Panel: 0x0F→preset(0,1), 0x08→(0,0), 0x27→(1,1), 0x20→(1,0), zoom no-FAIL; 0x15 display
|
||
1→2→0; 0x2C hold → `[flush] Reservoir level 6→0`; dead-class buttons (0x13/0x1A/0x12/0x14)
|
||
silent as predicted. ZERO [FAIL] lines in the whole session (was 2 per bank press).
|
||
- Keyboard: W hold → thr 0→1, speedDemand 61.501, gait advances; Q → turnDemand 0.94 (BAS);
|
||
M → `control mode -> 1`; A hold → turnDemand 1.0 (MID); Space → `[emitter] FIRED`; N →
|
||
`display notify 1`; J/K/L → presets (0,1)/(1,1)/(2,1); H → reservoir drain; C → condenser#1
|
||
valveState 1→5. BT_SHOT frame confirms visually: MID lit on the control-mode column, CRITICAL
|
||
DAMAGE schematic up, MFDs on Eng pages, heading moved. V not re-injected (user took
|
||
foreground mid-run) — code-identical to the verified backtick edge [T3].
|
||
- Suppression: typed 'w' swallowed (no [keych]), typed 't' flows ([keych] 0x74), WM_KEYUP
|
||
VK_F5 swallowed (the 't'-alias hazard).
|
||
- Un-regression: build-pod (gates OFF) compiles 0 errors; forced-walk run drives
|
||
(speedDemand=61.501, 32 subsystems, streamed ids 3→+0xdc / 4→+0xe4 unchanged).
|
||
|
||
### 2026-07-20 (later) — the "keymap regression" live report: STALE EXE, not code [T2]
|
||
|
||
**Report (human, live glass session):** arrows dead/half-dead (UP accelerated, no detent;
|
||
LEFT/RIGHT no turn), throttle not a lever, V no view toggle, J/K/L/N inert, weapons firing into
|
||
the ground. **Root cause: `build-glass/Release/btl4.exe` was built 07:47 — BEFORE commit
|
||
`5dd3536` (08:35) landed the whole audit fix set** (SuppressKey tables, V/J/K/L hardcoded keys,
|
||
the RIO-id pinning that makes 0x15/N work) — the session was relaunched without rebuilding, and
|
||
kept running the pre-audit image. `content\bindings.txt` (08:23) already WAS the merged map and
|
||
the loader parsed it fine (`[padrio] bindings loaded: 40 keys, 10 pad buttons, 5 pad axes`), so
|
||
every bindings-file symptom analysis was a red herring; the binary was simply old. The
|
||
ground-fire symptom is the same session: with the R/F/X semantic chaos of the unsuppressed typed
|
||
channel, the torso elevation had been driven down and nothing recentered it.
|
||
|
||
**Fix:** `rm build-glass/Release/btl4.exe && cmake --build build-glass --config Release` (0
|
||
`error C`). No input-code change was needed — arrows/lever/detent/V were all already correct at
|
||
HEAD. One diagnostic enrichment landed: the `BT_MPPR_TRACE` `[mppr-c]` line now also prints
|
||
`mode/pedals/stick/turn/elev`, so one env-gated line proves the whole control surface.
|
||
|
||
**Re-verification (build-glass, DEV.EGG solo, SendInput-injected keys, own PID only):**
|
||
- UP hold 0.9 s → thr 0→0.63, dmd 38.70; released → **holds** 0.63 for 3 s (persistent lever).
|
||
- DOWN hold 2.5 s → thr 0.36→0.0028→0, rev=0 throughout — **stops at zero, no reverse
|
||
crash-through** (the wire throttle is the authentic 0..1 lever; reverse is the 0x3F button).
|
||
- M → `[mode] control mode -> 1`; LEFT → pedals +1 / turnDemand +1 (CCW=left, the wire sign);
|
||
RIGHT → pedals −1 / turnDemand −1; both auto-center on release. (In BAS the pedals are
|
||
authentically inactive — turns are the stick, Q/E.)
|
||
- R/F → stick.y ±1, torso elev 0→+0.349→−0.195, holds where released (positional torso,
|
||
matches the btinput path); **boresight level at spawn (elev=0)**.
|
||
- V → `[view] COCKPIT eyepoint` ↔ `[view] external chase`, BT_SHOT pair confirms
|
||
(chase mech ↔ canopy + reticle). Closes the audit's one T3 (V edge) as [T2].
|
||
- SPACE → `[emitter] FIRED #1`. Pod build rebuilt + smoked: BT_AUTODRIVE=0.8 →
|
||
dmd=49.2008, clean log, no cross-contamination.
|
||
|
||
**Lesson (procedural):** after landing an engine/input commit, REBUILD every live build dir
|
||
before relaunching — a stale `build-glass` exe reproduces "the whole keymap is broken" exactly.
|
||
|
||
### 2026-07-20 (later still) — human re-test: GAIT DETENT ported to the PadRIO lever [T2]
|
||
|
||
**Report (fresh build):** zero-stop confirmed working, but "OUR detent is still missing — it
|
||
goes into that mixed walk/run mode and repeats the acceleration sample." That is the documented
|
||
gait DEAD BAND (locomotion.md [T1]): the SM has no stable state for a demand in
|
||
(`walkStrideLength`@0x534, `reverseSpeedMax`@0x538) — parked there it cycles 7→10→14→7 forever,
|
||
retriggering EngineShiftFwd/Rev each swing. The pod-build keyboard lever carries an
|
||
accommodation (mech4.cpp "GAIT DETENT": at KEY-REST, snap the lever out of the band to the
|
||
NEARER edge; sweeping through while held stays continuous) — the PadRIO slew channel had no
|
||
such protection, so the glass lever could park in-band.
|
||
|
||
**Port (faithful copy, not an invention):** mech4.cpp publishes the band in lever units every
|
||
driven frame through a new seam (`gBTGaitDetentLo/Hi`, same guards + the same +0.002 engage
|
||
margin; hi<=lo = unknown); `L4PADRIO.cpp Poll()` applies the IDENTICAL nearer-edge snap to the
|
||
Throttle channel when the slew is at rest (no slew key held, no pad slew axis deflected) —
|
||
same rule, same log tag, gated `BT_GAIT_TRACE`. The rest decision uses PadRIO's own held-state
|
||
(the lever's owner on glass), not gBTDrive.keyFwd/Back.
|
||
|
||
**Verified (build-glass, DEV.EGG solo, graduated UP-taps parking the lever every ~0.10 across
|
||
0..1, BT_GAIT_TRACE+BT_MPPR_TRACE):** band [0.358026, 0.50388) = demand 22.02→30.87 (the
|
||
locomotion.md Blackhawk numbers); park at 0.4137 → snapped DOWN to 0.358026, park at 0.465126 →
|
||
snapped UP to 0.50388 (both nearer-edge directions); rest-position histogram shows NO at-rest
|
||
sample inside the band; gait states: ONE walk→run clip (state 10, once) on the whole ascending
|
||
sweep, ONE run→walk (state 14, once) descending, normal 6↔7 / 12↔13 cycling between — the
|
||
accel/shift sample can fire only once per real transition. Zero-stop unaffected (thr=0 rest,
|
||
state 0). Pod build rebuilt + BT_AUTODRIVE smoke clean (the seam publish is a pure global
|
||
write on the pod path).
|
||
|
||
## 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.
|
||
|
||
## 2026-07-19 — Mission-control options: the AUTHORITATIVE catalog (TeslaSuite console.ini)
|
||
|
||
Source: `C:\VWE\TeslaSuite\410console\4_10-console-extracted\Console.ini`, the Mac 4.10 operator
|
||
console's `BT::` adventure tree (the `tag=` values are the egg field values; display names are the
|
||
console's). Cross-checked vs `410console/battletech-port/BATTLETECH-PORT-SPEC.md` + `reference/
|
||
cavern.egg`. This is every option the original console exposed for a BattleTech mission.
|
||
|
||
**Mission-level** — `[mission]`: `adventure=BattleTech` (fixed), `temperature=27` (fixed),
|
||
`scenario=freeforall` (the tag is always freeforall; the RESPAWN choice is the ROLE):
|
||
- map (8): cavern, grass, rav, polar3, polar4, arena1, arena2, dbase
|
||
- time (4): morning, day, evening, night ← was day/night/morning/**dusk** (wrong: evening)
|
||
- weather (3): clear, fog (light), soup (heavy)
|
||
- role (2): Default (`model=dfltrole`, respawn) / NoReturn (`model=noreturn`, one life)
|
||
- length: seconds
|
||
|
||
**Per-pilot** — participant block:
|
||
- experience (4 [T1 game decode]): novice, standard, veteran, expert ← was hardcoded `expert`
|
||
- vehicle (18, friendly→tag): Black Hawk=blkhawk, Loki=loki, Nova=bhk1, Mad Cat=madcat, Thor=thor,
|
||
Owens=owens, Kotori=own1, Summoner=thr1, Hellbringer=lok1, Timber Wolf=mad1, Avatar=avatar,
|
||
Satyr=ava1, Sunder=sunder, Sunder V1=snd1, Vulture=vulture, Mad Dog=vul1, Mischief=lok2,
|
||
Zanin Neko=mad2 (the 18 ModelList names; matches the 2026-07-18 vehicle sweep)
|
||
- color/camo (7, display→tag): White=White, Black=Black, Brown=Brown, Red=**Crimson**, Green=Green,
|
||
Gray=**Grey**, Tan=Tan ← was White/Black/Crimson only
|
||
- badge/emblem (7, the `badge=` field): VGL, Davion, Kurita, Liao, Marik, None, Steiner ← was `VGL`
|
||
- patch/badge-color (8, the `patch=` field): Yellow, Blue, Grey, Green, Violet, Red, White, Black
|
||
← was `Yellow`
|
||
- dropzone (5, the `dropzone=` field): one, two, three, four, five ← was `one`
|
||
- advancedDamage (0/1): the advanced damage model toggle ← was hardcoded `1`
|
||
- vehicleValue: 1000 (const), loadzones=1 (const), bitmapindex (per-pilot ordinal)
|
||
|
||
---
|
||
|
||
## 2026-07-20 — Per-display cockpit windows (BT_GLASS_PANELS)
|
||
|
||
**Goal (Cyd):** stop collapsing the desktop cockpit into combined surfaces. Break each
|
||
SECONDARY display out into its OWN window carrying that display's surface WITH its RIO button
|
||
bank placed around it, arranged pod-faithfully around the main 3D view — a dev cockpit that
|
||
reads like the pod, so button/lamp/display behavior can be validated in position.
|
||
|
||
**New TU: `engine/MUNGA_L4/L4GLASSWIN.{h,cpp}`** (BT_GLASS-only; added to the CMake BT_GLASS
|
||
`target_sources`). Builds SEVEN GDI windows and positions them around the main window:
|
||
|
||
| Window | Surface (port) | RIO bank | Buttons |
|
||
|---|---|---|---|
|
||
| Heat MFD (UL) | `Heat` (0x4000) | 0x28-0x2F | 8 red, 4 above / 4 below |
|
||
| MFD 2 (UC) | `Mfd2`/`Eng2` (0x0400) | 0x20-0x27 | 8 red, 4 above / 4 below |
|
||
| Comm MFD (UR) | `Comm` (0x8000) | 0x30-0x37 | 8 red, 4 above / 4 below |
|
||
| MFD 1 (LL) | `Mfd1`/`Eng1` (0x0100) | 0x08-0x0F | 8 red, 4 above / 4 below |
|
||
| MFD 3 (LR) | `Mfd3`/`Eng3` (0x1000) | 0x00-0x07 | 8 red, 4 above / 4 below |
|
||
| Secondary/Radar | `sec` (palette, portrait) | 0x10-0x1B Secondary | 12 yellow, 6 left / 6 right |
|
||
| Flight Controls | (none) | 0x38-0x47 (+ panic/icom) | neutral, 2 columns |
|
||
|
||
**Architecture — GDI + CPU blit, NO D3D for the secondaries.** Each window is a layered,
|
||
topmost GDI window (the L4PADPANEL pattern: double-buffered paint, ~62 ms repaint timer,
|
||
left-click = momentary / right-click = latch → `PadRIO::SetScreenButton`; lamps from
|
||
`PadRIO::GetLampState`). The surface is CPU-expanded from the shared gauge pixelBuffer by the
|
||
new **`SVGA16::ExpandPlaneToBGRA(mask, paletteID, monoTint, rotateQuadrant, dst, &ow, &oh)`**
|
||
(L4VB16.cpp) — the no-D3D analog of `DrawDevSurface`: palette-LUT expand for `sec` (monoTint
|
||
< 0), mono bit-plane → tint for the MFDs; the portrait secondary CRT is unrotated by
|
||
transposing into `dst` (rotate 1/3, `BT_GAUGE_SEC_ROT`) — then `StretchDIBits` (HALFTONE) into
|
||
the surface rect. An Mfd/Eng pair shows whichever plane is the live (non-`BlankColor`) one, as
|
||
on the pod monitor. This reuses the plasma-window CPU→window pattern and sidesteps the whole
|
||
D3D dev-composite bug family (additional-swap-chain lifetime, device-state save/restore,
|
||
depth-stencil mismatch, layered strobing).
|
||
|
||
**Gate: `BT_GLASS_PANELS`** (runtime env; `BTGlassPanelsActive()`). Default ON under
|
||
`-platform glass` (preset putenv in `btl4main.cpp`); `=0` restores the legacy single pad panel
|
||
+ docked gauge strip. Wired at three seams:
|
||
- `L4PADRIO` ctor: `BTGlassPanelsActive()` → `BTGlassPanels_Create()` instead of
|
||
`BTPadPanel_Create()`; dtor destroys both (safe no-ops).
|
||
- `L4VB16`: `BTDrawGaugeInset` / `BTGaugeWindowRenderAndPresent` early-return when
|
||
`GlassPanelsOwnSurfaces()` (the per-display windows own the surfaces).
|
||
- `btl4main`: skip the `gBTGaugeDockBottom` + 1100×600 world-window forcing (world stays
|
||
normal size — no bottom strip).
|
||
|
||
**Placement.** `ComputeLayout()` rings the windows around `ghWnd` (the main game window):
|
||
top row Heat/Mfd2/Comm above it, Mfd1/Mfd3 at the lower corners, radar below-center, Flight
|
||
Controls left of the seat; clamped to the work area, with an 800×600-centered fallback before
|
||
the main window exists + a one-shot reposition once `ghWnd` comes up (in the repaint timer).
|
||
|
||
**Verified [T2 startup].** `-DBT_GLASS=ON` Debug build links (the usual `/FORCE` mech3
|
||
subsystem set is pre-existing, unrelated). Bounded `-platform glass -egg DEV.EGG` launch:
|
||
`[glasswin] per-display cockpit up (7 windows: 5 MFD + radar + flight)`, `[plasmawin] … up`,
|
||
`[padrio] XInput pad found`, no failure/abort markers, full mission loop un-regressed (killed
|
||
after 18 s). Visual layout / surface content / click-through NOT yet human-verified.
|
||
|
||
**Tunable (Cyd flagged refinements):** surface display sizes, the exact button sub-positions
|
||
around each screen, the yellow left/right split (first cut 0x10-0x15 / 0x16-0x1B), and the MFD
|
||
mono phosphor tint — all named constants at the top of `L4GLASSWIN.cpp`. Deferred: folding the
|
||
plasma window into the same arrangement; per-window draggable/saved positions; moving/resizing
|
||
the main window into a true center cell for a tighter ring.
|
||
|
||
### Blank-surface bug (found + fixed same day) — /FORCE duplicate-global race
|
||
|
||
First glass-panels run showed the buttons but BLANK display surfaces. `BT_GLASS_LOG`
|
||
diagnostics in `BlitSurface` traced it stage by stage: `application == 0x00000000` in
|
||
`L4GLASSWIN.cpp` for the ENTIRE run (call 1 → 481), so `application->GetGaugeRenderer()` was
|
||
never even reached. Yet the legacy dock path (`BTDrawGaugeSurfaces`, `L4VB16.cpp`, SAME engine
|
||
lib) logged `application=047BA9C8 gr=0C57FF78` — non-NULL.
|
||
|
||
Root cause: the 1995 headers declare free globals (`application`, `ghWnd`, …) without
|
||
`inline`/`extern`, so they are DEFINED in both `munga_engine` and `bt410_l4`. Under
|
||
`/FORCE:MULTIPLE` the linker binds each `.obj`'s reference to one copy or the other
|
||
**non-deterministically per link** — the newly-added `L4GLASSWIN.obj` bound the copy the game
|
||
never assigns (stays NULL), and a relink even flipped `L4VB16.obj`. Routing through an engine
|
||
helper is therefore unreliable (its binding is equally random).
|
||
|
||
Fix: `BTResolveGaugeRenderer()` / `BTResolveMainWindow()` (declared in `l4vb16.h`) are DEFINED
|
||
in `game/btl4main.cpp` off the launcher's OWN file-scope pointers — `btl4App` (set to `new_app`
|
||
at btl4main.cpp:631) and the global `hWnd` (the real main window). Those live in `btl4main.obj`
|
||
and are the actual assigned pointers, so the resolvers are always correct regardless of the
|
||
duplicate-symbol binding. `BlitSurface` and the placement code call the resolvers, never the
|
||
`application`/`ghWnd` globals. New durable gotcha: `context/reconstruction-gotchas.md` §6
|
||
duplicate-GLOBAL corollary.
|
||
|
||
Verified after the fix (`BT_GLASS_LOG`): `[glass] 'Heat MFD' port=Heat mask=0x4000 pal=3
|
||
enable=2 ow=640 oh=480 nonzero=22227/307200`, `[glass] 'Secondary / Radar' port=sec mask=0x3f
|
||
pal=1 enable=3 ow=480 oh=640 nonzero=26206/307200` — surfaces resolve and carry live pixels.
|
||
`BT_GLASS_LOG` left in as a permanent per-surface resolve/pixel diagnostic (throttled).
|