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