Files
BT411/engine/MUNGA_L4/L4LAMP.cpp
T
arcattackandClaude Fable 5 6f5a264835 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>
2026-07-25 16:04:34 -05:00

403 lines
9.0 KiB
C++

#include "mungal4.h"
#pragma hdrstop
#define PRELOAD_ART
#include "..\munga\vdata.h"
#include "l4lamp.h"
#include "l4wrhous.h"
// #define LOCAL_TEST
#if defined(LOCAL_TEST)
# define Test_Tell(n) cout << n
#else
# define Test_Tell(n)
#endif
//#########################################################################
//############################# L4LampManager #############################
//#########################################################################
L4LampManager::L4LampManager(LBE4ControlsManager *controls_manager):
LampManager()
{
Check(controls_manager);
controlsManager = controls_manager;
previousLampState = new int[LBE4ControlsManager::LampCount];
Register_Pointer(previousLampState);
lampActiveFlag = new Logical[LBE4ControlsManager::LampCount];
Register_Pointer(lampActiveFlag);
//-----------------------------------------
// Clear L4 lamp state tables
//-----------------------------------------
int
i;
Check_Pointer(previousLampState);
for(i=0; i< LBE4ControlsManager::LampCount; ++i)
{
previousLampState[i] = RIO::solid + RIO::state1Off + RIO::state2Off;
}
Check_Fpu();
}
L4LampManager::~L4LampManager()
{
Check(this);
Unregister_Pointer(previousLampState);
delete[] previousLampState;
previousLampState = NULL;
Unregister_Pointer(lampActiveFlag);
delete[] lampActiveFlag;
lampActiveFlag = NULL;
Check_Fpu();
}
Logical
L4LampManager::TestInstance() const
{
Check_Fpu();
return LampManager::TestInstance();
}
void
L4LampManager::Update(ModeMask current_mode_mask)
{
Check(this);
Logical
flush_inactive_lamps = (current_mode_mask != previousModeMask);
int
i;
if (flush_inactive_lamps)
{
//---------------------------------------
// Mark all lamps as inactive
//---------------------------------------
Check_Pointer(lampActiveFlag);
for(i=0; i< LBE4ControlsManager::LampCount; ++i)
{
lampActiveFlag[i] = False;
}
}
//---------------------------------------
// Update LampManager, set lamp outputs,
// and mark active lamps
//---------------------------------------
LampManager::Update(current_mode_mask);
if (flush_inactive_lamps)
{
//---------------------------------------
// Turn off inactive lamps
//---------------------------------------
Check_Pointer(lampActiveFlag);
for(i=0; i< LBE4ControlsManager::LampCount; ++i)
{
if (lampActiveFlag[i] == False)
{
AssertNewLampValue(i, RIO::solid+RIO::state1Off+RIO::state2Off);
}
}
}
Check_Fpu();
}
void
L4LampManager::AssertNewLampValue(int lamp_number, int lamp_value)
{
Check(this);
Verify(lamp_number >= 0);
Verify(lamp_number < LBE4ControlsManager::LampCount);
//--------------------------------------
// Update external hardware
//--------------------------------------
Check_Pointer(previousLampState);
if (previousLampState[lamp_number] != lamp_value)
{
previousLampState[lamp_number] = lamp_value;
// (Gitea #47 diag) BT_LAMP_LOG traces every lamp-state change ABOVE the
// RIO seam -- on a desktop build without a RIO/PadRIO, SetLamp below
// no-ops (rioPointer NULL, L4CTRL.cpp:1174), so this line is the only
// observable. Flash states carry low bits 1-3 (flashSlow/Med/Fast).
{
static int s_log = -1;
if (s_log < 0) s_log = (getenv("BT_LAMP_LOG") != 0) ? 1 : 0;
if (s_log)
DEBUG_STREAM << "[lamp] 0x" << std::hex << lamp_number
<< " <- 0x" << lamp_value << std::dec
<< (((lamp_value & 3) != 0) ? " (FLASHING)" : "")
<< "\n" << std::flush;
}
//--------------------------------------
// Send RIO command to update L4 lamp
//--------------------------------------
Check(controlsManager);
controlsManager->SetLamp(lamp_number, lamp_value);
}
Check_Fpu();
}
//#########################################################################
//################################ L4Lamp #################################
//#########################################################################
L4Lamp::L4Lamp(
LampID lamp_number,
ModeMask mode_mask,
L4LampManager *lamp_manager
) :
Lamp(lamp_number, mode_mask, (LampManager *) lamp_manager)
{
automatic = True;
automaticValue = (ControlsButton) 0;
previousAutomaticValue = automaticValue;
SetState(LampStateDim);
Check_Fpu();
}
L4Lamp::~L4Lamp()
{
Check(this);
SetState(LampStateOff);
Check_Fpu();
}
Logical
L4Lamp::TestInstance() const
{
return Lamp::TestInstance();
}
void
L4Lamp::SetAutomaticOperation(Logical automatic_flag)
{
Check(this);
automatic = automatic_flag;
Check_Fpu();
}
void
L4Lamp::Update(Logical force_notification)
{
Check(this);
//--------------------------------------
// Tell manager we're still active
//--------------------------------------
if (lampID >= 0)
{
if (lampID < LBE4ControlsManager::LampCount)
{
Check_Pointer(((L4LampManager *)manager)->lampActiveFlag);
((L4LampManager *)manager)->lampActiveFlag[lampID] = True;
}
}
//--------------------------------------
// Update state if automatic
//--------------------------------------
if (automatic)
{
if (previousAutomaticValue != automaticValue)
{
previousAutomaticValue = automaticValue;
if (previousAutomaticValue > 0)
{
SetState(LampStateOn);
}
else
{
SetState(LampStateDim);
}
}
else if (force_notification)
{
NotifyOfStateChange();
}
}
else if (force_notification)
{
NotifyOfStateChange();
}
Check_Fpu();
}
void
L4Lamp::NotifyOfStateChange()
{
Check(this);
Check((L4LampManager *) manager);
int
output_value;
if (alertActive)
{
switch(previousState)
{
default:
output_value = RIO::flashFast + RIO::state1Off + RIO::state2Dim;
break;
case LampStateDim:
case LampStateOn:
output_value = RIO::flashFast + RIO::state1Dim + RIO::state2Bright;
break;
}
}
else
{
switch(previousState)
{
default:
output_value = RIO::solid + RIO::state1Off + RIO::state2Off;
break;
case LampStateDim:
output_value = RIO::solid + RIO::state1Dim + RIO::state2Dim;
break;
case LampStateOn:
output_value = RIO::solid + RIO::state1Bright + RIO::state2Bright;
break;
}
}
((L4LampManager *) manager)->AssertNewLampValue(lampID, output_value);
Check_Fpu();
}
//#########################################################################
//############################# L4VirtualLamp #############################
//#########################################################################
L4GraphicLamp::L4GraphicLamp(
LampID lamp_id,
ModeMask mode_mask,
GaugeRenderer *the_renderer,
int graphics_port_number,
int left, int bottom,
const char *map_name,
int bg_color, int fg_color,
Logical opaque_flag
) :
GraphicLamp(
lamp_id,
mode_mask,
the_renderer->GetLampManager(),
the_renderer,
graphics_port_number
)
{
//--------------------------------------------
// Build copy of map name
//--------------------------------------------
Check_Pointer(map_name);
mapName = new char[strlen(map_name)+1];
Register_Pointer(mapName);
strcpy(mapName, map_name);
//--------------------------------------------
// Get the image
//--------------------------------------------
# if defined(PRELOAD_ART)
Check(renderer);
L4Warehouse
*warehouse = (L4Warehouse *) renderer->warehousePointer;
Check(warehouse);
warehouse->bitMapBin.Get(mapName);
# endif
opaque = opaque_flag;
bgColor = bg_color;
fgColor = fg_color;
localView.SetOrigin(left, bottom);
Check_Fpu();
}
L4GraphicLamp::~L4GraphicLamp()
{
Check(this);
Check_Pointer(mapName);
//--------------------------------------------
// Release the preloaded image
//--------------------------------------------
# if defined(PRELOAD_ART)
Check(renderer);
L4Warehouse
*warehouse = (L4Warehouse *) renderer->warehousePointer;
Check(warehouse);
warehouse->bitMapBin.Release(mapName);
# endif
//--------------------------------------------
// Delete the copy of the name
//--------------------------------------------
Check_Pointer(mapName);
delete[] mapName;
mapName = NULL;
Check_Fpu();
}
Logical
L4GraphicLamp::TestInstance() const
{
return GraphicLamp::TestInstance();
}
void
L4GraphicLamp::NotifyOfStateChange()
{
Check(this);
//--------------------------------------------
// Get the bitmap
//--------------------------------------------
Check(renderer);
L4Warehouse
*warehouse = (L4Warehouse *) renderer->warehousePointer;
Check(warehouse);
BitMap
*bitmap = warehouse->bitMapBin.Get(mapName);
if (bitmap != NULL)
{
Check(bitmap);
//--------------------------------------------
// Set the color
//--------------------------------------------
if (previousState <= LampStateOff)
{
localView.SetColor(bgColor);
}
else
{
localView.SetColor(fgColor);
}
//--------------------------------------------
// Draw the bitmap
//--------------------------------------------
if (opaque)
{
localView.DrawBitMapOpaque(bgColor, 0, bitmap);
}
else
{
localView.DrawBitMap(0, bitmap);
}
//--------------------------------------------
// Release the bitmap
//--------------------------------------------
Check(warehouse);
warehouse->bitMapBin.Release(mapName);
}
Check_Fpu();
}