Input: PadRIO -- play without the pod (L4CONTROLS=PAD), Workstream A.1

Ported from RP412: RIOBase split out of the serial RIO (L4RIO.h),
rioPointer is RIOBase* (L4CTRL.h), PAD token -> new PadRIO() speaking
the RIO surface from an XInput pad + keyboard (L4PADRIO/L4PADBINDINGS,
vRIO bindings.txt grammar, hot-plug), KeyLight RGB mirror TU
(BT412KEYLIGHT, /std:c++17 per-file).

BT-side fixes PadRIO forced into the open:
- Both keyboard input bridges (mech4.cpp, mechmppr.cpp BT_KEY_BRIDGE)
  stand down when a RIO device exists -- they overwrote the engine
  controls push every frame. M/X conveniences stay live.
- Mapper attribute chain OFF BY ONE (latent real-pod bug): the DOS
  chain below MechControlsMapper carried two base attributes, WinTesla
  carries one, and AttributeIndexSet::Find is positional -- the .CTL
  stick mapping wrote throttlePosition. Pad slot + binary-locked enum;
  gotcha ledgered (reconstruction-gotchas #11).

Verified: PAD throttle lever ramps + sticks, stick turns with the
authentic speed-vs-turn clamp (61.5 -> 22.0 u/s), mech drives; keyboard
fallback intact (BT_FORCE_THROTTLE harness). New diags: BT_CTRLMAP_LOG,
BT_STICK_LOG. (Phase 2 of docs/BT412-ROADMAP.md)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-14 08:16:33 -05:00
co-authored by Claude Fable 5
parent 55e4295dc3
commit fe97746a51
17 changed files with 2050 additions and 60 deletions
+55 -8
View File
@@ -65,6 +65,17 @@
# include <pilot.hpp>
#endif
#include <torso.hpp> // real Torso: analog aim axes @0x1F0/0x1F4, horizontalEnabled @0x250
// LBE4ControlsManager (rioPointer presence gates the key bridge) -- needs
// the btl4app/btl4mode forward-decl order btl4mppr.cpp / mech4.cpp use.
#if !defined(BTL4APP_HPP)
# include <btl4app.hpp>
#endif
#if !defined(BTL4MODE_HPP)
# include <btl4mode.hpp>
#endif
#if !defined(L4CTRL_HPP)
# include <l4ctrl.hpp>
#endif
#include <hud.hpp> // real HUD: freeAimSlew @0x28C
#define JM_CLOSE_ENOUGH 1.0e-4f // _DAT_004b027c
@@ -252,6 +263,15 @@ MechControlsMapper::MessageHandlerSet&
const MechControlsMapper::IndexEntry
MechControlsMapper::AttributePointers[]=
{
//
// BT412 pad slot (see the mechmppr.hpp enum note): the DOS chain below
// the mapper carried a second base attribute the WinTesla chain lacks,
// and Find() is positional -- this entry occupies the missing slot so
// the stream's IDs (stick=3...) land on the right members. No stream
// references ID 2 (verified via BT_CTRLMAP_LOG); a lookup returns NULL.
//
{ Subsystem::NextAttributeID, "BT412BaseSlotPad",
(Simulation::AttributePointer)0 },
ATTRIBUTE_ENTRY(MechControlsMapper, StickPosition, stickPosition), // 0x114
ATTRIBUTE_ENTRY(MechControlsMapper, ThrottlePosition, throttlePosition), // 0x11C
ATTRIBUTE_ENTRY(MechControlsMapper, PedalsPosition, pedalsPosition), // 0x120
@@ -595,6 +615,18 @@ void
{
static const int s_keyBridge =
(getenv("BT_KEY_BRIDGE") == 0 || *getenv("BT_KEY_BRIDGE") != '0');
//
// BT412: a live RIO device (the pod board, or PadRIO's pad/keyboard
// synthesizer under L4CONTROLS=PAD) feeds the analog input attributes
// through the engine controls push that just ran -- the keyboard
// bridge's ANALOG writes below would clobber the device every frame.
// They stand down when a device exists; the desktop conveniences
// (M mode-cycle, X torso-recenter) stay live either way.
//
LBE4ControlsManager *bridge_controls = (application != 0)
? (LBE4ControlsManager *)application->GetControlsManager() : 0;
const int device_owns_input =
(bridge_controls != 0 && bridge_controls->rioPointer != 0);
if (s_keyBridge && mech != 0 && application != 0
&& (Entity *)mech == application->GetViewpointEntity())
{
@@ -670,8 +702,11 @@ void
}
}
}
throttlePosition = (key_throttle >= 0.0f) ? key_throttle : -key_throttle;
reverseThrust = (key_throttle < 0.0f) ? 1 : 0;
if (!device_owns_input)
{
throttlePosition = (key_throttle >= 0.0f) ? key_throttle : -key_throttle;
reverseThrust = (key_throttle < 0.0f) ? 1 : 0;
}
// CONTROL-MODE AXIS ROUTING (2026-07-13, the pod mapping): in
// BASIC the stick steers the legs (turn) and the torso auto-
// centers; in STANDARD/VETERAN the stick is the TORSO (free aim /
@@ -690,13 +725,19 @@ void
}
if (controlMode == BasicMode)
{
stickPosition.x = key_turn;
pedalsPosition = 0.0f;
if (!device_owns_input)
{
stickPosition.x = key_turn;
pedalsPosition = 0.0f;
}
}
else
{
stickPosition.x = gBTTwistAxis; // the torso axis
pedalsPosition = key_turn; // A/D = the pedals
if (!device_owns_input)
{
stickPosition.x = gBTTwistAxis; // the torso axis
pedalsPosition = key_turn; // A/D = the pedals
}
// 'X' recenter pulse (2026-07-13): one-frame centerCommand
// -> the sim arms recenterActive and Recenter (@004b6918)
// slews the torso home; any Q/E input cancels it sim-side.
@@ -712,15 +753,21 @@ void
gBTTorsoRecenter = 0;
rcTorso->CommandRecenter();
}
else
else if (!device_owns_input)
{
// A live device's mapped button owns
// centerCommand; only the keyboard
// bridge clears it while unpressed.
rcTorso->ClearRecenterCommand();
}
}
}
}
}
stickPosition.y = 0.0f;
if (!device_owns_input)
{
stickPosition.y = 0.0f;
}
}
}