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:
arcattack
2026-07-11 23:16:48 -05:00
co-authored by Claude Fable 5
parent 092408041c
commit d7b900d108
9 changed files with 355 additions and 29 deletions
+72
View File
@@ -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