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:
co-authored by
Claude Fable 5
parent
5ae4410914
commit
6f5a264835
@@ -352,10 +352,23 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
COUNTER at lamp+0x1C) → the L4 lamp flush `@00474e94` emits `0x37`/`0x13` = `flashFast` RIO
|
||||
states (== T0 L4LAMP.cpp:234-239) → `RIO::SetLamp` (binary `FUN_00476568`; cached wrapper
|
||||
`@00474d54`). T0-compiled already: LAMP.cpp / L4LAMP.cpp / GAUGALRM.cpp / RENDERER messages.
|
||||
**Missing in the port (3 pieces)**: the BTL4 override bodies (btl4galm.cpp is best-effort and
|
||||
its provenance note claiming "no override exists in the binary" is WRONG — @004cc148-2fc IS
|
||||
it), the gauge-watcher sender registration, and the lamp objects for the aux buttons. Plan in
|
||||
[[open-questions]].
|
||||
**✅ ALL THREE PIECES BUILT same day (see [[open-questions]] for the full closure note).**
|
||||
Corrections pinned while building: the SENDER is **`MechTech::TechnicalAssistance` @004ad33c**
|
||||
(not a StateIndicator gauge watcher) — the 7-condition `GetStatusFlags` edge-scan; message ctors
|
||||
`@00436688` (Start, id 7 size 0x20) / `@004366b8` (Stop, id 8 size 0x1C) / broadcast `@004364e4`
|
||||
(app+0x38 manager, renderer chain +0x10); `resourceID` = MechTech`+0x100` `alarmModel` = the
|
||||
**'mechalrm' ModelList id 83** → `SearchList(83, type 31)` → stream **331** (11 items, decoded:
|
||||
Destroyed→goto+cooling+busMode; CoolantLeaking→goto+cooling; AmmoBurning→goto+eject;
|
||||
Jammed→goto+eject; BadPower→goto+busMode). `@004bbf88` = ProjectileWeapon::GetStatusFlags
|
||||
(raw-disasm'd, export gap): base | 0x20 on `weaponAlarm==5` | 0x10 on linked-bin
|
||||
`cookOffArmed@0x18C`. GUID identities: **0x50f4bc = PoweredSubsystem** (via @004b1208 =
|
||||
its TestInstance; the old "Generator" comments were wrong — swept), **0x50fb60 = Generator**,
|
||||
0x50e4fc = Condenser. Lamp tables: names @0051cf1c (coolingLoop1-6, gotoEngineering 0x80,
|
||||
engGeneratorA-D 0x81-84, engEject 0x85, engBusMode 0x86, engCooling 0x87, engBackToQuad 0x88);
|
||||
quad lamps @0051cffc {0xF,0xD,0xB,0x9, 0x27,0x25,0x23,0x21, 0x7,0x5,0x3,0x1}[aux] with modes
|
||||
@0051cf9c; eng banks @0051d02c descend from {0xF/0x27/0x7} with modes @0051cfcc; condenser
|
||||
@0051d058[condenserNumber]; placement @0051d070[auxScreenPlacement] = {0x29,0x1A-0x1D}.
|
||||
Env: `BT_LAMP_LOG` = `[techstat]`/`[galarm]`/`[lamp]` — the full flash chain.
|
||||
- **MechRIOMapper** @0x51DD30: RE-REGISTERS the base ids 3..0x13 (Aux1Quad..ZoomOut) with the
|
||||
RIO override handlers @004d22fc..@004d24f8, + {0x19, Keypress→@004d2514} (RIO's OWN keypress,
|
||||
unreconstructed — we use the shared L4 0x17 @004d1bf0) + {0x1a, Hotbox→@004d2574} @0x51DE98.
|
||||
|
||||
+19
-2
@@ -360,8 +360,25 @@ reconstruction misread `bin+0x18C` as "the reload state" and bridged the icon to
|
||||
`BTAmmoBinFeeding` — so it blinked with every feed cycle and never lit on a bay fire
|
||||
(RajelAran: "it doesn't"). Now driven by `BTAmmoBinCookOffArmed/CookOffTime` complete-type
|
||||
bridges (ammobin.cpp). The member names `firing/reloading/reloadSeconds` in btl4gau2 are
|
||||
historic; semantically bayFireArmed / latch / secondsToDetonation. The ENG-BUTTON FLASH half
|
||||
of #47 (the GaugeAlarm/lamp machinery) is mapped-but-unbuilt — see [[open-questions]].
|
||||
historic; semantically bayFireArmed / latch / secondsToDetonation.
|
||||
|
||||
## The ENG-button ATTENTION FLASH (jam / bay fire) — BUILT (2026-07-25, #47) [T1 chain, T2 live]
|
||||
The pod's authored annunciator: on a subsystem condition (TechStatusType — Destroyed 0 / Damaged 1
|
||||
/ CoolantLeaking 2 / Overheating 3 / AmmoBurning 4 / Jammed 5 / BadPower 6) the affected
|
||||
subsystem's OWN bezel buttons flash. Chain: `MechTech::TechnicalAssistance` (@004ad33c, per-frame)
|
||||
edge-scans every monitored subsystem's `GetStatusFlags()` bitmask (now VIRTUAL — binary slot 12) →
|
||||
`Start/StopEntityAlarm` to the gauge renderer (port: direct implementation calls) →
|
||||
`GaugeAlarmManager::Activate(alarmModel=83 'mechalrm')` → the real `BTL4GaugeAlarmManager::
|
||||
ReadGaugeAlarmStreamItem` (btl4galm.cpp, from @004cc2fc) maps `{condition, lampCode}` items via
|
||||
the @0051cf1c vocabulary to bezel lamps (gotoEngineering = the subsystem's quad-select button;
|
||||
engEject/engBusMode/engCooling = the eng-page bank; per-condenser/per-generator specials) →
|
||||
`Lamp::SetAlertState` (a COUNTER — stacked alarms) → `L4Lamp` flushes `flashFast` RIO states
|
||||
(0x37/0x13) → the pod's physical lamps AND the glass panels (PadRIO IS the rioPointer in glass
|
||||
builds). The shipped 'mechalrm' table: Jammed/AmmoBurning → gotoEngineering + engEject (purge/
|
||||
unjam); Destroyed → +engCooling+engBusMode; CoolantLeaking → +engCooling; BadPower → +engBusMode.
|
||||
Verified live: bay fire → lamp 0xD (the LRM's select button) flashes 0x37 + engEject 0xB; clears
|
||||
on detonation/purge. Diagnostics: `BT_LAMP_LOG` → `[techstat]`/`[galarm]`/`[lamp]`. Details +
|
||||
the four load-bearing fixes en route: [[open-questions]] + [[decomp-reference]] §GaugeAlarm.
|
||||
|
||||
## ConfigMapGauge (the weapon panel's trigger-config joystick) — LIVE via LinkToEntity (2026-07-21)
|
||||
The per-weapon btjoy.pcc joystick image + 4 cm_* state lamps (off/other/only/both) showing,
|
||||
|
||||
@@ -209,7 +209,33 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
reconstruction that dispatches subsystem messages through a base pointer (the binary's crit
|
||||
path may) must first make the family virtual in one sweep (signatures must agree; some
|
||||
declare `Logical`, some `void`+int variants — audit before flipping).
|
||||
- **#47 remainder: the ENG-button attention FLASH (jam / bay fire) — machinery fully mapped,
|
||||
- **✅ #47 ENG-button attention FLASH BUILT same day (2026-07-25, evening).** All three pieces
|
||||
landed: the REAL `BTL4GaugeAlarmManager` bodies (btl4galm.cpp, from @004cc294/@004cc2fc + the
|
||||
lamp tables @0051cf1c..0x51d084), the sender (= `MechTech::TechnicalAssistance` @004ad33c —
|
||||
the "gauge watcher" guess was wrong; MechTech's 7-condition edge-scan builds
|
||||
`Start/StopEntityAlarmMessage` via @00436688/@004366b8 and broadcasts @004364e4; port shape =
|
||||
direct `Start/StopEntityAlarmImplementation` calls on the gauge renderer), and the lamps (the
|
||||
L4LampManager + streamed-mapping lamps already existed; the manager is now constructed +
|
||||
assigned in btl4grnd). Verified live end-to-end (`scratchpad/lampflash.py`): bay fire →
|
||||
`[techstat] LRM15_1 condition 4 SET (alarmModel 83)` → gotoEngineering lamp 0xD flashes 0x37 +
|
||||
engEject 0xB → detonation clears → the bin reports Destroyed. FOUR load-bearing fixes en
|
||||
route: `HeatableSubsystem`'s Derivation chained Subsystem directly (skipping MechSubsystem —
|
||||
the whole tree failed the monitor filter); `MechSubsystem::GetStatusFlags` made virtual (binary
|
||||
slot 12; static binding hid the weapon bits); `ProjectileWeapon::GetStatusFlags` raw-disasm'd
|
||||
from the @004bbf88 export gap (jam→0x20, bin cookOffArmed→0x10); and the Mech::Reset sweep's
|
||||
blanket `(MechSubsystem*)` cast filtered (it RespawnRepair'd MechTech through the wrong layout —
|
||||
benign only while the monitor chain head was NULL; #47 populating it exposed a load crash).
|
||||
Remaining risks: the wooHoo latch fields are still untested; FindLamp needs exact modeMask
|
||||
equality so unflashed lamps on rare pages would betray a mapping-stream mask mismatch.
|
||||
- **DEFERRED de-shadow: `MechSubsystem::damageZone` re-declares the PUBLIC engine
|
||||
`Subsystem::damageZone` (SUBSYSTM.h:159) — gotcha #1 (2026-07-25, found via #47).** All recon
|
||||
code coherently uses the derived member; the ENGINE base member stays NULL forever, and engine
|
||||
base paths that use it (`Subsystem::TakeDamage` derefs it unguarded!) would AV if ever reached.
|
||||
mechtech's monitor filter hit this (naive `sub->damageZone` resolved to the engine one → 0
|
||||
monitors). De-shadowing = make the recon write the ENGINE member and delete the re-declaration
|
||||
— a layout-neutral but call-site-wide sweep; do it deliberately, with the ReconDamageZone-proxy
|
||||
offset-0-vptr alias (#46's crash) retired in the same pass.
|
||||
- **(superseded) the original #47 remainder note follows for the record:** machinery fully mapped,
|
||||
3 pieces unbuilt (2026-07-25).** The authored data SHIPS: `BTL4.RES` carries one
|
||||
`GaugeAlarmStream` (type 31). The full chain + addresses: [[decomp-reference]] §GaugeAlarm.
|
||||
To make it live: (1) reconstruct the REAL `BTL4GaugeAlarmManager` override bodies from
|
||||
|
||||
@@ -121,6 +121,19 @@ void
|
||||
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
|
||||
//--------------------------------------
|
||||
|
||||
+220
-85
@@ -31,9 +31,9 @@
|
||||
// counter), and records the lamp on the GaugeAlarm's lampList.
|
||||
// @004cc294 the Create-side sibling.
|
||||
// This is the #47 ENG-BUTTON ATTENTION FLASH (jam / bay fire); the authored
|
||||
// data survives as the type-31 GaugeAlarmStream in BTL4.RES (exactly 1). The
|
||||
// bodies below remain best-effort scaffolding UNTIL re-transcribed from those
|
||||
// addresses -- see context/open-questions.md for the 3-piece plan. The
|
||||
// data survives as the type-31 GaugeAlarmStream in BTL4.RES (exactly 1:
|
||||
// ModelList 'mechalrm' 83 -> stream 331). The bodies below ARE now the real
|
||||
// re-transcriptions from those addresses (2026-07-25, same day). The
|
||||
// original (wrong) note is preserved for the record:
|
||||
//
|
||||
// btl4galm.obj links after btl4grnd.obj and before btl4vid.obj (BTL4.MAK
|
||||
@@ -102,36 +102,115 @@
|
||||
|
||||
|
||||
//
|
||||
// Recognised cockpit gauge-alarm type names. (BattleTech's l4gauge.cfg
|
||||
// "gaugeAlarm" sections name one of these; the threshold / flash parameters
|
||||
// follow in alarm_data.) The exact spelling list could not be recovered from
|
||||
// this image -- it lived in the folded-away override -- so the set below is the
|
||||
// minimal set implied by the GaugeAlarm fields the cockpit gauges flash on
|
||||
// (heat / power / armour / ammo). BEST-EFFORT.
|
||||
// THE REAL OVERRIDE BODIES (Gitea #47, 2026-07-25) -- re-transcribed from the
|
||||
// binary at @004cc294 (Create) / @004cc2fc (Read) + the mapping helpers
|
||||
// @004cc108/@004cc148/@004cc1a0/@004cc264/@004cc27c and their data tables
|
||||
// @0051cf1c..@0051d084. The previous bodies here were admitted fabrications
|
||||
// ("threshold/range/state/flash" types that exist nowhere in the image); the
|
||||
// provenance note at the top of this file records why.
|
||||
//
|
||||
enum GaugeAlarmType
|
||||
// The STREAM FORMAT (verified against the shipped resource -- BTL4.RES carries
|
||||
// ModelList 'mechalrm' id=83 -> GaugeAlarmStream id=331, 11 items):
|
||||
// { int32 condition; int32 lampCode; } per item
|
||||
// where condition = the MechSubsystem TechStatusType (the @0x50de74 name table:
|
||||
// Destroyed 0, Damaged 1, CoolantLeaking 2, Overheating 3, AmmoBurning 4,
|
||||
// Jammed 5, BadPower 6) and lampCode = the @0051cf1c vocabulary below.
|
||||
//
|
||||
// The shipped 'mechalrm' authoring (decoded 2026-07-25):
|
||||
// Destroyed -> gotoEngineering + engCooling + engBusMode
|
||||
// CoolantLeaking -> gotoEngineering + engCooling
|
||||
// AmmoBurning -> gotoEngineering + engEject
|
||||
// Jammed -> gotoEngineering + engEject
|
||||
// BadPower -> gotoEngineering + engBusMode
|
||||
// ("on a jam or bay fire the display eng button for the system flashes" --
|
||||
// Cyd's pod testimony, verbatim in the data.)
|
||||
//
|
||||
|
||||
//
|
||||
// @0051cf1c -- the lamp-code name vocabulary ({char*, code} pairs, 0-terminated).
|
||||
//
|
||||
struct BTLampNameEntry { const char *name; int code; };
|
||||
static const BTLampNameEntry kBTLampNames[] =
|
||||
{
|
||||
gaugeAlarmThreshold, // value crosses a fixed threshold
|
||||
gaugeAlarmRange, // value leaves a [low,high] band
|
||||
gaugeAlarmState, // subsystem operational-state change
|
||||
gaugeAlarmFlash, // timed flash while condition holds
|
||||
gaugeAlarmTypeCount,
|
||||
gaugeAlarmUndefined = -1
|
||||
{ "coolingLoop1", 0x00 },
|
||||
{ "coolingLoop2", 0x01 },
|
||||
{ "coolingLoop3", 0x02 },
|
||||
{ "coolingLoop4", 0x03 },
|
||||
{ "coolingLoop5", 0x04 },
|
||||
{ "coolingLoop6", 0x05 },
|
||||
{ "gotoEngineering", 0x80 },
|
||||
{ "engGeneratorA", 0x81 },
|
||||
{ "engGeneratorB", 0x82 },
|
||||
{ "engGeneratorC", 0x83 },
|
||||
{ "engGeneratorD", 0x84 },
|
||||
{ "engEject", 0x85 },
|
||||
{ "engBusMode", 0x86 },
|
||||
{ "engCooling", 0x87 },
|
||||
{ "engBackToQuad", 0x88 },
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
//
|
||||
// The lamp-resolution tables, byte-verified from the image:
|
||||
// @0051cf9c quad-page mode masks [12] (aux screens 1..12)
|
||||
// @0051cfcc eng-page mode masks [12]
|
||||
// @0051cffc quad-select button lamp ids [12] (code 0x80 gotoEngineering)
|
||||
// @0051d02c eng-page bank-top lamp ids [12] (codes 0x81.. descend from it)
|
||||
// @0051d058 per-condenser lamps (index = condenserNumber @0x1D4, 1-based)
|
||||
// @0051d070 per-placement lamps (index = auxScreenPlacement @0x1E0)
|
||||
//
|
||||
static const int kBTQuadModeMask[12] = { 0x1,0x1,0x1,0x1, 0x20,0x20,0x20,0x20, 0x400,0x400,0x400,0x400 };
|
||||
static const int kBTEngModeMask[12] = { 0x2,0x4,0x8,0x10, 0x40,0x80,0x100,0x200, 0x800,0x1000,0x2000,0x4000 };
|
||||
static const int kBTQuadLamp[12] = { 0xF,0xD,0xB,0x9, 0x27,0x25,0x23,0x21, 0x7,0x5,0x3,0x1 };
|
||||
static const int kBTEngBankTop[12] = { 0xF,0xF,0xF,0xF, 0x27,0x27,0x27,0x27, 0x7,0x7,0x7,0x7 };
|
||||
static const int kBTCondenserLamp[6] = { 0x7, 0x2F,0x2E,0x2D,0x2B,0x2A }; // [condenserNumber]
|
||||
static const int kBTPlacementLamp[5] = { 0x29, 0x1A,0x1B,0x1C,0x1D }; // [auxScreenPlacement]
|
||||
|
||||
//
|
||||
// BTL4GaugeAlarmManager::CreateGaugeAlarmStreamItem
|
||||
// @004cc148 -- the fixed (<0x80) map: coolingLoop1..6 -> the heat-display bank.
|
||||
//
|
||||
// Called by GaugeAlarmManager::CreateGaugeAlarmStream (@00448af0) once per
|
||||
// "gaugeAlarm" entry while a gauge model is being compiled. Recognise the
|
||||
// alarm type spelled in <alarm_name>, parse its parameters out of <alarm_data>,
|
||||
// and serialise (type, parameters) into <mem_stream>. Returning False makes
|
||||
// the base print "Model file '...' has undefined alarm type '<alarm_name>' -
|
||||
// resource not built." and abandon the model -- so an unknown name MUST yield
|
||||
// False (that diagnostic is the observable contract).
|
||||
static int BTFixedLampOf(int code)
|
||||
{
|
||||
static const int kFixed[6] = { 0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A };
|
||||
return (code >= 0 && code < 6) ? kFixed[code] : -1;
|
||||
}
|
||||
|
||||
//
|
||||
// BEST-EFFORT reconstruction (no distinct override body survived in BTL4OPT.EXE).
|
||||
// @004cc108 -- map an authored lamp NAME to its code.
|
||||
//
|
||||
static Logical BTLampCodeOf(const char *data, int *out_code)
|
||||
{
|
||||
for (const BTLampNameEntry *e = kBTLampNames; e->name != 0; ++e)
|
||||
{
|
||||
if (strcmp(data, e->name) == 0) // FUN_004d4b58
|
||||
{
|
||||
*out_code = e->code;
|
||||
return True;
|
||||
}
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
static Logical BTLampLog()
|
||||
{
|
||||
static int s_on = -1;
|
||||
if (s_on < 0) s_on = (getenv("BT_LAMP_LOG") != 0) ? 1 : 0;
|
||||
return s_on ? True : False;
|
||||
}
|
||||
|
||||
//
|
||||
// BTL4GaugeAlarmManager::CreateGaugeAlarmStreamItem -- @004cc294 [T1]
|
||||
//
|
||||
// iVar1 = FUN_004ac194(name, &condition); // MechSubsystem::LookupStatusType
|
||||
// if (!iVar1) return 0;
|
||||
// iVar1 = FUN_004cc108(data, &code); // the @0051cf1c vocabulary
|
||||
// if (!iVar1) return 0;
|
||||
// stream->WriteBytes(&condition, 4); // (*stream+0x20)
|
||||
// stream->WriteBytes(&code, 4);
|
||||
// return 1;
|
||||
//
|
||||
// (Bake-side only -- the shipped BTL4.RES already carries the baked stream; this
|
||||
// matters when re-baking resources from notation.)
|
||||
//
|
||||
Logical
|
||||
BTL4GaugeAlarmManager::CreateGaugeAlarmStreamItem(
|
||||
@@ -140,87 +219,143 @@ Logical
|
||||
const char *alarm_data
|
||||
)
|
||||
{
|
||||
GaugeAlarmType type = gaugeAlarmUndefined;
|
||||
int condition, code;
|
||||
|
||||
//
|
||||
// Map the type name -> enum. (Names best-effort; see note above.)
|
||||
//
|
||||
if (stricmp(alarm_name, "threshold") == 0) type = gaugeAlarmThreshold;
|
||||
else if (stricmp(alarm_name, "range") == 0) type = gaugeAlarmRange;
|
||||
else if (stricmp(alarm_name, "state") == 0) type = gaugeAlarmState;
|
||||
else if (stricmp(alarm_name, "flash") == 0) type = gaugeAlarmFlash;
|
||||
|
||||
if (type == gaugeAlarmUndefined)
|
||||
if (!MechSubsystem::LookupStatusType(alarm_name, &condition)) // FUN_004ac194
|
||||
{
|
||||
return False; // -> "has undefined alarm type ... resource not built"
|
||||
}
|
||||
if (!BTLampCodeOf(alarm_data, &code)) // FUN_004cc108
|
||||
{
|
||||
//
|
||||
// Unknown alarm type: tell the caller so it emits the
|
||||
// "undefined alarm type ... resource not built" warning.
|
||||
//
|
||||
return False;
|
||||
}
|
||||
|
||||
//
|
||||
// Serialise the type tag followed by the type-specific parameters parsed
|
||||
// from the alarm_data text (thresholds, flash period, target colour, ...).
|
||||
//
|
||||
mem_stream->WriteBytes(&type, sizeof(type)); // (*mem_stream+0x20)(...,4)
|
||||
// ... write parsed numeric parameters from alarm_data ...
|
||||
|
||||
mem_stream->WriteBytes(&condition, sizeof(condition));
|
||||
mem_stream->WriteBytes(&code, sizeof(code));
|
||||
return True;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem
|
||||
// BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem -- @004cc2fc [T1]
|
||||
//
|
||||
// Called by GaugeAlarmManager::BuildAlarmsForModel (@00448d00) once per baked
|
||||
// alarm record, with a freshly-constructed GaugeAlarm (already carrying
|
||||
// the_entity/the_subsystem/the_condition from the GaugeAlarm ctor @004489e8).
|
||||
// Read the (type, parameters) back out of <mem_stream> and configure <alarm>
|
||||
// so that, every frame, it samples the named subsystem attribute and raises /
|
||||
// clears its flashing state when the recorded condition is met.
|
||||
// Read one {condition, lampCode} item; when the item's condition equals the
|
||||
// condition being ACTIVATED, resolve the lamp:
|
||||
// * Condenser-derived (0x50e4fc) + condition CoolantLeaking(2):
|
||||
// lamp = @0051d058[condenserNumber@0x1D4] (its own loop button)
|
||||
// * Generator-derived (0x50fb60) + condition CoolantLeaking(2):
|
||||
// lamp = @0051d070[auxScreenPlacement@0x1E0] (its radar-rail button)
|
||||
// * else code < 0x80: the fixed heat-bank map (@004cc148)
|
||||
// * else code >= 0x80: PoweredSubsystem-derived only (0x50f4bc -- the same
|
||||
// gate BTGetSubsystemAuxScreen applies) + auxScreen@0x1DC > 0:
|
||||
// 0x80 gotoEngineering: lamp = @0051cffc[aux], mode = @0051cf9c[aux]
|
||||
// 0x81..: lamp = @0051d02c[aux] - (code - 0x81), mode = @0051cfcc[aux]
|
||||
// then FindLamp(lamp, mode) on the renderer lamp manager (@00444c80), start it
|
||||
// flashing (Lamp::SetAlertState(True) @00444e64 -- a COUNTER, alarms stack), and
|
||||
// record it on the GaugeAlarm lampList so ~GaugeAlarm un-flashes on Deactivate.
|
||||
// A lamp code that resolves to no lamp is silently skipped (iVar2 = -1 path).
|
||||
//
|
||||
// BEST-EFFORT reconstruction (no distinct override body survived in BTL4OPT.EXE).
|
||||
// (0x50f4bc = PoweredSubsystem::ClassDerivations, NOT Generator -- pinned via
|
||||
// @004b1208 = PoweredSubsystem::TestInstance in the powersub method cluster;
|
||||
// 0x50fb60 = the Generator one, adjacent to its message table @0x50fb90.)
|
||||
//
|
||||
void
|
||||
BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem(
|
||||
GaugeAlarm *alarm,
|
||||
Entity *the_entity,
|
||||
Entity * /*the_entity*/,
|
||||
Subsystem *the_subsystem,
|
||||
Enumeration the_condition,
|
||||
MemoryStream *mem_stream
|
||||
)
|
||||
{
|
||||
GaugeAlarmType type;
|
||||
mem_stream->ReadBytes(&type, sizeof(type)); // (*mem_stream+0x1c)(...,4)
|
||||
int item_condition = -1, lamp_code = -1;
|
||||
mem_stream->ReadBytes(&item_condition, sizeof(item_condition)); // (*stream+0x1c)
|
||||
mem_stream->ReadBytes(&lamp_code, sizeof(lamp_code));
|
||||
|
||||
//
|
||||
// Bind the alarm to its data source and trip parameters. The base
|
||||
// GaugeAlarm already stores entity/subsystem/condition; here we attach the
|
||||
// type-specific trip (threshold / band / state / flash period) read from
|
||||
// the stream so GaugeAlarm::Execute can flash the owning gauge.
|
||||
//
|
||||
switch (type)
|
||||
if ((int)the_condition != item_condition)
|
||||
{
|
||||
case gaugeAlarmThreshold:
|
||||
// alarm->SetThreshold( read Scalar ); alarm->SetCondition(the_condition);
|
||||
break;
|
||||
case gaugeAlarmRange:
|
||||
// alarm->SetBand( read low, read high );
|
||||
break;
|
||||
case gaugeAlarmState:
|
||||
// alarm->WatchState(the_subsystem);
|
||||
break;
|
||||
case gaugeAlarmFlash:
|
||||
// alarm->SetFlashPeriod( read Scalar );
|
||||
break;
|
||||
default:
|
||||
Verify(False, "Bad gauge alarm type",
|
||||
"d:\\tesla\\bt\\bt_l4\\BTL4GALM.CPP", 0);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
(void)the_entity;
|
||||
(void)the_subsystem;
|
||||
(void)the_condition;
|
||||
extern int BTSubsystemIsCondenser(::Subsystem *sub); // heat.cpp (0x50e4fc)
|
||||
extern int BTCondenserNumber(::Subsystem *sub); // heat.cpp (+0x1D4)
|
||||
extern int BTSubsystemIsGenerator(::Subsystem *sub); // powersub.cpp (0x50fb60)
|
||||
extern bool BTGetSubsystemAuxScreen(::Subsystem *sub, int *screen, int *placement, char *label64); // powersub.cpp (0x50f4bc gate + 0x1DC/0x1E0)
|
||||
|
||||
int lamp_id = -1;
|
||||
int mode = -1; // local_10: stays -1 for the fixed/family paths
|
||||
|
||||
if (BTSubsystemIsCondenser(the_subsystem))
|
||||
{
|
||||
if (the_condition == 2) // CoolantLeaking
|
||||
{
|
||||
int n = BTCondenserNumber(the_subsystem);
|
||||
if (n >= 0 && n < 6)
|
||||
lamp_id = kBTCondenserLamp[n];
|
||||
}
|
||||
}
|
||||
else if (BTSubsystemIsGenerator(the_subsystem))
|
||||
{
|
||||
if (the_condition == 2) // CoolantLeaking
|
||||
{
|
||||
int screen = 0, placement = -1;
|
||||
BTGetSubsystemAuxScreen(the_subsystem, &screen, &placement, 0);
|
||||
if (placement >= 0 && placement < 5)
|
||||
lamp_id = kBTPlacementLamp[placement];
|
||||
}
|
||||
}
|
||||
else if (lamp_code < 0x80)
|
||||
{
|
||||
lamp_id = BTFixedLampOf(lamp_code);
|
||||
}
|
||||
else
|
||||
{
|
||||
int screen = 0, placement = -1;
|
||||
if (BTGetSubsystemAuxScreen(the_subsystem, &screen, &placement, 0) // PoweredSubsystem gate
|
||||
&& screen > 0)
|
||||
{
|
||||
int aux = screen - 1; // +0x1DC - 1
|
||||
if (aux < 12)
|
||||
{
|
||||
if (lamp_code == 0x80) // gotoEngineering
|
||||
{
|
||||
lamp_id = kBTQuadLamp[aux];
|
||||
mode = kBTQuadModeMask[aux];
|
||||
}
|
||||
else // engGeneratorA..engBackToQuad
|
||||
{
|
||||
lamp_id = kBTEngBankTop[aux] - (lamp_code - 0x81);
|
||||
mode = kBTEngModeMask[aux];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lamp_id < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// @004cc2fc tail: FindLamp on the gauge renderer lamp manager, flash it,
|
||||
// and record it on the alarm. (DAT_004efc94+0x4c = the gauge renderer.)
|
||||
//
|
||||
GaugeRenderer *renderer = (GaugeRenderer *)application->GetGaugeRenderer();
|
||||
if (renderer == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Lamp *lamp = (renderer->GetLampManager() != 0)
|
||||
? renderer->GetLampManager()->FindLamp((LampID)lamp_id, (ModeMask)mode)
|
||||
: 0;
|
||||
|
||||
if (BTLampLog())
|
||||
DEBUG_STREAM << "[galarm] condition " << (int)the_condition
|
||||
<< " code 0x" << std::hex << lamp_code
|
||||
<< " -> lamp 0x" << lamp_id << " mode 0x" << mode << std::dec
|
||||
<< (lamp ? " FLASH" : " (no lamp registered -- skipped)") << "\n" << std::flush;
|
||||
|
||||
if (lamp != 0)
|
||||
{
|
||||
lamp->SetAlertState(True); // FUN_00444e64 -- the flash counter
|
||||
alarm->lampList.Add(lamp); // un-flashed by ~GaugeAlarm on Deactivate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
// FUN_0041f98c Entity::FindSubObject(name)
|
||||
// FUN_00447f1c GaugeRenderer::InstallPort / FUN_00447f84 GetGraphicsPort
|
||||
// FUN_00417ab4 SharedData::Resolve / operator Entity*
|
||||
// FUN_0041a1a4 IsDerivedFrom(classDerivations) (0x50f4bc = Generator)
|
||||
// FUN_0041a1a4 IsDerivedFrom(classDerivations) (0x50f4bc = PoweredSubsystem -- pinned #47 via @004b1208 = PoweredSubsystem::TestInstance; the old 'Generator' reading was wrong)
|
||||
// FUN_0042076c Subsystem::FindDamageZone(record) -> index
|
||||
// FUN_004dcd94 Round(Scalar) -> int
|
||||
// FUN_004700bc NumericDisplay ctor FUN_0047018c ::dtor
|
||||
@@ -1485,7 +1485,8 @@ SubsystemCluster::SubsystemCluster(
|
||||
WipeGaugeScalar::wipeUp, 0.0f, GeneratorMaxVoltage, subsystem_in, // pass the SUBSYSTEM
|
||||
"GeneratorVoltage(slotOf)"); // -> BTGeneratorVoltage reads voltage by name (attr-shift fix)
|
||||
|
||||
// stateLamp only when the subsystem is a Generator (IsDerivedFrom 0x50f4bc).
|
||||
// stateLamp gate: IsDerivedFrom 0x50f4bc = POWEREDSUBSYSTEM (#47 correction --
|
||||
// the old 'Generator' label was wrong; every powered subsystem passes).
|
||||
if (IsGeneratorDerived(subsystem_in))
|
||||
stateLamp = new OneOfSeveralStates(ChildRate(), eng_mode, renderer_in, // @004c5470
|
||||
engPort, 0xbe, 0, "btemode.pcc", 1, 3,
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
# 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
|
||||
@@ -205,6 +206,17 @@ BTL4GaugeRenderer::BTL4GaugeRenderer()
|
||||
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].
|
||||
|
||||
@@ -131,7 +131,16 @@ HeatableSubsystem::SharedData
|
||||
Derivation*
|
||||
HeatableSubsystem::GetClassDerivations()
|
||||
{
|
||||
static Derivation classDerivations(Subsystem::GetClassDerivations(), "HeatableSubsystem");
|
||||
// #47 CORRECTION: was chained to Subsystem:: directly, SKIPPING
|
||||
// MechSubsystem -- written before the WAVE-1 re-basing established
|
||||
// `HeatableSubsystem : MechSubsystem`. That made EVERY subsystem on
|
||||
// both family branches fail IsDerivedFrom(MechSubsystem::ClassDerivations),
|
||||
// which is exactly the filter MechTech's monitor walk applies
|
||||
// (FUN_0041a1a4(**sub[3], 0x50de2c)) -- so the status scan monitored
|
||||
// NOTHING and no jam/bay-fire annunciation could ever fire. The
|
||||
// derivation chain now mirrors the class hierarchy, as every other
|
||||
// family file already does (Searchlight -> PowerWatcher -> HeatWatcher...).
|
||||
static Derivation classDerivations(&MechSubsystem::ClassDerivations, "HeatableSubsystem");
|
||||
return &classDerivations;
|
||||
}
|
||||
|
||||
@@ -1249,3 +1258,17 @@ int BTCoolingLoopFrame(void *subsystem_v)
|
||||
<< " (condenser '" << master->GetName() << "')\n" << std::flush;
|
||||
return frame;
|
||||
}
|
||||
|
||||
//
|
||||
// Gitea #47 bridges (BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem):
|
||||
// the Condenser family tests + fields the alarm lamp mapping needs
|
||||
// (@004cc2fc gates on 0x50e4fc; @004cc264 reads condenserNumber @0x1D4).
|
||||
//
|
||||
int BTSubsystemIsCondenser(::Subsystem *sub)
|
||||
{
|
||||
return (sub != 0 && sub->IsDerivedFrom(*Condenser::GetClassDerivations())) ? 1 : 0;
|
||||
}
|
||||
int BTCondenserNumber(::Subsystem *sub)
|
||||
{
|
||||
return (sub != 0) ? ((Condenser *)sub)->condenserNumber : -1; // +0x1D4
|
||||
}
|
||||
|
||||
@@ -1787,8 +1787,18 @@ void
|
||||
Subsystem *s = GetSubsystem(i);
|
||||
if (s == 0)
|
||||
continue;
|
||||
s->DeathReset(mode); // -> per-class RTIS chain
|
||||
((MechSubsystem *)s)->RespawnRepair(); // fresh-mech: zone + status
|
||||
s->DeathReset(mode); // -> per-class RTIS chain (engine virtual -- safe for all)
|
||||
// #47 regression fix: the blanket (MechSubsystem*) cast is WRONG for the
|
||||
// Subsystem-level roster entries (MechTech 0xBDC, SubsystemMessageManager
|
||||
// 0xBD3) -- RespawnRepair read the recon damageZone slot THROUGH them,
|
||||
// which on MechTech lands on its subsystemMonitors chain head. While the
|
||||
// monitor walk was broken (0 monitors) that head was NULL and the guard
|
||||
// skipped; the moment #47 populated it, RespawnRepair virtual-called
|
||||
// through a SubsystemMonitor as if it were a DamageZone (the
|
||||
// StateIndicator::SetState load crash). Filter with the same
|
||||
// IsDerivedFrom the binary's own sweeps use.
|
||||
if (s->IsDerivedFrom(MechSubsystem::ClassDerivations))
|
||||
((MechSubsystem *)s)->RespawnRepair(); // fresh-mech: zone + status
|
||||
}
|
||||
|
||||
// --- locomotion pre-run + interest gates (a reset master must tick) ---
|
||||
|
||||
@@ -232,7 +232,13 @@ class Damage;
|
||||
// Subsystem virtual overrides (slots on vtable @0x50e210)
|
||||
//
|
||||
public:
|
||||
LWord
|
||||
// VIRTUAL (#47): the binary dispatches slot 12 through the vtable --
|
||||
// MechTech's TechnicalAssistance scan calls (*sub_vtbl+0x30) on every
|
||||
// monitored subsystem. Non-virtual here made a MechSubsystem* call bind
|
||||
// statically to this base tier, so ProjectileWeapon's Jammed/AmmoBurning
|
||||
// bits (@004bbf88) never reached the scan. (HeatableSubsystem already
|
||||
// declared its own virtual; this unifies the root.)
|
||||
virtual LWord
|
||||
GetStatusFlags(); // slot 12, @0x4ac144
|
||||
void
|
||||
ResetToInitialState(Logical powered); // slot 8, @0x4ac1d4
|
||||
@@ -294,9 +300,13 @@ class Damage;
|
||||
void DistributeCriticalHit(Damage &damage);
|
||||
|
||||
// @0x4ac194 -- map a status-state name string -> TechStatusType value
|
||||
// using the name table @0x50de74 (ConfigureActivePress support).
|
||||
// using the name table @0x50de74. PUBLIC (was protected): the binary
|
||||
// calls it cross-TU from BTL4GaugeAlarmManager::CreateGaugeAlarmStreamItem
|
||||
// (@004cc294) to parse "gaugeAlarm Jammed = engEject"-style entries (#47).
|
||||
public:
|
||||
static Logical
|
||||
LookupStatusType(const char *name, int *out_value);
|
||||
protected:
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Local data (byte offsets into the shipped object)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
|
||||
@@ -1713,3 +1713,13 @@ int BTSubsystemDestroyed(Subsystem *sub)
|
||||
return 0;
|
||||
return (((PoweredSubsystem *)sub)->GetSimulationState() == 1) ? 1 : 0; // +0x40 == 1
|
||||
}
|
||||
|
||||
//
|
||||
// Gitea #47 bridge (BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem):
|
||||
// the Generator family test the alarm lamp mapping needs (@004cc2fc gates its
|
||||
// placement-lamp special on 0x50fb60 = Generator::ClassDerivations).
|
||||
//
|
||||
int BTSubsystemIsGenerator(::Subsystem *sub)
|
||||
{
|
||||
return (sub != 0 && sub->IsDerivedFrom(*Generator::GetClassDerivations())) ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -451,17 +451,36 @@ void
|
||||
}
|
||||
|
||||
//
|
||||
// @004bbf88 -- slot 12 (GetStatusFlags). Body NOT recovered by the decompiler
|
||||
// (function prologue present at 0x4bbf88). Overrides MechWeapon's
|
||||
// PoweredSubsystem::GetStatusFlags (@004b0f48); best-effort: ORs the weapon's
|
||||
// ammo/jam status bits onto the base flags.
|
||||
// @004bbf88 -- slot 12 (GetStatusFlags). RAW-DISASSEMBLED 2026-07-25 (#47; the
|
||||
// body sat in a Ghidra export gap):
|
||||
//
|
||||
// call 0x4b0f48 ; flags = PoweredSubsystem::GetStatusFlags
|
||||
// mov eax,[ebx+0x364] ; weaponAlarm level
|
||||
// cmp eax,5 / jne / or esi,0x20 ; Jammed -> bit 5 (TechStatus Jammed)
|
||||
// lea ebx,[ebx+0x43c] / call Resolve ; the linked AmmoBin
|
||||
// mov eax,[eax+0x18c] / or esi,0x10 ; cookOffArmed -> bit 4 (AmmoBurning)
|
||||
//
|
||||
// These are the two bits MechTech's TechnicalAssistance edge-scan turns into
|
||||
// the Start/StopEntityAlarm reports that flash the ENG-page buttons (the
|
||||
// shipped 'mechalrm' table: Jammed/AmmoBurning -> gotoEngineering + engEject).
|
||||
//
|
||||
LWord
|
||||
ProjectileWeapon::GetStatusFlags()
|
||||
{
|
||||
// BEST-EFFORT -- body unrecovered. Defer to base until a human disassembles
|
||||
// 0x4bbf88.
|
||||
return MechWeapon::GetStatusFlags();
|
||||
LWord flags = PoweredSubsystem::GetStatusFlags(); // call 0x4b0f48 (direct in the raw)
|
||||
|
||||
if (weaponAlarm.GetLevel() == JammedState) // +0x364 == 5
|
||||
{
|
||||
flags |= 0x20; // Jammed
|
||||
}
|
||||
|
||||
extern int BTAmmoBinCookOffArmed(void *bin); // ammobin.cpp bridge (+0x18C)
|
||||
void *bin = ammoBinLink.Resolve(); // +0x43C, FUN_00417ab4
|
||||
if (bin != 0 && BTAmmoBinCookOffArmed(bin) != 0)
|
||||
{
|
||||
flags |= 0x10; // AmmoBurning
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
"""Gitea #46 -- verify the ammo bay fire detonates and applies real damage.
|
||||
|
||||
Before this fix, three stacked defects made an armed bay fire permanently inert:
|
||||
the clock stub (`0 < 0` never fired), the InjectHeat no-op (nothing applied),
|
||||
and the never-stamped Damage record (0 damage of type 0 even if it had).
|
||||
|
||||
Rig: BT_BAYTEST=<frame> sends message 1 (the crit-induced "begin cook-off
|
||||
countdown", @004bdb94) to the first AmmoBin. The authentic fuse is a FIXED
|
||||
10.0 seconds (raw disasm @004bd450: 10.0 x ticksPerSecond + 0.5, __ftol --
|
||||
the old "RandomDelay" was a Ghidra artifact).
|
||||
|
||||
PASS =
|
||||
[ammo] ... BAY FIRE (message): cook-off armed, N rounds, detonation in 10s
|
||||
...~10 wall seconds later...
|
||||
[ammo] ... BAY FIRE DETONATION: N rounds x D = total (type T)
|
||||
ammo explosion damaging <zoneName> (the binary's exact @0050df61 print)
|
||||
...and the mech takes real zone damage (BT_MP_NET handler line / DAMAGE state).
|
||||
|
||||
Kills only the PID it spawns.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
REPO = r"C:\git\bt411"
|
||||
LOG = os.path.join(REPO, "scratchpad_lampflash.log")
|
||||
if os.path.exists(LOG):
|
||||
os.remove(LOG)
|
||||
|
||||
env = dict(os.environ)
|
||||
env.update({
|
||||
"BT_START_INSIDE": "1",
|
||||
"BT_DEV_GAUGES": "1",
|
||||
"BT_BAYTEST": "400", # arm at sim frame 400
|
||||
"BT_AMMO_LOG": "1",
|
||||
"BT_MP_NET": "1",
|
||||
"BT_LAMP_LOG": "1", # [techstat]/[galarm]/[lamp] -- the #47 flash chain # [mp-hdlr] TakeDamageHandler lines (zone + amount)
|
||||
"BT_DEATH_LOG": "1", # crit cascade / zone destruction
|
||||
"BT_LOG": LOG,
|
||||
})
|
||||
proc = subprocess.Popen(
|
||||
[os.path.join(REPO, "build", "Release", "btl4.exe"), "-egg", "LAST.EGG"],
|
||||
cwd=os.path.join(REPO, "content"), env=env,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
print("pid", proc.pid)
|
||||
|
||||
try:
|
||||
deadline = time.time() + 240
|
||||
armed = False
|
||||
while time.time() < deadline:
|
||||
if os.path.exists(LOG):
|
||||
t = open(LOG, errors="replace").read()
|
||||
if "BAY FIRE (message)" in t:
|
||||
armed = True
|
||||
break
|
||||
time.sleep(2)
|
||||
if not armed:
|
||||
print("FAIL: never armed")
|
||||
# wait through the 10s fuse + margin for the fan-out and damage
|
||||
time.sleep(25)
|
||||
finally:
|
||||
proc.terminate()
|
||||
time.sleep(1)
|
||||
|
||||
t = open(LOG, errors="replace").read() if os.path.exists(LOG) else ""
|
||||
arm = re.findall(r"^\[ammo\].*BAY FIRE.*armed.*$", t, re.M)
|
||||
boom = re.findall(r"^\[ammo\].*DETONATION.*$", t, re.M)
|
||||
zones = re.findall(r"^ammo explosion damaging.*$", t, re.M)
|
||||
hdlr = re.findall(r"^\[mp-hdlr\] TakeDamageHandler.*$", t, re.M)
|
||||
dfx = re.findall(r"^\[deathfx\].*$", t, re.M)
|
||||
ext = re.findall(r"^\[ammo\].*EXTINGUISHED.*$", t, re.M)
|
||||
|
||||
print("\n=============== RESULT ===============")
|
||||
print("armed:", len(arm))
|
||||
for l in arm[:2]:
|
||||
print(" ", l[:120])
|
||||
print("\ndetonation:", len(boom))
|
||||
for l in boom[:2]:
|
||||
print(" ", l[:130])
|
||||
print("\n'ammo explosion damaging' zone prints:", len(zones))
|
||||
for l in zones[:6]:
|
||||
print(" ", l[:100])
|
||||
print("\nTakeDamage handler runs:", len(hdlr))
|
||||
for l in hdlr[:4]:
|
||||
print(" ", l[:150])
|
||||
print("\n[deathfx] zone/crit lines:", len(dfx))
|
||||
for l in dfx[:8]:
|
||||
print(" ", l[:120])
|
||||
if ext:
|
||||
print("\nextinguished (unexpected in this rig):", ext[:2])
|
||||
|
||||
ok = arm and boom and zones and hdlr
|
||||
print("\nVERDICT:", "PASS -- bay fire armed, detonated on the 10s fuse, and applied real zone damage"
|
||||
if ok else "FAIL -- see which stage is missing above")
|
||||
sys.exit(0 if ok else 2)
|
||||
Reference in New Issue
Block a user