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) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-21 14:31:46 -05:00
co-authored by Claude Opus 4.8
parent 4aab10ba89
commit e9db161404
5 changed files with 89 additions and 1 deletions
+1 -1
View File
@@ -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**
+21
View File
@@ -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
}
+20
View File
@@ -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<int> *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),
+10
View File
@@ -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<int> *message);
// --- damage-routing support (mechdmg / mech4) -----------------------
// Typed access to the inherited Entity::damageZones[] (engine stores DamageZone*;
+37
View File
@@ -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<ControlsButton> msg(
4 /*Condenser::MoveValveMessageID*/,
sizeof(ReceiverDataMessageOf<ControlsButton>),
(ControlsButton)1);
sub->Dispatch(&msg);
break;
}
}
}
else
{
ReceiverDataMessageOf<ControlsButton> msg(
0x16 /*Mech::BalanceCoolantMessageID*/,
sizeof(ReceiverDataMessageOf<ControlsButton>),
(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<ControlsButton> payload