Gait: keyboard detent snaps the lever out of the walk/run DEAD BAND + KB

Byte-decoded finding [T1]: the gait SM has no stable state for a demand in
(walkStrideLength@0x534, reverseSpeedMax@0x538).  Cap semantics settled from
LoadLocomotionClips @0x4a80d4: 0x538 is the walk->run TRANSITION CLIP's exit
speed (the run ENGAGE threshold -- not a reverse max) and 0x34c (run-cycle avg
root speed) is the mapper's continuous demand multiplier (FUN_004afd10).  The
finished-callbacks up-shift when tgt > 0x534 but enter/sustain run only when
tgt >= 0x538, so a demand parked between them (Blackhawk: 22.02-30.87 =
throttle 36-50%) hunts walk -> shift-up -> shift-down forever, firing the
authored EngineShiftFwd/Rev sounds each swing -- the user's repro, faithfully
reproduced by all-authentic logic + data.  Likely masked on the pod by
MECHANICAL throttle-quadrant detents ("5 speeds" lore; the software path is
notch-free) [T4] -- added to get-from-Nick.

Accommodation (keyboard = our stand-in lever): at key REST, snap the lever to
the nearer dead-band edge (walk cap, or run engage + margin since the cont
check is >=).  Sweeping THROUGH the band while held stays continuous -- an
authentic moving lever; the single shift it fires is the authentic shift.
BT_GAIT_TRACE logs [gaitdetent] snaps.

