//===========================================================================//
// File:	myomers.cpp                                                         //
// Project: BattleTech           Brick: Entity Manager                        //
// Contents: Myomers subsystem implementation (mech artificial-muscle drive). //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
//                      PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//
//
// RECONSTRUCTED from BTL4OPT.EXE.  No source or header survived; this file is
// rebuilt from the decompiled cluster @0x4b8a48 .. @0x4b95b8 (Ghidra
// pseudo-C in recovered/all/part_013.c), the vtable @005117dc, the attribute
// IndexEntry table @00511588 and the string pool @00511660.  Each method
// carries its @ADDR and the binary evidence used.
//
// --------------------------------------------------------------------------
// LOCATION / IDENTITY (all proven):
//   Class name "Myomers"                 string @0051166d
//   ClassID    0x0BC6                     stamped by CSS @004b9140 (resource +0x20)
//   model size 0x1B0                      stamped by CSS @004b9140 (resource +0x24)
//   object size 0x358                     Mech factory case 0xBC6 (part_012.c:10069)
//   vtable     @005117dc                 set by ctor @004b8fec
//   ctor       @004b8fec   dtor @004b9114
//   CSS        @004b9140   DefaultData @0051154c
//   base       PoweredSubsystem (ctor @004b0f74 called with &DAT_0051154c)
//
// CORRECTION vs powersub.cpp: powersub's "Myomers" (vtable 0050fb0c / ctor
//   4b1d18 / classID 0xBC3, members outputVoltage/powered/voltageAvailable)
//   was a MISLABEL of **Sensor** (now sensor.cpp).  Retire that naming.  The
//   real Myomers is here, classID 0xBC6.  See CLASSMAP.md.
// --------------------------------------------------------------------------
//
// CONFIDENCE
//   confident   : class identity, base, ClassID/size, vtable slot mapping,
//                 member layout (every member is backed by the @00511588
//                 attribute table or by a ctor store), resource layout
//                 (every field is backed by a CSS parse @004b9140),
//                 ctor/dtor/CSS/HandleMessage/ResetToInitialState/
//                 AvailableOutput/RegisterMaxOutput/HasAdequateVoltage/
//                 ConnectToMover/DisconnectFromMover bodies.
//   best-effort : MyomersSimulation (@004b8d18) is the per-tick Performance
//                 (only reachable via function pointer; SetPerformance call
//                 site not captured -- see note); GetSpeedReading (@004b8f94,
//                 slot 15) purpose; ToggleSeekVoltage (@004b8a48) not present
//                 in the decompiled shards; the heat-base field @0x150 and
//                 graphic-state block (this[7..9]) inherit-pattern stores.
//   excluded    : @0x4b7f94..@0x4b881c (gimbal / torso-horizon display, base
//                 4b18a4, DAT_00511228) -- adjacent cluster, NOT Myomers.
//                 @0x4b95ec MechWeapon abstract-sim trap -- next class.
//                 All inherited PoweredSubsystem/HeatSink/Subsystem vtable
//                 slots (1-8, 11-14) are inherited, not redefined here.
//

#include "myomers.hpp"			// FIRST: neutralizes powersub.hpp's stale 'Myomers'
#include <bt.hpp>				// engine aggregate: NotationFile / NameList / Generator / ...
#pragma hdrstop
#if !defined(APP_HPP)
#	include <app.hpp>
#endif
#if !defined(TESTBT_HPP)
#	include <testbt.hpp>
#endif
#include <math.h>				// fabs (speed-gauge magnitude)

//===========================================================================
// Shared Data Support  (DefaultData @0051154c, ClassDerivations near it)
//
// Standard 4-arg Subsystem::SharedData boilerplate (cf. sensor.cpp):
// ClassDerivations / MessageHandlers / AttributeIndex are defined BEFORE
// DefaultData, which takes their address/reference.
//===========================================================================
Derivation
	Myomers::ClassDerivations(
		PoweredSubsystem::GetClassDerivations(),		// returns Derivation* (no &)
		"Myomers"
	);

