forked from unom/punktfunk
Escalate-and-hold's missing half. The contention escalation (capture depth, then the NVENC pipelined retrieve) was permanent: one sustained overrun — even one CAUSED by the ABR overdrive's rebuild storms — cost the session its depth-1 latency and its sub-frame streaming forever, and `encode_us` reported queue depth instead of ASIC time for the rest of the session. - The leaky bucket now keeps scoring after escalation. A sustained every-frame-on-cadence run (~5 s at 120 fps) winds back one stage in reverse order: pipelined retrieve first (its rebuild restores the IO-stream binding and sub-frame chunked streaming), then capture depth back to 1. Attempts are paced by an exponential backoff (1 → 5 → 25 min, capped) — a workload that truly needs the escalation converges to keeping it, but never a permanent latch. - NVENC (Linux) implements `set_pipelined(false)`: a `want_sync` latch handled at the same drained safe point as the engage side (`maybe_disengage_async` mirrors `maybe_engage_async`); the lazy sync re-init re-arms everything and opens on an IDR. The stream loop polls until the switch lands, then re-runs the escalation warmup so the wind-back's own stall can't re-escalate it. `PUNKTFUNK_NVENC_ASYNC=1` (operator-pinned async) refuses the wind-back; the trait doc now specifies the two-way contract. - While escalated, `cadence_degraded` stays latched (bitrate climbs refused) even with the bucket drained: the headroom is spent, and climbs resuming mid-escalation would saw against it and starve the clean run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>