Files
BT411/engine/MUNGA_L4/L4PADRIO.h
T
CydandClaude Opus 5 5f88a4eeb2 The keyboard becomes the button board (RP412 bindings port) + a controls page
Ported RP412's bindings design and made it the DEFAULT: the letter and number
rows are the MFD button banks laid out WHERE THEY SIT ON THE PANEL, flight
moves to the numpad so the board stays free, F1-F12 are the map's two columns,
and G/B stay unbound as the physical gap between the lower clusters.  Expressed
in BT's OWN grammar (slew/deflect/set, the Turn channel) -- bindings.txt is a
documented compatibility surface and was not touched.

Coverage: 61 of 72 addresses on the keyboard, and the 11 absent ones are
exactly those the pod never wired (0x16/0x17/0x1E/0x1F column gaps, 0x38-0x3E
intercom/door).  All 72 stay clickable on the panel.

It lands on BT's addresses unreasonably well: 1-4 + QWER are the ENTIRE coolant
system (Condensers 1-6, flush, balance), F6/F7 the display and control-mode
cycles, F9-F12 Generators A-D, F4 the crouch button reconstructed earlier
today.

THE DELIBERATE COST.  A bound key is removed from the authentic 1995
typed-hotkey channel so it cannot double-dispatch, and this board binds nearly
everything -- so 5 (Quad page), z (Eng1), t/y/u/i/o (pilot select) and +/-
(target zoom) are given up.  Unbind a key to get its 1995 meaning back.
Documented in the file header, the markdown and the page.

NEW RULE: A BINDINGS ROW WINS OVER A BUILT-IN CONVENIENCE KEY
(PadRIO::KeyHasBinding).  V and J/K/L are board buttons now, and those polls
read GetAsyncKeyState directly -- without this they would have fired BOTH the
button and the built-in.  View toggle lives on BACKTICK alone.

CONTROLS.MAP rewritten to mirror the board so glass/pod/dev still feel
identical (the 2026-07-21 settlement): 90 bindings, 0 parse complaints.

HAT LABELS CORRECTED [T1].  INPUT_PATH_AUDIT flagged 0x41-0x44 and was right.
Settled from the streamed mapping (BT_CTRLMAP_LOG): elem 66 -> subsys 17
attrID 14, i.e. 0x42 is the TORSO subsystem, not a look; 0x44/0x43/0x41 are the
mapper's LookLeft/LookRight/LookBehind (attrID 10/11/12).  The .RES has no
"TORSO CENTER" string -- its names are LookBehind/Down/Forward/Left/Right -- so
the audit's wording was loose but its substance correct.  Swept L4GLASSWIN,
L4PADPANEL and L4VB16.

docs/CONTROLS.html: interactive keyboard (hover a key for its address and
meaning, MFD clusters outlined), pad diagram, panel map, the under-glass
press-target figure, radar placement thumbnails and the environ.ini table --
modelled on RP412's page, rewritten for BT's addresses and hazards.  mkdist
wraps the fragment in a doctype/charset shell and ships it beside CONTROLS.txt.

Verified: bindings.txt regenerates and loads (74 keys, 0 errors); CONTROLS.MAP
90 bindings, 0 errors; 72/72 panel addresses still dispatch; surround /
exploded / dock / pod / dev boot and simulate with zero faults.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 03:23:27 -05:00

181 lines
5.5 KiB
C++

