Main had moved 34 commits past the merge-base and 13 files had diverged.
Resolving now rather than later, since the force-feedback sweep work is landing
in the same files.
Five conflicts needed hand resolution. Four were "each side added something
different" and keep both: the Forwarding and PadAudioPrefs control variants with
their handlers and setters (pf-client-core/gamepad.rs), both of the session's
pre-attach declarations (forwarding first, so slots still declare their
pad-audio caps at open time), main's WiredPlan/fingerprint alongside the
branch's pad_render_ids (audio_control.rs), and main's judge_default signature
(wasapi_cap.rs).
wiring_plan.rs was not mechanical. Main's 652abeb3 added a flagged last-resort
loopback tier; the branch had added a fifth `plan` parameter excluding pad
endpoints from every role. Taking either side alone loses the other, and
combining them carelessly is worse than both: the new last-resort tier would
happily select the pad's own speaker endpoint, which is stamped "DualSense
Wireless Controller" with no virtual marker precisely so games read it as the
pad's speaker — routing the entire desktop mix into the controller's voice
coils. The branch's exclusion shadows `renders` before any tier runs, so the
last resort inherits it; `a_pad_is_never_the_last_resort` pins that, including
that a pad-only candidate set stays honestly unsatisfiable rather than falling
back onto the coils.
Verified: clippy -p punktfunk-host -p pf-client-core --all-targets --locked
-D warnings = 0; pf-client-core 93/93; punktfunk-host 387 passed with only the
known-environmental gamestream sender_delivers_batches UDP-loopback flake;
wiring_plan 21/21; fmt clean.
NOT verified: audio_control.rs and wasapi_cap.rs are cfg(windows), so neither
the Linux container nor xcheck.sh compiles them. Those two resolutions have had
review only and need the Windows runner before this merges.
punktfunk-session
The Vulkan session binary: one stream per invocation in an SDL3 window — no UI toolkit,
no widgets, terminal stats. The power-user / gamescope stream client, and the stage-2
presenter of the Linux client re-architecture (punktfunk-planning:
linux-client-rearchitecture.md).
This binary is deliberately dumb: a renderer the front-ends call INTO — the GTK shell
(punktfunk-client), the WinUI shell, and the punktfunk CLI all spawn it through the
same brain (pf_client_core::orchestrate), which resolves policy (profiles, settings,
wake) and hands the result down, normally as a --resolved-spec file. It reads the
shared stores only as the compat fallback for a bare hand-launched invocation.
punktfunk-session --connect host[:port] [--fp HEX] [--launch id] [--fullscreen] [--stats]
punktfunk-session --browse host[:port] [--mgmt PORT] [--fullscreen]
--browse opens the console game library (the Skia coverflow over the animated aurora)
instead of connecting: A launches the focused title as a stream in the same window,
session end returns to the library, B quits (Gaming Mode returns). Paired hosts only —
pairing is the desktop client / Decky plugin's job. PUNKTFUNK_FAKE_LIBRARY=<file.json>
feeds canned entries with no host (portrait paths starting with / load from disk).
Reads the same identity / known-hosts / settings stores as the desktop client
(punktfunk-client), so enrolling on either side makes the other work; this binary never
connects to a host it has no pinned fingerprint for (--fp HEX overrides the store).
Pairing is punktfunk pair <host> — the CLI, which ships alongside this binary in every
package and needs no window and no toolkit either. punktfunk-session --pair still works
for one release (someone's provisioning script calls it today) but prints a deprecation
notice: pairing is a trust ceremony and belongs to the brain, not a renderer.
Stdout is the machine interface: {"ready":true} after the first presented frame,
stats: … once per second while the overlay tier isn't Off (always the full detailed
text, whatever the OSD shows; --stats forces the overlay on), one
{"error"|"ended": …} JSON line on the way out. Logs go to stderr. Exit codes: 0
clean end, 2 connect failed, 3 trust rejected / pairing required, 4 presenter
init failed.
In-stream keys match the desktop client: click captures input (Ctrl+Alt+Shift+Q releases), Ctrl+Alt+Shift+D disconnects, F11 toggles fullscreen; the controller escape chord (L1+R1+Start+Select, hold to disconnect) works the same.
The default build carries the Skia console UI (ui feature): the stats OSD and capture
hint render in-window. Ctrl+Alt+Shift+S cycles the OSD tier live — Off → Compact (one
line: fps · latency · Mb/s) → Normal (mode + end-to-end percentiles) → Detailed (decoder
path + per-stage latency equation); any tier but Off also emits the stdout mirror.
--no-default-features is the ~5 MB power-user build — same streaming, stats on stdout
only, no Skia anywhere in the dependency tree.
Decode follows the Settings preference (auto: Vulkan Video → VAAPI → software on Linux,
Vulkan Video → D3D11VA → software on Windows): FFmpeg's Vulkan Video decoder runs on the
presenter's own device where the stack supports it (every vendor, zero copy); VAAPI
dmabufs import per-plane elsewhere (D3D11VA textures on Windows); software is the
universal fallback. 10-bit Main10 and HDR10 are advertised
(VIDEO_CAP_10BIT|HDR): P010 decodes through all three paths, and PQ streams present
on an HDR10/ST.2084 swapchain when the desktop offers one (KDE HDR, gamescope) or
tone-map in-shader to SDR when it doesn't (PUNKTFUNK_TONEMAP_PEAK tunes the rolloff,
default ≈1000 nits). The host still gates the upgrade behind its PUNKTFUNK_10BIT
policy.
Debug/bisect knobs: PUNKTFUNK_DECODER=vulkan|vaapi|d3d11va|software, PUNKTFUNK_PRESENT_MODE= mailbox|fifo|immediate|fifo_relaxed (default MAILBOX, FIFO where the surface offers no
MAILBOX — AMD on Windows), PUNKTFUNK_VK_DEVICE=<index> (multi-GPU), and
PUNKTFUNK_HW_FAULT=import (fault every VAAPI dmabuf import — proves the three-strike
demotion to software on healthy hardware).