//===========================================================================//
// File:	powersub.cpp                                                        //
// Project: BattleTech           Brick: Entity Manager                        //
// Contents: Powered (electrically driven) subsystems -- voltage routing,     //
//           generators, myomer drive and the low-voltage watchdog            //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
//  --/--/95  ??   Initial coding.                                            //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
//                      PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary.  Behaviour follows the Ghidra
// pseudo-C for the module cluster @0x4b02f0..@0x4b2678.  The only function the
// decomp tagged is the PoweredSubsystem ctor @004b0f74 (file=bt/powersub.cpp);
// the cluster was recovered via the call graph, the class vtables and the
// "d:\tesla_bt\bt\POWERSUB.CPP" assertion strings.  Each non-trivial method
// cites the originating @ADDR.
//
// Hex/byte float constants converted to decimal:
//      0x3f800000 = 1.0f   0x3f000000 = 0.5f   0xbf800000 = -1.0f
//      _DAT_004b0bcc = 1.0e-4f   _DAT_004b297c = 0.0f
//
// Helper-function name mapping (engine internals referenced by the decomp;
// the heat.* names are carried over from the heat-subsystem reconstruction):
//      FUN_004adda0  HeatSink::HeatSink            (PoweredSubsystem / Generator base ctor)
//      FUN_004adfd4  HeatSink::~HeatSink
//      FUN_004ad924  HeatSink::HeatSinkSimulation  (base Performance)
//      FUN_004ad760  HeatSink::ResetToInitialState
//      FUN_004ad748  HeatSink::HandleMessage       (base message dispatch)
//      FUN_004add30  HeatSink::GetStatusFlags
//      FUN_004ac868  HeatableSubsystem::~HeatableSubsystem (chained from PowerController dtor)
//      FUN_004ac530  Subsystem ctor (PowerController base)
//      FUN_004ac9c8  Subsystem::IsDamaged()        (returns True when the part is dead)
//      FUN_0049fb54  Subsystem::IsDamaged()/IsDead variant used by PowerController
//      FUN_00417ab4  SharedData::Resolve()         (connection -> Subsystem*)
//      FUN_004179d4  SharedData ctor   FUN_004179f8 SharedData dtor   FUN_00417a5c SharedData::Clear
//      FUN_0041b9ec  AlarmIndicator(levels)        FUN_0041baa4 ~AlarmIndicator
//      FUN_0041bbd8  AlarmIndicator::SetLevel(n)
//      FUN_0041a1a4  IsDerivedFrom(classDerivations)
//      FUN_0040385c  Verify()/assert(msg,file,line)
//      FUN_004dcd00  fabsf()
//      FUN_004dbb24  DebugStream::operator<<(char*) FUN_004d9c38 DebugStream::endl
//      FUN_004d4a78  strlen()
//      FUN_00404088  NotationFile::Read(name,key,char**)
//      FUN_00404118  NotationFile::Read(name,key,float*)
//      FUN_004040d8  NotationFile::Read(name,key,int*)
//      FUN_004215b0  Get_Segment_Index(file,name)
//      FUN_004ae150  HeatSink::CreateStreamedSubsystem
//

#include <bt.hpp>
#pragma hdrstop

#if !defined(POWERSUB_HPP)
#	include <powersub.hpp>
#endif
#if !defined(APP_HPP)
#	include <app.hpp>
#endif
#if !defined(TESTBT_HPP)
#	include <testbt.hpp>
#endif

//
// Tuning constants observed as read-only globals in the decomp.
//
static const Scalar VoltageEpsilon	= 1.0e-4f;	// _DAT_004b0bcc (short-detect threshold)
static const Scalar MinOutputVoltage	= 0.0f;		// _DAT_004b2158 / _DAT_004b297c
static const Scalar MaxOutputVoltage	= 1.0f;		// _DAT_004b2154 / _DAT_004b2258
static const Scalar RatedVoltageRef	= 1.0f;		// _DAT_004b1d10 (myomer rated reference)
static const Scalar DegradedDriveScale	= 0.5f;		// _DAT_004b1d14 (heat-degradation throttle)
static const Scalar ResourceUnset		= -1.0f;	// _DAT_004b177c / _DAT_004b1a3c / _DAT_004b2580
static const Scalar MinVoltageScale		= 1.0f;		// _DAT_004b1924 (PowerWatcher threshold scale)


//###########################################################################
//###########################################################################
//					PoweredSubsystem
//###########################################################################
//###########################################################################

//#############################################################################
// Shared Data Support
//
PoweredSubsystem::SharedData
	PoweredSubsystem::DefaultData(
		PoweredSubsystem::GetClassDerivations(),
		PoweredSubsystem::GetMessageHandlers(),
		PoweredSubsystem::GetAttributeIndex(),
		PoweredSubsystem::StateCount
	);

Derivation*
	PoweredSubsystem::GetClassDerivations()
	{
		static Derivation classDerivations(HeatSink::GetClassDerivations(), "PoweredSubsystem");
		return &classDerivations;
	}


