Ran the reconstructed exe and the shipped ALPHA_1 binary against the SAME GAUGE content, same mount, gauges on, and diffed the 640x480x16 framebuffers. OUR EXE DRAWS THE COCKPIT: 90.1% pixel-identical on the first run, and every difference traced to a single root cause. The authored aux-screen block (screen number / background placement / label) lives in the subsystem resource -- which is freed with the Mech ctor's stream buffer (the 5.3.24 use-after-free landmine). Both this tree and the BT411 port had stubbed it, which is why BT411's displays show the same artifacting. PoweredSubsystem now caches the block at ctor time with accessors, and btl4gau2 uses the AUTHORED screen instead of a roster-order stand-in and builds the placement strip art. Panels snapped onto their authored screens -- weapon dials now land on the same panels as the shipped exe, mech icons appear on the sensor/myomer panels, generator letters correct -- taking the match to 93.2% identical / 85% coverage. Remaining differences are donor-inherited stubs: the panel title art (now proven to come from the q-strip statusImage path, not auxScreenLabel -- the label blit is wired and guarded regardless), the pilot name bitmap, and some lamp frames. The A/B rig is banked at emulator/render-bridge/gauge-ab/ (both confs + the window-grab script) so the comparison is repeatable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
827 lines
23 KiB
C++
827 lines
23 KiB
C++
//===========================================================================//
|
|
// File: powersub.cpp //
|
|
// Project: BattleTech Brick: Mech subsystems //
|
|
// Contents: PoweredSubsystem -- a HeatSink drawing electrical 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(POWERSUB_HPP)
|
|
# include <powersub.hpp>
|
|
#endif
|
|
|
|
#if !defined(MECH_HPP)
|
|
# include <mech.hpp>
|
|
#endif
|
|
|
|
//
|
|
//#############################################################################
|
|
// Shared data support
|
|
//#############################################################################
|
|
//
|
|
Derivation
|
|
PoweredSubsystem::ClassDerivations(
|
|
HeatSink::ClassDerivations,
|
|
"PoweredSubsystem"
|
|
);
|
|
|
|
//
|
|
//#############################################################################
|
|
// The cockpit generator buttons (binary handler table @0x50F4EC, ids 4-8),
|
|
// chained onto the HeatSink set (id 3 ToggleCooling) so every powered
|
|
// subsystem's dispatch reaches the Eng-page Coolant button too. Static-init
|
|
// order is safe under the authentic .MAK lib order (heat precedes powersub).
|
|
//#############################################################################
|
|
//
|
|
const PoweredSubsystem::HandlerEntry
|
|
PoweredSubsystem::MessageHandlerEntries[] =
|
|
{
|
|
MESSAGE_ENTRY(PoweredSubsystem, SelectGeneratorA), // id 4 @004b099c
|
|
MESSAGE_ENTRY(PoweredSubsystem, SelectGeneratorB), // id 5 @004b09e4
|
|
MESSAGE_ENTRY(PoweredSubsystem, SelectGeneratorC), // id 6 @004b0a2c
|
|
MESSAGE_ENTRY(PoweredSubsystem, SelectGeneratorD), // id 7 @004b0a74
|
|
MESSAGE_ENTRY(PoweredSubsystem, ToggleGeneratorMode) // id 8 @004b0abc
|
|
};
|
|
|
|
PoweredSubsystem::MessageHandlerSet
|
|
PoweredSubsystem::MessageHandlers(
|
|
ELEMENTS(PoweredSubsystem::MessageHandlerEntries),
|
|
PoweredSubsystem::MessageHandlerEntries,
|
|
HeatSink::MessageHandlers
|
|
);
|
|
|
|
//
|
|
//#############################################################################
|
|
// Attribute tables (cockpit binding by name).
|
|
//#############################################################################
|
|
//
|
|
const PoweredSubsystem::IndexEntry
|
|
PoweredSubsystem::AttributePointers[]=
|
|
{
|
|
ATTRIBUTE_ENTRY(PoweredSubsystem, InputVoltage, inputVoltage),
|
|
ATTRIBUTE_ENTRY(PoweredSubsystem, OutputVoltage, outputVoltage),
|
|
ATTRIBUTE_ENTRY(PoweredSubsystem, RatedVoltage, ratedVoltage),
|
|
ATTRIBUTE_ENTRY(PoweredSubsystem, VoltageState, electricalStateAlarm),
|
|
ATTRIBUTE_ENTRY(PoweredSubsystem, ConnectMode, modeAlarm)
|
|
};
|
|
|
|
PoweredSubsystem::AttributeIndexSet
|
|
PoweredSubsystem::AttributeIndex(
|
|
ELEMENTS(PoweredSubsystem::AttributePointers),
|
|
PoweredSubsystem::AttributePointers,
|
|
HeatSink::AttributeIndex
|
|
);
|
|
|
|
PoweredSubsystem::SharedData
|
|
PoweredSubsystem::DefaultData(
|
|
PoweredSubsystem::ClassDerivations,
|
|
PoweredSubsystem::MessageHandlers,
|
|
PoweredSubsystem::AttributeIndex,
|
|
Subsystem::StateCount
|
|
);
|
|
|
|
//
|
|
//#############################################################################
|
|
// A HeatSink that draws electrical power from a generator (binary ctor
|
|
// @004b0f74). Resolves the "VoltageSource" roster index to the powering
|
|
// generator, attaches the tap, and primes the electrical state machine. The
|
|
// voltageSourceIndex indexes the owner mech's SUBSYSTEM ROSTER (the same
|
|
// index space the AmmoBin link uses) -- the roster slots ahead of this
|
|
// subsystem are already constructed by the segment walk, and the shipped
|
|
// stream orders the generators first.
|
|
//#############################################################################
|
|
//
|
|
PoweredSubsystem::PoweredSubsystem(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data
|
|
):
|
|
HeatSink(owner, subsystem_ID, subsystem_resource, shared_data),
|
|
voltageSource(),
|
|
electricalStateAlarm(5),
|
|
modeAlarm(3)
|
|
{
|
|
Check(owner);
|
|
Check_Pointer(subsystem_resource);
|
|
|
|
inputVoltage = 0.0f;
|
|
outputVoltage = 0.0f;
|
|
ratedVoltage = 0.0f;
|
|
|
|
thermalResistivityCoefficient = subsystem_resource->thermalResistivityCoefficient;
|
|
startTime = subsystem_resource->startTime;
|
|
|
|
//
|
|
// Cache the authored aux-screen block NOW -- the resource memory dies
|
|
// with the Mech ctor's stream buffer.
|
|
//
|
|
auxScreenNumber = subsystem_resource->auxScreenNumber;
|
|
auxScreenPlacement = subsystem_resource->auxScreenPlacement;
|
|
Str_Copy(auxScreenLabel, subsystem_resource->auxScreenLabel,
|
|
sizeof(auxScreenLabel));
|
|
Str_Copy(engScreenLabel, subsystem_resource->engScreenLabel,
|
|
sizeof(engScreenLabel));
|
|
startTimer = startTime;
|
|
voltageScale = 1.0f;
|
|
|
|
//
|
|
// Resolve the voltage source from the roster and attach the tap.
|
|
//
|
|
Subsystem *source = NULL;
|
|
if (subsystem_resource->voltageSourceIndex >= 0
|
|
&& subsystem_resource->voltageSourceIndex < owner->GetSubsystemCount())
|
|
{
|
|
source = owner->GetSubsystem(subsystem_resource->voltageSourceIndex);
|
|
}
|
|
if (source != NULL)
|
|
{
|
|
AttachToVoltageSource(source);
|
|
}
|
|
|
|
if (getenv("BT_POWER_LOG"))
|
|
{
|
|
DEBUG_STREAM << "[power] '" << GetName()
|
|
<< "' srcIdx=" << subsystem_resource->voltageSourceIndex << " -> ";
|
|
if (source != NULL)
|
|
{
|
|
DEBUG_STREAM << source->GetName();
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << "<none>";
|
|
}
|
|
DEBUG_STREAM << " startTime=" << startTime << endl << flush;
|
|
}
|
|
|
|
electricalStateAlarm.SetLevel(Ready);
|
|
modeAlarm.SetLevel(Connected);
|
|
|
|
//
|
|
// A master (non-replicant) instance runs the per-frame electrical
|
|
// simulation. Derived subsystems (the weapons, Sensor, ...) override with
|
|
// their own Performance in their ctors, each of which chains this step.
|
|
//
|
|
if (owner->GetInstance() != Entity::ReplicantInstance)
|
|
{
|
|
SetPerformance(&PoweredSubsystem::PoweredSubsystemSimulation);
|
|
}
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
PoweredSubsystem::~PoweredSubsystem()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PoweredSubsystem::ResetToInitialState(Logical powered)
|
|
{
|
|
Check(this);
|
|
HeatSink::ResetToInitialState(powered);
|
|
inputVoltage = 0.0f;
|
|
outputVoltage = 0.0f;
|
|
electricalStateAlarm.SetLevel(0);
|
|
modeAlarm.SetLevel(0);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
PoweredSubsystem::TestClass(Mech &)
|
|
{
|
|
return True;
|
|
}
|
|
|
|
Logical
|
|
PoweredSubsystem::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(ClassDerivations);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// AttachToVoltageSource Link this subsystem to its powering generator
|
|
// (binary @004b0dd8): take a tap on the generator (-1 when every tap is
|
|
// taken) and hold the live connection.
|
|
//#############################################################################
|
|
//
|
|
int
|
|
PoweredSubsystem::AttachToVoltageSource(Subsystem *source)
|
|
{
|
|
Check(this);
|
|
Check(source);
|
|
|
|
Generator *generator = (Generator *)source;
|
|
if (generator->TapVoltageSource() != 0)
|
|
{
|
|
return -1;
|
|
}
|
|
voltageSource.Add(source);
|
|
inputVoltage = generator->MeasuredVoltage();
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// DetachFromVoltageSource (binary @004b0e30): release the tap on the current
|
|
// source and clear the connection.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PoweredSubsystem::DetachFromVoltageSource()
|
|
{
|
|
Check(this);
|
|
|
|
Generator *source = (Generator *)voltageSource.Resolve();
|
|
if (source != NULL)
|
|
{
|
|
source->UntapVoltageSource();
|
|
voltageSource.Clear();
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// FindGeneratorByNumber (binary @004b0b18): walk the owner's roster for the
|
|
// Generator whose authored generatorNumber matches (1=A .. 4=D).
|
|
//#############################################################################
|
|
//
|
|
Subsystem*
|
|
PoweredSubsystem::FindGeneratorByNumber(int generator_number)
|
|
{
|
|
Check(this);
|
|
|
|
for (int slot = 2; slot < owner->GetSubsystemCount(); ++slot)
|
|
{
|
|
Subsystem *sub = owner->GetSubsystem(slot);
|
|
if (sub != NULL
|
|
&& sub->IsDerivedFrom(Generator::ClassDerivations)
|
|
&& ((Generator *)sub)->GetGeneratorNumber() == generator_number)
|
|
{
|
|
return sub;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// SelectGenerator -- the manual re-tap: release the current source, tap
|
|
// generator N, drop the connect mode back to Connected (a manual selection
|
|
// ends any auto-hunt). The binary dereferences the find unguarded (authored
|
|
// mechs always carry A-D); we skip loud instead.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PoweredSubsystem::SelectGenerator(int generator_number)
|
|
{
|
|
Check(this);
|
|
|
|
Subsystem *generator = FindGeneratorByNumber(generator_number);
|
|
if (generator == NULL)
|
|
{
|
|
if (getenv("BT_MECH_LOG") || getenv("BT_POWER_LOG"))
|
|
{
|
|
DEBUG_STREAM << "[gensel] '" << GetName()
|
|
<< "' -> generator " << generator_number
|
|
<< " NOT FOUND" << endl << flush;
|
|
}
|
|
return;
|
|
}
|
|
|
|
DetachFromVoltageSource();
|
|
int tap = AttachToVoltageSource(generator);
|
|
modeAlarm.SetLevel(Connected);
|
|
|
|
if (getenv("BT_MECH_LOG") || getenv("BT_POWER_LOG"))
|
|
{
|
|
DEBUG_STREAM << "[gensel] '" << GetName()
|
|
<< "' -> '" << generator->GetName() << "'"
|
|
<< ((tap >= 0) ? " (tapped)" : " (REFUSED: no spare tap)")
|
|
<< endl << flush;
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// The cockpit button handlers (ids 4-8). Press-only (dataContents > 0),
|
|
// novice-locked -- a novice cockpit's generator panel is inert.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PoweredSubsystem::SelectGeneratorAMessageHandler(
|
|
ReceiverDataMessageOf<int> *message)
|
|
{
|
|
Check(this);
|
|
if (!NoviceLockout() && message->dataContents > 0)
|
|
{
|
|
SelectGenerator(1);
|
|
}
|
|
}
|
|
|
|
void
|
|
PoweredSubsystem::SelectGeneratorBMessageHandler(
|
|
ReceiverDataMessageOf<int> *message)
|
|
{
|
|
Check(this);
|
|
if (!NoviceLockout() && message->dataContents > 0)
|
|
{
|
|
SelectGenerator(2);
|
|
}
|
|
}
|
|
|
|
void
|
|
PoweredSubsystem::SelectGeneratorCMessageHandler(
|
|
ReceiverDataMessageOf<int> *message)
|
|
{
|
|
Check(this);
|
|
if (!NoviceLockout() && message->dataContents > 0)
|
|
{
|
|
SelectGenerator(3);
|
|
}
|
|
}
|
|
|
|
void
|
|
PoweredSubsystem::SelectGeneratorDMessageHandler(
|
|
ReceiverDataMessageOf<int> *message)
|
|
{
|
|
Check(this);
|
|
if (!NoviceLockout() && message->dataContents > 0)
|
|
{
|
|
SelectGenerator(4);
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ToggleGeneratorMode (id 8, binary @004b0abc): cycle Manual -> AutoConnect
|
|
// -> (detach +) Manual. The auto-hunt itself (a shorted/dead source makes
|
|
// the subsystem walk for a live generator) lives in
|
|
// PoweredSubsystemSimulation -- a later brick.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PoweredSubsystem::ToggleGeneratorModeMessageHandler(
|
|
ReceiverDataMessageOf<int> *message)
|
|
{
|
|
Check(this);
|
|
|
|
if (NoviceLockout() || message->dataContents <= 0)
|
|
{
|
|
return;
|
|
}
|
|
if ((unsigned)modeAlarm.GetLevel() < (unsigned)AutoConnect)
|
|
{
|
|
modeAlarm.SetLevel(AutoConnect);
|
|
}
|
|
else if (modeAlarm.GetLevel() == AutoConnect)
|
|
{
|
|
DetachFromVoltageSource();
|
|
modeAlarm.SetLevel(ManualConnect);
|
|
}
|
|
if (getenv("BT_MECH_LOG") || getenv("BT_POWER_LOG"))
|
|
{
|
|
DEBUG_STREAM << "[gensel] '" << GetName()
|
|
<< "' mode -> " << modeAlarm.GetLevel() << endl << flush;
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ChargeTimeScale -- the heat/firepower feedback (binary @004b0d50):
|
|
// rise = max(0, sourceTemperature - sourceStartingTemperature)
|
|
// scale = max(voltageScale,
|
|
// (thermalResistivityCoefficient * rise + 1) * voltageScale)
|
|
// A hot generator stretches the exponential charge constant, so recharging
|
|
// slows exactly when the electrical plant is cooking.
|
|
//#############################################################################
|
|
//
|
|
Scalar
|
|
PoweredSubsystem::ChargeTimeScale()
|
|
{
|
|
Check(this);
|
|
|
|
Generator *source = (Generator *)voltageSource.Resolve();
|
|
if (source == NULL)
|
|
{
|
|
return voltageScale;
|
|
}
|
|
|
|
Scalar rise = source->CurrentTemperatureOf()
|
|
- source->StartingTemperatureOf();
|
|
if (rise < 0.0f)
|
|
{
|
|
rise = 0.0f;
|
|
}
|
|
|
|
Scalar stretched =
|
|
(thermalResistivityCoefficient * rise + 1.0f) * voltageScale;
|
|
return (stretched > voltageScale) ? stretched : voltageScale;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// DeathReset -- respawn restore for the powered chain and the generators.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PoweredSubsystem::DeathReset(Logical full_reset)
|
|
{
|
|
Check(this);
|
|
MechSubsystem::DeathReset(full_reset);
|
|
ResetToInitialState(True);
|
|
//
|
|
// ResetToInitialState drops the connect-mode indicator to Manual; the
|
|
// respawned subsystem keeps its live tap, so the mode is Connected
|
|
// (the ctor's spawn state).
|
|
//
|
|
modeAlarm.SetLevel(Connected);
|
|
}
|
|
|
|
void
|
|
Generator::DeathReset(Logical full_reset)
|
|
{
|
|
Check(this);
|
|
MechSubsystem::DeathReset(full_reset);
|
|
//
|
|
// PRESERVE the tap accounting across the reset: consumers keep their
|
|
// voltageSource links through respawn (nothing detaches), so zeroing
|
|
// currentTapCount here would desync the maxTapCount invariant and let
|
|
// post-respawn SelectGenerator presses oversubscribe the generator
|
|
// (caught by the 5.3.24 adversarial review).
|
|
//
|
|
int live_taps = currentTapCount;
|
|
ResetToInitialState();
|
|
currentTapCount = live_taps;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ForceShortRecovery (binary @004b11bc): on a short event, drive the powering
|
|
// generator to Shorted and clear its output; GeneratorSimulation then runs
|
|
// the short-recovery timer back to Ready. Both @4ac9c8 calls in the binary
|
|
// are the not-novice experience predicate (this part and its source share
|
|
// the same mech, hence the same player) -- novice cockpits never see
|
|
// electrical shorts.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PoweredSubsystem::ForceShortRecovery()
|
|
{
|
|
Check(this);
|
|
|
|
if (NoviceLockout())
|
|
{
|
|
return;
|
|
}
|
|
|
|
Generator *source = (Generator *)voltageSource.Resolve();
|
|
if (source != NULL)
|
|
{
|
|
source->ForceShort();
|
|
if (getenv("BT_POWER_LOG") || getenv("BT_MECH_LOG"))
|
|
{
|
|
DEBUG_STREAM << "[short] '" << source->GetName()
|
|
<< "' SHORTED by special damage" << endl << flush;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// PoweredSubsystemSimulation -- the per-frame electrical step (binary
|
|
// @004b0bd0). Runs the HeatSink thermal step, then advances the electrical
|
|
// state machine from the state of the powering generator.
|
|
//
|
|
// PARTIAL: the AutoConnect replacement-generator hunt (modeAlarm AutoConnect +
|
|
// the status-flag gate) joins with the damage wave.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PoweredSubsystem::PoweredSubsystemSimulation(Scalar time_slice)
|
|
{
|
|
Check(this);
|
|
|
|
HeatSink::HeatSinkSimulation(time_slice);
|
|
|
|
Generator *source = (Generator *)voltageSource.Resolve();
|
|
if (source == NULL)
|
|
{
|
|
electricalStateAlarm.SetLevel(NoVoltage);
|
|
}
|
|
else
|
|
{
|
|
if (source->GeneratorStateOf() == Generator::GeneratorShorted)
|
|
{
|
|
electricalStateAlarm.SetLevel(Shorted);
|
|
}
|
|
if (source->GeneratorStateOf() == Generator::GeneratorStarting
|
|
|| source->GeneratorStateOf() == Generator::GeneratorFailed)
|
|
{
|
|
electricalStateAlarm.SetLevel(GeneratorOff);
|
|
}
|
|
}
|
|
|
|
switch (electricalStateAlarm.GetLevel())
|
|
{
|
|
case Starting:
|
|
startTimer += time_slice;
|
|
if (startTime <= startTimer)
|
|
{
|
|
electricalStateAlarm.SetLevel(Ready);
|
|
}
|
|
break;
|
|
|
|
case NoVoltage:
|
|
if (source != NULL)
|
|
{
|
|
electricalStateAlarm.SetLevel(Starting);
|
|
startTimer = 0.0f;
|
|
}
|
|
break;
|
|
|
|
case Shorted:
|
|
case GeneratorOff:
|
|
if (source != NULL
|
|
&& source->GeneratorStateOf() == Generator::GeneratorReady)
|
|
{
|
|
electricalStateAlarm.SetLevel(Starting);
|
|
startTimer = 0.0f;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (source != NULL)
|
|
{
|
|
inputVoltage = source->MeasuredVoltage();
|
|
}
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//###########################################################################
|
|
//############################## Generator #############################
|
|
//###########################################################################
|
|
|
|
//
|
|
//#############################################################################
|
|
// Shared data support
|
|
//#############################################################################
|
|
//
|
|
Derivation
|
|
Generator::ClassDerivations(
|
|
HeatSink::ClassDerivations,
|
|
"Generator"
|
|
);
|
|
|
|
const Generator::IndexEntry
|
|
Generator::AttributePointers[]=
|
|
{
|
|
ATTRIBUTE_ENTRY(Generator, OutputVoltage, outputVoltage),
|
|
ATTRIBUTE_ENTRY(Generator, RatedVoltage, ratedVoltage),
|
|
ATTRIBUTE_ENTRY(Generator, GeneratorNumber, generatorNumber)
|
|
};
|
|
|
|
Generator::AttributeIndexSet
|
|
Generator::AttributeIndex(
|
|
ELEMENTS(Generator::AttributePointers),
|
|
Generator::AttributePointers,
|
|
HeatSink::AttributeIndex
|
|
);
|
|
|
|
Generator::SharedData
|
|
Generator::DefaultData(
|
|
Generator::ClassDerivations,
|
|
Subsystem::MessageHandlers,
|
|
Generator::AttributeIndex,
|
|
Subsystem::StateCount
|
|
);
|
|
|
|
//
|
|
//#############################################################################
|
|
// The generator -- the voltage source loads tap.
|
|
//#############################################################################
|
|
//
|
|
Generator::Generator(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data
|
|
):
|
|
HeatSink(owner, subsystem_ID, subsystem_resource, shared_data),
|
|
stateAlarm(5)
|
|
{
|
|
Check(owner);
|
|
Check_Pointer(subsystem_resource);
|
|
|
|
ratedVoltage = subsystem_resource->ratedVoltage;
|
|
outputVoltage = ratedVoltage;
|
|
maxTapCount = subsystem_resource->maxTapCount;
|
|
currentTapCount = 0;
|
|
percentVoltageAvailable = 1.0f;
|
|
startTime = subsystem_resource->startTime;
|
|
startTimer = startTime;
|
|
stateAlarm.SetLevel(GeneratorReady);
|
|
generatorOn = 1;
|
|
shortRecoveryTime = subsystem_resource->shortRecoveryTime;
|
|
shortTimer = shortRecoveryTime;
|
|
|
|
//
|
|
// Generator number from the last character of the segment name
|
|
// ('A' -> 1, 'B' -> 2, ...).
|
|
//
|
|
const char *name = GetName();
|
|
generatorNumber = name[strlen(name) - 1] - 0x40;
|
|
|
|
//
|
|
// Install the generator's per-frame electrical Performance.
|
|
//
|
|
if (owner->GetInstance() != Entity::ReplicantInstance)
|
|
{
|
|
SetPerformance(&Generator::GeneratorSimulation);
|
|
}
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Generator::~Generator()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
Generator::TestClass(Mech &)
|
|
{
|
|
return True;
|
|
}
|
|
|
|
Logical
|
|
Generator::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(ClassDerivations);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Generator::ResetToInitialState()
|
|
{
|
|
Check(this);
|
|
HeatSink::ResetToInitialState(True);
|
|
outputVoltage = ratedVoltage;
|
|
currentTapCount = 0;
|
|
percentVoltageAvailable = 1.0f;
|
|
startTimer = startTime;
|
|
shortTimer = shortRecoveryTime;
|
|
generatorOn = 1;
|
|
stateAlarm.SetLevel(GeneratorReady);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// GeneratorSimulation -- the generator's per-frame step (PARTIAL). Runs the
|
|
// HeatSink thermal step and the start/short-recovery timers. The authentic
|
|
// load model (output voltage sag under tap load / I^2R self-heat feeding the
|
|
// charge integration) joins with the electrical-charge wave
|
|
// (TrackSeekVoltage). A healthy generator holds GeneratorReady at its rated
|
|
// voltage.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Generator::GeneratorSimulation(Scalar time_slice)
|
|
{
|
|
Check(this);
|
|
|
|
HeatSink::HeatSinkSimulation(time_slice);
|
|
|
|
switch (stateAlarm.GetLevel())
|
|
{
|
|
case GeneratorStarting:
|
|
startTimer += time_slice;
|
|
if (startTime <= startTimer)
|
|
{
|
|
stateAlarm.SetLevel(GeneratorReady);
|
|
outputVoltage = ratedVoltage;
|
|
}
|
|
break;
|
|
|
|
case GeneratorShorted:
|
|
shortTimer -= time_slice;
|
|
if (shortTimer <= 0.0f)
|
|
{
|
|
shortTimer = shortRecoveryTime;
|
|
stateAlarm.SetLevel(GeneratorStarting);
|
|
startTimer = 0.0f;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//###########################################################################
|
|
//############################ PowerWatcher ############################
|
|
//###########################################################################
|
|
|
|
Derivation
|
|
PowerWatcher::ClassDerivations(
|
|
HeatWatcher::ClassDerivations,
|
|
"PowerWatcher"
|
|
);
|
|
|
|
PowerWatcher::SharedData
|
|
PowerWatcher::DefaultData(
|
|
PowerWatcher::ClassDerivations,
|
|
Subsystem::MessageHandlers,
|
|
Subsystem::AttributeIndex,
|
|
Subsystem::StateCount
|
|
);
|
|
|
|
PowerWatcher::PowerWatcher(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data
|
|
):
|
|
HeatWatcher(owner, subsystem_ID, subsystem_resource, shared_data),
|
|
watchdogAlarm(5)
|
|
{
|
|
Check(owner);
|
|
Check_Pointer(subsystem_resource);
|
|
//
|
|
// minVoltage is a scaled fraction of the watched supply; the exact scale
|
|
// constant is a tuning value (stored 1:1 here until located).
|
|
//
|
|
minVoltage = subsystem_resource->minVoltagePercent;
|
|
Check_Fpu();
|
|
}
|
|
|
|
PowerWatcher::~PowerWatcher()
|
|
{
|
|
}
|
|
|
|
Logical
|
|
PowerWatcher::TestClass(Mech &)
|
|
{
|
|
return True;
|
|
}
|
|
|
|
Logical
|
|
PowerWatcher::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(ClassDerivations);
|
|
}
|
|
|
|
void
|
|
PowerWatcher::ResetToInitialState(Logical powered)
|
|
{
|
|
Check(this);
|
|
HeatWatcher::ResetToInitialState(powered);
|
|
watchdogAlarm.SetLevel(0);
|
|
}
|
|
|
|
void
|
|
PowerWatcher::DeathReset(Logical full_reset)
|
|
{
|
|
Check(this);
|
|
MechSubsystem::DeathReset(full_reset);
|
|
ResetToInitialState(True);
|
|
}
|
|
|
|
//
|
|
// Per-frame supply-voltage watchdog. Not yet reconstructed.
|
|
//
|
|
void
|
|
PowerWatcher::Simulation(Scalar)
|
|
{
|
|
Fail("PowerWatcher::Simulation -- powersub.cpp not yet reconstructed");
|
|
}
|