feat(gamepad/apple): send Share/Create as BTN_MISC1 + pin wire bits to the C ABI

G5: buttonMask mapped the dedicated share/create/capture element onto BTN_BACK,
the same bit as View (buttonOptions). On an Xbox-Series pad those are two
distinct physical buttons, so Share was indistinguishable from View on the
host and never delivered the capture bit the host already decodes (DualSense
mute / Steam quick-access). Route it to BTN_MISC1 instead, matching the Rust
client's `Button::Misc1 => wire::BTN_MISC1`. Adds `misc1` to GamepadWire and
allButtons so a held capture button is released on flush like the others.
(On-glass verify owed on a real Xbox-Series pad; a clone pad that exposes one
button as both buttonOptions and Share now emits back+misc1 for it — harmless
on a plain xpad session and rare otherwise.)

G22 (partial): define paddle1..4 for wire completeness, but leave them out of
buttonMask/allButtons until the GameController paddleButton1..4 ↔ BTN_PADDLE
physical correspondence is confirmed on a real Elite pad.

G15: replace the 3-bit spot-check with an exhaustive assertion of every
GamepadWire button/axis constant against the generated C ABI header
(punktfunk_core.h), so any Swift-side drift from punktfunk_core::input::gamepad
fails CI.

swift build + full PunktfunkKit suite green (124 passed, 5 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-13 11:14:26 +02:00
parent 5cd66eca59
commit 68b9f108ab
3 changed files with 69 additions and 8 deletions
@@ -305,11 +305,15 @@ public final class GamepadCapture {
if g.dpad.right.isPressed { b |= GamepadWire.dpadRight }
if g.buttonMenu.isPressed { b |= GamepadWire.start }
if g.buttonOptions?.isPressed == true { b |= GamepadWire.back }
// The share/create/capture element (Xbox Series share, a clone pad's screenshot button
// e.g. the GameSir G8's, below its d-pad) folds into back/select too. On pads that expose
// the create button BOTH as buttonOptions and as the share element this OR is harmless
// same wire bit.
if g.buttons[GCInputButtonShare]?.isPressed == true { b |= GamepadWire.back }
// The dedicated share/create/capture element (Xbox-Series Share, DualSense Create, a clone
// pad's screenshot button e.g. the GameSir G8's, below its d-pad) the wire's capture
// bit, matching the Rust client's `Button::Misc1 => wire::BTN_MISC1`. On an Xbox-Series pad
// this is a button physically DISTINCT from View (buttonOptions, above), so it must not
// collapse onto back the host reads MISC1 as its own control (DualSense mute / Steam
// quick-access). Caveat: a pad that surfaces ONE physical button as both buttonOptions and
// this share element now emits back+misc1 for it harmless on a plain xpad session (no
// misc button) and rare otherwise. NOTE: on-glass verify on a real Xbox-Series pad.
if g.buttons[GCInputButtonShare]?.isPressed == true { b |= GamepadWire.misc1 }
if g.leftThumbstickButton?.isPressed == true { b |= GamepadWire.leftStickClick }
if g.rightThumbstickButton?.isPressed == true { b |= GamepadWire.rightStickClick }
if g.leftShoulder.isPressed { b |= GamepadWire.leftShoulder }