From 6ad1e074cc2e1ed7b2c640b36eac9cebb66593ae Mon Sep 17 00:00:00 2001 From: arcattack Date: Mon, 20 Jul 2026 10:57:34 -0500 Subject: [PATCH] Glass lever: port the pod-build GAIT DETENT to the PadRIO throttle slew Human re-test on the fresh build: zero-stop OK, but the lever could PARK inside the walk/run hysteresis band (walkStrideLength@0x534 .. reverseSpeedMax@0x538) where the gait SM has no stable state -- walk<->run hunt, EngineShiftFwd/Rev retriggering ("mixed walk/run mode + repeated acceleration sample"). The pod-build keyboard lever already snaps out of the band to the NEARER edge at key-rest (mech4.cpp GAIT DETENT); the PadRIO slew channel lacked it. Faithful port, not an invention: mech4.cpp publishes the band in lever units every driven frame (gBTGaitDetentLo/Hi seam, same guards + the same +0.002 engage margin); L4PADRIO Poll() applies the identical nearer-edge snap to the Throttle channel when the slew rests (no slew key held, no pad slew axis deflected). Sweeping through the band while held stays continuous (the authentic moving lever, one authentic shift). Verified (DEV.EGG glass, graduated parks each ~0.10 of the sweep, BT_GAIT_TRACE+BT_MPPR_TRACE): band [0.358026,0.50388) = demand 22.02-30.87; in-band parks snap both directions (0.4137->lo, 0.465126->hi); no at-rest sample in the band; exactly ONE walk->run clip (state 10) up and ONE run->walk (state 14) down across the whole sweep; zero-stop unaffected; pod build rebuilt + BT_AUTODRIVE smoke clean. Co-Authored-By: Claude Fable 5 --- context/glass-cockpit.md | 10 ++++++++ context/locomotion.md | 4 +++- docs/GLASS_COCKPIT.md | 28 +++++++++++++++++++++++ engine/MUNGA_L4/L4PADRIO.cpp | 44 ++++++++++++++++++++++++++++++++++++ game/reconstructed/mech4.cpp | 36 +++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 1 deletion(-) diff --git a/context/glass-cockpit.md b/context/glass-cockpit.md index d48178d..ec53ce5 100644 --- a/context/glass-cockpit.md +++ b/context/glass-cockpit.md @@ -94,6 +94,16 @@ copied from them. symptom, and V's edge is now [T2]. `BT_MPPR_TRACE` now prints mode/pedals/stick/turn/elev. Procedural rule: rebuild every live build dir after landing an input/engine commit. Detail: `docs/GLASS_COCKPIT.md` §2026-07-20 (later). +- **2026-07-20 human re-test: GAIT DETENT ported to the PadRIO lever [T2].** The remaining + "our detent is missing" (mixed walk/run mode + repeated accel sample) was the gait DEAD BAND + ([[locomotion]]): the pod-build keyboard lever snaps out of the walk/run hysteresis band at + key-rest (mech4.cpp GAIT DETENT), and the PadRIO throttle slew lacked it. Ported faithfully: + mech4.cpp publishes the band in lever units (`gBTGaitDetentLo/Hi` seam, +0.002 engage + margin), `L4PADRIO.cpp` applies the identical nearer-edge snap when no throttle slew key/pad + axis is active. Verified: parks at 0.414/0.465 snap to 0.358/0.504 (band = demand + 22.02-30.87), no at-rest sample in-band, exactly ONE up-shift + ONE down-shift clip across a + full graduated sweep, zero-stop + pod build unaffected. Detail: `docs/GLASS_COCKPIT.md` + §2026-07-20 (later still). ## Key Relationships - Extends: [[pod-hardware]] (RIO input path) · Uses: [[gauges-hud]] dev-gauge modes · diff --git a/context/locomotion.md b/context/locomotion.md index 3736377..1ddf49f 100644 --- a/context/locomotion.md +++ b/context/locomotion.md @@ -125,7 +125,9 @@ the walk→run transition clip's exit speed — the run ENGAGE threshold, not a 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). +Port: keyboard lever snaps out of the band at rest (mech4.cpp "GAIT DETENT" accommodation); +the glass PadRIO throttle slew applies the SAME at-rest snap (L4PADRIO.cpp, band published in +lever units via the mech4.cpp `gBTGaitDetentLo/Hi` seam — 2026-07-20 glass re-test) [T2]. ## 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 diff --git a/docs/GLASS_COCKPIT.md b/docs/GLASS_COCKPIT.md index 8a162db..08bd0fe 100644 --- a/docs/GLASS_COCKPIT.md +++ b/docs/GLASS_COCKPIT.md @@ -278,6 +278,34 @@ HEAD. One diagnostic enrichment landed: the `BT_MPPR_TRACE` `[mppr-c]` line now **Lesson (procedural):** after landing an engine/input commit, REBUILD every live build dir before relaunching — a stale `build-glass` exe reproduces "the whole keymap is broken" exactly. +### 2026-07-20 (later still) — human re-test: GAIT DETENT ported to the PadRIO lever [T2] + +**Report (fresh build):** zero-stop confirmed working, but "OUR detent is still missing — it +goes into that mixed walk/run mode and repeats the acceleration sample." That is the documented +gait DEAD BAND (locomotion.md [T1]): the SM has no stable state for a demand in +(`walkStrideLength`@0x534, `reverseSpeedMax`@0x538) — parked there it cycles 7→10→14→7 forever, +retriggering EngineShiftFwd/Rev each swing. The pod-build keyboard lever carries an +accommodation (mech4.cpp "GAIT DETENT": at KEY-REST, snap the lever out of the band to the +NEARER edge; sweeping through while held stays continuous) — the PadRIO slew channel had no +such protection, so the glass lever could park in-band. + +**Port (faithful copy, not an invention):** mech4.cpp publishes the band in lever units every +driven frame through a new seam (`gBTGaitDetentLo/Hi`, same guards + the same +0.002 engage +margin; hi<=lo = unknown); `L4PADRIO.cpp Poll()` applies the IDENTICAL nearer-edge snap to the +Throttle channel when the slew is at rest (no slew key held, no pad slew axis deflected) — +same rule, same log tag, gated `BT_GAIT_TRACE`. The rest decision uses PadRIO's own held-state +(the lever's owner on glass), not gBTDrive.keyFwd/Back. + +**Verified (build-glass, DEV.EGG solo, graduated UP-taps parking the lever every ~0.10 across +0..1, BT_GAIT_TRACE+BT_MPPR_TRACE):** band [0.358026, 0.50388) = demand 22.02→30.87 (the +locomotion.md Blackhawk numbers); park at 0.4137 → snapped DOWN to 0.358026, park at 0.465126 → +snapped UP to 0.50388 (both nearer-edge directions); rest-position histogram shows NO at-rest +sample inside the band; gait states: ONE walk→run clip (state 10, once) on the whole ascending +sweep, ONE run→walk (state 14, once) descending, normal 6↔7 / 12↔13 cycling between — the +accel/shift sample can fire only once per real transition. Zero-stop unaffected (thr=0 rest, +state 0). Pod build rebuilt + BT_AUTODRIVE smoke clean (the seam publish is a pure global +write on the pod path). + ## 2026-07-18 — Merge origin/master (commit f889e24): btinput meets PadRIO Master's 33-commit run landed (D1 relay/operator-console line, CONTROLS.MAP+XInput binding diff --git a/engine/MUNGA_L4/L4PADRIO.cpp b/engine/MUNGA_L4/L4PADRIO.cpp index 0f82e4a..2e798ba 100644 --- a/engine/MUNGA_L4/L4PADRIO.cpp +++ b/engine/MUNGA_L4/L4PADRIO.cpp @@ -443,8 +443,10 @@ void float slewDelta[PadBindingProfile::ChannelCount]; int deflectHeld[PadBindingProfile::ChannelCount]; + int slewHeld[PadBindingProfile::ChannelCount]; memset(slewDelta, 0, sizeof(slewDelta)); memset(deflectHeld, 0, sizeof(deflectHeld)); + memset(slewHeld, 0, sizeof(slewHeld)); for (int k = 0; k < bindings.keyBindingCount; ++k) { @@ -482,6 +484,7 @@ void case PadBindingProfile::ActionAxisSlew: if (held) { + slewHeld[binding.action.channel] = 1; slewDelta[binding.action.channel] += binding.action.rate * dt; } @@ -588,6 +591,10 @@ void } if (binding.slew) { + if (raw != 0.0f) + { + slewHeld[binding.channel] = 1; + } channelValue[binding.channel] += raw * binding.slewRate * dt; } else if (raw != 0.0f) @@ -602,6 +609,43 @@ void } } + // + //----------------------------------------------------------------- + // GAIT DETENT (keyboard/pad accommodation, ported from the pod-build + // lever -- mech4.cpp "GAIT DETENT", 2026-07-20 glass regression): the + // gait SM has NO stable state for a demand between the walk cycle's + // cap (walkStrideLength @0x534) and the run engage speed + // (reverseSpeedMax @0x538) -- a lever PARKED in that band hunts + // walk <-> run forever, retriggering the authored EngineShiftFwd/Rev + // samples each swing. The pod's PHYSICAL throttle plausibly rested + // only at mechanical notches [T4]; reproduce the pod-build bridge's + // accommodation EXACTLY: when the throttle slew is AT REST (no slew + // key held, no pad slew axis deflected), snap the lever out of the + // dead band to the NEARER edge. Sweeping THROUGH the band while + // held stays continuous -- an authentic moving lever, firing the one + // authentic shift. The band arrives in lever units from the player + // mech via the mech4.cpp seam (hi <= lo = no band known yet). + //----------------------------------------------------------------- + // + { + extern float gBTGaitDetentLo, gBTGaitDetentHi; // mech4.cpp seam + const float band_lo = gBTGaitDetentLo; + const float band_hi = gBTGaitDetentHi; + float &lever = channelValue[PadBindingProfile::ChannelThrottle]; + if (!slewHeld[PadBindingProfile::ChannelThrottle] + && band_hi > band_lo + && lever > band_lo && lever < band_hi) + { + const float snapped = + (lever - 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] pad lever " << lever + << " in dead band [" << band_lo << "," << band_hi + << ") -> " << snapped << "\n" << std::flush; } + lever = snapped; + } + } + // //----------------------------------------------------------------- // Clamp and publish the control surface. Throttle is the 0..1 diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index df61c7a..f74aaaf 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -654,6 +654,19 @@ static int gBTPinkyKey = 0; // key '4' = the pod's 4th fire butto int gBTModeCycle = 0; // 'M' edge: cycle the control mode (mapper consumes) int gBTDisplayCycle = 0; // 'N' edge: cycle the secondary schematic (Gitea #6, mapper consumes) int gBTPresetCycle[3] = {0,0,0}; // J/K/L edges: cycle an upper-MFD preset page (Gitea #9, L4 mapper consumes) +// +// GAIT-DETENT seam (glass, 2026-07-20): the walk/run dead band in LEVER units +// (throttlePosition 0..1), published every driven frame by the player mech so +// the PadRIO throttle-slew channel can apply the SAME at-rest snap-out-of-band +// the pod-build keyboard lever has (the "GAIT DETENT" accommodation below) -- +// without it a glass lever parked between the walk cap (walkStrideLength +// @0x534) and the run engage speed (reverseSpeedMax @0x538) hunts walk<->run +// forever, retriggering the EngineShiftFwd/Rev samples (the user's "mixed +// walk/run mode + repeated acceleration sample"). hi <= lo means "no band" +// (mech not yet known / degenerate caps). L4PADRIO.cpp consumes. +// +float gBTGaitDetentLo = 0.0f; +float gBTGaitDetentHi = 0.0f; int gBTLookBehind = 0; // 'V' held: the pod's rear-view button (task #68) float gBTTwistAxis = 0.0f; // Q/E torso-twist deflection (assisted-mode stick X) float gBTElevAxis = 0.0f; // R/F torso-elevation (pitch aim, stick Y) @@ -3037,6 +3050,29 @@ void } } } + // GAIT-DETENT seam publish (glass, 2026-07-20): export the SAME band + // (same guards, same +0.002 engage margin as above) in lever units + // every driven frame, so the PadRIO throttle-slew channel -- which + // replaces this bridge's sLever on glass builds -- can apply the + // identical at-rest snap. Published unconditionally (not just at + // key-rest): the REST decision belongs to the lever's owner, which + // on glass is PadRIO's key/pad-held state, not gBTDrive.keyFwd/Back. + { + extern float gBTGaitDetentLo, gBTGaitDetentHi; + gBTGaitDetentLo = 0.0f; + gBTGaitDetentHi = 0.0f; + if (walkStrideLength > 0.0f + && reverseSpeedMax > walkStrideLength + && reverseStrideLength >= reverseSpeedMax) + { + const float max_demand = reverseStrideLength * forwardThrottleScale; + if (max_demand > 0.0f) + { + gBTGaitDetentLo = walkStrideLength / max_demand; + gBTGaitDetentHi = reverseSpeedMax / max_demand + 0.002f; + } + } + } // the configured rate (CONTROLS.MAP `axis Throttle rate n`) IS the // sweep speed; kLeverRate above stays as the documented default float sweep = gBTInput.leverRate * dt;