forked from unom/punktfunk
Supersedes the check77797a9eshipped an hour ago. The suppression, the log-once, and the "unknown must not suppress" rule all stand; the field it reads does not.77797a9eread `Welcome.gamepad` — the backend the host resolved for the SESSION — and stopped sending motion when it had no motion plane. But the host does not build pads from that. It builds each virtual device from that pad's own `GamepadArrival` (`Pads::set_kind`) and falls back to the session default only for a pad that never declares one, which is precisely why `declared_kind` exists and why its doc comment says an explicit setting has to be re-declared per pad. So the check had a false negative, and it is an ordinary living-room setup. Under "Automatic" the Hello carries the ACTIVE pad's kind (`auto_pref`), so a couch with an X-Box pad on slot 0 and a DualSense on slot 1 echoes Xbox360 — while the host, reading pad 1's arrival, builds it a DualSense with a working motion plane. The old check read the echo, saw no motion plane, and killed pad 1's gyro. That is the exact failure 77797a9e's own commit message names as the worse of the two ("a false negative kills working motion"), introduced by the fix for the other one. The question is per pad, so the slot now carries what it declared, beside the physical `pref` it already held. The two are deliberately separate fields answering different questions: `pref` is the controller in the user's hands, which is what the local feedback paths must keep reading, and `declared` is the one the host is pretending to have. Three facts decide the predicate, and they are written out in `pad_motion_reaches` rather than at the call site because all three clients need the same reasoning: - the echo is not this pad's answer when the pad declared something else; - the host FOLDS what it cannot build — a Switch Pro on Windows, any UHID backend on a host whose /dev/uhid is unusable — and nothing client-side can predict it; - but the echo IS one observed sample of that fold, for the kind the Hello asked about, so it is authoritative for a pad that declared exactly that. Hence: trust the echo when declared == asked, else fall back to the declaration. That keeps both motivating cases — a generic pad under Automatic (declares X-Box 360, suppressed, the sweep's H5c) and an explicit Switch Pro folded to X-Box 360 by a Windows host (declared == asked, so the echo catches it, H5d) — where either field alone gets one of them wrong. `requested_gamepad` is kept on the client next to `resolved_gamepad` for this: the pair is what makes the echo usable per pad, and a lone field would only tempt the next reader back into the session-level question. The residual gap is a pad whose declared kind differs from the session's AND gets folded: we keep sending and the host keeps dropping. That is the direction to be wrong in, and it is what the session-level check was worth in the first place — wasted datagrams, not a dead gyro. Non-vacuity proven both directions rather than assumed. Reverting to `resolved.has_motion()` fails on the mixed-pad row; reverting to `declared.has_motion()` (no echo at all) fails on the Switch-Pro-on-Windows row. Each case in the table is a session someone can actually sit down to, and the comment on each says which of the three inputs decides it. Gate (Linux CI image, pf-lxcheck2): fmt, `build -p punktfunk-core`, `build -p pf-client-core`, `clippy --locked --all-targets -D warnings`, and both test suites — green, with the new case observed in the run's own `... ok` line rather than inferred from a green gate, and pf-client-core's 163 unchanged.