WAVE 4 (standalone readouts) -- Sensor(0xBC3)/Searchlight(0xBD8)/ ThermalSight(0xBDE)/AmmoBin(0xBCB) un-stubbed via Create<Class>Subsystem bridges + Torso-style de-shim (drop cross-family shadow fields, redirect accessors to the real inherited base, static_assert layout locks). - FIX: Searchlight/ThermalSight ctors gated their Performance on the shadow segmentFlags(=0) so it NEVER installed; switched to owner->simulationFlags. - AmmoBin: retype ammoAlarm HeatAlarm->WatcherGaugeAlarm(0x54) + drop the statusState@0x40 shadow -> exact 0x22C layout. - Guard HeatWatcher::WatchSimulation against the unresolved watchedLink (null-deref exposed once these sims run; faithful fix = resolve the link). - Heat-leaf branch (Sensor) is not byte-exact -> overflow-lock only. WAVE 6 (Myomers 0xBC6, mover-coupled) -- structural un-stub, gated BT_MYOMERS (default on; =0 -> Actuator stub). Wired INERT: MyomersSimulation early-returns (advanced-damage gate stubbed) + no-op mover feed, so the live JointedMover is untouched and the gait cannot regress. De-shim drops the owner*/segmentFlags shims to fit the exact-0x358 alloc. Authentic mover/heat coupling deferred (needs messmgr 0xBD3 + reconciling the mover feed with the gait cutover). Verified: BLH tick 20->27, Mad Cat 24, combat DESTROYED un-regressed, locomotion un-regressed, 0 crashes, 0 heap detections under BT_HEAPCHECK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
339 lines
16 KiB
C++
339 lines
16 KiB
C++
//===========================================================================//
|
|
// File: myomers.hpp //
|
|
// Project: BattleTech Brick: Entity Manager //
|
|
// Contents: Myomers subsystem -- the mech's artificial-muscle drive. //
|
|
// Converts available voltage into locomotion "speed effect" and //
|
|
// the mechanical work that feeds the heat model. //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// --/--/95 ?? Initial coding. //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
|
|
// PROPRIETARY AND CONFIDENTIAL //
|
|
//===========================================================================//
|
|
//
|
|
// RECONSTRUCTED from the shipped binary (BTL4OPT.EXE). NO header survived;
|
|
// every declaration below is recovered from the decompiled cluster
|
|
// @0x4b8a48 .. @0x4b95b8 (just before MechWeapon @0x4b95ec), the object
|
|
// vtable @005117dc, the Myomers attribute IndexEntry table @00511588, and
|
|
// the embedded string pool @00511660 ("Myomers", "ToggleSeekVoltage",
|
|
// "SpeedEffect", "CurrentSeekVoltageIndex", "RecommendedSeekVoltageIndex",
|
|
// "MinSeekVoltageIndex", "MaxSeekVoltageIndex", "SeekVoltage",
|
|
// "OutputVoltage", "VelocityEfficiency", "AccelerationEfficiency",
|
|
// "SeekVoltageRecommendedIndex"). See myomers.cpp for per-method @ADDR
|
|
// evidence. Field offsets in comments are byte offsets in the shipped
|
|
// object (runtime size 0x358, alloc'd by Mech factory case 0xBC6 @part_012).
|
|
//
|
|
// ===========================================================================
|
|
// IMPORTANT CORRECTION (supersedes the earlier powersub.cpp / powersub.hpp
|
|
// "Myomers" block):
|
|
//
|
|
// powersub.cpp/.hpp described a class it CALLED "Myomers" at vtable
|
|
// @0050fb0c / ctor @004b1d18 / classID 0x0BC3, with inferred members
|
|
// outputVoltage/powered/voltageAvailable. That was a MISLABEL. @004b1d18
|
|
// is **Sensor** (proven by the @0050fae0 string pool "Sensor / RadarPercent
|
|
// / SelfTest / BadVoltage" and the surviving SENSOR.HPP); it is now in
|
|
// sensor.cpp with classID 0x0BC3.
|
|
//
|
|
// The REAL Myomers is THIS class:
|
|
// vtable @005117dc (NB: PTR_LAB_005117dc)
|
|
// ctor @004b8fec
|
|
// dtor @004b9114
|
|
// CSS @004b9140 (stamps classID 0x0BC6, streamed model size 0x1B0)
|
|
// DefaultData @0051154c
|
|
// classID 0x0BC6 (Mech factory case 0xBC6, alloc 0x358)
|
|
// The "outputVoltage/powered/voltageAvailable" triad invented in powersub
|
|
// has NO string backing and must be retired. Myomers' real members are
|
|
// the SpeedEffect / SeekVoltage[] drive table below.
|
|
// ===========================================================================
|
|
//
|
|
// A Myomers IS-A PoweredSubsystem (powersub.hpp): it is driven from a
|
|
// Generator segment, spins up over StartTime, and tracks the same electrical
|
|
// state machine. On top of that it owns a discrete table of "seek voltages"
|
|
// (drive gears), selectable at run time, and reports the resulting locomotion
|
|
// "speed effect" both to the Mech's mover and to the heat model.
|
|
//
|
|
|
|
#if !defined(MYOMERS_HPP)
|
|
# define MYOMERS_HPP
|
|
|
|
#if !defined(POWERSUB_HPP)
|
|
//
|
|
// powersub.hpp STILL declares a stale class it calls "Myomers" (the mislabeled
|
|
// Sensor -- vtable 0050fb0c / ctor 4b1d18 / classID 0xBC3, resolved to Sensor in
|
|
// sensor.hpp + CLASSMAP.md, with inferred members outputVoltage/powered/
|
|
// voltageAvailable that have no string backing). The AUTHENTIC Myomers is
|
|
// classID 0xBC6, declared below. We may not edit powersub.hpp, so the stale
|
|
// token is neutralized for the duration of the include, then the real class is
|
|
// declared. CROSS-FAMILY: powersub.hpp should DELETE its stale 'Myomers'
|
|
// (it duplicates the Sensor now owned by sensor.hpp).
|
|
//
|
|
# define Myomers Myomers__powersub_stale
|
|
# include "powersub.hpp"
|
|
# undef Myomers
|
|
#endif
|
|
|
|
//##################### Forward Class Declarations #######################
|
|
class Mech;
|
|
|
|
//###########################################################################
|
|
// SubsystemMessage -- minimal local stand-in.
|
|
//
|
|
// CROSS-FAMILY: the engine subsystem-message type is not present in the
|
|
// reconstructed/engine headers available here. The mover connect/disconnect
|
|
// handlers only read the signed attachment index/handle at message+0xC, so a
|
|
// one-field stand-in lets this module compile. Real need: the BT
|
|
// SubsystemMessage class.
|
|
//
|
|
#if !defined(BT_SUBSYSTEM_MESSAGE_SHIM)
|
|
# define BT_SUBSYSTEM_MESSAGE_SHIM
|
|
struct SubsystemMessage
|
|
{
|
|
int value; // message+0xC : signed mover-attachment index/handle
|
|
};
|
|
#endif
|
|
|
|
//###########################################################################
|
|
//############################# Myomers #################################
|
|
//###########################################################################
|
|
//
|
|
// vtable @005117dc, ctor @004b8fec, dtor @004b9114, shared data @0051154c.
|
|
// RegisteredClass::MyomersClassID == 0x0BC6 (stamped by CreateStreamedSubsystem
|
|
// @004b9140 into resource +0x20; streamed model size 0x1B0 into resource +0x24).
|
|
//
|
|
class Myomers:
|
|
public PoweredSubsystem
|
|
{
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data Support
|
|
//
|
|
public:
|
|
static Derivation ClassDerivations;
|
|
static SharedData DefaultData; // @0051154c
|
|
static Receiver::MessageHandlerSet MessageHandlers;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Streamed model resource.
|
|
//
|
|
// Extends the PoweredSubsystem resource (which ends at startTime @+0x18C).
|
|
// The myomer-specific fields begin at +0x190. Parsed by
|
|
// CreateStreamedSubsystem @004b9140; streamed model size is 0x1B0.
|
|
//
|
|
struct Myomers__SubsystemResource:
|
|
public PoweredSubsystem::SubsystemResource
|
|
{
|
|
Scalar velocityEfficiency; // +0x190 "VelocityEfficiency" (-1.0 == missing -> error)
|
|
Scalar accelerationEfficiency; // +0x194 "AccelerationEfficiency" (-1.0 == missing -> error)
|
|
Scalar seekVoltage[5]; // +0x198 "SeekVoltage" (fraction of rated; list ends at -1.0 sentinel)
|
|
int seekVoltageRecommendedIndex;// +0x1AC "SeekVoltageRecommendedIndex" (-1 == missing -> error)
|
|
};
|
|
typedef Myomers__SubsystemResource SubsystemResource;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Attribute Support
|
|
//
|
|
// Recovered IndexEntry table @00511588 (each record {attribID, nameptr,
|
|
// byte_offset|1, 0}). ToggleSeekVoltage (id 0x09) is a mappable COMMAND
|
|
// bound to the toggle handler @004b8a48; the rest are data attributes.
|
|
// (PoweredSubsystem::NextAttributeID == 0x12, so SpeedEffect resolves to
|
|
// 0x12 and the enum below is contiguous.)
|
|
//
|
|
// ToggleSeekVoltage -> id 0x09 -> handler @004b8a48 (command)
|
|
// SpeedEffect -> id 0x12 -> @0x31C
|
|
// CurrentSeekVoltageIndex -> id 0x13 -> @0x320
|
|
// RecommendedSeekVoltageIndex -> id 0x14 -> @0x324
|
|
// MinSeekVoltageIndex -> id 0x15 -> @0x328
|
|
// MaxSeekVoltageIndex -> id 0x16 -> @0x32C
|
|
// SeekVoltage -> id 0x17 -> @0x330 (array base)
|
|
// OutputVoltage -> id 0x18 -> @0x344
|
|
//
|
|
public:
|
|
enum {
|
|
ToggleSeekVoltageCommandID = 0x09, // mappable command (not in the attribute id run)
|
|
SpeedEffectAttributeID = PoweredSubsystem::NextAttributeID, // 0x12
|
|
CurrentSeekVoltageIndexAttributeID, // 0x13
|
|
RecommendedSeekVoltageIndexAttributeID, // 0x14
|
|
MinSeekVoltageIndexAttributeID, // 0x15
|
|
MaxSeekVoltageIndexAttributeID, // 0x16
|
|
SeekVoltageAttributeID, // 0x17
|
|
OutputVoltageAttributeID, // 0x18
|
|
NextAttributeID // 0x19
|
|
};
|
|
|
|
private:
|
|
static const IndexEntry AttributePointers[]; // @00511588
|
|
protected:
|
|
static AttributeIndexSet AttributeIndex;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Test Class Support
|
|
//
|
|
public:
|
|
static Logical
|
|
TestClass(Mech&);
|
|
Logical
|
|
TestInstance() const;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
Myomers(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
); // @004b8fec
|
|
~Myomers(); // @004b9114
|
|
|
|
static int
|
|
CreateStreamedSubsystem( // @004b9140
|
|
NotationFile *model_file,
|
|
const char *model_name,
|
|
const char *subsystem_name,
|
|
SubsystemResource *subsystem_resource,
|
|
NotationFile *subsystem_file,
|
|
const ResourceDirectories *directories,
|
|
int passes = 1
|
|
);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Drive model.
|
|
//
|
|
// AvailableOutput() is the shared core: it converts an input voltage into
|
|
// the locomotion drive available, scaled by the Mech's base-speed factor,
|
|
// throttled by the heat-degradation curve, and attenuated by accumulated
|
|
// damage. RegisterMaxOutput() pushes the strongest myomer's full-throttle
|
|
// output to the Mech so the cockpit can show top speed.
|
|
//
|
|
public:
|
|
Scalar
|
|
AvailableOutput(Scalar input_voltage); // @004b8ac0 (helper, returns drive)
|
|
void
|
|
RegisterMaxOutput(); // @004b8ef0 (-> owner Mech max speed)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Simulation Support
|
|
//
|
|
// MyomersSimulation is the per-tick Performance: it integrates the work
|
|
// done driving the Mech's velocity/acceleration and accumulates the
|
|
// resulting heat (gated by the sim-control "advanced damage" flag).
|
|
//
|
|
public:
|
|
typedef void
|
|
(Myomers::*Performance)(Scalar time_slice);
|
|
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
|
|
void
|
|
MyomersSimulation(Scalar time_slice); // @004b8d18 (Performance)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Subsystem virtual overrides (vtable @005117dc)
|
|
//
|
|
public:
|
|
Logical
|
|
HandleMessage(int message); // slot 9, @004b8a8c (-> PoweredSubsystem @004b0efc)
|
|
void
|
|
ResetToInitialState(Logical powered = True); // slot 10, @004b8aa4 (-> PoweredSubsystem @004b0e6c)
|
|
// slot 15 (PoweredSubsystem base @004b1780) -- normalised speed/gauge readout
|
|
// (output km/h scaled to 350): GetSpeedReading @004b8f94
|
|
Scalar
|
|
GetSpeedReading(Scalar input_voltage); // slot 15, @004b8f94 (name best-effort)
|
|
// slot 16 (PoweredSubsystem::IsSourceShorted base @004b0b5c) -- Myomers
|
|
// requires the source to supply at least the selected seek voltage
|
|
// AND be in the Ready (==2) state.
|
|
Logical
|
|
HasAdequateVoltage(Subsystem *source = 0); // slot 16, @004b8f3c (overrides IsSourceShorted)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Mover-connection message handlers (bound via the simulation message map;
|
|
// route the live SpeedEffect @0x31C into / out of the Mech's JointedMover
|
|
// roster at owner Mech[0x128]).
|
|
//
|
|
protected:
|
|
void
|
|
ConnectToMover(SubsystemMessage &message); // @004b9550 (attach SpeedEffect feed)
|
|
void
|
|
DisconnectFromMover(SubsystemMessage &message); // @004b95b8 (detach SpeedEffect feed)
|
|
|
|
// ToggleSeekVoltage command handler @004b8a48 (cycles currentSeekVoltageIndex;
|
|
// present in the binary, not captured in the decompiled shards -- best-effort).
|
|
void
|
|
ToggleSeekVoltage(); // @004b8a48 (best-effort)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// CROSS-FAMILY compile shims (owning Mech / JointedMover roster / DamageZone).
|
|
//
|
|
// The drive and heat arithmetic below reads values that logically live on the
|
|
// owning Mech (base-speed, top-speed, velocity/acceleration, motion gain,
|
|
// mass, the sim-control "advanced damage" gate), on the Mech's JointedMover
|
|
// roster (Attach/Detach/Release of the SpeedEffect feed), and on this
|
|
// segment's DamageZone (structureLevel). None of those are reachable from a
|
|
// PoweredSubsystem here (owner is an incomplete Mech*, JointedMover exposes no
|
|
// roster API, and DamageZone is not in this hierarchy), so they are backed
|
|
// locally to keep the formulas intact and compiling. The real accessors
|
|
// belong on Mech / JointedMover / DamageZone -- see report CROSS-FAMILY NEEDS.
|
|
//
|
|
protected:
|
|
// WAVE 6 un-stub -- CROSS-FAMILY Mech/Mover/DamageZone couplings. These
|
|
// logically read the owning Mech's motion state, its JointedMover roster,
|
|
// and this segment's DamageZone -- none reachable from a PoweredSubsystem
|
|
// here (owner is an incomplete Mech*, JointedMover exposes no roster API,
|
|
// DamageZone is not in this hierarchy). To let the real class FIT its 0x358
|
|
// factory alloc, the shim BACKING FIELDS are dropped and the accessors
|
|
// return the neutral defaults the ctor used to prime, so the subsystem
|
|
// CONSTRUCTS + TICKS but is INERT w.r.t. locomotion/heat: the mover feed is
|
|
// a no-op and MyomersSimulation early-returns on OwnerAdvancedDamage()==False.
|
|
// ⚠ FAITHFUL FOLLOW-UP: redirect these to real Mech/JointedMover/DamageZone
|
|
// accessors + the advanced-damage gate (SubsystemMessageManager 0xBD3) so the
|
|
// mech actually slows when damaged/overheated and motion generates heat --
|
|
// that path DOES drive the live mover, so reconcile it with the gait cutover
|
|
// FIRST (both would feed the mover).
|
|
Scalar OwnerBaseSpeed() const { return 1.0f; } // Mech +0x34C (neutral)
|
|
Scalar OwnerMaxSpeed() const { return 0.0f; } // Mech +0x7A0
|
|
void SetOwnerMaxSpeed(Scalar) { } // no-op (real: write Mech top speed)
|
|
Scalar OwnerVelocityMag() const { return 0.0f; } // |Mech velocity|
|
|
Scalar OwnerDriveMag() const { return 0.0f; } // |Mech drive/accel|
|
|
Scalar OwnerVelocityY() const { return 0.0f; } // Mech velocity.y
|
|
Scalar OwnerMotionGain() const { return 0.0f; } // Mech +0x20C
|
|
Scalar OwnerMass() const { return 0.0f; } // *Mech +0x250
|
|
Logical OwnerAdvancedDamage() const { return False; } // advanced-damage gate (keeps sim INERT)
|
|
Scalar DamageStructureLevel() const{ return 0.0f; } // DamageZone +0x158
|
|
|
|
void MoverAttach(int index, Scalar *feed) { (void)index; (void)feed; } // JointedMover vtable +0x38 (no-op)
|
|
void MoverDetach(int index) { (void)index; } // JointedMover vtable +0x3C (no-op)
|
|
void MoverRelease(int handle, Scalar *feed) { (void)handle; (void)feed; } // JointedMover vtable +0x44 (no-op)
|
|
|
|
// Live-master gate constants (the ctor reads owner->simulationFlags).
|
|
enum {
|
|
SegmentCopyMask = 0x0C, // (flags & 0xC): 0 == master, 4 == damaged copy
|
|
MasterHeatSinkFlag = 0x0100 // live-master segment bit
|
|
};
|
|
int moverConnection; // @0x110 mover attachment handle (-1 == detached; set by ConnectToMover)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Local data. Offsets are byte offsets into the shipped object.
|
|
//
|
|
protected:
|
|
Scalar speedEffect; // @0x31C attr 0x12; ctor 1.0f -- live drive fed to the mover
|
|
int currentSeekVoltageIndex; // @0x320 attr 0x13; ctor = recommended -- selected drive "gear"
|
|
int recommendedSeekVoltageIndex;// @0x324 attr 0x14; ctor = resource +0x1AC
|
|
int minSeekVoltageIndex; // @0x328 attr 0x15; ctor 0
|
|
int maxSeekVoltageIndex; // @0x32C attr 0x16; ctor = (seekVoltageCount - 1)
|
|
Scalar seekVoltage[5]; // @0x330 attr 0x17; ctor = resource fraction * source ratedVoltage
|
|
Scalar outputVoltage; // @0x344 attr 0x18; runtime output voltage
|
|
Scalar heatRange; // @0x348 ctor = failureTemperature - degradationTemperature
|
|
Scalar heatRangeSquared; // @0x34C ctor = heatRange*heatRange (degradation-curve denominator)
|
|
Scalar velocityEfficiency; // @0x350 resource +0x190
|
|
Scalar accelerationEfficiency; // @0x354 resource +0x194
|
|
};
|
|
|
|
#endif
|