//===========================================================================// // 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