Receiver::MessageHandlerSet
	Myomers::MessageHandlers;

Myomers::AttributeIndexSet
	Myomers::AttributeIndex;

Myomers::SharedData
	Myomers::DefaultData(
		&Myomers::ClassDerivations,
		Myomers::MessageHandlers,
		Myomers::AttributeIndex,
		Myomers::StateCount
	);

//===========================================================================
// Recovered float constants (section_dump.txt)
//   _DAT_004b8b94 = 0.0f   _DAT_004b8b98 = 1.0f
//   _DAT_004b8ee4 = 0.5f   _DAT_004b8ee8 = 0.0f   _DAT_004b8eec = 1.0f
//   _DAT_004b9110 = -1.0f  (SeekVoltage list sentinel)
//   _DAT_004b9470 = -1.0f  (efficiency "missing" sentinel)
//===========================================================================


//***************************************************************************
// Myomers::Myomers                                                @004b8fec
//***************************************************************************
//
// Chains to PoweredSubsystem::PoweredSubsystem (@004b0f74) with the Myomers
// DefaultData (&DAT_0051154c), then installs vtable @005117dc and builds the
// drive table.  Field-store evidence (this[idx] -> byte offset):
//
//   this[199]=0x3f800000        speedEffect@0x31C       = 1.0f
//   this[0xd2]=fail-degrade      heatRange@0x348         = failureTemp(@0x11C) - degradationTemp(@0x118)
//   this[0xd3]=range*range       heatRangeSquared@0x34C  = heatRange * heatRange
//   this[0xd4]=res+0x190         velocityEfficiency@0x350
//   this[0xd5]=res+0x194         accelerationEfficiency@0x354
//   this[0x54]=0x3e19999a        heat-base field @0x150  = 0.15f   (best-effort; inherited HeatSink coeff)
//   this[10]|=8                  Subsystem flags @0x28  |= 8
//   (cond) this[7..9] = graphic-state PTR_LAB_00511620 / DAT_00511624 / DAT_00511628
//                               when (flags@0x28 & 0xC)==0 && (flags & 0x100)!=0  (inherited render hook)
//
//   seek-voltage table: source = FUN_00417ab4(this+0x1D0) resolves the
//   VoltageSource connection to its Generator; the resource SeekVoltage[]
//   fractions (res+0x198) are multiplied by the Generator's ratedVoltage
//   (source+0x1D8) and stored into seekVoltage[]@0x330, stopping at the -1.0
//   sentinel.  maxSeekVoltageIndex@0x32C = (count-1).
//   recommendedSeekVoltageIndex@0x324 = res+0x1AC; currentSeekVoltageIndex@0x320
//   is seeded to the same; minSeekVoltageIndex@0x328 = 0.
//
Myomers::Myomers(
	Mech				*owner,
	int					subsystem_ID,
	SubsystemResource	*resource,
	SharedData			&shared_data)
	:	PoweredSubsystem(owner, subsystem_ID, resource, shared_data)	// @004b0f74, &DAT_0051154c
{
	// vtable @005117dc installed by the compiler here.

	speedEffect				= 1.0f;									// @0x31C
	heatRange				= failureTemperature - degradationTemperature;	// @0x348 (this[0x11C]-this[0x118])
	heatRangeSquared		= heatRange * heatRange;				// @0x34C
	velocityEfficiency		= resource->velocityEfficiency;			// @0x350  res+0x190
	accelerationEfficiency	= resource->accelerationEfficiency;		// @0x354  res+0x194

	// (heat-base default coefficient @0x150 = 0.15f; inherited HeatSink slot)
	// (Subsystem flags @0x28 |= 8; optional graphic-state hook -- see header note)

	// (WAVE 6 de-shim) the owner*/damageStructure shim backing fields are gone --
	// their accessors return neutral defaults directly.  Only moverConnection
	// (used by ConnectToMover) remains a member.
	moverConnection			= -1;										// @0x110 (detached)

	// Register the per-tick Performance.  The binary ctor @004b8fec stores the
	// MyomersSimulation pointer-to-member into activePerformance (this[7..9] =
	// PTR_LAB_00511620 / DAT_00511624 / DAT_00511628) under the live-master
	// guard (owner segment flags & 0xC)==0 && (flags & 0x100) -- the exact same
	// shape as the Gyroscope/Torso ctors (gyro this[7..9]=PTR_FUN_0050fe08,
	// torso this[7..9]=PTR_FUN_00510c10).  The earlier reconstruction mislabeled
	// this store a "graphic-state hook" and omitted the registration, leaving
	// activePerformance at the DoNothingOnce default (drops out after frame 1).
	// (WAVE 6) the segmentFlags shim is removed; the gate reads OWNER
	// simulationFlags (param_2+0x28) directly -- the oracle-verified source.
	if ((owner->simulationFlags & SegmentCopyMask)    == 0
		&& (owner->simulationFlags & MasterHeatSinkFlag) != 0)	// owner flags & 0x100 (binary @004b8fec)
	{
		SetPerformance(&Myomers::MyomersSimulation);	// this[7..9] = &MyomersSimulation
	}

	// Resolve the powering Generator (PoweredSubsystem::ResolveVoltageSource,
	// FUN_00417ab4(this+0x1D0)) and build the seek-voltage drive table by
	// scaling each resource fraction by the Generator's rated voltage.
	Generator *source = (Generator *)ResolveVoltageSource();		// this+0x1D0 -> Generator
	Scalar     srcRated = (source != 0) ? source->ratedVoltage : 0.0f;	// source+0x1D8

	maxSeekVoltageIndex = -1;
	int count = 0;
	while (count < 5) {
		if (resource->seekVoltage[count] == -1.0f /*_DAT_004b9110*/) {
			maxSeekVoltageIndex = count - 1;				// @0x32C
			break;
		}
		seekVoltage[count] = resource->seekVoltage[count]
						   * srcRated;						// @0x330[count]  (source+0x1D8)
		++count;
	}
	if (maxSeekVoltageIndex < 0)
		maxSeekVoltageIndex = count - 1;					// list filled all 5 slots

	recommendedSeekVoltageIndex	= resource->seekVoltageRecommendedIndex;	// @0x324  res+0x1AC
	currentSeekVoltageIndex		= recommendedSeekVoltageIndex;				// @0x320
	minSeekVoltageIndex			= 0;										// @0x328
}


