#142 crouch: duckState is the POSTURE the cockpit animation reads

The crouch symbol animation is fully present and we were starving it.

    content/GAUGE/BDUCK.PCC                         the 3-frame strip
    OneOfSeveralPixInt  @004c5204/@004c52d8         the element, reconstructed
    btl4grnd.cpp:144                                registered in the factory
    L4GAUGE.CFG:5001                                oneOfSeveralPixInt(
                                                      E,ModeAlwaysActive,
                                                      bduck.pcc,3,1,DuckState)
    ATTRIBUTE_ENTRY(Mech, DuckState, duckState)     attribute 0x37

A 3-frame mech symbol beside the CROUCH button, indexed by duckState -- the
standing<->crouching animation Lynx and Draco describe.  It never played
because the 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.  Remains in stand mode."

THE ZEROING WAS OURS.  Every writer of +0x398 in the export is the
DuckRequest handler (=1) and Mech::Reset (=0).  FUN_004a9b5c -- the master
perf, which contains the address the old comment cited as "the DuckRequest
consumer (@0x4aa011)" -- does not reference 0x398 at all.  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, and we were clearing it behind the gauge's back.

Restructure: drive on DESIRED vs ACTUAL.  duckState is the desired posture;
the parked leg alarm is the actual.  Act only on a mismatch -- no re-fire, and
nothing clears the attribute.  A frame where mapPosture is not ready now
RETRIES (throttled [duck] WAITING) instead of silently dropping the request,
which retires the old "request consumed, posture=N" miss as well.

ONE DOCUMENTED DIVERGENCE: the handler now TOGGLES.  The binary writes a bare
1 and clears the cell only in Mech::Reset, with no per-frame reader, so a
second press could never rise -- and a pod pilot's second press must un-crouch
(Lynx: "Mech is immobilized until crouch is pushed again, and mech rises").
One cell, same meaning, noted at the site.

Benched (crouch142.sh, madcat):
  duckState -> 1 (crouch) -> SQUAT -> [holds 1 while crouched] ->
  duckState -> 0 (rise)   -> RISE
Value now persists across the crouched period instead of blipping, so frames
0/1 of the strip are reachable and stable.  Also removed the interim REQUEST
DROPPED receipt: after the restructure nothing is dropped, and a receipt that
says otherwise is a trap for the next session.

STILL OPEN on #142: no immobilization while crouched (Lynx) -- nothing gates
movement on duckState or the parked leg alarm.  A driven mech with a parked
leg channel is the [skate] signature (#52), so it may not be cosmetic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCJQkvq6G2JNrpVbA75tVZ
This commit is contained in:
Joe DiPrima
2026-08-07 16:31:20 -05:00
co-authored by Claude Opus 5
parent 0530366687
commit 59f53da07b
2 changed files with 62 additions and 39 deletions
+12 -2
View File
@@ -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;
}
//
+50 -37
View File
@@ -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