Files
TeslaRel410/restoration/source410/BT/TORSO.CPP
T
CydandClaude Fable 5 bd1070110b BT410 5.3.71: torso button commands wired into the sim; residual fault rate measured
TorsoSimulation now carries the donor's digital command handling (binary
@004b5cf0): elevate up/down, twist left/right with limit clamps, the
centre-button recenter latch slewing home across frames, and the ramp
machinery kept verbatim including the binary's punchline -- the shipped build
unconditionally overwrites the ramp with 1.0f, authored dead weight preserved
as the binary's shape.  recenterActive carved from the dynamicsState reserve.
With 5.3.68's table fix the RIO/TM twist buttons land in these members and
move the torso, and the canopy eye rides the twist chain.

Also corrected in the roadmap: the 5.3.70 TriggerState 'wart' was not a wart
-- CheckFireEdge already compares fireImpulse's bit pattern as a signed int,
sign-correct for button ints and floats alike, and the donor binds and types
the member identically.

Fault rate, measured: riostreak.sh ran four consecutive vRIO-live runs --
CLEAN/CLEAN/FAULT/CLEAN, faults only ever in the load window (ticks 42-53k),
clean runs always past launch.  Post-fix tally 8 runs / 2 faults (~25%; was
3/3 before the Torso fix).  Our load window is ~60s where shipped's is ~15s:
if the residual is a constant-rate hazard during load, shipped's expected
per-run rate is only ~7% -- 'shipped survives' may be exposure luck, and the
residual is most likely an emulator-fork serial/DPMI interaction rather than
a game defect.  Next probe: hook the fork's page-fault path on the fixed cr2
and dump recent serial-IRQ deliveries + guest cs:ip history, plus a
shipped-exe streak for the baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 15:44:12 -05:00

349 lines
10 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;
for (int i = 0; i < 22; ++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;
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;
}
}
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");
}