The ladder's last rung no longer runs FFmpeg. H.264 decodes through openh264, AV1 through rav1d, and HEVC is refused outright: no permissively licensed software HEVC decoder exists, so an HEVC session that exhausts its hardware rungs now tears down and re-dials advertising HEVC-less caps, and the host picks H.264. The plan calls that a first-class path; it is one. swscale is deleted, and with it the BT.601 default that its correction code existed to undo. Colour on the H.264 lane now comes from the same pf-bitstream planner every hardware rung submits from — openh264 reports no VUI at all — and AV1's comes per-picture from the sequence header. One colour source, one CSC: the old default is unrepresentable rather than merely fixed. Frames reach the presenter as three tightly-packed planes through the planar CSC pass, which had to be un-gated from the pyrowave feature and its device probe, since the last rung must exist on devices that failed that probe. rav1d rather than the dav1d crate, deliberately and against the plan's literal wording: dav1d-sys is system-deps-only, so it would add a system library and a .pc file to every client package — in the milestone family whose excision checklist exists to delete exactly that. rav1d is the same decoder, same licence, statically linked. The cost is honest: no-asm builds on both decoders, and software throughput is still unmeasured. The colour test is the milestone's exit criterion, so it is built to fail. Three fixtures, and a mutation check: hardcoding the swscale default turns the red bar to [255,24,0], and swapping Cb/Cr turns red to blue — a silent error no metadata assertion could catch. Review then disproved the range half of it numerically: with eight saturated bars, decoding the full-range fixture with the wrong range gives max error ZERO, because a mismatch only pushes values outside [0,1] where the shader clamps. A mid-tone was added; the wrong range now costs 11, well past the tolerance. The exit criterion I set was satisfiable by a test that proved nothing. Two blocking defects, both emergent rather than local. Software AV1 on a 10-bit stream never reached its typed refusal: rav1d is built 8-bit-only and returns ENOPROTOOPT, which the send loop turned into a generic error, so the pump's typed downcast missed and every AU failed identically — a permanent freeze on precisely the shipping case, since AV1 is advertised only where hardware AV1 exists and hardware AV1 plus HDR is Main 10. The shape is now read from the sequence header before any byte reaches the decoder, exactly as the H.264 leg reads the active SPS. And the new Reconnecting phase was the first state that is not streaming, not connecting, and still holding a live stream — which opened all three guards that had made a second launch impossible. Pressing A assigned over `stream` where every other site shuts down first, and StreamState has no Drop, so the old pump was detached: a second live session still submitting to a Vulkan device that gets destroyed underneath it. Nothing about the reconnect was wrong in isolation; the defect lived between a new state and three guards nobody re-examined. Start is now defensive and the retry raises the connecting modal, so the UI matches the state and B can cancel. Also closed: retry_caps was computed, tested and never applied, so a shape refusal could end a session reporting no codec available while a working retry existed; the retry inherited force_software sticky-true, landing an HEVC→H.264 fallback on software H.264 with working hardware H.264; it re-dialled with a stale mode; the CPU present arm had no survivable-failure handling where the pyrowave arm — same pass — has it; HEVC is no longer advertised when the decoder is pinned to software; and the software rung now feeds the recovery-point SEI it already had in hand to the re-anchor gate. ⚠ Two host-side gaps found while tracing, neither in scope here: Hello::launch is NOT idempotent (gog:/custom: targets spawn a second copy on a retry; the field is kept verbatim because dropping it orphans the gamescope display whose reuse key includes the command), and a reconnected session can never adopt a game predating its own launch stamp, so it has no game-exit detection. ⚠ OWED: the on-glass software run. ~200 lines of new Vulkan on a path that only runs because the GPU already failed, and no driver has seen it. The review's minimum check is sync validation enabled, a non-multiple-of-16 mode, a mid-session resize and demotion, and both colour matrices. Gates: container clippy -D warnings over four crates, 236 tests, workspace check. pf-vkdecode and pf-bitstream are byte-for-byte untouched, so the hardware rungs' 250/250 stands.
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 is vendor-ordered: hardware Vulkan Video →
VAAPI → software on Linux, hardware Vulkan Video → D3D11VA → software on Windows, with
VAAPI/D3D11VA first on Intel; on H.264 and HEVC the native pf-vkdecode Vulkan decoder
is tried immediately before FFmpeg-Vulkan): the Vulkan decoders run 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 the
native, FFmpeg-Vulkan, VAAPI/D3D11VA and software paths alike, 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=native-vulkan|native-vaapi|native-d3d11va|vulkan|vaapi|d3d11va|software
(the three native-* values pin this program's own decoders; native-vaapi also takes
PUNKTFUNK_VAAPI_DEVICE=/dev/dri/renderDNNN to choose the GPU), 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).
PUNKTFUNK_AU_FAULT=drop|truncate|flip[:period] deliberately corrupts decoder input on the
native Vulkan lane (default period 60 — one AU a second at 60 fps; inert everywhere else, and
inert entirely if the value doesn't parse). drop swallows the AU, so the next one references a
picture that was never decoded — the bitstream planner catches it immediately. truncate delivers
a picture whose slice data stops mid-frame and flip alters one byte deep in the payload: both
parse perfectly, so only the driver's per-frame decode-status query can see them, and neither is
visible at all on a driver without queryResultStatusSupport or on any FFmpeg lane. Watch the
result on the Detailed stats line's integrity: term (damaged = concealment the planner caught,
refused = AUs the decoder rejected outright, driver-failed = the hardware's own verdict, run
= consecutive frames with no picture, worst run = the longest such stretch of the session — the
once-a-second run sample misses the bad moment almost every time — and no driver status = this
device cannot answer the driver question at all). A session that lands on any other lane says so
in the log rather than faulting silently.
Note that PUNKTFUNK_AU_DUMP records the AU as it arrived from the HOST, while the fault injector
runs later, at the native decoder's own entry. On a faulted run the dump is therefore the clean
bitstream — reconstruct the damaged bytes from the spec if you need them (the injector is pure and
deterministic).