ci / bun-nix (pull_request) Successful in 29s
ci / web (pull_request) Successful in 1m8s
apple / swift (pull_request) Successful in 1m50s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 4m14s
ci / rust-arm64 (pull_request) Successful in 4m52s
ci / rust (pull_request) Successful in 4m57s
android / android (pull_request) Successful in 7m6s
The raw-dmabuf passthrough handed the SPA buffer back to gamescope at .process return while the encode thread had not yet imported - let alone read - its dmabuf, and nothing ordered the producer's writes against the consumer's read (no explicit sync; the implicit-fence wait measures NoFence on every compositor x vendor pairing we have). On the direct-VCN arms (native NV12, RGB-direct EFC) the captured buffer IS the encode source for the whole ring-2-deep encode plus the phase-lock hold, so at 120 fps gamescope cycles back into the buffer mid-encode and the stream ships torn frames: luma/chroma desync (magenta tint) plus block corruption propagating through the P-chain until the next intra. Field report: Nobara, gamescope mode at 120 fps - KDE sessions were clean because cursor_blend routes them to the compute-CSC copy arm whose read window is microseconds. The fix defers the requeue: a published passthrough frame carries a FrameHold (new on DmabufFrame), and the buffer rejoins the producer's pool only when the last clone drops. The Vulkan encoder clones the hold into the ring slot at submit and releases it when the slot's fence retires (poll/backpressure/reset), extending "the producer must not rewrite this" across exactly the GPU read. The host loop's repeat path is fixed by the same mechanism: a re-submitted frame now aliases a buffer the producer never got back, instead of whatever gamescope last composited into it. Bookkeeping lives in a per-stream HoldBook (loop-thread mutations only): holds release through a pw channel onto the loop thread, a generation tag keeps a stale release from requeueing a renegotiated pool's reused address, and at most pool_depth - HOLD_POOL_RESERVE buffers are ever withheld - a pool at the old floor of 2 cannot spare any and falls back (with one warn) to the previous racy contract. PUNKTFUNK_ZEROCOPY_HOLD=0 restores the old behavior outright for field bisects. Gates (.25): cargo check + clippy --all-targets -D warnings on pf-frame/pf-capture/pf-encode/punktfunk-host; pf-capture 68/68 (4 new HoldBook tests), pf-encode 75/75 (+15 ignored, host-feature set); workspace cargo fmt --check clean. punktfunk-host's hooks::prep_runs_do_in_order_and_undo_in_reverse fails on that box on pristine main too (pre-existing; crate untouched here).