The previous commit's attribution was wrong and is corrected in the roadmap. WRONG: 'the crash is the skeleton walk's' rested on 0 crashes in 2 runs with the walk disabled -- a 25% coin flip presented as evidence. The oldest preserved dump has no [skl] line and ends on the old 'couldn't figure out how to MakeEntityRenderables' fallback, so it predates the walk and faults identically. WRONG: 'it lands at different points each time'. Every dump carries the same numbers to the byte (cr2 7000FA64, EIP 66D9). What varies is how far the log gets, not where the fault is. Resolving the address through btl4opt.map names it exactly: EulerAngles::operator=(const LinearMatrix&)+0x19, a read through the matrix reference. A binary scan finds all three call sites of that operator pass a stack local, so the 1.79GB pointer still has no static explanation -- that is now its own open item rather than a guess. Two theories killed cleanly by the new BT_STACK_LOG probe and a binary scan: ESP drift (drift=0 over 2002 frames; exactly one callee-cleans function exists in the whole binary) and an undersized stack (our PE and the shipped one have identical 1MB/8K geometry). What the probe found matters more: the application is parked in state=2, which is LoadingMission, not WaitingForLaunch. Priority 0 is where the interest manager queues renderer events during load, the gate needs that priority empty, and it never empties -- so the mission never launches and the renderer holds a blank screen by design. The fault arrives ~30s into that wait. Runs that DO launch never print a single [launch] line. So 'crashes half the time' and 'hangs during load' are one event seen twice. A 15x disagreement between two clocks looked like a reconstruction slip -- BTL4.CPP passes GetTicksPerSecond() where ApplicationManager wants a frame rate. Checked against the shipped binary before touching it: same instruction sequence, same kind of static float pushed. Authentic. Documented so nobody 'fixes' it. Also swept every subsystem DefaultData against its real C++ base. Fifteen chain past their immediate parent, but fourteen skip only classes that add no handlers and no attributes, and no class's attribute-ID base disagrees with its index chain -- so there are no gap slots there. The one real defect: Generator is a HeatSink but chained to Subsystem::MessageHandlers, so it ignored every ToggleCooling message. Fixed (compiles next build). Tooling: podrun.sh stages the build over BTL4REC.EXE, exports the host-side VPX board env -- without which the run dies at the iserver handshake rather than merely rendering nothing -- and archives every run's log, marking it -CRASH when it faulted. Before this the only preserved dump was an accident, on a rig where each run costs four minutes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
840 lines
24 KiB
C++
840 lines
24 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
|
|
);
|
|
|
|
//
|
|
// A Generator IS a HeatSink, and HeatSink is one of only two classes in the
|
|
// subsystem tree that defines a message handler of its own -- ToggleCooling.
|
|
// Chaining to Subsystem::MessageHandlers here skipped it, so a generator
|
|
// silently ignored every cooling toggle it was sent. Found by sweeping every
|
|
// DefaultData against its real C++ base: fifteen classes chain past their
|
|
// immediate parent, but the other fourteen skip only classes that add no
|
|
// handlers and no attributes, which makes them equivalent (and probably
|
|
// authentic). This one loses a handler, so it is a real defect.
|
|
//
|
|
Generator::SharedData
|
|
Generator::DefaultData(
|
|
Generator::ClassDerivations,
|
|
HeatSink::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");
|
|
}
|