Re-implements the Rust half of #371 (see that PR for the Fire TV field report — the diagnosis there is right, this changes where the fix lands). #371 should not be merged.
Two independent defects behind the same symptom: a black screen at stream start on a constrained link, reported on a Fire TV Stick 4K Max and, earlier, on webOS.
1. The 2 Gbps probe target is more than any session can use
The probe bursts at capacity_probe_kbps (2 Gbps) on the reasoning that the target must be "deliberately far above any plausible link so the burst measures the link and not itself".
That reasoning is obsolete. pump.rs already computes stream_cap_kbps = abr::stream_ceiling_kbps(w, h, hz, codec, depth, chroma) and hands it to abr.set_stream_cap() immediately above the probe setup, and set_ceiling clamps every learned ceiling to it. The measured ceiling is delivered × 0.7. So every bit the burst measures above stream_cap_kbps / 0.7 is discarded the moment it lands — the height was paying bufferbloat for a number nothing reads.
Now derived: stream_cap_kbps × 2, capped at the old 2 Gbps.
×2 is the smallest multiplier that can still prove the cap. Reaching stream_cap_kbps needs delivered ≥ cap × 1.43; the rest is margin.
It cannot cap anyone. A session whose mode and codec justify a high ceiling asks for a correspondingly high target on its own. 1440p120 HEVC → a 664 Mbps burst, not 2 Gbps. 4K120 10-bit → 2 Gbps, as before.
A mode we can't size still gets 2 Gbps.stream_ceiling_kbps returns u32::MAX for a degenerate mode; saturating_mul(2).min(2_000_000) lands on the historic default.
Deliberately not a cfg!(target_os = "android") special case. The constraint belongs to the session, not to the platform, and this fixes webOS and every other constrained client at the same time — with a smaller diff and no risk of capping the wired Shield / Wi-Fi 6E clients that do have the capacity.
PUNKTFUNK_ABR_PROBE_KBPS and its > 0 filter are unchanged; it still overrides the target outright.
2. A keyframe lost inside the burst was never re-requested
If the burst takes the first IDR down with it, nothing re-anchors the decoder and the client sits on black until an unrelated recovery path happens to fire.
CtrlRequest::Keyframe at probe end, matching the two existing emitters in the file and funnelling through the control task's coalescing choke point — so it cannot IDR-storm (which matters given #377).
On the guard, deliberately widened.#371 used st.frames_completed == 0 || st.media_bytes_received == 0; both are cumulative session counters, so that only ever means "no frame has ever arrived". This snapshots frames_completed at the burst's leading edge and compares against that instead. At startup the two are the same test, but this one also catches a burst that kills an already-running stream — an embedder request_probe "Test connection" mid-session — which the cumulative counter structurally could not. (media_bytes_received == 0 was redundant: it is implied by frames_completed == 0.) The block runs on every probe end including a successful one; the frame-count guard is what makes it a no-op the rest of the time, and the comment now says that rather than the old "if the startup probe fails to complete".
Tests
the_probe_target_proves_the_stream_cap_without_overshooting_it — for four real modes (720p60 HEVC → 4K120 10-bit HEVC), the derived target must both prove the cap (target × 0.7 ≥ cap) and not overshoot it (target ≤ cap × 2), plus the u32::MAX and above-2-Gbps saturation edges. The clamp itself is already pinned by abr::tests::the_stream_bound_clamps_a_learned_ceiling_only.
cargo test -p punktfunk-core --features quic --lib → 481 passed, 0 failed. Clippy and rustfmt clean.
Still owed: on-glass numbers
Neither leg of the field verification is done — no Fire TV Stick 4K Max here. Before merge:
a Fire TV Stick 4K Max on Wi-Fi reaches first video with no 6 s probe timeout and no send_dropped spike;
a high-mode session on a fast link still climbs to the same ceiling it reaches on main today (both numbers recorded here).
User-facing fact changed? Yes — PUNKTFUNK_ABR_PROBE_KBPS's default is no longer a flat 2 Gbps. docs-site/content/docs/configuration.md is updated in this PR.
Re-implements the Rust half of #371 (see that PR for the Fire TV field report — the diagnosis there is right, this changes where the fix lands). #371 should not be merged.
Two independent defects behind the same symptom: **a black screen at stream start on a constrained link**, reported on a Fire TV Stick 4K Max and, earlier, on webOS.
## 1. The 2 Gbps probe target is more than any session can use
The probe bursts at `capacity_probe_kbps` (2 Gbps) on the reasoning that the target must be *"deliberately far above any plausible link so the burst measures the link and not itself"*.
That reasoning is obsolete. `pump.rs` already computes `stream_cap_kbps = abr::stream_ceiling_kbps(w, h, hz, codec, depth, chroma)` and hands it to `abr.set_stream_cap()` immediately above the probe setup, and `set_ceiling` clamps every learned ceiling to it. The measured ceiling is `delivered × 0.7`. **So every bit the burst measures above `stream_cap_kbps / 0.7` is discarded the moment it lands** — the height was paying bufferbloat for a number nothing reads.
Now derived: `stream_cap_kbps × 2`, capped at the old 2 Gbps.
* **×2 is the smallest multiplier that can still prove the cap.** Reaching `stream_cap_kbps` needs `delivered ≥ cap × 1.43`; the rest is margin.
* **It cannot cap anyone.** A session whose mode and codec justify a high ceiling asks for a correspondingly high target on its own. 1440p120 HEVC → a 664 Mbps burst, not 2 Gbps. 4K120 10-bit → 2 Gbps, as before.
* **A mode we can't size still gets 2 Gbps.** `stream_ceiling_kbps` returns `u32::MAX` for a degenerate mode; `saturating_mul(2).min(2_000_000)` lands on the historic default.
* Deliberately **not** a `cfg!(target_os = "android")` special case. The constraint belongs to the session, not to the platform, and this fixes webOS and every other constrained client at the same time — with a smaller diff and no risk of capping the wired Shield / Wi-Fi 6E clients that do have the capacity.
`PUNKTFUNK_ABR_PROBE_KBPS` and its `> 0` filter are unchanged; it still overrides the target outright.
## 2. A keyframe lost inside the burst was never re-requested
If the burst takes the first IDR down with it, nothing re-anchors the decoder and the client sits on black until an unrelated recovery path happens to fire.
`CtrlRequest::Keyframe` at probe end, matching the two existing emitters in the file and funnelling through the control task's coalescing choke point — so it cannot IDR-storm (which matters given #377).
**On the guard, deliberately widened.** #371 used `st.frames_completed == 0 || st.media_bytes_received == 0`; both are *cumulative session* counters, so that only ever means "no frame has ever arrived". This snapshots `frames_completed` at the burst's leading edge and compares against that instead. At startup the two are the same test, but this one also catches a burst that kills an **already-running** stream — an embedder `request_probe` "Test connection" mid-session — which the cumulative counter structurally could not. (`media_bytes_received == 0` was redundant: it is implied by `frames_completed == 0`.) The block runs on every probe end including a successful one; the frame-count guard is what makes it a no-op the rest of the time, and the comment now says that rather than the old "if the startup probe fails to complete".
## Tests
`the_probe_target_proves_the_stream_cap_without_overshooting_it` — for four real modes (720p60 HEVC → 4K120 10-bit HEVC), the derived target must both **prove** the cap (`target × 0.7 ≥ cap`) and **not overshoot** it (`target ≤ cap × 2`), plus the `u32::MAX` and above-2-Gbps saturation edges. The clamp itself is already pinned by `abr::tests::the_stream_bound_clamps_a_learned_ceiling_only`.
`cargo test -p punktfunk-core --features quic --lib` → 481 passed, 0 failed. Clippy and rustfmt clean.
## Still owed: on-glass numbers
Neither leg of the field verification is done — no Fire TV Stick 4K Max here. Before merge:
* a Fire TV Stick 4K Max on Wi-Fi reaches first video with no 6 s probe timeout and no `send_dropped` spike;
* a high-mode session on a fast link still climbs to the same ceiling it reaches on `main` today (both numbers recorded here).
**User-facing fact changed?** Yes — `PUNKTFUNK_ABR_PROBE_KBPS`'s default is no longer a flat 2 Gbps. `docs-site/content/docs/configuration.md` is updated in this PR.
The startup link-capacity probe burst at a flat 2 Gbps on the reasoning that it
must measure the link and not itself. That reasoning is obsolete: the ABR
already clamps the measured ceiling to `stream_cap_kbps` (what this session's
mode + codec could plausibly use), so every bit measured above `cap / 0.7` is
discarded the moment it lands. The height bought a number nothing reads, and
paid bufferbloat for it — a constrained Wi-Fi link can black-hole under it
(measured on webOS: a 6 s probe timeout delaying first video to 14 s, and a
"successful" probe still reporting send_dropped=20211; the same shape is now
reported on a Fire TV Stick 4K Max).
Derive the target instead: `stream_cap_kbps × 2`, capped at the old 2 Gbps.
×2 is the smallest multiplier that can still prove the cap (the ceiling is
`delivered × 0.7`, so proving it needs `delivered ≥ cap × 1.43`), so this can
never cap anyone — a session whose mode justifies a high ceiling asks for a
high target by itself, and a mode `stream_ceiling_kbps` declines to size still
gets 2 Gbps. Deliberately not a platform `cfg!`: the constraint is the
session's, not Android's, and webOS has the same bug.
Second half of the black screen: if the burst takes the first keyframe down
with it, nothing re-requests one and the client sits on black until an
unrelated recovery path happens to fire. Ask for a keyframe at probe end when
no frame completed across the burst — compared against the count snapshotted
at the burst's leading edge rather than against 0, so it also covers a
mid-session embedder speed test that kills a running stream. One request per
probe, through the control task's coalescer, so it cannot IDR-storm.
`PUNKTFUNK_ABR_PROBE_KBPS` and its `> 0` filter are unchanged.
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.
Re-implements the Rust half of #371 (see that PR for the Fire TV field report — the diagnosis there is right, this changes where the fix lands). #371 should not be merged.
Two independent defects behind the same symptom: a black screen at stream start on a constrained link, reported on a Fire TV Stick 4K Max and, earlier, on webOS.
1. The 2 Gbps probe target is more than any session can use
The probe bursts at
capacity_probe_kbps(2 Gbps) on the reasoning that the target must be "deliberately far above any plausible link so the burst measures the link and not itself".That reasoning is obsolete.
pump.rsalready computesstream_cap_kbps = abr::stream_ceiling_kbps(w, h, hz, codec, depth, chroma)and hands it toabr.set_stream_cap()immediately above the probe setup, andset_ceilingclamps every learned ceiling to it. The measured ceiling isdelivered × 0.7. So every bit the burst measures abovestream_cap_kbps / 0.7is discarded the moment it lands — the height was paying bufferbloat for a number nothing reads.Now derived:
stream_cap_kbps × 2, capped at the old 2 Gbps.stream_cap_kbpsneedsdelivered ≥ cap × 1.43; the rest is margin.stream_ceiling_kbpsreturnsu32::MAXfor a degenerate mode;saturating_mul(2).min(2_000_000)lands on the historic default.cfg!(target_os = "android")special case. The constraint belongs to the session, not to the platform, and this fixes webOS and every other constrained client at the same time — with a smaller diff and no risk of capping the wired Shield / Wi-Fi 6E clients that do have the capacity.PUNKTFUNK_ABR_PROBE_KBPSand its> 0filter are unchanged; it still overrides the target outright.2. A keyframe lost inside the burst was never re-requested
If the burst takes the first IDR down with it, nothing re-anchors the decoder and the client sits on black until an unrelated recovery path happens to fire.
CtrlRequest::Keyframeat probe end, matching the two existing emitters in the file and funnelling through the control task's coalescing choke point — so it cannot IDR-storm (which matters given #377).On the guard, deliberately widened. #371 used
st.frames_completed == 0 || st.media_bytes_received == 0; both are cumulative session counters, so that only ever means "no frame has ever arrived". This snapshotsframes_completedat the burst's leading edge and compares against that instead. At startup the two are the same test, but this one also catches a burst that kills an already-running stream — an embedderrequest_probe"Test connection" mid-session — which the cumulative counter structurally could not. (media_bytes_received == 0was redundant: it is implied byframes_completed == 0.) The block runs on every probe end including a successful one; the frame-count guard is what makes it a no-op the rest of the time, and the comment now says that rather than the old "if the startup probe fails to complete".Tests
the_probe_target_proves_the_stream_cap_without_overshooting_it— for four real modes (720p60 HEVC → 4K120 10-bit HEVC), the derived target must both prove the cap (target × 0.7 ≥ cap) and not overshoot it (target ≤ cap × 2), plus theu32::MAXand above-2-Gbps saturation edges. The clamp itself is already pinned byabr::tests::the_stream_bound_clamps_a_learned_ceiling_only.cargo test -p punktfunk-core --features quic --lib→ 481 passed, 0 failed. Clippy and rustfmt clean.Still owed: on-glass numbers
Neither leg of the field verification is done — no Fire TV Stick 4K Max here. Before merge:
send_droppedspike;maintoday (both numbers recorded here).User-facing fact changed? Yes —
PUNKTFUNK_ABR_PROBE_KBPS's default is no longer a flat 2 Gbps.docs-site/content/docs/configuration.mdis updated in this PR.