//***************************************************************************
// Myomers::~Myomers                                               @004b9114
//***************************************************************************
// vtable slot 0.  Thin; chains to the PoweredSubsystem chain.
Myomers::~Myomers()
{
}


//***************************************************************************
// Myomers::CreateStreamedSubsystem                               @004b9140
//***************************************************************************
//
// Chains to PoweredSubsystem::CreateStreamedSubsystem (@004b13ac) for the
// shared electrical/thermal fields, then:
//   * stamps  resource+0x20 = 0x0BC6 (MyomersClassID),
//             resource+0x24 = 0x1B0  (streamed model size).
//   * on first pass (passes==1) initialises VelocityEfficiency / Acceleration-
//     Efficiency (res+0x190/+0x194) to -1.0, the 5 SeekVoltage slots
//     (res+0x198..) to -1.0, and SeekVoltageRecommendedIndex (res+0x1AC) to -1.
//   * parses "VelocityEfficiency" -> res+0x190        (missing/-1.0 -> error)
//   * parses "AccelerationEfficiency" -> res+0x194    (missing/-1.0 -> error)
//   * parses the "SeekVoltage" notation list: each child either sets
//     "SeekVoltageRecommendedIndex" (res+0x1AC) or appends a SeekVoltage
//     fraction (res+0x198..); a missing list or missing recommended index is
//     an error.
//
int Myomers::CreateStreamedSubsystem(
	NotationFile				*model_file,
	const char					*model_name,
	const char					*subsystem_name,
	SubsystemResource			*resource,
	NotationFile				*subsystem_file,
	const ResourceDirectories	*directories,
	int							passes)
{
	if (!PoweredSubsystem::CreateStreamedSubsystem(model_file, model_name,
			subsystem_name, resource, subsystem_file, directories, passes))
		return False;									// @004b13ac

	resource->subsystemModelSize = sizeof(*resource);	// resource +0x24  (0x1B0)
	resource->classID            = (RegisteredClass::ClassID)0x0BC6;	// resource +0x20  (MyomersClassID)

	if (passes == 1) {
		resource->velocityEfficiency		= -1.0f;	// +0x190
		resource->accelerationEfficiency	= -1.0f;	// +0x194
		for (int i = 0; i < 5; ++i)
			resource->seekVoltage[i]		= -1.0f;	// +0x198..
		resource->seekVoltageRecommendedIndex = -1;		// +0x1AC
	}

	// "VelocityEfficiency" / "AccelerationEfficiency" -- required scalars.
	if (!subsystem_file->GetEntry(model_name, "VelocityEfficiency",
					&resource->velocityEfficiency)			// s_VelocityEfficiency_00511709
	 && resource->velocityEfficiency == -1.0f) {
		// Warning: "<subsystem> Missing VelocityEfficiency!"  (0051171c)
		return False;
	}
	if (!subsystem_file->GetEntry(model_name, "AccelerationEfficiency",
					&resource->accelerationEfficiency)		// s_AccelerationEfficiency_00511739
	 && resource->accelerationEfficiency == -1.0f) {
		// Warning: "<subsystem> Missing AccelerationEfficiency!"  (00511750)
		return False;
	}

	// "SeekVoltage" notation list: each child either sets the recommended index
	// or appends a seek-voltage fraction (up to 5).  (00511771)
	NameList *list = subsystem_file->MakeEntryList(model_name, "SeekVoltage");
	if (list == 0 && resource->seekVoltageRecommendedIndex == -1) {
		// Warning: "<model> missing SeekVoltage "  (0051177d)
	} else if (list != 0) {
		Scalar *out = resource->seekVoltage;				// +0x198
		Scalar *end = resource->seekVoltage + 5;
		for (NameList::Entry *node = list->GetFirstEntry();
			 node != 0;
			 node = node->GetNextEntry())
		{
			if (strcmp(node->GetName(), "SeekVoltageRecommendedIndex") == 0)	// 00511793
				resource->seekVoltageRecommendedIndex = node->GetAtoi();		// -> +0x1AC
			else if (out < end)
				*out++ = (Scalar)node->GetAtof();			// append fraction
		}
		delete list;
		if (resource->seekVoltageRecommendedIndex == -1) {
			// Warning: "<model> missing SeekVoltageRecommendedIndex!"  (005117af)
			return False;
		}
	}
	return True;
}


