Files
BT412/game/reconstructed/myomers.hpp
T
arcattackandClaude Fable 5 dd27238ceb Gauges: the CLOSEOUT wave -- radar pips, Myomers dedup, attribute parity, ledger sweep (tasks #14-#17)
TASK #17 -- AUTHENTIC RADAR SYMBOLOGY (the cross-blip stand-in retired).
The recon proved the 'missing pip raster set' never existed as BT game code:
pips are the ENGINE's L4GaugeImage vector-stroke system (T0 source in tree,
L4GAUIMA.cpp == FUN_0046f0c0 line-for-line), the 'pip table' is L4Warehouse::
gaugeImageBin keyed by Entity::resourceID, and BTL4.RES ships 110 type-0x12
shapes (every mech/vehicle/building/tree).  The six btl4rdr stubs are wired
to the real facilities: contacts draw their authentic model silhouettes with
LOD selection, the target gets the binary's 4px-inflated box highlight, and
player-name labels resolve via Mission::GetSmallNameBitmap (the prebuilt
64x16 egg rasters keyed by the player's bitmapindex).  DECODED: the invented
'VideoObject' was Entity::owningPlayer all along (+0x190; nameID =
playerBitmapIndex@0x1E0, target = BTPlayer::objectiveMech@0x284 -- new
bridge BTPlayerObjectiveMechOf); the 'LabelledEntity' class is Landmark
(cultural.h; label path dormant -- no landmark content ships, no runtime
landmarkID writer).  The L4GREND BT_DEV_GAUGES warehouse guard is removed
(its AV had a different culprit, below); resource type 18 corrected to
GaugeImageStream in decomp-reference (was mislabeled 'ModelList').

TASK #14 -- the Myomers ODR duplicate ELIMINATED: the powersub.cpp/hpp
'Myomers' (classID 0xBC3 -- actually Sensor) is retired whole; it duplicated
?DefaultData@Myomers@@ against the real class (dumpbin-verified) and /FORCE
picked the winner by link order.  The real Myomers (0xBC6) now chains
PoweredSubsystem's handler set (ids 4-8) and publishes its SEVEN binary
attributes (@00511588: SpeedEffect/Current/Recommended/Min/MaxSeekVoltage-
Index/SeekVoltage/OutputVoltage) -- the old empty unchained index starved
the Myomer engineering panel of every resolve.

TASK #16 -- attribute parity: MechWeapon publishes the FULL binary table
@0x511890 (11 entries; ids renumbered to binary truth -- the port aliases
had squatted the binary's DistanceToTarget/TargetWithinRange ids; the
streamed TriggerState 0x13 binding unchanged).  Binary names resolved two
TODO members: pipState -> estimatedReadyTime (attr 0x1A), and the EXT-model
flag is the binary's RearFiring (0x1B).  ThermalSight LightState published.
HUD (offset conflicts) + missile-side tables (id encoding suspect)
documented for a re-dump instead of publishing blind.
  THE CRASH THIS EXPOSED [T2, cdb-verified]: gotcha #11's dense-table gap
is a LATENT AV, not a guaranteed one -- the old table's 0x0D..0x12 gap
(task #5) survived on heap luck; the renumber reshuffled allocations and
Find() AV'd on a garbage entryName in WeaponCluster's PercentDone resolve.
Fixed with five named PAD entries (the mech.cpp attrPad idiom) + a
static_assert locking the pad base to PoweredSubsystem::NextAttributeID.
Gotcha #11 amended with the proof.

TASK #15 -- stale-ledger sweep: GAUGE_COMPOSITE ('composite not yet built',
Reservoir shadow, PlayerStatus/vehicleSubSystems 'remaining', valve-dormant-
until-0xBD3, sensor guard, the superseded 'Heat MFD near-static' reframe --
all banner-corrected), gauges-hud frontmatter, L4VB16 + powersub comments.

Verified live: 50/50 config attribute bindings resolve, 0 NULLs, 0 parse
skips, mech spawns and simulates 31/31 subsystems, no cross-blip fallbacks,
the pip cache fills through entity registration without the old guard.

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

348 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
// task #14: accessor idiom (function-local statics) -- the old empty
// static MessageHandlers/AttributeIndex members starved the class of
// BOTH its own 7 binary attrs AND every inherited resolve (temps,
// InputVoltage) -> the Myomer engineering panel's children fed NULL.
static Receiver::MessageHandlerSet& GetMessageHandlers();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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:
protected:
static AttributeIndexSet& GetAttributeIndex(); // @00511588 table, task #14
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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
};
// The mover attachment handle lives at BASE+0x110 (a MechSubsystem slot), NOT a
// Myomers own field -- declaring it here appended a 4-byte tail that overflowed the
// 0x358 alloc once the PoweredSubsystem base became byte-exact. The coupling is
// inert (ConnectToMover -> no-op MoverAttach), so the handle is write-only today;
// when the real mover feed is revived, write base+0x110 via a base accessor.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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
friend struct MyomersLayoutCheck;
};
#endif