Three decode rungs were writing decoded pictures into surfaces they were simultaneously predicting from. It was invisible on glass, it survived every green test suite we had, and on H.264 it hit ~99% of the frames of every stream a punktfunk host emits.
This started as the six follow-ups from #95/#96/#97 and turned into that.
The defect
A conversion released the pictures an access unit displaces inside the conversion, then assigned the decode target a slot. SlotMap::assign takes the lowest free slot — the one just vacated. So the submission named one surface as both CurrPicTextureIndex and one of its own references.
Fixed the same way everywhere: the plan gains release_after_decode, and the caller applies removals only once the decode op is issued. It costs no slot. pf-vkdecode's AV1 path already had exactly this concept, which is why that one leg was always correct.
rung / codec
before
after
D3D11VA AV1 (RTX 3500 Ada)
186/250 frames wrong
250/250 ✅
D3D11VA AV1 (Intel Arc)
245/250 wrong
250/250 ✅
D3D11VA H.264 (Ada / Arc)
117/120, 80/120 wrong
120/120 ✅
Vulkan H.264 (RTX 5070 Ti)
117/120 wrong
120/120 ✅
VAAPI
no aliasing — but only incidentally
hardened so it cannot start to
HEVC
exempt (snapshots after decode_rps)
now a standing gate, not an argument
Vulkan is the default rung on Linux and on Windows+NVIDIA, and H.264 is the fallback every client can land on. That is the release-blocking part.
Why nothing caught it
The vendored H.264 conformance vector shows zero occurrences. It reorders, and its level/VUI hand it a 7-frame DPB against 2 reference frames — both properties of that one vector. Our hosts emit the opposite shape: NVENC writes max_num_ref_frames = 3andmax_dec_frame_buffering = 3, a DPB exactly as deep as its reference count, so the sliding window unmarks the oldest reference in the very access unit whose bump evicts it.
Three tests turned out to be incapable of failing, each guarding the exact defect that shipped:
pf-vkdecodeasserted the aliasing as "the planner's normal behaviour" — it encoded the bug.
pf-dxvadec carried a vacuous predicate that compared an expression with itself.
pf-vaadec asserted the decode target was never a reference while giving every picture a unique surface id — distinct integers cannot collide.
Every fix here now carries a counterfactual that makes the bad ordering happen and proves it is caught.
New permanent gates
Host-shaped fixtures, because conformance vectors demonstrably cannot see this class of bug. All goldens cross-checked on two independent ffmpeg builds on two architectures.
lowdelay-640x480.h264 (282 KB) — the stream that exposed it
lowdelay-640x480.h265 (270 KB) — HEVC's exemption held, 0/120, with a counterfactual that makes it 115/120
lowdelay-3840x2160.ivf.av1 (261 KB) — 4K is mandatory: 720p/1080p/1440p all measure a single tile. Which surfaced that every AV1 frame either rung had ever been measured against was single-tile, so the tile arrays had only ever been written at index 0.
That rung had never had a pixel checked. It now has a #[cfg(test)] readback (vaDeriveImage, falling back to vaCreateImage+vaGetImage) and all seven legs are bit-identical to libavcodec on RDNA3.
The readback is kept off the production path structurally — the entry points are resolved by a test-only type that dlopens libva itself, the production Libva gains no field, and a CPU test scans the file's own source to keep it that way. Zero-copy on the real path is unchanged.
It also stopped dropping frames: finish showed only outputs.last() and never flushed, so it delivered 225/204/45 of 250/250/50. Now 250/250/50. Two defects fell out of that: color and display were stamped from the wrong access unit too (that is the in-band HDR switch), and concealment was about to start draining the queue, which would have zeroed the demotion streak and removed the ladder's escape hatch.
VAAPI stays verified = false on purpose. Parity is no longer the gap — one vendor and no soak are. And flipping it is a routing change: native_rung_admitted would put VAAPI ahead of Vulkan Video on every Linux AMD/Intel client, the Steam Deck included. That deserves its own decision.
Also in here
The six original follow-ups: the H.264 DPB level-ceiling cliff named and guarded (PlanWarning::LevelDerivedDpb); the CPU rung's process-isolation question decided and written down with its revisit trigger; the AV1 tile-aware sub-frame reader closed on measurement rather than left open; and the D3D11VA AV1 rung promoted to verified on two vendors.
Verification
Every gate run on the merged tree, not per-branch.
.21 — fmt, clippy --workspace --all-targets -D warnings, all unit suites, gpu_parity 11/11
.221 — 8/8 D3D11VA parity legs on both GPUs
.25 — 39 tests, 0 failed, 0 ignored, every VAAPI parity leg
Each fix was also re-verified independently against pre-fix source: the new H.264 leg fails 117/120 at frame 3 without it, and the VAAPI harness was proved falsifiable by planting a real pitch defect.
⚠ Known and recorded, not fixed here: VAAPI is one vendor; rav1d is still not panic-proof (539 abort sites reachable across a C ABI); pp.width sends UpscaledWidth where libavcodec sends FrameWidth, identical only while superres is off.
Three decode rungs were writing decoded pictures into surfaces they were simultaneously predicting from. It was invisible on glass, it survived every green test suite we had, and on H.264 it hit ~99% of the frames of every stream a punktfunk host emits.
This started as the six follow-ups from #95/#96/#97 and turned into that.
## The defect
A conversion released the pictures an access unit displaces **inside** the conversion, then assigned the decode target a slot. `SlotMap::assign` takes the lowest free slot — the one just vacated. So the submission named one surface as both `CurrPicTextureIndex` and one of its own references.
Fixed the same way everywhere: the plan gains `release_after_decode`, and the caller applies removals only once the decode op is issued. It costs no slot. `pf-vkdecode`'s AV1 path already had exactly this concept, which is why that one leg was always correct.
| rung / codec | before | after |
|---|---|---|
| D3D11VA AV1 (RTX 3500 Ada) | 186/250 frames wrong | 250/250 ✅ |
| D3D11VA AV1 (Intel Arc) | 245/250 wrong | 250/250 ✅ |
| D3D11VA H.264 (Ada / Arc) | 117/120, 80/120 wrong | 120/120 ✅ |
| **Vulkan H.264 (RTX 5070 Ti)** | **117/120 wrong** | 120/120 ✅ |
| VAAPI | no aliasing — but only incidentally | hardened so it cannot start to |
| HEVC | exempt (snapshots after `decode_rps`) | now a standing gate, not an argument |
**Vulkan is the default rung on Linux and on Windows+NVIDIA, and H.264 is the fallback every client can land on.** That is the release-blocking part.
## Why nothing caught it
The vendored H.264 conformance vector shows **zero** occurrences. It reorders, and its level/VUI hand it a 7-frame DPB against 2 reference frames — both properties of that one vector. Our hosts emit the opposite shape: NVENC writes `max_num_ref_frames = 3` *and* `max_dec_frame_buffering = 3`, a DPB exactly as deep as its reference count, so the sliding window unmarks the oldest reference in the very access unit whose bump evicts it.
**Three tests turned out to be incapable of failing**, each guarding the exact defect that shipped:
1. `pf-vkdecode` **asserted the aliasing as "the planner's normal behaviour"** — it encoded the bug.
2. `pf-dxvadec` carried a vacuous predicate that compared an expression with itself.
3. `pf-vaadec` asserted the decode target was never a reference **while giving every picture a unique surface id** — distinct integers cannot collide.
Every fix here now carries a counterfactual that makes the bad ordering happen and proves it is caught.
## New permanent gates
Host-shaped fixtures, because conformance vectors demonstrably cannot see this class of bug. All goldens cross-checked on two independent ffmpeg builds on two architectures.
- `lowdelay-640x480.h264` (282 KB) — the stream that exposed it
- `lowdelay-640x480.h265` (270 KB) — HEVC's exemption held, 0/120, with a counterfactual that makes it 115/120
- `lowdelay-3840x2160.ivf.av1` (261 KB) — 4K is mandatory: 720p/1080p/1440p all measure a single tile. Which surfaced that **every AV1 frame either rung had ever been measured against was single-tile**, so the tile arrays had only ever been written at index 0.
`gpu_parity` goes 8 legs → 11. D3D11VA parity goes 5 → 8.
## VAAPI: from unmeasured to parity-proven
That rung had never had a pixel checked. It now has a `#[cfg(test)]` readback (`vaDeriveImage`, falling back to `vaCreateImage`+`vaGetImage`) and **all seven legs are bit-identical to libavcodec** on RDNA3.
The readback is kept off the production path *structurally* — the entry points are resolved by a test-only type that dlopens libva itself, the production `Libva` gains no field, and a CPU test scans the file's own source to keep it that way. Zero-copy on the real path is unchanged.
It also stopped dropping frames: `finish` showed only `outputs.last()` and never flushed, so it delivered 225/204/45 of 250/250/50. Now 250/250/50. Two defects fell out of that: `color` and `display` were stamped from the wrong access unit too (that is the in-band HDR switch), and concealment was about to start draining the queue, which would have zeroed the demotion streak and removed the ladder's escape hatch.
**VAAPI stays `verified = false` on purpose.** Parity is no longer the gap — one vendor and no soak are. And flipping it is a *routing* change: `native_rung_admitted` would put VAAPI ahead of Vulkan Video on every Linux AMD/Intel client, the Steam Deck included. That deserves its own decision.
## Also in here
The six original follow-ups: the H.264 DPB level-ceiling cliff named and guarded (`PlanWarning::LevelDerivedDpb`); the CPU rung's process-isolation question decided and written down with its revisit trigger; the AV1 tile-aware sub-frame reader closed on measurement rather than left open; and the D3D11VA AV1 rung promoted to `verified` on two vendors.
## Verification
Every gate run on the merged tree, not per-branch.
- **`.21`** — fmt, `clippy --workspace --all-targets -D warnings`, all unit suites, **`gpu_parity` 11/11**
- **`.221`** — **8/8 D3D11VA parity legs on both GPUs**
- **`.25`** — **39 tests, 0 failed, 0 ignored**, every VAAPI parity leg
Each fix was also re-verified independently against pre-fix source: the new H.264 leg fails 117/120 at frame 3 without it, and the VAAPI harness was proved falsifiable by planting a real pitch defect.
⚠ Known and recorded, not fixed here: VAAPI is one vendor; rav1d is still not panic-proof (539 abort sites reachable across a C ABI); `pp.width` sends UpscaledWidth where libavcodec sends FrameWidth, identical only while superres is off.
H.264 derives its DPB size the same way HEVC did before #96 — from a level
ceiling that says what a stream MAY use, not what it needs — and the ceiling
saturates at 16 frames, which is 17 hardware slots with the picture in flight.
That is the exact arithmetic that cost 720p and 1080p their HEVC.
Measured on real encoders (2026-08-07) rather than assumed: H.264 escapes it
twice over, and both escapes belong to the encoders, not to the format.
encoder level picked VUI restriction
NVENC (RTX 5070 Ti, 610.57.04) 3.2/4.2/5.1/5.2 present, buffering 3
VAAPI via libavcodec (RDNA3, 26.0.3) 4.1/4.2/5.1/5.2 present, buffering 1
openh264 (the software rung) 3.2/4.2/5.1/5.2 present, buffering 1
Every one picks a level proportionate to the picture AND states its real need
in the VUI bitstream restriction, so the ceiling is never reached and never
consulted. Nothing is broken today, and clamping would be wrong: with the
restriction present the number IS the stream's own statement, and a stream that
genuinely asked for a deep DPB would decode wrong if we shrank it.
So this does not change what any stream decodes. It gives the arithmetic one
named home (`dpb_limit`, the twin of `h265::dpb_limit`) carrying the evidence
and the reasoning, and it adds the signal that was missing: when an SPS carries
no restriction AND its level ceiling would demand more slots than mainstream
hardware provides, the plan now says so with `PlanWarning::LevelDerivedDpb`
instead of a user silently losing the codec the way #96's users silently lost
HEVC. It is not an integrity warning — the picture is intact; what fails is
opening a session — so `is_integrity_warning` classifies it false.
One thing the sweep corrects about how the follow-up was framed: it is SMALL
pictures that saturate the ceiling most easily, not 720p specifically. 640x360
at level 3.1 computes 16 as readily as 720p at level 5.0, because the ceiling
is MaxDpbMbs divided by the picture's macroblocks. The authored 64x64 test
fixtures land there too, which is why they now assert through `picture_warnings`.
Guards, as the missing consumer-end half of pf-encode's
`rfi_dpb_fits_a_mainstream_vulkan_decoder`:
* every_reachable_h264_stream_fits_a_mainstream_slot_pool — the measured
(picture, level, declaration) pairs, asserting slots <= 16
* the_level_ceiling_alone_would_reproduce_96_and_is_warned_about — the same
resolutions at levels that saturate, pinned WITH the warning
* a_proportionate_level_fits_even_without_a_vui_restriction — so neither
escape looks like it is doing all the work alone
Gates: fmt + clippy -D warnings clean; pf-client-core 167/167; pf-bitstream
84/84; and gpu_parity 8/8 bit-identical to libavcodec on the RTX 5070 Ti, which
is the gate that matters for anything touching the bitstream layer.
#97's frame-context floor closes the one rav1d abort we hit and can prove. It
does not make the rung panic-proof and nothing at that call site can, because
rav1d's public surface is dav1d's C ABI: any reachable panic crosses
`extern "C"` as `panic_cannot_unwind` and becomes `abort()`, past every
`catch_unwind`, rung demotion and typed refusal we have.
Counted across rav1d 1.1.0's 60 source files: 285 `unwrap()`, 214 `assert!`,
19 `unreachable!`, 11 `expect()`, 10 `panic!`. 539 sites that end the client if
a stream can reach them. #97 fixed one of them.
Process isolation is the only defence that actually works, and this records the
decision NOT to build it, with the reasoning, so it is not re-argued from
scratch each time someone reads that number:
* the defect is upstream's and is one line (memorysafety/rav1d#1497, filed
2026-08-07 with the fix and a reproducer; still open, no PR, as of today);
* 539 is an unbounded number, not a risk estimate — none of those sites is
known reachable from a punktfunk stream, and the honest next step is to
fuzz the rung and find out, which is cheap, rather than buy insurance,
which is not;
* the cost lands on the video path across Linux, Windows and Android (the
Apple clients decode through VideoToolbox and never reach this code), each
needing its own shared-memory frame transport, child lifecycle and
backpressure, and it adds a scheduling boundary to the slowest rung on the
ladder while zero-copy is a hard requirement;
* an abort here costs a session that was already degraded — this rung exists
because the GPU rungs failed first.
The trigger to revisit is named as an event rather than a feeling: a SECOND
distinct abort in the field, or a fuzzer finding a reachable panic. Either
makes it a class of bugs instead of one, and a class is what would justify the
architecture.
Documentation only — no behaviour change.
#95 disarmed sub-frame readback for AV1, which means AV1 forgoes the latency
win HEVC gets from shipping slice 1 while slice 2 encodes. The follow-up was to
teach the reader AV1's units: cut on OBU boundaries rather than byte counts and
arm from the driver's reported unit count. Measured on .21 (RTX 5070 Ti,
av1_nvenc) before writing any of it, and the measurement closes it rather than
scoping it.
Reading the frame headers av1_nvenc actually emits at 4K:
width_in_sbs_minus_1[0] = 59 one tile column, the full 3840
height_in_sbs_minus_1[0..1] = 16, 16 two tile rows
tile_start_and_end_present_flag = 0 BOTH TILES IN ONE TILE GROUP OBU
That last flag is the finding. "Cut on OBU boundaries" presumes the tiles are
separate OBUs and they are not — there is no boundary between them to cut on.
Shipping tile 1 early would need the HOST to re-author AV1 syntax per chunk,
synthesising a fresh Tile Group OBU header with tile_start_and_end_present_flag
= 1 and its own tg_start/tg_end. That is bitstream surgery on the encode path,
not the reader change it was assumed to be.
And the prize would be small even then, because split encode already spent it.
The two tile rows go to two split-encode engines that run CONCURRENTLY, so they
complete at nearly the same moment — the win is bounded by the skew between
engines, not by half a frame. Whole-frame encode measures 3.3-3.6 ms at 4K60
against a 16.7 ms p50 end-to-end, so even the sequential-tiles fantasy caps near
1.7 ms and the real number is a fraction of it. HEVC's win is bigger for a
structural reason that does not transfer: forced split and sub-frame are
mutually unsupported, so HEVC's slices genuinely are produced one after another.
1080p settles it further: tile_cols_log2 = tile_rows_log2 = 0, a single tile, so
there is nothing to pipeline at the commonest streaming resolution at all.
Recorded next to the disarm with the reopen condition named — NVENC emitting one
OBU per tile, or setting tile_start_and_end_present_flag = 1 — so this is closed
on evidence rather than left as an open maybe.
Documentation only — no behaviour change.
Re-measured against a host carrying #95, from .21 (RTX 5070 Ti, av1_nvenc) to
.221, on glass:
Intel Arc, auto -> native-d3d11va 4K60, decode 1.4 ms, e2e 16.7 ms p50
RTX 3500 Ada, pinned native-d3d11va 4K60, decode 1.0 ms
RTX 3500 Ada, pinned native-vulkan 4K60, decode 11.6-16.7 ms
Plus a 5-minute Arc soak: 297 stats lines, 60 fps, decode 1.3 ms, e2e 10.9/14.8 ms
p50, and exactly one WARN in the whole run — the hardware_verified=false notice
itself. No refusals, no demotions, no concealed runs.
Three things that follow.
The rung is no longer a one-session curiosity: it decodes 4K60 AV1 on TWO
vendors and survives a soak. The Arc leg matters twice over, because the Arc
advertises no SAMPLED usage on any decode profile — zero-copy Vulkan Video
cannot work there — so `auto` demoting to D3D11VA and then decoding is the
whole demotion path working as designed.
It is roughly 10x faster than the Vulkan AV1 leg on the SAME NVIDIA GPU. That
is the strongest argument yet for eventually letting `auto` pick it ahead of
Vulkan Video, which is exactly what `verified` gates.
And it stays `verified = false` anyway, because the missing piece is specific:
there is no frame-hash parity against libavcodec. Every other verified pair in
that table earned it with one, and pf-dxvadec has no harness that could produce
one — `libav_picparams_parity` compares picture parameters on the CPU and never
decodes a frame. Building that harness is the work that promotes this rung; a
fourth session is not. The evidence string now says so, so the next reader does
not have to rediscover which half is missing.
The VAAPI row is corrected in the same spirit rather than left as a bare "NO":
the reachable VAAPI box (.25, RDNA3) reports VAProfileAV1Profile0 /
VAEntrypointVLD and advertises no Vulkan AV1 decode at all, which makes it the
right box to prove that rung on and an unambiguous oracle when it happens. What
stopped it is recorded too — no punktfunk checkout there and 4 GB of usable RAM.
Documentation only — no behaviour change, and no flag flipped.
The evidence table has said "native VAAPI: has never decoded a frame anywhere
(M6/M7)" since the rung was written. That is no longer true. Measured on `.25`
(Radeon 780M / Phoenix1 RDNA3, radeonsi, Mesa 26.0.3, VA-API 1.23, Ubuntu
26.04 — headless, no display server needed):
VAAPI AV1 rung constructed: native-vaapi av1
VAAPI AV1: 250 frames delivered, first 320x240 fourcc="NV12"
modifier=0x200000010401b04
250 of 250 displayed frames, first try, on the same vendored vector the Vulkan
and D3D11VA AV1 legs walk. The count matters as more than a smoke test: the
vector carries 274 coded frames in 250 temporal units — 24 units carry two, and
those extras are HIDDEN (decoded, referenced, never shown) — so 250 delivered is
this rung agreeing with the other two about which frames are output. A tiled AMD
DRM modifier rather than a linear one says the surface is a real decode target,
not a fallback.
Two changes, both in the rung's own file.
**The probe never asked about AV1.** `probe_this_machines_libva` walked H.264
High, HEVC Main and HEVC Main 10 and stopped there, which is part of why "never
decoded a frame" could stand so long without anyone noticing what had not been
asked. It now covers both AV1 profiles, and this box answers:
H.264 High: VLD decode AV1 Profile 0: VLD decode
HEVC Main: VLD decode AV1 Profile 1: no (VAProfile not supported)
Profile 1 being refused is correct — 4:4:4 AV1, which radeonsi does not do — and
it is the negative case that proves the probe reports rather than assumes.
**`av1_decodes_the_vendored_vector_on_this_machines_vaapi`** is the decode
itself, `#[ignore]`d beside the probe.
It is deliberately WEAKER than the Vulkan and D3D11VA AV1 legs, and the docs say
so rather than letting the name imply parity: those two hash every frame against
libavcodec's goldens because both can read their decoded surface back. This rung
hands out a DRM-PRIME dmabuf whose memory the driver tiles, so there is no
CPU-readable image to hash without adding a vaDeriveImage/vaGetImage path that
production neither uses nor wants. So it asserts what can be asserted honestly —
every temporal unit accepted, the right number of frames back, each a real
exported surface of the right shape, the first flagged as a keyframe — and it is
NOT frame-hash parity. Promoting this rung to `verified` still wants parity, and
parity wants a readback path first.
It fails loudly rather than skipping when the device has no AV1 entry point. It
is `#[ignore]`d, so it only runs when someone points it at a box that is supposed
to have one, and a silent pass there is exactly the invisible-failure mode this
program exists to end.
Gates: on `.25`, fmt clean, `clippy -p pf-client-core --all-targets -D warnings`
green under the Linux cfg where this rung actually compiles, the whole lib suite
167/167, and all 11 VAAPI tests green with `--include-ignored`. Workspace fmt +
clippy + lib suite also green in the Linux container.
⚠ Not touched here on purpose: the evidence table in `video.rs`. Its VAAPI row
still reads "never decoded a frame anywhere" and now understates what is known —
but a parallel agent is editing that same file for the D3D11VA AV1 row, so the
row is left for whoever lands second to update once, rather than conflicting.
Note for anyone reproducing on `.25`: it has no system SDL3 and no passwordless
sudo, so the test binary links only with `--features sdl3/build-from-source`
(SDL3 is gamepads, irrelevant to decode; production Linux still links the system
one). Its disk sits at ~99% full, and the tree there is a `git archive` export
with no `.git`, so `git apply`/`git checkout --` silently do nothing.
The follow-up was framed as "build the frame-hash parity harness the D3D11VA AV1
rung is missing, then flip hardware_verified to true". Both halves were wrong.
The harness was never missing. `video_d3d11_native`'s `parity` module has carried
`av1_every_delivered_frame_hashes_bit_identical_to_libavcodec` since M7 wired the
rung — wired to the SAME libavcodec goldens the Vulkan AV1 leg passes against,
with the display-order model that handles the vector's 24 hidden frames, sitting
`#[ignore]`d beside the H.264/H.265/Main10 legs. It had simply never been run on a
device; .173 was powered off the day it was written. What the old evidence note
called a missing harness is real about pf-dxvadec the CRATE, which cannot host one
— it links no D3D11 — but the device half lives here and was already done.
Run on .221, it FAILS, on both GPUs, deterministically (three runs each, identical
first-divergent frame and identical hashes): 186/250 diverging display frames on an
RTX 3500 Ada, 245/250 on an Intel Arc.
It is the decode that is wrong, not the measurement, and three independent checks
say so. H.264 and H.265 pass 250/250 and HEVC Main 10 50/50 through the same
harness, the same readback geometry, the same crop and the same slot map on those
same two GPUs. pf-vkdecode's Vulkan AV1 leg reproduces the same golden file 250/250
on the same box. And the goldens regenerate byte-for-byte from the ffmpeg build
their own header names.
Two signatures, and they are not one defect wearing two faces. NVIDIA is bit-exact
for display frames 0..=63 and then loses ONE 16x24 luma block — 174 pixels, max
|delta| 8, chroma untouched — on the frame whose order_hint first reaches 64, after
which every remaining frame is downstream of it through prediction. The stream
parks the key frame (order_hint 0) in BWDREF and ALTREF2 for its whole length, so
64 is where the distance to it reaches the edge of what get_relative_dist can
represent at OrderHintBits = 7. Intel is structurally wrong from display frame 4 —
47% of luma, max |delta| 242, chroma wrong too, a frame predicted from the wrong
picture — and the only later frame it gets right is the one whose primary_ref_frame
is PRIMARY_REF_NONE.
None of this is visible on glass, which is the whole argument for goldens: the rung
streams 4K60 on both parts with a clean five-minute soak at roughly ten times the
Vulkan leg's decode time. The 2026-08-07 field sessions that looked clean were
looking at wrong pixels.
So hardware_verified stays false, and the note now says why in the strongest
available terms — it prints at warn on every session that lands here, and "decodes
AV1 to wrong pixels" is what a support engineer needs to read. The pair stays in
`every_rung_runs_and_the_unproven_ones_are_named`'s unproven array; its note still
contains NEVER, because the pair has never PASSED parity, which is now a measured
statement rather than an absence.
Left deliberately unchanged: `auto` on Windows can still reach this rung for AV1,
and on Intel it is the arm that fires, because that vendor advertises no SAMPLED
usage on any decode profile so zero-copy Vulkan Video cannot run there. Barring it
trades visibly-wrong AV1 for the software rung, which cannot keep up at 4K and is
itself unproven. Which way that trade goes is a product call, so it is recorded at
the admission site rather than made silently here.
`av1_divergence_map` is kept, cleaned up and documented: it is what turned "186
frames differ" into a lead — one line per display frame, its verdict beside the
plan facts that could explain it, and an opt-in raw-NV12 dump. At a frame where one
vendor hashes correctly, that vendor's bytes ARE libavcodec's bytes and so a valid
reference for the other's, which is how "how badly" was answered without new
goldens. The tool that would localise the rest does not exist: pf-dxvadec's
libav_picparams_parity covers H.264 and HEVC only, so the AV1 conversion has never
been compared against libavcodec at the picture-parameter level either. That is the
next step, not another session.
Also in this file, since it is the same table and the same day: the VAAPI rung's
AV1 leg has now decoded 250/250 of the vendored vector on RDNA3 and its arm is
split from the H.264/H.265 ones, which genuinely have still never decoded anything.
It is unverified for the same reason as ever — no parity — and the D3D11VA row
above is exactly why that distinction is worth keeping: a rung can decode 250
frames and still be wrong.
AV1 applies `refresh_frame_flags` AFTER the frame is decoded (7.20), so a frame
that reads a reference slot and then overwrites it is the ORDINARY case, not an
exotic one: 268 of the vendored vector's 274 frames do it, first at frame 6.
`plan_to_dxva_av1` released every displaced picture inside the conversion — which
is what the H.264 and H.265 siblings do with their whole `removed` list — and then
assigned the decode target a slot. `SlotMap::assign` takes the lowest free slot,
and the lowest free slot is the one just vacated. So the submission said
`CurrPicTextureIndex = N` and `RefFrameMapTextureIndex[k] = N` in the same breath,
on 268 of 274 frames: decode into the surface you predict from.
Neither vendored H.264 nor H.265 vector ever produces that shape (measured: zero
on 250 AUs), which is why an eager release survived two hardware-proven codecs and
opened on the first AV1 frame past the key frame's neighbourhood. HEVC even has
the invariant under test already — `the_current_picture_is_named_by_curr_pic_and_
never_aliases_a_reference` — and AV1 had nothing.
The Vulkan rung already carries the fix; this is the same contract, and the DXVA
constraint is the STRICTER of the two: Vulkan binds only the references a frame
names, while `RefFrameMapTextureIndex` declares the whole store, so every picture
the store still names has to survive the conversion. `DecodePlanDxvaAv1` grows
`release_after_decode` and `frame_av1` applies it once the decode op is issued —
next to the `refresh_frame_flags == 0` release that already waits for the same
reason. Peak surfaces held goes 7 of the 9 the pool allocates, so the spare slot
`SlotMap::new` adds is doing exactly the job it exists for.
Measured on hardware before the fix: Intel Arc got 245 of 250 delivered frames
wrong — 47% of luma at the first bad frame, max |delta| 242, chroma wrong too, a
frame predicted from the wrong picture — and the only late frame it got right was
the one intra frame, which names no reference and so could not alias. That reads
as a `primary_ref_frame` defect and is not one: PRIMARY_REF_NONE and "has no
references to alias" are the same frames.
Two halves.
**The harness.** `libav_picparams_parity` covered H.264 and HEVC only, which is
exactly the gap that let a wrong AV1 submission ship. It now plans, converts and
packs all 274 frames of the vendored AV1 vector and checks what needs no capture:
the three-buffer descriptor set with no quantization matrix (AV1's matrices are
selected by index, so `dxva2_av1_end_frame` passes NULL/0 and there is no buffer
to submit), no macroblock count anywhere, the 912-byte picture-parameter buffer,
and the tile records — which unlike H.264/HEVC slice records do NOT abut, because
a `DXVA_Tile_AV1` addresses a tile PAYLOAD and consecutive payloads are separated
by their `tile_size_minus_1` fields.
The one that matters most is `no_av1_submission_names_its_decode_surface_in_the_
reference_store`: the invariant the previous commit fixed, over the submitted
BYTES rather than over the plan. libavcodec cannot produce that shape — it fills
`RefFrameMapTextureIndex` from the pre-refresh store and takes
`CurrPicTextureIndex` from a frame the reference update has not run on — which is
the argument for calling it a defect rather than a convention.
`AV1_FIELDS` reaches into the eight nested blocks (`tiles.widths`,
`segmentation.feature_data`, …) so a future capture reports a field and not "260
bytes of tiles differ"; `field_table!` grew nested-path support for it. The
`#[ignore]`d `our_av1_picture_parameters_match_libavcodecs` and the capture recipe
are in place, and `the_dump_and_the_parser_agree…` now self-compares AV1 too.
⚠ NO libavcodec AV1 capture was taken and the module docs say so rather than
leaving an absent result to be read as a pass: `.221` has no MSYS2, no gcc and no
make, so a patched FFmpeg there is a toolchain bring-up, not a build. Everything
this file claims about libavcodec's AV1 side is READ out of `dxva2_av1.c` (n8.1).
That reading did turn up one live divergence, recorded at `pic_av1.rs`'s
`pp.width` and deliberately NOT changed: libavcodec sends `avctx->width`, which is
FrameWidth (pre-superres), where this crate sends UpscaledWidth. The two are equal
whenever superres is off, which is every stream that exists here, so the 250/250
result says nothing either way and a blind change would be unmeasured.
**The promotion.** `(D3d11va, CODEC_AV1)` is `verified` — 250/250 delivered frames
bit-identical to libavcodec on an RTX 3500 Ada AND an Intel Arc. All three places
move together: the evidence arm, the module table and
`every_rung_runs_and_the_unproven_ones_are_named`, whose `unproven` array loses the
pair and whose proven list gains it.
⚠ This changes rung SELECTION, not just a label. `verified` is what lets `auto`
pick D3D11VA ahead of Vulkan Video, so Windows Intel and unknown-vendor boxes —
where the ladder is `native-d3d11va → native-vk → sw` — now decode AV1 on D3D11VA
where they previously fell to Vulkan. Taken deliberately: ~10x the Vulkan leg's
speed, and the parity that promoted it was measured on an Intel Arc, which is the
vendor family the change moves. Still no soak on the goldens, and the notes say so.
Also: `frame_av1` holds the decode's `Result` instead of `?`-ing it, so both slot
releases run on the failure path. `decode_av1` notes an error and keeps the
session rather than rebuilding the slot map, so an early return leaked a surface
per failed frame and hit `SlotError::Full` after nine.
The evidence table said these legs "have still never decoded a frame anywhere",
and VAAPI is the rung every Linux AMD/Intel client lands on. They have now
decoded, on `.25` (Radeon 780M / Phoenix1, RDNA3, radeonsi, Mesa 26.0.3, VA-API
1.23, /dev/dri/renderD128):
H.264 250/250 access units accepted, 225 frames delivered, NV12
H.265 250/250 accepted, 204 delivered, NV12
HEVC Main 10 50/50 accepted, 45 delivered, P010
(AV1, unchanged: 250/250 accepted, 250 delivered, NV12)
all on the same tiled AMD modifier (0x200000010401b04). Not one access unit of
any vector was refused.
Three `#[ignore]`d legs modelled on the AV1 one, plus the Annex-B access-unit
splitters they need — ported verbatim from `video_d3d11_native`'s test module so
the two platform rungs are driven over the same access units rather than over two
splitters free to disagree. Main 10 earns a third leg rather than a variation on
the second: ten bits is a different VAAPI profile, a different render-target
format and a different surface fourcc, and that leg's fourcc assertion is the
only thing that would catch a driver quietly handing back NV12 for a ten-bit
stream.
This is NOT frame-hash parity, and the doc comments say so rather than letting
the test names imply it. The Vulkan and D3D11VA legs hash every frame against
libavcodec because both can read their decoded surface back; this rung exports a
DRM-PRIME dmabuf whose memory the driver tiles, so there is no CPU-readable image
to hash without a `vaDeriveImage`/`vaGetImage` path production neither uses nor
wants. What these legs prove is that every access unit is accepted, that the
expected number of frames comes back, and that each one is a real exported
surface of the right shape and fourcc — enough to turn "never decoded a frame
anywhere" into a measurement, not enough to promote the rung to `verified`.
Two findings the run surfaced, neither of which bites punktfunk's own streams:
* The delivered counts are 225/204/45, not 250/250/50, and that is the RUNG, not
the driver. `finish` shows `outputs.last()` and never more, so an access unit
whose plan bumps several pictures out of the DPB displays the last and drops
the rest — 18 dropped at the H.264 vector's three draining IDRs, 45 on the
H.265 vector's 45 two-picture bumps — and there is no end-of-stream flush.
Hosts emit zero-reorder low-delay output with no B pictures, so `outputs` never
holds more than one picture in the field. A CPU-only test derives all three
counts from the planner alone, on any Linux box with no GPU, so they stay
explanations rather than recordings.
* `DmabufFrame::keyframe` labels the ACCESS UNIT, not the picture delivered:
`finish` is handed the current AU's `is_idr`. On a reordering stream the IDR is
bumped out several access units after it decoded and arrives flagged `false`,
while the access unit that drains the DPB at a later IDR flags whichever old
picture it displays as a keyframe. That flag is `DecodedImage::is_keyframe`,
the pump's post-loss re-anchor signal. Asserted so that fixing it is noticed,
not so that it is preserved.
Gates, all run on `.25` (this rung only compiles on Linux): `cargo fmt --all --
--check`; `cargo clippy -p pf-client-core --all-targets --features
sdl3/build-from-source -- -D warnings`; `cargo test -p pf-client-core --lib
--features sdl3/build-from-source` (169 passed); the same filtered to
video_vaapi_native with `--include-ignored` (16 passed). Plus the pf-lxcheck2
container's workspace-wide `cargo fmt --all -- --check` and `cargo clippy
--workspace --all-targets -- -D warnings`, both clean.
The evidence table in `video.rs` still says these legs have never decoded a
frame. It is being edited concurrently, so its replacement row is handed over
rather than raced for here.
`the_evidence_table_says_exactly_which_rungs_have_run_on_hardware` asserts the
same fact a third way — a proven list and a NOT-proven list, both spelled out —
so promoting the rung in the three places the handoff named still left a test
saying "the DXVA AV1 leg FAILS parity on two GPUs — claiming otherwise is the
dishonesty this program must not ship". It was right to fail; the pair moves
lists here.
Three prose sites that still described the leg as decoding wrong pixels move
with it: `native_supports_av1`'s device-facts note, `log_rung`'s honesty-surface
docs, and the OPEN question in the Windows Intel arm of `pick_native` — that last
one is marked CLOSED rather than deleted, because the question it raised (the
evidence filter asks "any evidence", and has no answer for BAD evidence) is a
real gap in the rule that outlived this particular leg.
Five findings from the adversarial pass, all real.
**The deferral predicate was vacuous.** `plan.dpb.removed` is ALWAYS a subset of
`plan.dpb_refs`: `Av1Planner::plan_frame` snapshots `dpb_refs` before any mutation
and `refresh_slots` can only report a picture that was in `self.slots` at that
moment. So `filter(|id| dpb_refs.contains(id))` was a condition that is never
false, the eager-release loop beside it could never release anything, and the test
assertion "only a picture the submission points at earns the reprieve" could never
fire. Now: defer every removal, say why in terms of the planner, and assert the
PLANNER's property (`removed ⊆ dpb_refs`) — which is falsifiable, and whose failure
would mean the conversion is releasing a surface `ref_frame_map` points at.
**The failure-path claim was overstated.** Holding the decode's `Result` closes
this frame's leak, not the unit's: `decode_av1` returns on the first failing frame
and abandons the rest of the temporal unit's plans, so their removals are never
released. 24 of 250 units carry a second frame. Named rather than fixed — what to
do with the frames after a failure is the pump's question.
**⚠⚠ The H.264 leg plausibly has the same defect, and the comment this change added
said it could not.** `pic.rs` builds `RefFrameList` from `plan.dpb_refs`, and
`H264Planner` snapshots that in `begin_picture` — BEFORE 8.2.5 marking and the DPB
bump. The vendored bump drops a picture the sliding window just unmarked once it
has been output, so a picture can land in both `RefFrameList` and `dpb.removed`:
the AV1 aliasing shape exactly. Measured zero on the vendored vector — but that
vector REORDERS, which is precisely what keeps an unmarked picture alive past the
AU that unmarked it. A punktfunk host emits LOW-DELAY H.264, where output happens
as each picture is decoded, which is the condition that makes eviction and
unmarking land in the same access unit. Traced end to end in source, not
reproduced (no low-delay vector). NOT fixed: changing a hardware-proven codec on an
unreproduced suspicion is the worse risk two commits before a release. Instead
`no_au_removes_a_picture_its_own_reference_list_names` makes the assumption
falsifiable, and its message says what to do when it fires. HEVC is structurally
safe and now says why: `H265Planner` snapshots `dpb_refs` AFTER `decode_rps`.
**Four more stale promotion sites**, past the four already fixed: `Backend::
NativeD3d11va`'s variant doc, `Decoder::new`'s Windows rung comment, `lib.rs`'s
module note and `clients/session/README.md`. Two sites that used the AV1 leg as
the live EXAMPLE of an unproven rung are marked as expired rather than deleted —
the reasoning is what the next bad-evidence leg will need.
**The AV1 dump was missing.** `PF_DXVA_DUMP` wrote h264 and hevc only, for the one
codec whose libavcodec capture has never been taken and where the dump is
therefore the only tool.
The `damaged` path has cleared `Session::held[setup_slot]` since M7, for a reason
that now applies to the failure path too: the slot map says the surface holds THIS
picture while the surface still carries whatever the previous occupant decoded, so
a later `show_existing_frame` naming it blits the old picture's pixels with the old
picture's geometry and colour. The failure path never reached that far before —
`decode_into`'s error returned straight out of `frame_av1` — and the previous commit
made it continue so the slot releases could run.
The AV1 review round flagged the H.264 leg as "plausibly the same defect, traced in
source, not reproduced" and deliberately did not touch it. It is reproduced now, and
it is worse than the AV1 one: it fires on 297 of 300 access units of every stream a
punktfunk host emits, at 720p, 1080p and 2160p alike, on BOTH the DXVA rung and the
Vulkan one.
**Decided on the CPU, no GPU needed.** `H264Planner` snapshots `dpb_refs` in
`begin_picture`, BEFORE `finish_picture` runs 8.2.5's marking and C.4.5.3's bump, so a
picture the sliding window unmarks and the bump then evicts lands in both `dpb_refs`
(which `RefFrameList` is built from) and `dpb.removed`. The conversion released the
whole `removed` list and then assigned the decode target a slot; `SlotMap::assign`
takes the lowest free slot, which is the one just vacated. `CurrPic = N` and
`RefFrameList[k] = N`, in one submission.
The two conditions have to coincide in ONE access unit, and low-delay H.264 is exactly
what makes them: `max_num_reorder_frames = 0` means the evicted picture has already
been output, which is what makes it evictable at all. NVENC seals it by writing
`max_num_ref_frames = 3` ALONGSIDE `max_dec_frame_buffering = 3` — a DPB exactly as
deep as its reference count — so the window unmarks the oldest reference in the very
unit whose bump drops it. The aliased picture is `ref_idx 2` of a three-entry
`num_ref_idx_l0_active` list: addressable by any macroblock, not a spare.
**Why two hardware-proven codecs and four GPUs never saw it.** `test-25fps.h264` is
level 1.3 with no VUI `bitstream_restriction`, so `dpb_limit` falls back to A.3.1's
level ceiling and gives a 7-frame DPB against 2 reference frames — the window unmarks
two units before the bump can evict — and it REORDERS, which keeps an unmarked picture
alive past the unit that unmarked it. Two independent reasons, both properties of that
vector rather than of H.264. It measured zero and passed 250/250 throughout.
`data/lowdelay-640x480.h264` is vendored to close exactly that: our own host's output,
120 pictures, goldens from libavcodec cross-checked bit-identical across two ffmpeg
builds on two architectures.
**The fix is the AV1 fix.** `DecodePlanDxva` and `DecodePlanVk` grow
`release_after_decode`, the conversions hand the removals back instead of applying
them, and the callers release them once the decode op is issued. It costs no slot the
map does not have: `SlotMap::new` allocates `max_dpb_frames + 1` and the DPB never
exceeds `max_dpb_frames`, so a free slot always exists with the whole `removed` list
still held — measured, peak 4 of 4 on the stream that defers on 117 of 120 units.
The Vulkan rung breaks on it in both DPB modes and neither loudly: DISTINCT hands the
aliased reference the same array layer the setup writes; COINCIDE clears
`slot_image[setup]` in the binding sync and the reference then resolves to no bound
image, dropping out of `pReferenceSlots` with a `trace!`. Its deferred release runs on
the FAILURE paths too — the fallible region's Result is held rather than `?`-ed,
because seven exits sat between the conversion and the release and each would have
leaked a slot.
`a_full_dpb_bump_reuses_the_slot_but_the_pool_model_binds_a_fresh_image` asserted the
aliasing as "the planner's normal behaviour": an authored depth-1 stream whose AU1
references the picture it evicts. It now asserts the opposite, which is the defect in
two lines.
New evidence, all of it runnable: the CPU proof pins BOTH numbers (0 on the vector,
117 of 120 on the low-delay stream) so neither can drift silently; the ledger-pressure
test measures the peak; and a low-delay parity leg is added to `pf-vkdecode`'s
`gpu_parity` and `pf-client-core`'s `video_d3d11_native::parity` so both rungs are held
to what they stream rather than only to what they conform to.
Four findings, all real.
**`SlotMap`'s own docs had become false.** "feed it every `DpbUpdate` in decode order
(via `Self::apply` or `plan_to_vk`, which applies internally)" — `plan_to_vk` no longer
applies internally, which is the entire point of the change, and `release`'s docs named
it as one of the two things that may free a slot. A reader following those docs would
build the next caller wrong in exactly the way this commit's parent fixed. Both now say
which conversions defer, which one does not, and why H.265 is the one that does not.
**The deferred release warned on a legitimate event.** `release_deferred` warned per id
when a deferred release found no slot — but a renegotiation replaces the whole
`Session`, and with it the slot map, INSIDE `plan`, while the planner's own drain
reports every drained picture in that same access unit's `removed`. Every one of those
ids then misses, and nothing is wrong. `debug!`, with the legitimate cause named so the
illegitimate one stays diagnosable.
**HEVC's exemption was asserted only in its consequence.** `the_current_picture_is_
named_by_curr_pic_and_never_aliases_a_reference` checked that no reference shares the
decode target's slot — which on the vendored vector holds whether or not the reasoning
behind it does. That is precisely how the H.264 leg passed for two milestones. The test
now also asserts the PLANNER property the exemption rests on (`removed ∩ dpb_refs = ∅`,
falsified by moving `dpb_snapshot()` above `decode_rps`), and records that the low-delay
measurement was 0 of 300 against H.264's 297 of 300 from the same host and the same run.
It also records what is still missing: no low-delay HEVC stream is vendored, so HEVC's
freedom is a re-derivable argument plus one measurement, not a standing hardware leg.
**Two stale cross-references.** Both AV1 conversions told the reader the H.264/H.265
zero was "measured on reordering vectors and not a proof" — the open question this
commit's parent closed. They now say what the answer was.
`fd6241a2` made HEVC's freedom from the release-ordering defect falsifiable on CPU and
recorded what was still missing: no low-delay HEVC stream was vendored, so the exemption
rested on a structural argument plus one throwaway measurement. This vendors the stream,
and the exemption HELD.
**The fixture.** `lowdelay-640x480.h265`, 270 KB, 120 pictures — `punktfunk-host spike
--source synthetic --codec h265 --width 640 --height 480 --fps 60 --seconds 2 --bitrate 1`
on .21 (NVENC, RTX 5070 Ti, driver 610.57.04). Deliberately the H.264 sibling's resolution
and frame count: the two are then directly comparable, 640 and 480 are both multiples of
MinCbSizeY so there is no conformance window and a hash mismatch can only be decode rather
than readback geometry, and 270 KB sits alongside the 282 KB already accepted for H.264.
Goldens are libavcodec's software decode, cross-checked BIT-IDENTICAL across ffmpeg n8.1.2
(Arch, x86_64) and 8.1.1 (Homebrew, macOS arm64), 120 of 120 digests distinct.
**The exemption held, measured rather than argued.** `sps_max_dec_pic_buffering_minus1 = 4`
against the four pictures 8.3.2 keeps marked in steady state, `sps_max_num_reorder_pics = 0`,
`numRefL0 = 1` — a five-picture DPB filled exactly by four references plus the current
picture. 115 of the 120 access units retire a picture, and `removed ∩ dpb_refs` is **0 of
120**. A 300-picture 1080p stream from the same host reports the same shape: 295
retirements, 0 intersections. It is the encoder and not the resolution, exactly as for
H.264.
**A zero proves nothing on its own, so the fixture is pinned by its counterfactual.**
`test-25fps.h264` reported zero for two milestones while every stream we ship aliased on
99% of its frames. So the guarantee here is not "we looked and it was fine": hand
`plan_to_dxva_h265` the marked DPB as it stood BEFORE `decode_rps` — the mutation a
snapshot move would cause, reconstructed exactly as `dpb_refs(N-1) ∪ {stored(N-1)}` — and
the alias appears on **115 of 120** access units, driven through the real conversion rather
than through planner arithmetic. If a regeneration ever produced a stream that reordered,
or a DPB deeper than its reference count, that 115 collapses to 0 and the tests say so
instead of continuing to pass.
**The two rungs are exempt for different reasons, and the asymmetry is now a gate.** DXVA
binds the whole marked DPB — `RefPicList` is spec-defined that way, and an RFI long-term
anchor has to survive in it — so its exemption really is `H265Planner`'s snapshot ordering,
one call away from being untrue. `plan_to_vk_h265` never reads `dpb_refs` at all:
`pReferenceSlots` is the slots the operation uses, so it binds the current RPS sets, which
`decode_rps` itself derives and which therefore cannot name a picture that same RPS just
dropped. A new test feeds that conversion the identical widened snapshot and asserts
nothing changes, so a future change making the Vulkan rung bind the marked DPB — a
legitimate thing to want, since a *Foll* anchor invisible to the hardware is the RFI
failure shape — fails loudly instead of silently acquiring the defect.
What the Vulkan pixel leg adds is therefore NOT aliasing coverage, and its docs say so:
it is the first HEVC frame either rung has decoded from our own encoder, under a DPB that
retires and reissues a slot on 115 of 120 access units back to back, where the vendored
vector's reordering keeps that eviction slack.
Legs: `low_delay_host_h265_every_frame_hashes_bit_identical_to_libavcodec` on the Vulkan
rung (10 ignored legs now, up from 9) and on the D3D11VA rung, plus three non-ignored CPU
guards that run in ordinary CI.
Verified: 10/10 Vulkan parity legs on .21 (RTX 5070 Ti, 610.57.04), the new one 120/120
bit-identical; workspace clippy `-D warnings` and `cargo fmt --all --check` clean on .21.
The D3D11VA and Vulkan rungs both decoded into a surface they were predicting
from, on 117 of 120 access units of our own host's low-delay H.264 (`1c54d099`
for AV1, `834b2443` for H.264). `pf-vaadec` feeds `reference_frames` from the
same `plan.dpb_refs` snapshot, releases its whole `removed` list inline exactly
as the two broken conversions did, and neither fix commit touched it. It is
still exempt — this is the evidence, and the thing that keeps it true.
**Measured on the CPU, no GPU needed.** `walk_for_aliasing` drives the planner
and `plan_to_va` over both streams and counts four shapes. On
`lowdelay-640x480.h264` the aliasing PRECONDITION is fully present: 117 of 120
access units remove a picture their own `dpb_refs` still names, and on the same
117 the setup picture is handed the slot of a picture that access unit READS —
the D3D11VA/Vulkan defect verbatim, in this conversion, today. On the vendored
conformance vector both counts are 0, which is why that vector proved nothing
on two other backends for two milestones. Aliased submissions: **0 on both**.
**Why.** A slot is not a surface here. `plan_to_va` never invents one — every
reference it can name is read out of the `surfaces` table it is handed — and
the decode target is a separate parameter the caller takes from OUTSIDE that
table. `setup_surface` reaches the submission at exactly one field per codec
(H.264/H.265 `curr_pic.picture_id`, AV1 `current_frame` and
`current_display_picture`); HEVC is doubly safe, because its per-slice
`RefPicList` stores an INDEX into `reference_frames` rather than a surface.
AV1's documented substitution fallback is the one place the target can be named
as a reference, and only where the store resolved nothing at all to prefer.
**The exemption was incidental; it is structural now.** It needs the reference
table and the decode target to come from ONE snapshot of the bindings, and the
rung had that only by writing `free_surface()` and `surface_table()` adjacently
at three call sites. Split them and this rung acquires the defect exactly: the
table must be the PRE-removal one (that is where the references are), while a
free list consulted after the removals offers precisely the displaced picture's
surface. `Session::acquire_target` now returns the index, the surface and the
table together from `&self`, so a later edit cannot move one call and not the
other. No behaviour change: same order, same values, same refusal message.
Tests. `no_submission_names_its_decode_target_as_one_of_its_own_references`
(both streams, 0) with
`taking_the_decode_target_from_the_slot_table_aliases_on_the_low_delay_stream`
as the counterfactual that reproduces the defect on 117 of 120 — so the walk
demonstrably CAN see it when it is there.
`the_low_delay_stream_reassigns_slots_whose_pictures_it_still_reads` pins 0/250
and 117/120 so neither can drift silently.
`the_decode_target_can_never_be_a_surface_the_reference_table_names` sweeps
every binding state a 4-surface/3-slot pool can hold, and
`taking_the_free_surface_after_the_removals_would_hand_out_a_referenced_surface`
is the ordering counterfactual.
⚠ One existing test lost a VACUOUS half.
`the_setup_picture_routinely_inherits_a_just_freed_slot` asserted the decode
target was never also a reference while handing every picture its own
never-reused surface id — distinct integers cannot collide, so that assertion
could not fail whatever the conversion did. Its real measurement (225 of 250
access units reuse a just-freed slot, which is why the target is a parameter)
is kept; the collision half is gone, and the doc says where the question is
actually answered and why a recycling pool is what it takes to answer it.
Gates, run on `.25` (Radeon 780M, radeonsi, Mesa 26.0.3, VA-API 1.23), this
rung being Linux-only: `cargo fmt --all -- --check`; `cargo clippy -p
pf-client-core -p pf-vaadec --all-targets --features sdl3/build-from-source --
-D warnings`; `cargo test -p pf-client-core --lib --features
sdl3/build-from-source` (171 passed); the same filtered to `video_vaapi_native`
with `--include-ignored` (18 passed); `cargo test -p pf-vaadec` (48 passed).
Plus the pf-lxcheck2 container for the cross-platform half — fmt, clippy and
`cargo test -p pf-vaadec`, all clean.
All four VAAPI legs still decode with the refactor in place, not one access
unit refused: H.264 225 of 250 access units delivering a frame, H.265 204 of
250, HEVC Main 10 45 of 50 (P010), AV1 250 of 250 — the same counts and the
same tiled modifier 0x200000010401b04 those legs recorded before it. ⚠ The
H.26x legs live on `fix/vaapi-h264-h265-hardware-proof`, not on this branch, so
they were run by overlaying that commit's test module onto the scratch tree;
only the AV1 leg and the libva probe are reachable from here. This is a decode
measurement, not frame-hash parity — the rung exports a driver-tiled DRM-PRIME
dmabuf, so there is no CPU-readable image to hash. The alias assertions above
are the real evidence and they need no device.
⚠ NOT taken: `finish`'s `outputs.last()`, which ships one frame per access unit
and drops the rest of what a bump displaces (225/204/45 against 250/250/50),
with no end-of-stream flush. It cannot bite punktfunk — hosts emit zero-reorder
output, so `outputs` never holds more than one picture — and fixing it changes
`decode()`'s one-frame-per-access-unit contract with the pump (it wants a
deliverable queue, which `video_vk_native` already keeps) plus an end-of-stream
flush and the `keyframe`-labels-the-access-unit defect in the same function.
It is recorded and asserted on that other branch, whose three delivered-count
assertions any fix has to move in the same commit; doing that from here, blind
to them, would be worse than leaving it.
Every AV1 frame either decode rung has ever been measured against is `tile_cols =
tile_rows = 1`. The vendored vector is single-tile on all 274 of its frames, so every
tile array the conversions fill — `tiles.widths`, `tiles.heights`, the per-tile records
— had only ever been written at index 0, and a conversion that wrote tile 0 and left
the rest zero would pass the whole suite. Our encoder splits 4K into TWO TILE ROWS.
**The fixture.** `lowdelay-3840x2160.ivf.av1`, 261 KB, 60 frames — `punktfunk-host
spike --source synthetic --codec av1 --width 3840 --height 2160 --fps 60 --seconds 1
--bitrate 1` on .21 (NVENC, RTX 5070 Ti), wrapped to IVF with `ffmpeg -f obu … -c copy`
so `common::split_av1_aus` (the vendored parser's own `IvfIterator`) frames it exactly
as it frames the vector, with no second splitter that could disagree.
**4K is not a size choice, it is the only shape with the property.** Measured on the
same box with the same command: 1280x720, 1920x1080 and 2560x1440 all give `tile_cols =
tile_rows = 1`; 3840x2160 gives `tile_cols = 1, tile_rows = 2` with
`width_in_sbs_minus_1 = [59]`, `height_in_sbs_minus_1 = [16, 16]`, and both tiles in ONE
Tile Group OBU. 60 frames instead of 120 pays for the resolution: 261 KB, under both the
282 KB H.264 and 270 KB H.265 low-delay fixtures.
Goldens are libavcodec's software decode, cross-checked between ffmpeg n8.1.2 (Arch
x86_64, libdav1d) and 8.1.1 (Homebrew, macOS arm64, libdav1d) whose 746,496,000-byte raw
outputs are BYTE-IDENTICAL, not merely equal per frame. 60 of 60 digests distinct.
**AV1's frame accounting is asserted, never derived.** The vendored vector is 250
temporal units carrying 274 coded frames of which 24 are hidden; this stream is 60 units,
60 coded, 60 shown, 0 hidden, 0 `show_existing_frame`, 1 key frame. Neither is the
general case, so both parity harnesses now take units / decoded / shown as three
independent parameters instead of computing one from another, and the CPU guard states
all six numbers.
**A CPU gate that needed no hardware at all.** `pic_av1`'s new
`a_two_tile_frame_fills_both_row_entries_and_leaves_the_rest_zero` pins the second row
entry against its OWN `height_in_sbs_minus_1`, requires the two rows to tile the frame
exactly, and requires TWO tile RECORDS out of ONE tile group with rows (0,0) and (1,0) —
the transposition a square grid could never reveal — each spanning real bytes. The
existing one-tile test asserts index 0 is right and `1..` are zero, which a broken
multi-tile conversion also satisfies.
⚠⚠ **This is a file, and on AV1 that distinction has already cost a release.** "250/250
delivered frames bit-identical to libavcodec" was true for the entire period the host was
shipping only the FIRST TILE of every 4K frame: the verification ran against a vendored
file while the truncation lived in packetisation, and the suite stayed green throughout.
This fixture closes the multi-tile gap on the DECODE rungs and closes nothing about
fragmentation, reassembly, loss or AU boundaries — the golden header, both module docs
and the leg docs all say so, at length, so the next reader does not inherit the same
false confidence.
Legs: `low_delay_host_av1_every_frame_hashes_bit_identical_to_libavcodec` on the Vulkan
rung (11 ignored legs now) and on the D3D11VA rung, plus two non-ignored CPU tests.
Verified: 11/11 Vulkan parity legs on .21 (RTX 5070 Ti, 610.57.04), the new one 60/60
bit-identical; workspace clippy `-D warnings` and `cargo fmt --all --check` clean on .21.
The H.264 and H.265 rows still read "NEVER decoded a frame on any hardware".
That stopped being true on 2026-08-07, in the same session that proved AV1:
every access unit of the vendored H.264 (250), H.265 (250) and HEVC Main 10
(50) vectors was accepted on .25 (Radeon 780M, RDNA3, Mesa 26.0.3) with no
decode error — NV12 for the 8-bit legs, P010 for Main 10, all on the same tiled
AMD modifier — and probe_this_machines_libva reports VLD decode for all three
profiles.
The row records the delivered counts honestly rather than rounding them up:
225/204/45 against 250/250/50 access units, because `finish` shows
`outputs.last()` and drops the other pictures an AU bumps, and nothing flushes
the DPB at end of stream. That is this rung's own behaviour — D3D11VA delivers
all 250 — and it is invisible on punktfunk's zero-reorder host output. It is
recorded and asserted rather than fixed: changing the one-frame-per-AU contract
touches the pump's deliverable queue, an end-of-stream flush, and the
`keyframe`-labels-the-access-unit defect in the same function, so it belongs in
a commit that moves all three.
Still `verified = false` for all four, and the note says why in the words the
unproven-rung test requires: never frame-hash parity-checked. That is not
pedantry — the D3D11VA AV1 row two lines above is a rung that decoded 250
frames and produced wrong pixels for every one of them. Parity is what
distinguishes them, and this rung exports a tiled dmabuf with no CPU-readable
image, so it needs a readback path nothing has written yet.
`finish` showed `outputs.last()` and retired every other picture an access unit
bumped out of the DPB without ever displaying it, and nothing flushed the DPB at
end of stream. Measured on .25 against the vendored vectors: 225 of 250 frames
for H.264, 204 of 250 for H.265, 45 of 50 for HEVC Main 10. D3D11VA and Vulkan
deliver every frame, so this was the rung's alone. All four legs now deliver
250 / 250 / 50 / 250.
The same function carried a second defect. `DmabufFrame::keyframe` was stamped
with the CURRENT access unit's `is_idr`, not the flag of the picture it was
about to display, and on a reordering stream those are different pictures: the
IDR is bumped out several units after it decodes and arrived flagged `false` on
all three legs' first frame, while a later AU draining the DPB flagged some old
trailing picture as a keyframe. That field is `DecodedImage::is_keyframe`, the
pump's post-loss re-anchor signal, so a mislabel re-anchors on the wrong frame.
Three changes, all inside this rung:
* **A deliverable queue**, the same shape as `video_vk_native`'s — extend, ship
the front, trim the oldest past the bound, count and rate-limit the drops into
`DecodeHealth::dropped`. Its DEPTH is derived differently and the divergence is
documented: the Vulkan rung's bound is `HOLD_HEADROOM - PIPELINE_HOLD` = 1
because a queued frame there counts against the pool ON TOP of the DPB's own
residency. Here the three claims are disjoint and a bumped picture MOVES from
`pending`/slot to `held`, so the queue inherits the claim rather than adding
one. The bound is the DPB's depth — the deepest carry-over a bump can leave —
and the measured cost is at most one surface (zero on H.264, whose three
seven-picture IDR drains are the deepest bursts these vectors have). A bound of
1 would have left 235 of 250 on H.264, most of the defect still in place.
* **An end-of-stream flush.** This rung has no EOS signal and cannot have one:
the pump feeds access units until the session ends and then drops the decoder.
So `flush` has the two honest callers — `Drop`, where nothing can be presented
and the job is to release the queue's surfaces and the DPB's before the pool
goes, and a caller that KNOWS the stream ended, which today is the conformance
harness. One walk, not a production path and an untested teardown path. AV1
needs none: it shows at most one frame per temporal unit and buffers nothing,
which its 250/250 says out loud.
* **`PictureFacts` recorded when a picture decodes**, and read back when it is
displayed. `keyframe` was the defect; `color` and `display` are the same
mistake one field along — an in-band HDR switch changes the VUI mid-stream and
AV1's render region is per-frame, so a queued frame shown two units later would
have been drawn with the newest picture's signalling.
Concealment answers `Ok(None)` and deliberately does NOT drain the queue, which
is the Vulkan rung's order and is load-bearing: `clears_demotion_streak` is
`delivered || !concealed`, so shipping a queued frame on a concealed AU would
zero the streak and take away the escape hatch that stops a rung concealing
forever from holding a frozen picture.
The three delivered-count assertions moved with the fix, and so did the CPU
derivation that reproduces them without a GPU — it now simulates the whole
delivery model (ledger, queue, one-per-AU hand-off, flush) in the order `decode`
does it, and carries the old behaviour beside the new one as a counterfactual:
a queue bound of 0 with no flush still reproduces 225/204/45 exactly, and the
test fails if it ever stops being SHORT. `settle` was split out as the pure half
of `finish` so the claim walk, the display ordering and the picture facts are
all assertable with no device; `the_queue_never_needs_a_surface_the_pool_does_not_have`
runs the surface-lifetime arithmetic over the real vectors and pins the peak
claims (9 of a 16-surface pool on H.264, 8 of 14 on both HEVC vectors), with an
unbounded queue as the counterfactual that shows the bound doing its job.
Gates run: `cargo fmt --all -- --check`, `cargo clippy -p pf-client-core
-p pf-vaadec --all-targets --features sdl3/build-from-source -- -D warnings`,
`cargo test -p pf-client-core --lib --features sdl3/build-from-source` (176
pass), the same filtered to `video_vaapi_native -- --include-ignored` (23 pass,
0 ignored) and `cargo test -p pf-vaadec` (48 pass) — all on .25 (Radeon 780M,
RDNA3, radeonsi, Mesa 26.0.3, VA-API 1.23); plus `cargo fmt --all -- --check`
and `cargo clippy --workspace --all-targets -- -D warnings` in pf-lxcheck2.
Every other decode rung earns `verified` with frame-hash parity against
libavcodec. VAAPI could not: it hands out a DRM-PRIME dmabuf whose memory the
driver tiles, so nothing could read its decoded pixels back, and all four of its
legs sat at "never frame-hash parity-checked".
That was never bookkeeping. The D3D11VA AV1 rung decoded 250 frames, streamed
4K60 through a clean five-minute soak, and produced WRONG PIXELS for 186 of 250
frames on NVIDIA and 245 of 250 on Intel. It looked perfect on glass; only the
goldens caught it, and the same defect turned out to be in H.264 on two other
rungs. VAAPI was the one rung where that class of bug could still be sitting
with nothing able to see it.
It is not. Measured on .25 (Radeon 780M, RDNA3, radeonsi, Mesa 26.0.3, VA-API
1.23) on 2026-08-08, against the SAME golden files the Vulkan and D3D11VA rungs
are held to, read across the crate boundary rather than copied:
H.264 vendored vector 250/250 bit-identical (7 from the flush)
H.264 our host, low-delay 640x480 120/120 bit-identical (3 from the flush)
H.265 vendored vector 250/250 bit-identical (2 from the flush)
H.265 our host, low-delay 640x480 120/120 bit-identical (0 from the flush)
HEVC Main 10, P010 50/50 bit-identical (2 from the flush)
AV1 vendored vector 250/250 delivered of 274 decoded, and
display frame 0 byte-identical to
libavcodec's own PIXELS
AV1 our host, 4K two-tile 60/60 bit-identical
⚠ ONE vendor. AMD/radeonsi only; no Intel iHD box has run these legs.
The readback that made it possible:
* `pf-vaadec`'s `va` module gains `VAImage` and `VAImageFormat`, hand-declared
with every size and offset measured off libva 2.23.0's real headers by
`layout-probe.c` and pinned as compile-time assertions — the same discipline
the decode buffers already keep. The trap: `VAImage::width`/`height` are
16-bit, so `data_size` sits at 60 and not at the 64 counting 32-bit fields
gives, and every field after them is two bytes earlier than it looks.
* `pack_two_plane` is the pure geometry — the crop to the picture, the padding
columns dropped per row, and the chroma plane taken from the driver's OWN
`offsets[1]` rather than from `pitch * display_height`, which is the 1088-row
smear this program has already paid for once. It needs no device, so ten CPU
tests cover it on macOS and in the container.
* `video_vaapi_native::parity` drives the seven streams above through the
production entry point and hashes what the rung DELIVERS, in delivery order,
tail included — so the delivery path is under test as well as the decode, and
a frame's surface comes from its own release token rather than from an
inference about which pool entry holds which picture.
THE READBACK CANNOT REACH THE PRODUCTION PATH, and that is structural rather
than a promise. `vaDeriveImage`, `vaCreateImage`, `vaGetImage`, `vaMapBuffer`
and the rest are resolved by a `#[cfg(test)]` type that dlopens libva itself;
the production `Libva` gains no field; `sha2` is a dev dependency. A CPU test
scans this file's own source and fails if any of those symbols is dlsym'd
outside the harness, so a refactor cannot quietly undo it.
Derive is not guaranteed, so both routes are implemented and neither is
optional: `vaDeriveImage` first, `vaCreateImage` + `vaGetImage` as the fallback
(which also detiles), and if neither yields the pool's own fourcc the leg FAILS
naming what the driver gave it. There is no skip path — a parity test that
passes because it could not read anything is the failure mode this program has
been bitten by three times. Both answer on radeonsi, the first frame of every
leg is read through BOTH and they must agree, and `PF_VAAPI_READBACK=getimage`
reproduces the H.264 leg's 250/250 through the copying route alone, so the
fallback is exercised rather than merely written.
And it can fail — proven, not asserted. Planting the real geometry defect this
driver's layout makes visible (rows read contiguously, ignoring the 512-byte
pitch behind a 320-wide picture) fails at display frame 0 with the full
localisation: 68312 luma and 14998 chroma samples differing, max |delta| 255,
luma bounding box (0,1)..(319,239) — and with the goldens forced through one
route, 250/250 diverging with "suspect the readback geometry". `compare` and
`localise` also have CPU counterfactuals, and a hardware leg proves the readback
reads real and DISTINCT pixels and localises a one-byte flip to the exact pixel.
⚠ One thing the hardware legs do NOT cover, found by planting the other defect
and watching it do nothing: radeonsi's decode surfaces for every fixture here
have no VERTICAL padding — `offsets[1]` is exactly `pitch * height` — so the
chroma-plane trap is untested on this driver, and `pf-vaadec`'s
`reading_chroma_at_the_display_height_would_have_been_caught` is the only place
it is checked at all. `probe_this_machines_readback_routes` now prints the
derived layout and says which of the two it is, so the next driver answers for
itself instead of being assumed.
Its rows still read "never frame-hash parity-checked: the rung exports a tiled
dmabuf with no CPU-readable image, so parity needs a readback path that does not
exist yet". That readback now exists, and all SEVEN legs came back bit-identical
to libavcodec on RDNA3: vendored H.264 250/250, our host's low-delay H.264
120/120, vendored H.265 250/250, host low-delay H.265 120/120, HEVC Main 10
50/50 as P010, vendored AV1 250/250 of 274 decoded, and host low-delay 4K
two-tile AV1 60/60.
The two arms collapse into one, because the thing that split them — AV1 having
evidence the other legs lacked — is gone. Every leg now has the same evidence.
It stays `verified = false`, and the note says why in the words the
unproven-rung test requires: it has NEVER run on a second vendor and has never
been soaked. That is a real limit rather than a formality — every other verified
pair in this table earned it on more than one part, and the D3D11VA AV1 row two
entries up is a rung that passed on one vendor's driver while failing on
another's.
The second reason is not about evidence at all, and it belongs in the record
rather than in a commit nobody reads later: flipping this flag is a ROUTING
change. `native_rung_admitted` is `verified || !below.verified`, so a verified
VAAPI outranks Vulkan Video on every Linux AMD and Intel client — the Steam Deck
included. The parity result justifies that change; it should still be made on
purpose, by someone who wants it, rather than arriving as a side effect of
writing down a test result.
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.
Three decode rungs were writing decoded pictures into surfaces they were simultaneously predicting from. It was invisible on glass, it survived every green test suite we had, and on H.264 it hit ~99% of the frames of every stream a punktfunk host emits.
This started as the six follow-ups from #95/#96/#97 and turned into that.
The defect
A conversion released the pictures an access unit displaces inside the conversion, then assigned the decode target a slot.
SlotMap::assigntakes the lowest free slot — the one just vacated. So the submission named one surface as bothCurrPicTextureIndexand one of its own references.Fixed the same way everywhere: the plan gains
release_after_decode, and the caller applies removals only once the decode op is issued. It costs no slot.pf-vkdecode's AV1 path already had exactly this concept, which is why that one leg was always correct.decode_rps)Vulkan is the default rung on Linux and on Windows+NVIDIA, and H.264 is the fallback every client can land on. That is the release-blocking part.
Why nothing caught it
The vendored H.264 conformance vector shows zero occurrences. It reorders, and its level/VUI hand it a 7-frame DPB against 2 reference frames — both properties of that one vector. Our hosts emit the opposite shape: NVENC writes
max_num_ref_frames = 3andmax_dec_frame_buffering = 3, a DPB exactly as deep as its reference count, so the sliding window unmarks the oldest reference in the very access unit whose bump evicts it.Three tests turned out to be incapable of failing, each guarding the exact defect that shipped:
pf-vkdecodeasserted the aliasing as "the planner's normal behaviour" — it encoded the bug.pf-dxvadeccarried a vacuous predicate that compared an expression with itself.pf-vaadecasserted the decode target was never a reference while giving every picture a unique surface id — distinct integers cannot collide.Every fix here now carries a counterfactual that makes the bad ordering happen and proves it is caught.
New permanent gates
Host-shaped fixtures, because conformance vectors demonstrably cannot see this class of bug. All goldens cross-checked on two independent ffmpeg builds on two architectures.
lowdelay-640x480.h264(282 KB) — the stream that exposed itlowdelay-640x480.h265(270 KB) — HEVC's exemption held, 0/120, with a counterfactual that makes it 115/120lowdelay-3840x2160.ivf.av1(261 KB) — 4K is mandatory: 720p/1080p/1440p all measure a single tile. Which surfaced that every AV1 frame either rung had ever been measured against was single-tile, so the tile arrays had only ever been written at index 0.gpu_paritygoes 8 legs → 11. D3D11VA parity goes 5 → 8.VAAPI: from unmeasured to parity-proven
That rung had never had a pixel checked. It now has a
#[cfg(test)]readback (vaDeriveImage, falling back tovaCreateImage+vaGetImage) and all seven legs are bit-identical to libavcodec on RDNA3.The readback is kept off the production path structurally — the entry points are resolved by a test-only type that dlopens libva itself, the production
Libvagains no field, and a CPU test scans the file's own source to keep it that way. Zero-copy on the real path is unchanged.It also stopped dropping frames:
finishshowed onlyoutputs.last()and never flushed, so it delivered 225/204/45 of 250/250/50. Now 250/250/50. Two defects fell out of that:coloranddisplaywere stamped from the wrong access unit too (that is the in-band HDR switch), and concealment was about to start draining the queue, which would have zeroed the demotion streak and removed the ladder's escape hatch.VAAPI stays
verified = falseon purpose. Parity is no longer the gap — one vendor and no soak are. And flipping it is a routing change:native_rung_admittedwould put VAAPI ahead of Vulkan Video on every Linux AMD/Intel client, the Steam Deck included. That deserves its own decision.Also in here
The six original follow-ups: the H.264 DPB level-ceiling cliff named and guarded (
PlanWarning::LevelDerivedDpb); the CPU rung's process-isolation question decided and written down with its revisit trigger; the AV1 tile-aware sub-frame reader closed on measurement rather than left open; and the D3D11VA AV1 rung promoted toverifiedon two vendors.Verification
Every gate run on the merged tree, not per-branch.
.21— fmt,clippy --workspace --all-targets -D warnings, all unit suites,gpu_parity11/11.221— 8/8 D3D11VA parity legs on both GPUs.25— 39 tests, 0 failed, 0 ignored, every VAAPI parity legEach fix was also re-verified independently against pre-fix source: the new H.264 leg fails 117/120 at frame 3 without it, and the VAAPI harness was proved falsifiable by planting a real pitch defect.
⚠ Known and recorded, not fixed here: VAAPI is one vendor; rav1d is still not panic-proof (539 abort sites reachable across a C ABI);
pp.widthsends UpscaledWidth where libavcodec sends FrameWidth, identical only while superres is off.The evidence table has said "native VAAPI: has never decoded a frame anywhere (M6/M7)" since the rung was written. That is no longer true. Measured on `.25` (Radeon 780M / Phoenix1 RDNA3, radeonsi, Mesa 26.0.3, VA-API 1.23, Ubuntu 26.04 — headless, no display server needed): VAAPI AV1 rung constructed: native-vaapi av1 VAAPI AV1: 250 frames delivered, first 320x240 fourcc="NV12" modifier=0x200000010401b04 250 of 250 displayed frames, first try, on the same vendored vector the Vulkan and D3D11VA AV1 legs walk. The count matters as more than a smoke test: the vector carries 274 coded frames in 250 temporal units — 24 units carry two, and those extras are HIDDEN (decoded, referenced, never shown) — so 250 delivered is this rung agreeing with the other two about which frames are output. A tiled AMD DRM modifier rather than a linear one says the surface is a real decode target, not a fallback. Two changes, both in the rung's own file. **The probe never asked about AV1.** `probe_this_machines_libva` walked H.264 High, HEVC Main and HEVC Main 10 and stopped there, which is part of why "never decoded a frame" could stand so long without anyone noticing what had not been asked. It now covers both AV1 profiles, and this box answers: H.264 High: VLD decode AV1 Profile 0: VLD decode HEVC Main: VLD decode AV1 Profile 1: no (VAProfile not supported) Profile 1 being refused is correct — 4:4:4 AV1, which radeonsi does not do — and it is the negative case that proves the probe reports rather than assumes. **`av1_decodes_the_vendored_vector_on_this_machines_vaapi`** is the decode itself, `#[ignore]`d beside the probe. It is deliberately WEAKER than the Vulkan and D3D11VA AV1 legs, and the docs say so rather than letting the name imply parity: those two hash every frame against libavcodec's goldens because both can read their decoded surface back. This rung hands out a DRM-PRIME dmabuf whose memory the driver tiles, so there is no CPU-readable image to hash without adding a vaDeriveImage/vaGetImage path that production neither uses nor wants. So it asserts what can be asserted honestly — every temporal unit accepted, the right number of frames back, each a real exported surface of the right shape, the first flagged as a keyframe — and it is NOT frame-hash parity. Promoting this rung to `verified` still wants parity, and parity wants a readback path first. It fails loudly rather than skipping when the device has no AV1 entry point. It is `#[ignore]`d, so it only runs when someone points it at a box that is supposed to have one, and a silent pass there is exactly the invisible-failure mode this program exists to end. Gates: on `.25`, fmt clean, `clippy -p pf-client-core --all-targets -D warnings` green under the Linux cfg where this rung actually compiles, the whole lib suite 167/167, and all 11 VAAPI tests green with `--include-ignored`. Workspace fmt + clippy + lib suite also green in the Linux container. ⚠ Not touched here on purpose: the evidence table in `video.rs`. Its VAAPI row still reads "never decoded a frame anywhere" and now understates what is known — but a parallel agent is editing that same file for the D3D11VA AV1 row, so the row is left for whoever lands second to update once, rather than conflicting. Note for anyone reproducing on `.25`: it has no system SDL3 and no passwordless sudo, so the test binary links only with `--features sdl3/build-from-source` (SDL3 is gamepads, irrelevant to decode; production Linux still links the system one). Its disk sits at ~99% full, and the tree there is a `git archive` export with no `.git`, so `git apply`/`git checkout --` silently do nothing.`fd6241a2` made HEVC's freedom from the release-ordering defect falsifiable on CPU and recorded what was still missing: no low-delay HEVC stream was vendored, so the exemption rested on a structural argument plus one throwaway measurement. This vendors the stream, and the exemption HELD. **The fixture.** `lowdelay-640x480.h265`, 270 KB, 120 pictures — `punktfunk-host spike --source synthetic --codec h265 --width 640 --height 480 --fps 60 --seconds 2 --bitrate 1` on .21 (NVENC, RTX 5070 Ti, driver 610.57.04). Deliberately the H.264 sibling's resolution and frame count: the two are then directly comparable, 640 and 480 are both multiples of MinCbSizeY so there is no conformance window and a hash mismatch can only be decode rather than readback geometry, and 270 KB sits alongside the 282 KB already accepted for H.264. Goldens are libavcodec's software decode, cross-checked BIT-IDENTICAL across ffmpeg n8.1.2 (Arch, x86_64) and 8.1.1 (Homebrew, macOS arm64), 120 of 120 digests distinct. **The exemption held, measured rather than argued.** `sps_max_dec_pic_buffering_minus1 = 4` against the four pictures 8.3.2 keeps marked in steady state, `sps_max_num_reorder_pics = 0`, `numRefL0 = 1` — a five-picture DPB filled exactly by four references plus the current picture. 115 of the 120 access units retire a picture, and `removed ∩ dpb_refs` is **0 of 120**. A 300-picture 1080p stream from the same host reports the same shape: 295 retirements, 0 intersections. It is the encoder and not the resolution, exactly as for H.264. **A zero proves nothing on its own, so the fixture is pinned by its counterfactual.** `test-25fps.h264` reported zero for two milestones while every stream we ship aliased on 99% of its frames. So the guarantee here is not "we looked and it was fine": hand `plan_to_dxva_h265` the marked DPB as it stood BEFORE `decode_rps` — the mutation a snapshot move would cause, reconstructed exactly as `dpb_refs(N-1) ∪ {stored(N-1)}` — and the alias appears on **115 of 120** access units, driven through the real conversion rather than through planner arithmetic. If a regeneration ever produced a stream that reordered, or a DPB deeper than its reference count, that 115 collapses to 0 and the tests say so instead of continuing to pass. **The two rungs are exempt for different reasons, and the asymmetry is now a gate.** DXVA binds the whole marked DPB — `RefPicList` is spec-defined that way, and an RFI long-term anchor has to survive in it — so its exemption really is `H265Planner`'s snapshot ordering, one call away from being untrue. `plan_to_vk_h265` never reads `dpb_refs` at all: `pReferenceSlots` is the slots the operation uses, so it binds the current RPS sets, which `decode_rps` itself derives and which therefore cannot name a picture that same RPS just dropped. A new test feeds that conversion the identical widened snapshot and asserts nothing changes, so a future change making the Vulkan rung bind the marked DPB — a legitimate thing to want, since a *Foll* anchor invisible to the hardware is the RFI failure shape — fails loudly instead of silently acquiring the defect. What the Vulkan pixel leg adds is therefore NOT aliasing coverage, and its docs say so: it is the first HEVC frame either rung has decoded from our own encoder, under a DPB that retires and reissues a slot on 115 of 120 access units back to back, where the vendored vector's reordering keeps that eviction slack. Legs: `low_delay_host_h265_every_frame_hashes_bit_identical_to_libavcodec` on the Vulkan rung (10 ignored legs now, up from 9) and on the D3D11VA rung, plus three non-ignored CPU guards that run in ordinary CI. Verified: 10/10 Vulkan parity legs on .21 (RTX 5070 Ti, 610.57.04), the new one 120/120 bit-identical; workspace clippy `-D warnings` and `cargo fmt --all --check` clean on .21.Every other decode rung earns `verified` with frame-hash parity against libavcodec. VAAPI could not: it hands out a DRM-PRIME dmabuf whose memory the driver tiles, so nothing could read its decoded pixels back, and all four of its legs sat at "never frame-hash parity-checked". That was never bookkeeping. The D3D11VA AV1 rung decoded 250 frames, streamed 4K60 through a clean five-minute soak, and produced WRONG PIXELS for 186 of 250 frames on NVIDIA and 245 of 250 on Intel. It looked perfect on glass; only the goldens caught it, and the same defect turned out to be in H.264 on two other rungs. VAAPI was the one rung where that class of bug could still be sitting with nothing able to see it. It is not. Measured on .25 (Radeon 780M, RDNA3, radeonsi, Mesa 26.0.3, VA-API 1.23) on 2026-08-08, against the SAME golden files the Vulkan and D3D11VA rungs are held to, read across the crate boundary rather than copied: H.264 vendored vector 250/250 bit-identical (7 from the flush) H.264 our host, low-delay 640x480 120/120 bit-identical (3 from the flush) H.265 vendored vector 250/250 bit-identical (2 from the flush) H.265 our host, low-delay 640x480 120/120 bit-identical (0 from the flush) HEVC Main 10, P010 50/50 bit-identical (2 from the flush) AV1 vendored vector 250/250 delivered of 274 decoded, and display frame 0 byte-identical to libavcodec's own PIXELS AV1 our host, 4K two-tile 60/60 bit-identical ⚠ ONE vendor. AMD/radeonsi only; no Intel iHD box has run these legs. The readback that made it possible: * `pf-vaadec`'s `va` module gains `VAImage` and `VAImageFormat`, hand-declared with every size and offset measured off libva 2.23.0's real headers by `layout-probe.c` and pinned as compile-time assertions — the same discipline the decode buffers already keep. The trap: `VAImage::width`/`height` are 16-bit, so `data_size` sits at 60 and not at the 64 counting 32-bit fields gives, and every field after them is two bytes earlier than it looks. * `pack_two_plane` is the pure geometry — the crop to the picture, the padding columns dropped per row, and the chroma plane taken from the driver's OWN `offsets[1]` rather than from `pitch * display_height`, which is the 1088-row smear this program has already paid for once. It needs no device, so ten CPU tests cover it on macOS and in the container. * `video_vaapi_native::parity` drives the seven streams above through the production entry point and hashes what the rung DELIVERS, in delivery order, tail included — so the delivery path is under test as well as the decode, and a frame's surface comes from its own release token rather than from an inference about which pool entry holds which picture. THE READBACK CANNOT REACH THE PRODUCTION PATH, and that is structural rather than a promise. `vaDeriveImage`, `vaCreateImage`, `vaGetImage`, `vaMapBuffer` and the rest are resolved by a `#[cfg(test)]` type that dlopens libva itself; the production `Libva` gains no field; `sha2` is a dev dependency. A CPU test scans this file's own source and fails if any of those symbols is dlsym'd outside the harness, so a refactor cannot quietly undo it. Derive is not guaranteed, so both routes are implemented and neither is optional: `vaDeriveImage` first, `vaCreateImage` + `vaGetImage` as the fallback (which also detiles), and if neither yields the pool's own fourcc the leg FAILS naming what the driver gave it. There is no skip path — a parity test that passes because it could not read anything is the failure mode this program has been bitten by three times. Both answer on radeonsi, the first frame of every leg is read through BOTH and they must agree, and `PF_VAAPI_READBACK=getimage` reproduces the H.264 leg's 250/250 through the copying route alone, so the fallback is exercised rather than merely written. And it can fail — proven, not asserted. Planting the real geometry defect this driver's layout makes visible (rows read contiguously, ignoring the 512-byte pitch behind a 320-wide picture) fails at display frame 0 with the full localisation: 68312 luma and 14998 chroma samples differing, max |delta| 255, luma bounding box (0,1)..(319,239) — and with the goldens forced through one route, 250/250 diverging with "suspect the readback geometry". `compare` and `localise` also have CPU counterfactuals, and a hardware leg proves the readback reads real and DISTINCT pixels and localises a one-byte flip to the exact pixel. ⚠ One thing the hardware legs do NOT cover, found by planting the other defect and watching it do nothing: radeonsi's decode surfaces for every fixture here have no VERTICAL padding — `offsets[1]` is exactly `pitch * height` — so the chroma-plane trap is untested on this driver, and `pf-vaadec`'s `reading_chroma_at_the_display_height_would_have_been_caught` is the only place it is checked at all. `probe_this_machines_readback_routes` now prints the derived layout and says which of the two it is, so the next driver answers for itself instead of being assumed.