//***************************************************************************
// Myomers::AvailableOutput                                       @004b8ac0
//***************************************************************************
//
// Core drive computation: returns the locomotion drive available for a given
// input voltage.  (helper; called by RegisterMaxOutput and GetSpeedReading.)
//
//   base   = ownerMech[0x34C] * (input_voltage / seekVoltage[recommendedIndex])
//                ^ Mech base-speed scalar       ^ normalise to the recommended gear
//
//   heatFactor (thermal degradation, mirrors the other PoweredSubsystem leaves):
//     if   currentTemp >= degradationTemp:
//          if currentTemp >= failureTemp        -> 0.0           (burned out)
//          else d = currentTemp - degradationTemp;
//               heatFactor = (base==0)? 1.0 : 1.0 - d*d/heatRangeSquared
//     else                                       -> 1.0          (cold, full drive)
//
//   return (1.0 - damage[0x158]) * base * heatFactor;
//     ^ accumulated damage/wear (this[0xE0]->structureLevel@0x158) bleeds off output.
//
Scalar Myomers::AvailableOutput(Scalar input_voltage)
{
	Scalar base = OwnerBaseSpeed()								// *(Mech@this[0xD0] + 0x34C)
				* (input_voltage / seekVoltage[recommendedSeekVoltageIndex]);	// @0x330[@0x324]

	Scalar heatFactor;
	if (degradationTemperature <= currentTemperature) {			// 0x118 <= 0x114
		if (failureTemperature <= currentTemperature) {			// 0x11C <= 0x114
			heatFactor = 0.0f;
		} else {
			Scalar d = currentTemperature - degradationTemperature;
			heatFactor = (base == 0.0f)
					? 1.0f
					: (base - d * d * (base / heatRangeSquared)) / base;	// == 1 - d*d/heatRangeSquared
		}
	} else {
		heatFactor = 1.0f;
	}

	Scalar damage = DamageStructureLevel();						// *(this[0xE0] + 0x158)
	return (1.0f - damage) * base * heatFactor;					// _DAT_004b8b98 = 1.0
}


