Files
BT411/game/reconstructed/myomers.hpp
T
arcattackandClaude Opus 4.8 8b36440d05 subsystems: un-stub Wave 4 readouts + Wave 6 Myomers (10->15 factory cases)
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>
2026-07-05 23:01:31 -05:00

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