The 'mysterious flag pair' gating every master-only registration was never BT-specific state waiting on unreconstructed stream builders. It is the Entity base itself: InstanceBits=2 puts the instance field at mask 0xC (ReplicantInstance=4), DynamicBit=8 makes DynamicFlag 0x100 -- so the binary's (flags & 0xC)==0 && (flags & 0x100)!=0 reads 'a master-instance dynamic entity', spelled MasterInstance + DynamicFlag in the 1995 headers. Mover::DefaultFlags = DynamicFlag|MasterInstance, ENTITY.CPP:978 seeds simulationFlags from the MakeMessage's instanceFlags, and our spawn recipe has passed Mech::DefaultFlags since the boot ladder -- the authentic gate was live all along. The BT411 donor's 'MasterHeatSinkFlag' name was a fabrication that made an engine constant look like a missing subsystem feature (donor drift #12). Eleven sites flip from the GetInstance() mirror to the binary pair: the heat-family registrations (x4), powersub (x3), gyro, hud, myomers, emitter, and the Torso ctor's master/copy selection (@004b6b0c verbatim, isDamagedCopy set in both branches). The MECHWEAP score-post and MECH view gates are different families and stay as they are. Statically proven (DefaultFlags carries both bits) and mission-soaked clean. No [T2] divergence markers remain in the tree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
282 lines
8.4 KiB
C++
282 lines
8.4 KiB
C++
//===========================================================================//
|
|
// 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 <bt.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(MYOMERS_HPP)
|
|
# include <myomers.hpp>
|
|
#endif
|
|
|
|
#if !defined(MECH_HPP)
|
|
# include <mech.hpp>
|
|
#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).
|
|
//
|
|
// The binary's master gate: MasterInstance + DynamicFlag -- the
|
|
// simulationFlags 0xC/0x100 pair, now decoded as engine enum identities
|
|
// (InstanceBits=2 makes InstanceMask 0xC; DynamicBit=8 makes DynamicFlag
|
|
// 0x100; Entity::DefaultFlags carries both, seeded from the MakeMessage).
|
|
if (
|
|
(owner->simulationFlags & Entity::InstanceMask) == Entity::MasterInstance &&
|
|
(owner->simulationFlags & Entity::DynamicFlag) != 0
|
|
)
|
|
{
|
|
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();
|
|
}
|