The Apple speed test asked for only 400 Mbps, capping the measured throughput
there and hiding the link's real headroom. Request the host's full
MAX_PROBE_KBPS (3 Gbps) instead, and raise the recommended-bitrate clamp from
500 Mbps to the host's 2 Gbps session ceiling so a fast measurement yields a
usable recommendation.
Also fix the stale caps left when the host clamps were raised (b8a33e2): the
resolved-bitrate range and the probe doc comments (abi.rs, client.rs,
regenerated header), plus the section 9 roadmap copy, now read 3 Gbps probe /
2 Gbps session.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Apple client grows full gamepad support and punktfunk/1 learns to negotiate
the virtual pad type:
- Protocol: Hello carries a GamepadPref byte (offset 21, the same trailing-byte
back-compat pattern as the compositor; echoed resolved in Welcome at 54).
Host precedence: explicit client choice > PUNKTFUNK_GAMEPAD env > Xbox 360,
DualSense (UHID) only where available. ABI: punktfunk_connect_ex2 +
punktfunk_connection_gamepad (connect_ex delegates; ABI_VERSION stays 2 — the
trailing byte IS the compat mechanism). punktfunk-client-rs gets --gamepad.
- Swift client: GamepadManager (app-lifetime discovery + selection — Settings
lists every controller with capabilities/battery/"In use"; exactly ONE pad
forwards as pad 0, auto = most recently connected, or pinned), GamepadCapture
(snapshot-diff button/axis events, DualSense touchpad + ~250 Hz motion on the
rich-input plane, held state released on switch/deactivate/stop),
GamepadFeedback (rumble → CoreHaptics per-handle engines; lightbar →
GCDeviceLight; player LEDs → playerIndex; adaptive-trigger blocks → the
table-driven DualSenseTriggerEffect parser → GCDualSenseAdaptiveTrigger,
exact for the 10-zone positional modes). The pad type auto-resolves from the
physical controller at connect time, user-overridable in Settings.
- Host DualSense fixes surfaced by adversarial review against hid-playstation /
SDL / Nielk1 ground truth: input-report sensor/touch offsets were off by one
(the kernel read garbage motion + phantom touches), the L2/R2 trigger blocks
were swapped (the report is right-trigger-first), feedback now gates on the
report's valid-flags (a plain rumble write no longer blanks lightbar/
triggers), and the touchpad rescale clamps to the advertised ABS_MT extents.
- Tests: Hello/Welcome trailing-byte back-compat, pick_gamepad precedence,
byte-exact input-report layout, valid-flag gating, per-mode trigger-parser
table (incl. packed 3-bit zones), wire conversions, and a scripted loopback
feedback burst (PUNKTFUNK_TEST_FEEDBACK=1) asserted through the xcframework
on the rumble + HID-output planes.
Validated: cargo test/clippy/fmt green on macOS + Linux (61 host tests), swift
build/test green, test-loopback.sh green, tvOS/iOS targets compile. DualSense
motion sign/scale is derived from the calibration blob, not yet live-verified
(constants isolated in GamepadWire).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts the new ABI surface (still v2, additive):
- PunktfunkConnection.sendMic(_:seq:ptsNs:) — Opus mic frames (48 kHz) to the host's
virtual PipeWire source; enqueue-only, empty data = DTX silence. Wiring the actual
Mac microphone (AVAudioEngine input → Opus) into the app is the follow-up, alongside
audio playback (README note 5).
- PunktfunkInputEvent.touchDown/touchMove/touchUp — absolute pixels + surface size in
flags, host injects via libei ei_touchscreen. Built for the iOS variant; nothing on
macOS emits them yet.
- Loopback round trip now also sends touch events and mic frames (incl. a DTX frame)
through the wrapper.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pairing/renegotiation batch bumped the punktfunk/1 ABI to v2 and the host now
hard-rejects v1 Hellos (m3.rs), so streaming from the Mac was dead until the bundled
PunktfunkCore.xcframework is rebuilt — it is gitignored, so that is a per-checkout step:
bash scripts/build-xcframework.sh. The Swift wrapper itself was already adapted upstream;
this lands the app on top of it.
- ClientIdentityStore: persistent client identity in the login Keychain, presented on
every connect so paired hosts recognize this Mac. Keychain access failure throws
instead of regenerating (a fresh identity would silently un-pair this Mac from every
--require-pairing host); a lost first-run race resolves toward the stored identity;
pairing uses the strict loadForPairing() so a memory-only identity can't strand a
ceremony.
- PairSheet: the SPAKE2 PIN ceremony, reachable from a host card's context menu and from
the trust prompt's "Pair with PIN instead…" (which drops the live session first — the
host's accept loop is sequential). Success pins the verified fingerprint and connects;
an in-flight ceremony self-discards when the sheet is dismissed, so a late success
can't pin + auto-connect behind the user's back. Wrong PIN and Keychain failures get
distinct, actionable error text.
- Tests: identity unit tests; the full pairing ceremony + --require-pairing gate on
loopback (test-loopback.sh arms a second host, parses its PIN from the log, and gives
both hosts throwaway config homes — no more writes to the real ~/.config/punktfunk);
remote pairing + pinned stream over the LAN (PUNKTFUNK_REMOTE_PIN, _PORT).
Validated live against the box: SPAKE2 ceremony with the host's arming PIN → verified
fingerprint → pinned + identified 720p60 session (host persisted the client identity);
first light 60/60 AUs decoded to pixels; vkcube on glass through the app.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full project rename, decided 2026-06-10:
- Crates/binaries: punktfunk-core / punktfunk-host / punktfunk-client-rs.
- C ABI: punktfunk_* symbols, Punktfunk* types, include/punktfunk_core.h,
PUNKTFUNK_FEATURE_QUIC guard (header regenerated; cbindgen renames updated, incl.
PUNKTFUNK_BTN_*/PUNKTFUNK_AXIS_* wire constants).
- Protocol: punktfunk/1 — control-plane magic LMN1 → PKF1, nonce salt lmn1 → pkf1.
WIRE BREAK: clients must be rebuilt from this revision.
- Env knobs: PUNKTFUNK_VIDEO_SOURCE / PUNKTFUNK_COMPOSITOR / PUNKTFUNK_ZEROCOPY / ….
- Host config dir: ~/.config/punktfunk (the box's dir was migrated in place — the
persistent identity is unchanged, pinned fingerprints stay valid).
- Swift package: PunktfunkKit + PunktfunkCore.xcframework + PunktfunkConnection
(Sources/PunktfunkClient app + tests renamed with it); build-xcframework.sh updated.
- scripts/: 60-punktfunk.rules, punktfunk-host.service; OpenAPI doc regenerated.
Also: scripts/headless/run-headless-kde.sh — full headless Plasma bringup. Root cause of
"desktop but no apps/settings" over the stream: plasmashell launched without
XDG_MENU_PREFIX=plasma-, so the launcher resolved a nonexistent applications.menu and
rendered an empty menu. The script sets the complete KDE session env (menu prefix,
KDE_FULL_SESSION, session version) and rebuilds ksycoca before starting plasmashell.
Gate: 97/97 tests, clippy -D warnings (both feature sets), fmt, C-ABI harness PASS,
zero lumen references left outside .git.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>