Four faults from one 0.29 field report on a Windows host (LEPC, 9070 XT, AMF, 2560×1440@120 HDR) with a Windows handheld client (Radeon 780M). They are independent; only the first is a single bug with a single cause.
Draft because the Windows-gated call site in commit 5 compiles on nothing available locally — cargo check --target x86_64-pc-windows-msvc dies in aws-lc-sys (windows.h missing) on a Mac, and the host crate does not build on macOS at all. CI is what will build it. Everything else is verified locally: cargo fmt --all --check clean, clippy clean, 464 punktfunk-core + 53 vendored-parser + 95 pf-bitstream tests green.
1 — AV1 has never decoded on an AMD host (a602e7cf)
Seven AV1 sessions in the field log, every one dead after ~287 frames / 2.4 s, every one silently replaced by an H.265 session at the same bitrate. The reporter believed AV1 was working well at 7 ms; that was H.265. Bitrate was irrelevant — 40 and 80 Mb/s failed identically.
The vendored cros-codecs BitReader refuses any read wider than 31 bits, "because that would break the read_bits_signed() function". True of the signed path's i32 accumulator, and misplaced: AV1 needs 32 bits in five places — timing_info's two f(32) fields, decoder_model_info's one, and the variable-width buffer-delay and buffer_removal_time fields whose lengths come from the stream and reach 32. AMF sets timing_info_present_flag; NVENC does not, which is why the rung shipped hardware_verified=true evidence="…one vendor, no soak" describing a codec that had never once decoded on AMD. Upstream's BitWriter already accepted 32 bits, so the crate could emit a header it could not read back.
Relaxing the guard alone would have been worse than the bug. Three edits are required together:
the trailing mask is u32::MAX at 32 — 1u32 << 32 overflows: a debug panic, and in release a mask of zero, i.e. a silent 0 return;
the byte cursor advances before the accumulation loop when it sits at zero remaining bits, which otherwise shifts by the full width and ORs the spent byte in (at ≤31 bits the mask discarded those bits, so it was invisible);
read_bits_signed carries its own > 31 guard, so widening the unsigned path does not silently widen the signed one.
That last guard made a latent panic reachable by test: the sign extension -1 ^ ((1 << n) - 1) overflows at n == 31, a width the guard admits and upstream's comment considered safe. Rewritten as -1i32 << n.
Blast radius is provably AV1-only — neither H.264 nor H.265 has a read wider than 31 bits, literal or variable. The 52 upstream conformance vectors pass unchanged. Recorded as PROVENANCE deviation 8.
Verified in both directions: an AV1 sequence header carrying timing_info now survives a synthesize/parse round trip, and reverting the guard makes that test fail with the exact string from the client log.
2 — A host-local rebuild read as congestion (88d071c0, 44692a0d)
An exclusive-topology eviction rebuilds the capture ring and encoder in place — ~400 ms, no packet lost. The client's 750 ms window straddled one and saw actual_kbps=390 against a 20 000 target, loss_ppm=0, encode_mean_us=15063 against a ~2800 baseline. The controller read congestion, backed off ×0.7 and retired slow start; the session spent three minutes at ~15 Mbps.
Client half.encode_us is averaged over the window's AUs, so when almost none flowed the mean describes whatever interrupted them. It is withheld when the window is starved — passed as absent, so it cannot teach the rolling baseline either. Deliberately narrow: a first attempt took the one-window severe path away from every signal and broke two existing tests that were right. Loss, a flush and a dropped frame describe what reached the client and mean the same however little flowed, so the periodic-capture-stall case still backs off on one window.
Host half — the root fix.discard_abr_window already exists and already feeds the controller nothing; it knew one cause, the client's own probe. PipelineGap (0x0A, host→client, 9 bytes) adds the second: the host announces its own in-place rebuilds. A duration, never an instant — host and client clocks are 14.7 s apart in this very log, so an instant would need skew-correcting to mean anything. The span comes from the transition trace's own total, so the client's gap_ms equals the host's total_ms: one number, checkable from either end of a field report.
Announced on the mode-switch path too. on_mode_switch does not already cover it — it clears the learned caps and baselines but touches neither current_kbps, probing nor bad_windows, and every baseline-free signal still scores the straddling window.
Additive: no ABI bump, no wire break. Old clients hit their "unknown control message" arm; old hosts never send one.
3 — The Auto bitrate ran away to 657 Mbps (abaac704)
The climb ceiling is pure link capacity (delivered × 0.7) with no term for what is being carried, and the utilization gate cannot supply one: a hardware encoder in CBR mode fills whatever target it is handed — utilization sat at 99 % the whole way up. The probe measured 939 Mbps and the session walked to 657 Mbps for 1440p120 (1.49 bits/pixel) in 37 seconds, taking client decode latency from 0.78 ms to 10 ms.
stream_ceiling_kbps bounds the learned ceiling by pixel rate and a bits-per-pixel allowance varying by codec, bit depth and chroma, at the same funnel PUNKTFUNK_ABR_MAX_MBPS already passes through, logging both numbers when it binds. Deliberately generous — 1440p120 HEVC Main10 lands at ~414 Mbps, under the decode knee this session found, while 1080p60 keeps ~93 Mbps untouched. Tested from both directions: a cap that trims a happy user is a regression nobody files.
Binds only what the probe learns; a negotiated rate is the host's decision. This is a backstop, not the answer to "how much is enough" — that is encoder QP, and it is not in this PR.
4 — Log noise that misleads triage (e50941c8)
Every host start on an AMD box logged four WARN edid_lock: adl-unlock-mode-off … ADL_ERR_NOT_SUPPORTED. The unlock is idempotent by design and runs over every connector, including ones never pinned; some drivers answer NOT_SUPPORTED where there is nothing to turn off. Scoped to the mode-off call — adl-unlock-remove is what actually clears a pin and keeps its warning.
Not in this PR
WP8, a P0: a fixed-bitrate session shipped 1 607 of ~11 400 frames with a 77-second window of zero host log output. Invisible because StallWatch only counts a gap as a stall when preceding frames were sustained ≥20 fps flow — right for noise, wrong for a desktop that was already quiet and then froze for over a minute. Trigger not yet established.
10-bit AV1 has no CPU rung at all (rav1d is built bitdepth_8), which is why one parser bug became a total feature failure.
The codec fallback is too quiet — it is why this looked like success for two rounds of analysis.
A Windows host still reports compositor=kwin.
Owed before merge
A Windows-runner build of the eviction call site.
On-glass confirmation that AV1 decodes on the reporter's pair.
AV1 coverage in corpus_replay.rs from an AMF capture — the harness covers H.264/H.265 only, and that gap is what let this ship.
An upstream cros-codecs issue for deviation 8.
Four faults from one 0.29 field report on a Windows host (LEPC, 9070 XT, AMF, 2560×1440@120 HDR) with a Windows handheld client (Radeon 780M). They are independent; only the first is a single bug with a single cause.
Draft because **the Windows-gated call site in commit 5 compiles on nothing available locally** — `cargo check --target x86_64-pc-windows-msvc` dies in `aws-lc-sys` (`windows.h` missing) on a Mac, and the host crate does not build on macOS at all. CI is what will build it. Everything else is verified locally: `cargo fmt --all --check` clean, clippy clean, 464 punktfunk-core + 53 vendored-parser + 95 pf-bitstream tests green.
---
## 1 — AV1 has never decoded on an AMD host (`a602e7cf`)
Seven AV1 sessions in the field log, every one dead after ~287 frames / 2.4 s, every one silently replaced by an H.265 session at the same bitrate. The reporter believed AV1 was working well at 7 ms; that was H.265. Bitrate was irrelevant — 40 and 80 Mb/s failed identically.
The vendored cros-codecs `BitReader` refuses any read wider than 31 bits, "because that would break the read_bits_signed() function". True of the signed path's `i32` accumulator, and misplaced: AV1 needs 32 bits in **five** places — `timing_info`'s two `f(32)` fields, `decoder_model_info`'s one, and the variable-width buffer-delay and `buffer_removal_time` fields whose lengths come from the stream and reach 32. AMF sets `timing_info_present_flag`; NVENC does not, which is why the rung shipped `hardware_verified=true evidence="…one vendor, no soak"` describing a codec that had never once decoded on AMD. Upstream's `BitWriter` already accepted 32 bits, so the crate could emit a header it could not read back.
**Relaxing the guard alone would have been worse than the bug.** Three edits are required together:
- the trailing mask is `u32::MAX` at 32 — `1u32 << 32` overflows: a debug panic, and in release a mask of zero, i.e. a silent `0` return;
- the byte cursor advances before the accumulation loop when it sits at zero remaining bits, which otherwise shifts by the full width and ORs the spent byte in (at ≤31 bits the mask discarded those bits, so it was invisible);
- `read_bits_signed` carries its own `> 31` guard, so widening the unsigned path does not silently widen the signed one.
That last guard made a **latent panic** reachable by test: the sign extension `-1 ^ ((1 << n) - 1)` overflows at `n == 31`, a width the guard admits and upstream's comment considered safe. Rewritten as `-1i32 << n`.
Blast radius is provably AV1-only — neither H.264 nor H.265 has a read wider than 31 bits, literal or variable. The 52 upstream conformance vectors pass unchanged. Recorded as PROVENANCE deviation 8.
Verified in both directions: an AV1 sequence header carrying `timing_info` now survives a synthesize/parse round trip, and reverting the guard makes that test fail with the exact string from the client log.
## 2 — A host-local rebuild read as congestion (`88d071c0`, `44692a0d`)
An exclusive-topology eviction rebuilds the capture ring and encoder in place — ~400 ms, no packet lost. The client's 750 ms window straddled one and saw `actual_kbps=390` against a 20 000 target, `loss_ppm=0`, `encode_mean_us=15063` against a ~2800 baseline. The controller read congestion, backed off ×0.7 and retired slow start; the session spent three minutes at ~15 Mbps.
**Client half.** `encode_us` is averaged over the window's AUs, so when almost none flowed the mean describes whatever interrupted them. It is withheld when the window is starved — passed as absent, so it cannot teach the rolling baseline either. Deliberately narrow: a first attempt took the one-window severe path away from *every* signal and broke two existing tests that were right. Loss, a flush and a dropped frame describe what reached the client and mean the same however little flowed, so the periodic-capture-stall case still backs off on one window.
**Host half — the root fix.** `discard_abr_window` already exists and already feeds the controller nothing; it knew one cause, the client's own probe. `PipelineGap` (`0x0A`, host→client, 9 bytes) adds the second: the host announces its own in-place rebuilds. A **duration, never an instant** — host and client clocks are 14.7 s apart in this very log, so an instant would need skew-correcting to mean anything. The span comes from the transition trace's own total, so the client's `gap_ms` equals the host's `total_ms`: one number, checkable from either end of a field report.
Announced on the mode-switch path too. `on_mode_switch` does **not** already cover it — it clears the learned caps and baselines but touches neither `current_kbps`, `probing` nor `bad_windows`, and every baseline-free signal still scores the straddling window.
Additive: no ABI bump, no wire break. Old clients hit their "unknown control message" arm; old hosts never send one.
## 3 — The Auto bitrate ran away to 657 Mbps (`abaac704`)
The climb ceiling is pure link capacity (`delivered × 0.7`) with no term for what is being carried, and the utilization gate cannot supply one: a hardware encoder in CBR mode fills whatever target it is handed — utilization sat at 99 % the whole way up. The probe measured 939 Mbps and the session walked to 657 Mbps for 1440p120 (1.49 bits/pixel) in 37 seconds, taking client decode latency from 0.78 ms to 10 ms.
`stream_ceiling_kbps` bounds the learned ceiling by pixel rate and a bits-per-pixel allowance varying by codec, bit depth and chroma, at the same funnel `PUNKTFUNK_ABR_MAX_MBPS` already passes through, logging both numbers when it binds. Deliberately generous — 1440p120 HEVC Main10 lands at ~414 Mbps, under the decode knee this session found, while 1080p60 keeps ~93 Mbps untouched. Tested from both directions: a cap that trims a happy user is a regression nobody files.
Binds only what the probe *learns*; a negotiated rate is the host's decision. This is a backstop, not the answer to "how much is enough" — that is encoder QP, and it is not in this PR.
## 4 — Log noise that misleads triage (`e50941c8`)
Every host start on an AMD box logged four `WARN edid_lock: adl-unlock-mode-off … ADL_ERR_NOT_SUPPORTED`. The unlock is idempotent by design and runs over every connector, including ones never pinned; some drivers answer NOT_SUPPORTED where there is nothing to turn off. Scoped to the mode-off call — `adl-unlock-remove` is what actually clears a pin and keeps its warning.
---
## Not in this PR
- **WP8, a P0**: a fixed-bitrate session shipped 1 607 of ~11 400 frames with a **77-second window of zero host log output**. Invisible because `StallWatch` only counts a gap as a stall when preceding frames were sustained ≥20 fps flow — right for noise, wrong for a desktop that was already quiet and then froze for over a minute. Trigger not yet established.
- 10-bit AV1 has **no CPU rung at all** (`rav1d` is built `bitdepth_8`), which is why one parser bug became a total feature failure.
- The codec fallback is too quiet — it is why this looked like success for two rounds of analysis.
- A Windows host still reports `compositor=kwin`.
## Owed before merge
- A Windows-runner build of the eviction call site.
- On-glass confirmation that AV1 decodes on the reporter's pair.
- **AV1 coverage in `corpus_replay.rs` from an AMF capture** — the harness covers H.264/H.265 only, and that gap is what let this ship.
- An upstream cros-codecs issue for deviation 8.
Every AV1 session on an AMD host died after ~287 frames and silently fell
back to H.265. The client log named it on the first access unit — "AV1
parse: more than 31 (32) bits were requested" — and then "No sequence
header parsed yet" for every AU after, because the sequence header never
parsed and each new keyframe re-hit the same wall.
The vendored cros-codecs BitReader refused any read wider than 31 bits,
"because that would break the read_bits_signed() function". True of the
signed path's i32 accumulator, and misplaced: AV1 needs 32 bits in five
places — timing_info's num_units_in_display_tick and time_scale,
decoder_model_info's num_units_in_decoding_tick, and the variable-width
buffer-delay and buffer_removal_time fields, whose lengths come from the
stream and reach 32. AMF sets timing_info_present_flag; NVENC does not,
which is why the rung's own evidence string ("one vendor, no soak")
described a codec that had never once decoded on AMD. Upstream's
BitWriter already accepted 32 bits, so the crate could emit a header it
could not read back.
Relaxing the guard alone would have been worse than the bug — three edits
are required together:
- the trailing mask is u32::MAX at 32. `1u32 << 32` overflows: a debug
panic, and in release a mask of zero, i.e. a silent 0 return;
- the byte cursor is advanced before the accumulation loop when it sits
at zero remaining bits, which otherwise shifts by the full width and
ORs the spent byte in. At <=31 bits the mask discarded those bits, so
it was invisible; at 32 the mask is all-ones and cannot;
- read_bits_signed carries its own > 31 guard, so widening the unsigned
path does not silently widen the signed one into an overflow. This is
the limit the original comment was actually protecting.
That last guard made a latent panic reachable by test: the sign extension
`-1 ^ ((1 << num_bits) - 1)` overflows at num_bits == 31, where 1i32 << 31
is i32::MIN and subtracting one from it panics in debug — a width the
guard admits and upstream considered safe. Rewritten as `-1i32 <<
num_bits`, equal for every accepted width.
Blast radius is provably AV1-only: neither H.264 nor H.265 has a read
wider than 31 bits, literal or variable — every dynamic-width call site in
the vendored tree is in the AV1 parser. The 52 upstream conformance tests
(H.264/H.265/AV1/VP9) still pass unchanged.
Tests: 32-bit reads byte-aligned, mid-byte, and entered on a spent cursor;
33 bits still refused; the signed path stops at 31 and still sign-extends;
every width 1..=31 checked against an independent extraction across a
spent-byte boundary; zero-width reads still consume nothing. End to end,
an AV1 sequence header carrying timing_info now survives a synthesize/parse
round trip — and reproduces the field error string exactly when the guard
is reverted.
Recorded as PROVENANCE deviation 8; owed upstream as a cros-codecs issue.
An exclusive-topology eviction on a Windows host rebuilds the capture ring
and the encoder in place — 401 ms, entirely host-local, no packet lost. The
client's 750 ms report window straddled one and recorded 390 kbps delivered
against a 20 000 target, loss_ppm=0, no flush, and encode_mean_us=15063
against a ~2800 baseline. That cleared ENCODE_SEVERE_US, took the
one-window path, and cost a x0.7 plus slow start for the rest of the
session. Recovery is then +6 % per six clean windows, so the field session
sat at ~15 Mbps for the three minutes it had left.
encode_us is a per-AU host measurement averaged over the window. When
almost no AUs flowed, the mean is taken over the handful that straddled
whatever interrupted them, and their encode time carries that interruption
rather than the cost of encoding at this rate. It is not a measurement, so
it is now withheld entirely when the window is STARVED — the predicate that
already existed for exactly this shape of window, hoisted above the signal
scoring. Passed as absent rather than ignored, so it cannot teach the
rolling-minimum baseline either.
Deliberately narrow. The first attempt took the one-window shortcut away
from every severe signal in a starved window and broke two tests that
turned out to be right: loss, a flush and a dropped frame describe what
reached the CLIENT and mean the same thing however little flowed, so the
periodic-capture-stall case still backs off on one window as
STARVED_DELIVERY_DIV's own comment requires. Only the host-encode signal is
withheld, because only it is measured over AUs that did not exist. Slow
start is likewise left alone: a starved window that is bad for a legitimate
reason still ends it.
Tests: the field window verbatim decides nothing and leaves slow start
armed, and the same encode excursion in a window that actually carried its
rate still backs off on one window — which is what proves the withheld
sample never entered the baseline. 459 core tests green.
The climb ceiling is pure link capacity — `delivered_kbps * 0.7`, with no
term for resolution, frame rate, codec or bit depth. The utilization gate
cannot supply one either: a hardware encoder in CBR mode genuinely fills
whatever target it is handed, and the field log shows utilization at 99 %
the whole way up, so "the encoder could not use the rate" never fires. On a
gigabit LAN the probe measured 939 Mbps and the session walked to 657 Mbps
for 1440p120 — 1.49 bits per pixel — in 37 seconds. Getting there took the
client's decode latency from 0.78 ms to 10 ms.
`stream_ceiling_kbps` computes what the shape could plausibly use from
pixel rate and a bits-per-pixel allowance that varies by codec generation,
bit depth and chroma, and `set_ceiling` holds the measured link ceiling to
it — the same funnel PUNKTFUNK_ABR_MAX_MBPS already passes through, and it
logs both numbers whenever it binds, because a cap that silently trims what
the link offered is the kind of thing nobody reports.
Deliberately generous: this is a bound on the absurd, not a quality
opinion. 1440p120 HEVC Main10 lands at ~414 Mbps — under the decode knee
this session actually found (flat at ~396 Mbps delivered, 10 ms by ~461) —
while 1080p60 HEVC keeps ~93 Mbps, above anything people run there. Tested
from both directions, because a cap that trims a happy user is a regression
nobody files.
Binds only what the probe LEARNS. A negotiated start rate is a number the
host resolved on purpose and is left alone; an explicit bitrate and every
PyroWave session are outside the controller entirely and never reach here.
Sessions that never call set_stream_cap behave exactly as before.
This is a backstop, not the answer to "how much is enough" — that is
content-dependent and only the encoder knows it, at minimum QP. It is the
part that works without new host telemetry.
Every host start on an AMD box logged four of these:
WARN edid_lock: adl-unlock-mode-off adapter5.connector0[DP] ok=false
rc=-8(ADL_ERR_NOT_SUPPORTED)
The unlock is deliberately idempotent and runs over every connector,
including the ones that were never pinned — and over all of them on a host
recovering from an unclean exit. Some drivers answer NOT_SUPPORTED to "turn
emulation off" where there is no emulation to turn off, so a perfectly
healthy start emitted one warning per connector, saying nothing. Four
standing warnings are how a log stops being read: these were the first
thing to catch the eye in a field bundle whose actual fault was elsewhere.
Scoped to the mode-off call on purpose. adl-unlock-remove is the call that
actually clears a pin, so its rc is the one that means something, and it
keeps its warning.
No behaviour change — the unlock did and does the same thing; only its
severity when it no-ops is now honest.
A Windows exclusive-topology eviction makes the host rebuild its capture
ring and encoder in place. It takes a few hundred milliseconds and is
entirely host-local: no packet is lost, the link never changes. But the
client's bitrate controller decides on 750 ms report windows, and a window
straddling that rebuild sees almost no stream. The 0.29 field log: 401 ms
of rebuild produced a window reporting actual_kbps=390 against a 20 000
target with loss_ppm=0, which the controller read as congestion — x0.7 and
slow start retired, three minutes at ~15 Mbps on a link that never dropped
a packet.
The client already knows how to throw a window away. `discard_abr_window`
feeds the controller nothing, sends no LossReport (so a bogus window cannot
spike the host's adaptive FEC) and closes the standing-latency detector as
not-loss-free. It had exactly one cause: the tail of the client's own speed
test. This adds the second, and it is the one party that actually knows —
the host.
`PipelineGap` (0x0A, host->client, 9 bytes) carries the rebuild's measured
span. A DURATION, never an instant: host and client clocks are not in the
same domain — 14.7 s apart in that same log — so an instant would need
skew-correcting before it meant anything. The client anchors the gap to its
own receive time and gap_ms is evidence for the log rather than an input to
arithmetic. The span is read from the transition trace's own total, so the
number the client logs is the total_ms on the host's trace line: one number,
checkable from either end of a field report.
Announced on both in-place rebuild paths, including the mode switch. The
mode switch is NOT already covered by the client's `on_mode_switch` reset:
that clears the learned caps, the three latency baselines and the proven
mark, which does mute OWD/decode/encode for a few windows — but it touches
neither `current_kbps`, `probing` nor `bad_windows`, and every signal that
needs no baseline (an unrecoverable frame, a flush, heavy loss over a
near-empty denominator, a keyframe-ask storm) still scores the straddling
window, where one severe verdict costs the same x0.7 plus slow start.
Deliberate limitation: only the window in flight is discarded. A rebuild
long enough to straddle a window boundary damaged the previous window too,
and that one is already decided. Retracting it would mean holding every
window back by a window in case a gap follows — trading a rare
over-reaction for a permanent one.
Additive: no ABI bump, no wire break. 0x0A extends the contiguous
0x01-0x09 rate-control block its only consumer already lives in, and is not
in the 0x30 clock block precisely because no clock domain is involved. A
client that predates it hits its "unknown control message" arm and keeps
today's behaviour; a host that predates it never sends one.
Tests: wire round trip including cross-decode against the three
identically-shaped rate-control messages either side of it (the type byte
is the only thing keeping a gap from re-decoding as a SetBitrate), and an
end-to-end pump test driving a real ControlTask over a real QUIC control
stream into a real DataPump, asserting the straddling window produces no
request and the next one reports normally. 464 core tests green, fmt and
clippy clean.
Not verified here: the Windows-gated eviction call site compiles on no
platform available locally (aws-lc-sys needs windows.h to cross-compile).
Type-checked by reading; owed a Windows runner build.
Known gaps, stated rather than papered over: a mode-switch rebuild that
FAILS keeps streaming the old mode and leaves its stall unannounced, and
three other in-place rebuild sites are still silent — the Gaming/Desktop
session switch, the ABR re-target's fallback open_video rebuild (~0.6 s,
and self-inflicted: the controller causing the stall its next window reads
as congestion), and reset_stalled_encoder.
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.
Four faults from one 0.29 field report on a Windows host (LEPC, 9070 XT, AMF, 2560×1440@120 HDR) with a Windows handheld client (Radeon 780M). They are independent; only the first is a single bug with a single cause.
Draft because the Windows-gated call site in commit 5 compiles on nothing available locally —
cargo check --target x86_64-pc-windows-msvcdies inaws-lc-sys(windows.hmissing) on a Mac, and the host crate does not build on macOS at all. CI is what will build it. Everything else is verified locally:cargo fmt --all --checkclean, clippy clean, 464 punktfunk-core + 53 vendored-parser + 95 pf-bitstream tests green.1 — AV1 has never decoded on an AMD host (
a602e7cf)Seven AV1 sessions in the field log, every one dead after ~287 frames / 2.4 s, every one silently replaced by an H.265 session at the same bitrate. The reporter believed AV1 was working well at 7 ms; that was H.265. Bitrate was irrelevant — 40 and 80 Mb/s failed identically.
The vendored cros-codecs
BitReaderrefuses any read wider than 31 bits, "because that would break the read_bits_signed() function". True of the signed path'si32accumulator, and misplaced: AV1 needs 32 bits in five places —timing_info's twof(32)fields,decoder_model_info's one, and the variable-width buffer-delay andbuffer_removal_timefields whose lengths come from the stream and reach 32. AMF setstiming_info_present_flag; NVENC does not, which is why the rung shippedhardware_verified=true evidence="…one vendor, no soak"describing a codec that had never once decoded on AMD. Upstream'sBitWriteralready accepted 32 bits, so the crate could emit a header it could not read back.Relaxing the guard alone would have been worse than the bug. Three edits are required together:
u32::MAXat 32 —1u32 << 32overflows: a debug panic, and in release a mask of zero, i.e. a silent0return;read_bits_signedcarries its own> 31guard, so widening the unsigned path does not silently widen the signed one.That last guard made a latent panic reachable by test: the sign extension
-1 ^ ((1 << n) - 1)overflows atn == 31, a width the guard admits and upstream's comment considered safe. Rewritten as-1i32 << n.Blast radius is provably AV1-only — neither H.264 nor H.265 has a read wider than 31 bits, literal or variable. The 52 upstream conformance vectors pass unchanged. Recorded as PROVENANCE deviation 8.
Verified in both directions: an AV1 sequence header carrying
timing_infonow survives a synthesize/parse round trip, and reverting the guard makes that test fail with the exact string from the client log.2 — A host-local rebuild read as congestion (
88d071c0,44692a0d)An exclusive-topology eviction rebuilds the capture ring and encoder in place — ~400 ms, no packet lost. The client's 750 ms window straddled one and saw
actual_kbps=390against a 20 000 target,loss_ppm=0,encode_mean_us=15063against a ~2800 baseline. The controller read congestion, backed off ×0.7 and retired slow start; the session spent three minutes at ~15 Mbps.Client half.
encode_usis averaged over the window's AUs, so when almost none flowed the mean describes whatever interrupted them. It is withheld when the window is starved — passed as absent, so it cannot teach the rolling baseline either. Deliberately narrow: a first attempt took the one-window severe path away from every signal and broke two existing tests that were right. Loss, a flush and a dropped frame describe what reached the client and mean the same however little flowed, so the periodic-capture-stall case still backs off on one window.Host half — the root fix.
discard_abr_windowalready exists and already feeds the controller nothing; it knew one cause, the client's own probe.PipelineGap(0x0A, host→client, 9 bytes) adds the second: the host announces its own in-place rebuilds. A duration, never an instant — host and client clocks are 14.7 s apart in this very log, so an instant would need skew-correcting to mean anything. The span comes from the transition trace's own total, so the client'sgap_msequals the host'stotal_ms: one number, checkable from either end of a field report.Announced on the mode-switch path too.
on_mode_switchdoes not already cover it — it clears the learned caps and baselines but touches neithercurrent_kbps,probingnorbad_windows, and every baseline-free signal still scores the straddling window.Additive: no ABI bump, no wire break. Old clients hit their "unknown control message" arm; old hosts never send one.
3 — The Auto bitrate ran away to 657 Mbps (
abaac704)The climb ceiling is pure link capacity (
delivered × 0.7) with no term for what is being carried, and the utilization gate cannot supply one: a hardware encoder in CBR mode fills whatever target it is handed — utilization sat at 99 % the whole way up. The probe measured 939 Mbps and the session walked to 657 Mbps for 1440p120 (1.49 bits/pixel) in 37 seconds, taking client decode latency from 0.78 ms to 10 ms.stream_ceiling_kbpsbounds the learned ceiling by pixel rate and a bits-per-pixel allowance varying by codec, bit depth and chroma, at the same funnelPUNKTFUNK_ABR_MAX_MBPSalready passes through, logging both numbers when it binds. Deliberately generous — 1440p120 HEVC Main10 lands at ~414 Mbps, under the decode knee this session found, while 1080p60 keeps ~93 Mbps untouched. Tested from both directions: a cap that trims a happy user is a regression nobody files.Binds only what the probe learns; a negotiated rate is the host's decision. This is a backstop, not the answer to "how much is enough" — that is encoder QP, and it is not in this PR.
4 — Log noise that misleads triage (
e50941c8)Every host start on an AMD box logged four
WARN edid_lock: adl-unlock-mode-off … ADL_ERR_NOT_SUPPORTED. The unlock is idempotent by design and runs over every connector, including ones never pinned; some drivers answer NOT_SUPPORTED where there is nothing to turn off. Scoped to the mode-off call —adl-unlock-removeis what actually clears a pin and keeps its warning.Not in this PR
StallWatchonly counts a gap as a stall when preceding frames were sustained ≥20 fps flow — right for noise, wrong for a desktop that was already quiet and then froze for over a minute. Trigger not yet established.rav1dis builtbitdepth_8), which is why one parser bug became a total feature failure.compositor=kwin.Owed before merge
corpus_replay.rsfrom an AMF capture — the harness covers H.264/H.265 only, and that gap is what let this ship.Every AV1 session on an AMD host died after ~287 frames and silently fell back to H.265. The client log named it on the first access unit — "AV1 parse: more than 31 (32) bits were requested" — and then "No sequence header parsed yet" for every AU after, because the sequence header never parsed and each new keyframe re-hit the same wall. The vendored cros-codecs BitReader refused any read wider than 31 bits, "because that would break the read_bits_signed() function". True of the signed path's i32 accumulator, and misplaced: AV1 needs 32 bits in five places — timing_info's num_units_in_display_tick and time_scale, decoder_model_info's num_units_in_decoding_tick, and the variable-width buffer-delay and buffer_removal_time fields, whose lengths come from the stream and reach 32. AMF sets timing_info_present_flag; NVENC does not, which is why the rung's own evidence string ("one vendor, no soak") described a codec that had never once decoded on AMD. Upstream's BitWriter already accepted 32 bits, so the crate could emit a header it could not read back. Relaxing the guard alone would have been worse than the bug — three edits are required together: - the trailing mask is u32::MAX at 32. `1u32 << 32` overflows: a debug panic, and in release a mask of zero, i.e. a silent 0 return; - the byte cursor is advanced before the accumulation loop when it sits at zero remaining bits, which otherwise shifts by the full width and ORs the spent byte in. At <=31 bits the mask discarded those bits, so it was invisible; at 32 the mask is all-ones and cannot; - read_bits_signed carries its own > 31 guard, so widening the unsigned path does not silently widen the signed one into an overflow. This is the limit the original comment was actually protecting. That last guard made a latent panic reachable by test: the sign extension `-1 ^ ((1 << num_bits) - 1)` overflows at num_bits == 31, where 1i32 << 31 is i32::MIN and subtracting one from it panics in debug — a width the guard admits and upstream considered safe. Rewritten as `-1i32 << num_bits`, equal for every accepted width. Blast radius is provably AV1-only: neither H.264 nor H.265 has a read wider than 31 bits, literal or variable — every dynamic-width call site in the vendored tree is in the AV1 parser. The 52 upstream conformance tests (H.264/H.265/AV1/VP9) still pass unchanged. Tests: 32-bit reads byte-aligned, mid-byte, and entered on a spent cursor; 33 bits still refused; the signed path stops at 31 and still sign-extends; every width 1..=31 checked against an independent extraction across a spent-byte boundary; zero-width reads still consume nothing. End to end, an AV1 sequence header carrying timing_info now survives a synthesize/parse round trip — and reproduces the field error string exactly when the guard is reverted. Recorded as PROVENANCE deviation 8; owed upstream as a cros-codecs issue.Every host start on an AMD box logged four of these: WARN edid_lock: adl-unlock-mode-off adapter5.connector0[DP] ok=false rc=-8(ADL_ERR_NOT_SUPPORTED) The unlock is deliberately idempotent and runs over every connector, including the ones that were never pinned — and over all of them on a host recovering from an unclean exit. Some drivers answer NOT_SUPPORTED to "turn emulation off" where there is no emulation to turn off, so a perfectly healthy start emitted one warning per connector, saying nothing. Four standing warnings are how a log stops being read: these were the first thing to catch the eye in a field bundle whose actual fault was elsewhere. Scoped to the mode-off call on purpose. adl-unlock-remove is the call that actually clears a pin, so its rc is the one that means something, and it keeps its warning. No behaviour change — the unlock did and does the same thing; only its severity when it no-ops is now honest.