feat(host/steam): M5 — fallback remap, motion rescale, degrade ladder

Keep the rich Steam inputs from silently dropping when the resolved backend
isn't the virtual hid-steam device, and fix a cross-device motion-scale bug.

- inject/proto/steam_remap.rs (new, pure + unit-tested):
  * motion_wire_to_deck — the wire carries DualSense-convention units (20 LSB/
    deg.s gyro, 10000 LSB/g accel — what every client capture emits), but the
    Deck's hid-steam report wants 16 LSB/deg.s + 16384 LSB/g. The Deck backend
    now rescales (gyro x16/20, accel x16384/10000): a real Deck<->Deck gyro/
    accel correctness fix (the DualSense/DS4 backends consume the wire 1:1).
  * fold_paddles + RemapConfig (PUNKTFUNK_STEAM_REMAP=paddles=drop|stickclicks|
    shoulders, default drop) — the DualSense + DS4 managers fold a client's back
    grips onto standard buttons rather than dropping them (those pads have no
    back-button HID slot; the uinput Xbox pad already exposes them as Elite
    paddles BTN_TRIGGER_HAPPY5-8).

- resolve_gamepad: a runtime degrade ladder — a UHID backend (DualSense / DS4 /
  Steam Deck) on a host where /dev/uhid isn't writable now falls back to the
  uinput Xbox 360 pad instead of a dead controller (the device-create would
  just fail). Separate from pick_gamepad's compile-time platform check, so the
  existing pick_gamepad tests are untouched.

- Delete the throwaway M0/M1 spike (src/bin/steam_uhid_spike.rs) — M2's
  #[ignore]d backend test subsumes its validation, and removing it frees
  steam_proto to reference steam_remap cleanly.

On-box backend test still green; workspace clippy/fmt/test green (incl. the new
steam_remap tests). Deferred as optional RemapConfig growth: gyro->mouse /
trackpad->stick synthesis on an Xbox target (no slot — documented drop today).
Not pushed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 12:57:23 +00:00
parent d8c254281e
commit 486a292845
8 changed files with 237 additions and 197 deletions
+21 -7
View File
@@ -1,12 +1,26 @@
# Rich Steam Controller & Steam Deck support
> **Status:** **M0M4 GREEN — the full Steam Controller/Deck pipeline is built (2026-06-29).** Host:
> the virtual `hid-steam` Deck binds, is byte-exact, is a wired backend (`PUNKTFUNK_GAMEPAD=
> steamdeck`), and the protocol carries the rich inputs. Clients: the Linux + Windows SDL clients
> capture + send them; the Decky plugin has the Steam Deck mode + Disable-Steam-Input UX; the C-ABI
> has the `TouchpadEx` send path; Apple/Android round-trip the type. Remaining is **validation, not
> construction** (see below) + the deferred extras (M5 fallback-remap polish, M6 SteamOS-host
> conflict check, M7 Windows UMDF Steam driver).
> **Status:** **M0M5 GREEN — full pipeline + fallback polish built (2026-06-29).** Host: the
> virtual `hid-steam` Deck binds, is byte-exact, is a wired backend (`PUNKTFUNK_GAMEPAD=steamdeck`),
> the protocol carries the rich inputs, and the **fallback remap** keeps them from silently dropping
> on a non-Steam backend. Clients: the Linux + Windows SDL clients capture + send them; the Decky
> plugin has the Steam Deck mode + Disable-Steam-Input UX; the C-ABI has the `TouchpadEx` send path;
> Apple/Android round-trip the type. Remaining is **validation, not construction** (see below) + the
> deferred extras (M6 SteamOS-host conflict check, M7 Windows UMDF Steam driver).
>
> **M5 (fallback remap + degrade ladder) result:** new pure, unit-tested `inject/proto/steam_remap.rs`:
> (1) **motion rescale** `motion_wire_to_deck` — the wire carries DualSense-convention units (what
> every client capture emits), the Deck's `hid-steam` report wants 16 LSB/°·s + 16384 LSB/g, so the
> Deck backend now rescales (gyro ×16/20, accel ×16384/10000) — a real **Deck↔Deck gyro/accel
> correctness fix**; (2) **`fold_paddles`** + `RemapConfig` (`PUNKTFUNK_STEAM_REMAP=paddles=drop|
> stickclicks|shoulders`, default drop) wired into the DualSense + DS4 managers so a client's back
> grips aren't silently lost on a PlayStation fallback (those pads have no back-button HID slot; the
> uinput Xbox pad already exposes them as `BTN_TRIGGER_HAPPY5-8`). Plus a **runtime degrade ladder**
> in `resolve_gamepad`: a UHID backend (DualSense/DS4/SteamDeck) on a host where `/dev/uhid` isn't
> writable now falls back to the uinput Xbox 360 pad instead of a dead controller. The throwaway M0/M1
> spike is deleted (M2's `#[ignore]`d backend test subsumes it). On-box backend test still green;
> workspace clippy/fmt/test green. *Deferred as optional `RemapConfig` growth: gyro→mouse / trackpad→
> stick/mouse synthesis on an Xbox target (no IMU/touchpad slot — currently a documented drop).*
>
> **M4 (complete) result:** *(desktop capture — see the prior entry.)* Plus: **C-ABI** —
> `PunktfunkRichInputEx` (size-prefixed superset) + `punktfunk_connection_send_rich_input2` (the