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;