Skip to content

fix: stop gyro drift via Eden-matched axis defaults + continuous bias calibration - #4

Merged
NightHammer1000 merged 6 commits into
mainfrom
claude/investigate-gyro-drift-T3tk9
May 13, 2026
Merged

NightHammer1000 merged 6 commits into
mainfrom
claude/investigate-gyro-drift-T3tk9

Conversation

@NightHammer1000

@NightHammer1000 NightHammer1000 commented May 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes user reports of gyro drifting over time. Two independent causes, both addressed here.

  1. Default axis maps did not match Eden/Yuzu's Cemuhook UDP remap. Closes Default axis maps do not match Eden/Yuzu Cemuhook UDP motion interpretation #3. Eden reads incoming DSU as (pitch, roll, -yaw) for gyro and (accel.x, -accel.z, accel.y) for accel. Our defaults were built for SDL's Steam Triton sensor order, so the emulator received gyro on mismatched axes/signs — visible as continued one-axis drift after motion stopped. AxisMap::DEFAULT now matches Eden's gyro expectation, and a new AxisMap::DEFAULT_ACCEL covers the asymmetric accel remap. Config::DEFAULT.accel wires to the new constant.

  2. Continuous gyro bias calibration ported from JibbSmart/GamepadMotionHelpers AddSampleStillness in GamepadMotion.hpp v9 (MIT). SDL's Triton driver and evdevhook2 both punt the zero-rate offset to the consumer; the emulator integrates it as orientation drift. New src/gyro_calibration.rs gates stillness on combined gyro + accel deltas across a growing collection window; once steady, the bias is exponential-LERP'd toward the window-mean gyro with confidence-scaled half-time, so the first lock is instant and later updates are slow (thermal drift). Accel is part of the gate so a slow constant rotation can't masquerade as stillness.

    Owned by OpenSlot, applied right after parse_state. dt comes from imu.timestamp_us clamped to 100 ms. Resets on slot reopen and on axis-map config change. Manual recalibration via the existing Recenter viz button (new stats::RECALIBRATE_REQUEST atomic) — set the controller down, click, gyro is zeroed.

⚠️ Note for existing users

Existing configs at %APPDATA%\sc2dsu\config.toml are not auto-migrated. Anyone who launched a previous version has the old (SDL-ordered) defaults saved as their config and will keep drifting in Eden/Yuzu until they either:

  • delete %APPDATA%\sc2dsu\config.toml and let the app write the new defaults on next launch, or
  • adjust the gyro/accel axis mapping manually in the settings window to match the new defaults (gyro: X, -Z, Y · accel: -X, -Z, Y).

A clean install picks up the correct defaults automatically. Worth calling out in the release notes for 0.1.4.

Test plan

  • CI: cargo fmt --check, cargo clippy -- -D warnings, cargo test --locked, cargo build --release --locked all pass on Windows.
  • Manual: fresh install on Windows, Proteus Puck plugged in, launch Eden — gyro behaves correctly without per-axis tweaks.
  • Manual: leave controller on desk after launch, confirm gyro values converge to ~0 dps within ~2 s (continuous calibration locks).
  • Manual: rotate controller, set it down, hit Recenter viz — gyro recalibrates against the new resting orientation.
  • Manual: change axis mapping in the UI mid-session — calibration resets, no stale bias bleed-through.
  • Manual: existing-user upgrade path — verify drift persists until config is reset (documents the migration caveat).

claude and others added 6 commits May 13, 2026 08:26
…ibration

Two independent causes of "gyro drifts over time" in emulators:

1. Default axis maps were built for SDL's Steam Triton sensor order, but
   Eden/Yuzu's Cemuhook UDP client remaps incoming DSU as
   (pitch, roll, -yaw) / (accel.x, -accel.z, accel.y). The mismatch fed
   the emulator gyro on the wrong axes/signs, which showed up as
   continued one-axis drift after motion stopped. Closes #3.

   AxisMap::DEFAULT now matches Eden's gyro expectation, and a new
   AxisMap::DEFAULT_ACCEL covers the asymmetric accel remap. Config
   defaults wire accel to the new constant.

2. Raw IMU has a permanent zero-rate offset (typical 0.1–2 dps); both
   SDL and evdevhook2 punt this to the consumer, but the emulator
   integrates it as orientation drift. Adds a continuous bias estimator
   ported from JibbSmart/GamepadMotionHelpers (AddSampleStillness in
   GamepadMotion.hpp v9, MIT). Stillness is gated on combined gyro+accel
   deltas across a growing collection window; once steady the bias is
   exponential-LERP'd toward the window-mean gyro with confidence-scaled
   half-time. Owned by OpenSlot, applied after parse_state, reset on
   slot reopen and on AxisMap change. dt comes from the IMU timestamp
   clamped to 100 ms.

   "Recenter viz" now also forces a fresh calibration via a new
   stats::RECALIBRATE_REQUEST atomic — user puts the controller down,
   clicks the button, gyro is zeroed.

Tests cover the stillness lock, motion-rejection, reset behaviour, and
that brief motion after a lock does not re-zero on bad samples.

https://claude.ai/code/session_01JQm3i22nVDxVCwvgvLMuex
…ion tests

clippy --all-targets flagged three for-i-in-0..3 / 0..N loops in
gyro_calibration tests where the index is only used to look up parallel
arrays. Rewrite to iter().enumerate() so -D warnings stays green.

https://claude.ai/code/session_01JQm3i22nVDxVCwvgvLMuex
Existing users carry the old SDL-ordered axis defaults in their saved
config.toml and keep drifting in Eden/Yuzu after the gyro fix. This
adds a one-click reset inside the System frame: confirm prompt, then
write Config::DEFAULT via the existing update_and_save path and resync
every widget. Avoids asking users to hand-delete %APPDATA%\sc2dsu\config.toml.
User-facing:
- "Auto-calibrate bias" checkbox in the gyro frame. Default on; off
  bypasses the calibrator and passes raw gyro through. Toggling resets.
- Subscribers row reports "gyro: off / calibrating… / locked N%" so
  users can tell when it's safe to start.
- "Recenter viz" renamed to "Recalibrate"; the button has also reset
  the bias calibrator since the drift fix, so the old name understated
  what it did.

stats refactor: ServerStats is now split into ServerSection (dsu),
MotionSection (dsu) and CalibrationSection (triton). Each writer can
only mutate its own section, which structurally removes the silent
clobbering the full-replace publish() had between dsu's periodic
stats emit and triton's per-sample calibration publish. Dead
last_accel bookkeeping in dsu.rs went with the removed code path.
@NightHammer1000
NightHammer1000 merged commit 2c766bd into main May 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default axis maps do not match Eden/Yuzu Cemuhook UDP motion interpretation

2 participants