From e9db1614040cdb76b4f7509b0f0de1fc4116cdc4 Mon Sep 17 00:00:00 2001 From: arcattack Date: Tue, 21 Jul 2026 14:31:46 -0500 Subject: [PATCH] Issue #20: wire Mech BalanceCoolant (id 0x16) -- the auto-cooling-balance button Third instance of the silent-swallow pattern (unregistered handler id -> Receiver NullHandler). The Mech handler table @0x50BDF8 binds {0x16, "BalanceCoolant" -> @0049f728}; the reconstruction never registered it. Disasm-verified body (@0049f728): press-only guard (msg+0xc > 0; the binary has NO novice lockout on this one), set EVERY condenser's valveState@0x1D0 to 1 (equal weights), then the shared redistribute @0049f788 -- which the port ALREADY had faithfully as BTRecomputeCondenserValves (flow = valve/total + condenserAlarm pulse; MoveValve calls it). So the fix is: - mech.hpp/mech.cpp: BalanceCoolantMessageID=0x16 + handler + MESSAGE_ENTRY - heatfamily_reslice.cpp: BTBalanceCondenserValves bridge (Condenser is a complete type there; sets valves to 1 + redistributes) - mech4.cpp: BT_BALTEST scripted harness (MoveValve press alternating with a Balance press) VERIFIED headless (BT_BALTEST=1 BT_VALVE_LOG=1): boot -> all valves 1, flows 0.1667 each MoveValve-> condenser#1 valve 5, flows 0.5 / 0.1x5 (the priority boost) BALANCE -> all valves 1, flows 0.1667 each (equalized) Awaiting the tester's live ADV-mode confirmation. KB: decomp-reference.md Mech-table row marked wired. Co-Authored-By: Claude Opus 4.8 (1M context) --- context/decomp-reference.md | 2 +- game/reconstructed/heatfamily_reslice.cpp | 21 +++++++++++++ game/reconstructed/mech.cpp | 20 ++++++++++++ game/reconstructed/mech.hpp | 10 ++++++ game/reconstructed/mech4.cpp | 37 +++++++++++++++++++++++ 5 files changed, 89 insertions(+), 1 deletion(-) diff --git a/context/decomp-reference.md b/context/decomp-reference.md index f0c17b2..be36662 100644 --- a/context/decomp-reference.md +++ b/context/decomp-reference.md @@ -246,7 +246,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a ### Binary message tables decoded 2026-07-20 (glass input audit) [T1] 16-byte HandlerEntry {id, namePtr, fnPtr, 0} rows in section_dump. Per-receiver-class id spaces. - **Mech (entity)** @0x50BDF8..: 0x12 TakeDamage@004a0230, 0x14 PlayerLink@0049f624, 0x15 - RealMaxSpeed@0049f604, **0x16 BalanceCoolant@0049f728**, 0x17 SetBurningState@0049f674, 0x18 + RealMaxSpeed@0049f604, **0x16 BalanceCoolant@0049f728 (WIRED 2026-07-21, issue #20**: press-only, NO novice guard; sets every condenser valveState@0x1D0=1 then the shared redistribute @0049f788 == BTRecomputeCondenserValves; verify BT_BALTEST=1 + BT_VALVE_LOG=1**)**, 0x17 SetBurningState@0049f674, 0x18 ClearBurningState@0049f700, **0x19 EjectPilot@0049f854**, **0x1a DuckRequest@0049fa00** (the manual's CROUCH; streamed button 0x13 sends it — unreconstructed). - **HeatableSubsystem** @0x50E41C: {3, "ToggleCooling"→@004ad6f8}. **Disassembled 2026-07-20** diff --git a/game/reconstructed/heatfamily_reslice.cpp b/game/reconstructed/heatfamily_reslice.cpp index 9c034a4..6f978c5 100644 --- a/game/reconstructed/heatfamily_reslice.cpp +++ b/game/reconstructed/heatfamily_reslice.cpp @@ -1342,3 +1342,24 @@ void BTRecomputeCondenserValves(Entity *owner) << " (total=" << total << ")\n" << std::flush; } } + +// +// BTBalanceCondenserValves -- the body of Mech's BalanceCoolant handler +// (@0049f728, id 0x16 -- issue #20): set EVERY condenser's valveState@0x1D0 +// to 1 (equal weights) then run the shared redistribute @0049f788. Lives +// here because Condenser is a complete type in this TU (the Mech TU cannot +// touch valveState). +// +void BTBalanceCondenserValves(Entity *owner) +{ + Check(owner); + Derivation &condClass = *Condenser::GetClassDerivations(); + int count = owner->GetSubsystemCount(); + for (int i = 0; i < count; ++i) + { + Subsystem *s = owner->GetSubsystem(i); + if (s != 0 && s->IsDerivedFrom(condClass)) + ((Condenser *)s)->valveState = 1; // *(iVar1+0x1d0) = 1 + } + BTRecomputeCondenserValves(owner); // FUN_0049f788 +} diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 0722b05..8f09c40 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -461,8 +461,28 @@ const Receiver::HandlerEntry { MESSAGE_ENTRY(Mech, TakeDamage), MESSAGE_ENTRY(Mech, PlayerLink), + MESSAGE_ENTRY(Mech, BalanceCoolant), // id 0x16 @0049f728 (issue #20) }; +// +// @0049f728 -- BalanceCoolant (id 0x16): the auto-cooling-balance button. +// Disasm-verified: press-only guard (NO novice lockout in the binary), then +// equal-weight every condenser valve + redistribute via the condenser- +// complete bridge. Issue #20 fix, 2026-07-21. +// +void + Mech::BalanceCoolantMessageHandler(ReceiverDataMessageOf *message) +{ + if (message->dataContents <= 0) // press only (msg+0xc) + { + return; + } + extern void BTBalanceCondenserValves(Entity *owner); // heatfamily_reslice.cpp + BTBalanceCondenserValves(this); + if (getenv("BT_VALVE_LOG")) + DEBUG_STREAM << "[valve] BALANCE (all valves -> 1)" << std::endl; +} + Receiver::MessageHandlerSet Mech::MessageHandlers( ELEMENTS(Mech::MessageHandlerEntries), diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index 5923d68..7fa7076 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -1180,6 +1180,16 @@ protected: // vehicle SCORES/ranks), and on the master seeds the heat bank's // ambientTemperature from the mission's [mission] temperature. void PlayerLinkMessageHandler(PlayerLinkMessage *message); + // @0049f728 -- "BalanceCoolant" (Mech handler table @0x50BDF8, id 0x16; + // the pod's auto-cooling-balance button). Issue #20 (2026-07-21): the + // id was never registered -- silently swallowed. Disasm-verified body: + // press-only (msg+0xc > 0; NO novice guard in the binary), set EVERY + // condenser's valveState@0x1D0 = 1 (equal weights), then the shared + // redistribute @0049f788 (== BTRecomputeCondenserValves: flow = + // valve/total + condenserAlarm pulse). Body lives in the condenser- + // complete TU (heatfamily_reslice.cpp bridge BTBalanceCondenserValves). + enum { BalanceCoolantMessageID = 0x16 }; + void BalanceCoolantMessageHandler(ReceiverDataMessageOf *message); // --- damage-routing support (mechdmg / mech4) ----------------------- // Typed access to the inherited Entity::damageZones[] (engine stores DamageZone*; diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 0536577..ae72988 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -5654,6 +5654,43 @@ void } } + // issue #20 verify (BT_BALTEST): alternate a MoveValve (id 4) press to + // the first condenser with a Mech BalanceCoolant (id 0x16) press every + // ~2s -- BT_VALVE_LOG shows the valve going 1->5 then BALANCE pulling + // every valve back to 1 with equal flows. + if (getenv("BT_BALTEST") && (Entity *)this == application->GetViewpointEntity()) + { + static int s_balTest = 0; + if ((++s_balTest % 120) == 0) + { + int phase = (s_balTest / 120) & 1; + if (phase == 0) + { + for (int s = 1; s < GetSubsystemCount(); ++s) + { + Subsystem *sub = GetSubsystem(s); + if (sub != 0 && (int)sub->GetClassID() == 0xBBD) // Condenser + { + ReceiverDataMessageOf msg( + 4 /*Condenser::MoveValveMessageID*/, + sizeof(ReceiverDataMessageOf), + (ControlsButton)1); + sub->Dispatch(&msg); + break; + } + } + } + else + { + ReceiverDataMessageOf msg( + 0x16 /*Mech::BalanceCoolantMessageID*/, + sizeof(ReceiverDataMessageOf), + (ControlsButton)1); + Dispatch(&msg); + } + } + } + // task #6 dev harness: the config-session BRACKET. 'G' edge -> // dispatch ConfigureMappables (id 9) press/release to the selected // weapon -- the exact ReceiverDataMessageOf payload