Cockpit: GENERATOR POWER ROUTING -- assign weapons to generators (task #12)
The pilot's second heat-management tool: the PoweredSubsystem message table
@0x50F4EC (ids 4-8) registered and implemented -- SelectGeneratorA-D
(@004b099c..@004b0a74: FindGeneratorByNumber roster walk on generatorNumber
@0x1E0 -> AttachToVoltageSource re-tap with tap accounting -> modeAlarm
Connected) and ToggleGeneratorMode (@004b0abc: Manual -> Auto -> detach+Manual
cycle). Weapons inherit via the MechWeapon handler chain. Desktop: F5-F8
assign the selected weapon (BT_CONFIG_SLOT) to Generator A-D, F9 toggles
reconnect mode; BT_GENSEL_TEST scripts a headless verify.
Verified live end-to-end: dispatch -> handler -> re-tap ("PPC_1 ->
GeneratorD (tapped)") -> the charging I^2R physically moved (GenD cold ~90K
baseline -> ~1570K carrying the PPC; GenA relieved), stable over a sustained
autofire soak with thermal-breaker trips.
FOUR defects found and fixed on the way [all T1/T2]:
- THE e17 HEAT EXPLOSION: Generator::SourceLevel misread *(this[0x38]+0x158)
as linkedSinks->heatEnergy; it is the engine-base DamageZone @0xE0 ->
damageLevel [0..1] (the same named-member pattern as the bank radiator's
zone read). A breaker-restarting generator emitted (1 - 4e8) x 10000
volts; squared through the customers' I^2R feed, one restart blew the
whole thermal network to e17. Authentic: a damaged generator yields
proportionally less voltage.
- FUN_004ac9c8 is NOT "IsDamaged": raw body = owner -> mech+0x190 player ->
roleClassIndex(+0x274) == 0 -- the ROOKIE-role lockout for advanced
cockpit systems. New bridge BTPlayerRoleLocksAdvanced (NULL player =
unlocked [T3]; bring-up role 2 = unlocked). The old stand-in gated the
handlers off permanently (healthy subsystems have simulationState==1).
- MESSAGE_ENTRY tables must be FUNCTION-LOCAL statics inside the accessor:
as namespace-scope arrays they are read by other TUs' static-init chains
before their own initializers run -- Build copies zeros and every id in
the table is silently dropped (ids 9/10 only worked by TU-order luck;
both tables relocated; gotcha recorded, reconstruction-gotchas #9).
- The AutoConnect hunt scanned GetSegment() -- the raw @004b0bd0 walks the
subsystem ROSTER (+0x124/+0x128); EntitySegments were being cast to
Subsystems (the classic +0x128 gotcha).
Also: the dense handler table's GAP slots (skipped ids) are uninitialized
heap -- the name-based Find strcmp-walks them and AVs; diagnostic probes use
the id-based Find only (the 1995 binary's own tables carry the same holes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
092408041c
commit
d7b900d108
@@ -175,8 +175,29 @@ authentic path scoped.
|
||||
the only MechWeapon attribute" claim was wrong (fixed in mechweap.hpp).
|
||||
* Tails: weapon+0x110 (write 0/-1 on session open/close; port name vitalSubsystemIndex)
|
||||
has NO confirmed reader yet; @004d1acc (+0x48 L4 Notify override) is unexported —
|
||||
re-export from Ghidra; the PoweredSubsystem generator-select handlers (ids 4-8,
|
||||
@004b099c..@004b0abc) are decoded but not yet registered in the port.
|
||||
re-export from Ghidra.
|
||||
* **Task #12 (2026-07-11) [T2]: the PoweredSubsystem POWER-ROUTING handlers landed** --
|
||||
ids 4-8 registered (table @0x50F4EC): SelectGeneratorA-D = FindGeneratorByNumber
|
||||
(@004b0b18 ROSTER walk matching generatorNumber@0x1E0) -> AttachToVoltageSource ->
|
||||
modeAlarm=Connected; ToggleGeneratorMode (@004b0abc) = <2 -> AutoConnect, ==2 ->
|
||||
Detach + ManualConnect. Weapons inherit via the MechWeapon handler chain (the old
|
||||
qualified GetMessageHandlers() calls silently resolved to the engine Receiver ROOT
|
||||
set -- same trap as the task #6 weapon handlers). The AutoConnect hunt's
|
||||
GetSegment() scan corrected to the subsystem ROSTER (the +0x128 gotcha). Desktop:
|
||||
F5-F8 assign the selected weapon (BT_CONFIG_SLOT) to Generator A-D, F9 toggles
|
||||
Manual/Auto; BT_GENSEL_TEST scripts a headless re-tap. Note: Myomers/Sensor still
|
||||
chain the root set (their binary tables unverified) -- extend if aux maps prove
|
||||
they receive these ids. TWO MORE TRAPS found landing this: (a) MESSAGE_ENTRY
|
||||
tables must be function-local statics INSIDE the accessor (cross-TU static-init
|
||||
order emptied the table -- see [[reconstruction-gotchas]] #9 last bullet); (b)
|
||||
**FUN_004ac9c8 is NOT IsDamaged** -- raw body: `owner(+0xD0) -> mech+0x190 player
|
||||
-> roleClassIndex(+0x274) == 0` = the ROOKIE-role lockout for advanced cockpit
|
||||
systems (port bridge BTPlayerRoleLocksAdvanced, btplayer.cpp; NULL player =
|
||||
unlocked [T3]; bring-up role defaults to 2 = unlocked). AUDIT TAIL: the other
|
||||
powersub.cpp sites annotated FUN_004ac9c8 (the coolant-draw gate ~:427,
|
||||
ForceShortRecovery ~:444, the Generator/PowerWatcher site ~:1085) still call the
|
||||
heat-family IsDamaged (simulationState != 0) stand-in -- each needs its raw fn
|
||||
re-checked and swapped to the role bridge where the binary calls 4ac9c8.
|
||||
- **Myomers authentic coupling** — the structural un-stub is INERT (mover feed + heat-gen no-op).
|
||||
Real coupling needs the advanced-damage gate (`OwnerAdvancedDamage`/`FUN_004ad7d4` → the owning
|
||||
**BTPlayer** `mech+0x190`+0x260, NOT 0xBD3 — see the mech+0x190 item above) + `MoverAttach` routing
|
||||
|
||||
@@ -160,6 +160,18 @@ real PoweredSubsystem). Keep the alloc SIZE + special-cache when swapping a fact
|
||||
Receiver::NextMessageID` early branch does NOT save you — it lacks a `return`, and the console IDs
|
||||
aren't in that range anyway.) [T2]
|
||||
|
||||
- **MESSAGE_ENTRY tables must be FUNCTION-LOCAL statics inside the GetMessageHandlers()
|
||||
accessor** (task #12). A namespace-scope `HandlerEntry MessageHandlerEntries[]` can be read
|
||||
by ANOTHER TU's static-init chain (DefaultData -> accessor -> Build) before its own TU's
|
||||
dynamic initializers run -- Build copies ZEROS, and every id in that table is silently
|
||||
dropped at dispatch (the set LOOKS built; ids added later in the chain still work, which
|
||||
hides it). Symptom: message transmitted, handler never runs, no error. The engine's own
|
||||
APP.cpp idiom (table + set both function-local in the accessor) is init-order-proof --
|
||||
always use it. Related trap: the dense handler table (Build indexes slots by id-1) leaves
|
||||
GAP slots (skipped ids) as uninitialized heap -- the NAME-based `Find(const char*)`
|
||||
strcmp-walks every slot and AVs on a gap's garbage entryName (the id-based Find is safe).
|
||||
The 1995 binary's own tables carry the same holes. [T2]
|
||||
|
||||
## 10. Container-Execute must override (gauges)
|
||||
|
||||
The 2007 engine `Gauge::Execute` base is `Fail("not overridden")` → `abort()` (GAUGE.cpp:598);
|
||||
|
||||
@@ -1467,3 +1467,28 @@ void BTPostKillScore(Entity *victim, Scalar damage) // Step 7: KILL (+ MP deat
|
||||
victim_player->Dispatch(&dead);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//#############################################################################
|
||||
// BTPlayerRoleLocksAdvanced -- complete-type bridge (task #12)
|
||||
//
|
||||
// FUN_004ac9c8 [T1]: `return *(*(*(sub+0xD0) + 0x190) + 0x274) == 0` -- the
|
||||
// subsystem's owner Mech -> the owning BTPlayer (mech+0x190, GetPlayerLink)
|
||||
// -> roleClassIndex (+0x274, role resource +0xE4). TRUE = the ROOKIE role
|
||||
// (class 0) LOCKS the advanced cockpit systems (generator routing, coolant
|
||||
// valves); nonzero role classes unlock them. The old reconstruction
|
||||
// mislabeled this fn "Subsystem::IsDamaged" -- with healthy subsystems at
|
||||
// simulationState==1, that stand-in gated the power-routing handlers OFF
|
||||
// permanently. A NULL player (the target dummy / unbound solo mech) would AV
|
||||
// in the binary (every pod mech has a player); the port reads NULL as
|
||||
// UNLOCKED so dev rigs work [T3].
|
||||
//
|
||||
int BTPlayerRoleLocksAdvanced(void *owner_mech)
|
||||
{
|
||||
if (owner_mech == 0)
|
||||
return 0;
|
||||
BTPlayer *player = MECH_OWNING_PLAYER(owner_mech);
|
||||
if (player == 0)
|
||||
return 0; // dev-permissive [T3]
|
||||
return (player->roleClassIndex == 0) ? 1 : 0; // player+0x274 == 0
|
||||
}
|
||||
|
||||
@@ -373,6 +373,7 @@ class DropZone__ReplyMessage;
|
||||
roleReturnDelay2; // @0x26c
|
||||
int
|
||||
roleClassIndex; // @0x274 (role resource +0xe4)
|
||||
friend int BTPlayerRoleLocksAdvanced(void *); // the FUN_004ac9c8 bridge (task #12)
|
||||
|
||||
int
|
||||
killCount; // @0x27c kills credited to this player
|
||||
|
||||
@@ -645,6 +645,8 @@ static int gBTLaserKey = 0; // raw key states (set by the keyboar
|
||||
static int gBTPPCKey = 0;
|
||||
static int gBTMissileKey = 0;
|
||||
static int gBTConfigKey = 0; // task #6: HOLD 'G' = the weapon-configure button
|
||||
static int gBTGenSelKey = 0; // task #12: F5..F8 = SelectGeneratorA..D, F9 = mode toggle
|
||||
// (0 idle; else the message id 4..8)
|
||||
|
||||
// Damage: each shot dispatches a REAL Entity::TakeDamageMessage to the target. Now
|
||||
// that the damage zones are constructed (mech.cpp Pass-3 zone build), the engine base
|
||||
@@ -1529,6 +1531,15 @@ void
|
||||
// weapon (BT_CONFIG_SLOT, default: the first weapon); while
|
||||
// held, the fire keys TOGGLE its group membership.
|
||||
gBTConfigKey = focused && (pAsync('G') & dn) ? 1 : 0;
|
||||
// task #12: F5..F8 assign the selected weapon to Generator
|
||||
// A..D; F9 toggles Manual/Auto reconnect.
|
||||
gBTGenSelKey =
|
||||
(focused && (pAsync(0x74 /*F5*/) & dn)) ? 4
|
||||
: (focused && (pAsync(0x75 /*F6*/) & dn)) ? 5
|
||||
: (focused && (pAsync(0x76 /*F7*/) & dn)) ? 6
|
||||
: (focused && (pAsync(0x77 /*F8*/) & dn)) ? 7
|
||||
: (focused && (pAsync(0x78 /*F9*/) & dn)) ? 8
|
||||
: 0;
|
||||
// gBTDrive.fire = "any weapon trigger down" (feeds the bring-up
|
||||
// damage dispatcher + the beam-visual keepalive)
|
||||
gBTDrive.fire = (gBTLaserKey || gBTPPCKey || gBTMissileKey) ? 1 : 0;
|
||||
@@ -3101,6 +3112,67 @@ void
|
||||
}
|
||||
|
||||
|
||||
// task #12 scripted verify (BT_GENSEL_TEST=1): one SelectGeneratorD
|
||||
// press at frame ~2500 -- the PPC re-taps from its authored GeneratorA
|
||||
// onto GeneratorD; the charging I^2R then lands on GenD ([heat-t]).
|
||||
if ((Entity *)this == application->GetViewpointEntity()
|
||||
&& getenv("BT_GENSEL_TEST"))
|
||||
{
|
||||
static int s_gsFrame = 0;
|
||||
++s_gsFrame;
|
||||
gBTGenSelKey = (s_gsFrame >= 600 && s_gsFrame < 610) ? 7 : 0;
|
||||
}
|
||||
|
||||
// task #12 dev harness: the POWER-ROUTING buttons. F5..F9 edge ->
|
||||
// dispatch SelectGeneratorA..D / ToggleGeneratorMode (ids 4..8) to the
|
||||
// selected weapon -- the same ReceiverDataMessageOf<ControlsButton>
|
||||
// press payload the pod's aux-panel EventMappings deliver. The
|
||||
// handler chain is PoweredSubsystem's (weapons inherit via MechWeapon).
|
||||
if ((Entity *)this == application->GetViewpointEntity())
|
||||
{
|
||||
static int s_prevGenSel = 0;
|
||||
if (gBTGenSelKey != s_prevGenSel)
|
||||
{
|
||||
int pressedID = gBTGenSelKey; // 0 on release
|
||||
s_prevGenSel = gBTGenSelKey;
|
||||
if (pressedID != 0)
|
||||
{
|
||||
Subsystem *weapon = 0;
|
||||
const char *slotEnv = getenv("BT_CONFIG_SLOT");
|
||||
int wantSlot = (slotEnv != 0) ? atoi(slotEnv) : -1;
|
||||
for (int s = 1; s < GetSubsystemCount(); ++s)
|
||||
{
|
||||
Subsystem *sub = GetSubsystem(s);
|
||||
if (sub == 0)
|
||||
continue;
|
||||
int cid = (int)sub->GetClassID();
|
||||
if (wantSlot >= 0 ? (s == wantSlot)
|
||||
: (cid == 0xBC8 || cid == 0xBCA || cid == 0xBCE
|
||||
|| cid == 0xBD0 || cid == 0xBD4))
|
||||
{
|
||||
weapon = sub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (weapon != 0)
|
||||
{
|
||||
if (getenv("BT_FIRE_LOG"))
|
||||
{
|
||||
extern void BTGenSelProbe();
|
||||
BTGenSelProbe();
|
||||
DEBUG_STREAM << "[gensel-tx] id=" << pressedID
|
||||
<< " -> " << weapon->GetName() << std::endl;
|
||||
}
|
||||
ReceiverDataMessageOf<ControlsButton> msg(
|
||||
pressedID /*4..8 (powersub.hpp ids)*/,
|
||||
sizeof(ReceiverDataMessageOf<ControlsButton>),
|
||||
(ControlsButton)1 /*press*/);
|
||||
weapon->Dispatch(&msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// task #6 dev harness: the config-session BRACKET. 'G' edge ->
|
||||
// dispatch ConfigureMappables (id 9) press/release to the selected
|
||||
// weapon -- the exact ReceiverDataMessageOf<ControlsButton> payload
|
||||
|
||||
@@ -84,20 +84,20 @@ Derivation
|
||||
|
||||
// task #6 -- the binary's MechWeapon handler table @0x511860 (section_dump:
|
||||
// {9, "ConfigureMappables"@0x511994, @004b9550}, {10, "ChooseButton"@0x5119A7,
|
||||
// @004b95b8}); chained onto the PoweredSubsystem set.
|
||||
const Receiver::HandlerEntry
|
||||
MechWeapon::MessageHandlerEntries[]=
|
||||
// @004b95b8}); chained onto the PoweredSubsystem set. Table INSIDE the
|
||||
// accessor (function-local static) -- the cross-TU static-init order trap
|
||||
// emptied the powersub table this same way (task #12); this one only worked
|
||||
// by TU-order luck.
|
||||
Receiver::MessageHandlerSet&
|
||||
MechWeapon::GetMessageHandlers()
|
||||
{
|
||||
static const Receiver::HandlerEntry entries[]=
|
||||
{
|
||||
MESSAGE_ENTRY(MechWeapon, ConfigureMappables), // id 9 @004b9550
|
||||
MESSAGE_ENTRY(MechWeapon, ChooseButton) // id 10 @004b95b8
|
||||
};
|
||||
|
||||
Receiver::MessageHandlerSet&
|
||||
MechWeapon::GetMessageHandlers()
|
||||
{
|
||||
static Receiver::MessageHandlerSet messageHandlers(
|
||||
ELEMENTS(MechWeapon::MessageHandlerEntries),
|
||||
MechWeapon::MessageHandlerEntries,
|
||||
ELEMENTS(entries), entries,
|
||||
PoweredSubsystem::GetMessageHandlers()
|
||||
);
|
||||
return messageHandlers;
|
||||
|
||||
@@ -107,9 +107,8 @@ class CockpitHud;
|
||||
static Derivation ClassDerivations;
|
||||
// task #6: the binary registers TWO handlers ({9,"ConfigureMappables"},
|
||||
// {10,"ChooseButton"} -- table @0x511860); the old empty static set
|
||||
// meant no pilot input could ever reach the config session. The
|
||||
// accessor idiom (function-local static) dodges cross-TU init order.
|
||||
static const Receiver::HandlerEntry MessageHandlerEntries[];
|
||||
// meant no pilot input could ever reach the config session. Table +
|
||||
// set both function-local statics in the accessor (init-order-proof).
|
||||
static Receiver::MessageHandlerSet& GetMessageHandlers();
|
||||
static AttributeIndexSet AttributeIndex;
|
||||
static SharedData DefaultData;
|
||||
|
||||
+160
-11
@@ -33,7 +33,9 @@
|
||||
// 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_004ac9c8 BTPlayerRoleLocksAdvanced() (owner mech+0x190 player ->
|
||||
// roleClassIndex+0x274 == 0: the ROOKIE role locks the
|
||||
// advanced cockpit systems -- NOT "IsDamaged"; task #12)
|
||||
// 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
|
||||
@@ -110,6 +112,35 @@ PoweredSubsystem::AttributeIndexSet&
|
||||
//#############################################################################
|
||||
// Shared Data Support
|
||||
//
|
||||
// task #12 -- the binary handler table @0x50F4EC (section_dump: ids 4-8,
|
||||
// names "SelectGeneratorA".."ToggleGeneratorMode"). Chained onto the engine
|
||||
// Receiver root set.
|
||||
//
|
||||
// STATIC-INIT ORDER (the silent-drop bug): the table lives INSIDE the accessor
|
||||
// as a function-local static. As a namespace-scope array it was read by
|
||||
// Emitter::DefaultData's cross-TU static-init chain BEFORE this TU's dynamic
|
||||
// initializers ran -- the Build saw zeros, so weapons carried ids 9/10 but the
|
||||
// 4-8 slots stayed empty (observed: [gensel-tx] logged, handler never ran).
|
||||
// Function-local statics initialize on first call -- order-proof; the same
|
||||
// idiom the T0 engine uses (APP.cpp GetMessageHandlers).
|
||||
Receiver::MessageHandlerSet&
|
||||
PoweredSubsystem::GetMessageHandlers()
|
||||
{
|
||||
static const Receiver::HandlerEntry entries[]=
|
||||
{
|
||||
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
|
||||
};
|
||||
static Receiver::MessageHandlerSet messageHandlers(
|
||||
ELEMENTS(entries), entries,
|
||||
Receiver::GetMessageHandlers()
|
||||
);
|
||||
return messageHandlers;
|
||||
}
|
||||
|
||||
PoweredSubsystem::SharedData
|
||||
PoweredSubsystem::DefaultData(
|
||||
PoweredSubsystem::GetClassDerivations(),
|
||||
@@ -317,19 +348,19 @@ void
|
||||
&& GetStatusFlags() == 0 // (*this[0x40])(this,0) == 0
|
||||
)
|
||||
{
|
||||
// task #12 ROSTER CORRECTION: the raw @004b0bd0 loop walks owner+0x124/
|
||||
// +0x128 -- the SUBSYSTEM ROSTER, not the skeleton segment table (the
|
||||
// same +0x128 gotcha as the heat/voltage index resolves). The old
|
||||
// GetSegment() read cast EntitySegments to Subsystems.
|
||||
Mech *mech = (Mech *)owner;
|
||||
for (int i = 0; ; ++i) // owner+0x124 segment scan
|
||||
int count = mech->GetSubsystemCount(); // owner+0x124
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
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
|
||||
Subsystem *sub = mech->GetSubsystem(i); // owner+0x128[i]
|
||||
if (sub != 0
|
||||
&& sub->GetClassID() == RegisteredClass::GeneratorClassID // +4 == 0xbc1
|
||||
&& GetStatusFlags() != 0
|
||||
&& AttachToVoltageSource(segment) != -1 // FUN_004b0dd8
|
||||
&& AttachToVoltageSource(sub) != -1 // FUN_004b0dd8
|
||||
)
|
||||
{
|
||||
break;
|
||||
@@ -497,6 +528,124 @@ void
|
||||
}
|
||||
|
||||
|
||||
//#############################################################################
|
||||
// The cockpit POWER-ROUTING message handlers (task #12)
|
||||
//
|
||||
// @004b099c/@004b09e4/@004b0a2c/@004b0a74 -- one per generator; identical
|
||||
// bodies (press only, undamaged only): find Generator N on the owner's
|
||||
// SUBSYSTEM ROSTER, re-tap onto it, and drop the connect mode back to
|
||||
// Connected (manual selection ends any auto-hunt).
|
||||
//
|
||||
void
|
||||
PoweredSubsystem::SelectGenerator(int generator_number)
|
||||
{
|
||||
Subsystem *gen = FindGeneratorByNumber(generator_number); // FUN_004b0b18
|
||||
if (gen == 0)
|
||||
{
|
||||
// the binary dereferences the result unguarded (authored mechs always
|
||||
// carry generators A-D); fail loud instead of crashing.
|
||||
Verify(False, "SelectGenerator: no such generator", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
int tap = AttachToVoltageSource(gen); // FUN_004b0dd8
|
||||
modeAlarm.SetLevel(Connected); // FUN_0041bbd8(this+0x2b8, 1)
|
||||
if (getenv("BT_FIRE_LOG") || getenv("BT_HEAT_LOG"))
|
||||
DEBUG_STREAM << "[gensel] " << GetName() << " -> " << gen->GetName()
|
||||
<< (tap >= 0 ? " (tapped)" : " (REFUSED: no spare tap)") << std::endl;
|
||||
}
|
||||
|
||||
extern int BTPlayerRoleLocksAdvanced(void *owner_mech); // btplayer.cpp (FUN_004ac9c8)
|
||||
|
||||
void
|
||||
PoweredSubsystem::SelectGeneratorAMessageHandler(ReceiverDataMessageOf<int> *message)
|
||||
{
|
||||
if (!BTPlayerRoleLocksAdvanced(owner) && message->dataContents > 0) // FUN_004ac9c8 == 0 && press
|
||||
SelectGenerator(1);
|
||||
}
|
||||
void
|
||||
PoweredSubsystem::SelectGeneratorBMessageHandler(ReceiverDataMessageOf<int> *message)
|
||||
{
|
||||
if (!BTPlayerRoleLocksAdvanced(owner) && message->dataContents > 0)
|
||||
SelectGenerator(2);
|
||||
}
|
||||
void
|
||||
PoweredSubsystem::SelectGeneratorCMessageHandler(ReceiverDataMessageOf<int> *message)
|
||||
{
|
||||
if (!BTPlayerRoleLocksAdvanced(owner) && message->dataContents > 0)
|
||||
SelectGenerator(3);
|
||||
}
|
||||
void
|
||||
PoweredSubsystem::SelectGeneratorDMessageHandler(ReceiverDataMessageOf<int> *message)
|
||||
{
|
||||
if (getenv("BT_FIRE_LOG"))
|
||||
DEBUG_STREAM << "[gensel-rx] D on " << GetName()
|
||||
<< " locked=" << BTPlayerRoleLocksAdvanced(owner)
|
||||
<< " v=" << message->dataContents << std::endl;
|
||||
if (!BTPlayerRoleLocksAdvanced(owner) && message->dataContents > 0)
|
||||
SelectGenerator(4);
|
||||
}
|
||||
|
||||
// diag (task #12): registration probe -- is the handler live in the set?
|
||||
// (id-based Find only: the NAME Find strcmp-walks every slot including the
|
||||
// uninitialized id-3 GAP slot between the root set and ours -- garbage
|
||||
// entryName pointer -> AV. The dense-table gap is engine T0 behavior; the
|
||||
// 1995 binary's own table has the same hole.)
|
||||
void BTGenSelProbe()
|
||||
{
|
||||
Receiver::MessageHandlerSet &set = PoweredSubsystem::GetMessageHandlers();
|
||||
Receiver::Handler byId = set.Find((Receiver::MessageID)7);
|
||||
DEBUG_STREAM << "[gensel-reg] byId=" << (int)(byId != Receiver::NullHandler)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
//
|
||||
// @004b0abc -- Manual/Auto connect-mode toggle (press only, undamaged only):
|
||||
// below AutoConnect -> arm the auto-hunt; at AutoConnect -> drop the current
|
||||
// tap and go fully Manual (the pilot takes over routing).
|
||||
//
|
||||
void
|
||||
PoweredSubsystem::ToggleGeneratorModeMessageHandler(ReceiverDataMessageOf<int> *message)
|
||||
{
|
||||
if (BTPlayerRoleLocksAdvanced(owner) || message->dataContents <= 0)
|
||||
return;
|
||||
if ((unsigned)modeAlarm.GetLevel() < (unsigned)AutoConnect) // this+0x2cc < 2
|
||||
{
|
||||
modeAlarm.SetLevel(AutoConnect); // 2
|
||||
}
|
||||
else if (modeAlarm.GetLevel() == AutoConnect)
|
||||
{
|
||||
DetachFromVoltageSource(); // FUN_004b0e30
|
||||
modeAlarm.SetLevel(ManualConnect); // 0
|
||||
}
|
||||
if (getenv("BT_FIRE_LOG") || getenv("BT_HEAT_LOG"))
|
||||
DEBUG_STREAM << "[gensel] " << GetName() << " mode -> "
|
||||
<< modeAlarm.GetLevel() << std::endl;
|
||||
}
|
||||
|
||||
//
|
||||
// @004b0b18 -- walk the owner's SUBSYSTEM ROSTER (mech+0x128, count +0x124)
|
||||
// for the Generator segment whose generatorNumber (+0x1E0, from the name
|
||||
// suffix 'A'..'D') matches. NULL when absent.
|
||||
//
|
||||
Subsystem *
|
||||
PoweredSubsystem::FindGeneratorByNumber(int generator_number)
|
||||
{
|
||||
Mech *mech = (Mech *)owner; // this+0xD0
|
||||
int count = mech->GetSubsystemCount(); // +0x124
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
Subsystem *sub = mech->GetSubsystem(i); // +0x128[i]
|
||||
if (sub != 0
|
||||
&& sub->GetClassID() == RegisteredClass::GeneratorClassID // +4 == 0xbc1
|
||||
&& ((Generator *)sub)->generatorNumber == generator_number) // +0x1E0
|
||||
{
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//#############################################################################
|
||||
// Voltage-source linkage helpers
|
||||
//
|
||||
|
||||
@@ -67,8 +67,45 @@ class Generator;
|
||||
//
|
||||
public:
|
||||
static Derivation *GetClassDerivations(); // @0050f4bc
|
||||
// task #12: the binary's PoweredSubsystem handler table @0x50F4EC --
|
||||
// {4,"SelectGeneratorA"->@004b099c} .. {8,"ToggleGeneratorMode"->@004b0abc}.
|
||||
// (The previous qualified calls resolved to the inherited engine
|
||||
// Receiver::GetMessageHandlers() root set, so the generator-select
|
||||
// messages had no handlers anywhere.) MechWeapon's set chains this
|
||||
// one, so every weapon inherits the five handlers. The entry table is
|
||||
// a function-local static INSIDE the accessor (static-init order).
|
||||
static Receiver::MessageHandlerSet& GetMessageHandlers();
|
||||
static SharedData DefaultData;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Messaging Support -- the cockpit POWER-ROUTING messages (task #12).
|
||||
// Ids PINNED to the binary table (MechWeapon extends the shared id space
|
||||
// with 9/10, the config-mode session). The aux-panel type-6 EventMappings
|
||||
// deliver these to a weapon: assign it to Generator 1-4, or toggle its
|
||||
// Manual/Auto reconnect mode.
|
||||
//
|
||||
public:
|
||||
enum {
|
||||
SelectGeneratorAMessageID = 4, // @004b099c
|
||||
SelectGeneratorBMessageID = 5, // @004b09e4
|
||||
SelectGeneratorCMessageID = 6, // @004b0a2c
|
||||
SelectGeneratorDMessageID = 7, // @004b0a74
|
||||
ToggleGeneratorModeMessageID = 8 // @004b0abc
|
||||
};
|
||||
// (ControlsButton == int, engine CONTROLS.h; spelled int here so TUs
|
||||
// that include this header without the controls layer still compile.)
|
||||
void
|
||||
SelectGeneratorAMessageHandler(ReceiverDataMessageOf<int> *message),
|
||||
SelectGeneratorBMessageHandler(ReceiverDataMessageOf<int> *message),
|
||||
SelectGeneratorCMessageHandler(ReceiverDataMessageOf<int> *message),
|
||||
SelectGeneratorDMessageHandler(ReceiverDataMessageOf<int> *message),
|
||||
ToggleGeneratorModeMessageHandler(ReceiverDataMessageOf<int> *message);
|
||||
protected:
|
||||
void
|
||||
SelectGenerator(int generator_number); // the shared @004b099c.. body
|
||||
Subsystem *
|
||||
FindGeneratorByNumber(int generator_number); // @004b0b18
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Electrical state machine (electricalStateAlarm @0x264, level @0x278,
|
||||
// reported by PrintState @004b1224).
|
||||
@@ -469,12 +506,22 @@ class Generator;
|
||||
// The generator's "short" state is its stateAlarm level (GeneratorShorted==3) +
|
||||
// outputVoltage (IsSourceShorted @004b0b5c reads 0x210/0x1dc). There is NO
|
||||
// separate shortFlag field on the Generator (it ends at 0x250 == stateAlarm end).
|
||||
// SourceLevel == linked master sink's stored heat energy (this[0x38]+0x158).
|
||||
//
|
||||
// task #12 CORRECTION (THE e17 HEAT EXPLOSION): the raw read
|
||||
// *(this[0x38]+0x158) is the engine-base DamageZone pointer @0xE0 and
|
||||
// the ZONE's damageLevel [0..1] -- the SAME named-member pattern as
|
||||
// the bank radiator's zone read (heatfamily_reslice.cpp:138), NOT
|
||||
// linkedSinks->heatEnergy. The old misread made a restarting
|
||||
// generator emit (1.0 - heatEnergy~4e8) x 10000 volts; squared
|
||||
// through the customers' I^2R feed, one generator breaker-restart
|
||||
// blew the whole thermal network to e17 (observed live, task #12
|
||||
// duty run). Authentic: a DAMAGED generator yields proportionally
|
||||
// less voltage -- outputVoltage = (1 - zoneDamage) x ratedVoltage.
|
||||
Scalar
|
||||
SourceLevel()
|
||||
{
|
||||
HeatSink *master = (HeatSink *)linkedSinks.Resolve();
|
||||
return (master != 0) ? master->heatEnergy : (Scalar)0.0f;
|
||||
::DamageZone *ownZone = this->Subsystem::damageZone; // @0xE0 (word 0x38)
|
||||
return (ownZone != 0) ? (Scalar)ownZone->damageLevel : (Scalar)0.0f; // +0x158
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Reference in New Issue
Block a user