From 0d032029a0f29831dcffed2ea16a34f97ab931e7 Mon Sep 17 00:00:00 2001 From: arcattack Date: Sun, 19 Jul 2026 23:30:43 -0500 Subject: [PATCH] Fix 2 (Gitea #12): gauge tree survives the in-session mech re-stream The pre-launch host drop/rejoin re-stream re-creates every mech at mission launch; the LOBBY-built gauge tree kept attribute pointers into the freed first-stream mechs, every widget Execute AV'd, and the BT_DEV_GAUGES SEH guard (Gauge::GuardedExecute, GAUGE.cpp:618) disabled each one PERMANENTLY (rate=0, no rebind) -- 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 bypassed it. Fix: BTL4GaugeRenderer::TearDownForViewpointRestream() performs the ENTITY-BOUND half of that same ShutdownImplementation sequence, in order (gaugeAlarmManager->RemoveAllAlarms() -> Remove(0) -> moving/static entity grid Clear), keeping the mission-scoped state (warehouse, graphics ports, interpreter, controls-owned L4Lamps -- RemoveAllLamps would delete them behind the buttonGroup's &lamp->automaticValue registrations). Called from BTL4Application::MakeViewpointEntity before ConfigureForModel("Init") on any viewpoint RE-make -- that handler runs once per stream (the incident log's [ctrlmap] installing x2) -- so the tree rebuilds bound to the NEW mech via the renderer's existing lazy build. Sentinel: "[gauge] viewpoint re-stream: tearing the gauge tree down for rebuild". Verified with the 2-instance relay harness (port 15600, MP_RELAY.EGG, the incident sequence): join A (dev gauges) + B, kill B by exact PID at WAITING FOR OPERATOR LAUNCH, rejoin B, operator launch. Pod A re-streamed (2x ctrlmap install, 4x zonebuild), logged the sentinel, and post-launch the docked composite is fully live (mission clock/heading/radar advancing between BT_SHOT frames) with ZERO [gauge-fault] DISABLED lines (the incident had 11). Co-Authored-By: Claude Fable 5 --- context/gauges-hud.md | 15 +++++++++-- context/open-questions.md | 46 ++++++++++++++++++--------------- game/reconstructed/btl4app.cpp | 25 ++++++++++++++++++ game/reconstructed/btl4grnd.cpp | 44 +++++++++++++++++++++++++++++++ game/reconstructed/btl4grnd.hpp | 17 ++++++++++++ 5 files changed, 124 insertions(+), 23 deletions(-) diff --git a/context/gauges-hud.md b/context/gauges-hud.md index 01df692..4d0c92e 100644 --- a/context/gauges-hud.md +++ b/context/gauges-hud.md @@ -92,8 +92,19 @@ PERMANENTLY, nothing re-enables or rebinds → the whole window static for the r session (= symptoms 1+3). The AUTHENTIC engine flow handles mission transitions by `Application::Shutdown → gaugeRenderer->Shutdown() → ShutdownImplementation → Remove(0)` (all gauges deleted, tree lazily rebuilt next mission, APP.cpp:787/GAUGREND.cpp:3264) — the port's -in-session re-stream path bypasses it. OPEN: rebuild/rebind the gauge tree on a viewpoint-mech -re-stream (or route the rejoin through the engine Shutdown flow); see [[open-questions]]. +in-session re-stream path bypasses it. **FIX LANDED (2026-07-19, awaiting human MP verify):** +`BTL4GaugeRenderer::TearDownForViewpointRestream()` (btl4grnd.cpp) performs the ENTITY-BOUND +half of the engine ShutdownImplementation sequence, same order (RemoveAllAlarms → Remove(0) → +entity-grid Clear), deliberately KEEPING warehouse/graphics-ports/interpreter/controls-L4Lamps +(RemoveAllLamps mid-session would delete the CONTROLS-owned L4Lamps behind the buttonGroup's +`&lamp->automaticValue` registrations — a dangling mapping; the mech-state "lamps" like +AnimatedSubsystemLamp are Gauges and go with Remove(0)). Called from +`BTL4Application::MakeViewpointEntity` before `ConfigureForModel("Init", entity)` whenever the +viewpoint is a RE-make (that handler empirically runs once per stream — twice in the incident +log, `[ctrlmap] installing` ×2) — the tree then rebuilds bound to the NEW mech. Log sentinel: +`[gauge] viewpoint re-stream: tearing the gauge tree down for rebuild`. Residual exposure: +`Remove(0)` flushes each gauge (`Update(gaugeRate_A)`, GAUGREND.cpp:3465) before deleting it, so +gauge CONNECTIONS may read the freed mech once more (plain reads; Execute stays SEH-guarded). ## The secondary screen's THREE views (Damage / Critical / Heat) — mode-gated [T0/T1/T2] The `sec` port stacks three mode-gated mech-schematic layers at offset (50,0) over the diff --git a/context/open-questions.md b/context/open-questions.md index 709598a..c0e17a4 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -629,29 +629,33 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR numbers no longer point at that code). ## Multiplayer (Phase 7 / P6) -- **Gitea #12 MP incident (2026-07-19) — root causes found, FIXES NOT YET LANDED (blocked on a - live-verify window; the user was playing).** Findings [T1/T2 — see the #12 comments + - `scratchpad/incident_2157/`]: +- **Gitea #12 MP incident (2026-07-19) — root causes found; BOTH FIXES LANDED 2026-07-19 + (awaiting the human MP death-and-survive verification).** Findings [T1/T2 — see the #12 + comments + `scratchpad/incident_2157/`]: (1) *Gauge-window freeze* = lobby-built gauge tree dangling over a mission-launch mech RE-STREAM after a pre-launch host drop/rejoin; SEH guard disables each faulted gauge - permanently — see [[gauges-hud]] §MP gauge-window FREEZE. FIX TODO: rebuild/rebind the gauge - tree on a viewpoint re-stream (engine analog: `Application::Shutdown` → - `gaugeRenderer->Shutdown()`); also consider re-enabling (not just disabling) a faulted gauge - after a rebind. - (2) *Missiles cycle-but-never-launch*: the port's ProjectileWeaponSimulation is a RivetGun - approximation — the AUTHENTIC @004bbd04 machine is now fully recovered ([[decomp-reference]] - §5). Port defects to fix faithfully: fault gate reads `simulationFlags==1` (binary: - `simulationState@0x40==1`); `simulationFlags|=0x1` on every salvo/beam = engine - DelayWatchersFlag poisoning ([[reconstruction-gotchas]] §20; binary = `updateModel|=1` == - ForceUpdate); view/target gating + ConsumeRound live in the port's FireWeapon (binary: in the - caller, with a 4-blip stay-Loaded denial — the port instead fakes a full Firing→Loading pip - cycle on a denied shot, which is exactly the reported symptom); recoil never clamped at 0 - (runs to −114); slot-17 @004b9c9c (rechargeLevel) never called. Solo repro on 4.11.387 - (pre-stop-order): SRM6s latch alarm 7 PERMANENTLY mid-session after a heat-failure episode - (recoil pinned → released → state 7 forever) — missiles stop while lasers keep working, with - or WITHOUT J/K/L presses. The binary's state 7 is also permanent once entered [T1] — open - fidelity question: is launcher FailureHeat reachable at authentic heat economy, or does our - launcher self-heat run hot? (Emitters never trip it — task #11 duty-cycle measurement.) + permanently — see [[gauges-hud]] §MP gauge-window FREEZE. ✅ FIX LANDED: + `BTL4GaugeRenderer::TearDownForViewpointRestream()` (alarms + Remove(0) + entity grids — the + entity-bound half of the engine `ShutdownImplementation`), called from + `MakeViewpointEntity` before `ConfigureForModel` on a viewpoint RE-make; the tree rebuilds + bound to the new mech. Sentinel: `[gauge] viewpoint re-stream: tearing the gauge tree down`. + (2) *Missiles cycle-but-never-launch*: ✅ FIX LANDED — the port's RivetGun-approximation + ProjectileWeaponSimulation replaced with the disasm-exact @004bbd04 machine + ([[decomp-reference]] §5; projweap.cpp): fault gate reads `simulationState@0x40==1`; the + `simulationFlags|=0x1` DelayWatchersFlag poisoning removed everywhere + ([[reconstruction-gotchas]] §20 — emitter SetDirty split into ForceUpdate/ExecuteOnUpdate per + site); view/target gating + the `bin->FeedAmmo` pull + recoil set moved into the caller's + Loaded case with the authentic 4-blip stay-Loaded denial; recoil bleeds ONLY in Loading at + electrical-Ready and clamps at 0; slot-17 @004b9c9c called from Loading/7 (the launcher + recharge dial now ANIMATES). State 7 kept binary-faithful (re-asserted unconditionally — + permanent once entered; recovery only via ResetToInitialState/mission reset). Solo-verified + on 4.11.388+: 54-60 `[projectile] PUSH` + impact smoke; denial window (BT_LOOK_TEST + look-back) = trig pulses, state STAYS 2, zero launches, resumes on release; disabled-mech + gate pins enemy launchers at 7+full recoil; STREAK-6 dial sweeps across screenshots; lasers + fire throughout. Open fidelity question REMAINS: is launcher FailureHeat/jam-rate at + sustained autofire authentic-intensity? (both SRM6s jam within ~2 min of held autofire via + the bring-up CheckForJam heat gate; emitters never trip it — task #11 duty-cycle + measurement.) (3) *J/K/L preset hypothesis REFUTED for solo* [T2]: BT_PRESET_TEST cycling on 387 — missiles kept launching after presets (36 PUSH before / 24 after), pip count unchanged, gauges live, [mode] masks touch ONLY ModeMFD bits 0-14 (`reticleElementMask` is a different object). diff --git a/game/reconstructed/btl4app.cpp b/game/reconstructed/btl4app.cpp index 5254d9b..3f23292 100644 --- a/game/reconstructed/btl4app.cpp +++ b/game/reconstructed/btl4app.cpp @@ -697,6 +697,31 @@ BTL4Application::SharedData << (int)(gauge_renderer != NULL) << " -> ConfigureForModel(Init)\n" << std::flush; if (gauge_renderer != NULL) { + // + // GAUGE-TREE SURVIVAL ACROSS THE IN-SESSION RE-STREAM (Gitea #12 fix): + // when the mechs are RE-CREATED pre-launch (host drop/rejoin re-stream), + // this handler runs AGAIN for the new viewpoint mech -- but the + // lobby-built gauge tree still holds attribute pointers into the FREED + // first-stream mech; every widget then faults and the BT_DEV_GAUGES SEH + // guard disables it permanently (GAUGE.cpp:618) = the frozen dev-gauges + // window. The engine's own transition path is the Shutdown teardown + // (Application::Shutdown -> gaugeRenderer->Shutdown() -> Remove(0), + // APP.cpp:787 / GAUGREND.cpp:3264) followed by a lazy rebuild; the + // re-stream bypasses it. So: tear the entity-bound tree down HERE + // (alarms + gauges + entity grids -- see TearDownForViewpointRestream) + // and let the ConfigureForModel below rebuild it against THIS mech. + // A no-op on the first (lobby) build. + // + static int s_gaugeTreeBuilt = 0; + if (s_gaugeTreeBuilt) + { + DEBUG_STREAM << "[gauge] viewpoint re-stream: tearing the gauge tree " + "down for rebuild on entity " << (void *)viewing_entity + << "\n" << std::flush; + gauge_renderer->TearDownForViewpointRestream(); + } + s_gaugeTreeBuilt = 1; + gauge_renderer->ConfigureForModel("Init", viewing_entity); // FUN_0046ff64 } diff --git a/game/reconstructed/btl4grnd.cpp b/game/reconstructed/btl4grnd.cpp index f80cc0e..eba2878 100644 --- a/game/reconstructed/btl4grnd.cpp +++ b/game/reconstructed/btl4grnd.cpp @@ -73,6 +73,7 @@ #if !defined(L4LAMP_HPP) # include #endif +#include // GaugeAlarmManager (TearDownForViewpointRestream, Gitea #12) #if !defined(L4WARE_HPP) # include #endif @@ -269,6 +270,49 @@ Logical 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", +// ) 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 diff --git a/game/reconstructed/btl4grnd.hpp b/game/reconstructed/btl4grnd.hpp index 56acab4..d32dd82 100644 --- a/game/reconstructed/btl4grnd.hpp +++ b/game/reconstructed/btl4grnd.hpp @@ -54,6 +54,23 @@ NotifyOfNewInterestingEntity(Entity *entity); void NotifyOfBecomingUninterestingEntity(Entity *entity); + + // + // PORT FIX (Gitea #12 -- NOT in the surviving 1995 header): tear the + // built gauge tree down when the viewpoint mech is RE-STREAMED + // in-session (pre-launch host drop/rejoin re-creates every mech; the + // lobby-built tree's attribute pointers dangle and each widget is + // permanently DISABLED by the BT_DEV_GAUGES SEH guard, GAUGE.cpp:618). + // Performs the entity-bound half of the engine's mission-transition + // teardown (GaugeRenderer::ShutdownImplementation, GAUGREND.cpp:3264): + // alarms + all gauges + the entity grids. Warehouse / graphics ports / + // interpreter / controls lamps stay -- entity-independent, reused by + // the immediate ConfigureForModel rebuild. Called from + // BTL4Application::MakeViewpointEntity before ConfigureForModel; + // a no-op on the first (lobby) build. + // + void + TearDownForViewpointRestream(); };