Ladder rungs, each run-verified on the pod rig: ReportLeak (HeatSink), GeneratorOn (Generator -- member existed, publication missing), ConfigureActivePress (MechSubsystem -- likewise), AmmoState + FireCountdownStarted + the rest of the AmmoBin table. THE PINNED RANGE IS NOW THE AUTHENTIC SHAPE. Publishing ReportLeak on HeatSink and ConfigureActivePress on MechSubsystem shifts the chain down two, and MechWeapon's bridge to its binary-pinned PercentDone (0x12) collapses from THREE guessed pads to ONE -- which is exactly the arithmetic the shipped string pool predicts (MechSubsystem 1, HeatableSubsystem 3, HeatSink 6, PoweredSubsystem 5). Three pads of guesswork replaced by a real attribute and a real base-class row. HeatableSubsystem and Torso rebase onto MechSubsystem::AttributeIndex; MechControlsMapper does not (it derives from Subsystem directly). Types were chosen deliberately this time, per the AudioWatcher families the engine instantiates (Motion / Hinge / Scalar / StateIndicator): every *State name resolves to a StateIndicator, ReportLeak is a Scalar. RESULT: the pod run now gets past every authored watcher and DRAWS THE FULL COCKPIT -- sensor cluster, myomers, cooling, the weapon panels, kills/deaths -- with the board booted and audio running. It then exits without flushing its redirected stdout, so the exit reason is not yet known; next step is a run with the redirect removed so the console is readable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
830 lines
23 KiB
C++
830 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),
|
|
ATTRIBUTE_ENTRY(Generator, GeneratorState, stateAlarm),
|
|
{ (int)Generator::GeneratorOnAttributeID2, "GeneratorOn",
|
|
(Simulation::AttributePointer)&Generator::generatorOn }
|
|
};
|
|
|
|
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");
|
|
}
|