Skip to content

PlayStation BT IMU: guard against accel-as-gyro offset shift (macOS side-to-side) (#83)#84

Draft
petegordon wants to merge 1 commit into
mainfrom
claude/issue-83-macos-bt-imu-offset
Draft

PlayStation BT IMU: guard against accel-as-gyro offset shift (macOS side-to-side) (#83)#84
petegordon wants to merge 1 commit into
mainfrom
claude/issue-83-macos-bt-imu-offset

Conversation

@petegordon

Copy link
Copy Markdown
Member

First pass at #83 — DualSense over Bluetooth on macOS fusing to a wrong, side-to-side orientation (USB is fine). Draft: needs a real macOS + DualSense BT capture to confirm before merge.

What I found

The IMU-offset probe already runs on Bluetooth (_probeConfig() handles 0x31/0x11) — the docstring claiming "USB only" was stale. The real gap was the selection in init(): it trusted the documented offset whenever its at-rest accel looked ~1g, without checking gyro.

A byte-shifted offset — e.g. macOS/IOKit including the leading report-ID byte in the BT 0x31 event.data — reads an accelerometer axis as a gyro axis, so its at-rest "gyro" sits near gravity (~8192 raw). Fusion integrates that as rotation → the model swings hard side to side, exactly the in-game symptom. The old accel-only check let such a shifted default still score "plausible" and win.

Change

  • Extracted the selection into a testable _chooseImuOffset(probe) with an accel-as-gyro guard: reject the documented default when its at-rest |gyro| is pinned near gravity, and recover the cleanest candidate (≈1g accel and near-zero gyro).
  • The high-gyro gate means a small real bias — or a pad merely being handled during the probe — never trips the override, so the existing off-by-2 still-pad protection is preserved (verified by the existing init test, now also a direct unit test).
  • Added raw-bytes + length diagnostic logging to the probe so a macOS BT 0x31 capture (length / report-ID-byte presence) is one console line away — that's the key unknown.
  • Fixed the stale "USB only" docstring.

Tests (89 pass, +4)

  • chooseImuOffset keeps the documented default when it's clean.
  • Recovers the +1-shifted offset when the default reads accel-as-gyro (the macOS side-to-side case the old logic got wrong).
  • Does not override on a small real bias (off-by-2 still-pad protection).
  • Falls back to the documented default when nothing is clean (pad in motion).

⚠️ Hardware verification needed before merge

I can't run macOS + DualSense BT here, so this is a robustness fix targeting the documented accel-as-gyro failure mode, not a confirmed root-cause fix. To close the loop, on a macOS BT session grab the new console lines:

PlayStation IMU probe scan (report 0x31, NNB): g=16 accel≈… gyro≈…  |  g=15 …  |  g=17 …
PlayStation IMU probe bytes (bluetooth 0x31, len=NN, baseOffset=1): 31? .. .. …

That tells us (a) the real event.data byteLength vs USB, (b) whether the report-ID byte is present, and (c) which offset is actually clean — which confirms whether this fix lands the right orientation or whether the true offset is outside the current candidate set [16, 15, 17] (easy follow-up to widen).

Fixes #83 once verified.

🤖 Generated with Claude Code

https://claude.ai/code/session_017NiS2a4jZ877XgftkH1Dd1


Generated by Claude Code

…ide-to-side) (#83)

First pass at the macOS BT DualSense "side to side" fusion bug. The IMU-offset
probe already runs on Bluetooth (the docstring claiming USB-only was stale);
the gap was the selection in init(), which trusted the documented offset
whenever its at-rest ACCEL looked ~1g — without checking gyro. A byte-shifted
offset (e.g. macOS/IOKit including the leading report-ID byte in the BT 0x31
DataView) reads an accel axis AS a gyro axis, so its at-rest "gyro" sits near
gravity (~8192). Fusion integrates that as rotation → the model swings side to
side, exactly the in-game symptom.

- Extract the selection into a testable `_chooseImuOffset(probe)` and add an
  accel-as-gyro guard: reject the default when its at-rest |gyro| is pinned
  near gravity, and recover the cleanest candidate (~1g accel AND near-zero
  gyro). The high-gyro gate means a small real bias / a handled pad never trips
  it, so the existing off-by-2 still-pad protection is preserved.
- Add raw-bytes + length diagnostic logging on the probe so a macOS BT 0x31
  capture (the key unknown) is one console line away.
- Fix the stale "USB only" probe docstring.
- Tests: default-kept, macOS accel-as-gyro recovery, off-by-2 still-pad
  protection, and motion fallback (89 pass).

Hardware-verification pending: needs a real macOS + DualSense BT capture to
confirm the exact byte mechanism (length/report-ID-byte) and that the fix
lands the correct orientation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017NiS2a4jZ877XgftkH1Dd1
@github-actions

Copy link
Copy Markdown

🔎 PR #84 preview — test it in isolation

🌐 Web overlay: https://lab.usersfirst.games/pr-preview/pr-84/overlay/
(landing page: https://lab.usersfirst.games/pr-preview/pr-84/)

💻 Desktop installers (prerelease): https://github.com/UsersFirst/tandemonium-controller-lab/releases/tag/pr-84

  • Windows…-Setup.exe (installer) or .zip (portable)
  • macOSWebHID-Controller-Overlay.dmg or .zip (unsigned: right-click → Open)

Updated for 0f37142. Web preview and prerelease are torn down when this PR closes.

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.

macOS WebHID: DualSense Bluetooth gyro fusion produces wrong orientation (USB-vs-BT report layout)

2 participants