KB: locomotion.md gait-dead-band section, pod-hardware.md mechanical-notch
hypothesis, open-questions.md Nick item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-16 11:34:18 -05:00
co-authored by Claude Opus 4.8
parent 537749cae3
commit aa004eb7b8
4 changed files with 69 additions and 0 deletions
+13
View File
@@ -114,6 +114,19 @@ through walk/run caps (`LoadLocomotionClips`, mech3.cpp:326 — measures `walkSt
`legAnimation.SelectSequence`). Transition callbacks (`Mech::BodyClipFinished @0x4a6d8c`,
`LegClipFinished @0x4a6928` — PE-parsed jump tables) alternate walk 6↔7 / run 12↔13 + walk→run. [T2]
**Gait DEAD BAND (2026-07-16, byte-decoded) [T1]:** the SM has NO stable state for a demand in
(`walkStrideLength`@0x534, `reverseSpeedMax`@0x538). Cap semantics (LoadLocomotionClips @0x4a80d4):
0x534 = walk-cycle avg root speed (clips 6+7); **0x538 = the LAST KEYFRAME SPEED of clip slot 10 =
the walk→run transition clip's exit speed — the run ENGAGE threshold, not a "reverse max"**; 0x34c
= run-cycle avg root speed (clips 12+13) — and 0x34c is the mapper's demand multiplier
(`FUN_004afd10`: `speedDemand = mech->0x34c × throttle × 0x5c0`, continuous). Walk cyc clamps to
[standSpeed, 0x534]; run cyc clamps to [0x538, 0x7a0]. The finished-callback up-shifts when
`tgt > 0x534` but enters/sustains run only when `tgt ≥ 0x538` (x87 decode of 0x4a6f11/0x4a6fc7/
0x4a7041) → a demand parked between them cycles 7→10→14→7 forever ("gallop hunt"), firing the
authored EngineShiftFwd/Rev audio each swing (Blackhawk: 22.0230.87 = throttle 3650%). Faithful
mechanics — likely masked on real hardware by MECHANICAL lever detents ([[pod-hardware]] [T4]).
Port: keyboard lever snaps out of the band at rest (mech4.cpp "GAIT DETENT" accommodation).
## Turn-in-place (trn, state 4) — AUTHENTIC dispatcher decoded (task #64, 2026-07-14)
**The entry dispatcher is NOT in the Ghidra decomp** — it lives in the master-perf gap
`0x4a9b5c0x4ab188` (functions_index.tsv jumps FUN_004a9770→FUN_004ab188; the SOLE reader of
+4
View File
@@ -20,6 +20,10 @@ authentic path scoped.
`BTL4.RES` already has 8 maps + the full mech anim set.) Status: OPEN.
- Pod specifics for Phase 8: the 7-monitor driver setup, the RIO cockpit I/O protocol, current
Win10+wrapper pod config. Status: OPEN.
- **Did the pod throttle quadrant have MECHANICAL detents** (how many / lever positions)? The
software path is notch-free [T1, [[pod-hardware]]], but the gait SM's walk/run dead band
([[locomotion]]) only makes sense if the hardware discouraged parking the lever mid-band —
"5 speeds" pod lore suggests physical notches. Status: OPEN.
## BINARY-COVERAGE AUDIT discoveries (2026-07-13, 6-agent decomp census) [T1 verified-uncited]
New unaccounted functionality no prior list knew (addresses verified absent from game/+context/+docs):
+13
View File
@@ -55,6 +55,19 @@ in the software path. (Keyboard keys '1'-'5' in @004d1bf0 set controls-manager M
is a speed setting.) The mech's `throttleState@0x4a4` writer remains un-exported (likely in the
0x4a9b5a0x4ab188 gap) — [[open-questions]].
**MECHANICAL-notch hypothesis (2026-07-16) [T4, hardware]:** the "5 speeds" pod lore may still be
true — as detents in the throttle QUADRANT hardware, invisible to the software (the pot reads
continuous counts regardless of where the lever mechanically rests). Independent corroboration
from the gait math ([[locomotion]]): the gait SM has NO stable state for a demand between the walk
cap (`walkStrideLength`@0x534) and the run engage speed (`reverseSpeedMax`@0x538, the walk→run
transition clip's exit speed) — a demand PARKED in that band hunts walk→shift-up→shift-down
forever, firing the authored EngineShiftFwd/Rev sounds each swing (binary-verified, all-authentic
data: Blackhawk band = demand 22.0230.87 = throttle 3650%). A design like that only ships if the
hardware discourages parking in the band. Port accommodation: the keyboard lever snaps out of the
dead band AT REST (`mech4.cpp` "GAIT DETENT"; sweeping through while held stays continuous =
authentic moving lever). Ask Nick: did the pod throttle quadrant have mechanical detents
(how many / positions)? — [[open-questions]].
## Multi-surface gauge path (intact, pod-only by default)
The pod multi-surface path EXISTS and is intact: `DPLRenderer::FindBestAdapterIndices` (multi-
adapter selector, honors PRIMGAUGE/SECGAUGE/MFDGAUGE/SPANDISABLE), `SVGA16::BuildWindows` (a
+39
View File
@@ -2775,6 +2775,45 @@ void
const int fwd = gBTDrive.keyFwd, back = gBTDrive.keyBack;
if (!fwd && !back) sDetent = 0; // keys released -> detent re-arms
// GAIT DETENT (keyboard accommodation, 2026-07-16): the gait SM has NO
// stable state for a demand between the walk cycle's cap
// (walkStrideLength @0x534) and the run cycle's engage speed
// (reverseSpeedMax @0x538 = the walk->run transition clip's exit
// speed). A demand PARKED in that band hunts walk -> shift-up ->
// shift-down forever, firing the authored EngineShiftFwd/Rev sounds
// each swing (binary-verified: the BodyClipFinished thresholds at
// 0x4a6f11/0x4a6fc7 + the continuous FUN_004afd10 demand formula
// reproduce the hunt with all-authentic data). The pod's PHYSICAL
// throttle lever plausibly rested only at mechanical notches -- the
// software path is notch-free (pod-hardware.md), so the notches, if
// real, lived in the quadrant hardware [T4]. Reproduce that feel:
// when the keys REST, snap the lever out of the dead band to the
// nearer edge. Sweeping THROUGH the band while a key is held stays
// continuous -- that is an authentic moving lever, and the one
// transition it triggers is the authentic shift.
if (!fwd && !back && sLever > 0.0f
&& walkStrideLength > 0.0f
&& reverseSpeedMax > walkStrideLength
&& reverseStrideLength >= reverseSpeedMax)
{
const float max_demand = reverseStrideLength * forwardThrottleScale;
if (max_demand > 0.0f)
{
const float band_lo = walkStrideLength / max_demand;
// margin: the run cont-check is tgt >= cap, so land the demand
// a hair ABOVE the engage speed against float rounding
const float band_hi = reverseSpeedMax / max_demand + 0.002f;
if (sLever > band_lo && sLever < band_hi)
{
const float snapped =
(sLever - band_lo < (band_hi - band_lo) * 0.5f) ? band_lo : band_hi;
{ static int s_dLog = 0; if (getenv("BT_GAIT_TRACE") && s_dLog++ < 40)
DEBUG_STREAM << "[gaitdetent] lever " << sLever << " in dead band ["
<< band_lo << "," << band_hi << ") -> " << snapped << "\n" << std::flush; }
sLever = snapped;
}
}
}
float sweep = ((fwd ? 1.0f : 0.0f) - (back ? 1.0f : 0.0f)) * kLeverRate * dt;
if (sweep != 0.0f && !sDetent)
{