//#############################################################################
// Construction / Destruction
//
// @004b0f74 -- THE tagged powersub.cpp function (size 486).  Builds on the
// HeatSink base, then resolves and attaches the voltage source named in the
// resource and primes the electrical state machine.
//
PoweredSubsystem::PoweredSubsystem(
	Mech				*owner,
	int					subsystem_ID,
	SubsystemResource	*subsystem_resource,
	SharedData			&shared_data
):
	HeatSink(owner, subsystem_ID, subsystem_resource, shared_data),	// FUN_004adda0
	voltageSource(0),							// FUN_004b1bb1(this+0x74, 0)
	electricalStateAlarm(5),					// FUN_0041b9ec(this+0x99, 5)
	modeAlarm(3)								// FUN_0041b9ec(this+0xae, 3)
{
	Check(owner);
	Check_Pointer(subsystem_resource);

	//
	// Resolve the "VoltageSource" index to the generator subsystem.
	//
	// ⚠ ROOT-CAUSE FIX (sibling of the heat.cpp heap corruption): voltageSourceIndex
	// indexes the owner's SUBSYSTEM ROSTER, not the skeleton segment table.  Raw
	// PoweredSubsystem ctor (part_013.c:1198):
	//     if (res->voltageSourceIndex /*+0xFC*/ < owner->subsystemCount /*+0x124*/)
	//         source = owner->subsystemArray[voltageSourceIndex] /*+0x128*/;
	// The earlier GetSegment draft cast an EntitySegment (288B) to Generator*, so
	// AttachToVoltageSource's `currentTapCount += 1` wrote 136 bytes past that heap
	// block at every mech spawn.  Roster slots are pre-zeroed -> NULL = warn path.
	Subsystem *source = 0;
	if (subsystem_resource->voltageSourceIndex >= 0
		&& subsystem_resource->voltageSourceIndex < owner->GetSubsystemCount())
	{
		source = owner->GetSubsystem(subsystem_resource->voltageSourceIndex);
	}
	else
	{
		// @004b0f74: "Bad voltageSourceIndex to connect power to!"  POWERSUB.CPP:0x264
		Verify(False, "Bad voltageSourceIndex to connect power to!", __FILE__, 0x264);
	}

	//
	// A "master" instance (segment flagged 0x100, not a sub-/damaged copy)
	// runs the per-frame electrical simulation and owns the live link.
	//
	// INTEGRATION (gate reconcile): master-gate reads OWNER simulationFlags
	// (param_2+0x28), the oracle-verified authoritative source — not the per-
	// segment resource flags (which stream 0 here).  See heat.cpp note.
	if (
		(owner->simulationFlags & SegmentCopyMask) == 0			// (owner flags & 0xC) == 0
		&& (owner->simulationFlags & MasterHeatSinkFlag) != 0		// owner flags & 0x100
	)
	{
		SetPerformance(&PoweredSubsystem::PoweredSubsystemSimulation);	// this[7..9] = PTR @0050f5b0
		if (source == 0)
		{
			// @004b0f74: POWERSUB.CPP:0x272
			Verify(False, "Master PoweredSubsystem is missing required generator", __FILE__, 0x272);
		}
		else
		{
			AttachToVoltageSource(source);	// FUN_004b0dd8
		}
	}
	else if (
		// gate on OWNER flags, matching raw part_013.c:1216 (param_2+0x28 & 0xc)==4
		// (the earlier draft read the resource flags here -- always 0 -> dead branch)
		(owner->simulationFlags & SegmentCopyMask) == 4
		&& source != 0
	)
	{
		AttachToVoltageSource(source);
	}

	thermalResistivityCoefficient	= subsystem_resource->thermalResistivityCoefficient;	// +0x100
	startTime						= subsystem_resource->startTime;						// +0x18C
	startTimer						= startTime;

	electricalStateAlarm.SetLevel(Ready);		// FUN_0041bbd8(this+0x99, 4)
	modeAlarm.SetLevel(Connected);				// FUN_0041bbd8(this+0xae, 1)
	voltageScale			= 1.0f;

	auxScreenNumber			= subsystem_resource->auxScreenNumber;		// +0x104
	auxScreenPlacement		= subsystem_resource->auxScreenPlacement;	// +0x108
	strcpy(auxScreenLabel,	subsystem_resource->auxScreenLabel);		// inline copy from +0x10C
	strcpy(engScreenLabel,	subsystem_resource->engScreenLabel);		// inline copy from +0x14C

	Check_Fpu();
}

//
// @004b115c -- tears down the alarms and the voltage-source connection, then
// chains to ~HeatSink.
//
PoweredSubsystem::~PoweredSubsystem()
{
	Check(this);
	// modeAlarm (@0xae) / electricalStateAlarm (@0x99) / voltageSource (@0x74)
	// are released here; FUN_004adfd4 finishes the HeatSink chain.
	Check_Fpu();
}

//###########################################################################
//					TestClass / TestInstance -- PoweredSubsystem
//
Logical
	PoweredSubsystem::TestClass(Mech &)
{
	return True;
}

Logical
	PoweredSubsystem::TestInstance() const		// @004b1208
{
	return IsDerivedFrom(*GetClassDerivations());	// FUN_0041a1a4(**this[3], 0x50f4bc)
}


//#############################################################################
// Per-frame simulation
//

