diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 7942629..5ae9906 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -510,8 +510,18 @@ void { return; } - duckState = 1; - DEBUG_STREAM << "[duck] DuckRequest: duckState -> 1" << std::endl << std::flush; + // #142: duckState is the DESIRED POSTURE, and it is what the cockpit's + // crouch-symbol animation reads (L4GAUGE.CFG:5001 binds attribute 0x37 to a + // 3-frame bduck.pcc strip). The binary writes a bare 1 here and clears the + // cell only in Mech::Reset -- with no per-frame reader anywhere in the + // export, so nothing there ever brings it back to 0 for a rise. A pod + // pilot's second press has to un-crouch, and the symbol has to follow, so + // the port TOGGLES. Documented divergence, deliberately minimal: one cell, + // same meaning, and the consumer (mech4.cpp) now acts on desired-vs-actual + // rather than consuming the cell out from under the gauge. + duckState = (duckState != 0) ? 0 : 1; + DEBUG_STREAM << "[duck] DuckRequest: duckState -> " << duckState + << (duckState ? " (crouch)" : " (rise)") << std::endl << std::flush; } // diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 51be77e..63fda7a 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -4420,35 +4420,41 @@ void // stability, so peers pose the squat for free) and flip the // stability alarm (ducked = 0, risen = 1). The request is // consumed whenever both gates passed, hit or miss. - // #142 RECEIPT (ungated, 1 Hz). A pending duck request that produces - // no posture change is INVISIBLE today: if squatCapable is 0 the - // consumer below is skipped entirely -- no log, and duckState is not - // even consumed, so the latch sticks at 1 forever. The other miss - // (posture not 1/2) logs only under BT_DUCK_LOG, which no player - // sets. Field report (Oracle, night 13): "the light next to it - // always flashes when pressed, but state does not change" -- the - // press reaches the handler and a GATE declines it, exactly the - // shape the eject bug had (#108: EvaluateEjectPermission refusing a - // healthy mech while the lamp still responded). Name the gate. - if (duckState != 0 - && (squatCapable == 0 || (mapPosture != 1 && mapPosture != 2))) + // #142 RESTRUCTURE 2026-08-07 -- duckState is the POSTURE, not a + // one-shot request, because the COCKPIT ANIMATION reads it. + // + // L4GAUGE.CFG:5001 authors + // oneOfSeveralPixInt(E,ModeAlwaysActive,bduck.pcc,3,1,DuckState) + // -- a 3-frame mech-symbol strip beside the CROUCH button, indexed by + // attribute 0x37 (= duckState). Asset (content/GAUGE/BDUCK.PCC), + // element (OneOfSeveralPixInt @004c5204) and factory registration + // (btl4grnd.cpp) are all present. The animation never played because + // the old consumer zeroed duckState the frame after the press, in + // BOTH directions -- so the strip sat on frame 0 with a one-frame + // blip to frame 1. That is the field report verbatim: "button + // flickers sometimes on press ... state does not change". + // + // The binary does NOT zero it per frame. Every writer of +0x398 in + // the export is: the DuckRequest handler (=1) and Mech::Reset (=0). + // The master perf FUN_004a9b5c -- which contains the address the old + // comment cited as "the DuckRequest consumer (@0x4aa011)" -- does not + // reference 0x398 anywhere, and mech.hpp's own note already said + // "duckState has NO code reader anywhere in the decomp ... whatever + // consumes it consumes it through DATABINDING". The databinding + // consumer is this gauge strip. The per-frame zeroing was ours. + // + // So: drive on DESIRED vs ACTUAL instead of on a latch. duckState is + // the desired posture (the handler now toggles it); the parked leg + // alarm is the actual one. Act only on a mismatch -- no re-fire, and + // nothing clears the attribute behind the gauge's back. A frame + // where mapPosture is not ready RETRIES next frame instead of + // silently dropping the request, which also retires the old + // "request consumed, posture=N" miss. + const int duckWant = (duckState != 0); + const int duckActual = ((int)legStateAlarm.GetLevel() == 1); // parked in 'sqd' + if (squatCapable != 0 && duckWant != duckActual) { - static int s_duckDrop = 0; - if ((s_duckDrop++ % 60) == 0) - DEBUG_STREAM << "[duck] REQUEST DROPPED: squatCapable=" - << squatCapable << " posture=" << mapPosture - << " mode=" << MovementMode() - << " legLvl=" << (int)legStateAlarm.GetLevel() - << " myo=" << myomerEffectiveness - << (squatCapable == 0 - ? " <-- no squ/sqd clips: consumer skipped, latch stuck" - : " <-- posture gate") - << "\n" << std::flush; - } - - if (duckState != 0 && squatCapable != 0) - { - if (mapPosture == 1) + if (duckWant && mapPosture == 1) { SetLegAnimation(2); // 'sqd' -- squat down ForceUpdate(8); @@ -4457,7 +4463,7 @@ void if (getenv("BT_DUCK_LOG") || getenv("BT_GAIT_LOG")) DEBUG_STREAM << "[duck] SQUAT (posture 1 -> leg clip 2)\n" << std::flush; } - else if (mapPosture == 2) + else if (!duckWant && mapPosture == 2) { SetLegAnimation(3); // 'squ' -- rise ForceUpdate(8); @@ -4466,14 +4472,21 @@ void if (getenv("BT_DUCK_LOG") || getenv("BT_GAIT_LOG")) DEBUG_STREAM << "[duck] RISE (posture 2 -> leg clip 3)\n" << std::flush; } - else if (getenv("BT_DUCK_LOG")) - DEBUG_STREAM << "[duck] request consumed, posture=" << mapPosture - << " (mode=" << MovementMode() - << " legLvl=" << (int)legStateAlarm.GetLevel() - << " simLive=" << 1 // re-read below costs a bridge call; posture already folded it - << " myo=" << myomerEffectiveness - << " squat=" << squatCapable << ")\n" << std::flush; - duckState = 0; // consumed (@0x4aa0a9) + else + { + // Gates not ready THIS frame (posture selector still + // settling). Retry next frame -- do NOT clear duckState: + // it is the gauge's posture source now, and dropping it + // here is what made the request vanish silently. + static int s_duckWait = 0; + if ((s_duckWait++ % 60) == 0) + DEBUG_STREAM << "[duck] WAITING: want=" << duckWant + << " actual=" << duckActual + << " posture=" << mapPosture + << " mode=" << MovementMode() + << " myo=" << myomerEffectiveness + << "\n" << std::flush; + } } // (3b) AIRBORNE AUTO-RISE -- recovered 2026-08-06 by the #60