The damaged-copy twist path (@004b65f8 + @004b6510), reconstructed from the raw decomp rather than the BT411 donor -- which drifted twice: its ease aged by lastUpdate (+0x14) where the binary reads lastPerformance (+0x10), and it calls the joint-write helper from the copy sim where 1995 does not (port-era addition, dropped). The extrapolator predicts targetTwist = twistAtUpdate + twistRate * elapsed on two time bases (settled = this frame's window, slewing = the command window), and the copy sim snaps when settled or eases by the remaining-time form dt/((stamp - now) + dt) -- landing exactly when the clock reaches the command stamp. The decomp's DAT_0052140c divide is just Time::operator- inlined; no hand conversion existed. Members carved from the dynamicsState reserve (16->12): targetTwist @0x218, twistAtUpdate @0x21C, twistRate @0x238, lastUpdateTime @0x254. The ctor now registers the copy Performance on replicants (PTR @00510c1c); [T2] the gate mirrors the binary's simulationFlags 0xC/0x100 pair with the instance test, same as the whole watcher family, until the stream builders that seed those bits are reconstructed. Feeders (Read/WriteUpdateRecord) stay staged -- zero-init eases a copy to centre, correct-by-vacuity in single-pod. Stubs: 16 across 10 files. Regression: clean mission soak on the rig (no faults, wheel turning, pacing steady). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
233 lines
8.5 KiB
C++
233 lines
8.5 KiB
C++
//===========================================================================//
|
|
// File: torso.hpp //
|
|
// Project: BattleTech Brick: Mech subsystems //
|
|
// Contents: Torso -- the torso twist / weapon-elevation subsystem //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
|
// All Rights reserved worldwide //
|
|
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
|
//===========================================================================//
|
|
|
|
#if !defined(TORSO_HPP)
|
|
# define TORSO_HPP
|
|
|
|
# if !defined(POWERSUB_HPP)
|
|
# include <powersub.hpp>
|
|
# endif
|
|
|
|
//##################### Forward Class Declarations #######################
|
|
class Mech;
|
|
class Joint;
|
|
|
|
//###########################################################################
|
|
//##################### Torso Model Resource ###########################
|
|
//###########################################################################
|
|
|
|
struct Torso__SubsystemResource:
|
|
public PowerWatcher::SubsystemResource
|
|
{
|
|
Scalar horizontalRotationPerSecond;
|
|
Scalar verticalRotationPerSecond;
|
|
Scalar horizontalLimitRight;
|
|
Scalar horizontalLimitLeft;
|
|
Scalar verticalLimitTop;
|
|
Scalar verticalLimitBottom;
|
|
char torsoHorizontalJoint[32];
|
|
char torsoHorizontalShadowJoint[32];
|
|
Logical torsoHorizontalEnabled;
|
|
Scalar buttonAccelerationPerSecond;
|
|
Scalar buttonAccelerationStartValue;
|
|
};
|
|
|
|
//###########################################################################
|
|
//############################### Torso ################################
|
|
//###########################################################################
|
|
//
|
|
// Drives torso twist (horizontal) and weapon elevation (vertical). The
|
|
// tuning rates/limits come from the resource; the live twist/elevation state
|
|
// and the resolved skeleton joints are advanced by the per-frame
|
|
// TorsoSimulation (staged) once the Mech skeleton link is live.
|
|
//
|
|
class Torso:
|
|
public PowerWatcher
|
|
{
|
|
public:
|
|
static Derivation ClassDerivations;
|
|
static SharedData DefaultData;
|
|
|
|
typedef void
|
|
(Torso::*Performance)(Scalar time_slice);
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
|
|
static Logical
|
|
TestClass(Mech &);
|
|
Logical
|
|
TestInstance() const;
|
|
Scalar
|
|
CurrentTwist() const { Check(this); return currentTwist; }
|
|
Scalar
|
|
CurrentElevation() const { Check(this); return currentElevation; }
|
|
Logical
|
|
GetHorizontalEnabled() const{ Check(this); return horizontalEnabled; }
|
|
|
|
//
|
|
// Analog aim inputs (written by MechControlsMapper::InterpretControls;
|
|
// consumed by TorsoSimulation to slew the twist/elevation each frame).
|
|
//
|
|
void
|
|
SetAnalogElevationAxis(Scalar axis) { Check(this); analogElevationAxis = axis; }
|
|
void
|
|
SetAnalogTwistAxis(Scalar axis) { Check(this); analogTwistAxis = axis; }
|
|
|
|
void
|
|
TorsoSimulation(Scalar time_slice);
|
|
//
|
|
// The extrapolation helper (binary @004b6510): predict targetTwist
|
|
// from the last update snapshot, clamp to the twist limits, return
|
|
// True while the copy is still slewing toward a future command time.
|
|
//
|
|
Logical
|
|
ComputeTargetTwist();
|
|
|
|
void
|
|
TorsoCopySimulation(Scalar time_slice);
|
|
|
|
public:
|
|
typedef Torso__SubsystemResource SubsystemResource;
|
|
|
|
Torso(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
~Torso();
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Attribute publication. The shipped string pool lists this class's
|
|
// attribute names contiguously after the class name "Torso":
|
|
//
|
|
// RotationOfTorsoVertical RotationOfTorsoHorizontal
|
|
// HorizontalLimitRight HorizontalLimitLeft
|
|
// SpeedOfTorsoVertical SpeedOfTorsoHorizontal
|
|
//
|
|
// The FULL authentic table, ids 3..15, confirmed twice over: the BT411
|
|
// donor's decompiled enum carries these names WITH binary member offsets
|
|
// (torso.hpp 111-124, @0x1E4..@0x20C), and BTL4.RES's own streamed
|
|
// control mappings bind ids 12/13 as DIRECT button destinations on
|
|
// subsystem 17 = Torso (the [map] audit).
|
|
//
|
|
// An earlier revision declared "TorsoUp/Down/Left/Right/Center are
|
|
// MESSAGES, not attributes" and stopped at 7 entries with MotionState at
|
|
// id 9. That was WRONG in a specifically nasty way: the ids are the
|
|
// binding key for the streamed mappings, so Thrustmaster's buttons
|
|
// (12/13) resolved NULL -- the boot write-fault at
|
|
// ControlsInstanceDirectOf<int>::Update+0xE -- and the RIO's ANALOG
|
|
// StickPosition (9) resolved to our motionState StateIndicator, letting
|
|
// every analog update from a live RIO smash a watcher-socketed object
|
|
// with raw floats.
|
|
//
|
|
// The chain -- PowerWatcher -> HeatWatcher -> MechSubsystem -- publishes
|
|
// nothing of its own, so these start at MechSubsystem::NextAttributeID.
|
|
//
|
|
public:
|
|
enum {
|
|
RotationOfTorsoVerticalAttributeID = MechSubsystem::NextAttributeID,
|
|
RotationOfTorsoHorizontalAttributeID,
|
|
HorizontalLimitRightAttributeID,
|
|
HorizontalLimitLeftAttributeID,
|
|
SpeedOfTorsoVerticalAttributeID,
|
|
SpeedOfTorsoHorizontalAttributeID,
|
|
StickPositionAttributeID,
|
|
TorsoUpAttributeID,
|
|
TorsoDownAttributeID,
|
|
TorsoLeftAttributeID,
|
|
TorsoRightAttributeID,
|
|
TorsoCenterAttributeID,
|
|
MotionStateAttributeID,
|
|
NextAttributeID
|
|
};
|
|
|
|
static const IndexEntry AttributePointers[];
|
|
static AttributeIndexSet AttributeIndex;
|
|
|
|
protected:
|
|
int isDamagedCopy;
|
|
int statusFlags;
|
|
Logical horizontalEnabled;
|
|
Scalar baseTwistRate;
|
|
Scalar baseElevationRate;
|
|
Scalar horizontalLimitRight;
|
|
Scalar horizontalLimitLeft;
|
|
Scalar verticalLimitTop;
|
|
Scalar verticalLimitBottom;
|
|
Scalar twistCenterHigh;
|
|
Scalar twistCenterLow;
|
|
Scalar elevationCenter;
|
|
Scalar elevationHalfBottom;
|
|
Scalar buttonAccelerationPerSecond;
|
|
Scalar buttonAccelerationStart;
|
|
int buttonRampActive;
|
|
Scalar buttonRamp;
|
|
Joint *horizontalJointNode;
|
|
Joint *horizontalShadowJointNode;
|
|
Scalar currentTwist;
|
|
Scalar currentElevation;
|
|
Scalar analogElevationAxis;
|
|
Scalar analogTwistAxis;
|
|
//
|
|
// MUST BE A StateIndicator. The authored watcher for a *State
|
|
// name is an AudioStateWatcher, which is AudioWatcherOf<StateIndicator>
|
|
// and whose ctor immediately runs
|
|
// Cast_Object(StateIndicator*, attributePointer)->AddAudioWatcher(this)
|
|
// (AUDWTHR.CPP:891). Point it at a plain int and that call goes
|
|
// through garbage -- the pod dies on the extender.
|
|
//
|
|
StateIndicator motionState; // authored watcher
|
|
//
|
|
// The five BUTTON COMMAND destinations (donor torso.hpp @0x1F8-0x208).
|
|
// The streamed control mappings bind these BY ID as DIRECT WRITE
|
|
// targets -- LBE4ControlsManager::CreateStreamedMappings registers
|
|
// GetAttributePointer(attributeID) as the destination a device poll
|
|
// writes through (L4CTRL.CPP:1936). Carved from the front of the
|
|
// dynamicsState reserve so the class size is unchanged.
|
|
//
|
|
int elevateUpCommand;
|
|
int elevateDownCommand;
|
|
int twistLeftCommand;
|
|
int twistRightCommand;
|
|
int centerCommand;
|
|
//
|
|
// Recenter latch (donor @0x268-adjacent family): armed by the centre
|
|
// button, cleared by a twist button or on arrival at centre.
|
|
//
|
|
int recenterActive;
|
|
//
|
|
// TWIST REPLICATION (binary @0x218/@0x21C/@0x238/@0x254) -- the
|
|
// damaged-copy dead-reckoning state. A console update stamps a twist
|
|
// snapshot (twistAtUpdate) + rate (twistRate) + its command time
|
|
// (lastUpdateTime); ComputeTargetTwist extrapolates targetTwist from
|
|
// them and TorsoCopySimulation eases currentTwist onto it. The
|
|
// UPDATE-RECORD feeders (Read/WriteUpdateRecord, torso.cpp census
|
|
// remainder) are still staged -- zero-init means a copy eases to
|
|
// centre, which is correct-by-vacuity until MP updates arrive.
|
|
// Carved from the dynamicsState reserve; class size unchanged.
|
|
//
|
|
Scalar targetTwist;
|
|
Scalar twistAtUpdate;
|
|
Scalar twistRate;
|
|
Time lastUpdateTime;
|
|
//
|
|
// Twist/elevation dynamics accumulators advanced by TorsoSimulation.
|
|
// Reserved until the per-frame sim is reconstructed (phase 5).
|
|
//
|
|
Scalar dynamicsState[12];
|
|
};
|
|
|
|
#endif
|