From fcd1a0ca8dad8cbc115c8048a51bb6477009d08d Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Fri, 7 Aug 2026 16:39:05 -0500 Subject: [PATCH] #142 crouch: refuse-and-snap, not queue -- and honour the must-be-stopped rule Fixes a regression I introduced in 59f53da. That revision retried the request when the posture gate was not ready, which is worse than the drop it replaced: benched, a crouch tapped at a walk QUEUED for 41 seconds (41 [duck] WAITING lines) and would fire the instant the pilot stopped -- while duckState stayed 1, so the cockpit symbol read "crouched" for the whole time a STANDING mech walked around. duckState is what the gauge strip draws; it has to tell the truth. Now: if the gate refuses, snap desired back to actual (duckState = duckActual) and say so once, throttled. This also confirms the authentic rule rather than assuming it. Benched, a crouch pressed while driving gives posture=0 and no squat -- exactly Lynx: "When a mech STOPS, crouch button lowers its stance." Immobilization while crouched looks EMERGENT rather than gated: the leg channel parked in 'sqd' produces no root motion to travel on, and the operator's read ("i think you cant walk when you crouch") matches. No [skate] in the driving case either. Also guards re-issue: while 'sqd'/'squ' is playing (legAnimationState 2 or 3) the transition owns the channel, so want != actual no longer re-fires SetLegAnimation every frame. Benched both cases: stopped duckState -> 1 (crouch) -> SQUAT -> holds -> -> 0 (rise) -> RISE moving REFUSED (not stopped): posture=0 ... duckState 1 -> 0 0 squats, 0 queued waits Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01NCJQkvq6G2JNrpVbA75tVZ --- game/reconstructed/mech4.cpp | 47 ++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 63fda7a..17fdb7c 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -4450,9 +4450,13 @@ void // 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) + const int duckWant = (duckState != 0); + const int duckActual = ((int)legStateAlarm.GetLevel() == 1); // parked in 'sqd' + // Already playing 'sqd'/'squ': the transition owns the channel, so + // do not re-issue it every frame while want != actual. + const int duckInTransit = + (legAnimationState == 2 || legAnimationState == 3); + if (squatCapable != 0 && duckWant != duckActual && !duckInTransit) { if (duckWant && mapPosture == 1) { @@ -4474,18 +4478,31 @@ void } 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; + // GATE NOT READY -- and the posture selector reads 0 for a + // MOVING mech, which is the authentic rule: Lynx, "when a + // mech STOPS, crouch button lowers its stance". Benched: + // pressing crouch at a walk gives posture=0 and no squat. + // + // So snap the desired posture back to the actual one. An + // earlier revision retried instead, and that was worse than + // what it replaced: a crouch tapped while running QUEUED for + // 41 seconds and would fire the moment the pilot stopped, + // while the cockpit symbol read "crouched" the whole time a + // standing mech walked around. duckState is what the gauge + // strip draws -- it has to tell the truth, and the truth is + // that this mech did not crouch. + if (duckState != duckActual) + { + static int s_duckRefuse = 0; + if ((s_duckRefuse++ % 30) == 0) + DEBUG_STREAM << "[duck] REFUSED (not stopped): posture=" + << mapPosture << " mode=" << MovementMode() + << " legLvl=" << (int)legStateAlarm.GetLevel() + << " myo=" << myomerEffectiveness + << " -- duckState " << duckState << " -> " + << duckActual << "\n" << std::flush; + duckState = duckActual; + } } }