Files
punktfunk/docs/releases/v0.23.0.md
T
enricobuehler 213b353dad
ci / rust (push) Canceled after 1m18s
ci / rust-arm64 (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / docs-site (push) Canceled after 0s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
docs(release): the 0.23.0 notes link to the docs site that exists
Both links pointed at https://punktfunk.unom.io/docs/... — the marketing host,
which 404s (verified live; the docs site serves from docs.punktfunk.unom.io, as
README.md has used throughout). The Updating link was the one a reader following
the one-click update section would actually click.

The echo link additionally pointed at the docs ROOT rather than the page it
names; both now resolve to their real slugs, confirmed against
docs-site/content/docs/{echo,updating}.md and by fetching them (200/200, vs 404
for the old host).

Release body re-synced by body-only PATCH.
2026-08-01 01:25:01 +02:00

34 KiB
Raw Blame History

Wire-compatible with 0.22.x — everything you have already paired keeps working, and you can update one side at a time. Every new ability here is negotiated per device: a phone, TV or host that does not know about one simply streams the way it does today.

This is a big release, and it pulls in four directions. Latency: Android phones and tablets get a new frame scheduler that cut glass-to-glass delay by roughly a third on the devices we measured, a host can line its capture up with your device's display instead of drifting against it, and a frame can start decoding before the rest of it has arrived. Your microphone: it can be muted mid-stream on every client now, echo cancellation is on by default, and the uplink itself was rebuilt to be about half as slow. Settings that do what they say: the Linux app has been quietly streaming at defaults since 0.22.0 — bitrate, resolution, codec, HDR, profiles — and that is fixed, along with HDR and full chroma no longer being mutually exclusive on Windows. The web console: it now follows the host live instead of polling it, shows you what just happened, hands a phone a link to connect with, and installs to a home screen. Plus HDR by default on Linux hosts that run their games through gamescope, a DualSense over USB on Android, and a real Update now button.

New

  • Your microphone has an off switch you can reach mid-stream. Muting used to mean leaving the stream and switching the microphone off in Settings — an app-wide setting doing the job of a moment-to-moment one. Every client can mute now: Ctrl+Alt+Shift+V on Windows and Linux, ⌃⌥⇧A or the HUD button on Mac, iPhone and iPad, and a tap on the pill (or Select + Y on a controller) on Android. A "Microphone muted" badge sits over the stream independently of the stats overlay, because "am I muted?" is not a statistic and the overlay is exactly what a player turns off — and on touch, tapping the badge is the way back. Muting is local and instant: the host is never asked and never told. It is per session and deliberately not remembered, so a mute you made three days ago never follows you into a call.

  • Echo cancellation, and a switch for it. If you play through speakers, the host used to hear its own game audio coming back through your microphone. Every client now runs the microphone through the operating system's echo canceller by default — on Mac and iPhone that meant putting playback and capture on one engine so the voice processor can see both, and on Android it meant opening the microphone in the mode that keeps the phone's hardware canceller switched on. There is an Echo cancellation toggle beside the microphone setting on every platform if you want it off. A new Why do I hear myself page walks through the four different ways an echo loop can form and which switch stops each one.

  • Android streams reach the glass sooner. The app used to hand each decoded frame to the screen the instant it appeared, so every frame inherited whatever jitter the network and decoder had just added, and bursts piled up behind the display. Frames are now scheduled onto the panel's own refresh. Measured end to end on a 120 Hz phone under game load: 43.7 ms down to 26.4 ms; on a second phone against a Linux host, 2224 ms down to 1418 ms. Two ingredients were plain bugs — Android quietly down-rates a game's frame callbacks to 60 Hz and then reports 60 Hz as the panel's rate, so the app was pacing a 120 fps session at half speed, and the panel was not being pinned to the stream's refresh at all. Under Decoding you can pick Lowest latency (the default) or Smoothness, matching the iPhone and Apple TV apps.

  • The host can line its capture up with your screen. A host's capture and your device's display are two independent clocks, so the wait between "captured" and "shown" slowly sweeps across a whole refresh — a large part of residual judder, and something no client can fix alone. Your device can now report the phase of its own display schedule and the host walks its capture timing until the two agree. It engages only when the reports are steady enough to trust, which in practice means a clean or wired link; over noisy Wi-Fi it stays out of the way rather than chasing noise. Android, the Apple apps and the Windows and Linux desktop clients all report it.

  • A frame starts decoding before it has finished arriving. Video is cut into slices during encoding, and those slices now travel as they are produced instead of waiting for the whole picture, so a decoder can work on the front of a frame while its tail is still on the wire. It is on by default between hosts and clients that both understand it; devices that can only handle whole frames keep receiving exactly what they did before.

  • HDR and full chroma stop being mutually exclusive on Windows. An HDR desktop used to cost you 4:4:4: the only 10-bit capture format available threw away colour detail, after the host had already told the client it was sending full chroma. Both now work together on HDR sessions. Separately, full chroma can now stay on your graphics card's decoder instead of falling back to software, and the stats overlay tells you the truth about what you got — it prints the encoder's actual target next to the measured rate (19.4 Mb/s · target 20 Mb/s (auto)) and shows 4:4:4→4:2:0 when the host declined. That missing number is the reason the Linux settings bug below survived four releases: measured throughput alone cannot tell "the encoder is capped at 20" from "my 200 Mb/s allowance met a cheap scene".

  • Linux hosts stream HDR from a gamescope session by default. If your host runs its games through gamescope — a Bazzite or SteamOS machine in Game Mode, a handheld, an HTPC — a 10-bit HDR session used to need a build of gamescope most people did not have and a setting nobody knew to turn on. So capable machines quietly streamed SDR, and an RX 5700 reported "10-bit HEVC unsupported" when the GPU was never the problem. HDR is attempted by default now, and the Linux install routes carry the gamescope build that can actually capture it: the Bazzite and Arch system extensions, the NixOS module, and a new script that builds it on a Steam Deck's own on-device install. A host still running stock gamescope keeps exactly the 8-bit behaviour it has today.

  • The web console follows the host instead of interrogating it. It used to poll ten endpoints on timers, so a change could be five seconds stale and two pages could disagree while you looked at them — and the Library page did not poll at all, so a game you installed in Steam never appeared until you reloaded. The console now subscribes to the host's live event stream and refreshes exactly what each event affects. Three things come with it: a Recent activity feed on the dashboard, so a client that connected and left while you were on another page leaves a trace; a Connect a device card with the host's address and a copyable link that opens an installed client straight onto this host; and an Automation page for the start/stop hooks the host has always run and the console never showed. The console also installs to a phone's home screen, and warns you when another Moonlight-compatible server such as Sunshine or Apollo is running on the same machine — the most common reason a host looks fine but no client can reach it.

  • A DualSense or DualShock 4 plugged into an Android phone behaves like a real one. Over USB the app now talks to the pad directly: rumble, adaptive trigger effects, the lightbar and player LEDs, the gyro and the touchpad, on the DualSense, DualSense Edge and DualShock 4. Android offers no way to do any of this over Bluetooth, so USB is the route — plug the pad in and the app asks for permission the moment it appears rather than hiding the switch in Settings.

  • Updating the host is one click. The Updates card grows an Update now button. On Windows it asks for your console password again (a remembered login should not be able to restart your PC), downloads the installer, checks it against the signed release manifest and its code signature, and runs it silently — the service restarts and the page reconnects by itself. If a freshly installed version ever crash-loops, the host puts the previous installer back on its own and tells you. On Linux the button is opt-in: sudo usermod -aG punktfunk-update $USER is the entire grant, and all it can ever do is run your own package manager's normal update for the Punktfunk packages from repositories you already trust. Fedora Silverblue-style installs stage the update and ask you to reboot; Arch additionally needs you to accept a full pacman -Syu, because a partial upgrade is how Arch boxes break. A Steam Deck on-device install gets the button with no opt-in at all. PUNKTFUNK_UPDATE_APPLY=0 removes it entirely. Details on the Updating the Host page.

  • The Linux app can tell you it is out of date, and update itself. punktfunk-client --check-update verifies the same signed release information the host does and reports what can be done on your kind of install; --apply-update does it. Where nothing on the box can install an update — a system extension, a Nix profile, a source build — it says so and gives you the command rather than a button that could only fail, and a check that failed reads as a failure rather than "up to date". The Steam Deck plugin now follows the client you actually launch, instead of offering updates only for the Flatpak and leaving every other kind silently stale.

  • A dead tray icon on Windows is one command away. punktfunk-host tray start, stop and status — previously the icon only ever appeared at sign-in, so anything that killed it left you without one until you signed out and back in.

  • "Capture system shortcuts" finally decides where Alt+Tab goes. The switch has been in settings for a while and nothing read it: Windows always grabbed the keyboard whether you asked or not, Linux never grabbed at all. Both honour it now, on Wayland and X11 as well. It is on by default, so on Linux the stream now keeps chords like Alt+Tab and Super by default — release input capture, or turn the switch off, to hand them back.

  • Host cards show which system a machine actually runs. Bazzite, CachyOS and Nobara used to collapse onto their parent distribution's logo and Windows machines wore the Windows 8 flag; all have their own mark now. On Android every non-square logo was being stretched sideways — fixed too.

  • An Apple Pencil near the screen keeps its full sampling rate. Drawing over a 60 fps stream on a 120 Hz iPad silently halved how often the Pencil was sampled, which is exactly the workload that feels it. The panel is lifted to its top rate whenever the Pencil is in range and drops back afterwards.

Improved

  • The microphone is about half as slow, and heals itself. The uplink used to send 20 ms stereo packets built out of buffers up to 42 ms long, and duplicated a mono voice into two channels for no reason. Every client now sends 10 ms mono packets with forward error correction, so a lost packet's audio is reconstructed from the next one instead of leaving a hole. The queues behind it were worse than the format: on some paths a single stall could park over a second of standing delay that never drained, and now a backlog is dropped rather than played out late. On the host side the microphone gets a proper jitter buffer that sizes itself to the client it actually has, between 10 and 60 ms, instead of the fixed worst-case buffer everyone used to pay for. The stats overlay's Detailed tier shows whether your microphone frames are arriving.

  • Windows hosts encode with less delay by default. Reading each slice out of the encoder while the frame is still being encoded — which Linux hosts have done for a while — now switches itself on wherever the graphics card reports support, instead of needing an environment variable.

  • Android picks AV1 when the silicon prefers it. Under Automatic, a device that hardware-decodes AV1 and cannot use slice-progressive delivery now asks for AV1, measured about 1.2 ms faster end to end than HEVC in an identical A/B. An explicit choice still wins, and the host only honours the preference within what both ends actually support.

  • The picture quality control notices when the decoder is drowning. If your device keeps asking for a fresh keyframe, that is the decoder saying it cannot keep up — but with nothing lost on the network the bitrate used to hold steady while the picture kept breaking up. Repeated requests now back it off.

  • The console shows numbers it was already being told. How long a session took to start, what a mid-stream resolution change cost, and packet loss and error-correction recovery while the capture runs rather than only in a saved recording. There is also a card that names each plugin which has added games to your library, counts them, and can remove them in one go — previously a plugin's entries were stuck there once it was gone.

  • A controller no longer disappears over a brief hiccup. A momentary gap in controller state used to tear the virtual pad down and build a new one, which Windows treats as a full unplug and replug. It waits a fraction of a second before believing it now.

  • Windows logs explain a stutter instead of guessing. When frames stop arriving the host now says whether the game went quiet (a menu, a loading screen, an ordinary hitch) or the display path did, rather than blaming the display path by default — and a long degraded stretch is summarised in one line at the end instead of falling silent exactly while you suffer.

  • The documentation caught up with five releases. Around 280 corrections and nine new pages, including what works on which platform, mouse/touch/pen input and the in-stream chords, client settings, profiles and links, the game library, clipboard, Wake-on-LAN, HDR and uninstalling. The five-minute quickstart could not actually work as written; it can now. Debian is no longer claimed as supported, because it was never tested and its system libraries are too old.

Fixed

  • The Linux app ignored every setting you chose. Since 0.22.0 the desktop app handed the stream a set of defaults instead of your settings, so bitrate, resolution, refresh rate, render scale, codec, decoder, HDR, full chroma, audio channels, microphone, touch and mouse mode, scroll direction, the stats overlay and the controller type all did nothing — the report that started this was "my bitrate seems to be stuck at 20 Mbps", which is what the host falls back to when the app sends nothing. Profiles never applied at all. The GPU and audio-device pickers kept working the whole time, which is what made it so confusing. Everything is read again, and connecting from a script or the Steam Deck plugin with --profile now actually selects that profile.

  • Full chroma (4:4:4) did nothing on Linux or Windows. It was announced in 0.22.0 and only the Apple apps ever asked the host for it. Both desktop apps ask now.

  • Settings saves stopped reverting each other. The settings file has several writers and no merge, so saving from one screen silently undid what another had stored — most visibly the window size the app itself had just remembered. Every whole-file save now re-reads the file first. Windows also gained the speaker and microphone pickers it never had, its decoder list no longer offers a macOS-only option while hiding the actual Windows one, and its controller picker learned Steam Deck.

  • A host that was reinstalled locked your client out forever. Regenerate a host's identity — a reinstall, a wiped configuration — and the desktop clients refused it permanently with "Host identity rejected", including right after a successful re-pair, showing two cards for one address with no way to tell which to forget. Pairing was appending a second record and the lookup kept returning the dead one. Trust decisions now retire the stale record and carry your settings across, so a reinstall no longer costs you what you had configured.

  • The bottom of the picture stopped smearing. On some 1080p streams the last few rows repeated the final row of pixels and the whole image looked slightly stretched. Video is decoded into a buffer taller than the picture itself — 1088 rows for a 1080-row frame — and the colour conversion was sampling that padding into view, squashing the picture by 0.7%.

  • HDR stopped leaking out of the stream and blowing out the interface. Connect to an HDR host, disconnect, and the gamepad interface came back overblown with wrong colours until you restarted. The interface is drawn into the same surface the stream used, and nothing ever switched that surface back out of HDR when the stream ended.

  • A Steam Deck streaming to a Windows PC had a stuck stick and a stuck d-pad. Every virtual controller was presented to Windows as a DualSense regardless of what it was, so a Deck's button data was read as a DualSense's — a stick jammed hard left and a d-pad permanently pressed up. Controllers enumerate as what they are now; DualShock 4 and DualSense Edge pads were affected the same way.

  • Chromecast with Google TV 4K stopped crashing. Since 0.17.0, streaming to one froze on the first frame and usually crashed and rebooted the device — with Moonlight too, not just Punktfunk. Linux hosts had started cutting every frame into four slices, which that class of TV decoder cannot handle. The host now asks what the client can take.

  • A muted microphone is a pause, not a hole. On Android the frame counter kept running while muted, so the first frame after unmuting looked to the host like lost audio and it played up to five frames of stale voice trying to cover the gap. Quick mute-unmute toggles were the visible case.

  • A Windows host could feed your own voice back into the stream. On a machine with VoiceMeeter, the host could pick two strips of the same virtual mixer as the microphone and the loopback — a digital feedback loop with no acoustic path to break. Virtual mixer outputs are excluded from loopback now.

  • Installing a plugin no longer fails on some machines. If any folder above the plugins directory happened to contain a package.json — entirely normal in a home directory — the install landed there instead, and the console reported the plugin as "not present after install" or failed outright. A newly installed plugin also shows up in the sidebar on its own now, in about seven seconds rather than whenever you next reloaded.

  • The console's Logs page came back after a host restart. It died permanently every time the host restarted — which the console's own update flow does — showing stale lines with no error and no way out short of a full reload. Following also stopped following once the log got busy enough to matter, and pausing did not actually pause.

  • One wrong password stopped locking out the whole console. The login throttle was documented as per-address and was not: every attempt from anywhere was charged to one shared bucket, so five wrong guesses from any machine on your network locked out everybody, including you, and including the update button. Logging out now genuinely revokes the session too — previously it only deleted the browser's copy of the cookie, and a captured one stayed valid for its full seven days, surviving even a restart.

  • The console's charts stopped lying about time. Every sample was drawn as an evenly spaced step, so a capture that idled for two minutes rendered that gap as a single notch — in the one view you open specifically to find where the time went. They use a real time axis now, and no longer join two separate sessions into one continuous line.

  • A fresh Linux install could not start the host at all. systemctl --user enable --now punktfunk-host died with "Failed to load environment files", because the service demanded a configuration file that no package creates and only the docs told you to copy. It is optional now — without it you get the defaults, which is what it always should have meant.

  • A Linux host with no configuration file served a test pattern. Moonlight and other GameStream clients connecting to a packaged install that had never been configured got synthetic test video instead of the screen.

  • A brand-new host stopped looking broken. The Updates card reported "feed returned HTTP 404" on the stable channel, which is simply what an empty channel answers before anyone has published to it. It says so plainly now instead of showing a failure banner.

  • Streaming from a Steam Deck-style session no longer starts a login storm. On some images the login manager tried to restart the gaming session about three times a second for the entire stream — over eight minutes one test box logged 1481 login sessions, the journal overflowed, and a 5070 Ti "could not hold 240 fps".

  • The web console on Windows stops going quiet. It is started and watched by the host service itself now, restarted whenever it exits with a widening delay, and its output finally lands in a log file. Three separate outages in one week were the same missing supervision. A separate defect let a half-finished console deployment report success while every page served an error under a 200 status.

  • Updating no longer kills the tray icon for good, and the script and plugin runner on Windows can start at all — it could not read its own program file, and the error that said so was thrown away.

  • A cold-start connect on Android no longer loses HDR. Opening a link from a cold start could reach the connect before the app was attached to a display, and the checks silently fell back to their worst answers: SDR, and 1080p60.

  • Drawing no longer loses parts of a stroke. A stroke held still for more than a fifth of a second while the app was busy could be dropped by the host's safety timer, and a fast burst of samples was truncated rather than split — losing exactly the geometry that matters most, at exactly the moment the app hitched. Fixed on iPad and Android.

  • Rumble survives a game that hammers it. A game driving a virtual DualSense's outputs at over 2000 messages a second overflowed a queue and silenced rumble for the whole storm, while filling the log with about 230 warnings a second.

  • A game link on Windows launches its game. A punktfunk:// link carrying a game opened a plain desktop session instead — the id was parsed, validated, planned, and dropped at the last step. A link to a host you have not paired with now runs the pairing ceremony instead of refusing outright.

  • Android tablets and foldables stop being forced into landscape, and the stream handles camera cutouts explicitly so the notch area behaves the same on every Android version.

  • A PC whose only screen is a switched-off TV stops warning on every disconnect, and the Apple and Android apps credit their icons again — the bundled third-party notices had drifted behind the project's own.

Under the hood (for developers)

  • Versions. Wire protocol 2, virtual-display driver protocol 6 and the Windows virtual-gamepad channel 3 are unchanged from 0.22.x. C ABI 13 → 14: the client surface grew punktfunk_connection_report_phase and the PUNKTFUNK_CLIENT_CAP_PHASE_LOCK mirror constant. Additive and capability-gated — an embedder that never calls the new function needs no change — and the wire grows only a control message an old host never reads plus a strict-prefix append on the host-timing tail, which is why WIRE_VERSION stays 2.
  • Slice-streamed access units. USER_FLAG_SLICE_STREAM marks frames whose FEC blocks are cut at slice boundaries rather than at a fixed byte count, so blocks are variable-size and can no longer be addressed by the uniform block_index * K_max formula: sentinel blocks reuse frame_bytes as a shard-aligned base offset, the final block's base derives from the totals, and the receiver pins positionally (sentinels must sit strictly below the final block's base; a mixed-flag or out-of-range header is dropped before it can place a byte). The host sets the flag only toward clients advertising STREAMED_AU + VIDEO_CAP_MULTI_SLICE; PUNKTFUNK_SLICE_STREAM=0 pins legacy for an A/B.
  • Partial delivery. connect's frame_parts opt-in yields each AU's newly-contiguous prefix as Frame::part pieces (offset tiling, first/last marked, the completing push carrying only the suffix). The reassembler walks a per-frame cursor over successfully-completed blocks, coalesces out-of-order completions, keeps probe filler whole and stops short of the final block so the zero-padded tail still trims. Per-AU accounting keeps its units — OWD/ABR feeds, the inter-arrival series and the jump-to-live detector count only completing deliveries, and FrameChannel::depth() counts AUs. Never flows without the opt-in, never on PyroWave. Consumer contract: a gap or orphan part means the AU is lost — abandon, flush, resync on the next first. Field note: on the NP3 all three c2.qti low-latency decoders refuse FEATURE_PartialFrame, and forcing it through debug.punktfunk.force_parts showed they accept the pieces but only assemble them — codec time unchanged, so the overlap is dead on SM8735 either way.
  • Multi-slice gating and sub-frame readback. VIDEO_CAP_MULTI_SLICE is 0x80, the video-caps byte's last free bit; the next cap needs a second byte and an ABI bump. GameStream reads x-nv-video[0].videoEncoderSlicesPerFrame (absent or out of range ⇒ 1). The Windows direct-NVENC session now seeds resolve_subframe from NV_ENC_CAPS_SUPPORT_SUBFRAME_READBACK instead of a hard false, so PUNKTFUNK_NVENC_SUBFRAME is the same tri-state escape it is on Linux. The submit-time IDR hint gained the Linux twin's opening term — NVENC emits the session-opening frame as an IDR regardless of pic flags, which was firing the divergence check at every stream start.
  • Phase-locked capture. PhaseReport (control 0x32) carries the client's next display latch already converted to host clock, the panel period, an uncertainty and a circular arrival-lead statistic with a coherence permille; the 0xCF host-timing tail grows a 29-byte applied_phase_ns ACK under the existing strict-prefix append discipline. The controller locks submits to an absolute grid (epoch + k × period + offset) and walks only the offset, so phase actuation is linear by construction and a saturating plant disengages rather than free-running or parking a hold. Coherence floor 300‰, antipode damping within 1 ms of ±period/2, decay-to-zero on incoherence or travel-budget exhaustion. PUNKTFUNK_PHASE_LOCK=0 disarms. The desktop clients have the best latch signal of the three — true on-glass stamps via VK_KHR_present_wait — and advertise the cap only when present timing is real. punktfunk_core::phase::circular_latch is shared by every reporter and by a closed-loop simulation harness that turns both on-glass failures (the median-driven orbit and the parked-hold e2e tax) into CI regressions.
  • Android presenter. An AChoreographer thread publishes the vsync grid and frame timelines; releases go through releaseOutputBufferAtTime against the timeline's expected present minus a latch margin that starts at 0 and widens by 500 µs per 1 Hz window only when the paced counter shows real misses, one-way per stream. The panel period is learned downward from observed timeline spacing because Display.getRefreshRate reports the frame-rate-category override rather than the panel. debug.punktfunk.latch_margin_us (0..=8000) and debug.punktfunk.presenter=arrival pin the margin and restore the legacy path without a rebuild. nativeVideoStats is now 33 doubles — the decode stage splits into feed (received→queued) and codec (queued→decoded) at 30/31, with a skipped counter at 32 that separates benign newest-wins pacing from parked-AU overflow; 025 remain frozen and the JNI KDoc is the authoritative index list.
  • Microphone path. Uplink is 10 ms mono Opus at 48 kbps with in-band FEC against 10% assumed loss, on every client; the wire is unchanged (the host decodes any Opus frame ≤ 120 ms and its stereo decoder upmixes mono). MIC_QUEUE drops 64 → 12 and sheds oldest-first past a ~60 ms backlog — the old constant claimed 320 ms of 5 ms frames but the frames were 20 ms, so it really allowed 1.28 s of standing delay that only a >600 ms silence gap ever flushed. MicFrame {seq, pts_ns, opus} now survives ingest, feeding a real host-side de-jitter (audio/mic_jitter.rs): two-frame reorder window, libopus concealment up to 5 frames, adaptive target depth from inter-arrival jitter clamped to 1060 ms, with PUNKTFUNK_MIC_LEGACY_BUFFER=1 as a one-release escape hatch and a 30 s "mic uplink health" line. Sequence deliberately freezes while muted on every client — a pause is not loss and must not be concealed or counted as a gap. Settings::echo_cancel (default on, #[serde(default)]) gates what PUNKTFUNK_NO_AEC gated; the env var still wins one-way and can only turn AEC off.
  • HDR × 4:4:4 on Windows. HdrRgb10Converter is one full-res pass from the FP16 scRGB desktop to packed R10G10B10A2 in BT.2020 PQ, reusing the P010 shader's scrgb_to_pq2020 verbatim so both HDR outputs share bit-identical colour math — it simply stops before the RGB→YUV matrix, the studio-range squeeze and the chroma decimation, and NVENC does the CSC to YUV 4:4:4 itself under FREXT. No swizzle is involved despite appearances: NVENC names packed formats MSB-down, so its ABGR10 is bit-identical to DXGI R10G10B10A2_UNORM. capturer_supports_444 no longer has a depth it cannot serve, so the chroma resolved before the Welcome is the chroma the wire carries. AV1 is deliberately untouched — Range Extensions are HEVC-only. On the client side vkframe_plane_views accepts the 2-plane 4:4:4 pool formats (8-bit and the 10-bit 3PACK16 sibling); 3-plane 4:4:4 stays rejected and demotes cleanly. No capability probe gates VIDEO_CAP_444 on purpose — software decode is the guaranteed display floor, and a probe-gated bit would go inert on exactly the boxes relying on the fallback.
  • Vulkan decode crop. The CSC pass sampled decoded planes with the fullscreen triangle's 0..1 UVs while its render target is the cropped size. FFmpeg sizes the decode pool from avctx->coded_* and H.264 codes 16 * mb_height, so a 1080-row picture decodes into a 1088-row pool and destination row 1079 sampled source row ~1087.5. VkVideoFrame carries the pool extent now and record_csc takes a uv_scale. Only the Vulkan-Video path passes a scale below 1.0; every other call site passes [1.0, 1.0] and is inert.
  • Console architecture. The event subscription needed four separate defects fixed before it could work, each found by measuring: Nitro's localFetch accumulates the whole response so nothing streams through the deployed Bun server (SSE gets its own route handing back a web Response wrapping the upstream stream); hydration mounts the shell and discards it ~15 ms later, so the subscription is a refcounted module singleton with a grace period; getRouter() runs more than once in the browser and each call built its own QueryClient; and invalidateQueries only refetches queries with a live observer. Events never carry data into the cache — they only mark staleness — so an unknown future kind costs nothing and a missed event degrades to the slow safety-net polling underneath.
  • Console security. The login throttle's per-IP bucket was always undefined because Nitro's synthetic request has no remoteAddress; the Bun entry stamps the real peer into a header (deleting any client-supplied copy first). Session cookies carry an epoch persisted next to the host config, so logout revokes across a restart — it was a module-level counter that reset with the process. Installing an unreviewed package or adding a catalog source now re-ask for the console password, the plugin-UI response filter became an allowlist (Clear-Site-Data from a plugin error page could sign the operator out on our own origin), the ui-credential denylist matches the normalised path, and a half-configured TLS setup refuses to start rather than serving with the login password in the clear. @unom/ui/button reached two game-UI sprite sheets through sound/defaults.js — a 4.8 MB .wav and a 2.2 MB .mp3 that rode into the Windows installer and the .deb and could never be played; a build-time rewrite takes the asset payload from 8.2 MB to 1.5 MB.
  • Known-hosts store. upsert matches on fingerprint, which is what lets a host that moved address keep its record; a host that changed identity matched nothing and appended a second record, and find_by_addr returned whichever came first. Trust decisions funnel through upsert_trusted, which retires — deletes, not demotes — any other record for that address, carrying the box-describing fields across (MAC, OS chain, bound profile, pinned cards, last used) while paired and clipboard_sync are re-decided, being decisions about one specific certificate. Only trust decisions may retire; the wake path's address re-key and every learn-from-advert path stay on plain upsert, since letting an unauthenticated mDNS advert delete a saved host by claiming its address trades this bug for a much worse one. find_by_addr stops being positional: a real fingerprint beats a placeholder, newest trust decision wins among real ones.
  • Environment. PUNKTFUNK_GAMESCOPE_HDR defaults on (explicit-off grammar); the NixOS module's gamescopeHdr only controls whether the patched binary is on PATH. PUNKTFUNK_VIDEO_SOURCE defaults to virtual — unset previously fell through to the synthetic test pattern, reachable once host.env became optional. PUNKTFUNK_SECURE_DDA is read by nothing and is no longer written into a fresh host.env; PUNKTFUNK_INPUT_BACKEND documented a uinput value that does not exist and omitted kwin. PFVD_NO_RT_GPU makes the virtual display's realtime GPU priority A/B-able without a rebuild. PUNKTFUNK_STALL_PROBES=0 opts a box out of the capture micro-probe engine.