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
- 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.
- 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.
- 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.
- 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.
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)
Primary hypothesis
The DualSense BT report (ID
0x31, ~78 bytes, CRC-tailed) has different gyro/accel byte offsets than the USB report (ID0x01, ~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(nearinit()/ 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
inputreportevent'sdataDataView differently than Windows/Linux. The driver's gyro/accel offsets are computed assuming the report-ID byte is absent fromevent.data(offsets are relative toevent.databyteOffset 0, withevent.reportIdcarried 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 inevent.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 report0x01today. 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
0x31report and dumpevent.data.byteLength+ the first ~24 bytes alongsideevent.reportId. Compare against the USB0x01capture to see whether the report-ID byte is present inevent.dataon macOS (i.e. isbyteLength~77 vs ~78?), and whether the CRC tail is present.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._probeImuOffsetover0x31/0x11), so the at-rest magnitude check auto-corrects a platform-specific offset shift instead of trusting the static default. The probe-then-add-baseOffsetlogic already generalizes; only the USB-only guard needs lifting.0x31fixture underpackages/core/test/fixtures/(mirroring the existing GameSir USB capture) and aparseReport/offset-probe unit test, so this is regression-covered cross-platform.Acceptance
Symptom confirmed from in-game observation during petegordon/tandemonium play, 2026-06-19.