diff --git a/context/decomp-reference.md b/context/decomp-reference.md index 3d8b89b..f333335 100644 --- a/context/decomp-reference.md +++ b/context/decomp-reference.md @@ -616,6 +616,20 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a @00444c80` (lamp map at renderer`+0x1c0c8`) → **`Lamp::SetAlertState @00444e64`** (the flash 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 +- **#135 (2026-08-11): the GENERATOR special @004cc27c is `table_0051d070[generatorNumber]`** + (sub+0x1E0, A=1..D=4, 1-based; bytes byte-read: {0x29, 0x1A,0x1B,0x1C,0x1D}; slot 5 is + already string data — bounds-guard 1..4) [T1] — the SAME shape as the condenser special + @004cc264 beside it. The port had mis-reconstructed it as an aux-screen placement probe + (fields generators never fill) → the field-logged `NO LAMP RESOLVED (generator=1)` silent + generator leak; benched fixed (`[lamp] 0x1a <- 0x37 (FLASHING)` on a GeneratorA leak). +- **#135 stale-flash gotcha [T0]:** `Lamp::SetAlertState/SetState` notify the panel ONLY when + the lamp's modeMask intersects the manager's current mask — an edge landing off-page (or in + the one-frame mask race of a page transition) is silently dropped, and the panel value + LATCHES the last-asserted flash (Oracle: "Loop 6 NEVER stopped flashing"; his log shows + Condenser6 SET/CLEARED balanced 3/3 — the manager was right, the lamp was stale). Port fix: + the skipped notify is remembered (`staleAlertNotify`) and delivered by the manager's + active-lamp loop at the next in-mode frame (LAMP.cpp). Field SET/CLEARED balance + a stuck + flash = this gotcha, not an alarm leak. `@00474d54`). T0-compiled already: LAMP.cpp / L4LAMP.cpp / GAUGALRM.cpp / RENDERER messages. **✅ ALL THREE PIECES BUILT same day (see [[open-questions]] for the full closure note).** Corrections pinned while building: the SENDER is **`MechTech::TechnicalAssistance` @004ad33c** diff --git a/engine/MUNGA/LAMP.cpp b/engine/MUNGA/LAMP.cpp index c1a5256..56161f8 100644 --- a/engine/MUNGA/LAMP.cpp +++ b/engine/MUNGA/LAMP.cpp @@ -53,6 +53,17 @@ void while ((lamp_pointer=i.ReadAndNext()) != NULL) { Check(lamp_pointer); + // #135: an edge that arrived while this lamp's page was not current + // (or during the one-frame mask race of a page transition) skipped its + // NotifyOfStateChange -- the panel value latches the last asserted + // flash, so a missed CLEAR left loop buttons flashing forever. The + // lamp is on the ACTIVE list here, so its mode is current: deliver + // the missed notification now. + if (lamp_pointer->staleAlertNotify) + { + lamp_pointer->staleAlertNotify = False; + lamp_pointer->NotifyOfStateChange(); + } lamp_pointer->Update(); } Check_Fpu(); @@ -248,6 +259,7 @@ Lamp::Lamp( previousState = LampStateUndefined; manager = lamp_manager; alertActive = 0; + staleAlertNotify = False; // #135 manager->AddLamp(this); Check_Fpu(); @@ -281,6 +293,10 @@ void { NotifyOfStateChange(); } + else + { + staleAlertNotify = True; // #135: deliver at next in-mode Update + } } Check_Fpu(); } @@ -311,6 +327,10 @@ void { NotifyOfStateChange(); } + else + { + staleAlertNotify = True; // #135: deliver at next in-mode Update + } Check_Fpu(); } diff --git a/engine/MUNGA/LAMP.h b/engine/MUNGA/LAMP.h index 03b4134..217fa4d 100644 --- a/engine/MUNGA/LAMP.h +++ b/engine/MUNGA/LAMP.h @@ -78,6 +78,14 @@ protected: *manager; int alertActive; + // #135: an alert-state EDGE whose NotifyOfStateChange was skipped by the + // mode gate (page not current, or the one-frame mask race during a page + // transition). The panel value latches the last asserted flash until the + // next assert, so a skipped clear-edge left loop buttons flashing forever + // (field: Oracle night-15, "Loop 6 NEVER stopped"). Delivered by Update() + // at the lamp's next in-mode frame. + Logical + staleAlertNotify; }; //######################################################################### diff --git a/game/reconstructed/btl4galm.cpp b/game/reconstructed/btl4galm.cpp index 0bba761..4766cbd 100644 --- a/game/reconstructed/btl4galm.cpp +++ b/game/reconstructed/btl4galm.cpp @@ -170,7 +170,13 @@ static const int kBTEngBankTop[12] = { 0xF,0xF,0xF,0xF, 0x27,0x27,0x27,0x27, 0 // no 0x29) -- overlapping but distinct lamps. Slot 6 (0x29) is also // DAT_0051d070[0], the first per-placement lamp: the two tables abut. static const int kBTCondenserLamp[7] = { 0x7, 0x2F,0x2E,0x2D,0x2B,0x2A, 0x29 }; -static const int kBTPlacementLamp[5] = { 0x29, 0x1A,0x1B,0x1C,0x1D }; // [auxScreenPlacement] +// #135 CORRECTED: these are the GENERATOR leak lamps -- the binary's table at +// 0x51d070 (@004cc27c), indexed by generatorNumber 1-BASED (A=1..D=4; slot 0 +// overlaps the condenser table's last entry and is unused; slot 5 is already +// string bytes in the image, hence the bounds guard). The old name/comment +// ("auxScreenPlacement") described a mis-reconstruction that indexed this +// table with PoweredSubsystem aux fields generators never fill. +static const int kBTGeneratorLamp[5] = { 0x29, 0x1A,0x1B,0x1C,0x1D }; // // @004cc148 -- the fixed (<0x80) map: coolingLoop1..6 -> the heat-display bank. @@ -335,10 +341,15 @@ void { 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]; + // #135 FIX: the binary's generator special @004cc27c indexes the + // 0x51d070 table by generatorNumber (sub+0x1E0, A=1..D=4) -- NOT by + // any aux-screen placement (generators never fill those fields; the + // old probe left placement=-1 and a leaking generator annunciated + // NOTHING, the field-logged 'NO LAMP RESOLVED (generator=1)'). + extern int BTGeneratorNumber(::Subsystem *sub); // powersub.cpp + int n = BTGeneratorNumber(the_subsystem); + if (n >= 1 && n <= 4) + lamp_id = kBTGeneratorLamp[n]; } } else if (lamp_code < 0x80) diff --git a/game/reconstructed/powersub.cpp b/game/reconstructed/powersub.cpp index 593fda0..6760557 100644 --- a/game/reconstructed/powersub.cpp +++ b/game/reconstructed/powersub.cpp @@ -1822,6 +1822,17 @@ int BTSubsystemIsGenerator(::Subsystem *sub) return (sub != 0 && sub->IsDerivedFrom(*Generator::GetClassDerivations())) ? 1 : 0; } +// #135 bridge: the binary's generator lamp special @004cc27c is +// `table_0051d070[*(int*)(sub+0x1E0)]` -- indexed by generatorNumber (A=1..D=4), +// the same shape as the condenser special beside it. Expose the number so the +// alarm-lamp TU can do the authentic lookup (it had been probing the +// PoweredSubsystem aux-screen fields, which generators never fill -- the +// field-logged 'NO LAMP RESOLVED (generator=1)' silent leak). +int BTGeneratorNumber(::Subsystem *sub) +{ + return BTSubsystemIsGenerator(sub) ? ((Generator *)sub)->generatorNumber : -1; +} + // Destruction -> OUT display state (see Generator::MarkGeneratorOut, the [T3 // inferred link]): a destroyed generator must reach stateAlarm 4 or the // authored gotoEngineering/engEject FLASH invite (alarm conditions 4/5) never