diff --git a/context/locomotion.md b/context/locomotion.md index e91cf7b..3736377 100644 --- a/context/locomotion.md +++ b/context/locomotion.md @@ -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.02–30.87 = throttle 36–50%). 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 `0x4a9b5c–0x4ab188` (functions_index.tsv jumps FUN_004a9770→FUN_004ab188; the SOLE reader of diff --git a/context/open-questions.md b/context/open-questions.md index 6d4e28e..8c68ade 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -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): diff --git a/context/pod-hardware.md b/context/pod-hardware.md index 4b911fa..59fe3fc 100644 --- a/context/pod-hardware.md +++ b/context/pod-hardware.md @@ -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 0x4a9b5a–0x4ab188 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.02–30.87 = throttle 36–50%). 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 diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index c84b190..d902dd9 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -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) {