The 0xD1 pad-audio plane streams a DualSense's voice-coil haptics (back channel pair, 5 ms Opus frames) and speaker (front pair, 10 ms) per pad from a Windows host to the SDL clients, which render them into a USB DualSense's own 4-channel audio device. Wire (punktfunk-core, ABI v15): PAD_AUDIO_MAGIC 0xD1 [pad][kind][seq][pts] [opus]; CLIENT_CAP_PAD_AUDIO 0x04 / HOST_CAP_PAD_AUDIO 0x20; per-pad render capability rides GamepadArrival flags bits 8/9, sent only toward a host that advertised its cap so old hosts see byte-identical arrivals; silence is a frozen seq (mic-mute discipline), loss is a seq gap concealed via AudioGapTracker. HidOutput::AudioCtl (0xCD kind 0x06) forwards the 0x02 report's audio-control bytes 5..=10 change-only, value-deduped, with a once-per-pad "title asserted haptics-select" diagnosis log. Windows host endpoint provider (audio/windows/pad_endpoint.rs): per-pad render endpoints are additional devnode instances of Valve's Steam Streaming Speakers driver (SetupDiRegisterDeviceInfo, NOT the class installer - it needs an interactive window station), stamped with DualSense identity: desc "Wireless Controller", device name "DualSense Wireless Controller", ContainerId = the virtual pad's PFDS GUID, 4ch/48k format triplet. IPropertyStore route first, ACL-repaired registry fallback (the MMDevices keys deny writes even to SYSTEM; the owner's implicit WRITE_DAC + an ACE for S-1-5-18 resolved by SID is the way in). Provisioned at host startup (PUNKTFUNK_PAD_AUDIO, PUNKTFUNK_PAD_AUDIO_SLOTS, default 1), idempotent via a persisted PunktfunkPadIndex marker; pad endpoints are structurally ineligible for the mic/loopback wiring plan and guarded against default- device theft; capture is WASAPI loopback on the stamped endpoint. Devtest: punktfunk-host pad-endpoint ensure|remove|status. Host service (native/pad_audio.rs): per-(session,pad) thread, loopback 4ch -> pair splitter -> per-kind stereo Opus (48k LowDelay CBR 64k) -> per-kind silence gate (opens at peak>=1e-3, 250 ms hangover, gated = no send + frozen seq) -> datagrams. Spawned from the native input pump when a DualSense/Edge arrival carries audio bits and both caps negotiated; idempotent re-arrivals; reaped on remove and teardown. Client tier A (pf-client-core/pad_audio.rs): settings pad_haptics (default on) and pad_speaker (default "pad"); tier A = wired USB DS5/Edge via SDL connection state with an audio-sibling fallback; correlation maps the SDL HID path to the pad's own render endpoint (Windows: ContainerId match + 4ch gate via registry; Linux: Sony sink signature); renderer decodes both kinds into a quad interleave and plays it on the pad's endpoint (WASAPI autoconvert / PipeWire target.object, 240-2400 frame ring floor, dont-reconnect so an unplug never re-routes haptics to the desktop speakers). SDL's DualSense driver sets "disable audio haptics" whenever it drives rumble emulation, so tier-A pads suppress wire rumble and send one cleared-enable-bits effects packet to keep the actuators live; AudioCtl bytes fold back into the effects packet at report-minus-one offsets. Verification: punktfunk-core 265 tests (macOS) + clippy -D warnings (mac + Linux docker); pf-inject 85 tests (Linux docker); punktfunk-host cargo check + clippy + 19 pad tests + 46 audio-module tests (Windows box); pf-client-core 30 tests + clippy (Linux docker CI image) + cargo check (Windows box); punktfunk-client-session clippy (Linux) + check (Windows); cargo fmt --all --check clean on the final tree. NOT yet verified: any on-glass run (host deploy + real title + physical pad), the stamp-route split at runtime, exclusive-mode Initialize isolation, Linux-host emission (the per-pad PipeWire sink is not in this change - Windows hosts only). Scope excluded deliberately: tier B (Apple CoreHaptics) and tier C (haptics->rumble derivation), pad_speaker="mix", Android leg, settings UI surfaces (keys are serde-defaulted), GameStream-plane arrivals (audio_caps always 0 there). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
punktfunk-core
The shared protocol core — the one place where punktfunk's transport, forward error correction, and crypto live. It's linked into the host and every native client, so there's exactly one implementation of the wire format everywhere.
Written in Rust with no async on the per-frame path (native threads only). It exposes both a normal Rust API and a stable, versioned C ABI, so the Swift and Kotlin clients — and any C embedder — link the same code as the Rust ones.
What's in here
- Transport & session (
session.rs,transport/,packet.rs) — thepunktfunk/1data plane over raw UDP: packetization, reassembly (with attacker-bounded limits), pacing, and socket tuning. - FEC (
fec/) — the wall-breaker. Two codes:- GF(2⁸) classic Reed–Solomon with the Cauchy generator matrix — byte-identical to the
nanorslibrary Moonlight uses, so our parity is decodable by a stock Moonlight client. - GF(2¹⁶) Leopard-RS (SIMD, O(n log n)) — up to 65535 shards/block, which removes the ~1 Gbps
FEC ceiling.
punktfunk/1negotiates this one.
- GF(2⁸) classic Reed–Solomon with the Cauchy generator matrix — byte-identical to the
- Crypto (
crypto.rs) — AES-128-GCM session encryption with per-direction nonce salts and sequence-as-AAD; SPAKE2 PIN pairing lives behind thequicfeature. - QUIC control plane (
quic.rs,client.rs, featurequic) — the Hello/Welcome/Start handshake, cert pinning/TOFU, reverse audio, and the embeddableNativeClientconnector. This is the only placetokio/quinnare allowed; the feature is off by default so the core stays runtime-free. - C ABI (
abi.rs) — the versioned surface (punktfunk_abi_version(),PunktfunkConfigcarrying its ownstruct_size) that generatesinclude/punktfunk_core.hvia cbindgen at build time.
Build outputs
The crate builds three ways at once (crate-type = ["lib", "cdylib", "staticlib"]):
| Output | Used by |
|---|---|
lib (rlib) |
the host, probe, and tools link it as a normal Rust crate |
cdylib (.so/.dylib) |
the Swift / Kotlin clients via the C ABI |
staticlib (.a) |
the C test harness and static embedding |
Test
cargo test -p punktfunk-core # unit + proptest + loopback
cargo run -p loss-harness # FEC loss-resilience sweep (no network needed)
bash crates/punktfunk-core/tests/c/run.sh # standalone C-ABI link + round-trip proof
Design invariants (do not regress)
- One core, linked everywhere — protocol/FEC/crypto live only here, behind the stable C ABI.
- No async on the hot path — the per-frame pipeline is native threads only;
quic(tokio/quinn) is control-plane only, feature-gated, off by default. - Security hardening stays intact — the reassembler bounds attacker-controlled fields before
allocating; AES-GCM keeps per-direction nonce salts + seq-as-AAD; the ABI checks
struct_size. Regression tests exist — keep them green.
Related
punktfunk-host— the streaming host built on this core- Clients — the apps that link this core over the C ABI (or directly, in Rust)
- punktfunk-planning:
implementation-plan.md(internal planning repo) — why GF(2¹⁶) FEC, the latency budget, and the architecture thesis