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
@@ -26,11 +26,27 @@ public enum GamepadWire {
public static let y: UInt32 = 0x8000
/// DualSense touchpad click (Moonlight's extended-button bit position).
public static let touchpadClick: UInt32 = 0x10_0000
/// Misc / capture button Xbox-Series Share, DualSense Create, Steam-Deck quick-access
/// (Moonlight's extended-button namespace; `input::gamepad::BTN_MISC1`). The host routes it to
/// the DualSense mute / Steam quick-access menu; a plain virtual xpad has no such button.
public static let misc1: UInt32 = 0x0020_0000
/// Back-grip paddles (Xbox Elite P1P4 / DualSense Edge / Steam-Deck L4-L5-R4-R5), in
/// Moonlight's extended-button namespace (`input::gamepad::BTN_PADDLE1..4`, R4/L4/R5/L5).
/// Defined for wire completeness and pinned by the tests; `GamepadCapture.buttonMask` does not
/// read them yet the GameController `paddleButton1..4` BTN_PADDLE physical correspondence
/// needs confirming on a real Elite pad first (see the gamepad-review-cleanup plan, G22), so
/// they are intentionally absent from `allButtons` until that forwarding lands.
public static let paddle1: UInt32 = 0x0001_0000
public static let paddle2: UInt32 = 0x0002_0000
public static let paddle3: UInt32 = 0x0004_0000
public static let paddle4: UInt32 = 0x0008_0000
/// Every button `buttonMask`/`sendGuide` can set walked by `sync`'s transition diff and by
/// `flush` on release. Paddles are excluded until their capture lands (see above).
public static let allButtons: [UInt32] = [
dpadUp, dpadDown, dpadLeft, dpadRight, start, back,
leftStickClick, rightStickClick, leftShoulder, rightShoulder, guide,
a, b, x, y, touchpadClick,
a, b, x, y, touchpadClick, misc1,
]
public static let axisLSX: UInt32 = 0