diff --git a/context/gauges-hud.md b/context/gauges-hud.md index 1680758..3331d44 100644 --- a/context/gauges-hud.md +++ b/context/gauges-hud.md @@ -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 diff --git a/game/reconstructed/heat.hpp b/game/reconstructed/heat.hpp index 90134bf..bc3dbb3 100644 --- a/game/reconstructed/heat.hpp +++ b/game/reconstructed/heat.hpp @@ -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 *message + ); virtual void SetValveSetting(int setting) { valveState = setting; } diff --git a/game/reconstructed/heatfamily_reslice.cpp b/game/reconstructed/heatfamily_reslice.cpp index d252b14..2172b47 100644 --- a/game/reconstructed/heatfamily_reslice.cpp +++ b/game/reconstructed/heatfamily_reslice.cpp @@ -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 *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 diff --git a/game/reconstructed/heatfamily_reslice.hpp b/game/reconstructed/heatfamily_reslice.hpp index d9163cc..aec53ff 100644 --- a/game/reconstructed/heatfamily_reslice.hpp +++ b/game/reconstructed/heatfamily_reslice.hpp @@ -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 //########################################################################### diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 69c2109..afb9eb4 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -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 msg( + 4 /*Condenser::MoveValveMessageID*/, + sizeof(ReceiverDataMessageOf), + (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