Cockpit: the COOLANT VALVE control -- the heat-management triad completes (task #13)
The pilot's third heat tool: Condenser::MoveValve (id 4, the Condenser handler table @0x50E52C -- PE-verified, exactly one entry) registered via the order-proof function-local accessor and GUARDED by the real FUN_004ac9c8 (task #12's BTPlayerRoleLocksAdvanced rookie-role bridge -- the old deferral note blamed the 0xBD3 messmgr, a stale misattribution, swept). Each press cycles the valve 1 -> 5 -> 50 -> 0 -> 1 and RecomputeCondenserValves re-shares the fixed total coolant flow across all six condensers -- opening one valve wide starves the others: the pilot's cooling-triage lever. Desktop: 'C' cycles the selected condenser's valve (BT_VALVE_SLOT picks the roster slot; default Condenser1). BT_VALVE_TEST scripts a headless press. Verified live: press -> "[valve] condenser#1 valveState=5 flow=0.5 (total=10)" -- exactly the authored redistribution math (5/(5+5x1)), through the real role gate. The heat-management triad is now complete and all-authentic: G + fire buttons = regroup weapons across buttons (task #6) F5-F8 / F9 = reassign weapon generators / mode (task #12) C = coolant valve triage (task #13) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d7b900d108
commit
80cadc98c7
@@ -89,13 +89,14 @@ At the authentic 1e7-unit economy (tasks #9-#10) the Heat MFD is fully dynamic:
|
||||
fire, and the bank plateaus ~600 — temperatures, heatLoad and the heat alarms all animate. [T2]
|
||||
|
||||
## Remaining = DATA FEEDS, not widgets (deferred systems)
|
||||
The **condenser valve CONTROL** gates on the owning **BTPlayer**'s game-mode flag (`mech+0x190`,
|
||||
`GetPlayerLink()`, via `MechSubsystem::IsDamaged`/`FUN_004ac9c8` reading `player+0x274`) — NOT the
|
||||
0xBD3 messmgr (a common earlier misconception; 0xBD3 is a damage/explosion hub at `Mech[0x10d]`=0x434).
|
||||
In a basic mission the valve/mode flags are likely off, so the dormant valve is plausibly AUTHENTIC —
|
||||
see [[open-questions]] `mech+0x190`. The **MessageBoard** feed needs **StatusMessagePool** (a NULL
|
||||
stub) + the per-player status queue. `SeekVoltageGraph`'s 4 Seek* attrs are a cluster-child (not
|
||||
config-called). [T2]
|
||||
**✅ The condenser valve CONTROL is LIVE (task #13, 2026-07-11) [T2]:** `MoveValve` (id 4, the
|
||||
Condenser handler table @0x50E52C — exactly one entry) registered + guarded by the REAL
|
||||
FUN_004ac9c8 = `player+0x274 roleClassIndex == 0` (the ROOKIE-role lockout; task #12's
|
||||
`BTPlayerRoleLocksAdvanced` bridge — the old "game-mode flag / likely off in a basic mission"
|
||||
hedge is superseded: bring-up role = 2 = UNLOCKED, verified live: press → valveState 1→5 →
|
||||
flow redistribution 1/6 → 5/10). Desktop: 'C' cycles the selected condenser (BT_VALVE_SLOT).
|
||||
The **MessageBoard** feed needs **StatusMessagePool** (a NULL stub) + the per-player status
|
||||
queue. `SeekVoltageGraph`'s 4 Seek* attrs are a cluster-child (not config-called). [T2]
|
||||
|
||||
## Cockpit HUD reticle (main screen, inside view) — LIVE
|
||||
`BTReticleRenderable` (0x358 bytes, ctor @004cc40c) draws over the finished 3D frame in cockpit
|
||||
|
||||
@@ -535,6 +535,12 @@ inline int
|
||||
//
|
||||
public:
|
||||
static Derivation *GetClassDerivations();
|
||||
// task #13: the Condenser handler table @0x50E52C -- exactly ONE entry,
|
||||
// {4, "MoveValve", @4ae464} (PE-verified). Per-receiver-class id space:
|
||||
// id 4 to a condenser = MoveValve; to a weapon = SelectGeneratorA.
|
||||
// Table + set are function-local statics in the accessor (the task #12
|
||||
// static-init-order trap).
|
||||
static Receiver::MessageHandlerSet& GetMessageHandlers();
|
||||
static SharedData DefaultData;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -554,8 +560,11 @@ inline int
|
||||
public:
|
||||
void
|
||||
RefrigerationSimulation(Scalar time_slice); // @4ae4d8 (vtable slot 9)
|
||||
enum { MoveValveMessageID = 4 }; // table @0x50E52C [T1]
|
||||
void
|
||||
MoveValve(int message); // @4ae464 (valve cycle 1->5->50->0; dormant until 0xBD3)
|
||||
MoveValveMessageHandler( // @4ae464 (valve cycle 1->5->50->0)
|
||||
ReceiverDataMessageOf<int> *message
|
||||
);
|
||||
virtual void
|
||||
SetValveSetting(int setting) { valveState = setting; }
|
||||
|
||||
|
||||
@@ -148,26 +148,26 @@ void
|
||||
}
|
||||
|
||||
//
|
||||
// @4ae464 -- the REAL "MoveValve" message handler (disassembled; the assert-anchored
|
||||
// exporter did not capture it). Cycles the coolant valve through its FOUR settings
|
||||
// 1 -> 5 -> 50 -> 0 -> 1 (NOT the 0/1/2 the earlier draft guessed -- that draft had
|
||||
// FUN_004afbe0's body, which is a CONTROLS-mapper display-mode cycler @0x190, a
|
||||
// mislabel). After changing valveState it redistributes coolant flow across all
|
||||
// condensers (RecomputeCondenserValves) so every ValveSetting gauge updates.
|
||||
// @4ae464 -- the "MoveValve" message handler (id 4, table @0x50E52C [T1]).
|
||||
// Cycles the coolant valve through its FOUR settings 1 -> 5 -> 50 -> 0 -> 1,
|
||||
// then redistributes coolant flow across all condensers
|
||||
// (RecomputeCondenserValves) so every ValveSetting gauge updates.
|
||||
//
|
||||
// ROUTE/GUARD are DEFERRED to WAVE 8: the binary guards on
|
||||
// owner->messageManager(+0x190)->field_0x274 == 0 (FUN_004ac9c8, the 0xBD3
|
||||
// SubsystemMessageManager, unreconstructed) and the message that CALLS this arrives
|
||||
// through that same manager. Reproducing the raw owner+0x190 deref would be a
|
||||
// databinding trap in our layout, and there is no route to invoke this yet, so the
|
||||
// handler is faithful-but-DORMANT: the valve gauge shows the authentic static 1/N
|
||||
// (RecomputeCondenserValves at ctor) until the message manager is reconstructed.
|
||||
// task #13 -- ROUTE + GUARD LANDED (the old deferral note claimed the guard
|
||||
// was the 0xBD3 messmgr; task #12 proved FUN_004ac9c8 reads mech+0x190 ->
|
||||
// the owning BTPlayer's roleClassIndex(+0x274) == 0 -- the ROOKIE-role
|
||||
// lockout, implemented as the BTPlayerRoleLocksAdvanced bridge). Registered
|
||||
// under id 4 via Condenser::GetMessageHandlers(); the pod route is the
|
||||
// engineering-screen aux buttons (type-6 EventMappings), the desktop route
|
||||
// is the 'C' key (mech4 harness).
|
||||
//
|
||||
void
|
||||
Condenser::MoveValve(int message) // FUN_004ae464
|
||||
Condenser::MoveValveMessageHandler(ReceiverDataMessageOf<int> *message) // FUN_004ae464
|
||||
{
|
||||
// binary: if (FUN_004ac9c8(this)) return; -- messmgr guard, deferred (see note)
|
||||
if (message /* *(int*)(param_2+0xc) = msg->count */ <= 0)
|
||||
extern int BTPlayerRoleLocksAdvanced(void *owner_mech); // btplayer.cpp (FUN_004ac9c8)
|
||||
if (BTPlayerRoleLocksAdvanced(owner))
|
||||
return;
|
||||
if (message->dataContents <= 0) // press only (*(int*)(msg+0xc))
|
||||
return;
|
||||
|
||||
switch (valveState) // this[0x74] @0x1D0
|
||||
@@ -181,6 +181,25 @@ void
|
||||
BTRecomputeCondenserValves((Entity *)owner); // FUN_0049f788(this[0xd0]=owner Mech)
|
||||
}
|
||||
|
||||
//
|
||||
// task #13 -- the Condenser handler registration (table @0x50E52C: exactly
|
||||
// one entry). Chained onto the engine Receiver root set; function-local
|
||||
// statics per the static-init-order rule (reconstruction-gotchas #9).
|
||||
//
|
||||
Receiver::MessageHandlerSet&
|
||||
Condenser::GetMessageHandlers()
|
||||
{
|
||||
static const Receiver::HandlerEntry entries[]=
|
||||
{
|
||||
MESSAGE_ENTRY(Condenser, MoveValve) // id 4 @4ae464
|
||||
};
|
||||
static Receiver::MessageHandlerSet messageHandlers(
|
||||
ELEMENTS(entries), entries,
|
||||
Receiver::GetMessageHandlers()
|
||||
);
|
||||
return messageHandlers;
|
||||
}
|
||||
|
||||
//
|
||||
// @4ae658 -- parse the Condenser resource record. Stamps classID 0x0BBD and
|
||||
// model size 0x100, defaults RefrigerationFactor to -1.0f on pass 1, then
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
// @4ae5fc ~Condenser(byte flags) -- vtable slot 0
|
||||
// @4ae63c TestInstance() -- IsDerivedFrom 0x50e4fc
|
||||
// @4ae658 CreateStreamedSubsystem(...) -- parses RefrigerationFactor
|
||||
// @4afbe0 MoveValve(message) [best-effort] -- cycles valveState 0->1->2->0,
|
||||
// @4afbe0 MoveValveMessageHandler (id 4) -- valve cycle 1->5->50->0 (live),
|
||||
// drives owner HUD gauges
|
||||
|
||||
//###########################################################################
|
||||
|
||||
@@ -647,6 +647,7 @@ static int gBTMissileKey = 0;
|
||||
static int gBTConfigKey = 0; // task #6: HOLD 'G' = the weapon-configure button
|
||||
static int gBTGenSelKey = 0; // task #12: F5..F8 = SelectGeneratorA..D, F9 = mode toggle
|
||||
// (0 idle; else the message id 4..8)
|
||||
static int gBTValveKey = 0; // task #13: 'C' = MoveValve on the selected condenser
|
||||
|
||||
// Damage: each shot dispatches a REAL Entity::TakeDamageMessage to the target. Now
|
||||
// that the damage zones are constructed (mech.cpp Pass-3 zone build), the engine base
|
||||
@@ -1540,6 +1541,9 @@ void
|
||||
: (focused && (pAsync(0x77 /*F8*/) & dn)) ? 7
|
||||
: (focused && (pAsync(0x78 /*F9*/) & dn)) ? 8
|
||||
: 0;
|
||||
// task #13: 'C' cycles the coolant valve (BT_VALVE_SLOT
|
||||
// picks the condenser roster slot; default = Condenser1).
|
||||
gBTValveKey = focused && (pAsync('C') & dn) ? 1 : 0;
|
||||
// gBTDrive.fire = "any weapon trigger down" (feeds the bring-up
|
||||
// damage dispatcher + the beam-visual keepalive)
|
||||
gBTDrive.fire = (gBTLaserKey || gBTPPCKey || gBTMissileKey) ? 1 : 0;
|
||||
@@ -3123,6 +3127,60 @@ void
|
||||
gBTGenSelKey = (s_gsFrame >= 600 && s_gsFrame < 610) ? 7 : 0;
|
||||
}
|
||||
|
||||
// task #13 scripted verify (BT_VALVE_TEST=1): one MoveValve press at
|
||||
// frame ~600 -- Condenser1's valveState cycles 1 -> 5, so the flow
|
||||
// redistribution gives it 5/10 of the total coolant flow ([valve] log
|
||||
// + its bank conduction speeds up 5x).
|
||||
if ((Entity *)this == application->GetViewpointEntity()
|
||||
&& getenv("BT_VALVE_TEST"))
|
||||
{
|
||||
static int s_vtFrame = 0;
|
||||
++s_vtFrame;
|
||||
gBTValveKey = (s_vtFrame >= 600 && s_vtFrame < 610) ? 1 : 0;
|
||||
}
|
||||
|
||||
// task #13 dev harness: the COOLANT VALVE lever. 'C' edge -> dispatch
|
||||
// MoveValve (id 4, the Condenser table @0x50E52C) to the selected
|
||||
// condenser -- the same press payload the pod's engineering-screen aux
|
||||
// buttons deliver.
|
||||
if ((Entity *)this == application->GetViewpointEntity())
|
||||
{
|
||||
static int s_prevValve = 0;
|
||||
if (gBTValveKey != s_prevValve)
|
||||
{
|
||||
s_prevValve = gBTValveKey;
|
||||
if (gBTValveKey != 0)
|
||||
{
|
||||
Subsystem *condenser = 0;
|
||||
const char *slotEnv = getenv("BT_VALVE_SLOT");
|
||||
int wantSlot = (slotEnv != 0) ? atoi(slotEnv) : -1;
|
||||
for (int s = 1; s < GetSubsystemCount(); ++s)
|
||||
{
|
||||
Subsystem *sub = GetSubsystem(s);
|
||||
if (sub == 0)
|
||||
continue;
|
||||
if (wantSlot >= 0 ? (s == wantSlot)
|
||||
: ((int)sub->GetClassID() == 0xBBD)) // Condenser
|
||||
{
|
||||
condenser = sub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (condenser != 0)
|
||||
{
|
||||
if (getenv("BT_FIRE_LOG"))
|
||||
DEBUG_STREAM << "[valve-tx] MoveValve -> "
|
||||
<< condenser->GetName() << std::endl;
|
||||
ReceiverDataMessageOf<ControlsButton> msg(
|
||||
4 /*Condenser::MoveValveMessageID*/,
|
||||
sizeof(ReceiverDataMessageOf<ControlsButton>),
|
||||
(ControlsButton)1 /*press*/);
|
||||
condenser->Dispatch(&msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// task #12 dev harness: the POWER-ROUTING buttons. F5..F9 edge ->
|
||||
// dispatch SelectGeneratorA..D / ToggleGeneratorMode (ids 4..8) to the
|
||||
// selected weapon -- the same ReceiverDataMessageOf<ControlsButton>
|
||||
|
||||
Reference in New Issue
Block a user