Diag: speed-change investigation -- authentic analog throttle CONFIRMED, harnesses + gait probes (task #50)

Decomp workflow w0odszxro settled the speed model: the pod throttle was ANALOG-
CONTINUOUS (RIO Ranger, 800 ADC counts, 0.05 deadband; sole detent = snap-to-1.0
within 0.05 @004d196c).  '5 speeds' is FALSE -- the 1-5 keys are MFD mode pages;
the 0..5 stepper is HUD/radar zoom.  BONUS: throttleState@0x4a4 is a MISNOMER --
binary census proves it is the fall-contact surface material cache (0..7, init
2=Concrete), written only at knockdown; rename pending.

Empirical rule-outs (autonomous drive-sweep harness, this commit):
 - Record density: with the clock guard + incremental heading in, a per-frame
   continuous demand sweep measures IDENTICAL to constant throttle on position
   evenness ([repljit]), render heading ([rendhdg]), and gait cadence ([gaitev]).
 - Type-3 stomps: ~0 fire in sustained sweeps (only at launch) -- not the driver.
 - The 0.05-grid keyboard publish experiment is retained env-gated OFF
   (BT_GRID_LEVER) -- the ADC was ~continuous, so the grid is NOT authentic and
   gains nothing measurable; default publish stays continuous (authentic).

New tooling: BT_DRIVE_SWEEP[0] (forced-drive triangle sweep, optional through-
zero), BT_FORCE_STEP (0.05-grid variant), BT_GAITEV (per-frame leg-clip advance
+ state-flip + demand-change stats), [t3rx] (type-3 stomp trace).

STATUS: user still reports visible speed-change glitches in interactive play;
all harness metrics saturate at baseline -- next step is probes ON the user's
interactive session (their eyes + instruments on the same run).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-14 19:47:03 -05:00
co-authored by Claude Opus 4.8
parent 93456be051
commit 2c6db6a2de
3 changed files with 108 additions and 2 deletions
+95 -2
View File
@@ -2124,6 +2124,41 @@ void
legCycleSpeed = 0.0f;
(void)AdvanceLegAnimation(dt); // joints only; travel = DeadReckon
// GAIT-EVENNESS probe (BT_GAITEV, 1s stats): the metric of the VISIBLE
// speed-change glitch -- per-frame leg-clip advance regularity + leg-state
// transition (flap) rate on the peer. Position dead-reckons smoothly
// regardless; the churn shows in the LEGS.
if (getenv("BT_GAITEV"))
{
static float s_gvPrevF = -1.0f, s_gvAcc = 0.0f, s_gvSum = 0.0f, s_gvMax = 0.0f;
static int s_gvN = 0, s_gvBack = 0, s_gvTrans = 0, s_gvPrevSt = -1;
static float s_gvDemPrev = 0.0f; static int s_gvDemChg = 0;
const float gf = (float)legAnimation.currentFrame;
const int gs = (int)legStateAlarm.GetLevel();
if (s_gvPrevF >= 0.0f)
{
float df = gf - s_gvPrevF;
if (df < -5.0f) df += 20.0f; // clip wrap heal (approx)
const float adf = (df < 0.0f) ? -df : df;
s_gvSum += adf; if (adf > s_gvMax) s_gvMax = adf;
if (df < 0.0f) s_gvBack++;
s_gvN++;
}
if (s_gvPrevSt >= 0 && gs != s_gvPrevSt) s_gvTrans++;
if ((float)bodyTargetSpeed != s_gvDemPrev) { s_gvDemChg++; s_gvDemPrev = (float)bodyTargetSpeed; }
s_gvPrevF = gf; s_gvPrevSt = gs; s_gvAcc += dt;
if (s_gvAcc >= 1.0f && s_gvN > 0)
{
DEBUG_STREAM << "[gaitev] frmAvg=" << (s_gvSum / s_gvN)
<< " frmMax=" << s_gvMax
<< " back=" << s_gvBack << "/" << s_gvN
<< " stateFlips/s=" << s_gvTrans
<< " demChanges/s=" << s_gvDemChg
<< " state=" << gs << "\n" << std::flush;
s_gvAcc = 0.0f; s_gvSum = 0.0f; s_gvMax = 0.0f; s_gvN = 0; s_gvBack = 0; s_gvTrans = 0; s_gvDemChg = 0;
}
}
// TURN-STEP rate probe (BT_TRNRATE, 0.5s): body rotation rate vs trn clip advance.
if (getenv("BT_TRNRATE"))
{
@@ -2522,6 +2557,35 @@ void
: (r < 1.0f ? sAutoDrive * r : sAutoDrive);
}
}
// BT_DRIVE_SWEEP=<period>: triangle-sweep the FORCED throttle
// 0.2..0.9 -- sweeps the ACTUAL drive velocity AND (via mechmppr's
// forced key_throttle) the mapper demand together, the true
// keyboard accel/decel regime. BT_FORCE_STEP=1 quantizes it to
// the RIO Ranger 0.05 grid (the authentic stepped input shape).
{
static float s_dsP = -1.0f, s_dsClock = 0.0f;
static int s_dsStep = -1;
if (s_dsP < 0.0f)
{
const char *v = getenv("BT_DRIVE_SWEEP");
s_dsP = v ? (float)atof(v) : 0.0f;
s_dsStep = getenv("BT_FORCE_STEP") ? 1 : 0;
}
if (s_dsP > 0.0f)
{
s_dsClock += dt;
float ph = fmodf(s_dsClock, 2.0f * s_dsP) / s_dsP;
float tri = (ph < 1.0f) ? ph : (2.0f - ph);
// BT_DRIVE_SWEEP0=1: sweep 0.0..0.7 (through STOP each cycle -- the
// stand<->walk gauntlet: walk-entry, came-to-rest sends, type-3 stomps).
static int s_dsZero = -1;
if (s_dsZero < 0) s_dsZero = getenv("BT_DRIVE_SWEEP0") ? 1 : 0;
float thr = s_dsZero ? (0.7f * tri) : (0.2f + 0.7f * tri);
if (s_dsStep)
thr = floorf(thr / 0.05f + 0.5f) * 0.05f;
gBTDrive.forcedThrottle = thr;
}
}
}
else if (sGotoDrive)
{
@@ -2580,8 +2644,37 @@ void
else sStick = 0.0f;
}
gBTDrive.throttle = sLever;
gBTDrive.turn = sStick;
// RIO-GRID PUBLISH (authentic input shaping; decomp workflow w0odszxro):
// the pod's analog lever/stick reached the mapper through the RIO Ranger
// (800 ADC counts, 0.05 deadband -- L4RIO.cpp:801), so the DEMAND the 1995
// machinery consumed moved in coarse, settled STEPS with silence between
// them -- one type-2 speed record per step, and each gait threshold
// (standSpeed etc., zero hysteresis) crossed ONCE per maneuver. Our
// keyboard integrator sweeps CONTINUOUSLY (kLeverRate every frame of a
// key-hold): ~86 records per accel, demand DWELLING on the knife edges ->
// transition-clip churn -- the speed-change peer glitch. Publish on the
// Ranger's own 0.05 grid: internal lever feel unchanged, published demand
// steps like the real hardware (a bucket holds ~4 frames at kLeverRate, so
// the send gate self-closes between steps). Also kills -epsilon reverse
// publishes from rest (bucket rounds to 0 until the lever passes -0.025;
// authentic reverse is a BUTTON sign-flip, never a sweep through zero).
// DEFAULT: CONTINUOUS publish (authentic -- the pod lever was analog,
// 800-count ADC ~= continuous; empirical A/B found NO gait/position
// difference between swept and stepped demand after the clock-guard +
// heading-integrator fixes, so the grid gains nothing and makes the
// DRIVE step unauthentically). BT_GRID_LEVER=1 keeps the 0.05-grid
// variant for experiments.
static const int s_gridLever = getenv("BT_GRID_LEVER") ? 1 : 0;
if (s_gridLever)
{
gBTDrive.throttle = floorf(sLever / 0.05f + 0.5f) * 0.05f;
gBTDrive.turn = floorf(sStick / 0.05f + 0.5f) * 0.05f;
}
else
{
gBTDrive.throttle = sLever;
gBTDrive.turn = sStick;
}
}
// DEATH GATE (task #52): a DESTROYED mech takes no pilot input -- kill