From 02b78eadc7b20bc2534ff5914ac75e71c0bd097c Mon Sep 17 00:00:00 2001 From: Cyd Date: Mon, 3 Aug 2026 22:27:00 -0500 Subject: [PATCH] BT410 5.3.111: the myomers drive -- and runSpeedMax's writer is found in the muscles Myomers::MyomersSimulation (@004b8d18) reconstructed, five blocks in the binary's order, and it answers a question left open since 5.3.94: runSpeedMax (+0x7a0) IS WRITTEN BY THE MYOMERS. Each myomer's RegisterMaxOutput (@004b8ef0) raises the mech's run-cycle cap to its full-throttle AvailableOutput -- so the mech's true top speed is a property of its MUSCLE ASSEMBLY, and it degrades live as heat crosses the degradation band and as the myomer's own zone accumulates damage. The 1e9 ctor sentinel now stands only until the first myomer registers. The drive computation (@004b8ac0): base = the mech base speed (reverseStrideLength, mech+0x34C) scaled by the gear ratio -- input voltage over the RECOMMENDED gear -- with a quadratic thermal bleed across the degradation band, zero past failure, and the subsystem's wear taken off the top. speedEffect republishes per frame as a 0..1 fraction of full drive: input clamped to the SELECTED gear then the top gear, through AvailableOutput, normalised by the base speed. AND THE MUSCLES HEAL: while a myomer has NO VOLTAGE and its zone is not destroyed, it hands its own zone a NEGATIVE explosive tick (-0.011, 0xbc343958) -- unloaded muscle knits itself back together. The donor's history records this block as the fix for "torso twist dead after respawn" (the Torso mirrors this subsystem's electrical level; with the base machine never running, a myomer that lost power in the death window sat at NoVoltage forever) -- and our runs respawn now, so the fix is live where it matters. Registered on the master (the sibling gate); the base electrical machine runs FIRST and unconditionally, per the donor's hard-learned ordering note. Not carried over: the port's BT_FORCE_SEEK / repair-test bench scaffolding. Live run clean; the gait's speed sequence is unchanged (7.31972, 26.6726, ...) -- the muscle cap sits above the demanded 26.9 on a healthy Mad Cat, exactly as it should. Stub census: 18 across 12 files. Co-Authored-By: Claude Fable 5 --- restoration/source410/BT/MECH.HPP | 33 ++- restoration/source410/BT/MYOMERS.CPP | 411 ++++++++++++++++++--------- restoration/source410/BT/MYOMERS.HPP | 242 ++++++++-------- 3 files changed, 434 insertions(+), 252 deletions(-) diff --git a/restoration/source410/BT/MECH.HPP b/restoration/source410/BT/MECH.HPP index f39f7fa3..7b1a5a0a 100644 --- a/restoration/source410/BT/MECH.HPP +++ b/restoration/source410/BT/MECH.HPP @@ -439,6 +439,31 @@ int LoadClipSlot(int slot, const char *prefix, const char *suffix); + // + //-------------------------------------------------------------------- + // The myomer drive contract (myomers.cpp). The mech's BASE speed + // scalar is reverseStrideLength (binary mech+0x34C -- the measured + // run-cycle speed); the myomers normalise their output by it and + // RAISE the run cap (runSpeedMax, +0x7a0) to their available output + // at the top gear. + //-------------------------------------------------------------------- + // + Scalar + BaseSpeedOf() + { Check(this); return reverseStrideLength; } + Scalar + RunSpeedMaxOf() + { Check(this); return runSpeedMax; } + void + RaiseRunSpeedMax(Scalar candidate) + { + Check(this); + if (candidate > runSpeedMax || runSpeedMax >= 1.0e8f) + { + runSpeedMax = candidate; + } + } + // //-------------------------------------------------------------------- // The per-frame gait entry points (mech2.cpp). Each advances its @@ -748,9 +773,11 @@ // it yet; defaulted 1. // // runSpeedMax (+0x7a0) caps the run cycle's upward slew the way - // walkStrideLength caps the walk's. UNSOURCED -- not set by - // LoadLocomotionClips; defaulted huge so it never binds until its - // real source is found. + // walkStrideLength caps the walk's. SOURCED (2026-08-03): the + // MYOMERS write it -- each myomer's RegisterMaxOutput raises it to + // AvailableOutput(top gear), so the mech's true top speed is a + // property of its muscle assembly, degrading with heat and damage. + // The huge ctor default stands until the first myomer registers. // // The two reset latches and the death latch are one-shots the // fall/reset clip group clears; motionEventName/Armed are cleared diff --git a/restoration/source410/BT/MYOMERS.CPP b/restoration/source410/BT/MYOMERS.CPP index 99d96f91..0b9d43e9 100644 --- a/restoration/source410/BT/MYOMERS.CPP +++ b/restoration/source410/BT/MYOMERS.CPP @@ -1,137 +1,274 @@ -//===========================================================================// -// File: myomers.cpp // -// Project: BattleTech Brick: Mech subsystems // -// Contents: Myomers -- artificial-muscle locomotion power // -//---------------------------------------------------------------------------// -// Copyright (C) 1995, Virtual World Entertainment, Inc. // -// All Rights reserved worldwide // -// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // -//===========================================================================// - -#include -#pragma hdrstop - -#if !defined(MYOMERS_HPP) -# include -#endif - -#if !defined(MECH_HPP) -# include -#endif - -Derivation - Myomers::ClassDerivations( - PoweredSubsystem::ClassDerivations, - "Myomers" - ); - -const Myomers::IndexEntry - Myomers::AttributePointers[]= -{ - ATTRIBUTE_ENTRY(Myomers, SpeedEffect, speedEffect), - ATTRIBUTE_ENTRY(Myomers, CurrentSeekVoltageIndex, currentSeekVoltageIndex), - ATTRIBUTE_ENTRY(Myomers, RecommendedSeekVoltageIndex, - recommendedSeekVoltageIndex), - ATTRIBUTE_ENTRY(Myomers, MinSeekVoltageIndex, minSeekVoltageIndex), - ATTRIBUTE_ENTRY(Myomers, MaxSeekVoltageIndex, maxSeekVoltageIndex), - { (int)Myomers::SeekVoltageAttributeID, "SeekVoltage", - (Simulation::AttributePointer)&Myomers::seekVoltage } -}; - -Myomers::AttributeIndexSet - Myomers::AttributeIndex( - ELEMENTS(Myomers::AttributePointers), - Myomers::AttributePointers, - PoweredSubsystem::AttributeIndex - ); - -// -// The shared data must carry the POWERED tables, not Subsystem's -- the -// same miswiring found on MissileLauncher (5.3.33). Myomers derives from -// PoweredSubsystem, so wiring it to Subsystem's dropped every powered -// attribute AND the powered message handlers (ToggleSeekVoltage and the -// generator-select family) on the way past. -// -Myomers::SharedData - Myomers::DefaultData( - Myomers::ClassDerivations, - PoweredSubsystem::MessageHandlers, - Myomers::AttributeIndex, - Subsystem::StateCount - ); - -Myomers::Myomers( - Mech *owner, - int subsystem_ID, - SubsystemResource *resource, - SharedData &shared_data -): - PoweredSubsystem(owner, subsystem_ID, resource, shared_data) -{ - Check(owner); - Check_Pointer(resource); - - speedEffect = 1.0f; - heatRange = failureTemperature - degradationTemperature; - heatRangeSquared = heatRange * heatRange; - velocityEfficiency = resource->velocityEfficiency; - accelerationEfficiency = resource->accelerationEfficiency; - - // - // Build the seek-voltage drive table: each resource fraction scaled by the - // powering generator's rated voltage. The voltage source is resolved once - // AttachToVoltageSource runs (phase-4 wiring); until then it is null and the - // table scales to zero. - // - Generator *source = (Generator *)ResolveVoltageSource(); - Scalar srcRated = (source != NULL) ? source->ratedVoltage : 0.0f; - - maxSeekVoltageIndex = -1; - int count = 0; - while (count < 5) - { - if (resource->seekVoltage[count] == -1.0f) - { - maxSeekVoltageIndex = count - 1; - break; - } - seekVoltage[count] = resource->seekVoltage[count] * srcRated; - ++count; - } - if (maxSeekVoltageIndex < 0) - { - maxSeekVoltageIndex = count - 1; - } - - minSeekVoltageIndex = 0; - recommendedSeekVoltageIndex = resource->seekVoltageRecommendedIndex; - currentSeekVoltageIndex = recommendedSeekVoltageIndex; - - Check_Fpu(); -} - -Myomers::~Myomers() -{ -} - -Logical - Myomers::TestClass(Mech &) -{ - return True; -} - -Logical - Myomers::TestInstance() const -{ - return IsDerivedFrom(ClassDerivations); -} - -// -// Per-frame: convert seek voltage into locomotion speed/acceleration effect. -// Not yet reconstructed. -// -void - Myomers::MyomersSimulation(Scalar) -{ - Fail("Myomers::MyomersSimulation -- myomers.cpp not yet reconstructed"); -} +//===========================================================================// +// File: myomers.cpp // +// Project: BattleTech Brick: Mech subsystems // +// Contents: Myomers -- artificial-muscle locomotion power // +//---------------------------------------------------------------------------// +// Copyright (C) 1995, Virtual World Entertainment, Inc. // +// All Rights reserved worldwide // +// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // +//===========================================================================// + +#include +#pragma hdrstop + +#if !defined(MYOMERS_HPP) +# include +#endif + +#if !defined(MECH_HPP) +# include +#endif + +Derivation + Myomers::ClassDerivations( + PoweredSubsystem::ClassDerivations, + "Myomers" + ); + +const Myomers::IndexEntry + Myomers::AttributePointers[]= +{ + ATTRIBUTE_ENTRY(Myomers, SpeedEffect, speedEffect), + ATTRIBUTE_ENTRY(Myomers, CurrentSeekVoltageIndex, currentSeekVoltageIndex), + ATTRIBUTE_ENTRY(Myomers, RecommendedSeekVoltageIndex, + recommendedSeekVoltageIndex), + ATTRIBUTE_ENTRY(Myomers, MinSeekVoltageIndex, minSeekVoltageIndex), + ATTRIBUTE_ENTRY(Myomers, MaxSeekVoltageIndex, maxSeekVoltageIndex), + { (int)Myomers::SeekVoltageAttributeID, "SeekVoltage", + (Simulation::AttributePointer)&Myomers::seekVoltage } +}; + +Myomers::AttributeIndexSet + Myomers::AttributeIndex( + ELEMENTS(Myomers::AttributePointers), + Myomers::AttributePointers, + PoweredSubsystem::AttributeIndex + ); + +// +// The shared data must carry the POWERED tables, not Subsystem's -- the +// same miswiring found on MissileLauncher (5.3.33). Myomers derives from +// PoweredSubsystem, so wiring it to Subsystem's dropped every powered +// attribute AND the powered message handlers (ToggleSeekVoltage and the +// generator-select family) on the way past. +// +Myomers::SharedData + Myomers::DefaultData( + Myomers::ClassDerivations, + PoweredSubsystem::MessageHandlers, + Myomers::AttributeIndex, + Subsystem::StateCount + ); + +Myomers::Myomers( + Mech *owner, + int subsystem_ID, + SubsystemResource *resource, + SharedData &shared_data +): + PoweredSubsystem(owner, subsystem_ID, resource, shared_data) +{ + Check(owner); + Check_Pointer(resource); + + speedEffect = 1.0f; + heatRange = failureTemperature - degradationTemperature; + heatRangeSquared = heatRange * heatRange; + velocityEfficiency = resource->velocityEfficiency; + accelerationEfficiency = resource->accelerationEfficiency; + + // + // Build the seek-voltage drive table: each resource fraction scaled by the + // powering generator's rated voltage. The voltage source is resolved once + // AttachToVoltageSource runs (phase-4 wiring); until then it is null and the + // table scales to zero. + // + Generator *source = (Generator *)ResolveVoltageSource(); + Scalar srcRated = (source != NULL) ? source->ratedVoltage : 0.0f; + + maxSeekVoltageIndex = -1; + int count = 0; + while (count < 5) + { + if (resource->seekVoltage[count] == -1.0f) + { + maxSeekVoltageIndex = count - 1; + break; + } + seekVoltage[count] = resource->seekVoltage[count] * srcRated; + ++count; + } + if (maxSeekVoltageIndex < 0) + { + maxSeekVoltageIndex = count - 1; + } + + minSeekVoltageIndex = 0; + recommendedSeekVoltageIndex = resource->seekVoltageRecommendedIndex; + currentSeekVoltageIndex = recommendedSeekVoltageIndex; + + // + // The master instance runs the myomer per-frame (the same gate every + // sibling uses; the binary's is the segment-copy/master flag pair). + // + if (owner->GetInstance() != Entity::ReplicantInstance) + { + SetPerformance(&Myomers::MyomersSimulation); + } + +Check_Fpu(); +} + +Myomers::~Myomers() +{ +} + +Logical + Myomers::TestClass(Mech &) +{ + return True; +} + +Logical + Myomers::TestInstance() const +{ + return IsDerivedFrom(ClassDerivations); +} + +// +//############################################################################# +// @004b8ac0 -- the drive computation. base = the mech base speed scaled by +// the gear ratio (input over the RECOMMENDED gear's voltage); a heat factor +// bleeds it off quadratically across the degradation band and kills it past +// failure; and the subsystem's own accumulated zone damage takes its cut. +//############################################################################# +// +Scalar + Myomers::AvailableOutput(Scalar input_voltage) +{ + Check(this); + + Scalar + base = + ((Mech *)GetEntity())->BaseSpeedOf() * + (input_voltage / seekVoltage[recommendedSeekVoltageIndex]); + + Scalar + heat_factor; + if (degradationTemperature <= currentTemperature) + { + if (failureTemperature <= currentTemperature) + { + heat_factor = 0.0f; + } + else + { + Scalar + band = currentTemperature - degradationTemperature; + heat_factor = + (base == 0.0f) + ? 1.0f + : (base - band * band * (base / heatRangeSquared)) / base; + } + } + else + { + heat_factor = 1.0f; + } + + return (1.0f - GetSubsystemDamageLevel()) * base * heat_factor; +} + +// +//############################################################################# +// @004b8ef0 -- raise the owner's run-speed cap to this myomer's +// full-throttle output. An idempotent max: repeat calls never lower it, +// and the best available output only falls as heat and damage degrade -- +// so re-registering per tick converges on the binary's once-at-assembly +// value while staying live to degradation. +//############################################################################# +// +void + Myomers::RegisterMaxOutput() +{ + Check(this); + + Scalar + best = AvailableOutput(seekVoltage[maxSeekVoltageIndex]); + ((Mech *)GetEntity())->RaiseRunSpeedMax(best); +} + +// +//############################################################################# +// @004b8d18 -- the per-frame myomer. Five blocks, in the binary's order: +// +// 0. RegisterMaxOutput -- the assembly-time top-speed cap (see above). +// 1. The base electrical state machine. FIRST, unconditionally -- gating +// it behind anything denies power handling to most pilots. +// 2. The un-powered SELF-REPAIR: while this myomer has NO VOLTAGE and its +// zone is not destroyed, hand the zone a NEGATIVE explosive tick -- +// muscle knits itself back together while unloaded. +// 3. Republish the input voltage from the resolved source. +// 4. Republish speedEffect, the live drive handed to the mover: the input +// clamped to the SELECTED gear then the top gear, through +// AvailableOutput, normalised by the mech base speed -- a 0..1 +// fraction carrying the gear ratio, the thermal curve and the wear. +//############################################################################# +// + +static const Scalar + MyomerRecoveryPerTick = -0.011f; // 0xbc343958 -- the heal is a + // negative damage amount + +void + Myomers::MyomersSimulation(Scalar time_slice) +{ + Check(this); + + RegisterMaxOutput(); + + PoweredSubsystemSimulation(time_slice); + + if ( + GetVoltageState() == PoweredSubsystem::NoVoltage && + GetSubsystemDamageLevel() < 1.0f + ) + { + Damage + repair; + repair.damageType = Damage::ExplosiveDamageType; + repair.damageAmount = MyomerRecoveryPerTick; + repair.impactPoint = ((Mech *)GetEntity())->localOrigin.linearPosition; + repair.burstCount = 1; + TakeDamage(repair); + } + + Generator + *source = (Generator *)ResolveVoltageSource(); + outputVoltage = + (GetVoltageState() == PoweredSubsystem::Ready && source != NULL) + ? source->MeasuredVoltage() + : 0.0f; + + if (GetVoltageState() != PoweredSubsystem::Ready) + { + speedEffect = 0.0f; + } + else + { + Scalar + drive = outputVoltage; + if (drive >= seekVoltage[currentSeekVoltageIndex]) + { + drive = seekVoltage[currentSeekVoltageIndex]; + } + if (drive > seekVoltage[maxSeekVoltageIndex]) + { + drive = seekVoltage[maxSeekVoltageIndex]; + } + speedEffect = + AvailableOutput(drive) / ((Mech *)GetEntity())->BaseSpeedOf(); + } + + Check_Fpu(); +} diff --git a/restoration/source410/BT/MYOMERS.HPP b/restoration/source410/BT/MYOMERS.HPP index 0c39e693..7a92521c 100644 --- a/restoration/source410/BT/MYOMERS.HPP +++ b/restoration/source410/BT/MYOMERS.HPP @@ -1,112 +1,130 @@ -//===========================================================================// -// File: myomers.hpp // -// Project: BattleTech Brick: Mech subsystems // -// Contents: Myomers -- the artificial-muscle (locomotion power) subsystem // -//---------------------------------------------------------------------------// -// Copyright (C) 1995, Virtual World Entertainment, Inc. // -// All Rights reserved worldwide // -// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // -//===========================================================================// - -#if !defined(MYOMERS_HPP) -# define MYOMERS_HPP - -# if !defined(POWERSUB_HPP) -# include -# endif - -//##################### Forward Class Declarations ####################### - class Mech; - -//########################################################################### -//#################### Myomers Model Resource ########################## -//########################################################################### - - struct Myomers__SubsystemResource: - public PoweredSubsystem::SubsystemResource - { - Scalar velocityEfficiency; - Scalar accelerationEfficiency; - Scalar seekVoltage[5]; - int seekVoltageRecommendedIndex; - }; - -//########################################################################### -//############################## Myomers ############################### -//########################################################################### - - class Myomers: - public PoweredSubsystem - { - public: - static Derivation ClassDerivations; - static SharedData DefaultData; - - typedef void - (Myomers::*Performance)(Scalar time_slice); - void - SetPerformance(Performance performance) - { - Check(this); - activePerformance = (Simulation::Performance)performance; - } - - static Logical - TestClass(Mech &); - Logical - TestInstance() const; - void - MyomersSimulation(Scalar time_slice); - - public: - typedef Myomers__SubsystemResource SubsystemResource; - - Myomers( - Mech *owner, - int subsystem_ID, - SubsystemResource *subsystem_resource, - SharedData &shared_data = DefaultData - ); - ~Myomers(); - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Attribute publication. The shipped binary's string pool lists this - // class's attribute names contiguously, immediately after the class name - // "Myomers" and its ToggleSeekVoltage message: - // - // SpeedEffect CurrentSeekVoltageIndex RecommendedSeekVoltageIndex - // MinSeekVoltageIndex MaxSeekVoltageIndex SeekVoltage - // - // -- which is EXACTLY the order of the members below, so the - // reconstruction's layout matches the original and the ids can be - // pinned faithfully rather than guessed. SpeedEffect is the one an - // authored AttributeWatcher in BTL4.RES binds (the myomer whine). - // - public: - enum { - SpeedEffectAttributeID = PoweredSubsystem::NextAttributeID, - CurrentSeekVoltageIndexAttributeID, - RecommendedSeekVoltageIndexAttributeID, - MinSeekVoltageIndexAttributeID, - MaxSeekVoltageIndexAttributeID, - SeekVoltageAttributeID, - NextAttributeID - }; - - static const IndexEntry AttributePointers[]; - static AttributeIndexSet AttributeIndex; - - protected: - Scalar speedEffect; - int currentSeekVoltageIndex; - int recommendedSeekVoltageIndex; - int minSeekVoltageIndex; - int maxSeekVoltageIndex; - Scalar seekVoltage[5]; - Scalar heatRange; - Scalar heatRangeSquared; - Scalar velocityEfficiency; - Scalar accelerationEfficiency; - }; - -#endif +//===========================================================================// +// File: myomers.hpp // +// Project: BattleTech Brick: Mech subsystems // +// Contents: Myomers -- the artificial-muscle (locomotion power) subsystem // +//---------------------------------------------------------------------------// +// Copyright (C) 1995, Virtual World Entertainment, Inc. // +// All Rights reserved worldwide // +// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // +//===========================================================================// + +#if !defined(MYOMERS_HPP) +# define MYOMERS_HPP + +# if !defined(POWERSUB_HPP) +# include +# endif + +//##################### Forward Class Declarations ####################### + class Mech; + +//########################################################################### +//#################### Myomers Model Resource ########################## +//########################################################################### + + struct Myomers__SubsystemResource: + public PoweredSubsystem::SubsystemResource + { + Scalar velocityEfficiency; + Scalar accelerationEfficiency; + Scalar seekVoltage[5]; + int seekVoltageRecommendedIndex; + }; + +//########################################################################### +//############################## Myomers ############################### +//########################################################################### + + class Myomers: + public PoweredSubsystem + { + public: + static Derivation ClassDerivations; + static SharedData DefaultData; + + typedef void + (Myomers::*Performance)(Scalar time_slice); + void + SetPerformance(Performance performance) + { + Check(this); + activePerformance = (Simulation::Performance)performance; + } + + static Logical + TestClass(Mech &); + Logical + TestInstance() const; + void + MyomersSimulation(Scalar time_slice); + + public: + typedef Myomers__SubsystemResource SubsystemResource; + + Myomers( + Mech *owner, + int subsystem_ID, + SubsystemResource *subsystem_resource, + SharedData &shared_data = DefaultData + ); + ~Myomers(); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Attribute publication. The shipped binary's string pool lists this + // class's attribute names contiguously, immediately after the class name + // "Myomers" and its ToggleSeekVoltage message: + // + // SpeedEffect CurrentSeekVoltageIndex RecommendedSeekVoltageIndex + // MinSeekVoltageIndex MaxSeekVoltageIndex SeekVoltage + // + // -- which is EXACTLY the order of the members below, so the + // reconstruction's layout matches the original and the ids can be + // pinned faithfully rather than guessed. SpeedEffect is the one an + // authored AttributeWatcher in BTL4.RES binds (the myomer whine). + // + public: + enum { + SpeedEffectAttributeID = PoweredSubsystem::NextAttributeID, + CurrentSeekVoltageIndexAttributeID, + RecommendedSeekVoltageIndexAttributeID, + MinSeekVoltageIndexAttributeID, + MaxSeekVoltageIndexAttributeID, + SeekVoltageAttributeID, + NextAttributeID + }; + + static const IndexEntry AttributePointers[]; + static AttributeIndexSet AttributeIndex; + + public: + // + // The drive computation (@004b8ac0): the locomotion output available + // for an input voltage -- the mech base speed scaled by the gear + // ratio, degraded by heat past the degradation band and by the + // subsystem's own accumulated zone damage. + // + Scalar + AvailableOutput(Scalar input_voltage); + + // + // Raise the owner mech's run-speed cap to this myomer's full-throttle + // output (@004b8ef0) -- idempotent max(), the binary calls it from + // the assembly loop. + // + void + RegisterMaxOutput(); + + protected: + Scalar speedEffect; + int currentSeekVoltageIndex; + int recommendedSeekVoltageIndex; + int minSeekVoltageIndex; + int maxSeekVoltageIndex; + Scalar seekVoltage[5]; + Scalar heatRange; + Scalar heatRangeSquared; + Scalar velocityEfficiency; + Scalar accelerationEfficiency; + }; + +#endif