Stall program T2 (planning: design/vdisplay-disturbance-immunity.md §9). A 300–750 ms capture stall must degrade the stream by its own duration, not by seconds of crater.
The mapping (what turns a hole into a crater): a pure capture stall creates no frame-index gap and no loss — the recovery machinery correctly stays silent. The damage is all at the resume edge: the first frame after the hole carries seconds of scene delta, and the native pace budget min(0.9 × time-to-deadline, overflow @ 3× rate) let the deadline term clamp a multi-interval overflow into the remainder of one interval — a line-rate blast that overruns the socket tx-buffer (the field WSAENOBUFS(10055) + loss_ppm edge-spike fingerprint across all three RDNA4 standby-sink cases). The self-inflicted loss then costs recovery-IDR round trips (each another oversized blast), and the ABR reads dropped/flushed/kf-storms as SEVERE — ×0.7 per event — and since #36 could even latch a phantom decode cap from a pair of stall-shaped windows at the same rate.
Commit 1 — fix(host/send): send_pacing::native_budget() (pure, unit-tested). With the rate cap active, the overflow's wire time at the proven ~3× stream rate is the budget, bounded by a 100 ms ceiling; the deadline never under-cuts it. Steady-state frames produce byte-identical schedules (the rate term already decided there); PUNKTFUNK_PACE_FACTOR=0 keeps the legacy deadline-only spread; the GameStream plane's Moonlight-pinned schedule is untouched.
Commit 2 — fix(core/abr): a deciding window that delivered under ¼ of target (capture stall, outage) still backs off, but takes the same "not a knee sample either way" arm as a draining backoff — it neither latches a decode cap nor erases the reference a genuine choke set. Two new tests model the 5 s stall cycle (no-latch) and reference-preservation around a starved interruption.
Verified no-change-needed: keyframe-storm capping already ships (IDR cooldown 750 ms/2 s + RFI-echo swallowing); in-place reconfigure_bitrate (no IDR) already exists for NVENC/AMF, so the SetBitrate-rebuild cost is confined to fallback backends.
Gates: core 346 lib tests green (--features quic; 44 abr, 2 new) · host clippy --all-targets -D warnings + 9 send_pacing tests green (linux/amd64 container) · full CI board green on the PR head (the earlier windows reds were main's pad-audio audio_wasapi breakage, fixed in #57).
Post-merge follow-up: field A/B with the case-#3 reporter (LG UltraGear / RX 9070 XT) — expected: freeze duration ≈ raw stall duration, no edge loss, no bitrate crater.
Stall program T2 (planning: `design/vdisplay-disturbance-immunity.md` §9). A 300–750 ms capture stall must degrade the stream by its own duration, not by seconds of crater.
**The mapping** (what turns a hole into a crater): a pure capture stall creates no frame-index gap and no loss — the recovery machinery correctly stays silent. The damage is all at the **resume edge**: the first frame after the hole carries seconds of scene delta, and the native pace budget `min(0.9 × time-to-deadline, overflow @ 3× rate)` let the *deadline* term clamp a multi-interval overflow into the remainder of one interval — a line-rate blast that overruns the socket tx-buffer (the field WSAENOBUFS(10055) + `loss_ppm` edge-spike fingerprint across all three RDNA4 standby-sink cases). The self-inflicted loss then costs recovery-IDR round trips (each another oversized blast), and the ABR reads `dropped`/`flushed`/kf-storms as SEVERE — ×0.7 per event — and since #36 could even **latch a phantom decode cap** from a pair of stall-shaped windows at the same rate.
**Commit 1 — `fix(host/send)`**: `send_pacing::native_budget()` (pure, unit-tested). With the rate cap active, the overflow's wire time at the proven ~3× stream rate *is* the budget, bounded by a 100 ms ceiling; the deadline never under-cuts it. Steady-state frames produce byte-identical schedules (the rate term already decided there); `PUNKTFUNK_PACE_FACTOR=0` keeps the legacy deadline-only spread; the GameStream plane's Moonlight-pinned schedule is untouched.
**Commit 2 — `fix(core/abr)`**: a deciding window that delivered under ¼ of target (capture stall, outage) still backs off, but takes the same "not a knee sample either way" arm as a draining backoff — it neither latches a decode cap nor erases the reference a genuine choke set. Two new tests model the 5 s stall cycle (no-latch) and reference-preservation around a starved interruption.
**Verified no-change-needed**: keyframe-storm capping already ships (IDR cooldown 750 ms/2 s + RFI-echo swallowing); in-place `reconfigure_bitrate` (no IDR) already exists for NVENC/AMF, so the SetBitrate-rebuild cost is confined to fallback backends.
**Gates**: core 346 lib tests green (`--features quic`; 44 abr, 2 new) · host `clippy --all-targets -D warnings` + 9 send_pacing tests green (linux/amd64 container) · full CI board green on the PR head (the earlier windows reds were main's pad-audio `audio_wasapi` breakage, fixed in #57).
**Post-merge follow-up**: field A/B with the case-#3 reporter (LG UltraGear / RX 9070 XT) — expected: freeze duration ≈ raw stall duration, no edge loss, no bitrate crater.
Stall program T2 (amplification kill), the resume-burst half. The native
pace budget was min(0.9 × time-to-deadline, overflow at ~3× stream rate) —
for steady-state frames the rate term is smaller and decides, but for an
OVERSIZED frame (a capture-stall resume carrying seconds of scene delta, a
cold IDR) the deadline term clamped a multi-interval overflow into the
remainder of ONE: an instantaneous many-×-stream-rate blast that overruns
the socket tx-buffer and loses the very frame that would have ended the
freeze. Field fingerprint across three RDNA4 standby-sink cases:
WSAENOBUFS(10055) + loss_ppm spikes at stall edges, then a recovery-IDR
round trip per retry while the client shows 'current bitrate 0.1'.
The budget is now the overflow's wire time at the pace rate itself
(send_pacing::native_budget, pure + unit-tested), bounded by an absolute
100 ms ceiling so a pathological frame can't park the send thread; the
deadline stays a target, never a license to blast. Steady-state frames
produce byte-identical schedules (the rate term already decided);
PUNKTFUNK_PACE_FACTOR=0 keeps the legacy deadline-only spread; the
GameStream plane's Moonlight-pinned schedule is untouched.
Gates: host clippy --all-targets -D warnings + 9 send_pacing tests green
(linux/amd64 container), fmt clean.
Stall program T2 (amplification kill), the phantom-latch half. A deciding
window that delivered under a quarter of the target rate (a host-side
capture stall, an outage, a mid-window pause) carries starvation-shaped
distress — a jump-to-live flush, a keyframe-ask burst — that the decode-cap
latch read as decoder evidence: under a periodic capture stall (the RDNA4
standby-sink field cases, one stall every ~5 s) every edge offers another
'backoff' at the SAME rate, and one pair latches a phantom decoder knee at
whatever rate the display driver happened to interrupt. The session then
fights the cap's re-probe ladder (+12.5% per 16-128 clean windows) for
minutes on a decoder that was never the problem.
Starved windows still back off (real damage deserves the safe response) but
take the same 'not a knee sample either way' arm as a draining backoff:
they neither latch a decode cap nor erase the reference a genuine choke
set, so a real knee's pair still finds itself around the interruption. The
¼ bar sits deliberately far under the ×¾ utilization bar climbs require.
Gates: 44 abr tests green (2 new: the stall-cycle no-latch scenario and the
reference-preservation scenario), full core lib suite 346 green
(--features quic), fmt + clippy clean.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Stall program T2 (planning:
design/vdisplay-disturbance-immunity.md§9). A 300–750 ms capture stall must degrade the stream by its own duration, not by seconds of crater.The mapping (what turns a hole into a crater): a pure capture stall creates no frame-index gap and no loss — the recovery machinery correctly stays silent. The damage is all at the resume edge: the first frame after the hole carries seconds of scene delta, and the native pace budget
min(0.9 × time-to-deadline, overflow @ 3× rate)let the deadline term clamp a multi-interval overflow into the remainder of one interval — a line-rate blast that overruns the socket tx-buffer (the field WSAENOBUFS(10055) +loss_ppmedge-spike fingerprint across all three RDNA4 standby-sink cases). The self-inflicted loss then costs recovery-IDR round trips (each another oversized blast), and the ABR readsdropped/flushed/kf-storms as SEVERE — ×0.7 per event — and since #36 could even latch a phantom decode cap from a pair of stall-shaped windows at the same rate.Commit 1 —
fix(host/send):send_pacing::native_budget()(pure, unit-tested). With the rate cap active, the overflow's wire time at the proven ~3× stream rate is the budget, bounded by a 100 ms ceiling; the deadline never under-cuts it. Steady-state frames produce byte-identical schedules (the rate term already decided there);PUNKTFUNK_PACE_FACTOR=0keeps the legacy deadline-only spread; the GameStream plane's Moonlight-pinned schedule is untouched.Commit 2 —
fix(core/abr): a deciding window that delivered under ¼ of target (capture stall, outage) still backs off, but takes the same "not a knee sample either way" arm as a draining backoff — it neither latches a decode cap nor erases the reference a genuine choke set. Two new tests model the 5 s stall cycle (no-latch) and reference-preservation around a starved interruption.Verified no-change-needed: keyframe-storm capping already ships (IDR cooldown 750 ms/2 s + RFI-echo swallowing); in-place
reconfigure_bitrate(no IDR) already exists for NVENC/AMF, so the SetBitrate-rebuild cost is confined to fallback backends.Gates: core 346 lib tests green (
--features quic; 44 abr, 2 new) · hostclippy --all-targets -D warnings+ 9 send_pacing tests green (linux/amd64 container) · full CI board green on the PR head (the earlier windows reds were main's pad-audioaudio_wasapibreakage, fixed in #57).Post-merge follow-up: field A/B with the case-#3 reporter (LG UltraGear / RX 9070 XT) — expected: freeze duration ≈ raw stall duration, no edge loss, no bitrate crater.