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>
403 lines
9.0 KiB
C++
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();
|
|
}
|