//***************************************************************************
// Myomers::RegisterMaxOutput                                     @004b8ef0
//***************************************************************************
//
// Computes this myomer's full-throttle drive (AvailableOutput at the top gear
// seekVoltage[maxSeekVoltageIndex]) and raises the owner Mech's top-speed
// field (Mech[0x7A0]) to it.  Called once per myomer while the Mech is being
// assembled (loop @part_012.c:10322 over the Mech's myomer list @0x1EB).
//
void Myomers::RegisterMaxOutput()
{
	Scalar best = AvailableOutput(seekVoltage[maxSeekVoltageIndex]);	// @0x330[@0x32C]
	if (best > OwnerMaxSpeed())					// Mech[0x7A0]
		SetOwnerMaxSpeed(best);
}


//***************************************************************************
// Myomers::GetSpeedReading                          slot 15      @004b8f94
//***************************************************************************
//
// PoweredSubsystem virtual slot 15 (base @004b1780).  Best-effort: converts
// AvailableOutput(input_voltage) into a normalised gauge reading -- output is
// scaled by 3.6 (m/s -> km/h) and divided by 350 (gauge full scale), then run
// through FUN_004dd138 (fp magnitude helper / sqrt).  Drives a cockpit
// speed gauge.  (Decompiled body discards the result -> returned via FPU.)
//
Scalar Myomers::GetSpeedReading(Scalar input_voltage)
{
	Scalar output = AvailableOutput(input_voltage);				// @004b8ac0
	return (Scalar)fabs((double)((output * 3.6f - 0.0f) / 350.0f));	// FUN_004dd138 (fp magnitude)
}


//***************************************************************************
// Myomers::HasAdequateVoltage                       slot 16      @004b8f3c
//***************************************************************************
//
// Override of PoweredSubsystem::IsSourceShorted (slot 16, base @004b0b5c).
// Returns True only when the (resolved) voltage source can supply at least
// the currently-selected seek voltage AND is in the Ready state (state==2).
//
//   if (source == 0) source = ResolveVoltageSource();   // FUN_00417ab4(this+0x1D0)
//   if (source == 0) return False;
//   return seekVoltage[currentSeekVoltageIndex] <= source->outputVoltage
//          && source->state == 2;                        // source+0x1DC, source+0x210
//
Logical Myomers::HasAdequateVoltage(Subsystem *source)
{
	Generator *gen = source ? (Generator *)source
							: (Generator *)ResolveVoltageSource();	// FUN_00417ab4(this+0x1D0)
	if (gen == 0)
		return False;

	if (seekVoltage[currentSeekVoltageIndex] <= gen->MeasuredVoltage()	// @0x330[@0x320] <= src+0x1DC
	 && gen->GeneratorStateOf() == Generator::GeneratorReady)			// src+0x210 == 2
		return True;
	return False;
}


