bdcbb2d3a7
The embedder-facing frame queue was a 16-deep sync_channel whose try_send dropped the NEWEST access unit on overflow — backwards for a live stream (keeps stale, discards fresh), a ~266 ms floor that could not self-drain (producer and consumer both run at frame rate, so any depth a burst injects is conserved forever — the latency ratchet), and a silent reference-chain break the loss counters never saw. The clock-based flush meant to catch it was gated on the skew handshake and never even drained that queue. Replace it with a purpose-built FrameChannel (VecDeque + Condvar) exposing depth() and clear(). Pre-decode AUs are reference-chained under the host's infinite GOP, so they are never dropped mid-stream; instead, when the embedder falls persistently behind, the pump JUMPS TO LIVE — flush_backlog() + clear the queued AUs + request a keyframe — so decode re-anchors cleanly at an IDR. Two cooldown-gated detectors, both suspended during a speed test: - clock-based (existing): > FLUSH_LATENCY behind the skew-corrected clock for FLUSH_AFTER_FRAMES straight; also catches kernel/reassembler backlog. - clock-free (new): the hand-off queue sat >= QUEUE_HIGH without draining to QUEUE_LOW for STANDING_FRAMES straight. Works on same-clock / no-handshake sessions where the clock path is disarmed — the direct "the embedder can't keep up" signal. A transient Wi-Fi clump drains in a few frames and never trips it. Bounded (90-frame hard cap, drop-oldest memory backstop) and diagnosable (each jump logs queue_depth / flushed_datagrams / dropped_frames). next_frame's external Timeout/Closed contract is unchanged, so every native client inherits the fix. Adds 5 FrameChannel unit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>