Files
BT411/scratchpad/night13/modecycle.sh
T
Joe DiPrimaandClaude Opus 5 9657fbb11e 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=<n>: 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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018SgmXGNMXavXiafKXf9MDC
2026-08-08 13:59:39 -05:00

61 lines
3.1 KiB
Bash

#!/usr/bin/env bash
# =========================================================================
# Sauron: "toggled through advanced controls from standard to advanced and
# back to standard -- lost torso control."
#
# THE MECHANISM (@004afbe0, the binary's CycleControlModeMessageHandler):
# the mode cycles 0 Basic -> 1 Standard -> 2 Veteran -> WRAPS TO BASIC. So
# getting from Veteran/"advanced" back to Standard PASSES THROUGH BASIC, and
# the Basic arm re-centres the torso.
#
# The port set that re-centre with CommandRecenter() -> centerCommand (@0x208).
# That is the HELD-BUTTON cell: TorsoSimulation re-arms `recenterActive` from
# it EVERY frame it is non-zero and only the input path clears it -- and a mode
# switch has no button release to follow. One visit to Basic pinned it at 1
# forever. Digital twist commands are processed BEFORE the centerCommand
# block, so they were overridden as fast as they were applied = "lost torso
# control". The binary writes recenterActive (@0x274) instead: a ONE-SHOT that
# self-clears on settle and is cancelled by any twist input.
#
# THE MEASUREMENT. BT_MODECYCLE_EVERY=<n> cycles the control mode every n mapper ticks
# frames from that frame. BT_TORSO_LOG's gate probe now prints the two cells:
# [torso] ... ctrCmd=<centerCommand> recen=<recenterActive> vLim=(lo..hi)
#
# PASS: ctrCmd stays 0 across every cycle (the one-shot is used instead), and
# vLim SWAPS between the Basic pair and the assisted pair as the mode
# changes -- proving the elevation-limit swap (@0x228/@0x22C vs
# @0x230/@0x234) that the port previously never implemented.
# FAIL: ctrCmd latches to 1 after the first pass through Basic and never
# returns to 0 -> the torso re-centres forever.
#
# Single node: this is entirely local control state, no peer needed.
# =========================================================================
set -x
. /c/git/bt411/scratchpad/night6/bench_common.sh
cd /c/git/bt411/content || exit 1
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 2
rm -f mc_a.log
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=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
echo "=================== CONTROL-MODE TORSO STATE ==================="
echo "--- the mode cycles that happened ---"
grep -a "\[mode\] control mode" mc_a.log | head -10
echo
echo "--- centerCommand must NEVER latch (ctrCmd=1 with no button = the bug) ---"
echo -n " samples with ctrCmd=1 : "; grep -ao "ctrCmd=[0-9]*" mc_a.log | grep -c "ctrCmd=1"
echo -n " samples with ctrCmd=0 : "; grep -ao "ctrCmd=[0-9]*" mc_a.log | grep -c "ctrCmd=0"
echo
echo "--- the elevation-limit SWAP (should differ between Basic and assisted) ---"
grep -ao "vLim=([^)]*)" mc_a.log | sort | uniq -c | sort -rn | head -5
echo
echo "--- torso state around each mode change ---"
grep -aE "\[mode\] control mode|ctrCmd=" mc_a.log | grep -aA1 "\[mode\]" | head -12