#pragma once
//###########################################################################
//
// L4PADRIO -- the hardware-less cockpit device (BT_GLASS only).
//
// PadRIO synthesizes the pod's abstract control surface (RIOBase: the five
// analog Scalars + button/keypad events + lamps) from an XInput controller
// and the PC keyboard, so the whole stock RIO path above the seam -- the
// LBE4ControlsManager push, MechRIOMapper, streamed .CTL mappings, lamp
// writes -- runs with no serial hardware. Selected at runtime with
// L4CONTROLS=PAD (the factory arm in L4CTRL.cpp).
//
// Input model (bindings: L4PADBINDINGS.h, content\bindings.txt):
// - keyboard keys emit button/keypad edge events and drive the analog
// channels (spring-stick deflect / throttle-lever slew / detent set)
// - XInput pad buttons emit button events; pad axes drive the channels
// directly (or as slew rates); hot-plug re-probe every ~3 s
// - on-screen cockpit buttons (the L4PADPANEL window, or any WndProc)
// inject through the static SetScreenButton() entry
// - lamps are recorded in lampState[] for the on-screen panel to render
//
// The keyboard is only read while a window of THIS process has focus.
// L4PADFLIP=1 inverts both stick axes.
//
//###########################################################################
#include "l4rio.h"
#include "l4padbindings.h"
class PadRIO :
public RIOBase
{
public:
PadRIO();
~PadRIO();
Logical
GetNextEvent(RIOEvent *destinationPointer);
void
SetLamp(int lampNumber, int state);
Logical
IsOperational() const
{ return True; }
//
// The on-screen panel / WndProc entries. Safe no-ops when no PadRIO
// is active (pod build never links this TU; a glass build without
// L4CONTROLS=PAD never constructs one). Same-thread use only: the
// panel WndProc runs on the app thread's message pump.
//
static Logical
IsActive();
static void
SetScreenButton(int unit, int pressed);
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:
void
Poll();
void
PushEvent(const RIOEvent &event);
void
EmitButton(int address, int pressed);
void
EmitKeypad(int unit, int key);
PadBindingProfile
bindings;
//
// Event queue (ring; drop-newest on overflow, logged).
//
enum { EventQueueSize = 128 };
RIOEvent
eventQueue[EventQueueSize];
int
eventHead, eventTail;
//
// Poll state.
//
unsigned long
lastPollMilliseconds;
unsigned long
lastPadProbeMilliseconds;
int
padIndex; // connected XInput slot, -1 = none
unsigned
previousPadButtons;
unsigned char
previousKeyHeld[192]; // per key BINDING (parallel to the profile)
//
// Generic-joystick edge state, per BINDING (parallel to the profile's
// joy tables -- the previousKeyHeld pattern). A device detach reads
// everything as released, so held buttons let go automatically (the
// issue-#24 rule).
//
unsigned char
previousJoyButtonHeld[48];
unsigned char
previousJoyHatHeld[16];
//
// Direct-axis RELEASE EDGES (issue #36, night-2: "the mech may keep
// turning when control is released"). A direct-mode axis write was
// gated on raw != 0, so an axis returning INSIDE the deadzone simply
// stopped writing -- the channel LATCHED at the last deflection (fatal
// on channels with no keyboard spring, e.g. the Turn composite). Track
// the previous post-deadzone value per binding: the outside->inside
// transition writes 0 exactly once, then leaves the channel to other
// inputs (the composition model is preserved).
//
float
previousPadAxisRaw[12]; // parallel to padAxisBindings
float
previousJoyAxisRaw[24]; // parallel to joyAxisBindings
float
channelValue[PadBindingProfile::ChannelCount];
float
channelReturnRate[PadBindingProfile::ChannelCount];
int
flipStickAxes; // L4PADFLIP
int
lampState[LampCount];
// RGB keyboard lamp mirror running? (l4keylight.h; BT_KEYLIGHT=0 off,
// also stays False when no bound key carries a lamp address)
Logical
keyLightActive;
// True when the loaded bindings claim this virtual key. The built-in
// convenience keys (` / V view toggle, J/K/L preset cycles) yield to an
// explicit row rather than double-dispatching with it.
Logical
KeyHasBinding(int virtualKey) const;
//
// 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;
public:
//
// Is the desktop pad RIO the live input device? (0 on real pod hardware,
// whose RIO is the serial Ranger board -- see BTPadRIOActive.)
//
static int
HasActiveInstance() { return activeInstance != 0; }
};