diff --git a/context/pod-hardware.md b/context/pod-hardware.md index b0125b4..4b911fa 100644 --- a/context/pod-hardware.md +++ b/context/pod-hardware.md @@ -25,6 +25,36 @@ to keyboard/gamepad on a dev box; real wiring is a pod bring-up task (Phase 8). model — `LBE4ControlsManager` groups are fed by all devices (RIO on the pod, DirectInput on dev); the `MechControlsMapper` interprets them ([[locomotion]]). [T2] +### The pod throttle is ANALOG-CONTINUOUS, not notched (verified end-to-end) [T1] +The authentic pod throttle path — traced 2026-07 (task #50 throttle-fidelity question): +1. **Hardware → RIO:** serial `AnalogReply` packet → `Ranger("Throttle", 0, 800, .05)` — raw counts + 0–800, 5% deadband, auto-ranging offset, output a CONTINUOUS Scalar; the sign is inverted + ("Throttle counts BACKWARDS", `engine/MUNGA_L4/L4RIO.cpp:1374-1377`, Ranger @L4RIO.cpp:461-701). + No quantization/notching anywhere in `Ranger::Update`. +2. **Manager:** `LBE4ControlsManager::Execute` → `scalarGroup[ScalarThrottle].Update(&rioPointer-> + Throttle, mode_mask)` on every AnalogReply (`L4CTRL.cpp:1379-1382`). ScalarThrottle = index 0 → + manager+0x24 (scalarGroup base 0x24, 0x20/entry; buttonGroup base 0x1c0, keyboardGroup 0x160). +3. **Streamed `.CTL` mapping (the "handled elsewhere"):** `MechRIOMapper`'s ctor @004d266c binds NO + throttle — the bind comes from the type-19 `ControlMappingStream` resource named **"L4"** (child + of the per-mech type-6 `ControlMappingsList`; installed by `BTL4APP MakeViewpointEntity` via + `CreateStreamedMappings` @0047703c). BTL4.RES "L4" record [1]: + `Scalar Throttle → subsystemID 0 (ControlsMapper slot), DirectMapping, attr 4, mask 0xffffffff`. + Attr 4 = "ThrottlePosition" @ mapper+0x11c (binary IndexEntry table @0050efd8: id 4 → offset + 0x11d-1 = 0x11c, name "ThrottlePosition" @0050f28f). Reverse = record [2]: `Button Throttle1 + (0x3F, on the throttle handle) → attr 6 ReverseThrust@0x124`. Turn = pedals value-bound in the + ctor (manager+0x44/+0x64 → mapper+0x1b4/+0x1b8). +4. **Interpretation:** `L4MechControlsMapper::InterpretControls` @004d196c applies the ONLY software + detent — snap to 1.0 when |t−1.0| ≤ 0.05 — then `MechControlsMapper::InterpretControls` @004afd10 + computes `speedDemand@0x128 = maxSpeed(mech+0x34c) × throttlePosition(0x11c) × scale(mech+0x5c0)` + (forward) or `−maxSpeed × throttlePosition` (reverse flag 0x124). + +**Consequence:** the original pod produced a continuously varying speedDemand while the lever moved +(updated per serial AnalogReply, NOT per render frame). There are NO "5 throttle notches" anywhere +in the software path. (Keyboard keys '1'-'5' in @004d1bf0 set controls-manager MODE masks; the +'+'/'-' pair steps a [0,5] value that drives `pow(2,x)` into mech+0x404 = HUD zoom 1×–32× — neither +is a speed setting.) The mech's `throttleState@0x4a4` writer remains un-exported (likely in the +0x4a9b5a–0x4ab188 gap) — [[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/btl4mppr.cpp b/game/reconstructed/btl4mppr.cpp index 66baf62..bc881d8 100644 --- a/game/reconstructed/btl4mppr.cpp +++ b/game/reconstructed/btl4mppr.cpp @@ -349,7 +349,12 @@ L4MechControlsMapper::MessageHandlerSet& // // (1) Full-throttle detent. // - if (Abs(throttlePosition - 1.0f) <= 0.05f) // _DAT_004d1ac0 / _DAT_004d1ac4 + // NB: the Abs() macro (STYLE.H:118) is unparenthesized -- Abs(throttlePosition + // - 1.0f) mis-expands to -(throttlePosition + 1.0f) on the false branch (always, + // since throttle <= 1), which is <= 0.05 UNCONDITIONALLY -> the detent used to + // snap throttle to full every frame. Diff into a temp so the macro sees one token. + const Scalar _thrOff = throttlePosition - 1.0f; + if (((_thrOff < 0.0f) ? -_thrOff : _thrOff) <= 0.05f) // _DAT_004d1ac0 / _DAT_004d1ac4 { throttlePosition = 1.0f; // @0x11c } diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 1c004a5..06b0b80 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -2189,6 +2189,30 @@ void localToWorld = localOrigin; // refresh with the coupled position } + // SLIDE probe (BT_SLIDE): per-frame, log when the peer position MOVES + // while its legs are in STAND (legState 0) -- the user's "sliding after + // legs stopped". Reports move magnitude, offset to authority, leg state. + if (getenv("BT_SLIDE")) + { + static Point3D s_slPrev; static int s_slInit = 0; + const int ls = (int)legStateAlarm.GetLevel(); + if (s_slInit) + { + const float dx = (float)(localOrigin.linearPosition.x - s_slPrev.x); + const float dz = (float)(localOrigin.linearPosition.z - s_slPrev.z); + const float mv = sqrtf(dx * dx + dz * dz); + if (ls == 0 && mv > 0.02f) // standing but moving = slide + { + Vector3D off; off.Subtract(updateOrigin.linearPosition, localOrigin.linearPosition); + DEBUG_STREAM << "[slide] move=" << mv + << " legAdv=" << replLegAdv + << " offToAuth=" << (float)off.Length() + << " legState=" << ls << "\n" << std::flush; + } + } + s_slPrev = localOrigin.linearPosition; s_slInit = 1; + } + // PEER-DRIFT probe (BT_MIRDIV, 1s max): how far the peer's coupled // position wanders from the last-received authority (updateOrigin) // before a record re-anchors it -- the other end of the snap. @@ -3422,6 +3446,21 @@ void // impact freezes travel as before.) const Scalar travelAdv = s_realControls ? legAdv : adv; const Scalar localAdv = travelAdv * dir; + // MASTER-SLIDE probe (BT_SLIDE): does the MASTER itself advance while its + // legs are in STAND (legState 0)? If yes -> authentic momentum coast, the + // peer correctly mirrors it. If the master travelAdv reaches 0 exactly + // when legState reaches 0 -> the master stops clean and the peer's stand- + // slide is a peer leg-SM-too-fast desync. Logs the decel profile. + if (getenv("BT_SLIDE")) + { + const int mls = (int)legStateAlarm.GetLevel(); + const float ta = (float)(travelAdv < 0 ? -travelAdv : travelAdv); + if (ta > 0.005f) // master still moving + DEBUG_STREAM << "[mslide] travelAdv=" << travelAdv + << " legState=" << mls + << " legCycle=" << legCycleSpeed + << " thr=" << throttle << "\n" << std::flush; + } linearSpeed = (localAdv < 0.0f ? -localAdv : localAdv) * invDt; // forward ground speed -> LinearSpeed gauge Vector3D localVel(0.0f, 0.0f, -localAdv * invDt); // exact frame distance as velocity Matrix34 orient; // rotation from the heading (set @ line ~626)