//
// @004b0bd0 -- registered Performance for a master PoweredSubsystem.  Runs the
// HeatSink simulation, then advances the electrical state machine from the
// state of the powering generator and (in AutoConnect mode) hunts for a
// replacement generator when the link is lost.
//
void
	PoweredSubsystem::PoweredSubsystemSimulation(Scalar time_slice)
{
	HeatSink::HeatSinkSimulation(time_slice);		// FUN_004ad924

	Generator *source = (Generator *)voltageSource.Resolve();	// FUN_00417ab4(this+0x74)
	if (source == 0)
	{
		electricalStateAlarm.SetLevel(NoVoltage);
	}
	else
	{
		if (source->GeneratorStateOf() == 3)			// source+0x210 == 3 (shorted)
		{
			electricalStateAlarm.SetLevel(Shorted);
		}
		if (source->GeneratorStateOf() == 1 || source->GeneratorStateOf() == 4)
		{
			electricalStateAlarm.SetLevel(GeneratorOff);
		}
	}

	switch (electricalStateAlarm.GetLevel())			// this[0x9e] @0x278
	{
	case Starting:
		startTimer += time_slice;
		if (startTime <= startTimer)
		{
			electricalStateAlarm.SetLevel(Ready);
		}
		break;

	case NoVoltage:
		if (source != 0)
		{
			electricalStateAlarm.SetLevel(Starting);
			startTimer = 0.0f;
		}
		break;

	case Shorted:
	case GeneratorOff:
		if (source != 0 && source->GeneratorStateOf() == 2)		// generator back to Ready
		{
			electricalStateAlarm.SetLevel(Starting);
			startTimer = 0.0f;
		}
		break;
	}

	//
	// AutoConnect: if the mode alarm is in the auto state and we are not
	// damaged and currently unpowered, scan every GeneratorClassID segment in
	// the mech and attach to the first one that will accept a tap.
	//
	if (
		modeAlarm.GetLevel() == AutoConnect							// this[0xb3] @0x2cc == 2
		&& GetStatusFlags() == 0									// (*this[0x40])(this,0) == 0
	)
	{
		Mech *mech = (Mech *)owner;
		for (int i = 0; ; ++i)										// owner+0x124 segment scan
		{
			EntitySegment *segment_node = mech->GetSegment(i);		// owner+0x128[i]
			if (segment_node == 0)
			{
				break;
			}
			Subsystem *segment = (Subsystem *)segment_node;
			if (
				segment->GetClassID() == RegisteredClass::GeneratorClassID	// segment+4 == 0xbc1
				&& GetStatusFlags() != 0
				&& AttachToVoltageSource(segment) != -1				// FUN_004b0dd8
			)
			{
				break;
			}
		}
	}

	Check_Fpu();
}


//#############################################################################
// Subsystem virtual overrides
//

//
// @004b0e6c -- powered/unpowered transition.  When power is cut the electrical
// alarm goes idle; when restored it returns to "GeneratorOff" with the start
// timer reset, but only while the source is actually Ready (state 2).
//
void
	PoweredSubsystem::ResetToInitialState(Logical powered)
{
	HeatSink::ResetToInitialState(True);				// FUN_004ad760

	Generator *source = (Generator *)voltageSource.Resolve();	// FUN_00417ab4(this+0x74)
	if (!powered)
	{
		if (source != 0)
		{
			electricalStateAlarm.SetLevel(NoVoltage);	// SetLevel 0 in decomp -> Starting/idle
			startTimer = 0.0f;							// this[0xc6] = 0
		}
	}
	else
	{
		if (source != 0 && source->GeneratorStateOf() == 2)
		{
			electricalStateAlarm.SetLevel(Ready);		// SetLevel 4
			startTimer = startTime;						// this[0xc6] = this[0xc5]
		}
	}
}

//
// @004b0efc -- pass the message to the HeatSink base; if the part is alive,
// the source short flag is set and message 4 arrives, force a short-recovery.
//
Logical
	PoweredSubsystem::HandleMessage(int message)
{
	HeatSink::HandleMessage(message);				// FUN_004ad748

	Generator *source = (Generator *)(/* owner subsystem-message manager */
		BT_GetMessageManager((Mech *)owner));		// *(this[0x34]+0x190)

	if (!IsDamaged()								// FUN_004ac9c8 == 0
		&& source->ShortFlag() != 0					// source+0x25c != 0
		&& message == 4)
	{
		ForceShortRecovery();						// FUN_004b11bc(this)
	}
	return True;
}

//
// @004b11bc -- on a short event, if neither this part nor its live source is
// damaged, drive the source's alarm to "shorted" (3) and clear its output.
//
void
	PoweredSubsystem::ForceShortRecovery()			// internal helper for @004b0efc
{
	if (IsDamaged())								// FUN_004ac9c8
		return;
	Generator *source = (Generator *)voltageSource.Resolve();	// FUN_00417ab4(this+0x1d0)
	if (source != 0 && !source->IsDamaged())
	{
		source->stateAlarm.SetLevel(Generator::GeneratorShorted);	// source+0x1fc -> 3
		source->outputVoltage = 0.0f;								// source+0x1f8 = 0
	}
}

//
// @004b0f48 -- base HeatSink flags plus 0x40 when this part has no usable
// voltage (the slot-0x40 virtual returns 0).
//
LWord
	PoweredSubsystem::GetStatusFlags()
{
	LWord flags = HeatSink::GetStatusFlags();		// FUN_004add30

	if (!HasVoltage())								// virtual "has voltage" query, slot @vtable+0x40
	{
		flags |= 0x40;
	}
	return flags;
}

//
// Virtual "has usable voltage" query (vtable+0x40).  Base powered subsystem
// reports voltage present whenever the electrical state machine is Ready.
//
Logical
	PoweredSubsystem::HasVoltage()
{
	return (electricalStateAlarm.GetLevel() == Ready) ? True : False;
}

