Glass input audit: RIO mapper id fix (panel banks live), keyboard reconciliation

Root cause of the dead on-screen panel: MechRIOMapper's message-id enum
chained off L4MechControlsMapper::NextMessageID (=0x19), registering all 18
RIO override handlers at 0x19-0x2a while the streamed .CTL EventMapping
records carry the binary's ids -- the binary RIO table @0051dd30 re-registers
the BASE aux/zoom ids 3..0x13 (Hotbox 0x1a @0051de98).  Every MFD-bank/zoom
press hit the base ConfigureMappableMessageHandler FAIL trap (26/72 buttons
dead; an abort() on a trap-armed pod).  Ids now pinned to the binary
numbering + static_assert-locked (btl4mppr.hpp) -- panel goes 26 -> 52
working buttons (8 await handler reconstruction, filed on Gitea; 12 have no
streamed mapping authored = authentically inert).

Keyboard reconciliation (glass-only): the merged map -- keyboard hosts the
~20 core gameplay actions on the CONTROLS.MAP keys, the panel covers every
pod address by click (right-click = hold latch):
- W/S throttle lever, A/D pedals, Q/E twist, R/F elevation, X all-stop,
  arrows drive; numpad flight cluster kept (Cyd)
- 1/2/3/4/Space/LCTRL/RCTRL fire, LALT reverse, B look-behind (0x41)
- M=0x18 mode cycle, N=0x15 display cycle, H=0x2C coolant 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 / gen mode)
- hardcoded: ` or V = view toggle, J/K/L = Mfd1/2/3 preset-page cycle
  (PadRIO edges -> gBTPresetCycle; no pod "cycle" button exists)
- PadRIO::SuppressKey: bound keys are swallowed from the typed 1995 channel
  (the btinput suppression pattern, glass side) -- ends the glass double
  dispatch ('w'=pilot select 0, 'a'..'g'=MFD2 presets, NUMPAD/F-key key-up
  VK aliases like VK_F5=0x74='t').  Unbound keys keep their authentic
  meanings (5/z presets, t-o pilot select, +/- zoom, F1/F2 align).

DISPLACED from Cyd's default bindings (each reachable on the panel or by a
bindings.txt edit -- flagging for Cyd's veto): number row -> secondary panel
(1-4 now fire), QWERTY row -> pilot keypad (dropped), arrows -> hat looks
(now drive), V/C/B -> fire 0x47/0x46/0x45 (now view/valve/look-behind),
LCTRL -> throttle-down (now fire).  XInput pad section untouched.

Audit by-products (KB updated): the always-active msg-4 records identified
(0x2C = Reservoir flush, 0x29-0x2F = condenser valves, 0x1A-0x1D =
GeneratorA-D ToggleGeneratorOnOff @004b1ed0 unreconstructed); 0x13 = Mech
DuckRequest (crouch), 0x28 = BalanceCoolant; Searchlight/ThermalSight
ToggleLamp handler-sets are default-constructed EMPTY; weapon Eng-page msgs
0x3/0xb = ToggleCooling / ToggleSeekVoltage / EjectAmmo, all unwired;
unhandled messages are SILENT (no [FAIL] -- census = BT_CTRLMAP_LOG dump x
handler tables).

Verified live (build-glass2, ARENA1): panel presets/zoom/display/flush;
W drive (speedDemand 61.5, gait advances), Q turn (BAS), M -> MID, A turn
(MID), Space fires, N display, J/K/L presets, H flush drain, C valve 1->5;
suppression ('w' swallowed, 't' flows, F5-keyup swallowed); BT_SHOT frame.
Un-regression: pod build (gates OFF) compiles 0 errors, forced-walk drives,
streamed ids unchanged; CONTROLS.MAP/btinput untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-20 08:35:55 -05:00
co-authored by Claude Fable 5
parent cb190aa00a
commit 5dd35365c7
11 changed files with 422 additions and 48 deletions
+23
View File
@@ -239,6 +239,29 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
- MechWeapon handler table @0x511860: {9,"ConfigureMappables"→@004b9550}, {10,"ChooseButton"→@004b95b8}
(strings @0x511994/@0x5119A7). PoweredSubsystem table @0x50F4EC: ids 4-8 = SelectGeneratorA-D /
ToggleGeneratorMode → @004b099c/@004b09e4/@004b0a2c/@004b0a74/@004b0abc.
### Binary message tables decoded 2026-07-20 (glass input audit) [T1]
16-byte HandlerEntry {id, namePtr, fnPtr, 0} rows in section_dump. Per-receiver-class id spaces.
- **Mech (entity)** @0x50BDF8..: 0x12 TakeDamage@004a0230, 0x14 PlayerLink@0049f624, 0x15
RealMaxSpeed@0049f604, **0x16 BalanceCoolant@0049f728**, 0x17 SetBurningState@0049f674, 0x18
ClearBurningState@0049f700, **0x19 EjectPilot@0049f854**, **0x1a DuckRequest@0049fa00** (the
manual's CROUCH; streamed button 0x13 sends it — unreconstructed).
- **HeatableSubsystem** @0x50E41C: {3, "ToggleCooling"→@004ad6f8} (the Eng-page msg-3 button on
every weapon/avionics — unwired in the reconstruction).
- **Generator** @0x50FB90: {4, "ToggleGeneratorOnOff"→@004b1ed0} (streamed buttons 0x1A-0x1D →
GeneratorA-D — unreconstructed).
- **Searchlight** @0x51120C: {3, "ToggleLamp"→@004b860c}; PowerWatcher/ThermalSight-side variant
@0x51117C: {3, "ToggleLamp"→@004b838c}. (Reconstruction has Searchlight::ToggleLamp but the
MessageHandlers set is default-constructed EMPTY — unwired.)
- **Myomers-class** @0x51158C: {9, "ToggleSeekVoltage"→@004b8a48}; **energy-weapon** variant
@0x511DB8: {0xb, "ToggleSeekVoltage"→@004ba478}; **ammo-weapon** @0x512210: {0xb,
"EjectAmmo"→@004bb9b8}.
- **MechRIOMapper** @0x51DD30: RE-REGISTERS the base ids 3..0x13 (Aux1Quad..ZoomOut) with the
RIO override handlers @004d22fc..@004d24f8, + {0x19, Keypress→@004d2514} (RIO's OWN keypress,
unreconstructed — we use the shared L4 0x17 @004d1bf0) + {0x1a, Hotbox→@004d2574} @0x51DE98.
The reconstruction's enum chained these ids to 0x19-0x2a until 2026-07-20 — every streamed
MFD-bank/zoom press hit the base FAIL trap; now pinned + static_assert-locked (btl4mppr.hpp).
- Unhandled ids are SILENT: `Receiver::Receive` Find() returns NullHandler and does nothing.
- MechWeapon ATTRIBUTE table @0x511890: ELEVEN entries — 0x12 PercentDone@0x320, 0x13 TriggerState@0x31C,
0x14 DistanceToTarget@0x324, 0x15 TargetWithinRange@0x34C, 0x16 WeaponRange@0x328, 0x17 PipPosition,
0x18 PipColor, 0x19 PipExtendedRange, 0x1A EstimatedReadyTime@0x330, 0x1B RearFiring@0x334,
+2 -1
View File
@@ -10,7 +10,8 @@ open_questions:
- "HUD attr table RESOLVED 2026-07-19 (Gitea #10 audit): re-dumped @5110b8 -- the table starts one record earlier (id 3 FlickerRate@0x1D8), every hud.hpp offset was shifted one slot; hud.hpp/CLASSMAP corrected, no behavioral impact (the reticle feeds via port globals). See GAUGE_COMPOSITE.md AUDIT finding B"
- "SeekVoltageGraph RECONSTRUCTED 2026-07-19 (Gitea #11, was #10 finding A): full widget landed (see §SeekVoltageGraph below) -- ghosts gone steady-state (BT_PRESET_HOLD verification); remaining polish: a 1-frame transition artifact when a BT_SHOT lands on the exact page-switch frame (label BecameActive vs the graph's next rated Execute -- same lag class as the binary; self-heals next frame)"
- "Secondary-view cycling RESOLVED 2026-07-19 (Gitea #6): the selector is the DISPLAY mode (CycleDisplayMode -> vtbl+0x4C override @4d1ae4), NOT CycleControlMode; desktop 'N' / pad RightThumb wired; pixel-verified dama->crit->heat"
- "Upper-MFD PRESET pages RESOLVED 2026-07-19 (Gitea #9): SetPresetMode table @0051dbf0 re-decoded (little-endian -> ModeMFD bits 0-14), per-MFD pod button banks identified from the .CTL dump, desktop J/K/L cycle wired; remaining: the always-active 0x28-0x2F / 0x1A-0x1D per-subsystem msg-4 records (subs 3-13 -- likely the condenser-valve/coolant buttons) unidentified"
- "Upper-MFD PRESET pages RESOLVED 2026-07-19 (Gitea #9): SetPresetMode table @0051dbf0 re-decoded (little-endian -> ModeMFD bits 0-14), per-MFD pod button banks identified from the .CTL dump, desktop J/K/L cycle wired"
- "Always-active msg-4 records IDENTIFIED 2026-07-20 (glass input audit): 0x2C = Reservoir InjectCoolant (the flush button), 0x2F/0x2E/0x2D/0x2B/0x2A/0x29 = Condenser1-6 MoveValve, 0x1A-0x1D = GeneratorA-D ToggleGeneratorOnOff (@0050fb90, unreconstructed); plus 0x13 = Mech DuckRequest (crouch), 0x28 = Mech BalanceCoolant, 0x12/0x14 = ThermalSight/Searchlight toggles -- see pod-hardware.md + docs/GLASS_COCKPIT.md 2026-07-20"
- "MP DEATHS resolved 2026-07-12 (observed-death tally + display clamp); remaining: verify multi-death tallies stay in sync across a long session (GAUGE_COMPOSITE.md)"
---
+15
View File
@@ -70,6 +70,21 @@ copied from them.
- **Step 4 (Steam): CODE COMPLETE 2026-07-18 [T2 single-machine].** See [[steam-networking]] —
the wire seam + `L4STEAMNET` identity-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 in `L4PADBINDINGS.cpp`
(auto-writes `content\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. Full audit + census: `docs/GLASS_COCKPIT.md` §2026-07-20.
## Key Relationships
- Extends: [[pod-hardware]] (RIO input path) · Uses: [[gauges-hud]] dev-gauge modes ·
+11
View File
@@ -137,6 +137,17 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
(task #8 "landed" vs the kShotDamage=12 bring-up residue) — audit before reworking.
## Deferred subsystems / feeds (authentic path scoped, marked in code)
- **Glass panel dead-button backlog (input-coverage audit 2026-07-20) [T1 census].** 8 of the 72
panel buttons dispatch a streamed message with NO reconstructed handler (silently ignored):
0x13 → Mech `DuckRequest` @0049fa00 (CROUCH — `duckState` attr + SQUAT clips exist!), 0x28 →
Mech `BalanceCoolant` @0049f728, 0x1A-0x1D → Generator A-D `ToggleGeneratorOnOff` @004b1ed0,
0x12 → ThermalSight toggle (pvision unported), 0x14 → Searchlight `ToggleLamp` (body
reconstructed, handler-set unwired — note the 1995 latent bug below: even wired, the light
never turns on in the original). Plus page-gated Eng-page meanings: `ToggleCooling` @004ad6f8
(msg 3), `ToggleSeekVoltage` @004ba478/@004b8a48 (msg 0xb energy / 9 Myomers), `EjectAmmo`
@004bb9b8 (msg 0xb ammo weapons), and MechRIOMapper's own Keypress @004d2514 (id 0x19).
Filed as the Gitea "glass panel: unreconstructed button handlers backlog" issue; tables in
[[decomp-reference]] §2026-07-20; census in `docs/GLASS_COCKPIT.md`.
- **Searchlight-driven fog swap — DEFERRED, and it is an ORIGINAL 1995 LATENT BUG (task #63,
2026-07-13) [T1 decomp].** The arcade swaps fog between `fog=` (lights on) and `nosearchlightfog=`
(off) via `PullFogRenderable` watching the Searchlight's `lightState`. It never fires — in the PORT
+17 -5
View File
@@ -42,11 +42,23 @@ preset-able MFD owns the 8-button bank AROUND it, mode-mask-gated per page — *
left) = 0x08-0x0F (AuxLowerLeft), Mfd2 (upper center) = 0x20-0x27 (AuxUpperCenter), Mfd3
(lower right) = 0x00-0x07 (AuxLowerRight)**: Quad page → the outer buttons direct-select the
populated Eng pages; Eng page → one button back to Quad, the rest drive the shown subsystem
(generator select A-D, gen mode, weapon config/unjam). Always-active: **0x28-0x2F
(AuxUpperLeft) → per-subsystem msg 0x4 to roster subs 3-9** and secondary-panel 0x1A-0x1D →
subs 10-13 msg 0x4 (identities unverified — likely the condenser-valve / coolant buttons
[T4]); 0x10/0x11 = map ZoomIn/Out, 0x15 = CycleDisplayMode, 0x18 = CycleControlMode (#6).
Full decode: [[gauges-hud]] §preset pages.
(generator select A-D msg 4-7, gen mode msg 8, weapon configure msg 9; also msg 0x3 =
ToggleCooling and msg 0xb = ToggleSeekVoltage (energy) / EjectAmmo (ammo weapons) —
unreconstructed, silently dead). **Always-active records IDENTIFIED 2026-07-20 [T1]** (roster:
sub 3 = Reservoir, 4-9 = Condenser1-6, 10-13 = GeneratorA-D): **0x2C = the COOLANT FLUSH
button** (Reservoir InjectCoolant, hold-to-flush — works), **0x2F/0x2E/0x2D/0x2B/0x2A/0x29 =
the per-condenser VALVE buttons** (MoveValve, Cond1-6 — work), **0x1A-0x1D = Generator A-D
ON/OFF** (`ToggleGeneratorOnOff` id 4, binary table @0050fb90 fn @004b1ed0 — DEAD until
reconstructed). Newly decoded from the binary message tables: **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 the handler-set is default-constructed
EMPTY — unwired)** — all currently dead; an unhandled message is SILENTLY ignored
(Receiver::Receive finds no handler and does nothing — dead buttons produce NO log).
0x10/0x11 = map ZoomIn/Out, 0x15 = CycleDisplayMode, 0x18 = CycleControlMode (#6). Buttons
with NO streamed mapping authored (authentically inert): 0x16/0x17/0x19/0x1E/0x1F/0x38-0x3E.
Full decode: [[gauges-hud]] §preset pages; the complete 72-button census + verification:
`docs/GLASS_COCKPIT.md` §2026-07-20.
### Desktop input remap — CONTROLS.MAP + XInput (2026-07-18) [T2 live]
`game/reconstructed/btinput.cpp` + `content/CONTROLS.MAP` (WASD-classic default, compiled-in twin;
+81
View File
@@ -162,6 +162,87 @@ map `169.254.77.1=<steamID64>` → egg roster carries the token → mission proc
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-18 — Merge origin/master (commit f889e24): btinput meets PadRIO
Master's 33-commit run landed (D1 relay/operator-console line, CONTROLS.MAP+XInput binding
+9
View File
@@ -1605,6 +1605,15 @@ void
{
// swallowed: remapped by CONTROLS.MAP
}
#ifdef BT_GLASS
else if (PadRIO::SuppressKey((unsigned int)keyValue,
msg.message == WM_CHAR))
{
// swallowed: claimed by a bindings.txt row (PadRIO) --
// the glass-side analog of the CONTROLS.MAP suppression
// above (btinput stands down when PadRIO owns the path)
}
#endif
else
{
keyboardGroup[KeyboardPC].ForceUpdate(&keyValue, mode_mask);
+58 -36
View File
@@ -139,6 +139,21 @@ static int
// right/left), 0x45-0x47 pinky/thumb-low/thumb-high, 0x10-0x15 + 0x18-0x1D
// the 12 secondary-panel buttons, 0x00-0x0F the lower aux banks, 0x20-0x37
// the upper aux banks. Keypad unit 0 = the pilot's MFD keypad.
//
// KEYBOARD RECONCILIATION (2026-07-20, input-coverage audit): the keyboard
// carries the ~20 core gameplay actions on the SAME keys the desktop
// CONTROLS.MAP uses (W/S/A/D drive, Q/E twist, R/F elevation, X all-stop,
// 1-4/Space/Ctrl fire, M/N mode/display cycle, H flush, C valve, B look
// behind, G configure, V view); FULL pod-address coverage lives on the
// on-screen panel (every one of the 72 buttons is one click away,
// right-click = hold latch). Keys DISPLACED from the previous default
// (each still reachable by editing bindings.txt or on the panel):
// - number row 1-0/-/= -> secondary-panel 0x10-0x1D (1-4 now fire)
// - QWERTY row Q..P -> pilot MFD keypad unit 0 (dropped)
// - arrow keys -> hat looks 0x41-0x44 (arrows now drive)
// - V/C/B -> fire 0x47/0x46/0x45 (now view/valve/behind)
// - LCTRL -> throttle slew down (now fire 0x47)
// The XInput pad section is unchanged.
//###########################################################################
static const char *defaultProfileText =
@@ -153,8 +168,33 @@ static const char *defaultProfileText =
"# padaxis <PADAXIS> axis <channel> [invert] [slew <rate>]\n"
"# Channels: Throttle JoystickX JoystickY LeftPedal RightPedal\n"
"# Pad buttons: A B X Y LB RB BACK START LS RS DPAD_* Axes: LX LY RX RY LT RT\n"
"#\n"
"# LAYOUT: the keyboard hosts the core gameplay actions on the desktop\n"
"# CONTROLS.MAP keys; the on-screen cockpit panel covers EVERY pod button\n"
"# by click (right-click = press-and-hold latch). A key bound here is\n"
"# REMOVED from the authentic 1995 typed-hotkey channel (no double\n"
"# dispatch); unbound keys keep their 1995 meaning -- 5 = MFD1 Quad page,\n"
"# z = MFD3 Eng1 page, t/y/u/i/o = pilot select 3-7, + / - = target zoom.\n"
"# Hardcoded (not rebindable here): ` or V = view toggle, J/K/L = cycle\n"
"# the Mfd1/Mfd2/Mfd3 preset page.\n"
"\n"
"# --- flight: numpad spring stick, pedals, throttle lever ---\n"
"# --- drive: W/S sweep the throttle lever (sticks; X = all-stop detent),\n"
"# --- A/D spring the turn pedals, Q/E twist the torso, R/F aim it up/down\n"
"key W axis Throttle slew + 0.7\n"
"key S axis Throttle slew - 0.7\n"
"key UP axis Throttle slew + 0.7\n"
"key DOWN axis Throttle slew - 0.7\n"
"key A axis LeftPedal deflect + 2.5\n"
"key D axis RightPedal deflect + 2.5\n"
"key LEFT axis LeftPedal deflect + 2.5\n"
"key RIGHT axis RightPedal deflect + 2.5\n"
"key Q axis JoystickX deflect - 2.5\n"
"key E axis JoystickX deflect + 2.5\n"
"key R axis JoystickY deflect + 2.5\n"
"key F axis JoystickY deflect - 2.5\n"
"key X axis Throttle set 0\n"
"\n"
"# --- numpad flight cluster (alternative stick/pedals/lever) ---\n"
"key NUMPAD8 axis JoystickY deflect + 2.5\n"
"key NUMPAD2 axis JoystickY deflect - 2.5\n"
"key NUMPAD4 axis JoystickX deflect - 2.5\n"
@@ -162,51 +202,33 @@ static const char *defaultProfileText =
"key NUMPAD7 axis LeftPedal deflect + 2.5\n"
"key NUMPAD9 axis RightPedal deflect + 2.5\n"
"key LSHIFT axis Throttle slew + 0.7\n"
"key LCTRL axis Throttle slew - 0.7\n"
"key NUMPAD5 axis Throttle set 0\n"
"\n"
"# --- weapons / hat / handle buttons ---\n"
"# --- fire groups (the joystick's four mappable buttons) + reverse + look\n"
"key 1 button 0x40\n"
"key SPACE button 0x40\n"
"key 2 button 0x46\n"
"key 3 button 0x47\n"
"key LCTRL button 0x47\n"
"key RCTRL button 0x47\n"
"key 4 button 0x45\n"
"key LALT button 0x3F\n"
"key V button 0x47\n"
"key C button 0x46\n"
"key B button 0x45\n"
"key UP button 0x42\n"
"key DOWN button 0x41\n"
"key LEFT button 0x44\n"
"key RIGHT button 0x43\n"
"key B button 0x41\n"
"\n"
"# --- secondary panel (12) on the number row ---\n"
"key 1 button 0x10\n"
"key 2 button 0x11\n"
"key 3 button 0x12\n"
"key 4 button 0x13\n"
"key 5 button 0x14\n"
"key 6 button 0x15\n"
"key 7 button 0x18\n"
"key 8 button 0x19\n"
"key 9 button 0x1A\n"
"key 0 button 0x1B\n"
"key MINUS button 0x1C\n"
"key EQUALS button 0x1D\n"
"# --- systems (pod console buttons) ---\n"
"key M button 0x18 # cycle control mode (BAS/MID/ADV)\n"
"key N button 0x15 # cycle secondary schematic (Damage/Critical/Heat)\n"
"key H button 0x2C # HOLD = coolant flush (Reservoir InjectCoolant)\n"
"key C button 0x2F # cycle Condenser1 coolant valve (MoveValve)\n"
"key G button 0x0E # HOLD + fire key = regroup weapon 1 (Mfd1 Quad page)\n"
"\n"
"# --- aux banks on F5-F8 (upper center 1-4) ---\n"
"# --- Mfd2 bank on F5-F8 + F9 (page-gated: Quad page = select the Eng\n"
"# --- pages, Eng page = generator A-D select / F9 gen-mode toggle) ---\n"
"key F5 button 0x27\n"
"key F6 button 0x26\n"
"key F7 button 0x25\n"
"key F8 button 0x24\n"
"\n"
"# --- pilot MFD keypad on QWERTY row (unit 0, keys 0-9) ---\n"
"key Q keypad 0 1\n"
"key W keypad 0 2\n"
"key E keypad 0 3\n"
"key R keypad 0 4\n"
"key T keypad 0 5\n"
"key Y keypad 0 6\n"
"key U keypad 0 7\n"
"key I keypad 0 8\n"
"key O keypad 0 9\n"
"key P keypad 0 0\n"
"key F9 button 0x22\n"
"\n"
"# --- XInput pad ---\n"
"padaxis LX axis JoystickX\n"
+152 -4
View File
@@ -21,11 +21,23 @@
PadRIO *PadRIO::activeInstance = NULL;
//
// Pending backtick view-toggle edges (set in Poll, consumed by the game's
// Pending backtick/V view-toggle edges (set in Poll, consumed by the game's
// view-toggle block through BTPadViewToggleEdge).
//
int gBTPadViewToggleEdges = 0;
//
// The desktop per-MFD preset-page cycle edges (J/K/L -> Mfd1/2/3), consumed
// by L4MechControlsMapper::InterpretControls (btl4mppr.cpp step 3b -- the
// same seam the dev-build mech4 poll feeds). Defined in mech4.cpp (always
// compiled), so the glass TU externs it -- keyboard reconciliation
// 2026-07-20: J/K/L are the CONTROLS.MAP muscle-memory keys and there is no
// single pod button that "cycles" an MFD (the pod's bank buttons are
// mode-mask-gated direct selects), so the cycle stays a port-side sender
// with the authentic SetPresetMode body.
//
extern int gBTPresetCycle[3];
int
BTPadViewToggleEdge(void)
{
@@ -118,6 +130,7 @@ PadRIO::PadRIO():
memset(lampState, 0, sizeof(lampState));
bindings.Load();
BuildKeySuppression();
//
// Per-channel spring return rate = the fastest deflect rate bound to
@@ -173,6 +186,118 @@ PadRIO::~PadRIO()
}
}
//###########################################################################
// Typed-channel suppression (the btinput sSuppressChar/sSuppressKeyUp
// pattern -- btinput.cpp AddSuppression -- rebuilt here because btinput
// stands down whenever a cockpit device owns the input path). A bound key
// must NOT also reach the 1995 in-cockpit keyboard dispatcher: 'w' selects
// pilot 0, 'a'/'s'/'d'/'f'/'g' flip MFD2 preset pages, letter/numpad
// KEY-UP VK values alias onto lowercase hotkeys (VK_F5==0x74=='t' = pilot
// select 3, VK_NUMPAD2==0x62=='b' = MFD3 Quad, ...). Unbound keys keep
// their authentic 1995 typed meaning.
//###########################################################################
void
PadRIO::AddKeySuppression(int virtual_key)
{
//
// WM_KEYUP delivers the raw VK; every consumer downstream compares
// typed CHARACTERS, so the VK value itself is the alias to swallow.
//
if (virtual_key >= 0 && virtual_key < 256)
{
suppressKeyUp[virtual_key] = 1;
}
//
// WM_CHAR delivers typed characters: both cases of a letter, the digit
// itself (main row AND numpad -- VK_NUMPAD0..9 type '0'..'9'), space,
// and the base punctuation of the Oem keys.
//
if (virtual_key >= 'A' && virtual_key <= 'Z')
{
suppressChar[virtual_key + ('a' - 'A')] = 1;
suppressChar[virtual_key] = 1;
}
else if (virtual_key >= '0' && virtual_key <= '9')
{
suppressChar[virtual_key] = 1;
}
else if (virtual_key >= VK_NUMPAD0 && virtual_key <= VK_NUMPAD9)
{
suppressChar['0' + (virtual_key - VK_NUMPAD0)] = 1;
}
else if (virtual_key == VK_SPACE)
{
suppressChar[' '] = 1;
suppressKeyUp[VK_SPACE] = 1;
}
else
{
static const struct { int vk; char ch; } oem[] =
{
{ VK_OEM_MINUS, '-' }, { VK_OEM_PLUS, '=' },
{ VK_OEM_COMMA, ',' }, { VK_OEM_PERIOD, '.' },
{ VK_OEM_2, '/' }, { VK_OEM_3, '`' },
{ VK_OEM_4, '[' }, { VK_OEM_5, '\\' },
{ VK_OEM_6, ']' }, { VK_OEM_1, ';' },
{ VK_OEM_7, '\'' }, { VK_RETURN, '\r' },
{ VK_TAB, '\t' }, { VK_BACK, '\b' },
};
for (int i = 0; i < (int)(sizeof(oem) / sizeof(oem[0])); ++i)
{
if (oem[i].vk == virtual_key)
{
suppressChar[(unsigned char)oem[i].ch] = 1;
}
}
}
//
// The generic modifier VK is what WM_KEYUP reports for L/R variants.
//
if (virtual_key == VK_LSHIFT || virtual_key == VK_RSHIFT)
{
suppressKeyUp[VK_SHIFT] = 1;
}
if (virtual_key == VK_LCONTROL || virtual_key == VK_RCONTROL)
{
suppressKeyUp[VK_CONTROL] = 1;
}
}
void
PadRIO::BuildKeySuppression()
{
memset(suppressChar, 0, sizeof(suppressChar));
memset(suppressKeyUp, 0, sizeof(suppressKeyUp));
for (int k = 0; k < bindings.keyBindingCount; ++k)
{
AddKeySuppression(bindings.keyBindings[k].virtualKey);
}
//
// The hardcoded keys (Poll): backtick + V = view toggle, J/K/L = the
// per-MFD preset-page cycle.
//
AddKeySuppression(VK_OEM_3);
AddKeySuppression('V');
AddKeySuppression('J');
AddKeySuppression('K');
AddKeySuppression('L');
}
int
PadRIO::SuppressKey(unsigned int key_value, int is_char)
{
if (activeInstance == NULL || key_value > 255)
{
return 0; // no glass device / ALT_BIT-tagged value
}
return is_char
? activeInstance->suppressChar[key_value]
: activeInstance->suppressKeyUp[key_value];
}
//###########################################################################
// Event queue
//###########################################################################
@@ -278,13 +403,16 @@ void
//
// The backtick view toggle (per Cyd: ` = 1st/3rd person in the glass
// cockpit). Edge-detected here (async poll, message-path-free) and
// consumed by the game's view-toggle block via BTPadViewToggleEdge.
// cockpit) + V (the CONTROLS.MAP ViewToggle key -- keyboard
// reconciliation 2026-07-20). Edge-detected here (async poll,
// message-path-free) and consumed by the game's view-toggle block via
// BTPadViewToggleEdge.
//
{
static int s_backtickWas = 0;
int backtick_held = focused &&
(GetAsyncKeyState(VK_OEM_3) & 0x8000) != 0;
(((GetAsyncKeyState(VK_OEM_3) & 0x8000) != 0) ||
((GetAsyncKeyState('V') & 0x8000) != 0));
if (backtick_held && !s_backtickWas)
{
extern int gBTPadViewToggleEdges;
@@ -293,6 +421,26 @@ void
s_backtickWas = backtick_held;
}
//
// J/K/L: cycle the Mfd1/Mfd2/Mfd3 preset page (the CONTROLS.MAP keys;
// the L4 mapper consumes gBTPresetCycle and runs the authentic
// SetPresetMode body -- btl4mppr.cpp CyclePresetModeNow).
//
{
static int s_presetWas[3] = { 0, 0, 0 };
static const int s_presetKey[3] = { 'J', 'K', 'L' };
for (int g = 0; g < 3; ++g)
{
int held = focused &&
(GetAsyncKeyState(s_presetKey[g]) & 0x8000) != 0;
if (held && !s_presetWas[g])
{
gBTPresetCycle[g] = 1;
}
s_presetWas[g] = held;
}
}
float slewDelta[PadBindingProfile::ChannelCount];
int deflectHeld[PadBindingProfile::ChannelCount];
memset(slewDelta, 0, sizeof(slewDelta));
+27
View File
@@ -58,6 +58,20 @@ public:
static int
GetLampState(int unit);
//
// Legacy typed-channel suppression (the btinput BTInputSuppressKey
// pattern, glass-side). A key CLAIMED by a bindings.txt row (or by the
// hardcoded `/V view-toggle and J/K/L preset-cycle keys) is swallowed
// from the engine's WM_CHAR/WM_KEYUP key-command feed (L4CTRL.cpp:1506
// block) -- otherwise every bound letter double-dispatches into the 1995
// in-cockpit dispatcher ('w' = pilot select 0, 'a'/'s'/'d'/'f' = MFD2
// preset pages, NUMPAD key-up VKs alias to 'b'/'d'/'e' hotkeys, ...).
// Unbound keys keep their authentic 1995 typed meaning. Returns 0 when
// no PadRIO is active (the feed flows untouched on dev/pod builds).
//
static int
SuppressKey(unsigned int key_value, int is_char);
enum { LampCount = 128 };
protected:
@@ -105,6 +119,19 @@ protected:
int
lampState[LampCount];
//
// Typed-channel suppression tables (built from the loaded bindings +
// the hardcoded view/preset keys; see SuppressKey).
//
unsigned char
suppressChar[256]; // WM_CHAR values
unsigned char
suppressKeyUp[256]; // WM_KEYUP values (VK aliases)
void
AddKeySuppression(int virtual_key);
void
BuildKeySuppression();
static PadRIO
*activeInstance;
};
+27 -2
View File
@@ -288,16 +288,41 @@
// Messaging Support
//
public:
//
// GLASS INPUT-AUDIT FIX (2026-07-20): these ids are PINNED to the
// binary numbering. The old chain started at
// L4MechControlsMapper::NextMessageID (=0x19), which registered every
// RIO handler at 0x19..0x2a -- but the streamed "L4" .CTL EventMapping
// records carry the BINARY's ids, and the binary RIO table @0051dd30
// (section_dump) RE-REGISTERS the MechControlsMapper aux/zoom ids
// 3..0x13 with the RIO override handlers (that is the whole point of
// the derived table: same message, real body instead of the base FAIL
// trap). With the shifted ids every streamed MFD-bank/zoom button
// press found only the base ConfigureMappableMessageHandler ("[FAIL]
// Unhandled button mapping") -- ~38 of the 72 pod panel buttons dead
// (and, on a real pod with BT_BUTTON_TRAP, an abort()). Hotbox is
// 0x1a in the binary (table entry @0051de98); the ctor/handler pair
// was previously self-consistent at the wrong value (0x2a), so it
// worked by accident -- now it matches the binary.
// (Faithful-fidelity note: the binary RIO table also carries its OWN
// Keypress id 0x19 -> @004d2514; our ctor still registers the shared
// L4 Keypress (0x17, @004d1bf0) -- see the Gitea backlog issue.)
//
enum {
Aux1QuadMessageID = L4MechControlsMapper::NextMessageID, // 3
Aux1QuadMessageID = MechControlsMapper::Aux1QuadMessageID, // 3
Aux1Eng1MessageID, Aux1Eng2MessageID, Aux1Eng3MessageID, Aux1Eng4MessageID,
Aux2QuadMessageID, Aux2Eng1MessageID, Aux2Eng2MessageID, Aux2Eng3MessageID, Aux2Eng4MessageID,
Aux3QuadMessageID, Aux3Eng1MessageID, Aux3Eng2MessageID, Aux3Eng3MessageID, Aux3Eng4MessageID,
ZoomInMessageID, // 0x12
ZoomOutMessageID, // 0x13
HotboxMessageID, // pilot-target select
HotboxMessageID = 0x1a, // pilot-target select (@0051de98)
NextMessageID
};
static_assert(Aux1QuadMessageID == 0x03, "RIO Aux1Quad must be the binary id 3 (table @0051dd30)");
static_assert(Aux3Eng4MessageID == 0x11, "RIO Aux3Eng4 must be the binary id 0x11");
static_assert(ZoomInMessageID == 0x12 && ZoomOutMessageID == 0x13,
"RIO ZoomIn/ZoomOut must be the binary ids 0x12/0x13");
static_assert(HotboxMessageID == 0x1a, "RIO Hotbox must be the binary id 0x1a (@0051de98)");
void
Aux1QuadMessageHandler(ReceiverDataMessageOf<ControlsButton> *message), // @004d22fc