//***************************************************************************
// Myomers::MyomersSimulation                        Performance  @004b8d18
//***************************************************************************
//
// Per-tick drive/heat integration.  BEST-EFFORT (only reachable via the
// activePerformance function pointer; the SetPerformance call site was not
// captured in the shards -- registration is presumed in the un-decompiled
// ToggleSeekVoltage/activation path).
//
// Reads the owner Mech's motion state and the seek-voltage ratio, then -- when
// the sim-control "advanced damage" gate is on (FUN_004ad7d4: *(Mech[0xD0]+0x190)
// +0x260) -- accumulates mechanical-work heat into the HeatSink heat
// accumulator @0x1C8:
//
//   v   = |Mech velocity vector|        (Mech +0x1C4/0x1C8/0x1CC, magnitude)
//   a   = |Mech accel/desired vector|   (Mech +0x82C/0x830/0x834, magnitude)
//   vDot= Mech[0x20C] * (dot of the velocity vector with itself) * _DAT_004b8ee4(0.5)
//   ratio = seekVoltage[current]/seekVoltage[recommended], clamped >= 1.0
//   damageGain = 1.0 + damage[0x158]
//   velTerm =  (1 - accelerationEfficiency) * |Vy| * Mech[0x20C] * *Mech[0x250] * dt
//   accTerm =  (1 - accelerationEfficiency) * |v| * |a| * Mech[0x20C] * dt
//   work    =  (1 - velocityEfficiency) * vDot
//   heat[0x1C8] += ratio*ratio * damageGain * (velTerm + work + accTerm)
//
// (Field roles in the work expression are best-effort; the structure -- heat
// rises with the square of the gear ratio, with damage, and with the
// inefficiency complements of VelocityEfficiency/AccelerationEfficiency -- is
// faithful to the decompiled arithmetic.)
//
void Myomers::MyomersSimulation(Scalar time_slice)
{
	if (!OwnerAdvancedDamage())							// FUN_004ad7d4 gate
		return;

	Scalar velMag  = OwnerVelocityMag();				// |Mech velocity| (+0x1C4/0x1C8/0x1CC)
	Scalar accMag  = OwnerDriveMag();					// |Mech drive vector| (+0x82C/0x830/0x834)
	Scalar vy      = OwnerVelocityY();					// Mech +0x1C8
	Scalar k       = OwnerMotionGain();					// Mech +0x20C
	Scalar m       = OwnerMass();						// *Mech[0x250]

	Scalar velComplement   = 1.0f - accelerationEfficiency;	// @0x354
	Scalar workComplement  = 1.0f - velocityEfficiency;		// @0x350
	Scalar damageGain      = 1.0f + DamageStructureLevel();	// this[0xE0]+0x158

	Scalar ratio = seekVoltage[currentSeekVoltageIndex]		// @0x330[@0x320]
				 / seekVoltage[recommendedSeekVoltageIndex];// @0x330[@0x324]
	if (ratio < 1.0f) ratio = 1.0f;

	Scalar work = k * (velMag * velMag) * 0.5f;			// dot(v,v)*0.5 == |v|^2*0.5 (_DAT_004b8ee4)

	pendingHeat /* @0x1C8 */ +=
		ratio * ratio * damageGain *
		( velComplement * vy * k * m * time_slice
		+ workComplement * work
		+ velComplement * velMag * accMag * k * time_slice );
}


//***************************************************************************
// Myomers::HandleMessage                            slot  9      @004b8a8c
//***************************************************************************
// Thin override; forwards to PoweredSubsystem::HandleMessage (@004b0efc).
Logical Myomers::HandleMessage(int message)
{
	return PoweredSubsystem::HandleMessage(message);	// @004b0efc
}


//***************************************************************************
// Myomers::ResetToInitialState                      slot 10      @004b8aa4
//***************************************************************************
// Thin override; forwards to PoweredSubsystem::ResetToInitialState (@004b0e6c).
void Myomers::ResetToInitialState(Logical powered)
{
	PoweredSubsystem::ResetToInitialState(powered);		// @004b0e6c
}


