Gitea #47 COMPLETE: the ENG-button attention FLASH is live (jam / bay fire) -- MechTech status scan + BTL4GaugeAlarmManager + lamp chain, all from the binary

The pod behaviour Cyd described -- "on a jam or bay fire the display eng button
for the system flashes" -- is authored data + a five-stage chain, now running:

  MechTech::TechnicalAssistance (@004ad33c, per frame)
    edge-scans every monitored subsystem's GetStatusFlags() 7-bit condition
    mask (TechStatusType: Destroyed 0, Damaged 1, CoolantLeaking 2,
    Overheating 3, AmmoBurning 4, Jammed 5, BadPower 6)
  -> Start/StopEntityAlarmMessage (@00436688 id 7 size 0x20 / @004366b8 id 8
     size 0x1C; broadcast @004364e4; port shape: direct
     Start/StopEntityAlarmImplementation calls on the gauge renderer)
  -> GaugeAlarmManager::Activate(alarmModel) -- alarmModel = MechTech+0x100 =
     the 'mechalrm' ModelList (id 83) -> SearchList(83, type 31) -> the baked
     GaugeAlarmStream (id 331, 11 items {condition, lampCode}), decoded:
         Destroyed      -> gotoEngineering + engCooling + engBusMode
         CoolantLeaking -> gotoEngineering + engCooling
         AmmoBurning    -> gotoEngineering + engEject
         Jammed         -> gotoEngineering + engEject
         BadPower       -> gotoEngineering + engBusMode
  -> BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem -- THE REAL BODY, from
     @004cc2fc + helpers @004cc108/148/1a0/264/27c + tables @0051cf1c..0x51d084
     (gotoEngineering 0x80 = the subsystem's QUAD-SELECT bezel button via
     lamp[aux]/mode[aux] tables; 0x81+ descend the eng-page bank; Condenser /
     Generator specials on CoolantLeaking; <0x80 = the heat-bank fixed map).
     btl4galm.cpp's old bodies were admitted fabrications and its provenance
     note ("no override body exists in the image") was wrong -- corrected.
  -> LampManager::FindLamp (@00444c80) -> Lamp::SetAlertState (@00444e64, a
     COUNTER so stacked alarms hold the flash) -> L4Lamp::NotifyOfStateChange
     emits RIO flashFast states 0x37/0x13 (== T0 L4LAMP.cpp:234-239) -> the
     pod's physical lamps AND the glass panels (PadRIO IS rioPointer there).

