BT_FORCE_TORSO sweeps the twist BUTTONS -- the members the streamed direct mappings write -- so the chain runs without a hand on the RIO. Live on the pod: twist sweeps -0.015 to -0.891 rad and back as cmdL/cmdR alternate, at the resource rate 0.873 rad/s, inside the authored +/-2.443 limits, with jointtorso resolved. End to end: attribute id -> direct-mapping destination -> TorsoSimulation integrator -> skeleton joint -> the canopy eye hanging off that chain. 5.3.68's table and 5.3.71's integrator both confirmed against real button semantics. Two buffer overruns, one of them mine. Reading TORSO.CPP back caught the ctor still zeroing dynamicsState[22] after I shrank the array to [16] by carving the command members out of its front: 24 bytes past the end of every Torso, on the heap, every mission, since 5.3.68. A sweep for the same shape across BT/BT_L4/MUNGA found a pre-existing one -- MECHMPPR.CPP zeroing reserved[22] against reserved[21]. Both now bound by ELEMENTS(). The rule for this tree: any array carved out of a reserve block must have its initialiser bound by ELEMENTS, because the carve is exactly the edit that silently invalidates a literal. Neither explains the residual host fault (it predates 5.3.68 at 3/3), but both were real corruption running in every mission -- and the Torso one was introduced by the very fix that cut the fault rate, which is worth remembering whenever a rate MOVES instead of going to zero. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
340 lines
10 KiB
C++
340 lines
10 KiB
C++
//===========================================================================//
|
|
// File: mechmppr.cpp //
|
|
// Project: BattleTech //
|
|
// Contents: Implementation details for the mech controls mapper //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
|
// All Rights reserved worldwide //
|
|
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
|
//===========================================================================//
|
|
|
|
#include <bt.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(MECHMPPR_HPP)
|
|
# include <mechmppr.hpp>
|
|
#endif
|
|
|
|
#if !defined(MECH_HPP)
|
|
# include <mech.hpp>
|
|
#endif
|
|
|
|
#if !defined(TORSO_HPP)
|
|
# include <torso.hpp>
|
|
#endif
|
|
|
|
Derivation
|
|
MechControlsMapper::ClassDerivations(
|
|
Subsystem::ClassDerivations,
|
|
"MechControlsMapper"
|
|
);
|
|
|
|
//
|
|
//#############################################################################
|
|
// Published control-input attributes. The streamed control mappings bind (by
|
|
// name) to these; the pad entry fills the chain-vs-id gap at id 2.
|
|
//#############################################################################
|
|
//
|
|
const MechControlsMapper::IndexEntry
|
|
MechControlsMapper::AttributePointers[]=
|
|
{
|
|
{ (int)MechControlsMapper::MechControlsMapperPadFirstAttributeID,
|
|
"MechControlsMapperPad02",
|
|
(Simulation::AttributePointer)&MechControlsMapper::throttlePosition },
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, StickPosition, stickPosition),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, ThrottlePosition, throttlePosition),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, PedalsPosition, pedalsPosition),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, ReverseThrust, reverseThrust),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, SpeedDemand, speedDemand),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, TurnDemand, turnDemand),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, LookForward, lookForward),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, LookLeft, lookLeft),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, LookRight, lookRight),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, LookBehind, lookBehind),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, LookDown, lookDown),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, TorsoUp, torsoUp),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, TorsoDown, torsoDown),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, TorsoLeft, torsoLeft),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, TorsoRight, torsoRight),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, TorsoCenter, torsoCenter),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, ControlMode, controlMode),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, DisplayMode, displayMode),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, PilotArrayPage, pilotArrayPage),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, PilotArray, pilotArray),
|
|
ATTRIBUTE_ENTRY(MechControlsMapper, TargetRangeExponent,
|
|
targetRangeExponent)
|
|
};
|
|
|
|
MechControlsMapper::AttributeIndexSet
|
|
MechControlsMapper::AttributeIndex(
|
|
ELEMENTS(MechControlsMapper::AttributePointers),
|
|
MechControlsMapper::AttributePointers,
|
|
Subsystem::AttributeIndex
|
|
);
|
|
|
|
MechControlsMapper::SharedData
|
|
MechControlsMapper::DefaultData(
|
|
MechControlsMapper::ClassDerivations,
|
|
Subsystem::MessageHandlers,
|
|
MechControlsMapper::AttributeIndex,
|
|
1
|
|
);
|
|
|
|
MechControlsMapper::MechControlsMapper(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
CString subsystem_name,
|
|
RegisteredClass::ClassID class_ID,
|
|
SharedData &shared_data
|
|
):
|
|
Subsystem(
|
|
(Entity *)owner,
|
|
subsystem_ID,
|
|
subsystem_name,
|
|
class_ID,
|
|
shared_data
|
|
)
|
|
{
|
|
Check(owner);
|
|
//
|
|
// Prime the published control inputs to neutral. Per-frame interpretation
|
|
// (InterpretControls) is reconstructed with the mech2/3/4 sim (phase 5.3).
|
|
//
|
|
stickPosition.x = 0.0f;
|
|
stickPosition.y = 0.0f;
|
|
throttlePosition = 0.0f;
|
|
pedalsPosition = 0.0f;
|
|
reverseThrust = 0;
|
|
speedDemand = 0.0f;
|
|
turnDemand = 0.0f;
|
|
lookForward = 0;
|
|
lookLeft = 0;
|
|
lookRight = 0;
|
|
lookBehind = 0;
|
|
lookDown = 0;
|
|
torsoUp = 0;
|
|
torsoDown = 0;
|
|
torsoLeft = 0;
|
|
torsoRight = 0;
|
|
torsoCenter = 0;
|
|
controlMode = BasicMode;
|
|
displayMode = 0;
|
|
pilotArrayPage = 0;
|
|
pilotArray = 0;
|
|
targetRangeExponent = 0.0f; // staged: bound by an authored watcher
|
|
lookState = LookNone;
|
|
previousLookState = LookNone;
|
|
{
|
|
//
|
|
// ELEMENTS(), never a literal -- this said 22 against reserved[21]
|
|
// and wrote 4 bytes off the end of every mapper. Found by a sweep
|
|
// after the same mistake surfaced in Torso (5.3.78).
|
|
//
|
|
int
|
|
i;
|
|
for (i = 0; i < (int)ELEMENTS(reserved); ++i)
|
|
{
|
|
reserved[i] = 0;
|
|
}
|
|
}
|
|
//
|
|
// Install the per-frame control-interpretation Performance.
|
|
//
|
|
SetPerformance(&MechControlsMapper::InterpretControls);
|
|
|
|
if (getenv("BT_MECH_LOG"))
|
|
{
|
|
DEBUG_STREAM << "[mapper] ctor id=" << subsystem_ID
|
|
<< " throttleAttr=" << GetAttributePointer(ThrottlePositionAttributeID)
|
|
<< " controlModeAttr=" << GetAttributePointer(ControlModeAttributeID)
|
|
<< endl << flush;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
MechControlsMapper::~MechControlsMapper()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// InterpretControls -- the mapper's per-frame Performance. Reads the raw input
|
|
// attributes (the engine controls push refreshes throttle/stick/pedals/buttons
|
|
// from the RIO/keyboard before this runs) and publishes the locomotion demands
|
|
// (speedDemand in world-u/s, turnDemand [-1..1]) the mech drive consumes.
|
|
//
|
|
// Reconstructs the authentic demand math (mechmppr.cpp @004afd10):
|
|
// * throttle -> forward speed: speedDemand = topSpeed * throttle * fwdScale
|
|
// (reverse thrust inverts and drops the forward scale);
|
|
// * soft stick response: square the yaw (sign preserved), cube the pedals;
|
|
// * Basic: stick yaw = turn; Standard/Veteran: pedals = turn;
|
|
// * speed is clamped down while turning hard (max_turn ramp).
|
|
//
|
|
// DEV hook (headless verification, no RIO/keyboard): BT_FORCE_THROTTLE /
|
|
// BT_FORCE_TURN override the pushed raw inputs so the demand math + mech drive
|
|
// are exercisable without hardware. The torso-aim / free-look interpretation
|
|
// (torso analog axes, look/eyepoint commit) is the aiming wave, deferred.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
MechControlsMapper::InterpretControls(Scalar time_slice)
|
|
{
|
|
Check(this);
|
|
|
|
Mech *mech = GetMech();
|
|
Check(mech);
|
|
|
|
//
|
|
// DEV forced-input override.
|
|
//
|
|
{
|
|
const char *force_throttle = getenv("BT_FORCE_THROTTLE");
|
|
if (force_throttle != NULL)
|
|
{
|
|
Scalar t = (Scalar)atof(force_throttle);
|
|
throttlePosition = (t >= 0.0f) ? t : -t;
|
|
reverseThrust = (t < 0.0f) ? 1 : 0;
|
|
}
|
|
const char *force_turn = getenv("BT_FORCE_TURN");
|
|
if (force_turn != NULL)
|
|
{
|
|
stickPosition.x = (Scalar)atof(force_turn);
|
|
}
|
|
const char *force_elev = getenv("BT_FORCE_ELEV");
|
|
if (force_elev != NULL)
|
|
{
|
|
stickPosition.y = (Scalar)atof(force_elev);
|
|
}
|
|
const char *force_look = getenv("BT_FORCE_LOOK");
|
|
if (force_look != NULL)
|
|
{
|
|
int state = atoi(force_look);
|
|
lookLeft = (state == (int)LookLeftState) ? 1 : 0;
|
|
lookRight = (state == (int)LookRightState) ? 1 : 0;
|
|
lookBehind = (state == (int)LookBehindState) ? 1 : 0;
|
|
lookDown = (state == (int)LookDownState) ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
Torso *torso = (Torso *)mech->GetTorsoSubsystem();
|
|
|
|
Scalar topSpeed = mech->GetReverseStrideLength();
|
|
Scalar walkSpeed = mech->GetWalkStrideLength();
|
|
|
|
//
|
|
// Throttle -> forward speed demand.
|
|
//
|
|
if (reverseThrust < 1)
|
|
{
|
|
speedDemand = topSpeed * throttlePosition * mech->GetForwardThrottleScale();
|
|
}
|
|
else
|
|
{
|
|
speedDemand = -topSpeed * throttlePosition;
|
|
}
|
|
|
|
//
|
|
// Soft response: square the stick yaw (sign preserved), cube the pedals.
|
|
//
|
|
Scalar stick_x = stickPosition.x * stickPosition.x;
|
|
if (stickPosition.x < 0.0f)
|
|
{
|
|
stick_x = -stick_x;
|
|
}
|
|
Scalar stick_y = stickPosition.y * stickPosition.y;
|
|
if (stickPosition.y < 0.0f)
|
|
{
|
|
stick_y = -stick_y;
|
|
}
|
|
Scalar pedal_3 = pedalsPosition * pedalsPosition * pedalsPosition;
|
|
|
|
turnDemand = 0.0f;
|
|
|
|
//
|
|
// Torso aim. Every control mode routes the stick pitch (stick_y) into the
|
|
// torso weapon-elevation axis. In Basic the stick yaw is the turn (legs);
|
|
// in Standard/Veteran the stick yaw is the torso twist (free aim) and the
|
|
// pedals steer. (The HUD free-aim slew + look/eyepoint commit are the
|
|
// aiming/camera wave, deferred.)
|
|
//
|
|
if (controlMode == BasicMode)
|
|
{
|
|
turnDemand = stick_x;
|
|
if (torso != NULL)
|
|
{
|
|
torso->SetAnalogElevationAxis(stick_y);
|
|
torso->SetAnalogTwistAxis(0.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
turnDemand = pedal_3;
|
|
if (torso != NULL)
|
|
{
|
|
torso->SetAnalogElevationAxis(stick_y);
|
|
torso->SetAnalogTwistAxis(stick_x);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clamp forward speed down while turning hard (except VeteranMode, which has
|
|
// no speed-dependent turn clamp). max_turn ramps from topSpeed (no turn) to
|
|
// walkSpeed (full turn).
|
|
//
|
|
if (controlMode != VeteranMode)
|
|
{
|
|
Scalar turn_mag = (turnDemand < 0.0f) ? -turnDemand : turnDemand;
|
|
if (turn_mag > 0.001f)
|
|
{
|
|
Scalar max_turn = (topSpeed - walkSpeed) * (1.0f - turn_mag) + walkSpeed;
|
|
if (speedDemand > max_turn) speedDemand = max_turn;
|
|
if (speedDemand < -max_turn) speedDemand = -max_turn;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Look / eyepoint selection. Choose a look direction from the buttons;
|
|
// when it changes, commit it -- the mech re-aims the eyepoint from its
|
|
// authored look angles (and, once the weapon wave lands, re-arms the
|
|
// per-view weapon fire enables through the same commit).
|
|
//
|
|
previousLookState = lookState;
|
|
if (lookLeft > 0) lookState = LookLeftState;
|
|
else if (lookRight > 0) lookState = LookRightState;
|
|
else if (lookBehind > 0) lookState = LookBehindState;
|
|
else if (lookDown > 0) lookState = LookDownState;
|
|
else lookState = LookNone;
|
|
|
|
if (lookState != previousLookState)
|
|
{
|
|
mech->CommitLookState(lookState);
|
|
}
|
|
|
|
if (getenv("BT_MECH_LOG"))
|
|
{
|
|
static Scalar reportAccum = 0.0f;
|
|
reportAccum += time_slice;
|
|
if (reportAccum >= 1.0f)
|
|
{
|
|
reportAccum = 0.0f;
|
|
DEBUG_STREAM << "[mppr] thr=" << throttlePosition
|
|
<< " rev=" << reverseThrust
|
|
<< " stickX=" << stickPosition.x
|
|
<< " stickY=" << stickPosition.y
|
|
<< " mode=" << controlMode
|
|
<< " -> speedDemand=" << speedDemand
|
|
<< " turnDemand=" << turnDemand
|
|
<< " torsoElev=" << (torso ? torso->CurrentElevation() : 0.0f)
|
|
<< endl << flush;
|
|
}
|
|
}
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
Logical
|
|
MechControlsMapper::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(ClassDerivations);
|
|
}
|