//***************************************************************************
// Myomers::ConnectToMover                                        @004b9550
//***************************************************************************
//
// Attaches the live SpeedEffect output (@0x31C) into the Mech's JointedMover
// roster (the mover at **(Mech[0xD0]+0x128)).  Driven by a connect message
// whose payload index is at message+0xC:
//   index = abs(message->value) - 1;
//   if (message->value < 1) { mover->Detach(index); this[0x110] = -1; }
//   else { this[0x110] = 0; mover->Attach(index, &speedEffect, this, 10, 9, 0); }
// (mover vtable +0x38 = Attach, +0x3C = Detach.)
//
void Myomers::ConnectToMover(SubsystemMessage &message)
{
	int index = abs(message.value) - 1;						// message+0xC
	if (message.value < 1) {
		MoverDetach(index);									// owner mover vtable +0x3C
		this->moverConnection /*@0x110*/ = -1;
	} else {
		this->moverConnection /*@0x110*/ = 0;
		MoverAttach(index, &speedEffect);					// owner mover vtable +0x38, feed @0x31C
														// (original args: index,&speedEffect,this,10,9,0)
	}
}


//***************************************************************************
// Myomers::DisconnectFromMover                                   @004b95b8
//***************************************************************************
// Counterpart of ConnectToMover: detaches the SpeedEffect feed.
//   if (message->value > 0)
//       mover->Release(message->value, &speedEffect);   // mover vtable +0x44
void Myomers::DisconnectFromMover(SubsystemMessage &message)
{
	if (message.value > 0) {								// message+0xC
		MoverRelease(message.value, &speedEffect);			// owner mover vtable +0x44, feed @0x31C
	}
}


//***************************************************************************
// Myomers::ToggleSeekVoltage                                     @004b8a48
//***************************************************************************
// Mappable command (attribute id 0x09 "ToggleSeekVoltage", bound in the
// @00511588 table to handler @004b8a48).  Cycles currentSeekVoltageIndex
// @0x320 between minSeekVoltageIndex@0x328 and maxSeekVoltageIndex@0x32C.
// BEST-EFFORT: present in the binary but not captured in the decompiled
// shards (the address falls in the untagged gap 0x4b8837..0x4b8a8c).
void Myomers::ToggleSeekVoltage()
{
	// cycle currentSeekVoltageIndex within [minSeekVoltageIndex, maxSeekVoltageIndex]
}


//===========================================================================//
// WAVE 6 -- compile-time OVERFLOW lock (the real class must fit the 0x358
//   factory alloc).  Like Sensor, the heat-leaf base (PoweredSubsystem:HeatSink)
//   is NOT byte-exact to the binary, so we lock only the overflow bound, not the
//   exact field offsets -- nothing reads Myomers at a raw offset today (the mover
//   feed + gauge readouts are inert) and MyomersSimulation uses named members.
//   The de-shim dropped the owner*/segmentFlags backing fields to make room.
//===========================================================================//
struct MyomersLayoutCheck
{
	static_assert(sizeof(Myomers) <= 0x358, "sizeof(Myomers) must fit the factory Memory::Allocate(0x358)");
};


//===========================================================================//
// WAVE 6 factory bridge -- Myomers (factory case 0xBC6, "Actuator" stub).
//   The real class at 0xBC6 (ctor @004b8fec) is Myomers (the mech's artificial-
//   muscle drive); the factory built an Actuator RECON_SUBSYS stub.  alloc 0x358.
//   The subsystem constructs (resolves its Generator + builds the seek-voltage
//   gear table) and ticks its real Performance, but is currently INERT w.r.t.
//   locomotion/heat (no-op mover feed + the OwnerAdvancedDamage()==False sim gate)
//   -- so it cannot regress the gait/drive.  Authentic coupling = a follow-up.
//===========================================================================//
Subsystem *CreateMyomersSubsystem(Mech *owner, int id, void *seg)
{
	return (Subsystem *) new (Memory::Allocate(0x358))
		Myomers(owner, id, (Myomers::SubsystemResource *)seg);
}
