#142 crouch: duckState is a THREE-state posture -- the strip is an animation
Operator confirmed on screen: bduck.pcc is a real duck ANIMATION, and stepping
duckState 0->1->2 plays it. So the attribute is not a flag:
0 = standing 1 = moving between 2 = crouched
Everything else was already right -- asset, element (OneOfSeveralPixInt
@004c5204), factory registration, L4GAUGE.CFG:5001, and the attribute binding
(new [gauge] receipt confirms 'bduck.pcc' frames=3x1 attr=BOUND). We were
writing a two-value flag into a three-frame strip, so frame 2 was unreachable
and the cockpit saw a snap: "it lights up and sticks, no animation".
The handler is back to the binary's exact write (duckState = 1, @0049fa00).
That value now MEANS the middle frame, so the press gives immediate visual
feedback and the earlier toggle divergence is retired.
Needed a separate duckRequest cell, which I tried twice to avoid:
* duckState cannot be both the request and the display. Settling it to the
real posture destroys the request, so on the frame the squat clip parked
the consumer read "crouched + pending" and issued the opposite direction --
69 transitions from 2 presses, benched, twice.
* reading the CACHED legAnimationState instead of the alarm made it worse:
the cache refreshes only at the top of AdvanceLegAnimation, so right after
SetLegAnimation it still reads the old state. Read the alarm.
duckRequest is port-only, appended, never read by offset.
Also fixes a silent failure in the gauge factory: the missing-image path used
DebugStream -- the no-op ReconStream (project gotcha) -- so a strip that failed
to load reported NOTHING. Now DEBUG_STREAM, plus an ungated one-line receipt
per element naming the image, frame grid, port and whether the attribute BOUND
or came back NULL. That receipt is what proved the element was healthy and
sent me looking at the value instead of the plumbing.
Benched (crouch142.sh, madcat): 2 presses -> exactly 2 transitions,
SQUAT -> parked (settles to 2) then RISE (settles to 0). Refusal while moving
still holds (posture=0, authentic per Lynx).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCJQkvq6G2JNrpVbA75tVZ
This commit is contained in:
co-authored by
Claude Opus 5
parent
fcd1a0ca8d
commit
03c4d55672
@@ -1744,10 +1744,27 @@ Logical
|
|||||||
L4Warehouse *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer;
|
L4Warehouse *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer;
|
||||||
if (warehouse->pixelMap8Bin.Get(p[2].data.string) == NULL) // FUN_00442d2b
|
if (warehouse->pixelMap8Bin.Get(p[2].data.string) == NULL) // FUN_00442d2b
|
||||||
{
|
{
|
||||||
DebugStream << "OneOfSeveralPixInt: Missing image '" << p[2].data.string << "'\n";
|
// WAS DebugStream -- the no-op ReconStream (gotcha: use DEBUG_STREAM).
|
||||||
|
// A missing strip therefore failed COMPLETELY SILENTLY, which is
|
||||||
|
// exactly the state #142 was stuck in: the crouch symbol never drew and
|
||||||
|
// nothing anywhere said why.
|
||||||
|
DEBUG_STREAM << "[gauge] oneOfSeveralPixInt: MISSING IMAGE '"
|
||||||
|
<< p[2].data.string << "' -- element not created\n" << std::flush;
|
||||||
return False;
|
return False;
|
||||||
}
|
}
|
||||||
warehouse->pixelMap8Bin.Release(p[2].data.string); // FUN_00442e51
|
warehouse->pixelMap8Bin.Release(p[2].data.string); // FUN_00442e51
|
||||||
|
|
||||||
|
// #142 receipt (ungated, one line per element): does this strip exist, and
|
||||||
|
// did its integer attribute actually RESOLVE? A NULL attributePointer
|
||||||
|
// leaves the connection reading nothing, so the strip pins to frame 0 and
|
||||||
|
// looks like "no animation at all" -- indistinguishable, from outside, from
|
||||||
|
// a missing image or an unbuilt page.
|
||||||
|
DEBUG_STREAM << "[gauge] oneOfSeveralPixInt '" << p[2].data.string
|
||||||
|
<< "' frames=" << p[3].data.integer << "x" << p[4].data.integer
|
||||||
|
<< " port=" << display_port_index
|
||||||
|
<< " at(" << position.x << "," << position.y << ")"
|
||||||
|
<< " attr=" << (p[5].data.attributePointer != 0 ? "BOUND" : "NULL !!")
|
||||||
|
<< "\n" << std::flush;
|
||||||
return True;
|
return True;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-12
@@ -510,18 +510,25 @@ void
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// #142: duckState is the DESIRED POSTURE, and it is what the cockpit's
|
// #142: duckState is the POSTURE the cockpit's crouch-symbol animation
|
||||||
// crouch-symbol animation reads (L4GAUGE.CFG:5001 binds attribute 0x37 to a
|
// reads -- L4GAUGE.CFG:5001 binds attribute 0x37 to a THREE-frame
|
||||||
// 3-frame bduck.pcc strip). The binary writes a bare 1 here and clears the
|
// bduck.pcc strip, confirmed on screen as a duck animation:
|
||||||
// cell only in Mech::Reset -- with no per-frame reader anywhere in the
|
// 0 = standing 1 = moving between 2 = crouched
|
||||||
// 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
|
// A bare 1 here is therefore exactly right, and is what the binary writes:
|
||||||
// the port TOGGLES. Documented divergence, deliberately minimal: one cell,
|
// it means "in transition", which is both the request AND the middle frame.
|
||||||
// same meaning, and the consumer (mech4.cpp) now acts on desired-vs-actual
|
// The consumer (mech4.cpp) reads the parked leg alarm to decide DIRECTION
|
||||||
// rather than consuming the cell out from under the gauge.
|
// -- parked means the pending move is a rise, not parked means a squat --
|
||||||
duckState = (duckState != 0) ? 0 : 1;
|
// and settles duckState to 0 or 2 when the clip finishes. No separate
|
||||||
DEBUG_STREAM << "[duck] DuckRequest: duckState -> " << duckState
|
// request cell, no toggle, no divergence from @0049fa00.
|
||||||
<< (duckState ? " (crouch)" : " (rise)") << std::endl << std::flush;
|
//
|
||||||
|
// (An earlier revision toggled 0<->1 here. That produced a two-pose snap,
|
||||||
|
// which is what the cockpit reported as "it lights up and sticks, no
|
||||||
|
// animation": frame 2 was never reachable.)
|
||||||
|
duckState = 1; // show the MIDDLE frame at once (the binary's write)
|
||||||
|
duckRequest = 1; // and remember that a move is pending (#142)
|
||||||
|
DEBUG_STREAM << "[duck] DuckRequest: duckState -> 1 (in transition)"
|
||||||
|
<< std::endl << std::flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -1578,6 +1585,7 @@ Mech::Mech(
|
|||||||
radarLinearPosition = &localOrigin.linearPosition; // map reads the mech's live world position...
|
radarLinearPosition = &localOrigin.linearPosition; // map reads the mech's live world position...
|
||||||
radarAngularPosition= &localOrigin.angularPosition; // ...and orientation (pointers into the base origin)
|
radarAngularPosition= &localOrigin.angularPosition; // ...and orientation (pointers into the base origin)
|
||||||
duckState = 0; // not crouching
|
duckState = 0; // not crouching
|
||||||
|
duckRequest = 0; // no pending duck request (#142)
|
||||||
// (AUDIO_FIDELITY F7) missile alarm: the binary reset writes 0 / FLT_MAX
|
// (AUDIO_FIDELITY F7) missile alarm: the binary reset writes 0 / FLT_MAX
|
||||||
// (part_012.c:9446-9447; FLT_MAX = "no missile" far default)
|
// (part_012.c:9446-9447; FLT_MAX = "no missile" far default)
|
||||||
incomingLock = 0;
|
incomingLock = 0;
|
||||||
|
|||||||
@@ -964,7 +964,15 @@ protected:
|
|||||||
Scalar radarRange; // 0x2f RadarRange (scale/max)
|
Scalar radarRange; // 0x2f RadarRange (scale/max)
|
||||||
Point3D *radarLinearPosition; // 0x30 RadarLinearPosition
|
Point3D *radarLinearPosition; // 0x30 RadarLinearPosition
|
||||||
Quaternion *radarAngularPosition; // 0x31 RadarAngularPosition
|
Quaternion *radarAngularPosition; // 0x31 RadarAngularPosition
|
||||||
int duckState; // 0x37 DuckState (crouch posture)
|
int duckState; // 0x37 DuckState (crouch POSTURE the cockpit
|
||||||
|
// strip draws: 0 stand, 1 moving, 2 crouched)
|
||||||
|
// PORT-ONLY (#142): the pending duck REQUEST, kept separate from the
|
||||||
|
// posture above. duckState cannot carry both -- settling it to the
|
||||||
|
// real posture destroys the request, and the consumer then re-issues
|
||||||
|
// the opposite direction on the very frame the clip parks (benched:
|
||||||
|
// 69 squat/rise transitions from 2 presses). Not a binary field; it
|
||||||
|
// is appended, never read by offset.
|
||||||
|
int duckRequest;
|
||||||
// (AUDIO_FIDELITY F7) the incoming-missile alarm attributes. Binary
|
// (AUDIO_FIDELITY F7) the incoming-missile alarm attributes. Binary
|
||||||
// Mech table [T1]: IncomingLock id 54 @0x3fc (Logical; authored match
|
// Mech table [T1]: IncomingLock id 54 @0x3fc (Logical; authored match
|
||||||
// ==1 Start / ==0 Stop of the looped beeper), DistanceToMissile id 56
|
// ==1 Start / ==0 Stop of the looped beeper), DistanceToMissile id 56
|
||||||
|
|||||||
@@ -4450,15 +4450,30 @@ void
|
|||||||
// where mapPosture is not ready RETRIES next frame instead of
|
// where mapPosture is not ready RETRIES next frame instead of
|
||||||
// silently dropping the request, which also retires the old
|
// silently dropping the request, which also retires the old
|
||||||
// "request consumed, posture=N" miss.
|
// "request consumed, posture=N" miss.
|
||||||
const int duckWant = (duckState != 0);
|
// THE POSTURE MACHINE (#142). duckState is the cockpit strip's frame:
|
||||||
const int duckActual = ((int)legStateAlarm.GetLevel() == 1); // parked in 'sqd'
|
// 0 = standing 1 = moving between 2 = crouched
|
||||||
// Already playing 'sqd'/'squ': the transition owns the channel, so
|
// The handler writes 1 (the binary's exact behaviour) meaning "a move is
|
||||||
// do not re-issue it every frame while want != actual.
|
// pending", which doubles as the middle frame. Direction comes from the
|
||||||
const int duckInTransit =
|
// parked leg alarm, so no separate request cell is needed:
|
||||||
(legAnimationState == 2 || legAnimationState == 3);
|
// parked -> the pending move is a RISE
|
||||||
if (squatCapable != 0 && duckWant != duckActual && !duckInTransit)
|
// !parked -> the pending move is a SQUAT
|
||||||
|
// While the clip runs we hold 1; when it settles we write 0 or 2.
|
||||||
|
// Read the ALARM, not the cached legAnimationState member: the cache
|
||||||
|
// is only refreshed at the top of AdvanceLegAnimation, so in the
|
||||||
|
// frame right after SetLegAnimation it still reads the OLD state.
|
||||||
|
// With the cached read, "am I already moving?" answered no on the
|
||||||
|
// frame after issuing, the consumer re-issued, and the machine
|
||||||
|
// ping-ponged squat/rise -- 68 transitions from 2 presses, benched.
|
||||||
|
// SetLegAnimation writes the alarm synchronously, so the alarm is
|
||||||
|
// true the instant the clip is armed.
|
||||||
|
const int duckLegLvl = (int)legStateAlarm.GetLevel();
|
||||||
|
const int duckParked = (duckLegLvl == 1);
|
||||||
|
const int duckMoving = (duckLegLvl == 2 || duckLegLvl == 3); // 'sqd' / 'squ'
|
||||||
|
|
||||||
|
if (duckRequest != 0 && !duckMoving && squatCapable != 0)
|
||||||
{
|
{
|
||||||
if (duckWant && mapPosture == 1)
|
duckRequest = 0; // one shot, whatever happens
|
||||||
|
if (!duckParked && mapPosture == 1)
|
||||||
{
|
{
|
||||||
SetLegAnimation(2); // 'sqd' -- squat down
|
SetLegAnimation(2); // 'sqd' -- squat down
|
||||||
ForceUpdate(8);
|
ForceUpdate(8);
|
||||||
@@ -4467,7 +4482,7 @@ void
|
|||||||
if (getenv("BT_DUCK_LOG") || getenv("BT_GAIT_LOG"))
|
if (getenv("BT_DUCK_LOG") || getenv("BT_GAIT_LOG"))
|
||||||
DEBUG_STREAM << "[duck] SQUAT (posture 1 -> leg clip 2)\n" << std::flush;
|
DEBUG_STREAM << "[duck] SQUAT (posture 1 -> leg clip 2)\n" << std::flush;
|
||||||
}
|
}
|
||||||
else if (!duckWant && mapPosture == 2)
|
else if (duckParked && mapPosture == 2)
|
||||||
{
|
{
|
||||||
SetLegAnimation(3); // 'squ' -- rise
|
SetLegAnimation(3); // 'squ' -- rise
|
||||||
ForceUpdate(8);
|
ForceUpdate(8);
|
||||||
@@ -4478,34 +4493,33 @@ void
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// GATE NOT READY -- and the posture selector reads 0 for a
|
// Gate refuses -- posture reads 0 for a MOVING mech, which is the
|
||||||
// MOVING mech, which is the authentic rule: Lynx, "when a
|
// authentic rule (Lynx: "when a mech STOPS, crouch button lowers
|
||||||
// mech STOPS, crouch button lowers its stance". Benched:
|
// its stance"; benched: crouch at a walk gives posture=0). Settle
|
||||||
// pressing crouch at a walk gives posture=0 and no squat.
|
// the strip back to the truth instead of holding the mid frame or
|
||||||
//
|
// queueing the request for the next time the pilot stops.
|
||||||
// So snap the desired posture back to the actual one. An
|
static int s_duckRefuse = 0;
|
||||||
// earlier revision retried instead, and that was worse than
|
if ((s_duckRefuse++ % 30) == 0)
|
||||||
// what it replaced: a crouch tapped while running QUEUED for
|
DEBUG_STREAM << "[duck] REFUSED (not stopped): posture="
|
||||||
// 41 seconds and would fire the moment the pilot stopped,
|
<< mapPosture << " mode=" << MovementMode()
|
||||||
// while the cockpit symbol read "crouched" the whole time a
|
<< " myo=" << myomerEffectiveness << "\n" << std::flush;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SETTLE, unconditionally, re-reading the alarm AFTER any issue
|
||||||
|
// above. This is the part that was missing: duckState stayed 1
|
||||||
|
// forever, so once a clip completed the block above kept firing and
|
||||||
|
// flipped direction every frame -- 68 transitions from 2 presses.
|
||||||
|
// The squat/rise clips finish fast enough that the alarm is already
|
||||||
|
// back to 0/1 by the next visit, so "am I still moving?" has to be
|
||||||
|
// asked fresh, and the strip settled whenever the answer is no.
|
||||||
|
{
|
||||||
|
const int lvlNow = (int)legStateAlarm.GetLevel();
|
||||||
|
if (lvlNow != 2 && lvlNow != 3) // not playing 'sqd'/'squ'
|
||||||
|
duckState = (lvlNow == 1) ? 2 : 0; // crouched : standing
|
||||||
|
// else: hold 1, the middle frame, while the clip runs.
|
||||||
|
}
|
||||||
|
|
||||||
// (3b) AIRBORNE AUTO-RISE -- recovered 2026-08-06 by the #60
|
// (3b) AIRBORNE AUTO-RISE -- recovered 2026-08-06 by the #60
|
||||||
// RE-EXPORT (the raw-disasm pass stopped at 0x4aa0af and missed
|
// RE-EXPORT (the raw-disasm pass stopped at 0x4aa0af and missed
|
||||||
// this tail). Pseudocode @004a9b5c:
|
// this tail). Pseudocode @004a9b5c:
|
||||||
|
|||||||
Reference in New Issue
Block a user