Torso elevation (pitch aim) wired: the pod stick's Y axis lives

The mechs could always tilt their aim up/down -- Torso models the full
vertical axis (currentElevation, rate, VerticalLimitTop/Bottom,
recenter) and EVERY 1995 control mode routes stickPosition.y into
Torso::SetAnalogElevationAxis -- but the desktop bridge hard-zeroed
stick Y, so the axis was dead on a keyboard rig.  The one pod control
the remap left unwired.

- btinput: JoystickY axis -> elevTarget/elevActive/elevAbsolute
- mech4 shim: sElev integrator (same walk/spring model as the twist;
  X recenters pitch too via gBTElevRecenter)
- mechmppr bridge: feeds stickPosition.y every bridged frame (the old
  unconditional zero removed); both mode branches covered
- CONTROLS.MAP (+ numpad profile + compiled default): R/F = aim
  up/down, pad LeftStickY = elevation (was unused)
- torso.hpp: CurrentElevation()/ElevationVelocity() accessors (diag)
- [mppr] trace gains stickY (note: the trace reads AFTER the next
  frame's device push re-zeroes the stick -- input flows regardless)

Verified live: R held -> torso elevation climbs at the authored rate
and clamps at 0.349066 rad = exactly 20.0 deg (the Blackhawk's
VerticalLimitTop); release holds the aim.  The eyepoint correctly
stays level -- pitch aims the GUNS and reads on the HUD's vertical
elevation tape, as in the pod.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-18 16:44:22 -05:00
co-authored by Claude Fable 5
parent 0a77d8e54b
commit 1271d3bc76
8 changed files with 101 additions and 4 deletions
+5 -1
View File
@@ -46,7 +46,8 @@
# ---- WASD CLASSIC (the shipped default) ----------------------------------
# Driving: W/S sweep the throttle lever (sticks, detent at zero); A/D are
# the turn pedals (spring back); Q/E twist the torso; X = all stop.
# the turn pedals (spring back); Q/E twist the torso; R/F aim the torso
# up/down (elevation); X = all stop (recenters twist AND pitch).
key W axis Throttle rate 0.7
key S axis Throttle rate -0.7
key Up axis Throttle rate 0.7
@@ -57,6 +58,8 @@ key Left axis LeftPedal deflect 1
key Right axis RightPedal deflect 1
key Q axis JoystickX deflect -1
key E axis JoystickX deflect 1
key R axis JoystickY deflect 1
key F axis JoystickY deflect -1
key X action AllStop
# Fire: the pod joystick's four buttons.
@@ -83,6 +86,7 @@ key F9 action Reconnect
# Left stick steers (the pedals), right stick twists the torso and pushes
# the throttle lever; triggers fire.
padaxis LeftStickX axis Pedals deadzone 0.24
padaxis LeftStickY axis JoystickY deadzone 0.24
padaxis RightStickX axis JoystickX deadzone 0.24
padaxis RightStickY axis Throttle rate 0.75 deadzone 0.24
pad RightTrigger button 0x40 # Main
+3
View File
@@ -21,6 +21,8 @@ key NumPad4 axis LeftPedal deflect 1 # turn left
key NumPad6 axis RightPedal deflect 1 # turn right
key NumPad7 axis JoystickX deflect -1 # torso twist left
key NumPad9 axis JoystickX deflect 1 # torso twist right
key Home axis JoystickY deflect 1 # torso aim up
key End axis JoystickY deflect -1 # torso aim down
key NumPad5 action AllStop
key Shift axis Throttle rate 0.75 # throttle up (alt)
key Ctrl axis Throttle rate -0.75 # throttle down (alt)
@@ -82,6 +84,7 @@ key OemCloseBrackets button 0x30 # hotbox
# ---- Xbox controller (same as the default profile) ----------------------
padaxis LeftStickX axis Pedals deadzone 0.24
padaxis LeftStickY axis JoystickY deadzone 0.24
padaxis RightStickX axis JoystickX deadzone 0.24
padaxis RightStickY axis Throttle rate 0.75 deadzone 0.24
pad RightTrigger button 0x40
+4 -1
View File
@@ -44,7 +44,10 @@ press = `buttonGroup[a].Update(a+1, modeMask)` (mask saved), release = `Update(-
button <addr>|axis <axis> deflect|rate <n>|keypad pilot|external <0-15>|pckey <char>|action
<name>`; `pad <button>` same targets; `padaxis <src> axis ... [invert] [deadzone d] [rate n]`.
Axes: Throttle (lever, rate-walks + sticks), LeftPedal/RightPedal/Pedals (turn, springs),
JoystickX (torso twist). Emissions mirror the RIO conventions above exactly; the four fire
JoystickX (torso twist), **JoystickY (torso ELEVATION — pitch aim; every 1995 control mode
routes it to Torso::SetAnalogElevationAxis; keys R/F + pad LeftStickY in the default map;
verified to the authentic VerticalLimitTop clamp, 20° on the Blackhawk; aims the GUNS — reads
on the HUD elevation tape, the eyepoint stays level)**. Emissions mirror the RIO conventions above exactly; the four fire
buttons (0x40/45/46/47) publish as levels to the bring-up fire channels instead of buttonGroup
(avoids double-fire once the streamed ChooseButton mappings are exercised). XInput loads
dynamically (xinput1_4 → 9_1_0; disconnected-pad probing rate-limited to 1 Hz). **Keys claimed by
+12 -1
View File
@@ -47,7 +47,7 @@ enum BTAxisID
BTAxisRightPedal,
BTAxisPedals, // signed convenience alias: + = right pedal
BTAxisJoystickX, // the pod stick X = torso twist (Standard mode)
BTAxisJoystickY, // parsed + tracked; no consumer yet
BTAxisJoystickY, // the pod stick Y = torso ELEVATION (pitch aim)
BTAxisCount
};
@@ -240,6 +240,8 @@ static const char *sDefaultProfile =
"key Right axis RightPedal deflect 1\n"
"key Q axis JoystickX deflect -1\n"
"key E axis JoystickX deflect 1\n"
"key R axis JoystickY deflect 1\n"
"key F axis JoystickY deflect -1\n"
"key X action AllStop\n"
"key D1 button 0x40\n"
"key Space button 0x40\n"
@@ -258,6 +260,7 @@ static const char *sDefaultProfile =
"key F8 action Generator4\n"
"key F9 action Reconnect\n"
"padaxis LeftStickX axis Pedals deadzone 0.24\n"
"padaxis LeftStickY axis JoystickY deadzone 0.24\n"
"padaxis RightStickX axis JoystickX deadzone 0.24\n"
"padaxis RightStickY axis Throttle rate 0.75 deadzone 0.24\n"
"pad RightTrigger button 0x40\n"
@@ -953,6 +956,14 @@ void
next.twistTarget = twist;
next.twistActive = axisActive[BTAxisJoystickX];
next.twistAbsolute = axisAbsolute[BTAxisJoystickX];
// stick Y = torso ELEVATION (pitch aim) -- every 1995 control mode routes
// it to Torso::SetAnalogElevationAxis (mechmppr InterpretControls)
float elev = axisDeflect[BTAxisJoystickY];
if (elev > 1.0f) elev = 1.0f;
if (elev < -1.0f) elev = -1.0f;
next.elevTarget = elev;
next.elevActive = axisActive[BTAxisJoystickY];
next.elevAbsolute = axisAbsolute[BTAxisJoystickY];
next.leverRate = axisRate[BTAxisThrottle];
gBTInput = next;
+3
View File
@@ -34,8 +34,11 @@ struct BTInputState
int turnActive; // any turn input held (else spring-center)
float twistTarget; // torso-twist deflection target [-1,1]
int twistActive;
float elevTarget; // torso-elevation (pitch) target [-1,1]
int elevActive;
int turnAbsolute; // pad stick overrides the integrator outright
int twistAbsolute;
int elevAbsolute;
// the four pod joystick fire buttons (levels)
int fireTrigger; // 0x40 Main
+51
View File
@@ -654,6 +654,8 @@ static int gBTPinkyKey = 0; // key '4' = the pod's 4th fire butto
int gBTModeCycle = 0; // 'M' edge: cycle the control mode (mapper consumes)
int gBTLookBehind = 0; // 'V' held: the pod's rear-view button (task #68)
float gBTTwistAxis = 0.0f; // Q/E torso-twist deflection (assisted-mode stick X)
float gBTElevAxis = 0.0f; // R/F torso-elevation (pitch aim, stick Y)
int gBTElevRecenter = 0; // X edge: zero the pitch axis
int gBTTorsoRecenter = 0; // 'X' edge: pulse the authentic torso recenter (mapper consumes)
static int gBTConfigKey = 0; // task #6: HOLD 'G' = the weapon-configure button
static int gBTGenSelKey = 0; // task #12: F5..F8 = SelectGeneratorA..D, F9 = mode toggle
@@ -2625,9 +2627,57 @@ void
{
sTwist = 0.0f;
gBTTorsoRecenter = 1; // mapper consumes -> CommandRecenter()
{
extern int gBTElevRecenter;
gBTElevRecenter = 1; // pitch axis zeroes below
}
}
sPrevXT = xtNow;
gBTTwistAxis = sTwist;
// TORSO ELEVATION (pitch aim -- the pod stick's Y
// axis; every 1995 control mode routes it to
// Torso::SetAnalogElevationAxis). Same integrator
// model as the twist: keys walk the axis, release
// springs the RATE to 0 (position holds, the sim's
// vertical limits clamp); a pad stick is absolute.
{
extern float gBTElevAxis;
static float sElev = 0.0f;
if (gBTInput.elevAbsolute)
{
sElev = gBTInput.elevTarget;
}
else if (gBTInput.elevActive)
{
sElev += gBTInput.elevTarget * kStickRate * dt;
if (sElev > 1.0f) sElev = 1.0f;
if (sElev < -1.0f) sElev = -1.0f;
}
else if (sElev != 0.0f)
{
const float estep = kStickCenterRate * dt;
if (sElev > estep) sElev -= estep;
else if (sElev < -estep) sElev += estep;
else sElev = 0.0f;
}
extern int gBTElevRecenter; // set by the X edge above
if (gBTElevRecenter)
{
gBTElevRecenter = 0;
sElev = 0.0f; // X recenters pitch too
}
gBTElevAxis = sElev;
if (getenv("BT_INPUT_LOG"))
{
static float s_eacc = 0.0f; s_eacc += dt;
if (s_eacc >= 1.0f) { s_eacc = 0.0f;
DEBUG_STREAM << "[input] elev tgt="
<< gBTInput.elevTarget << " act="
<< gBTInput.elevActive << " axis="
<< sElev << "\n" << std::flush; }
}
}
}
// gBTDrive.fire = "any weapon trigger down" (feeds the bring-up
// damage dispatcher + the beam-visual keepalive)
@@ -3158,6 +3208,7 @@ void
<< " pre=" << preThrottle
<< " rev=" << mppr->reverseThrust
<< " stickX=" << mppr->stickPosition.x
<< " stickY=" << mppr->stickPosition.y
<< " -> speedDemand=" << mppr->speedDemand
<< " turnDemand=" << mppr->turnDemand
<< " mode=" << mppr->controlMode
+19 -1
View File
@@ -719,6 +719,14 @@ void
gBTModeCycle = 0;
CycleControlModeNow();
}
// TORSO ELEVATION (pitch): the pod stick's Y axis -- every
// control mode routes stickPosition.y into
// Torso::SetAnalogElevationAxis below, so the bridge feeds
// it in BOTH branches (keys R/F or a pad stick via btinput).
{
extern float gBTElevAxis;
stickPosition.y = gBTElevAxis;
}
if (controlMode == BasicMode)
{
stickPosition.x = key_turn;
@@ -751,7 +759,8 @@ void
}
}
}
stickPosition.y = 0.0f;
// (stickPosition.y no longer zeroed here -- the bridge above
// feeds the elevation axis every bridged frame, 2026-07-18)
}
}
@@ -822,6 +831,15 @@ void
{
torso->SetAnalogElevationAxis(stick_y); // raw torso+0x1f4 (500)
torso->SetAnalogTwistAxis(0.0f); // raw torso+0x1f0
if (getenv("BT_INPUT_LOG"))
{
static float s_eacc2 = 0.0f; s_eacc2 += time_slice;
if (s_eacc2 >= 1.0f) { s_eacc2 = 0.0f;
DEBUG_STREAM << "[input] mppr elev in=" << stick_y
<< " torsoElev=" << torso->CurrentElevation()
<< " vel=" << torso->ElevationVelocity()
<< "\n" << std::flush; }
}
}
if (cockpit)
{
+4
View File
@@ -171,6 +171,10 @@ class Joint; // engine skeleton node (JOINT.h); the twist target
// Address of the twist scalar (task #56: the binary bt_mech stream tail
// re-points gyro+0x258 to torso+0x1D8; the gyro damage-response reads it).
Scalar* CurrentTwistAddr() { return &currentTwist; }
// Current vertical (pitch) torso aim in radians (binary torso+0x1e4) --
// elevation-axis bring-up diagnostics (2026-07-18).
Scalar CurrentElevation() const { return currentElevation; }
Scalar ElevationVelocity() const { return elevationVelocity; }
protected:
// Resolve a named skeleton node to its live Joint* via the owning Mech
// (the ctor @004b6b0c inlines Mech::ResolveJoint == FUN_00424b60). Defined