//
// @004b1224 -- prints "<name> = <electrical state>".
//
void
	PoweredSubsystem::PrintState()
{
	HeatSink::PrintState();							// FUN_004ae050

	switch (electricalStateAlarm.GetLevel())		// this+0x278
	{
	case Starting:		DebugStream << GetName() << " = Starting"     << endl; break;
	case NoVoltage:		DebugStream << GetName() << " = NoVoltage"    << endl; break;
	case Shorted:		DebugStream << GetName() << " = Shorted"      << endl; break;
	case GeneratorOff:	DebugStream << GetName() << " = GeneratorOff" << endl; break;
	case Ready:			DebugStream << GetName() << " = Ready"        << endl; break;
	default:			DebugStream << GetName() << " Unknown Electrical State!" << endl; break;
	}
}


//#############################################################################
// Voltage-source linkage helpers
//

//
// @004b0dd8 -- claim a tap on 'source' if it has a spare one, drop any current
// link, then bind the connection object.  Returns the new tap index, -1 on
// failure (source already fully loaded).
//
int
	PoweredSubsystem::AttachToVoltageSource(Subsystem *source)
{
	int tap;
	Generator *gen = (Generator *)source;				// the voltage source is a Generator
	if (gen->currentTapCount < gen->maxTapCount)		// source+0x1e8 < source+0x1e4
	{
		gen->currentTapCount += 1;
		tap = gen->currentTapCount;
	}
	else
	{
		tap = -1;
	}

	if (tap >= 0)
	{
		DetachFromVoltageSource();						// FUN_004b0e30
		voltageSource.Add(source);						// (**(this[0x74]+4))(this+0x74, source)
	}
	return tap;
}

//
// @004b0e30 -- release our tap on the current source and raise the
// "disconnected" mode alarm.
//
void
	PoweredSubsystem::DetachFromVoltageSource()
{
	Generator *source = (Generator *)voltageSource.Resolve();	// FUN_00417ab4(this+0x1d0)
	if (source != 0)
	{
		source->currentTapCount -= 1;					// source+0x1e8 -= 1
		voltageSource.Clear();							// FUN_00417a5c(this+0x1d0)
	}
	modeAlarm.SetLevel(1);								// FUN_0041bbd8(this+0x264, 1)  TODO: confirm which alarm
}

