Files
BT411/game/reconstructed/btl4grnd.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

423 lines
18 KiB
C++

//===========================================================================//
// File: btl4grnd.cpp //
// Project: BattleTech Brick: Gauge Renderer Manager //
// Contents: BTL4GaugeRenderer -- the BattleTech L4 (cockpit) gauge renderer //
// manager. Owns the gauge warehouse + lamp manager, builds the //
// gauge configuration from gauge\l4gauge.cfg, and routes //
// interesting/uninteresting world entities into the moving/static //
// draw lists. //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 02/13/95 CPB Initial coding. //
//---------------------------------------------------------------------------//
// Copyright (C) 1994, Virtual World Entertainment, Inc. All rights reserved //
// PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary. Behaviour follows the Ghidra
// pseudo-C in the bt_l4 cluster (part_014.c); class/member/method names are
// taken from the surviving BTL4GRND.HPP and from the direct Red Planet
// analogue RP_L4/RPL4GRND.cpp (RPL4GaugeRenderer). Each method cites the
// originating @ADDR.
//
// NOTE ON THE FILE NAME: despite the "...GRND" name, this brick is the
// *gauge* renderer manager (HUD/cockpit), NOT a 3D ground/world renderer.
// The surviving header comment even calls the brick "Gauge Renderer
// Manager", and the only class it declares is BTL4GaugeRenderer. The
// out-the-window 3D world view is assembled by BTL4VID (btl4vid.cpp,
// @004cdac0..) -- a separate translation unit.
//
// Helper-function name mapping (engine internals referenced by the decomp):
// FUN_0046f97c L4GaugeRenderer base constructor
// FUN_0046fbcc L4GaugeRenderer::~L4GaugeRenderer (chain)
// FUN_0046fd6c L4GaugeRenderer::NotifyOfNewInterestingEntity
// FUN_0046fda0 L4GaugeRenderer::NotifyOfBecomingUninterestingEntity
// FUN_00447b60 GaugeRenderer::TestInstance
// FUN_00447fc0 GaugeRenderer::Remove(0) (purge all gauges)
// FUN_00448928 GaugeRenderer::BuildConfigurationFile(cfg, methods)
// FUN_00407064 ResourceFile::SearchList(resourceID, type)
// FUN_0041a1a4 Object::IsDerivedFrom(ClassDerivations&)
// FUN_004434f4 movingEntities.Add(entity) (this+0x94)
// FUN_00443510 movingEntities.Remove(entity)
// FUN_00443864 staticEntities.Add(entity) (this+0xb0)
// FUN_00443930 staticEntities.Remove(entity)
// FUN_00448c54 L4LampManager base constructor
// FUN_00468778 L4Warehouse constructor
// FUN_00474c4c (controls/secondary-palette helper ctor, app-derived)
// FUN_004022d0 operator delete / Unregister+free
// FUN_00402298 operator new
// DAT_004efc94 the global Application* (application)
//
// Inherited members used below live in L4GaugeRenderer / GaugeRenderer
// (l4grend.hpp / gaugrend.hpp), NOT in this subclass -- which is why the
// surviving header declares no data members:
// warehousePointer this[0x13] (@0x4c) -- L4Warehouse*
// lampManager this[0x7033] -- L4LampManager*
// movingEntities @0x94 -- list of Movers
// staticEntities @0xb0 -- list of Terrain
//
#include <bt.hpp>
#pragma hdrstop
#if !defined(BTL4GRND_HPP)
# include <btl4grnd.hpp>
#endif
#if !defined(BTL4GAUG_HPP)
# include <btl4gaug.hpp> // the BT gauge classes (Compass, etc.)
# include <btl4gau2.hpp> // the composite cluster gauges + VehicleSubSystems
# include <btl4rdr.hpp> // MapDisplay (the "map" radar gauge)
# include <btl4gau3.hpp> // PlayerStatus (the comm/score gauge)
#endif
#if !defined(L4LAMP_HPP)
# include <l4lamp.hpp>
#endif
#include <GAUGALRM.hpp> // GaugeAlarmManager (TearDownForViewpointRestream, Gitea #12)
#include <btl4galm.hpp> // BTL4GaugeAlarmManager (the #47 override -- constructed in the ctor)
#if !defined(L4WARE_HPP)
# include <l4ware.hpp>
#endif
#if !defined(TERRAIN_HPP)
# include <terrain.hpp>
#endif
#if !defined(APP_HPP)
# include <app.hpp>
#endif
//
//#############################################################################
// BTL4MethodDescription list
//#############################################################################
//
// Passed to GaugeRenderer::BuildConfigurationFile() so the config-file
// interpreter can instantiate gauges by name. Mirrors RPL4MethodDescription
// in RPL4GRND.cpp (which lists &Compass::methodDescription,
// &ThreatIndicator::methodDescription, ...). The concrete BT gauge classes
// are defined in btl4gaug.cpp; the table itself is the .data block at
// 0051c910 and chains to the L4 base table.
//
// Recovered table entries (@0051c910, in order) -- MethodDescription* each;
// concrete gauge class names TODO (resolve against btl4gaug.cpp):
// 0x517ea4 0x51a2b0 0x5160ac 0x517614 0x5142b4 0x514b44
// 0x514f8c 0x5146fc 0x5153d4 0x51581c 0x515c64 0x5164f4
// 0x51693c 0x5171cc 0x5138f4 0x51a6f8 (one of the last two is the
// BTL4ChainToPrevious chain)
//
extern
MethodDescription
*L4MethodDescription[];
MethodDescription
BTL4ChainToPrevious = METHOD_DESCRIPTION_CHAIN(L4MethodDescription);
extern
MethodDescription
*BTL4MethodDescription[]; // defined in .data @0051c910
// TODO(bring-up): minimal reconstruction of the BT gauge method table. The
// real .data table @0051c910 listed the BT-specific gauge methodDescription
// entries (Compass/ThreatIndicator/... analogues, see RPL4MethodDescription);
// those gauge classes' methodDescription members are not all reconstructed yet,
// so for the first link this table contains only the chain-to-previous link,
// which routes config lines to the engine's base L4MethodDescription methods.
MethodDescription
*BTL4MethodDescription[] =
{
//
// BT gauge WIDGET reconstruction -- register each BT-specific gauge
// class's methodDescription so the config interpreter can build it
// (the CFG keyword -> class Make). Add ONE at a time, only when its
// whole Make->ctor->feed chain is REAL code (a prose-only/undefined
// link would be /FORCE-stubbed and AV at runtime; see CLAUDE.md §10).
// The chain-to-previous link MUST stay LAST (routes unmatched keywords
// to the engine base table L4MethodDescription).
//
&ColorMapperHeat::methodDescription, // "cmHeat" -- heat-driven palette tint
&HeadingPointer::methodDescription, // "headingPointer" -- compass needle + heading
&ColorMapperArmor::methodDescription, // "cmArmor" -- per-zone armor-damage tint
&ColorMapperMultiArmor::methodDescription, // "colorMapperMultiArmor" -- worst-of-8-zones tint
&ColorMapperCritical::methodDescription, // "cmCrit" -- subsystem-critical-state tint
&VertTwoPartBar::methodDescription, // "vertBar" -- vertical fill bar (coolant/heat)
&SegmentArcRatio::methodDescription, // "segmentArcRatio" -- segmented arc dial (speed)
&OneOfSeveralPixInt::methodDescription, // "oneOfSeveralPixInt" -- button-state lamp (duck/light/mode)
&MapDisplay::methodDescription, // "map" -- the radar / tactical display
&PlayerStatus::methodDescription, // "PlayerStatus" -- comm/score name-tag gauge
&VehicleSubSystems::methodDescription, // "vehicleSubSystems" -- engineering-screen subsystem cluster panels
&BitMapInverseWipe::methodDescription, // "LeakGauge" -- coolant-leak inverse-wipe (Condenser/CoolantMassLeakRate)
&VertNormalSlider::methodDescription, // "vertNormalSlider" -- condenser valve-setting slider (heat MFD)
&PilotList::methodDescription, // "pilotList" -- Comm KILLS/DEATHS pilot roster
&GeneratorCluster::methodDescription, // "GeneratorCluster" -- the 4 generator engineering panels (buttons 9-12)
&SectorDisplay::methodDescription, // "sectorDisplay" -- radar SECTOR X/Z read-out (Secondary overlay)
&PrepEngrScreen::methodDescription, // "prepEngr" -- per-Eng-screen static label overlay (12x)
&MessageBoard::methodDescription, // "messageBoard" -- sec-MFD comm/status ticker (DEFERRED source -> empty)
&BTL4ChainToPrevious
};
//
//#############################################################################
// BTL4GaugeRenderer
//#############################################################################
//
// @004cbea0 (vtable 0051cebc)
//
// Parallels RPL4GaugeRenderer::RPL4GaugeRenderer: chain to the L4 base, build
// the lamp manager and gauge warehouse, then drive BuildConfigurationFile
// over gauge\l4gauge.cfg using the BTL4MethodDescription table.
//
BTL4GaugeRenderer::BTL4GaugeRenderer()
:
//------------------------------------------------------------------
// In the shipped BT binary the base ctor (FUN_0046f97c) was called
// with `this` only -- the original L4GaugeRenderer took no arguments
// and BT never drove the secondary-palette flashing path (unlike RP,
// which calls SVGA16::FlashPalette in its ctor). The modernized
// WinTesla engine grew the signature
// L4GaugeRenderer(bool windowed, int*, int*, int*)
// to support windowed/secondary-palette modes. The pod runs
// fullscreen and BT supplies no secondary/aux gauge indices, so the
// faithful translation passes windowed=false and null index pointers.
//------------------------------------------------------------------
L4GaugeRenderer(false, NULL, NULL, NULL) // FUN_0046f97c (orig: no args)
{
Check_Pointer(this);
//----------------------------------------
// Create lamp manager
// alloc 0x14; L4LampManager() @004c8c54; the object is then given the
// BT-specific lamp-name/index vtable (0051ceb0) so config lines like
// "coolingLoop1" resolve to the right lamp -- see btl4gaug.cpp.
//
// The shipped binary built the lamp manager with no argument
// (FUN_00448c54(obj)); the WinTesla L4LampManager ctor now requires
// the LBE4ControlsManager, fetched from the application exactly as
// the RP analogue (RPL4GRND.cpp) does. The application[0x3c] slot
// the decomp reads to seed the secondary helper IS this controls
// manager (Application::GetControlsManager()).
//----------------------------------------
Check(application);
LBE4ControlsManager
*controls_manager = (LBE4ControlsManager *)
(application->GetControlsManager());
Check(controls_manager);
lampManager = new L4LampManager(controls_manager); // this[0x7033]
Register_Object(lampManager);
//----------------------------------------
// Gauge-alarm manager (Gitea #47): the BTL4 override whose
// Read/CreateGaugeAlarmStreamItem bodies live at @004cc2fc/@004cc294.
// The base GaugeRenderer ctor NULLs gaugeAlarmManager (GAUGREND.cpp:2841)
// and Activate/Deactivate Check() it -- without this, MechTech's
// Start/StopEntityAlarm delivery (the jam / bay-fire ENG-button flash)
// had no receiver.
//----------------------------------------
gaugeAlarmManager = new BTL4GaugeAlarmManager();
Register_Object(gaugeAlarmManager);
//----------------------------------------
// Secondary / controls helper bound to the application
// alloc 0x44; FUN_00474c4c(application[0x3c]). this[0x7032].
//----------------------------------------
// (BT-specific; see ctor @004cbea0)
//----------------------------------------
// Create warehouse
//----------------------------------------
warehousePointer = new L4Warehouse(); // alloc 0x74; FUN_00468778; this[0x13]
Register_Object(warehousePointer);
//----------------------------------------
// Initialize interpreter
//----------------------------------------
BuildConfigurationFile( // FUN_00448928
"gauge\\l4gauge.cfg", // @0051ce9e
BTL4MethodDescription
);
Check_Fpu();
}
//
//#############################################################################
// ~BTL4GaugeRenderer
//#############################################################################
//
// @004cbf40 (vtable slot 0 -- scalar/vector deleting destructor)
//
BTL4GaugeRenderer::~BTL4GaugeRenderer()
{
Check(this);
//----------------------------------------
// Make sure all gauges removed BEFORE we
// destroy the warehouse!!
//----------------------------------------
Remove(0); // FUN_00447fc0(this, 0)
//----------------------------------------
// destroy warehouse
//----------------------------------------
Check(warehousePointer); // this[0x13]
Unregister_Object(warehousePointer);
delete warehousePointer; // (**warehousePointer)(warehousePointer, 3)
warehousePointer = NULL;
Check_Fpu();
// base ~L4GaugeRenderer (FUN_0046fbcc) runs on chain-out.
}
//
//#############################################################################
// TestInstance
//#############################################################################
//
// @004cbf90
//
Logical
BTL4GaugeRenderer::TestInstance() const
{
return GaugeRenderer::TestInstance(); // FUN_00447b60
}
//
//#############################################################################
// TearDownForViewpointRestream (PORT FIX -- Gitea #12)
//#############################################################################
//
// The in-session mech RE-STREAM (a pre-launch host drop/rejoin re-creates all
// mechs at mission launch) frees the mech the LOBBY-built gauge tree was bound
// to; every gauge Execute then AVs on its dangling attribute pointers and the
// BT_DEV_GAUGES SEH guard (Gauge::GuardedExecute, GAUGE.cpp:618) disables each
// one PERMANENTLY (rate=0, nothing rebinds) -- the frozen dev-gauges window of
// incident #12. The AUTHENTIC engine flow only tears the tree down at mission
// transitions (Application::Shutdown -> gaugeRenderer->Shutdown() ->
// ShutdownImplementation -> Remove(0), APP.cpp:787 / GAUGREND.cpp:3264); the
// re-stream path bypasses it. This method performs the ENTITY-BOUND half of
// that same ShutdownImplementation sequence, in the same order:
// gaugeAlarmManager->RemoveAllAlarms() (alarms hold entity/subsystem ptrs)
// Remove(0) (delete every gauge, active+inactive)
// movingEntities/staticEntities.Clear() (raw Entity* grids for radar/map)
// and deliberately SKIPS the mission-scoped steps -- warehouse purge, graphics
// port destruction, lampManager->RemoveAllLamps() (the L4Lamps are owned by the
// CONTROLS layer, which registers &lamp->automaticValue into its buttonGroup --
// deleting them mid-session would dangle the button mappings; the mech-state
// "lamps" like AnimatedSubsystemLamp are Gauges and go with Remove(0)). The
// caller (MakeViewpointEntity) immediately re-runs ConfigureForModel("Init",
// <new mech>) against the still-initialized interpreter/warehouse, so the tree
// rebuilds bound to the NEW mech -- the gauge renderer builds lazily by design.
//
void
BTL4GaugeRenderer::TearDownForViewpointRestream()
{
Check(this);
if (gaugeAlarmManager != NULL)
{
Check(gaugeAlarmManager);
gaugeAlarmManager->RemoveAllAlarms();
}
Remove(0); // FUN_00447fc0(this, 0) -- all gauges
movingEntities.Clear();
staticEntities.Clear();
Check_Fpu();
}
//
//#############################################################################
// NotifyOfNewInterestingEntity
//#############################################################################
//
// @004cbfa0 (vtable slot 9)
//
void
BTL4GaugeRenderer::NotifyOfNewInterestingEntity(Entity *entity)
{
Check(this);
Check(entity);
//------------------------------------------------------
// If the entity has no gauge representation, discard it
//------------------------------------------------------
Check(application);
Check(application->GetResourceFile());
ResourceDescription
*resource_description =
application->GetResourceFile()->SearchList( // FUN_00407064
entity->GetResourceID(), // entity[0x1bc]
ResourceDescription::GaugeImageStreamResourceType // 0x12
);
if (resource_description != NULL)
{
//
//---------------------------------------------------------
// Place object in either moving list or static array
//---------------------------------------------------------
//
if (entity->IsDerivedFrom(*Mover::GetClassDerivations())) // 0x4e4518
{
movingEntities.Add(entity); // FUN_004434f4, this+0x94
}
else if (entity->IsDerivedFrom(*Terrain::GetClassDerivations())) // 0x4e6ef8
{
staticEntities.Add(entity); // FUN_00443864, this+0xb0
}
}
//------------------------------------------------------
// Chain to "ancestor" method
//------------------------------------------------------
L4GaugeRenderer::NotifyOfNewInterestingEntity(entity); // FUN_0046fd6c
}
//
//#############################################################################
// NotifyOfBecomingUninterestingEntity
//#############################################################################
//
// @004cc028 (vtable slot 10)
//
void
BTL4GaugeRenderer::NotifyOfBecomingUninterestingEntity(Entity *entity)
{
Check(this);
Check(entity);
//------------------------------------------------------
// If the entity has no gauge representation, discard it
//------------------------------------------------------
Check(application);
Check(application->GetResourceFile());
ResourceDescription
*resource_description =
application->GetResourceFile()->SearchList( // FUN_00407064
entity->GetResourceID(),
ResourceDescription::GaugeImageStreamResourceType // 0x12
);
if (resource_description != NULL)
{
//---------------------------------------------------------
// Remove entity from either moving list or static array
//---------------------------------------------------------
if (entity->IsDerivedFrom(*Mover::GetClassDerivations())) // 0x4e4518
{
movingEntities.Remove(entity); // FUN_00443510, this+0x94
}
else if (entity->IsDerivedFrom(*Terrain::GetClassDerivations())) // 0x4e6ef8
{
staticEntities.Remove(entity); // FUN_00443930, this+0xb0
}
}
//------------------------------------------------------
// Chain to "ancestor" method
//------------------------------------------------------
L4GaugeRenderer::NotifyOfBecomingUninterestingEntity(entity); // FUN_0046fda0
}