docs(steam): criterion-4 RESOLVED — the interface-2 ceiling; recommend dropping M7
Hardware finding (a SteamOS Deck @ .253 + a Bazzite host @ .41, both running
Steam, via a minimal C UHID probe on Bazzite): a UHID virtual Steam Deck binds
the kernel hid-steam and creates the evdevs (so kernel-evdev + SDL-hidapi
consumers see the full grips/trackpads/IMU surface), but Steam Input will NOT
manage it. Steam's controller.txt enumerates it ("Local Device Found, 28de 1205,
Product Punktfunk Steam Deck") but logs Interface: -1 and never promotes it (no
28de:11ff XInput pad). The physical Deck on the same logs is Interface: 2 — a
real Deck is a 3-interface USB device (kbd 0 / mouse 1 / controller 2) and Steam
binds the controller on interface 2; a single UHID device has no USB interface
number, so Steam reads -1 and filters it out. (The feared 0x83/0xA1 attribute
probes never fired — it's an interface filter, not a probe-reject.)
Consequences (design §11):
- The virtual Deck's value is non-Steam / SDL games on Linux (grips + trackpads
+ gyro via evdev / SDL HIDAPI), NOT Steam Input.
- The virtual DualSense stays the Steam-Input path everywhere (Steam recognizes
a single-interface DualSense); M5's paddle-fold carries the back grips.
- M7 (a Windows UMDF virtual Deck) is NOT recommended: same interface filter,
and Windows has no kernel-hid-steam evdev fallback, so nothing would consume
it; the existing Windows virtual DualSense already covers that case.
- M0-M6 is not wasted: the protocol/wire + client capture feed the DualSense
path too, and the virtual Deck is the best option for non-Steam Linux games.
Doc-only (design/steam-controller-deck-support.md): added §11, updated the status
+ pending-validation. Not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,21 +6,32 @@
|
|||||||
> the **conflict gate** keeps a virtual Steam pad off a host that already has a physical one. Clients:
|
> the **conflict gate** keeps a virtual Steam pad off a host that already has a physical one. Clients:
|
||||||
> the Linux + Windows SDL clients capture + send them; the Decky plugin has the Steam Deck mode +
|
> 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.
|
> Disable-Steam-Input UX; the C-ABI has the `TouchpadEx` send path; Apple/Android round-trip the type.
|
||||||
> Remaining: M7 (Windows UMDF Steam driver) + the last hardware validation.
|
>
|
||||||
|
> **⚠ Hardware finding that reframes the ceiling (2026-06-29, §11):** a UHID virtual Deck binds the
|
||||||
|
> kernel `hid-steam` (so the **kernel evdev + SDL-hidapi** consumers see the full surface — grips,
|
||||||
|
> trackpads, IMU) but **Steam Input will NOT manage it** — Steam filters the Deck's controller to USB
|
||||||
|
> **interface 2**, and a single UHID device reports interface `-1`. So the virtual Deck's value is for
|
||||||
|
> **non-Steam / SDL games on Linux**, not Steam Input; the **virtual DualSense** stays the right path
|
||||||
|
> for Steam-Input hosts (Steam recognizes a single-interface DualSense). **Recommendation: do NOT
|
||||||
|
> build M7** (a Windows virtual Deck would hit the same filter with no kernel-evdev fallback — nothing
|
||||||
|
> on Windows would consume it). Remaining is validation only (Moonlight paddle regression; a live
|
||||||
|
> SDL-game consume test).
|
||||||
>
|
>
|
||||||
> **M6 (conflict gate) result — validated on real hardware (a SteamOS Deck + a Bazzite host running
|
> **M6 (conflict gate) result — validated on real hardware (a SteamOS Deck + a Bazzite host running
|
||||||
> Steam, 2026-06-29):** (a) **Empirical conflict confirmed.** A Deck-as-host already has its physical
|
> Steam, 2026-06-29):** (a) **Empirical conflict confirmed.** A Deck-as-host already has its physical
|
||||||
> `28DE:1205` *and* Steam's `28DE:11FF` XInput output pad live — so a second virtual `28DE` makes Steam
|
> `28DE:1205` *and* Steam's `28DE:11FF` XInput output pad live — so a second virtual `28DE` makes Steam
|
||||||
> juggle two Decks. (b) **Bind robustness:** the virtual Deck binds `hid-steam` on a *second*
|
> juggle two Decks. (b) **Bind robustness:** the virtual Deck binds `hid-steam` on a *second*
|
||||||
> independent kernel (Bazzite 6.17.7, vs the dev box 7.0) and the kernel accepted our serial (the M1
|
> independent kernel (Bazzite 6.17.7, vs the dev box 7.0) and the kernel accepted our serial (the M1
|
||||||
> report-id-0 fix). (c) **Criterion-4 (running-Steam recognition) — partial:** with Steam running on
|
> report-id-0 fix). (c) **Criterion-4 (running-Steam recognition) — RESOLVED, negative for Steam
|
||||||
> Bazzite, a userspace consumer (Steam/SDL) *did* engage the virtual Deck — it opened the hidraw and
|
> Input (this is the third wall, §2).** Steam's `controller.txt` *enumerates* the virtual Deck
|
||||||
> ran the lizard-disable + settings sequence (`0xAE`/`0x85`/`0x8E`/`0x81`/`0x87`; the kernel's Deck path
|
> (`Local Device Found, type: 28de 1205, Product "Punktfunk Steam Deck"`) but logs **`Interface:
|
||||||
> skips lizard, so that's Steam/SDL) — **but emitted no `28DE:11FF` XInput pad** on the desktop, even
|
> -1`** and never promotes it (no `28DE:11FF` pad, no "Controller connected"). On the same Steam logs
|
||||||
> though the Deck proves desktop-Steam *does* emit one for a recognized Deck. So Steam recognizes the
|
> the **physical** Deck is **`Interface: 2`** — a real Deck is a 3-interface USB device (keyboard 0,
|
||||||
> device enough to manage its lizard mode but did **not** promote it to a managed XInput controller —
|
> mouse 1, **controller 2**), and Steam binds the controller on interface 2. A single UHID device has
|
||||||
> likely needs a Big-Picture/game foreground context or a richer device (the `0x83`/`0xA1` attribute
|
> no USB interface number → `-1` → Steam skips it. `hid-steam` binds by VID/PID regardless (so the
|
||||||
> probes the research feared never fired, so it wasn't a probe-reject either). **Policy (code):**
|
> kernel evdevs + SDL-hidapi path work), **but Steam Input itself will not manage a UHID virtual
|
||||||
|
> Deck.** (The feared `0x83`/`0xA1` attribute probes never fired — it's an interface filter, not a
|
||||||
|
> probe-reject.) See §11 for what this means + the M7 recommendation. **Policy (code):**
|
||||||
> `physical_steam_controller_present()` (scans `/sys/bus/hid/devices` for a non-virtual `28DE`) +
|
> `physical_steam_controller_present()` (scans `/sys/bus/hid/devices` for a non-virtual `28DE`) +
|
||||||
> `degrade_steam_on_conflict()` in `resolve_gamepad` — a resolved `SteamDeck`/`SteamController` on a
|
> `degrade_steam_on_conflict()` in `resolve_gamepad` — a resolved `SteamDeck`/`SteamController` on a
|
||||||
> host with a physical Steam controller degrades to DualSense (then the uhid ladder), overridable with
|
> host with a physical Steam controller degrades to DualSense (then the uhid ladder), overridable with
|
||||||
@@ -53,13 +64,13 @@
|
|||||||
> device; Android has no rich-input plane yet). Rust workspace clippy/fmt/test green; Decky `src/`
|
> device; Android has no rich-input plane yet). Rust workspace clippy/fmt/test green; Decky `src/`
|
||||||
> typechecks clean; Swift/Kotlin compile on their CI.
|
> typechecks clean; Swift/Kotlin compile on their CI.
|
||||||
>
|
>
|
||||||
> **Pending VALIDATION:** (1) **running-Steam recognition — PARTIAL** (M6: Steam engages the virtual
|
> **Pending VALIDATION (construction is done; M7 is NOT recommended — §11):** (1) running-Steam
|
||||||
> Deck's hidraw but didn't emit an XInput pad on the desktop; re-test with a foreground Big-Picture
|
> recognition is **RESOLVED** — Steam won't promote a UHID virtual Deck (interface filter, §11); the
|
||||||
> game on the Deck/Bazzite to see if it promotes then — and if not, capture the real `0x83`/`0xA1`
|
> virtual Deck serves non-Steam/SDL games, the virtual DualSense serves Steam Input. (2) A **live
|
||||||
> attribute blobs from a physical Deck for M7-style fidelity); (2) a **live Deck/Steam Controller
|
> SDL/non-Steam game** consuming the virtual Deck's grips/trackpads (the path that works) — needs a
|
||||||
> client** actually sending paddles/trackpads/gyro (the desktop capture is built but unexercised by
|
> real Deck/SC client + a Steam-Input-disabled consumer; note the Deck's `/dev/uhid` is root-only
|
||||||
> real hardware — note the Deck's `/dev/uhid` is root-only `0600`, so the Deck-as-host needs a udev
|
> `0600`, so a Deck-as-host needs a udev rule for the input group. (3) The **Moonlight paddle
|
||||||
> rule for the input group); (3) the **Moonlight paddle regression** from the M3 xpad-map change.
|
> regression** from the M3 xpad-map change.
|
||||||
>
|
>
|
||||||
> **M4 (desktop client capture) result:** `clients/{linux,windows}/src/gamepad.rs` (the SDL services)
|
> **M4 (desktop client capture) result:** `clients/{linux,windows}/src/gamepad.rs` (the SDL services)
|
||||||
> now: set the SDL HIDAPI Steam hints (`SDL_JOYSTICK_HIDAPI_STEAMDECK`/`_STEAM`) so SDL opens Valve
|
> now: set the SDL HIDAPI Steam hints (`SDL_JOYSTICK_HIDAPI_STEAMDECK`/`_STEAM`) so SDL opens Valve
|
||||||
@@ -646,3 +657,64 @@ phase, itself re-gated on its own recognition spike.
|
|||||||
|
|
||||||
(Full risks + open questions in the structured fields.)
|
(Full risks + open questions in the structured fields.)
|
||||||
|
|
||||||
|
|
||||||
|
## 11. The interface-2 ceiling — Steam Input won't manage a UHID virtual Deck (hardware-validated 2026-06-29)
|
||||||
|
|
||||||
|
Validated on a SteamOS Steam Deck (`192.168.1.253`) + a Bazzite host (`192.168.1.41`), both running
|
||||||
|
Steam, with a minimal C UHID probe (`28DE:1205` + the proven descriptor/handshake) run on Bazzite
|
||||||
|
(no physical Steam controller, so a clean test bed).
|
||||||
|
|
||||||
|
**What works.** The kernel `hid-steam` binds the virtual Deck by VID/PID on a second independent
|
||||||
|
kernel (Bazzite 6.17.7) exactly as on the dev box (7.0): it accepts our serial (the M1 report-id-0
|
||||||
|
fix), and creates both the `"Steam Deck"` gamepad evdev and the `"Steam Deck Motion Sensors"` IMU
|
||||||
|
evdev. So **any consumer that reads the kernel evdev or opens the hidraw via SDL's HIDAPI Steam Deck
|
||||||
|
driver sees the full surface** — the four grips (`BTN_GRIPL/R/L2/R2`), both trackpads (`ABS_HAT0/1`),
|
||||||
|
and the IMU.
|
||||||
|
|
||||||
|
**What does NOT work: Steam Input promotion.** Steam's own controller driver *enumerates* the device
|
||||||
|
— `controller.txt` logs `Local Device Found, type: 28de 1205, Product "Punktfunk Steam Deck", path
|
||||||
|
/dev/hidraw1, Interface: -1` — but never promotes it: no `28DE:11FF` virtual XInput pad, no
|
||||||
|
"Controller N connected". On the same Steam logs the **physical** Deck appears as **`Interface: 2`**.
|
||||||
|
A real Steam Deck is a **3-interface USB device** (keyboard = interface 0, mouse = 1, **controller =
|
||||||
|
2**), and Steam binds the controller specifically on interface 2. A single `/dev/uhid` device is not
|
||||||
|
a USB device and has no `bInterfaceNumber`, so Steam reads **`-1`** and filters it out. (Notably the
|
||||||
|
`0x83 GET_ATTRIBUTES` / `0xA1 GET_DEVICE_INFO` probes the prior research feared — SDL #12166 — never
|
||||||
|
fired: this is an interface filter, not an attribute-probe rejection. That blocker, if it exists, is
|
||||||
|
Windows-driver-specific.)
|
||||||
|
|
||||||
|
**Why UHID can't fix it.** UHID creates one HID interface with no USB interface number; you cannot set
|
||||||
|
one, and creating three UHID devices wouldn't help (each is still interface-less / `-1`). Presenting a
|
||||||
|
real multi-interface USB Deck with the controller on interface 2 needs a **USB gadget** (`dummy_hcd` +
|
||||||
|
configfs) or a kernel USB bus driver — a much larger, less portable lift, and contrary to punktfunk's
|
||||||
|
"no kernel bus driver" stance (ViGEm was deliberately removed).
|
||||||
|
|
||||||
|
### Strategic consequences
|
||||||
|
|
||||||
|
- **The virtual Deck's real value is non-Steam / SDL games on Linux** (emulators, native SDL titles,
|
||||||
|
anything reading the kernel evdev or SDL's HIDAPI Steam driver) — there it delivers grips +
|
||||||
|
trackpads + gyro. It does **not** deliver Steam Input glyphs/bindings.
|
||||||
|
- **For Steam-Input hosts, the virtual DualSense is the right path** on every platform: Steam *does*
|
||||||
|
recognize a single-interface DualSense (it needs no interface filtering), giving the client gyro +
|
||||||
|
a touchpad through Steam Input; the M5 paddle-fold carries the back grips onto standard buttons.
|
||||||
|
This is why the M6 conflict gate degrades to DualSense, and why `Auto` already prefers it.
|
||||||
|
- **M7 (a Windows UMDF virtual Steam Deck) is NOT recommended.** Windows Steam applies the same
|
||||||
|
interface filter, and Windows has **no kernel-`hid-steam` evdev fallback** — Windows games consume
|
||||||
|
XInput / RawInput / Windows.Gaming.Input, none of which a non-promoted virtual Deck feeds. So a
|
||||||
|
Windows virtual Deck would be consumed by *nothing*. The existing Windows **virtual DualSense**
|
||||||
|
already covers the Steam-Input + gyro/touchpad case there.
|
||||||
|
|
||||||
|
### What the M0–M6 work still delivers (not wasted)
|
||||||
|
|
||||||
|
- The **protocol/wire** (back buttons, second trackpad, gyro/accel, trackpad haptics) and the
|
||||||
|
**client capture** (paddles, both trackpads, gyro from a real Deck/SC) are general — they feed the
|
||||||
|
virtual DualSense path (Deck client → Steam-Input host) just as well, with the grips folded in (M5).
|
||||||
|
- The **virtual Deck backend** is the best option for non-Steam Linux games, and the **M5 motion
|
||||||
|
rescale + fallback remap** + the **M6 conflict gate** make the cross-backend behavior correct.
|
||||||
|
- The whole effort proved the greenfield `hid-steam` UHID device is real and kernel-validated on two
|
||||||
|
kernels — the open question was always Steam-userspace promotion, and now it's answered.
|
||||||
|
|
||||||
|
### Remaining validation (no further construction recommended)
|
||||||
|
|
||||||
|
1. A **live SDL/non-Steam game** on a Linux host actually consuming the virtual Deck's grips/trackpads
|
||||||
|
(the path that *does* work) — needs a real Deck/SC client + a Steam-Input-disabled consumer.
|
||||||
|
2. The **Moonlight paddle regression** from the M3 xpad-map change (stock paddle client → host).
|
||||||
|
|||||||
Reference in New Issue
Block a user