//
// @004b0b5c -- True when the (resolved or supplied) source is in the shorted
// state (state 2 in this query) and its measured voltage exceeds the short
// threshold.
//
Logical
	PoweredSubsystem::IsSourceShorted(Subsystem *source)
{
	if (source == 0)
	{
		source = voltageSource.Resolve();				// FUN_00417ab4(this+0x1d0)
	}
	Generator *gen = (Generator *)source;
	if (
		gen != 0
		&& gen->GeneratorStateOf() == 2					// source+0x210 == 2
		&& fabsf(gen->MeasuredVoltage() - VoltageEpsilon) > VoltageEpsilon	// source+0x1dc
	)
	{
		return True;
	}
	return False;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem -- PoweredSubsystem  (@004b13ac)
//
// Parses the power-specific resource fields on top of the HeatSink record.
//
int
	PoweredSubsystem::CreateStreamedSubsystem(
		NotationFile				*model_file,
		const char					*model_name,
		const char					*subsystem_name,
		SubsystemResource			*subsystem_resource,
		NotationFile				*subsystem_file,
		const ResourceDirectories	*directories,
		int							passes
	)
{
	if (
		!HeatSink::CreateStreamedSubsystem(			// FUN_004ae150
			model_file, model_name, subsystem_name,
			subsystem_resource, subsystem_file, directories, passes
		)
	)
	{
		return False;
	}

	subsystem_resource->subsystemModelSize	= 0x190;					// resource+0x24
	subsystem_resource->classID				= RegisteredClass::PoweredSubsystemClassID;	// 0x0bc2, resource+0x20

	if (passes == 1)
	{
		// first pass: prime fields to "unset"
		subsystem_resource->voltageSourceIndex			= -1;
		subsystem_resource->thermalResistivityCoefficient	= ResourceUnset;	// -1.0f
		subsystem_resource->auxScreenPlacement			= -1;
		memset(subsystem_resource->auxScreenLabel, 0, 64);					// resource+0x10C
		memset(subsystem_resource->engScreenLabel, 0, 64);					// resource+0x14C
		subsystem_resource->startTime					= ResourceUnset;
	}

	//
	// "VoltageSource" -- segment name; resolve to a segment index (biased +2).
	//
	const char *voltageSourceName = "Unspecified";
	int found = model_file->GetEntry(subsystem_name, "VoltageSource", &voltageSourceName);
	if (!found && subsystem_resource->voltageSourceIndex == -1)
	{
		DebugStream << subsystem_name << " missing VoltageSource!";
		return False;
	}
	if (strcmp(voltageSourceName, "Unspecified") != 0)
	{
		subsystem_resource->voltageSourceIndex =
			Get_Segment_Index(model_file, model_name, directories, voltageSourceName);		// FUN_004215b0
	}
	if (subsystem_resource->voltageSourceIndex < 0)
	{
		DebugStream << subsystem_name << " has an invalid voltage source!";
		return False;
	}
	if (strcmp(voltageSourceName, "Unspecified") != 0)
	{
		subsystem_resource->voltageSourceIndex += 2;
	}

	if (
		!model_file->GetEntry(subsystem_name, "ThermalResistivityCoefficient",
			&subsystem_resource->thermalResistivityCoefficient)
		&& subsystem_resource->thermalResistivityCoefficient == ResourceUnset
	)
	{
		DebugStream << subsystem_name << " missing ThermalResistivityCoefficient!";
		return False;
	}

	if (
		!model_file->GetEntry(subsystem_name, "AuxScreenPlacement",
			&subsystem_resource->auxScreenPlacement)
		&& subsystem_resource->auxScreenPlacement == -1
	)
	{
		subsystem_resource->auxScreenPlacement = -1;	// left unset (optional)
	}

	if (
		!model_file->GetEntry(subsystem_name, "AuxScreenNumber",
			&subsystem_resource->auxScreenNumber)
		&& subsystem_resource->auxScreenNumber == -1
	)
	{
		DebugStream << subsystem_name << " missing AuxScreenNumber!";
		return False;
	}

	const char *label;
	if (model_file->GetEntry(subsystem_name, "AuxScreenLabel", &label))
	{
		strcpy(subsystem_resource->auxScreenLabel, label);
	}
	else if (subsystem_resource->auxScreenLabel[0] == '\0')
	{
		DebugStream << subsystem_name << " missing AuxScreenLabel!";
		return False;
	}

	if (model_file->GetEntry(subsystem_name, "EngScreenLabel", &label))
	{
		strcpy(subsystem_resource->engScreenLabel, label);
	}
	else if (subsystem_resource->engScreenLabel[0] == '\0')
	{
		DebugStream << subsystem_name << " missing EngScreenLabel!";
		return False;
	}

	if (
		!model_file->GetEntry(subsystem_name, "StartTime", &subsystem_resource->startTime)
		&& subsystem_resource->startTime == ResourceUnset
	)
	{
		DebugStream << subsystem_name << " missing StartTime!";
		return False;
	}

	Check_Fpu();
	return True;
}


//###########################################################################
//###########################################################################
//					Myomers
//###########################################################################
//###########################################################################

//#############################################################################
// Shared Data Support
//
Myomers::SharedData
	Myomers::DefaultData(
		Myomers::GetClassDerivations(),
		Myomers::GetMessageHandlers(),
		Myomers::GetAttributeIndex(),
		Myomers::StateCount
	);

Derivation*
	Myomers::GetClassDerivations()
	{
		static Derivation classDerivations(PoweredSubsystem::GetClassDerivations(), "Myomers");
		return &classDerivations;
	}

//
// @004b1d18 -- master myomer bundle registers a separate Performance and a
// status-flag bit (0x8); copies skip the live simulation.
//
Myomers::Myomers(
	Mech				*owner,
	int					subsystem_ID,
	SubsystemResource	*subsystem_resource,
	SharedData			&shared_data
):
	PoweredSubsystem(owner, subsystem_ID, subsystem_resource, /*shared_data=*/DefaultData)	// FUN_004b0f74(..,&DAT_0050fa1c,0,0)
{
	outputVoltage		= 1.0f;		// this[0xc7]
	powered				= 0;		// this[0xc8]
	voltageAvailable	= 0;		// this[0xc9]
	simulationFlags			|= 0x8;		// this[0xa] |= 8

	if ((subsystem_resource->subsystemFlags & SegmentCopyMask) != 4)	// (flags & 0xC) != 4
	{
		SetPerformance((Performance)&Myomers::MyomersSimulation);	// PTR @0050fa94
	}
}

//
// @004b1d90
//
Myomers::~Myomers()
{
	Check(this);
	BT_ClearMyomers((Mech *)owner);			// *(this[0x34]+0x374) = 0
	Check_Fpu();
}

Logical Myomers::TestClass(Mech &)	{ return True; }
Logical Myomers::TestInstance() const		// @004b1e18
{
	return IsDerivedFrom(*GetClassDerivations());	// FUN_0041a1a4(..,0x50fa2c)
}

//
// @004b1c4c -- compute the actuator drive voltage available to the locomotion
// model from the powering generator, throttled by heat damage and gated by
// the electrical state.
//
void
	Myomers::MyomersSimulation(Scalar time_slice)
{
	PoweredSubsystem::PoweredSubsystemSimulation(time_slice);	// FUN_004b0bd0

	Generator *source = (Generator *)voltageSource.Resolve();	// this[0x38] linked source
	outputVoltage = RatedVoltageRef - source->MeasuredVoltage();	// 1.0 - *(source+0x158)

	if (simulationState == 1)											// this[0x10] @0x40
	{
		outputVoltage	= 0.0f;
		powered			= 0;
	}
	else
	{
		powered = 1;
	}

	if (electricalStateAlarm.GetLevel() == Ready)				// this[0x9e] == 4
	{
		voltageAvailable = 0;
	}
	else
	{
		voltageAvailable = 1;
		outputVoltage    = 0.0f;
	}

	//
	// Heat damage (HeatSink heat-state @0x184) throttles the drive.
	//
	switch (heatState)								// this[0x61] @0x184
	{
	case HeatSink::NormalHeat:		// 0
		powered = 1;
		break;
	case HeatSink::DegradationHeat:	// 1
		outputVoltage *= DegradedDriveScale;	// *= 0.5
		powered = 1;
		break;
	case HeatSink::FailureHeat:		// 2
		outputVoltage = 0.0f;
		powered = 0;
		break;
	}
}

//
// @004b1c18 -- reset clears the myomer output state, then chains to the base.
//
void
	Myomers::ResetToInitialState(Logical powered_flag)
{
	if (powered_flag)
	{
		outputVoltage		= 1.0f;		// this[0xc7]
		powered				= 0;		// this[0xc8]
		voltageAvailable	= 0;		// this[0xc9]
	}
	PoweredSubsystem::ResetToInitialState(powered_flag);	// FUN_004b0e6c
}

//
// @004b1bfc -- straight delegation to the base.
//
Logical
	Myomers::HandleMessage(int message)
{
	return PoweredSubsystem::HandleMessage(message);		// FUN_004b0efc
}

//
// @004b1dcc -- no extra resource fields; just tags the record as Myomers.
//
int
	Myomers::CreateStreamedSubsystem(
		NotationFile				*model_file,
		const char					*model_name,
		const char					*subsystem_name,
		SubsystemResource			*subsystem_resource,
		NotationFile				*subsystem_file,
		const ResourceDirectories	*directories,
		int							passes
	)
{
	if (
		!PoweredSubsystem::CreateStreamedSubsystem(		// FUN_004b13ac
			model_file, model_name, subsystem_name,
			subsystem_resource, subsystem_file, directories, passes
		)
	)
	{
		return False;
	}
	subsystem_resource->subsystemModelSize	= 0x190;
	subsystem_resource->classID				= RegisteredClass::MyomersClassID;	// 0x0bc3, resource+0x20
	return True;
}


//###########################################################################
//###########################################################################
//					Generator
//###########################################################################
//###########################################################################

//#############################################################################
// Shared Data Support
//
Generator::SharedData
	Generator::DefaultData(
		Generator::GetClassDerivations(),
		Generator::GetMessageHandlers(),
		Generator::GetAttributeIndex(),
		Generator::StateCount
	);

Derivation*
	Generator::GetClassDerivations()
	{
		static Derivation classDerivations(HeatSink::GetClassDerivations(), "Generator");
		return &classDerivations;
	}

//
// @004b225c -- a generator is itself a HeatSink (it gets hot producing power).
// It starts on-line at rated voltage; the trailing letter of the segment name
// ("GeneratorA".."GeneratorD") becomes the generator number.
//
Generator::Generator(
	Mech				*owner,
	int					subsystem_ID,
	SubsystemResource	*subsystem_resource,
	SharedData			&shared_data
):
	HeatSink(owner, subsystem_ID, subsystem_resource, /*shared_data=*/DefaultData),	// FUN_004adda0(..,&DAT_0050fb50,0,0)
	stateAlarm(5)								// FUN_0041b9ec(this+0x7f, 5)
{
	if (
		(owner->simulationFlags & SegmentCopyMask) == 0			// owner+0x28 (gate reconcile)
		&& (owner->simulationFlags & MasterHeatSinkFlag) != 0
	)
	{
		SetPerformance(&Generator::GeneratorSimulation);	// PTR @0050fc14
	}

	ratedVoltage			= subsystem_resource->ratedVoltage;		// resource+0xFC
	outputVoltage			= ratedVoltage;
	maxTapCount				= subsystem_resource->maxTapCount;		// resource+0x100
	currentTapCount			= 0;
	percentVoltageAvailable	= 1.0f;
	startTime				= subsystem_resource->startTime;		// resource+0x104
	startTimer				= startTime;
	stateAlarm.SetLevel(GeneratorReady);							// SetLevel 2
	generatorOn				= 1;
	shortRecoveryTime		= subsystem_resource->shortRecoveryTime;	// resource+0x108
	shortTimer				= shortRecoveryTime;
	simulationFlags				|= 0x8;									// this[0xa] |= 8

	// last character of the segment name -> generator number ('A'==0x41 -> 1)
	const char *name = GetName();					// this[0x35]
	generatorNumber = name[strlen(name) - 1] - 0x40;	// FUN_004d4a78

	Check_Fpu();
}

//
// @004b237c
//
Generator::~Generator()
{
	Check(this);
	// stateAlarm (@0x7f) released here; FUN_004adfd4 finishes the HeatSink chain.
	Check_Fpu();
}

Logical Generator::TestClass(Mech &)	{ return True; }
Logical Generator::TestInstance() const	{ return IsDerivedFrom(*GetClassDerivations()); }

//
// Generator vtable slot 10 -- recovered VERBATIM from the surviving GNRATOR.TCP
// fragment (the one Generator method the @0x4b02f0.. cluster decomp omitted; see
// gnrator.cpp).  Chains the HeatableSubsystem reset (NOT the HeatSink one --
// matching the inlined base chain seen across the HeatSink family) and forces
// the generator output cold.
//
void
	Generator::ResetToInitialState(Logical /*powered*/)
{
	HeatableSubsystem::ResetToInitialState(True);
	outputVoltage = 0.0f;
}

//
// @004b1f7c -- generator state machine: spin-up, short recovery, and the
// resulting percent-of-rated voltage made available to attached loads.
//
void
	Generator::GeneratorSimulation(Scalar time_slice)
{
	HeatSink::HeatSinkSimulation(time_slice);		// FUN_004ad924

	if (simulationState == 1)								// this[0x10] @0x40
	{
		stateAlarm.SetLevel(GeneratorIdle);			// SetLevel 1
	}
	if (heatState == HeatSink::FailureHeat)			// this[0x61] @0x184 == 2
	{
		stateAlarm.SetLevel(GeneratorRecovered);	// SetLevel 4
	}

	switch (stateAlarm.GetLevel())					// this[0x84] @0x210
	{
	case GeneratorStarting:		// 0
		outputVoltage = 0.0f;
		startTimer += time_slice;
		if (startTime <= startTimer)
		{
			stateAlarm.SetLevel(GeneratorReady);	// 2
			outputVoltage = (MaxOutputVoltage - SourceLevel()) * ratedVoltage;	// (1.0 - *(this[0x38]+0x158)) * this[0x76]
		}
		break;

	case GeneratorIdle:			// 1
		outputVoltage = 0.0f;
		break;

	case GeneratorShorted:		// 3
		outputVoltage = 0.0f;
		shortTimer += time_slice;
		if (shortRecoveryTime <= shortTimer)
		{
			stateAlarm.SetLevel(GeneratorReady);	// 2
			outputVoltage = (MaxOutputVoltage - SourceLevel()) * ratedVoltage;
		}
		break;

	case GeneratorRecovered:	// 4
		outputVoltage = 0.0f;
		if (heatState == HeatSink::NormalHeat)		// this[0x61] == 0
		{
			if (generatorOn == 0)					// this[0x75]
			{
				stateAlarm.SetLevel(GeneratorIdle);	// 1
			}
			else
			{
				startTimer = 0.0f;
				stateAlarm.SetLevel(GeneratorStarting);	// 0
			}
		}
		break;
	}

	//
	// percent of rated voltage actually available, clamped to [0, 1].
	//
	percentVoltageAvailable = outputVoltage / ratedVoltage;		// this[0x74]
	if (percentVoltageAvailable < MinOutputVoltage)
	{
		percentVoltageAvailable = MinOutputVoltage;				// 0.0
	}
	else if (percentVoltageAvailable > MaxOutputVoltage)
	{
		percentVoltageAvailable = MaxOutputVoltage;				// 1.0
	}
}

//
// @004b21d0 -- when the mech's electrical bus is live: message 4 with the
// generator damaged/off drives it into the shorted-recovery state; any other
// message recomputes the output from the source level.
//
Logical
	Generator::HandleMessage(int message)
{
	HeatSink::HandleMessage(message);				// FUN_004ad748

	Mech *mech = (Mech *)owner;
	if (BT_IsBusLive(mech))							// *(*(this[0x34]+0x190)+0x274) != 0
	{
		if (message == 4)
		{
			if ((simulationState == 2 || simulationState == 0) && !IsDamaged())	// this[0x10]; FUN_004ac9c8
			{
				stateAlarm.SetLevel(GeneratorShorted);	// 3
				shortTimer = 0.0f;						// this[0x7e]
			}
		}
		else
		{
			outputVoltage = (MaxOutputVoltage - SourceLevel()) * ratedVoltage;
		}
	}
	return True;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem -- Generator  (@004b23bc)
//
int
	Generator::CreateStreamedSubsystem(
		NotationFile				*model_file,
		const char					*model_name,
		const char					*subsystem_name,
		SubsystemResource			*subsystem_resource,
		NotationFile				*subsystem_file,
		const ResourceDirectories	*directories,
		int							passes
	)
{
	if (
		!HeatSink::CreateStreamedSubsystem(			// FUN_004ae150
			model_file, model_name, subsystem_name,
			subsystem_resource, subsystem_file, directories, passes
		)
	)
	{
		return False;
	}

	subsystem_resource->subsystemModelSize	= 0x10c;
	subsystem_resource->classID				= RegisteredClass::GeneratorClassID;	// 0x0bc1, resource+0x20

	if (passes == 1)
	{
		subsystem_resource->ratedVoltage		= ResourceUnset;	// -1.0f
		subsystem_resource->startTime			= ResourceUnset;
		subsystem_resource->shortRecoveryTime	= ResourceUnset;
	}

	if (
		!model_file->GetEntry(subsystem_name, "RatedVoltage", &subsystem_resource->ratedVoltage)
		&& subsystem_resource->ratedVoltage == ResourceUnset
	)
	{
		DebugStream << subsystem_name << " missing RatedVoltage!";
		return False;
	}

	if (
		!model_file->GetEntry(subsystem_name, "MaxTapCount", &subsystem_resource->maxTapCount)
		&& subsystem_resource->maxTapCount == -1
	)
	{
		DebugStream << subsystem_name << " missing MaxTapCount!";
		return False;
	}

	if (
		!model_file->GetEntry(subsystem_name, "StartTime", &subsystem_resource->startTime)
		&& subsystem_resource->startTime == ResourceUnset
	)
	{
		DebugStream << subsystem_name << " missing StartTime!";
		return False;
	}

	if (
		!model_file->GetEntry(subsystem_name, "ShortRecoveryTime", &subsystem_resource->shortRecoveryTime)
		&& subsystem_resource->shortRecoveryTime == ResourceUnset
	)
	{
		DebugStream << subsystem_name << " missing ShortRecoveryTime!";
		return False;
	}

	Check_Fpu();
	return True;
}


//###########################################################################
//###########################################################################
//					PowerWatcher        (BEST-EFFORT)
//###########################################################################
//###########################################################################
//
// NOTE: PowerWatcher's base class lies just below the captured decomp window
// (base ctor @004aeb40, base streamer @004aec54, base message/sim @004aea84 /
// @004aea9c).  Only the power-specific override bodies were recovered; the
// base relationship is reconstructed and flagged best-effort.
//

PowerWatcher::SharedData
	PowerWatcher::DefaultData(
		PowerWatcher::GetClassDerivations(),
		PowerWatcher::GetMessageHandlers(),
		PowerWatcher::GetAttributeIndex(),
		PowerWatcher::StateCount
	);

Derivation*
	PowerWatcher::GetClassDerivations()
	{
		// Real base ctor is @004aeb40 (HeatWatcher); HeatableSubsystem stands in.
		static Derivation classDerivations(HeatableSubsystem::GetClassDerivations(), "PowerWatcher");
		return &classDerivations;
	}

//
// @004b18a4 -- threshold = scale * resource->minVoltagePercent.
//
PowerWatcher::PowerWatcher(
	Mech				*owner,
	int					subsystem_ID,
	SubsystemResource	*subsystem_resource,
	SharedData			&shared_data
):
	HeatWatcher(owner, subsystem_ID, subsystem_resource, shared_data),	// FUN_004aeb40 -- HeatWatcher base
	watchdogAlarm(5)							// FUN_0041b9ec(this+0x61, 5)
{
	minVoltage = MinVoltageScale * subsystem_resource->minVoltagePercent;	// _DAT_004b1924 * (resource+0xf0)

	if (
		(owner->simulationFlags & SegmentCopyMask) == 0			// owner+0x28 (gate reconcile)
		&& (owner->simulationFlags & MasterHeatSinkFlag) != 0
	)
	{
		SetPerformance(&PowerWatcher::Simulation);	// PTR @0050f5fc
	}
}

//
// @004b1930
//
PowerWatcher::~PowerWatcher()
{
	Check(this);
	Check_Fpu();
}

Logical PowerWatcher::TestClass(Mech &)	{ return True; }
Logical PowerWatcher::TestInstance() const	{ return IsDerivedFrom(*GetClassDerivations()); }	// @004b1970

//
// @004b179c -- on message 4, if the watched source's source is shorted (and
// alive), force the short-recovery alarm; then chain to the base handler.
//
Logical
	PowerWatcher::HandleMessage(int message)
{
	if (message == 4)
	{
		Subsystem *watched = watchedLink.Resolve();		// FUN_00417ab4(this+0x45)
		Generator *source  = (watched != 0)				// FUN_00417ab4(watched+0x1d0)
			? (Generator *)((PoweredSubsystem *)watched)->ResolveVoltageSource()
			: 0;
		if (source != 0 && !source->IsDamaged())
		{
			source->stateAlarm.SetLevel(Generator::GeneratorShorted);	// source+0x1fc -> 3
			source->outputVoltage = 0.0f;								// source+0x1f8 = 0
		}
	}
	return MechSubsystem::HandleMessage(message);	// FUN_004aea84 -> FUN_004ac0bc = MechSubsystem::HandleMessage
}

//
// @004b1804 -- delegate to the base simulation.
//
void
	PowerWatcher::Simulation(Scalar time_slice)
{
	HeatWatcher::ResetToInitialState(True);				// FUN_004aea9c = HeatWatcher::ResetToInitialState
	(void)time_slice;
}

//
// @004b198c -- reads "MinVoltagePercent".
//
int
	PowerWatcher::CreateStreamedSubsystem(
		NotationFile				*model_file,
		const char					*model_name,
		const char					*subsystem_name,
		SubsystemResource			*subsystem_resource,
		NotationFile				*subsystem_file,
		const ResourceDirectories	*directories,
		int							passes
	)
{
	if (
		!HeatWatcher::CreateStreamedSubsystem(	// FUN_004aec54 -- HeatWatcher parse
			model_file, model_name, subsystem_name,
			subsystem_resource, subsystem_file, directories, passes
		)
	)
	{
		return False;
	}

	subsystem_resource->subsystemModelSize	= 0xf4;
	subsystem_resource->classID				= RegisteredClass::PowerWatcherClassID;	// resource+0x20

	if (passes == 1)
	{
		subsystem_resource->minVoltagePercent = ResourceUnset;	// -1.0f
	}

	if (
		!model_file->GetEntry(subsystem_name, "MinVoltagePercent",
			&subsystem_resource->minVoltagePercent)
		&& subsystem_resource->minVoltagePercent == ResourceUnset
	)
	{
		DebugStream << subsystem_name << " missing MinVoltagePercent!";
		return False;
	}
	return True;
}


//###########################################################################
//###########################################################################
//					VoltageSourceConnection
//###########################################################################
//###########################################################################
//
// Small SharedData specialisation that links a PoweredSubsystem to its
// Generator segment (vtable @0050f994).
//

//
// @004b1bb1
//
VoltageSourceConnection::VoltageSourceConnection(int initial):
	SubsystemConnection(initial)			// FUN_004179d4
{
	// *this = &PTR_FUN_0050f994
}

//
// @004b1bd0
//
VoltageSourceConnection::~VoltageSourceConnection()
{
	// *this = &PTR_FUN_0050f994; FUN_004179f8(this, 0)
}

//===========================================================================//
// WAVE 3a factory bridges -- the power bus (Generator 0xBC1, PoweredSubsystem
// 0xBC2).  Energy weapons resolve a real VoltageSource off these to charge.
//===========================================================================//
Subsystem *CreateGeneratorSubsystem(Mech *owner, int id, void *seg)
{
	Check(sizeof(Generator) <= 0x250);
	return (Subsystem *) new (Memory::Allocate(0x250))
		Generator(owner, id, (Generator::SubsystemResource *)seg, Generator::DefaultData);
}

Subsystem *CreatePoweredSubsystem(Mech *owner, int id, void *seg)
{
	Check(sizeof(PoweredSubsystem) <= 0x31c);
	return (Subsystem *) new (Memory::Allocate(0x31c))
		PoweredSubsystem(owner, id, (PoweredSubsystem::SubsystemResource *)seg, PoweredSubsystem::DefaultData);
}
