The Switch applies its stored SPI calibration when interpreting IMU data:
gyro_dps = (raw - spi_origin) * 936 / coeff
The firmware had real hardware offsets as calibration origins:
gyro_origin = (9, -22, -95) accel_origin = (-29, -199, 493)
A real Pro Controller sensor reads those values at rest, so the Switch
subtracts them to get zero. But our bridge already removes hardware bias
via gyro bias calibration and sends near-zero counts when still.
The Switch was then applying a second origin correction:
gyro_z=2 → (2 - (-95)) * 0.070 = 6.79 dps constant yaw rotation
This caused the character to spin horizontally even when holding the
controller perfectly still.
Fix: zero all calibration origins. The bridge handles bias correction;
the Switch must not apply a second offset on top.
Two bugs causing constant camera drift and jarring first-frame behaviour:
1. Bias calibration was immediately collecting samples at launch, while the
user is still typing / setting down the controller. This polluted the bias
estimate (observed: by=-0.073 rad/s vs true ~-0.009 rad/s), causing a
permanent ~4 deg/s camera drift even when the controller is held still.
Fix: reject samples with gyro magnitude >= 0.5 rad/s (motion threshold)
during the calibration window so only truly still samples count. Also add
a return-early so no IMU is sent to the Pico until bias is locked.
2. last_accel initialised to (0,0,0), but the first gyro event fires before
the first accel event. The Pico received accel=(0,0,0) on the first sample
instead of the expected ~4096 counts on the gravity axis.
Fix: default last_accel to (0.0, 9.80665, 0.0) — gravity on SDL Y axis,
which is correct for a Pro Controller held in normal gaming position.
- Migrate all SDL2 GameController API calls to SDL3 Gamepad equivalents
- Fix PermissionError on controller reconnect by closing UART handle before
releasing port back to pool (Windows holds COM ports exclusively)
- Add DisplayIndexAllocator so controllers get stable display indices (0, 0, 0)
instead of incrementing SDL instance IDs (1, 2, 3) on each reconnect
- Button mapping now uses SDL3 positional constants (SOUTH/EAST/WEST/NORTH)
which works correctly for all controller types without USE_BUTTON_LABELS hint
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
SDL3 includes a dedicated 8BitDo HIDAPI driver with native gyro/accel sensor support, which SDL2 lacked.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
SDL's hidapi_switch.c SendSensorUpdate() remaps Nintendo's raw sensor axes
to match PlayStation convention before emitting SDL sensor events:
SDL_X = -Nintendo_Y, SDL_Y = +Nintendo_Z, SDL_Z = -Nintendo_X
This commit reverses that transform so the values sent to the Pico are in
Nintendo's native coordinate frame:
Nintendo_X_raw = -SDL_Z
Nintendo_Y_raw = -SDL_X
Nintendo_Z_raw = +SDL_Y
Same mapping for gyro (bias subtraction happens before axis reversal).
Confirmed from SDL3 hidapi_switch.c source (SDL2 uses same SDL3 backend).
- Add SwitchImuSample struct and IMU fields to SwitchInputState
- Add fill_imu_report_data() to pack samples into imuData[36]
- Rewrite switch_pro_apply_uart_packet() for v2 with checksum validation
- Enlarge poll_uart_frames() buffer to 64 bytes for variable-length frames
- Clear imu_sample_count after each USB report send (prevent stale IMU)