FOUR load-bearing defects found and fixed en route -- each independently fatal
to the feature:

  1. HeatableSubsystem's Derivation chained Subsystem directly, SKIPPING
     MechSubsystem (written before the WAVE-1 re-basing) -- so EVERY subsystem
     on both family branches failed IsDerivedFrom(MechSubsystem), which is
     exactly MechTech's monitor filter: the status scan watched NOTHING.
  2. MechSubsystem::GetStatusFlags was non-virtual (binary: vtable slot 12) --
     the scan's MechSubsystem* call bound statically to the base tier and the
     weapon bits could never surface.
  3. ProjectileWeapon::GetStatusFlags sat in a Ghidra export gap -- raw-disasm
     @004bbf88: base | 0x20 (weaponAlarm==5 Jammed) | 0x10 (linked bin
     cookOffArmed@0x18C AmmoBurning).  The port had "defer to base".
  4. Mech::Reset's respawn sweep blanket-cast every roster entry to
     MechSubsystem and called RespawnRepair -- wrong for the Subsystem-level
     entries (MechTech 0xBDC, SubsystemMessageManager 0xBD3).  On MechTech the
     recon damageZone slot lands on its subsystemMonitors chain head: NULL
     while the scan was broken (fix #1's bug MASKED this one), but the moment
     the monitors populated, RespawnRepair virtual-called through a
     SubsystemMonitor as if it were a DamageZone -> load-time crash in
     StateIndicator::SetState (caught with cdb; call [edx+14h] on code bytes).
     The sweep now filters IsDerivedFrom(MechSubsystem) before the cast.

Also: GUID identities pinned -- 0x50f4bc = PoweredSubsystem::ClassDerivations
(via @004b1208 = its TestInstance; btl4gau2's two "Generator" comments were
wrong, swept), 0x50fb60 = Generator's.  A shadow-field instance documented for
the deferred de-shadow: MechSubsystem::damageZone re-declares the PUBLIC engine
Subsystem::damageZone (SUBSYSTM.h:159) -- mechtech's naive `sub->damageZone`
read the never-written engine member (0 monitors again); now reads the recon
member via GetDamageZoneProxy().  Logged in open-questions.

Wiring: BTL4GaugeRenderer now constructs + assigns the BTL4GaugeAlarmManager
(the base ctor NULLs it and Activate Check()s it); MechTech's Report*/
StatusMessageSink stubs are real; alarmModel confirmed baked (= 83) at runtime.

VERIFIED LIVE (scratchpad/lampflash.py, BT_LAMP_LOG chain trace):
    [techstat] MechTech id 32 monitors 29 subsystems, alarmModel 83
    [techstat] LRM15_1 condition 4 SET (alarmModel 83)      <- bay fire armed
    [galarm] condition 4 code 0x80 -> lamp 0xd mode 0x1 FLASH
    [lamp] 0xd <- 0x37  (FLASHING)                          <- the select button
    [galarm] condition 4 code 0x85 -> lamp 0xb mode 0x4 FLASH   <- engEject
    [techstat] LRM15_1 condition 4 CLEARED                  <- detonation clears
    [techstat] AmmoBinLRM15_1 condition 0 SET               <- bin Destroyed
Regressions: baytest PASS (bay fire kills), baypurge PASS (purge extinguishes),
sim3 3-pod brawl PASS with ZERO crashes (6 kills, organic heat-route bay fires,
respawns clean through the new sweep filter).

Env: BT_LAMP_LOG ([techstat]/[galarm]/[lamp]).  KB: gauges-hud (the flash
section), decomp-reference (the GaugeAlarm closure + tables + GUIDs),
open-questions (built-note + the de-shadow deferred item), btl4gau2 comment
sweep.  checkctx CLEAN.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-25 16:04:34 -05:00
co-authored by Claude Fable 5
parent 5ae4410914
commit 6f5a264835
14 changed files with 556 additions and 122 deletions
+64 -16
View File
@@ -207,10 +207,17 @@ MechTech::MechTech(
for (int seg = entity->GetSubsystemCount() - 1; seg >= 0; --seg)
{
Subsystem *sub = entity->GetSubsystem(seg);
// #47 SHADOW-FIELD TRAP (gotcha #1): the naive `sub->damageZone`
// resolves to the PUBLIC engine Subsystem::damageZone
// (SUBSYSTM.h:159) -- which the reconstruction NEVER WRITES; the
// recon zone lives on the re-declared MechSubsystem member @0xE0.
// Testing the engine one rejected every subsystem (0 monitors).
// Read the recon member through its accessor. (The re-declaration
// itself is logged in open-questions as a deferred de-shadow.)
if (
sub != 0
&& sub->IsDerivedFrom(MechSubsystem::ClassDerivations) // FUN_0041a1a4(**sub[3], 0x50de2c)
&& sub->damageZone != 0 // damageable segment (segment bit 0x8)
&& ((MechSubsystem *)sub)->GetDamageZoneProxy() != 0 // damageable segment (segment bit 0x8)
)
{
SubsystemMonitor *monitor =
@@ -218,6 +225,21 @@ MechTech::MechTech(
subsystemMonitors.Add(monitor); // (*this[0x39].vt[1])(...)
}
}
// #47 diag: how many subsystems the status scan actually watches, and
// which alarm table it will Activate with. A monitor count of ~2 here
// betrays the roster-order hazard (the ctor walk sees only the slots
// built BEFORE MechTech in the stream).
if (getenv("BT_LAMP_LOG"))
{
int n = 0;
ChainIteratorOf<SubsystemMonitor*> it(&subsystemMonitors);
for (it.First(); it.GetCurrent() != 0; it.Next()) ++n;
DEBUG_STREAM << "[techstat] MechTech id " << subsystem_id
<< " monitors " << n << " subsystems (roster count "
<< entity->GetSubsystemCount() << "), alarmModel "
<< alarmModel << "\n" << std::flush;
}
}
else
{
@@ -462,12 +484,21 @@ Logical
//
//
// @0050e248-era global: the status / MFD message sink == *(DAT_004efc94 + 0x38).
// @0050e248-era global: the status / MFD message sink == *(DAT_004efc94 + 0x38)
// = the RENDERER MANAGER whose renderer chain (+0x10) FUN_004364e4 broadcasts
// Start/StopEntityAlarm messages to (ids 7/8, sizes 0x20/0x1C).
//
// PORT SHAPE (Gitea #47): the port has no renderer-manager broadcast -- the only
// alarm consumer is the gauge renderer (Renderer::Start/StopEntityAlarmMessage-
// Handler just forwards to Start/StopEntityAlarmImplementation, RENDERER.cpp:574/
// 601, both T0) -- so the sink is the gauge renderer and Report* below call the
// implementations directly. Faithful semantics, direct delivery (the same shape
// the port uses for StartEntityEffect).
//
void*
MechTech::StatusMessageSink()
{
return 0; // TODO(cross-family): *(theApplication + 0x38) -- HUD status sink
return (application != 0) ? (void *)application->GetGaugeRenderer() : 0;
}
//
@@ -480,32 +511,49 @@ Time
}
//
// FUN_004366b8 (build "status cleared" message) + FUN_004364e4 (dispatch).
// FUN_004366b8 = Renderer__StopEntityAlarmMessage ctor (id 8, size 0x1C) +
// FUN_004364e4 = the renderer broadcast. Port shape: direct implementation
// call on the gauge renderer (see StatusMessageSink). [Gitea #47]
//
void
MechTech::ReportStatusCleared(
void * /*sink*/,
Mech * /*owner*/,
MechSubsystem * /*sub*/,
int /*type*/
void *sink,
Mech *owner,
MechSubsystem *sub,
int type
)
{
// TODO(cross-family): build + dispatch the cockpit "status cleared" message.
if (getenv("BT_LAMP_LOG"))
DEBUG_STREAM << "[techstat] " << (sub ? sub->GetName() : "?")
<< " condition " << type << " CLEARED\n" << std::flush;
if (sink != 0)
((GaugeRenderer *)sink)->StopEntityAlarmImplementation(
owner, sub, (Enumeration)type);
}
//
// FUN_00436688 (build "status set" message, carries alarmModel) + FUN_004364e4.
// FUN_00436688 = Renderer__StartEntityAlarmMessage ctor (id 7, size 0x20:
// {msg 0xC | EntityID 8 | subsystem | condition | resourceID}) + FUN_004364e4.
// resourceID = MechTech's alarmModel (this+0x100) -- the 'mechalrm' ModelList
// (id 83) whose list resolves to the type-31 GaugeAlarmStream (id 331) inside
// GaugeAlarmManager::Activate's SearchList. [Gitea #47]
//
void
MechTech::ReportStatusSet(
void * /*sink*/,
Mech * /*owner*/,
MechSubsystem * /*sub*/,
int /*type*/,
ResourceDescription::ResourceID /*alarm_model*/
void *sink,
Mech *owner,
MechSubsystem *sub,
int type,
ResourceDescription::ResourceID alarm_model
)
{
// TODO(cross-family): build + dispatch the cockpit "status set" message.
if (getenv("BT_LAMP_LOG"))
DEBUG_STREAM << "[techstat] " << (sub ? sub->GetName() : "?")
<< " condition " << type << " SET (alarmModel " << alarm_model
<< ")\n" << std::flush;
if (sink != 0)
((GaugeRenderer *)sink)->StartEntityAlarmImplementation(
owner, sub, (Enumeration)type, alarm_model);
}
//===========================================================================//