Skip to content

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

@petegordon

Description

@petegordon

Type: Bug / hardware-verification · Status: Observed (2026-06-19)

Context

macOS Electron build, WebHID input spine (#80). Per-vendor 3D model + sensor-fusion gyro is the product. DualSense over Bluetooth on macOS produces incorrect fused orientation — motion data is arriving (fusion runs), but the result is wrong. Failure is in parsing/fusion, not enumeration.

Observed (Fri 2026-06-19)

  • DualSense over BT on macOS → fused orientation swings/jerks hard side to side ("side-to-side fusing"), observed during petegordon/tandemonium gameplay.
  • Same DualSense over USB → correct.

Primary hypothesis

The DualSense BT report (ID 0x31, ~78 bytes, CRC-tailed) has different gyro/accel byte offsets than the USB report (ID 0x01, ~64 bytes). The macOS BT path is likely reading USB offsets, or mis-detecting the report ID, feeding wrong raw values into fusion.

The "side to side" symptom sharpens this: it matches a documented failure mode in the driver where a wrong gyro offset reads an accelerometer axis as gyro. From playstation-driver.js (near init() / the probe): "the gyro tie-breaker can flip to the wrong (off-by-2) neighbour — which reads an accel axis as gyro and makes steering jerk hard side to side." So this looks like an offset misread on the BT path, not a sign-flip or scale error — gravity bleeding into a gyro axis yaws the model side to side as the controller tilts.

Sub-hypothesis (most likely macOS-specific culprit)

macOS/IOKit may include or strip the leading report-ID byte in the inputreport event's data DataView differently than Windows/Linux. The driver's gyro/accel offsets are computed assuming the report-ID byte is absent from event.data (offsets are relative to event.data byteOffset 0, with event.reportId carried separately). If macOS's WebHID/IOKit path includes the report-ID byte (or shifts by the BT counter/header byte differently), every gyro/accel field lands one byte off on the BT path — reading an accel axis as gyro / shifted values into fusion, which matches "data arrives but orientation is wrong" and the side-to-side swing.

Relevant code (where a fix would land)

packages/core/src/drivers/playstation-driver.js:

  • _defaultGyroOffset() — USB DS5 = 15, BT DS5 (0x31) = 16 (+1); DS4 BT (0x11) = +2. The +1/+2 "counter byte" shift is already modeled — but assumes a fixed platform convention for whether the report-ID byte is in event.data.
  • _probeConfig() — BT DS5 path: { reportId: 0x31, baseOffset: 1, candidates: [16, 15, 17] }.
  • _probeImuOffset() — the at-rest IMU-offset probe (scores candidate offsets by at-rest accel magnitude ≈ 8192 = 1g). Key gap: per its own docstring it "Only runs on USB report 0x01 today. BT branch differs … punted for now." So on BT the driver falls back to the documented default offset with no at-rest self-correction — meaning a macOS-specific 1-byte shift would go uncaught, exactly the scenario here.

Suggested investigation

  1. Capture a raw macOS BT 0x31 report and dump event.data.byteLength + the first ~24 bytes alongside event.reportId. Compare against the USB 0x01 capture to see whether the report-ID byte is present in event.data on macOS (i.e. is byteLength ~77 vs ~78?), and whether the CRC tail is present.
  2. Confirm the gyro/accel offsets against the macOS byte layout: at rest, accel magnitude at the chosen offset should be ≈ 8192 (1g) and gyro ≈ 0. If the best at-rest offset is documented ± 1/± 2, that's the smoking gun for the report-ID-byte convention. The side-to-side symptom predicts an axis where gravity (≈8192) is currently being integrated as rotation.
  3. Extend the IMU-offset probe to the BT branch (_probeImuOffset over 0x31/0x11), so the at-rest magnitude check auto-corrects a platform-specific offset shift instead of trusting the static default. The probe-then-add-baseOffset logic already generalizes; only the USB-only guard needs lifting.
  4. Add a committed macOS-BT 0x31 fixture under packages/core/test/fixtures/ (mirroring the existing GameSir USB capture) and a parseReport/offset-probe unit test, so this is regression-covered cross-platform.

Acceptance

  • DualSense over BT on macOS yields fused orientation matching USB (axis directions, scale, stability) — no side-to-side swing.
  • Cross-platform: no regression for DualSense BT on Windows/Linux, or DS4 BT.
  • Covered by a committed macOS-BT fixture + test.

Symptom confirmed from in-game observation during petegordon/tandemonium play, 2026-06-19.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions