diff --git a/game/reconstructed/btl4gaug.cpp b/game/reconstructed/btl4gaug.cpp index 1e2314f..3ac5579 100644 --- a/game/reconstructed/btl4gaug.cpp +++ b/game/reconstructed/btl4gaug.cpp @@ -1744,10 +1744,27 @@ Logical L4Warehouse *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer; 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; } 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; } diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 5ae9906..d96c376 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -510,18 +510,25 @@ void { return; } - // #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; + // #142: duckState is the POSTURE the cockpit's crouch-symbol animation + // reads -- L4GAUGE.CFG:5001 binds attribute 0x37 to a THREE-frame + // bduck.pcc strip, confirmed on screen as a duck animation: + // 0 = standing 1 = moving between 2 = crouched + // + // A bare 1 here is therefore exactly right, and is what the binary writes: + // it means "in transition", which is both the request AND the middle frame. + // The consumer (mech4.cpp) reads the parked leg alarm to decide DIRECTION + // -- parked means the pending move is a rise, not parked means a squat -- + // and settles duckState to 0 or 2 when the clip finishes. No separate + // request cell, no toggle, no divergence from @0049fa00. + // + // (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... radarAngularPosition= &localOrigin.angularPosition; // ...and orientation (pointers into the base origin) duckState = 0; // not crouching + duckRequest = 0; // no pending duck request (#142) // (AUDIO_FIDELITY F7) missile alarm: the binary reset writes 0 / FLT_MAX // (part_012.c:9446-9447; FLT_MAX = "no missile" far default) incomingLock = 0; diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index e02f491..c0e736d 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -964,7 +964,15 @@ protected: Scalar radarRange; // 0x2f RadarRange (scale/max) Point3D *radarLinearPosition; // 0x30 RadarLinearPosition 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 // Mech table [T1]: IncomingLock id 54 @0x3fc (Logical; authored match // ==1 Start / ==0 Stop of the looped beeper), DistanceToMissile id 56 diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 17fdb7c..182b0e1 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -4450,15 +4450,30 @@ 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' - // 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) + // THE POSTURE MACHINE (#142). duckState is the cockpit strip's frame: + // 0 = standing 1 = moving between 2 = crouched + // The handler writes 1 (the binary's exact behaviour) meaning "a move is + // pending", which doubles as the middle frame. Direction comes from the + // parked leg alarm, so no separate request cell is needed: + // parked -> the pending move is a RISE + // !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 ForceUpdate(8); @@ -4467,7 +4482,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 (!duckWant && mapPosture == 2) + else if (duckParked && mapPosture == 2) { SetLegAnimation(3); // 'squ' -- rise ForceUpdate(8); @@ -4478,34 +4493,33 @@ void } else { - // 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; - } + // Gate refuses -- posture reads 0 for a MOVING mech, which is the + // authentic rule (Lynx: "when a mech STOPS, crouch button lowers + // its stance"; benched: crouch at a walk gives posture=0). Settle + // the strip back to the truth instead of holding the mid frame or + // queueing the request for the next time the pilot stops. + static int s_duckRefuse = 0; + if ((s_duckRefuse++ % 30) == 0) + DEBUG_STREAM << "[duck] REFUSED (not stopped): posture=" + << mapPosture << " mode=" << MovementMode() + << " myo=" << myomerEffectiveness << "\n" << std::flush; } } + // 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 // RE-EXPORT (the raw-disasm pass stopped at 0x4aa0af and missed // this tail). Pseudocode @004a9b5c: