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>
This commit is contained in:
Cyd
2026-07-26 03:23:27 -05:00
co-authored by Claude Opus 5
parent 56d9971b0e
commit 5f88a4eeb2
12 changed files with 1489 additions and 189 deletions
+2 -2
View File
@@ -111,8 +111,8 @@ struct NamedButton { int address; const char *name; };
static const NamedButton namedButtons[] =
{
{ 0x3D, "Panic" }, { 0x3F, "Throttle" },
{ 0x40, "Main" }, { 0x41, "Hat Bk" }, { 0x42, "Hat Up" },
{ 0x43, "Hat R" }, { 0x44, "Hat L" }, { 0x45, "Pinky" },
{ 0x40, "Main" }, { 0x41, "Look Bk" }, { 0x42, "Torso Ctr" },
{ 0x43, "Look R" }, { 0x44, "Look L" }, { 0x45, "Pinky" },
{ 0x46, "Middle"}, { 0x47, "Upper" },
};
enum { NamedButtonCount = sizeof(namedButtons) / sizeof(namedButtons[0]) };
+133 -63
View File
@@ -161,20 +161,35 @@ static int
// 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.
// THE KEYBOARD IS THE BUTTON BOARD (2026-07-26, ported from RP412).
//
// Every one of the pod's 72 buttons is reachable from the keyboard, laid out
// WHERE IT SITS ON THE PANEL: the number row is the upper MFDs' top row and
// QWERTY the row beneath it, running left to right across Heat / Engineering /
// Comm; the home row and the row below it do the same for the two lower
// weapon MFDs; F1-F12 are the two columns flanking the map. G and B stay
// unbound on purpose -- they are the physical gap between the lower clusters.
// Flight moves to the NUMPAD (which already carried it as an alternative), so
// the letter field is free for the board.
//
// It lands on BT's own addresses better than it has any right to: 1-4 and
// QWER are the Heat display's two rows, which is the entire coolant system
// (Condensers 1-6, the flush and the balance button); F6/F7 are the display
// and control-mode cycles; F9-F12 are Generators A-D.
//
// WHAT THIS COSTS (the deliberate trade). A bound key is REMOVED from the
// authentic 1995 typed-hotkey channel to stop double-dispatch, and this board
// binds nearly the whole field -- so the typed hotkeys go: 5 = MFD1 Quad page,
// z = MFD3 Eng1, t/y/u/i/o = pilot select 3-7, +/- = target zoom. They remain
// reachable on the panel and by editing this file. The built-in convenience
// keys yield the same way: V and J/K/L are board buttons now, so PadRIO's
// view-toggle and preset-cycle polls stand down for them (KeyHasBinding) and
// the view toggle lives on BACKTICK alone.
//
// PREVIOUS DEFAULT (2026-07-20 - 2026-07-26) was the ~20-action CONTROLS.MAP
// mirror: W/S/A/D drive, Q/E twist, R/F elevation, 1-4/Space/Ctrl fire,
// M/N/H/C/G systems. An existing bindings.txt is never overwritten, so a
// player only meets this board by deleting theirs.
//###########################################################################
static const char *defaultProfileText =
@@ -192,66 +207,121 @@ static const char *defaultProfileText =
"# made for mapping a gamepad stick to turning)\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"
"# LAYOUT: THE KEYBOARD IS THE POD'S BUTTON BOARD. The letter and number\n"
"# rows are the MFD button banks laid out where they sit on the panel, and\n"
"# flight lives on the numpad so the board stays free. Every one of the 72\n"
"# pod buttons is also one mouse click away on the cockpit itself\n"
"# (right-click = press-and-hold latch).\n"
"#\n"
"# A key bound here is REMOVED from the authentic 1995 typed-hotkey channel\n"
"# so it cannot double-dispatch -- and this board binds nearly everything,\n"
"# so those hotkeys (5 = MFD1 Quad, z = MFD3 Eng1, t/y/u/i/o = pilot select,\n"
"# +/- = target zoom) are given up by design. Unbind a key here to get its\n"
"# 1995 meaning back. Same rule for the built-ins: BACKTICK = view toggle\n"
"# (V is a board button now), and J/K/L cycle the Mfd1/2/3 preset pages ONLY\n"
"# while unbound.\n"
"\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"
"# --- flight: the numpad ------------------------------------------------\n"
"# 8/2/4/6 stick, 7/9 pedals, 5 all-stop detent, 0 main trigger.\n"
"# SHIFT/CTRL walk the throttle lever and it STICKS where you leave it;\n"
"# ALT is the throttle handle's reverse-thrust button.\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"
"key NUMPAD6 axis JoystickX deflect + 2.5\n"
"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 NUMPAD5 axis Throttle set 0\n"
"\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 LSHIFT axis Throttle slew + 0.7\n"
"key RSHIFT axis Throttle slew + 0.7\n"
"key LCTRL axis Throttle slew - 0.7\n"
"key RCTRL axis Throttle slew - 0.7\n"
"key LALT button 0x3F\n"
"key B button 0x41\n"
"key RALT button 0x3F\n"
"\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"
"# --- the four mappable fire buttons ------------------------------------\n"
"key SPACE button 0x40 # main trigger\n"
"key NUMPAD0 button 0x40 # main trigger\n"
"key NUMPAD1 button 0x45 # pinky\n"
"key NUMPAD3 button 0x46 # middle thumb\n"
"key NUMPADDOT button 0x47 # upper thumb\n"
"\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"
"key F9 button 0x22\n"
"# --- the hat: look around, on the arrows -------------------------------\n"
"key UP button 0x42 # torso CENTER (the shipped .RES name)\n"
"key DOWN button 0x41 # look behind\n"
"key LEFT button 0x44 # look left\n"
"key RIGHT button 0x43 # look right\n"
"\n"
"# --- UPPER MFD banks: number row on top, QWERTY beneath ----------------\n"
"# Heat / coolant (0x28-0x2F) -- 1-4 and QWER ARE the coolant system:\n"
"# 1/2/3 Condenser 1-3 valve, 4 coolant FLUSH (hold), Q/W/E Condenser\n"
"# 4-6 valve, R balance coolant. The valve detents run 1-5-50-CLOSED.\n"
"key 1 button 0x2F\n"
"key 2 button 0x2E\n"
"key 3 button 0x2D\n"
"key 4 button 0x2C\n"
"key Q button 0x2B\n"
"key W button 0x2A\n"
"key E button 0x29\n"
"key R button 0x28\n"
"# Engineering / Mfd2 (0x20-0x27) -- page-gated: Quad page picks an Eng\n"
"# page, Eng page drives generator select A-D / bus mode / coolant.\n"
"key 5 button 0x27\n"
"key 6 button 0x26\n"
"key 7 button 0x25\n"
"key 8 button 0x24\n"
"key T button 0x23\n"
"key Y button 0x22\n"
"key U button 0x21\n"
"key I button 0x20\n"
"# Comm / target hotbox (0x30-0x37) -- pilot select.\n"
"key 9 button 0x37\n"
"key 0 button 0x36\n"
"key MINUS button 0x35\n"
"key EQUALS button 0x34\n"
"key O button 0x33\n"
"key P button 0x32\n"
"key LBRACKET button 0x31\n"
"key RBRACKET button 0x30\n"
"\n"
"# --- LOWER MFD banks: home row on top, the row below beneath -----------\n"
"# G and B are the PANEL GAP between the two lower clusters -- left\n"
"# unbound on purpose, so they keep their 1995 typed meaning.\n"
"# Left Weapons / Mfd1 (0x08-0x0F).\n"
"key A button 0x0F\n"
"key S button 0x0E\n"
"key D button 0x0D\n"
"key F button 0x0C\n"
"key Z button 0x0B\n"
"key X button 0x0A\n"
"key C button 0x09\n"
"key V button 0x08\n"
"# Right Weapons / Mfd3 (0x00-0x07).\n"
"key H button 0x07\n"
"key J button 0x06\n"
"key K button 0x05\n"
"key L button 0x04\n"
"key N button 0x03\n"
"key M button 0x02\n"
"key COMMA button 0x01\n"
"key PERIOD button 0x00\n"
"\n"
"# --- the two columns flanking the map, top to bottom -------------------\n"
"# Left (0x10-0x15): map zoom in/out, thermal IR, CROUCH, searchlight,\n"
"# cycle secondary display. Right (0x18-0x1D): cycle control mode\n"
"# (BAS/MID/ADV), unused, Generator A-D on/off.\n"
"key F1 button 0x10\n"
"key F2 button 0x11\n"
"key F3 button 0x12\n"
"key F4 button 0x13\n"
"key F5 button 0x14\n"
"key F6 button 0x15\n"
"key F7 button 0x18\n"
"key F8 button 0x19\n"
"key F9 button 0x1A\n"
"key F10 button 0x1B\n"
"key F11 button 0x1C\n"
"key F12 button 0x1D\n"
"\n"
"# --- XInput pad ---\n"
"# POD-FAITHFUL (default): stick X = torso twist, triggers = pedals.\n"
+4 -4
View File
@@ -96,10 +96,10 @@ static const NamedButton namedButtons[] =
{ 0x3D, "Panic" },
{ 0x3F, "Throttle" },
{ 0x40, "Main" },
{ 0x41, "Hat Back" },
{ 0x42, "Hat Up" },
{ 0x43, "Hat Right" },
{ 0x44, "Hat Left" },
{ 0x41, "Look Back" },
{ 0x42, "Torso Ctr" },
{ 0x43, "Look Right" },
{ 0x44, "Look Left" },
{ 0x45, "Pinky" },
{ 0x46, "Middle" },
{ 0x47, "Upper" },
+26 -2
View File
@@ -69,6 +69,24 @@ int
// takes no engine headers (l4keylight.h is scalars only), so it hands us
// finished lines instead.
//
//
// Does the loaded profile bind this virtual key? The built-in convenience
// keys (view toggle, preset cycles) consult this and stand down when the
// player's bindings claim the key -- see the note at their poll sites.
//
Logical
PadRIO::KeyHasBinding(int virtualKey) const
{
for (int i = 0; i < bindings.keyBindingCount; ++i)
{
if (bindings.keyBindings[i].virtualKey == virtualKey)
{
return True;
}
}
return False;
}
static void
KeyLightLog(const char *line)
{
@@ -615,11 +633,17 @@ void
// message-path-free) and consumed by the game's view-toggle block via
// BTPadViewToggleEdge.
//
// ⚠ A bindings.txt ROW WINS over these built-in convenience keys
// (2026-07-26). The default profile now hands the whole letter field to
// the MFD banks -- V is a Left Weapons button, J/K/L are Right Weapons --
// and polling them here as well would fire BOTH the button and the
// built-in, which is the double-dispatch the typed-channel suppression
// exists to prevent. Bind the key, and the built-in stands down.
{
static int s_backtickWas = 0;
int backtick_held = focused &&
(((GetAsyncKeyState(VK_OEM_3) & 0x8000) != 0) ||
((GetAsyncKeyState('V') & 0x8000) != 0));
(!KeyHasBinding('V') && (GetAsyncKeyState('V') & 0x8000) != 0));
if (backtick_held && !s_backtickWas)
{
extern int gBTPadViewToggleEdges;
@@ -638,7 +662,7 @@ void
static const int s_presetKey[3] = { 'J', 'K', 'L' };
for (int g = 0; g < 3; ++g)
{
int held = focused &&
int held = focused && !KeyHasBinding(s_presetKey[g]) &&
(GetAsyncKeyState(s_presetKey[g]) & 0x8000) != 0;
if (held && !s_presetWas[g])
{
+6
View File
@@ -148,6 +148,12 @@ protected:
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).
+2 -2
View File
@@ -551,8 +551,8 @@ void BTCockpitComputeLayout(int canvasW, int canvasH, BTCockpitLayout *out)
{
static const char *thrLabel[8] = { 0, 0, 0, 0, 0, "Panic", 0, "Throttle" }; // 0x38..0x3F
static const int thrInert[8] = { 1, 1, 1, 1, 1, 0, 1, 0 };
static const char *joyLabel[8] = { "Main", "Hat Bk", "Hat Up", "Hat R",
"Hat L", "Pinky", "Middle", "Upper" }; // 0x40..0x47
static const char *joyLabel[8] = { "Main", "Look Bk", "Torso Ctr", "Look R",
"Look L", "Pinky", "Middle", "Upper" }; // 0x40..0x47
// These cells carry TEXT, so the surround runs them bigger than the
// exploded window's 58x28 -- the caller owns the cell size
// (l4riobank.h), only the grid walk is shared.