From a8a0042f288745ad6bddb6a57e82827b7d774ed7 Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Fri, 31 Jul 2026 08:26:40 -0500 Subject: [PATCH] #88: coolant leaks LIVE -- the #64 damageZone shadow was the single break in the leak chain HeatSink::UpdateCoolant prices the damage-driven leak from the QUALIFIED engine member (Subsystem::damageZone @0xE0): coolantDraw = zoneDamage * heatLoad, with the coolantActive hysteresis (= the ReportLeak attribute the 19 authored leak watchers ride). But the MechSubsystem ctor only ever filled its re-declared SHADOW member (gitea #64, gotcha #1), so the engine member stayed NULL forever, zoneDamage pinned 0, and a coolant leak was STRUCTURALLY IMPOSSIBLE no matter how much damage landed -- Oracle's night-7 report exactly. The #80 crit fix and the #83 collision rattle write real subsystem-zone damage, but into an object the leak reader could never see. Fix: alias the ENGINE base member to the same zone in both MechSubsystem ctors. Every shadow reader keeps working (same object); the engine base Subsystem::TakeDamage latent null-AV (#64 consequence 2) is disarmed; the full #64 de-shadow sweep remains the long-term cleanup. Verified live (solo, BT_COOL_LOG/BT_CRIT_LOG): collision rattle drove a damaged Myomers to [cool] draw = dmg*heatLoad with the level draining; sustained weapon fire rolled real crits ([critroll]) and produced 588 leak-pricing lines. Authored routing extracted from BTL4.RES: collision rattle targets HeatSinkBank 0.3 / Gyro 0.35 / Torso 0.25 / Myomers 0.35 (Condenser+Reservoir authored 0 for collisions); weapon crits select via the zone's crit-entry list. Known-remaining fidelity item (documented): HeatSink::DrawCoolant (slot 14, central top-up) is still a return-0 TODO. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019Zh7PTkFy4KwTzVighLR9J --- context/subsystems.md | 18 ++++++++++++++++++ game/reconstructed/heat.cpp | 8 ++++++-- game/reconstructed/mechsub.cpp | 11 +++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/context/subsystems.md b/context/subsystems.md index d0ac3c8..14150b8 100644 --- a/context/subsystems.md +++ b/context/subsystems.md @@ -167,6 +167,24 @@ its electrical state. The full chain, byte-verified [T1]: - **STILL DEAD:** factory loops 2-4 (heatable/weapon/damageable capability rosters) go through the `SubProxy` stub whose `IsDerivedFrom` returns 0 — they add NOTHING. See [[open-questions]]. +## Coolant LEAKS (Gitea #88 fix, 2026-07-31) — the #64 shadow was the single break [T2] +`HeatSink::UpdateCoolant` (@004adbf8) prices the damage-driven leak: `coolantDraw = +ownZone->damageLevel × heatLoad`, floor 0.0025, and the `coolantActive@0x138` hysteresis +(ON >0.003) IS the `ReportLeak` attribute the 19 authored leak watchers (3-note warning) ride. +The read is the **qualified ENGINE member** `Subsystem::damageZone@0xE0` — which the port's +MechSubsystem ctor never assigned (it filled only its re-declared shadow, gitea #64 / gotcha #1), +so `zoneDamage` pinned 0 and a leak was **structurally impossible** regardless of damage. Fixed by +ALIASING the engine base member to the same zone in both MechSubsystem ctors (mechsub.cpp) — every +shadow reader is untouched (same object), the engine base `Subsystem::TakeDamage` null-AV is +disarmed, and the #64 full de-shadow sweep remains the long-term cleanup. Verified live: collision +rattle and weapon crits (`[critroll]`) both drive `[cool]` leak lines (draw ≈ dmg×heatLoad, level +draining, hysteresis arming). **Authored damage routing** (BTL4.RES): collision rattle targets +HeatSinkBank 0.3 / Gyro 0.35 / Torso 0.25 / Myomers 0.35 — Condenser + Reservoir are authored 0 +for collisions; weapon crits select via the ZONE's crit-entry list. Remaining fidelity item: +`HeatSink::DrawCoolant` (vtable slot 14, the central-system top-up) is still a `return 0` TODO, so +a leaking subsystem drains its own loop; the pull-through to the central Reservoir gauge rides the +loop-flow machinery. + ## The coolant FLUSH (Gitea #7, 2026-07-19) — Reservoir InjectCoolant end-to-end [T2 live-verified] The manual-p24 coolant button (coolant MFD top-right; punch or HOLD): message id **4 "InjectCoolant"** on the Reservoir (handler table @**0x50e680**, one entry → @4aee70 — same per-receiver id space as the diff --git a/game/reconstructed/heat.cpp b/game/reconstructed/heat.cpp index 3d37372..e257d39 100644 --- a/game/reconstructed/heat.cpp +++ b/game/reconstructed/heat.cpp @@ -952,8 +952,12 @@ void // to empty every frame -- the opposite of the authentic near-static behavior. // An undamaged subsystem has damageLevel 0 -> coolantDraw 0 -> NO leak (the coolant // bars stay full on a pristine mech); the draw rises only as the heat sink / - // condenser itself takes battle damage. (Read the qualified engine zone -- the - // nearer MechSubsystem::damageZone is a shim SHADOW; see mechweap.cpp:252.) + // condenser itself takes battle damage. (#88 fix 2026-07-31: this qualified + // engine-member read was NULL forever -- the MechSubsystem ctor only filled its + // re-declared SHADOW, so `zoneDamage` pinned 0 and a leak was structurally + // impossible no matter how much damage landed. The ctor now ALIASES the engine + // base member to the same zone (mechsub.cpp, the #64 half-fix), so this read + // sees the real crit/rattle damage.) ::DamageZone *ownZone = this->Subsystem::damageZone; // @0xE0 (word 0x38) Scalar zoneDamage = (ownZone != 0) ? ownZone->damageLevel : 0.0f; // +0x158 coolantDraw = zoneDamage * heatLoad; // *(this[0x38]+0x158) * this[0x48] diff --git a/game/reconstructed/mechsub.cpp b/game/reconstructed/mechsub.cpp index 36d3d10..994e942 100644 --- a/game/reconstructed/mechsub.cpp +++ b/game/reconstructed/mechsub.cpp @@ -152,6 +152,16 @@ MechSubsystem::MechSubsystem( printSimulationState = 0; // this[0x41] configureActivePress = -1; // this[0x44] configure-session idle damageZone = (ReconDamageZone *)new DamageZone(this, 0); // this[0x38] = FUN_0041de1c(new 0x160, this, 0) + // #64/#88: ALIAS the ENGINE base member to the same zone. The binary has + // ONE cell (subsystem word 0x38); the port's re-declared shadow left + // Subsystem::damageZone null forever, so every engine-member reader -- + // HeatSink::UpdateCoolant's leak feed (`coolantDraw = zoneDamage * + // heatLoad`), the engine base TakeDamage -- saw a pristine/null zone. + // With the crit sink + collision rattle now writing real damage into the + // (same) zone, the null base was the single break in the coolant-leak + // chain. Aliasing (not moving) keeps every shadow reader intact; the + // full #64 de-shadow sweep remains the long-term cleanup. + Subsystem::damageZone = (::DamageZone *)damageZone; } // @@ -190,6 +200,7 @@ MechSubsystem::MechSubsystem( collisionCriticalHitWeight = subsystem_resource->collisionCriticalHitWeight; // res+0xdc damageZone = (ReconDamageZone *)new DamageZone(this, 0); // this[0x38] + Subsystem::damageZone = (::DamageZone *)damageZone; // #64/#88 alias -- see the sibling ctor BindName((char *)damageZone + 0x15c, GetName()); // FUN_00402a98(., dz+0x15c, name) //