From 9657fbb11eb11e7e75f2da33483d08e1f1b20a91 Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Sat, 8 Aug 2026 13:59:39 -0500 Subject: [PATCH] control mode: REPRODUCE the "centering fought my control" fight, and prove the fix in Sauron's config Follow-up to 4ccc2a7, which fixed the mechanism but could not reproduce the field symptom. His fuller wording -- the centering "FOUGHT" his control, not "the torso died" -- is what cracked it. WHY "FOUGHT" IS THE PRECISE SYMPTOM. TorsoSimulation's frame order is 1. digital twist commands -> currentTwist += d; recenterActive = 0 2. centerCommand > 0 -> recenterActive = 1 (re-armed) 3. analog twist axis != 0 -> currentTwist += d; recenterActive = 0 4. if (recenterActive) -> Recenter(dt) (drags toward 0) Desktop/glass torso input is ANALOG (Q/E -> gBTTwistAxis -> stickPosition.x), so with centerCommand stuck the torso HOLDS while you are actively pushing (step 3 clears the arm) and snaps back the instant you ease off (step 2's arm survives into step 4). You can only hold it off-centre by pushing continuously. That is "the centering fought my control", exactly. WHY IT ONLY BITES THE GLASS/POD BUILD -- and why the first bench came back clean. The ONLY caller of ClearRecenterCommand() sits INSIDE the desktop key-bridge block, gated on `gBTDrive.forced || !BTRIODevicePresent()`. With a RIO present -- and on glass builds PadRIO IS the rioPointer -- the bridge is OFF and NOTHING ever clears centerCommand, so one pass through Basic pins it at 1 for good. A plain desktop build clears it every frame and self-recovers. The first modecycle.sh run needed BT_KEY_BRIDGE=1 to make the mode-cycle hook run at all -- and that same flag switched on the only thing that clears the cell, masking the bug under test. The hook is now deliberately OUTSIDE that block so the bench can run the RIO-present configuration. MEASURED A/B, bridge OFF (Sauron's config), BT_TWIST_PULSE deflect/release: LEGACY FIXED ctrCmd=1 samples 310 (latched for good) 0 twist during RELEASE decays 0.443->0, HOLDS 2.44346 0.900->0.436 (recen=1) recen=1 samples permanently armed 14 (one-shot per Basic entry, then self-clears) So the authentic one-shot re-centre still happens on entering Basic; it just settles instead of fighting the pilot forever. New bench hook BT_TWIST_PULSE=: deflect the analog twist axis for n ticks then RELEASE for n ticks, repeating. BT_LOCK_SWEEP never releases, so it cannot show this symptom at all -- the release window IS the measurement. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018SgmXGNMXavXiafKXf9MDC --- game/reconstructed/mechmppr.cpp | 40 +++++++++++++++++++-------------- game/reconstructed/torso.cpp | 24 ++++++++++++++++++++ scratchpad/night13/modecycle.sh | 2 +- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/game/reconstructed/mechmppr.cpp b/game/reconstructed/mechmppr.cpp index 8cffb05..c6bd9de 100644 --- a/game/reconstructed/mechmppr.cpp +++ b/game/reconstructed/mechmppr.cpp @@ -704,6 +704,29 @@ void // after the push, immediately before interpretation -- making the keyboard // authoritative on the dev box. Interpretation below stays 100% authentic. // + // BENCH (BT_MODECYCLE_EVERY=): cycle the control mode every n + // InterpretControls calls, driving the SAME body the 'M' key and the pod + // console button (key 0x13d -- not a RIO button, so BT_BTNTEST cannot press + // it) drive. Dev-only; default off. + // + // ⚠ DELIBERATELY OUTSIDE the key-bridge block below. The ONLY caller of + // ClearRecenterCommand() lives inside that block, so forcing BT_KEY_BRIDGE=1 + // to make this hook run would ALSO switch on the one thing that clears + // centerCommand -- masking the very bug under test. That is exactly how the + // first run of modecycle.sh came back clean. Keeping the hook out here lets + // the bench reproduce the RIO-present (glass/PadRIO) configuration, where the + // bridge is OFF and nothing clears the cell. + { + static const char *s_mcEvery = getenv("BT_MODECYCLE_EVERY"); + if (s_mcEvery != 0) + { + static int s_mcN = 0; + int period = atoi(s_mcEvery); + if (period < 1) period = 300; + if (++s_mcN % period == 0) + CycleControlModeNow(); + } + } { // STAND-DOWN (glass-cockpit step 2c): BT_KEY_BRIDGE unset = AUTO -- // the bridge runs only when NO live cockpit device (serial RIO / @@ -868,23 +891,6 @@ void gBTModeCycle = 0; CycleControlModeNow(); } - // BENCH (BT_MODECYCLE_EVERY=): cycle the control mode every n - // InterpretControls calls. mech4's BT_MODECYCLE_TEST hook sits in - // a scope whose frame counter did not advance in a solo run, and - // the pod's own route is console key 0x13d -- not a RIO button, so - // BT_BTNTEST cannot press it. This drives the SAME body the key - // and the console button drive. Dev-only; default off. - { - static const char *s_mcEvery = getenv("BT_MODECYCLE_EVERY"); - if (s_mcEvery != 0) - { - static int s_mcN = 0; - int period = atoi(s_mcEvery); - if (period < 1) period = 300; - if (++s_mcN % period == 0) - CycleControlModeNow(); - } - } // Gitea #6: 'N' cycles the secondary screen's schematic // (Damage -> Critical -> Heat) -- the same body the pod's // status-info-center button message drives. diff --git a/game/reconstructed/torso.cpp b/game/reconstructed/torso.cpp index a3842ce..a7e15ec 100644 --- a/game/reconstructed/torso.cpp +++ b/game/reconstructed/torso.cpp @@ -618,6 +618,30 @@ void if (lsw != 0 && s_lockSweep <= 0.0f) s_lockSweep = 0.12f; if (s_lockSweep > 1.0f) s_lockSweep = 1.0f; } + // BENCH (BT_TWIST_PULSE=): deflect the analog twist axis for n ticks, + // then RELEASE it for n ticks, repeating. BT_LOCK_SWEEP never releases, + // so it cannot show the reported symptom: with a stuck centerCommand the + // torso holds while you are actively pushing (the analog arm clears + // recenterActive) and snaps back the moment you let go (centerCommand + // re-arms it) -- "the torso centering FOUGHT my control". Measure the + // RELEASE windows: currentTwist should HOLD, not decay toward 0. + { + static const char *s_tp = getenv("BT_TWIST_PULSE"); + if (s_tp != 0) + { + static int s_tpN = 0; + int period = atoi(s_tp); + if (period < 1) period = 120; + const int phase = (s_tpN++ / period) % 2; + analogTwistAxis = phase ? 0.0f : 0.6f; + if ((s_tpN % 30) == 0) + DEBUG_STREAM << "[twistpulse] phase=" << (phase ? "RELEASE" : "deflect") + << " axis=" << analogTwistAxis + << " twist=" << currentTwist + << " ctrCmd=" << centerCommand + << " recen=" << recenterActive << "\n" << std::flush; + } + } if (s_lockSweep > 0.0f) { effectiveTwistRate = baseTwistRate; diff --git a/scratchpad/night13/modecycle.sh b/scratchpad/night13/modecycle.sh index f378522..7e0ae38 100644 --- a/scratchpad/night13/modecycle.sh +++ b/scratchpad/night13/modecycle.sh @@ -40,7 +40,7 @@ bt_expert_egg MP.EGG MC.EGG sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; s/^vehicle=.*/vehicle=madcat/" MC.EGG ( export BT_MODECYCLE_EVERY=400 - export BT_TORSO_LOG=1 BT_KEY_NOFOCUS=1 BT_KEY_BRIDGE=1 ${LEGACY:+BT_LEGACY_MODE_RECENTER=1} + export BT_TORSO_LOG=1 BT_KEY_NOFOCUS=1 BT_KEY_BRIDGE=0 BT_TWIST_PULSE=150 ${LEGACY:+BT_LEGACY_MODE_RECENTER=1} bt_launch mc_a.log MC.EGG 0x03 ) sleep 150 bt_kill_ours; sleep 2; taskkill //F //IM btl4.exe > /dev/null 2>&1; sleep 3