Files
TeslaRel410/restoration/source410/BT/TORSO.CPP
T
CydandClaude Fable 5 3c289ff10a BT410 5.3.78: the torso twists from the button path; two buffer overruns fixed
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>
2026-07-29 19:52:38 -05:00

399 lines
12 KiB
C++

//===========================================================================//
// File: torso.cpp //
// Project: BattleTech Brick: Mech subsystems //
// Contents: Torso -- torso twist / weapon elevation //
//---------------------------------------------------------------------------//
// 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(TORSO_HPP)
# include <torso.hpp>
#endif
#if !defined(MECH_HPP)
# include <mech.hpp>
#endif
#if !defined(JOINT_HPP)
# include <joint.hpp>
#endif
Derivation
Torso::ClassDerivations(
PowerWatcher::ClassDerivations,
"Torso"
);
//
// The full 13-entry authentic table (ids 3..15) -- donor torso.cpp:152 with
// binary offsets, cross-confirmed by BTL4.RES's streamed control mappings
// binding ids 12/13 on subsystem 17. The five command members and
// analogTwistAxis are DIRECT WRITE DESTINATIONS for device polls
// (L4CTRL.CPP:1936), which is why every id from 9 up must exist and be the
// right kind of storage: id 9 short of this table landed the RIO's analog
// stick on the motionState StateIndicator.
//
// SpeedOf* still point at the base rates rather than the donor's live
// velocity accumulators (elevationVelocity/twistVelocity @0x1EC/0x1E8) --
// those members arrive with TorsoSimulation; the authored audio watcher on
// SpeedOfTorsoHorizontal just reads a constant until then.
//
const Torso::IndexEntry
Torso::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Torso, RotationOfTorsoVertical, currentElevation),
ATTRIBUTE_ENTRY(Torso, RotationOfTorsoHorizontal, currentTwist),
ATTRIBUTE_ENTRY(Torso, HorizontalLimitRight, horizontalLimitRight),
ATTRIBUTE_ENTRY(Torso, HorizontalLimitLeft, horizontalLimitLeft),
ATTRIBUTE_ENTRY(Torso, SpeedOfTorsoVertical, baseElevationRate),
ATTRIBUTE_ENTRY(Torso, SpeedOfTorsoHorizontal, baseTwistRate),
ATTRIBUTE_ENTRY(Torso, StickPosition, analogTwistAxis),
ATTRIBUTE_ENTRY(Torso, TorsoUp, elevateUpCommand),
ATTRIBUTE_ENTRY(Torso, TorsoDown, elevateDownCommand),
ATTRIBUTE_ENTRY(Torso, TorsoLeft, twistLeftCommand),
ATTRIBUTE_ENTRY(Torso, TorsoRight, twistRightCommand),
ATTRIBUTE_ENTRY(Torso, TorsoCenter, centerCommand),
ATTRIBUTE_ENTRY(Torso, MotionState, motionState)
};
Torso::AttributeIndexSet
Torso::AttributeIndex(
ELEMENTS(Torso::AttributePointers),
Torso::AttributePointers,
MechSubsystem::AttributeIndex
);
Torso::SharedData
Torso::DefaultData(
Torso::ClassDerivations,
Subsystem::MessageHandlers,
Torso::AttributeIndex,
Subsystem::StateCount
);
Torso::Torso(
Mech *owner,
int subsystem_ID,
SubsystemResource *r,
SharedData &shared_data
):
PowerWatcher(owner, subsystem_ID, r, shared_data)
{
Check(owner);
Check_Pointer(r);
isDamagedCopy = (owner->GetInstance() == Entity::ReplicantInstance);
statusFlags = 0;
buttonAccelerationPerSecond = r->buttonAccelerationPerSecond;
buttonAccelerationStart = r->buttonAccelerationStartValue;
baseTwistRate = r->horizontalRotationPerSecond * RAD_PER_DEG;
baseElevationRate = r->verticalRotationPerSecond * RAD_PER_DEG;
horizontalLimitRight= r->horizontalLimitRight * RAD_PER_DEG;
horizontalLimitLeft = r->horizontalLimitLeft * RAD_PER_DEG;
verticalLimitTop = r->verticalLimitTop * RAD_PER_DEG;
verticalLimitBottom = r->verticalLimitBottom * RAD_PER_DEG;
twistCenterHigh = verticalLimitTop;
twistCenterLow = verticalLimitBottom;
elevationCenter = verticalLimitTop;
elevationHalfBottom = verticalLimitBottom * 0.5f;
buttonRampActive = 0;
buttonRamp = 0.0f;
horizontalEnabled = r->torsoHorizontalEnabled;
//
// The device-poll write destinations (published ids 9..14). Zero =
// stick centred, no button held -- their state before the first poll.
//
elevateUpCommand = 0;
elevateDownCommand = 0;
twistLeftCommand = 0;
twistRightCommand = 0;
centerCommand = 0;
recenterActive = 0;
//
// Resolve the named torso-twist skeleton joints (twist body + shadow) from
// the mech skeleton (now live). A mech with a fixed torso, or one whose
// joint name is absent from the skeleton, resolves NULL and simply never
// twists.
//
horizontalJointNode = owner->ResolveJoint(r->torsoHorizontalJoint);
horizontalShadowJointNode = owner->ResolveJoint(r->torsoHorizontalShadowJoint);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[torso] horizJoint '" << r->torsoHorizontalJoint
<< "' -> " << (void *)horizontalJointNode;
if (horizontalJointNode != NULL)
{
DEBUG_STREAM << " type=" << (int)horizontalJointNode->GetJointType();
}
DEBUG_STREAM << " enabled=" << (int)horizontalEnabled << endl << flush;
}
currentTwist = 0.0f;
currentElevation = 0.0f;
analogElevationAxis = 0.0f;
analogTwistAxis = 0.0f;
//
// ELEMENTS(), never a literal. This loop said 22 while the array was
// shrunk to 16 by carving the command members out of its front (5.3.68 /
// 5.3.71) -- 24 bytes written past the end of every Torso, on the heap,
// every mission. It compiled and ran; C++ does not check. The macro
// makes the next carve harmless.
//
int
i;
for (i = 0; i < (int)ELEMENTS(dynamicsState); ++i)
{
dynamicsState[i] = 0.0f;
}
//
// Install the per-frame twist/elevation Performance (the replicant copy is
// driven by console updates via TorsoCopySimulation instead, still staged).
//
if (owner->GetInstance() != Entity::ReplicantInstance)
{
SetPerformance(&Torso::TorsoSimulation);
}
Check_Fpu();
}
Torso::~Torso()
{
}
Logical
Torso::TestClass(Mech &)
{
return True;
}
Logical
Torso::TestInstance() const
{
return IsDerivedFrom(ClassDerivations);
}
//
//#############################################################################
// TorsoSimulation -- per-frame torso twist / weapon-elevation integration.
//
// Slews the current elevation/twist toward the analog aim axes (written by
// MechControlsMapper::InterpretControls) at the resource base rates, clamped to
// the resource limits. Authentic core (torso.cpp @004b6xxx): current += axis *
// baseRate * dt. APPLYING the resolved angles onto the skeleton torso/gun
// joints (horizontalJointNode / the elevation joint) is deferred with the
// skeleton-link + render wave; here we advance the scalar aim state (read by the
// HUD reticle / weapon aim and, once wired, the joints).
//#############################################################################
//
void
Torso::TorsoSimulation(Scalar time_slice)
{
Check(this);
//
// The BUTTON COMMANDS (donor torso.cpp:557, binary @004b5cf0). The five
// command members are the streamed direct mappings' write destinations --
// the RIO/TM twist and elevation buttons land in them as raw
// ControlsButton ints (positive = held). The donor's ramp machinery is
// carried verbatim, including its punchline: the shipped build
// unconditionally overwrites the ramp with 1.0f, so the acceleration
// ramp is authored dead weight. Kept because it is the binary's shape.
//
Scalar
twist_step = baseTwistRate * time_slice,
elev_step = baseElevationRate * time_slice;
//
// DEV HOOK BT_FORCE_TORSO=1: sweep the twist BUTTONS left/right without a
// hand on the RIO. This drives the exact members the streamed direct
// mappings write (twistLeftCommand / twistRightCommand), so it exercises
// the real button path end to end -- table binding, this integrator, the
// skeleton joint, and the cockpit eye that rides the twist chain -- and
// makes the result visible on the render bridge. The donor carries the
// same hook for the same reason (torso.cpp:583).
//
// Held for ~90 frames each way, matching the donor's period.
//
if (getenv("BT_FORCE_TORSO") != NULL)
{
static int
sweep = 0;
sweep++;
twistLeftCommand = ((sweep / 90) & 1) ? 1 : 0;
twistRightCommand = ((sweep / 90) & 1) ? 0 : 1;
}
if (buttonRampActive == 0)
{
buttonRamp = buttonAccelerationStart;
}
else
{
buttonRamp += buttonAccelerationPerSecond * time_slice;
if (buttonRamp > 1.0f)
{
buttonRamp = 1.0f;
}
buttonRampActive = 0;
}
buttonRamp = 1.0f; // @004b5cf0 unconditionally overwrites with 1.0f
if (elevateUpCommand > 0)
{
currentElevation += elev_step * buttonRamp;
if (currentElevation > verticalLimitTop)
{
currentElevation = verticalLimitTop;
}
buttonRampActive = 1;
}
if (elevateDownCommand > 0)
{
currentElevation -= elev_step * buttonRamp;
if (currentElevation < verticalLimitBottom)
{
currentElevation = verticalLimitBottom;
}
buttonRampActive = 1;
}
//
// Elevation (weapon pitch): slew toward the analog axis, clamp to limits.
//
currentElevation += analogElevationAxis * baseElevationRate * time_slice;
{
Scalar lo = (verticalLimitBottom < verticalLimitTop)
? verticalLimitBottom : verticalLimitTop;
Scalar hi = (verticalLimitBottom < verticalLimitTop)
? verticalLimitTop : verticalLimitBottom;
if (currentElevation < lo) currentElevation = lo;
if (currentElevation > hi) currentElevation = hi;
}
//
// Twist (horizontal torso rotation): only mechs with an enabled torso joint.
//
if (horizontalEnabled)
{
//
// Digital twist commands, then recenter, then the analog axis --
// the donor's order. Twist buttons cancel a pending recenter;
// the centre button arms it and it slews home across frames
// (donor Recenter, binary @004b6918).
//
if (twistLeftCommand > 0)
{
currentTwist += twist_step * buttonRamp;
if (currentTwist > horizontalLimitLeft)
{
currentTwist = horizontalLimitLeft;
}
recenterActive = 0;
buttonRampActive = 1;
}
if (twistRightCommand > 0)
{
currentTwist -= twist_step * buttonRamp;
if (currentTwist < horizontalLimitRight)
{
currentTwist = horizontalLimitRight;
}
recenterActive = 0;
buttonRampActive = 1;
}
if (centerCommand > 0)
{
recenterActive = 1;
buttonRampActive = 0;
}
if (recenterActive != 0)
{
if (currentTwist > 0.0f)
{
currentTwist -= twist_step;
if (currentTwist < 0.0f) currentTwist = 0.0f;
}
else if (currentTwist < 0.0f)
{
currentTwist += twist_step;
if (currentTwist > 0.0f) currentTwist = 0.0f;
}
if (currentTwist == 0.0f)
{
recenterActive = 0;
}
}
if (getenv("BT_TORSO_LOG") != NULL)
{
static int
torso_tick = 0;
if ((torso_tick++ % 240) == 0)
{
DEBUG_STREAM << "[torso] twist=" << currentTwist
<< " elev=" << currentElevation
<< " cmdL=" << twistLeftCommand
<< " cmdR=" << twistRightCommand
<< " axis=" << analogTwistAxis
<< " rate=" << baseTwistRate
<< " limits=(" << horizontalLimitRight
<< ".." << horizontalLimitLeft << ")"
<< " joint=" << (horizontalJointNode != NULL ? 1 : 0)
<< endl << flush;
}
}
currentTwist += analogTwistAxis * baseTwistRate * time_slice;
Scalar lo = (horizontalLimitLeft < horizontalLimitRight)
? horizontalLimitLeft : horizontalLimitRight;
Scalar hi = (horizontalLimitLeft < horizontalLimitRight)
? horizontalLimitRight : horizontalLimitLeft;
if (currentTwist < lo) currentTwist = lo;
if (currentTwist > hi) currentTwist = hi;
//
// Push the twist onto the skeleton torso joint so the upper body rotates
// on the model. Hinge joints take the scalar angle about their axis;
// ball joints take it as the yaw component.
//
if (horizontalJointNode != NULL)
{
Joint::JointType jt = horizontalJointNode->GetJointType();
if (jt == Joint::HingeXJointType
|| jt == Joint::HingeYJointType
|| jt == Joint::HingeZJointType)
{
horizontalJointNode->SetRotation(Radian(currentTwist));
}
else if (jt == Joint::BallJointType)
{
horizontalJointNode->SetRotation(EulerAngles(0.0f, currentTwist, 0.0f));
}
}
}
Check_Fpu();
}
void
Torso::TorsoCopySimulation(Scalar)
{
Fail("Torso::TorsoCopySimulation -- torso.cpp not yet reconstructed");
}