//! GPU frame-hash parity tests (WP-D) — the decode legs are `#[ignore]`d
//! because they need real Vulkan Video hardware; the coherence guards at the
//! bottom of this file are not, and run in ordinary CI.
//!
//! Run on a Vulkan-Video box with:
//!
//! ```text
//! cargo test -p pf-vkdecode --test gpu_parity -- --ignored --nocapture
//! ```
//!
//! (RADV boxes additionally need `RADV_PERFTEST=video_decode` — for AV1 as much as
//! for the other two, and without it `bring_up` reports "no physical device with
//! VK_KHR_video_decode_av1", which reads like missing silicon; multi-GPU boxes
//! pin the vendor with `PF_VKD_SMOKE_VENDOR=0x1002` / `0x10de`, same knob as
//! the smoke tests. Device bring-up lives in `tests/common/mod.rs`.)
//!
//! What they prove: H.264, H.265 and AV1 decoding are all exactly specified — every
//! conformant decoder must produce bit-identical output — so the vendored 25fps
//! vector of each codec is decoded through [`VkH264Decoder`] / [`VkH265Decoder`] /
//! [`VkAv1Decoder`],
//! every output frame's NV12 planes are read back (`vkCmdCopyImageToBuffer` on the
//! graphics queue — GPU→CPU is fine in a test; the pool grows TRANSFER_SRC via the
//! decoders' `PF_VKD_TEST_READBACK` hook), cropped to the display region,
//! SHA-256-hashed in DISPLAY order and compared against goldens from libavcodec's
//! SOFTWARE decoder (the reference implementation — provenance in
//! `data/test-25fps.nv12.sha256`, `data/test-25fps-h265.nv12.sha256` and
//! `data/test-25fps-av1.nv12.sha256`). ALL
//! frames are collected, including the tail `flush` delivers, and the frame count
//! must match libavcodec's too.
//!
//! Every leg runs ONE body ([`collect_hashes`]) over `common::TestDecoder`, so the
//! H.265 and AV1 legs cannot quietly test something weaker than the H.264 one. A box
//! that decodes only some of the three runs those legs and reports the rest as "no
//! physical device with VK_KHR_video_decode_…", which is a fact about the box —
//! and on today's fleet AV1 is the one most likely to say so.
//!
//! The two Annex-B codecs run that body TWICE: once over the vendored vector as it
//! sits, and once over the same vector rewritten to FOUR-byte start codes, which is
//! what the real host emits on 100% of access units in both codecs (1514/1514
//! H.264 and 1133/1133 HEVC, measured off the M0 NVENC corpus). Prefix width
//! carries no information, so both runs must reproduce the same goldens —
//! and submitting the four-byte form to the driver unchanged is precisely the
//! defect that made HEVC unplayable on every driver tested. Until these legs
//! existed no parity vector exercised the form that actually ships. **AV1 has no
//! such twin and needs none**: OBUs are length-delimited, so there is no start-code
//! prefix for a driver to mis-skip and no second framing to test (see
//! `common::split_av1_aus`). Its absence is deliberate.
//!
//! # The three legs that decode OUR OWN streams
//!
//! [`LOWDELAY_H264`], [`LOWDELAY_H265`] and [`LOWDELAY_AV1`] are not conformance
//! vectors — they are `punktfunk-host spike` output, vendored because a conformance
//! vector proves conformance to itself and the encoder we ship behind is a different
//! stream. Each is here for its own reason:
//!
//! * **H.264** caught a defect the vector is structurally blind to — 117 of its 120
//!   access units named one surface as both the decode target and a reference.
//! * **H.265** is EXEMPT from that defect for a structural reason, and an exemption
//!   with no stream behind it is how the H.264 defect survived two milestones.
//! * **AV1** is neither: the vendored AV1 vector already aliases on 268 of its 274
//!   frames, so that class was covered. It is here because the vector is ONE TILE on
//!   every frame while our encoder splits 4K into two tile rows, so every tile array
//!   the conversions fill had only ever been exercised at index 0.
//!
//! All three are backed by a non-ignored CPU guard asserting the stream still has the
//! property it was vendored for. ⚠ And all three are FILES. A file fixture says
//! nothing about packetisation, reassembly or loss — which for AV1 is not a
//! hypothetical caveat but a recorded failure: this suite reported 250/250 throughout
//! the period the host was shipping only the first tile of every 4K frame.
//!
//! # Why the AV1 leg exists at all
//!
//! Because until it did, the AV1 rung had no pixel evidence whatsoever. An
//! adversarial review of the conversion found four defects — per-frame flags left
//! unset on all 274 frames, a units error in `LoopRestorationSize`, per-reference
//! info describing the wrong picture, and zeroed film-grain fields — and every one
//! of them would have shown as a hash mismatch on frame 0 or shortly after, while
//! NONE of them failed clippy or the crate's unit tests. Type-checking a struct
//! conversion cannot tell you the struct describes the right picture; only the
//! pixels can.
//!
//! The readback follows the presenter's exact frame contract: wait the frame's
//! timeline `value`, round-trip the layout, signal `value + 1` in the SAME
//! submission, then `release_frame(frame, true)` — and every submission is
//! host-waited before the next decode, so nothing here races the decode queue.
//!
//! Reading a failure: frame 0 is intra-only — if it already mismatches, suspect
//! the readback geometry (row pitch / crop) or intra decode; mismatches that
//! only appear on later frames point at inter prediction / DPB management.

mod common;

use ash::vk;
use common::TestDecoder;
use pf_vkdecode::DecodeStatus;
use pf_vkdecode::DecodedVkFrame;
use pf_vkdecode::NoopQueueLock;
use pf_vkdecode::VkAv1Decoder;
use pf_vkdecode::VkH264Decoder;
use pf_vkdecode::VkH265Decoder;
use sha2::Digest;

/// Golden SHA-256 per display-order frame of the H.264 vector, from libavcodec
/// software decode (generation command + ffmpeg version in the file's header).
const GOLDENS_H264: &str = include_str!("data/test-25fps.nv12.sha256");

/// The H.265 twin, cross-checked between two independent FFmpeg builds (header).
const GOLDENS_H265: &str = include_str!("data/test-25fps-h265.nv12.sha256");

/// The AV1 twin: 250 DISPLAYED frames of a 274-coded-frame vector, cross-checked
/// between two independent FFmpeg builds on two architectures AND against the
/// per-frame MD5s cros-codecs vendored beside the vector (full provenance in the
/// file's header — it is the only golden here with a third-party corroboration).
const GOLDENS_AV1: &str = include_str!("data/test-25fps-av1.nv12.sha256");

/// The AV1 vector's FIRST frame, as libavcodec decodes it: the 320x240 render
/// region, tightly packed NV12, 115200 bytes — the same bytes
/// [`GOLDENS_AV1`]'s first line hashes.
///
/// Hashes tell you a frame is wrong; only pixels tell you HOW. This exists for
/// [`av1_frame0_pixels_say_which_plane_and_how_badly`], whose whole job is to turn
/// "FIRST DIVERGENT FRAME = 0" into a class: luma or chroma, a shift or a
/// difference, a filter's worth of error or a structural one. Frame 0 earns the
/// 113 KiB because it is intra-only — nothing upstream of it can be blamed — and
/// because on this rung it is where a divergence appears first.
///
/// It cannot drift from the golden set it was cut out of:
/// [`the_av1_frame0_reference_is_the_first_golden`] re-derives its SHA-256 and
/// compares, in ordinary CI, with no GPU.
const AV1_FRAME0: &[u8] = include_bytes!("data/test-25fps-av1.frame0.nv12");

/// The ten-bit vector and its goldens. No hardware leg in this file consumes them
/// yet — the D3D11VA rung is where the ten-bit parity leg currently runs — but the
/// files live here, beside the other goldens, so the guard that keeps them honest
/// belongs here too and runs on every platform rather than only on Windows.
const TEST_MAIN10_H265: &[u8] = include_bytes!("data/test-main10.h265");
const GOLDENS_MAIN10: &str = include_str!("data/test-main10.p010.sha256");

/// **Our own host's H.264**, and the only stream here that is not a conformance
/// vector — vendored 2026-08-07 because the conformance vector is BLIND to the one
/// defect this rung had.
///
/// `test-25fps.h264` reorders and carries a 7-frame DPB against 2 reference frames,
/// so a picture the sliding window unmarks is never evicted in the same access unit.
/// A punktfunk host emits low-delay IPPP with `max_num_reorder_frames = 0` and — this
/// is the part that matters — NVENC writes `max_num_ref_frames = 3` alongside
/// `max_dec_frame_buffering = 3`, a DPB exactly as deep as its reference count. 8.2.5's
/// window then unmarks the oldest reference in the very access unit whose C.4.5.3 bump
/// evicts it, and the conversion used to release that picture's slot before assigning
/// the setup one — so `pSetupReferenceSlot` and a reference named the same slot on
/// **117 of these 120 access units**.
///
/// The vector passed 250/250 throughout. This is the stream that could not.
const LOWDELAY_H264: &[u8] = include_bytes!("data/lowdelay-640x480.h264");
const GOLDENS_LOWDELAY: &str = include_str!("data/lowdelay-640x480.nv12.sha256");

/// The low-delay stream is 120 display frames at 640x480 (no conformance window —
/// both dimensions are macroblock-aligned, so the coded and display sizes agree).
const LOWDELAY_FRAME_COUNT: usize = 120;
const DISPLAY_LOWDELAY: (u32, u32) = (640, 480);

/// **Our own host's HEVC**, the twin of [`LOWDELAY_H264`] — and the one vendored to
/// keep an exemption honest rather than to catch a defect.
///
/// H.264 and AV1 both had to defer their slot releases past the decode op because
/// their planners snapshot the marked DPB BEFORE the marking that retires a picture.
/// `H265Planner` snapshots AFTER `decode_rps`, so an RPS-dropped picture is never in
/// the set `RefPicList`/`pReferenceSlots` is built from, and the HEVC conversions
/// still release inline. That argument is correct — and it was, until this stream,
/// backed by `test-25fps.h265` (which REORDERS, so it cannot reach the shape at all)
/// plus one throwaway measurement.
///
/// This stream reaches the shape. `sps_max_dec_pic_buffering_minus1 = 4` against four
/// pictures marked in steady state, `sps_max_num_reorder_pics = 0`: 115 of its 120
/// access units retire exactly one picture, and **all 115 of them would alias** if
/// the snapshot moved above `decode_rps`. Measured `removed ∩ dpb_refs` is 0 of 120,
/// so the exemption is a measurement on our own encoder's output rather than a
/// re-derivable argument.
///
/// ⚠ On THIS rung that counterfactual is about the planner, not about
/// [`pf_vkdecode::plan_to_vk_h265`]: Vulkan's `pReferenceSlots` is spec-defined as the
/// slots the decode operation uses, so the conversion binds `plan.rps` — the three
/// current sets, which `decode_rps` itself derives — and never reads `dpb_refs` at
/// all. The DXVA rung is the one that binds the whole marked DPB (`RefPicList` is
/// spec-defined that way, and an RFI long-term anchor must survive in it), so it is
/// the rung a moved snapshot would actually alias on;
/// `pf_dxvadec::pic_h265`'s tests drive that counterfactual through the conversion.
/// What the leg below adds on this rung is the thing no HEVC leg here had: PIXELS
/// from our own encoder, under a DPB that evicts and reuses a slot on 115 of 120
/// access units instead of a vector whose reordering keeps eviction slack.
///
/// Provenance, the `punktfunk-host spike` command and the two-build ffmpeg
/// cross-check are in the golden file's header, as for the H.264 sibling.
const LOWDELAY_H265: &[u8] = include_bytes!("data/lowdelay-640x480.h265");
const GOLDENS_LOWDELAY_H265: &str = include_str!("data/lowdelay-640x480-h265.nv12.sha256");

/// **Our own host's AV1**, and the only stream here with more than ONE TILE.
///
/// The vendored AV1 vector already exercises the reference-slot aliasing shape (268 of
/// its 274 frames), so unlike the H.264 and H.265 siblings this is not vendored to
/// close that. It closes a different gap: no host-generated AV1 stream was tested at
/// pixel level anywhere, and our encoder's AV1 is structurally unlike the vector —
/// `RFI_DPB = 5` references, reference-frame invalidation, and at 4K a split encode
/// that puts **two tile rows in one frame**.
///
/// 4K is not a size choice, it is the only shape that has the property. Measured on
/// .21, same command at four resolutions: 1280x720, 1920x1080 and 2560x1440 all give
/// `tile_cols = tile_rows = 1`; 3840x2160 gives `tile_cols = 1, tile_rows = 2` with
/// both tiles in ONE Tile Group OBU. It is paid for with 60 frames instead of 120,
/// which lands at 261 KB — under both other low-delay fixtures.
///
/// ⚠ It is a FILE, and a file is not the wire path. "250/250 bit-identical to
/// libavcodec" was true for AV1 throughout the period the host was shipping only the
/// first tile of every 4K frame: that number came from a vendored file while the
/// truncation lived in packetisation. This fixture gives the multi-tile shape pixel
/// coverage on the DECODE rungs and says nothing whatever about fragmentation,
/// reassembly or loss. The golden file's header says the same, at length.
const LOWDELAY_AV1: &[u8] = include_bytes!("data/lowdelay-3840x2160.ivf.av1");
const GOLDENS_LOWDELAY_AV1: &str = include_str!("data/lowdelay-3840x2160-av1.nv12.sha256");

/// The low-delay AV1 stream's temporal units, DISPLAYED frames and render region.
///
/// Units and frames are two constants holding 60 rather than one, and that is
/// deliberate: for the vendored vector they are 250 and 250 while the CODED count is
/// 274, and a leg that derived one from the other would be asserting AV1's frame
/// accounting instead of measuring it.
const LOWDELAY_AV1_UNIT_COUNT: usize = 60;
const LOWDELAY_AV1_FRAME_COUNT: usize = 60;
const DISPLAY_LOWDELAY_AV1: (u32, u32) = (3840, 2160);

/// The HEVC low-delay stream's own frame count and display region.
///
/// Deliberately NOT shared with [`LOWDELAY_FRAME_COUNT`]/[`DISPLAY_LOWDELAY`] even
/// though the two fixtures agree today: they are separate files from separate
/// encoder configurations, and one regenerated at another size must fail on its own
/// leg rather than silently redefine the other's geometry. Same reason
/// [`DISPLAY_H264`] and [`DISPLAY_H265`] are two constants holding 320x240.
const LOWDELAY_H265_FRAME_COUNT: usize = 120;
const DISPLAY_LOWDELAY_H265: (u32, u32) = (640, 480);

/// The Main 10 vector is 50 display frames.
const MAIN10_FRAME_COUNT: usize = 50;

/// The H.264 vector's display (conformance-window) region; the goldens hash
/// exactly this as tightly packed NV12.
const DISPLAY_H264: (u32, u32) = (320, 240);

/// The H.265 vector's display region. Its SPS carries NO conformance window at
/// all, so this is also its coded size (golden header) — the two vectors merely
/// HAPPEN to share dimensions, which is why [`Readback`] takes the size as a
/// parameter instead of reading one global pair.
const DISPLAY_H265: (u32, u32) = (320, 240);

/// The AV1 vector's display region — its `render_width` x `render_height`, AV1's
/// answer to a conformance window, and what [`DecodedVkFrame::crop`] carries on this
/// rung. Equal to the coded (post-superres) size for this vector, which
/// [`av1_goldens_and_the_ivf_split_agree_with_the_planner`] pins rather than assumes:
/// a re-synced vector whose render region shrank would make the readback crop a
/// region the goldens never hashed.
const DISPLAY_AV1: (u32, u32) = (320, 240);

/// All three vectors' picture format: 8-bit 4:2:0. H.264 is NV12 by envelope
/// (`derive_caps` wants nothing else), H.265 Main resolves to it from the SPS and
/// AV1 Main (`seq_profile = 0`, `high_bitdepth = 0`) from the sequence header —
/// and [`DecodedVkFrame::format`] exists precisely so a pool misconfigured to
/// P010 fails loudly instead of hashing differently.
const EXPECTED_FORMAT: vk::Format = pf_vkdecode::NV12;

/// Every vendored 25fps vector, in all three codecs, is 250 DISPLAY frames.
///
/// For H.264 and H.265 that is also one per access unit. For AV1 it is emphatically
/// not: its 250 temporal units carry [`AV1_CODED_FRAME_COUNT`] coded frames, 24 of
/// which are HIDDEN — decoded, referenced by later frames, never shown (the vector
/// uses no `show_existing_frame`, so they are displayed by no route at all). The
/// rung delivers one frame per `dpb.outputs` id, so 250 is the number the goldens
/// carry and the number the parity leg must compare.
const FRAME_COUNT: usize = 250;

/// The AV1 vector's CODED frame count — 24 more than [`FRAME_COUNT`].
///
/// Asserted by the CPU guard so the display/coded distinction stays a measured fact
/// rather than a comment: if a re-sync ever made these two numbers equal, the vector
/// would have lost its hidden-frame coverage (the exact thing that makes AV1's
/// multi-frame temporal units worth testing) while every hash still matched.
const AV1_CODED_FRAME_COUNT: usize = 274;

/// The golden file's hash lines (comments and blanks skipped).
fn golden_hashes(file: &'static str) -> Vec<&'static str> {
    file.lines()
        .map(str::trim)
        .filter(|line| !line.is_empty() && !line.starts_with('#'))
        .collect()
}

/// Refuse a golden set that could make a parity verdict vacuous.
///
/// Three ways a comparison can "pass" while proving nothing, all closed here:
///
/// - **an empty or short set** — [`assert_bit_identical`] compares `hashes` against
///   `goldens` pairwise and asserts the lengths match, so a file that lost its
///   entries to a bad regeneration would agree with a decoder that delivered
///   nothing. Pinning the count against a constant the CPU guards also re-derive
///   from the planner closes that.
/// - **junk that is not a digest** — a truncated or re-formatted line can never
///   equal a real hash, but a file of blank-looking lines could quietly become a
///   comparison of nothing.
/// - **all entries identical** — the one that matters most on a video codec. If
///   every golden were the same digest, a decoder emitting one frozen frame 250
///   times would pass, which is precisely the failure mode a broken reference
///   conversion produces. All four golden sets here are fully distinct (250/250
///   H.264, 250/250 H.265, 250/250 AV1, 50/50 Main 10), so requiring full
///   distinctness is not a weak bound.
fn assert_goldens_are_a_real_set(goldens: &[&str], expected: usize, path: &str) {
    assert_eq!(
        goldens.len(),
        expected,
        "{path} must carry one hash per display frame"
    );
    assert!(
        goldens
            .iter()
            .all(|line| line.len() == 64 && line.bytes().all(|b| b.is_ascii_hexdigit())),
        "{path}: every golden line is a bare lowercase SHA-256 hex digest"
    );
    let distinct = goldens
        .iter()
        .collect::<std::collections::HashSet<_>>()
        .len();
    assert_eq!(
        distinct,
        goldens.len(),
        "{path}: {distinct} of {} goldens are distinct — a set with repeats (and \
         above all a set that is ALL one digest) would let a decoder that froze on \
         a single frame pass parity",
        goldens.len()
    );
}

fn sha256_hex(data: &[u8]) -> String {
    use std::fmt::Write as _;
    sha2::Sha256::digest(data)
        .iter()
        .fold(String::with_capacity(64), |mut out, byte| {
            let _ = write!(out, "{byte:02x}");
            out
        })
}

/// Test-only GPU→CPU readback: one persistently mapped staging buffer plus one
/// command buffer on the GRAPHICS queue. Each read follows the presenter's
/// frame contract — wait the frame's timeline `value`, transition the image out
/// of its video layout, copy, restore the layout, signal `value + 1` in the
/// same submission — and is host-waited (fence) before returning, so the test
/// stays fully serialized against the decode queue.
///
/// The display size is a CONSTRUCTION parameter, not a module constant: it sizes
/// the staging buffer and is the crop every read asserts against, and the two
/// vectors sharing 320x240 today is a coincidence that must not become the next
/// vector's silent corruption.
struct Readback {
    device: ash::Device,
    queue: vk::Queue,
    cmd_pool: vk::CommandPool,
    cmd: vk::CommandBuffer,
    fence: vk::Fence,
    buffer: vk::Buffer,
    memory: vk::DeviceMemory,
    mapped: *const u8,
    /// The display region every read copies, and the crop it requires.
    display: (u32, u32),
    /// The picture format the pool must carry. Held here rather than read from a
    /// module constant so the sizing below and the per-frame assertion come from
    /// ONE source — a readback sized for eight bits that then accepted a ten-bit
    /// frame would hash half a picture and blame the decoder.
    format: vk::Format,
    /// 1 for NV12, 2 for the `3PACK16` ten-bit family (its samples are 16-bit
    /// words with the ten bits in the high end — the same layout P010 has, which
    /// is why one golden file serves both this rung and the D3D11VA one).
    bytes_per_sample: u32,
    /// `w * h * 3 / 2 * bytes_per_sample` — the tightly packed frame this buffer
    /// holds.
    frame_bytes: usize,
}

impl Readback {
    /// # Safety
    ///
    /// `instance`/`pd`/`device` are live; `graphics_qf` names a queue family a
    /// queue was created on (index 0) whose family supports TRANSFER (GRAPHICS
    /// implies it).
    unsafe fn new(
        instance: &ash::Instance,
        pd: vk::PhysicalDevice,
        device: &ash::Device,
        graphics_qf: u32,
        display: (u32, u32),
        format: vk::Format,
    ) -> Self {
        let (width, height) = display;
        // The two-plane copy below halves both dimensions for the chroma plane, so
        // an odd display region would silently drop a chroma row/column.
        assert_eq!(
            (width % 2, height % 2),
            (0, 0),
            "the display region must be chroma-aligned"
        );
        let bytes_per_sample = match format {
            f if f == pf_vkdecode::NV12 => 1,
            f if f == pf_vkdecode::P010 => 2,
            other => panic!("readback has no sample size for {other:?}"),
        };
        let frame_bytes = (width * height * 3 / 2 * bytes_per_sample) as usize;

        // SAFETY: fn contract — live device, queue 0 of this family exists.
        let queue = unsafe { device.get_device_queue(graphics_qf, 0) };
        let pool_ci = vk::CommandPoolCreateInfo::default()
            .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
            .queue_family_index(graphics_qf);
        // SAFETY: live device; destroyed in `destroy`.
        let cmd_pool = unsafe { device.create_command_pool(&pool_ci, None) }
            .expect("create the readback command pool");
        let alloc_ci = vk::CommandBufferAllocateInfo::default()
            .command_pool(cmd_pool)
            .level(vk::CommandBufferLevel::PRIMARY)
            .command_buffer_count(1);
        // SAFETY: the pool was just created on this device.
        let cmd = unsafe { device.allocate_command_buffers(&alloc_ci) }
            .expect("allocate the readback command buffer")[0];
        // SAFETY: live device; destroyed in `destroy`.
        let fence = unsafe { device.create_fence(&vk::FenceCreateInfo::default(), None) }
            .expect("create the readback fence");

        let buffer_ci = vk::BufferCreateInfo::default()
            .size(frame_bytes as u64)
            .usage(vk::BufferUsageFlags::TRANSFER_DST)
            .sharing_mode(vk::SharingMode::EXCLUSIVE);
        // SAFETY: live device; destroyed in `destroy`.
        let buffer =
            unsafe { device.create_buffer(&buffer_ci, None) }.expect("create the staging buffer");
        // SAFETY: the buffer was just created on this device.
        let req = unsafe { device.get_buffer_memory_requirements(buffer) };
        // SAFETY: live instance + physical device (fn contract).
        let props = unsafe { instance.get_physical_device_memory_properties(pd) };
        let wanted = vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT;
        let type_index = (0..props.memory_type_count)
            .find(|&i| {
                req.memory_type_bits & (1u32 << i) != 0
                    && props.memory_types[i as usize]
                        .property_flags
                        .contains(wanted)
            })
            .expect("a HOST_VISIBLE|HOST_COHERENT memory type for the staging buffer");
        let alloc = vk::MemoryAllocateInfo::default()
            .allocation_size(req.size)
            .memory_type_index(type_index);
        // SAFETY: live device, size from the requirements just queried; freed in
        // `destroy`.
        let memory =
            unsafe { device.allocate_memory(&alloc, None) }.expect("allocate staging memory");
        // SAFETY: fresh buffer bound to fresh memory of the required size.
        unsafe { device.bind_buffer_memory(buffer, memory, 0) }.expect("bind staging memory");
        // SAFETY: the memory is HOST_VISIBLE and not yet mapped; the mapping
        // lives until `destroy` frees the memory (implicit unmap).
        let mapped =
            unsafe { device.map_memory(memory, 0, vk::WHOLE_SIZE, vk::MemoryMapFlags::empty()) }
                .expect("map the staging buffer")
                .cast_const()
                .cast::<u8>();

        Self {
            device: device.clone(),
            queue,
            cmd_pool,
            cmd,
            fence,
            buffer,
            memory,
            mapped,
            display,
            format,
            bytes_per_sample,
            frame_bytes,
        }
    }

    /// Copy `frame`'s cropped NV12 planes into the staging buffer and return
    /// them tightly packed (Y `w*h` bytes, then interleaved UV `w*h/2` bytes) —
    /// exactly the layout ffmpeg's `-f rawvideo -pix_fmt nv12` writes, so the
    /// hashes compare 1:1 and row pitch/crop padding can never leak in
    /// (`bufferRowLength = 0` packs rows at the copy extent).
    ///
    /// # Safety
    ///
    /// `frame` was delivered by a decoder on this device and is not yet
    /// released; its image carries TRANSFER_SRC usage (the decoders'
    /// `PF_VKD_TEST_READBACK` hook); no other work uses the graphics queue or
    /// this frame's image concurrently (the test is fully serialized).
    unsafe fn read_nv12(&self, frame: &DecodedVkFrame) -> Vec<u8> {
        let (width, height) = self.display;
        assert_eq!(
            (frame.crop.width, frame.crop.height),
            self.display,
            "the vector's display size this readback was built for (the goldens \
             hash exactly this region)"
        );
        assert_eq!(
            (frame.crop.x % 2, frame.crop.y % 2),
            (0, 0),
            "chroma-aligned crop origin"
        );

        let begin = vk::CommandBufferBeginInfo::default()
            .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
        // SAFETY: the buffer came from a RESET_COMMAND_BUFFER pool (begin
        // implicitly resets) and its previous submission was fence-waited.
        unsafe { self.device.begin_command_buffer(self.cmd, &begin) }
            .expect("begin the readback command buffer");

        let subresource = vk::ImageSubresourceRange {
            aspect_mask: vk::ImageAspectFlags::COLOR,
            base_mip_level: 0,
            level_count: 1,
            base_array_layer: frame.layer,
            layer_count: 1,
        };
        // Into TRANSFER_SRC: execution/memory dependencies against the decode
        // are carried by the timeline wait at submit (visibility included), so
        // no src access is needed here.
        let to_transfer = vk::ImageMemoryBarrier2::default()
            .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS)
            .src_access_mask(vk::AccessFlags2::empty())
            .dst_stage_mask(vk::PipelineStageFlags2::COPY)
            .dst_access_mask(vk::AccessFlags2::TRANSFER_READ)
            .old_layout(frame.layout)
            .new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL)
            .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
            .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
            .image(frame.image)
            .subresource_range(subresource);
        let dep =
            vk::DependencyInfo::default().image_memory_barriers(std::slice::from_ref(&to_transfer));
        // SAFETY: recording state; the image is live until release (fn contract).
        unsafe { self.device.cmd_pipeline_barrier2(self.cmd, &dep) };

        // Two plane copies, crop applied at the source (plane-1 offsets/extents
        // are in the R8G8 plane's own half-resolution coordinates), rows packed
        // into the buffer at the copy extent.
        let layers = |aspect| vk::ImageSubresourceLayers {
            aspect_mask: aspect,
            mip_level: 0,
            base_array_layer: frame.layer,
            layer_count: 1,
        };
        let regions = [
            vk::BufferImageCopy {
                buffer_offset: 0,
                buffer_row_length: 0,
                buffer_image_height: 0,
                image_subresource: layers(vk::ImageAspectFlags::PLANE_0),
                image_offset: vk::Offset3D {
                    x: frame.crop.x as i32,
                    y: frame.crop.y as i32,
                    z: 0,
                },
                image_extent: vk::Extent3D {
                    width,
                    height,
                    depth: 1,
                },
            },
            vk::BufferImageCopy {
                // A BYTE offset, unlike the extents above, which are texels: the
                // luma plane occupies `w * h * bytes_per_sample` bytes.
                buffer_offset: u64::from(width * height * self.bytes_per_sample),
                buffer_row_length: 0,
                buffer_image_height: 0,
                image_subresource: layers(vk::ImageAspectFlags::PLANE_1),
                image_offset: vk::Offset3D {
                    x: (frame.crop.x / 2) as i32,
                    y: (frame.crop.y / 2) as i32,
                    z: 0,
                },
                image_extent: vk::Extent3D {
                    width: width / 2,
                    height: height / 2,
                    depth: 1,
                },
            },
        ];
        // SAFETY: the image is in TRANSFER_SRC_OPTIMAL via the barrier above and
        // carries TRANSFER_SRC usage (fn contract); the buffer's `frame_bytes`
        // exactly spans the two packed regions.
        unsafe {
            self.device.cmd_copy_image_to_buffer(
                self.cmd,
                frame.image,
                vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
                self.buffer,
                &regions,
            );
        }

        // Restore the video layout (the presenter contract: the consumer puts
        // the image back exactly as delivered) and make the copy host-readable.
        let restore = vk::ImageMemoryBarrier2::default()
            .src_stage_mask(vk::PipelineStageFlags2::COPY)
            .src_access_mask(vk::AccessFlags2::empty())
            .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS)
            .dst_access_mask(vk::AccessFlags2::empty())
            .old_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL)
            .new_layout(frame.layout)
            .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
            .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
            .image(frame.image)
            .subresource_range(subresource);
        let host_read = vk::BufferMemoryBarrier2::default()
            .src_stage_mask(vk::PipelineStageFlags2::COPY)
            .src_access_mask(vk::AccessFlags2::TRANSFER_WRITE)
            .dst_stage_mask(vk::PipelineStageFlags2::HOST)
            .dst_access_mask(vk::AccessFlags2::HOST_READ)
            .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
            .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
            .buffer(self.buffer)
            .offset(0)
            .size(vk::WHOLE_SIZE);
        let dep = vk::DependencyInfo::default()
            .image_memory_barriers(std::slice::from_ref(&restore))
            .buffer_memory_barriers(std::slice::from_ref(&host_read));
        // SAFETY: recording state; own buffer, live image.
        unsafe { self.device.cmd_pipeline_barrier2(self.cmd, &dep) };
        // SAFETY: recording above is complete and valid.
        unsafe { self.device.end_command_buffer(self.cmd) }.expect("end the readback commands");

        // Wait `value`, signal `value + 1` — the DecodedVkFrame sync contract.
        let wait = vk::SemaphoreSubmitInfo::default()
            .semaphore(frame.semaphore)
            .value(frame.value)
            .stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS);
        let signal = vk::SemaphoreSubmitInfo::default()
            .semaphore(frame.semaphore)
            .value(frame.value + 1)
            .stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS);
        let cmd_info = vk::CommandBufferSubmitInfo::default().command_buffer(self.cmd);
        let submit = vk::SubmitInfo2::default()
            .wait_semaphore_infos(std::slice::from_ref(&wait))
            .command_buffer_infos(std::slice::from_ref(&cmd_info))
            .signal_semaphore_infos(std::slice::from_ref(&signal));
        // SAFETY: live queue/fence; the semaphore is the frame's timeline
        // semaphore (fn contract), the fence was reset after its last use.
        unsafe {
            self.device
                .queue_submit2(self.queue, std::slice::from_ref(&submit), self.fence)
        }
        .expect("submit the readback");
        // SAFETY: the fence was just submitted.
        unsafe {
            self.device
                .wait_for_fences(&[self.fence], true, 10_000_000_000)
        }
        .expect("readback completes within 10s");
        // SAFETY: the fence was observed signalled above.
        unsafe { self.device.reset_fences(&[self.fence]) }.expect("reset the readback fence");

        // SAFETY: `mapped` points at `frame_bytes` host-coherent bytes (the
        // buffer was created at that size); the fence wait (plus the HOST_READ
        // barrier) ordered the device writes before this host read.
        unsafe { std::slice::from_raw_parts(self.mapped, self.frame_bytes) }.to_vec()
    }

    /// # Safety
    ///
    /// No submission in flight (every `read_nv12` fence-waited before
    /// returning) and nothing else references these handles.
    unsafe fn destroy(&self) {
        // SAFETY: own handles on the live device, idle per the fn contract;
        // freeing the memory implicitly unmaps it.
        unsafe {
            self.device.destroy_buffer(self.buffer, None);
            self.device.free_memory(self.memory, None);
            self.device.destroy_fence(self.fence, None);
            self.device.destroy_command_pool(self.cmd_pool, None);
        }
    }
}

/// Wait the frame's decode verdict, read + hash its pixels, release it (with
/// the presenter write-back the readback enqueued). `index` is the display
/// index the hash will land at.
fn consume_frame(
    decoder: &mut impl TestDecoder,
    readback: &Readback,
    frame: &DecodedVkFrame,
    index: usize,
) -> String {
    assert_eq!(
        decoder.wait_status(frame),
        DecodeStatus::Ok,
        "frame {index}: decode op not COMPLETE\n  state: {}",
        decoder.debug_snapshot()
    );
    // A pool built for the wrong picture format would decode correctly and then
    // hash differently for a reason no mismatch report could explain
    // (`DecodedVkFrame::format` docs) — refuse it here instead.
    assert_eq!(
        frame.format, readback.format,
        "frame {index}: the vector must decode into the pool format the readback \
         was built for"
    );
    // SAFETY: the frame is delivered and unreleased on the readback's device;
    // the pool carries TRANSFER_SRC (PF_VKD_TEST_READBACK was set before the
    // decoder's first decode); the test is fully serialized, so nothing else
    // touches the graphics queue or this image.
    let nv12 = unsafe { readback.read_nv12(frame) };
    decoder
        .release_frame(frame, true)
        .unwrap_or_else(|e| panic!("frame {index}: release failed: {e}"));
    sha256_hex(&nv12)
}

/// Decode every AU, hash every delivered frame in display order, including the
/// tail `flush` hands back. One body for all three codecs.
///
/// The flush tail is where the codecs legitimately differ and the body deliberately
/// does not: H.264 and H.265 can hold pictures back for reorder, so their planners'
/// `flush` releases a tail. AV1's planner has no `flush` at all — a shown frame is
/// output by the very temporal unit that decodes it — so `VkAv1Decoder::flush` frees
/// the hidden pictures' images and hands back nothing. Draining afterwards is
/// therefore a no-op for AV1 rather than a special case, and running the identical
/// body means an AV1 rung that ever DID strand a shown frame would be caught by the
/// frame-count assertion instead of hidden by a codec-specific shortcut.
fn collect_hashes(
    decoder: &mut impl TestDecoder,
    readback: &Readback,
    aus: &[&[u8]],
) -> Vec<String> {
    let mut hashes: Vec<String> = Vec::new();
    for (au_index, au) in aus.iter().enumerate() {
        let mut next = decoder.decode(au).unwrap_or_else(|e| {
            panic!(
                "AU {au_index}: decode failed: {e}\n  state: {}",
                decoder.debug_snapshot()
            )
        });
        while let Some(frame) = next {
            let hash = consume_frame(decoder, readback, &frame, hashes.len());
            hashes.push(hash);
            next = decoder.take_ready();
        }
    }
    // The decoders emit in bumping (display) order and a stream may hold frames —
    // the flush tail belongs in the comparison too.
    decoder.flush();
    while let Some(frame) = decoder.take_ready() {
        let hash = consume_frame(decoder, readback, &frame, hashes.len());
        hashes.push(hash);
    }
    eprintln!(
        "final state: {} status_queries={}",
        decoder.debug_snapshot(),
        decoder.status_queries()
    );
    hashes
}

/// The verdict, run AFTER teardown so a mismatch panic cannot leave the device
/// alive.
fn assert_bit_identical(hashes: &[String], goldens: &[&str], codec: &str) {
    assert_eq!(
        hashes.len(),
        goldens.len(),
        "{codec}: frame count diverges from libavcodec ({} decoded vs {} golden)",
        hashes.len(),
        goldens.len()
    );
    let mut mismatches = 0usize;
    let mut first_divergence: Option<usize> = None;
    for (index, (got, want)) in hashes.iter().zip(goldens.iter()).enumerate() {
        if got.as_str() != *want {
            if mismatches < 10 {
                eprintln!("frame {index}: MISMATCH\n  ours:   {got}\n  golden: {want}");
            }
            first_divergence.get_or_insert(index);
            mismatches += 1;
        }
    }
    // The FIRST divergent index is the whole diagnostic: everything after it may be
    // downstream of that one frame through prediction and the DPB, so a report that
    // only counted mismatches would bury the one number that localises the defect.
    assert!(
        first_divergence.is_none(),
        "{codec}: FIRST DIVERGENT FRAME = {} ({mismatches}/{} frames diverge from \
         libavcodec; up to 10 printed above). Frame 0 is intra-only — if IT is the \
         first, suspect readback geometry (pitch/crop), the picture format, or intra \
         decode / the per-frame parameter conversion; a first divergence LATER points \
         at inter prediction, per-reference info or DPB management, and the frames \
         after it are probably just downstream of it.",
        first_divergence.unwrap_or_default(),
        hashes.len()
    );
    eprintln!(
        "{codec}: {} frames bit-identical to libavcodec software decode",
        hashes.len()
    );
}

/// One H.264 parity run over a caller-supplied AU list.
///
/// The AUs are a parameter rather than a constant because two legs share this
/// body: the vendored vector as it sits (three-byte start codes) and the same
/// vector rewritten to the four-byte ones the real host actually emits. Both
/// must reproduce the SAME goldens, because prefix width carries no
/// information — and running one body twice is what makes that an equality
/// rather than two similar-looking assertions that could drift apart.
fn h264_parity_run(aus: &[&[u8]], label: &str) {
    h264_parity_run_against(aus, label, GOLDENS_H264, FRAME_COUNT, DISPLAY_H264);
}

/// [`h264_parity_run`] with its stream's own goldens and geometry, for the legs that
/// do not decode the vendored vector.
fn h264_parity_run_against(
    aus: &[&[u8]],
    label: &str,
    goldens: &'static str,
    frame_count: usize,
    display: (u32, u32),
) {
    // One codec at a time on the device, and the `set_var` below happens only
    // under this lock (see `common::gpu_lock`).
    let _gpu = common::gpu_lock();

    // The decoder reads this at session creation (first decode call): pool
    // images grow TRANSFER_SRC so vkCmdCopyImageToBuffer is legal.
    // SAFETY: `_gpu` holds the binary-wide GPU lock (`common::gpu_lock`); the parity legs are
    // this variable's only writers and readers, and they run one at a time under that lock.
    unsafe { std::env::set_var("PF_VKD_TEST_READBACK", "1") };

    let goldens = golden_hashes(goldens);
    assert_eq!(
        goldens.len(),
        frame_count,
        "the golden file carries one hash per libavcodec frame"
    );

    let setup = common::bring_up(&common::Request {
        codec: common::H264,
        // Unlike the smoke legs this one NEEDS a graphics queue (the readback
        // records there), so a device without one is skipped, not defaulted.
        graphics: common::Graphics::Required,
        report_families: true,
    });
    let handles = setup.handles();

    let hashes = {
        // SAFETY: `setup` outlives this block (destroyed below, after the decoder
        // and readback drop at the block's end), it was created with the H.264
        // decode extensions + timeline/sync2 features, and its queue fields name
        // the families/queues it created.
        let mut decoder = unsafe { VkH264Decoder::new(&handles, Box::new(NoopQueueLock)) }
            .expect("wrap the device");
        // SAFETY: live instance/device; queue 0 of `graphics_qf` was created by
        // the bring-up; destroyed at the end of this block after its last read.
        let readback = unsafe {
            Readback::new(
                &setup.instance,
                setup.pd,
                &setup.device,
                setup.graphics_qf,
                display,
                EXPECTED_FORMAT,
            )
        };
        let hashes = collect_hashes(&mut decoder, &readback, aus);
        // SAFETY: every readback was fence-waited inside `read_nv12`; nothing
        // else references its handles.
        unsafe { readback.destroy() };
        hashes
    };

    // SAFETY: the decoder is gone (its Drop drained the queue and destroyed its
    // session/pools), the readback's handles are destroyed, and nothing else
    // references the setup's handles.
    unsafe { setup.destroy() };

    assert_bit_identical(&hashes, &goldens, label);
}

#[test]
#[ignore = "needs a Vulkan Video H.264 decode device (fleet boxes; see module docs)"]
fn h264_every_frame_hashes_bit_identical_to_libavcodec() {
    h264_parity_run(&common::split_h264_aus(common::TEST_25FPS_H264), "H.264");
}

/// The same 250 frames, submitted the way the real host submits them.
///
/// A failure here where the leg above passes means the four-byte prefix is
/// reaching the driver — `ring::pack_slices` stopped trimming the leading zero
/// byte, or stopped deriving the slice offsets from the trimmed lengths — which
/// is the defect that made HEVC unplayable on every driver tested.
#[test]
#[ignore = "needs a Vulkan Video H.264 decode device (fleet boxes; see module docs)"]
fn h264_four_byte_start_codes_decode_bit_identically() {
    let stream = common::h264_four_byte_start_codes(common::TEST_25FPS_H264);
    h264_parity_run(
        &common::split_h264_aus(&stream),
        "H.264 (4-byte start codes)",
    );
}

/// The leg the vendored vector cannot be: **our own host's low-delay H.264**.
///
/// The vector above passed 250/250 on every driver in the fleet while this rung
/// named one DPB slot as both `pSetupReferenceSlot` and a reference on 117 of the
/// 120 access units below — the shape it simply never produces (see
/// [`LOWDELAY_H264`]). Both of this rung's DPB modes take it badly and neither
/// loudly: DISTINCT hands the aliased reference the same array layer the setup
/// writes; COINCIDE finds no bound image for it, drops it from `pReferenceSlots` and
/// `trace!`s. So a leg that decodes what we actually ship is not redundant with the
/// conformance leg, it is the only one that can see this class at all.
#[test]
#[ignore = "needs a Vulkan Video H.264 decode device (fleet boxes; see module docs)"]
fn low_delay_host_h264_every_frame_hashes_bit_identical_to_libavcodec() {
    h264_parity_run_against(
        &common::split_h264_aus(LOWDELAY_H264),
        "H.264 (low-delay host stream)",
        GOLDENS_LOWDELAY,
        LOWDELAY_FRAME_COUNT,
        DISPLAY_LOWDELAY,
    );
}

/// The H.265 twin of [`h264_parity_run`]; see its docs for why the AUs are a
/// parameter.
fn h265_parity_run(
    aus: &[&[u8]],
    goldens_file: &'static str,
    expected_frames: usize,
    bit_depth_luma_minus8: u8,
    format: vk::Format,
    display: (u32, u32),
    label: &str,
) {
    // As the H.264 leg: one codec at a time, `set_var` under the lock.
    let _gpu = common::gpu_lock();

    // SAFETY: `_gpu` holds the binary-wide GPU lock (`common::gpu_lock`); the parity legs are
    // this variable's only writers and readers, and they run one at a time under that lock.
    unsafe { std::env::set_var("PF_VKD_TEST_READBACK", "1") };

    let goldens = golden_hashes(goldens_file);
    assert_eq!(
        goldens.len(),
        expected_frames,
        "the golden file carries one hash per libavcodec frame"
    );

    let setup = common::bring_up(&common::Request {
        codec: common::H265,
        graphics: common::Graphics::Required,
        report_families: true,
    });
    let handles = setup.handles();

    let hashes = {
        // SAFETY: as the H.264 leg — `setup` outlives this block and was created
        // with the H.265 decode extensions + timeline/sync2 features.
        let mut decoder = unsafe { VkH265Decoder::new(&handles, Box::new(NoopQueueLock)) }
            .expect("wrap the device");
        // The construction-time shape gate the client's ladder relies on, on the
        // vector's own facts (Main, 4:2:0, 8-bit → NV12): a device that cannot
        // host the combination refuses here with a caps reason instead of failing
        // mid-stream.
        decoder
            .probe_stream_support(1, bit_depth_luma_minus8)
            .unwrap_or_else(|e| {
                panic!("{label}: the box must host this H.265 shape — {e:?}");
            });
        // SAFETY: as the H.264 leg — live instance/device, queue 0 of
        // `graphics_qf` exists; destroyed at the end of this block.
        let readback = unsafe {
            Readback::new(
                &setup.instance,
                setup.pd,
                &setup.device,
                setup.graphics_qf,
                display,
                format,
            )
        };
        let hashes = collect_hashes(&mut decoder, &readback, aus);
        // SAFETY: every readback was fence-waited inside `read_nv12`; nothing
        // else references its handles.
        unsafe { readback.destroy() };
        hashes
    };

    // SAFETY: as the H.264 leg — decoder and readback are gone.
    unsafe { setup.destroy() };

    assert_bit_identical(&hashes, &goldens, label);
}

#[test]
#[ignore = "needs a Vulkan Video H.265 decode device (fleet boxes; see module docs)"]
fn h265_every_frame_hashes_bit_identical_to_libavcodec() {
    h265_parity_run(
        &common::split_h265_aus(common::TEST_25FPS_H265),
        GOLDENS_H265,
        FRAME_COUNT,
        0,
        EXPECTED_FORMAT,
        DISPLAY_H265,
        "H.265",
    );
}

/// The ten-bit path — the only leg in this file that is not eight-bit.
///
/// Every other golden set in this program is NV12, so no rung had pixel evidence
/// for its ten-bit path: the HDR legs proved a Main10 session BUILDS and streams
/// clean, which a stream decoding to garbage would also do. The goldens are P010
/// and the Vulkan pool is `G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16`, whose
/// samples are 16-bit words with the ten bits in the high end — the same layout,
/// which is why one golden file serves this rung and the D3D11VA one.
#[test]
#[ignore = "needs a Vulkan Video H.265 Main 10 decode device (fleet boxes; see module docs)"]
fn main10_every_frame_hashes_bit_identical_to_libavcodec() {
    h265_parity_run(
        &common::split_h265_aus(TEST_MAIN10_H265),
        GOLDENS_MAIN10,
        MAIN10_FRAME_COUNT,
        2,
        pf_vkdecode::P010,
        (320, 240),
        "HEVC Main 10",
    );
}

/// The HEVC twin of [`low_delay_host_h264_every_frame_hashes_bit_identical_to_libavcodec`]:
/// **our own host's HEVC**, in the shape the vendored vector cannot produce.
///
/// Its job is the opposite of the H.264 leg's. That one exists because the rung was
/// broken and only this stream shape could show it. This one exists because until now
/// no HEVC leg anywhere had decoded a single frame our own encoder produced: both
/// existing legs run vendored vectors, and the H.264 sibling is the standing proof
/// that a vector's silence about a stream shape is not evidence.
///
/// What it exercises that `h265_every_frame_hashes_bit_identical_to_libavcodec` does
/// not: a five-picture DPB with four references marked and no reordering, so the
/// `SlotMap` retires and reissues a slot on 115 of the 120 access units, back to back,
/// with the decode target taking the slot freed in the same access unit. The vector
/// reorders, which keeps that eviction slack and never puts the two together.
///
/// It is NOT the leg that would catch a moved `dpb_snapshot()` — see [`LOWDELAY_H265`]
/// for why that lands on the DXVA rung instead, and
/// [`the_low_delay_h265_stream_agrees_with_its_goldens_and_keeps_the_exemption_falsifiable`]
/// for the guard that keeps the planner property itself pinned, on CPU, in ordinary CI.
#[test]
#[ignore = "needs a Vulkan Video H.265 decode device (fleet boxes; see module docs)"]
fn low_delay_host_h265_every_frame_hashes_bit_identical_to_libavcodec() {
    h265_parity_run(
        &common::split_h265_aus(LOWDELAY_H265),
        GOLDENS_LOWDELAY_H265,
        LOWDELAY_H265_FRAME_COUNT,
        0,
        EXPECTED_FORMAT,
        DISPLAY_LOWDELAY_H265,
        "H.265 (low-delay host stream)",
    );
}

/// The HEVC leg of the production prefix form — the one that would have caught
/// the shipped defect. See [`h264_four_byte_start_codes_decode_bit_identically`].
#[test]
#[ignore = "needs a Vulkan Video H.265 decode device (fleet boxes; see module docs)"]
fn h265_four_byte_start_codes_decode_bit_identically() {
    let stream = common::h265_four_byte_start_codes(common::TEST_25FPS_H265);
    h265_parity_run(
        &common::split_h265_aus(&stream),
        GOLDENS_H265,
        FRAME_COUNT,
        0,
        EXPECTED_FORMAT,
        DISPLAY_H265,
        "H.265 (4-byte start codes)",
    );
}

/// The AV1 twin of [`h265_parity_run`].
///
/// Concrete where the H.265 one is parameterised, because AV1 has exactly one
/// vendored vector and one shape (Main 4:2:0 8-bit, no film grain, 320x240); the
/// facts it hard-codes are re-derived from the planner, without a GPU, by
/// [`av1_goldens_and_the_ivf_split_agree_with_the_planner`], so a re-synced vector
/// of another shape fails in ordinary CI with the reason rather than on the fleet as
/// a confusing probe refusal. A second AV1 vector (Main 10, or one that uses
/// `show_existing_frame`) is the point at which this should grow the same parameters
/// the H.265 body carries — not before.
fn av1_parity_run(aus: &[&[u8]], label: &str) {
    av1_parity_run_against(
        aus,
        label,
        GOLDENS_AV1,
        "data/test-25fps-av1.nv12.sha256",
        FRAME_COUNT,
        FRAME_COUNT,
        DISPLAY_AV1,
    );
}

/// [`av1_parity_run`] with its stream's own goldens and geometry, for the leg that
/// does not decode the vendored vector.
///
/// `units` and `frames` are SEPARATE parameters and must stay so. They are equal for
/// the low-delay host stream (one shown frame per temporal unit) and unequal for the
/// vendored vector only in the sense that its 250 units carry 274 coded frames of
/// which 250 are shown — deriving either from the other is exactly the assumption
/// AV1 punishes.
fn av1_parity_run_against(
    aus: &[&[u8]],
    label: &str,
    goldens_file: &'static str,
    goldens_path: &str,
    units: usize,
    frames: usize,
    display: (u32, u32),
) {
    // As the other legs: one codec at a time on the device, and the `set_var` below
    // happens only under this lock (see `common::gpu_lock`).
    let _gpu = common::gpu_lock();

    // SAFETY: `_gpu` holds the binary-wide GPU lock (`common::gpu_lock`); the parity legs are
    // this variable's only writers and readers, and they run one at a time under that lock.
    unsafe { std::env::set_var("PF_VKD_TEST_READBACK", "1") };

    let goldens = golden_hashes(goldens_file);
    // Non-vacuity, before any hardware is touched: the right number of entries, all
    // real digests, all distinct (see the helper's docs — a frozen-frame decoder
    // must not be able to pass this leg).
    assert_goldens_are_a_real_set(&goldens, frames, goldens_path);
    // …and the leg must actually be fed something. An IVF whose packets failed to
    // parse would hand `collect_hashes` an empty AU list, which delivers no frames
    // and would then fail as a frame-count mismatch that reads like a decoder defect.
    assert_eq!(
        aus.len(),
        units,
        "{label}: the stream must split into {units} temporal units"
    );

    let setup = common::bring_up(&common::Request {
        codec: common::AV1,
        // As the other parity legs: the readback records on the graphics queue, so a
        // device without a graphics family is skipped rather than defaulted.
        graphics: common::Graphics::Required,
        report_families: true,
    });
    let handles = setup.handles();

    let hashes = {
        // SAFETY: as the H.264/H.265 legs — `setup` outlives this block (destroyed
        // below, after the decoder and readback drop at the block's end), it was
        // created with the AV1 decode extension + timeline/sync2 features, and its
        // queue fields name the families/queues it created.
        let mut decoder = unsafe { VkAv1Decoder::new(&handles, Box::new(NoopQueueLock)) }
            .expect("wrap the device");
        // The construction-time shape gate, on the vector's own facts: 4:2:0, 8-bit,
        // and NO film grain. The third argument is the load-bearing one — grain
        // synthesis is part of the Vulkan decode PROFILE, so a box that offers only
        // the grain-enabled profile (or only the disabled one) refuses HERE with a
        // caps reason instead of failing at the first temporal unit.
        decoder
            .probe_stream_support(1, 8, false)
            .unwrap_or_else(|e| {
                panic!("{label}: the box must host AV1 Main 4:2:0 8-bit, no film grain — {e:?}");
            });
        // SAFETY: as the other legs — live instance/device, queue 0 of `graphics_qf`
        // was created by the bring-up; destroyed at the end of this block.
        let readback = unsafe {
            Readback::new(
                &setup.instance,
                setup.pd,
                &setup.device,
                setup.graphics_qf,
                display,
                EXPECTED_FORMAT,
            )
        };
        let hashes = collect_hashes(&mut decoder, &readback, aus);
        // SAFETY: every readback was fence-waited inside `read_nv12`; nothing else
        // references its handles.
        unsafe { readback.destroy() };
        hashes
    };

    // SAFETY: as the other legs — the decoder is gone (its Drop drained the queue and
    // destroyed its session/pools) and the readback's handles are destroyed.
    unsafe { setup.destroy() };

    assert_bit_identical(&hashes, &goldens, label);
}

/// The AV1 rung's first pixel evidence.
///
/// 250 temporal units in, 250 DISPLAYED frames out (the 24 hidden frames the vector
/// also codes are decoded, referenced and never shown — module docs), each read back
/// as tightly packed NV12 over its `render_width` x `render_height` region and
/// compared against libavcodec's software decode.
///
/// What a failure looks like, and where to point it:
/// - **frame 0** — the sequence header or the per-frame parameter conversion:
///   `StdVideoAV1SequenceHeader`, the eight per-frame sub-blocks (tile info,
///   quantisation, segmentation, loop filter, CDEF, loop restoration, global motion,
///   film grain), the tile-group ranges, or the readback geometry. AV1 puts in the
///   frame header what H.26x puts in parameter sets, so a single wrong field here
///   damages every frame.
/// - **frame 1** — the first frame with a reference. Per-reference info, the
///   reference-NAME → DPB-slot table, or `ref_frame_idx` ordering.
/// - **later, then everything after** — DPB slot management, `refresh_frame_flags`,
///   or the hidden frames: a run that is clean until roughly the first multi-frame
///   temporal unit and wrong thereafter is the signature of the hidden ALTREF being
///   stored wrong or not at all.
#[test]
#[ignore = "needs a Vulkan Video AV1 decode device (fleet boxes; see module docs)"]
fn av1_every_frame_hashes_bit_identical_to_libavcodec() {
    av1_parity_run(&common::split_av1_aus(common::TEST_25FPS_AV1), "AV1");
}

/// **Our own host's AV1, at the only resolution where it emits more than one tile.**
///
/// The leg above proves the conversion against a vector with `tile_cols = tile_rows
/// = 1` on every one of its 274 frames, so every tile-info field it exercises is the
/// degenerate case: one `width_in_sbs_minus_1`, one `height_in_sbs_minus_1`, one
/// `context_update_tile_id`, `TileCols = TileRows = 1`. This stream carries
/// `tile_rows = 2` with `height_in_sbs_minus_1 = [16, 16]` on all 60 frames, and both
/// tiles arrive in a single Tile Group OBU — so a conversion that got the tile arrays,
/// the per-tile sizing or the tile-group range wrong would decode the vector perfectly
/// and this stream visibly (see [`LOWDELAY_AV1`]).
///
/// It is also 4K, which no other parity leg in this program is: the readback moves
/// 12,441,600 bytes per frame instead of 115,200.
#[test]
#[ignore = "needs a Vulkan Video AV1 decode device (fleet boxes; see module docs)"]
fn low_delay_host_av1_every_frame_hashes_bit_identical_to_libavcodec() {
    av1_parity_run_against(
        &common::split_av1_aus(LOWDELAY_AV1),
        "AV1 (low-delay host stream, 4K two-tile)",
        GOLDENS_LOWDELAY_AV1,
        "data/lowdelay-3840x2160-av1.nv12.sha256",
        LOWDELAY_AV1_UNIT_COUNT,
        LOWDELAY_AV1_FRAME_COUNT,
        DISPLAY_LOWDELAY_AV1,
    );
}

/// Frame 0's pixels against libavcodec's, byte for byte — the diagnostic leg.
///
/// [`av1_every_frame_hashes_bit_identical_to_libavcodec`] is the verdict; this is
/// the microscope, and it decodes only as far as the first delivered frame. A hash
/// mismatch names no cause, and the four causes the parity leg's own message ranks
/// for a frame-0 divergence produce *completely different* pixel signatures:
///
/// | printed here | what it means |
/// |---|---|
/// | `luma IDENTICAL`, chroma differs | the chroma plane's layout — `PLANE_1`'s copy region, or a pool whose chroma plane starts somewhere other than where the readback reads it. NOT a decode problem |
/// | both differ, and a **shift** matches | readback geometry: the crop origin, or a copy extent taken from the pool rather than the render region. The printed `dy`/`dx` IS the error |
/// | both differ, deltas ≤ ~8 over most of the plane | an in-loop filter parameter — CDEF, loop restoration, the deblocking levels. Small and everywhere is what a filter does, and it is why the whole 250 frames go with it: CDEF runs before the frame is stored as a reference |
/// | both differ, deltas large and structured | quantisation, tile geometry, or the tile payloads themselves — the frame was reconstructed from the wrong data rather than filtered wrongly |
/// | ours is CONSTANT | nothing was decoded into the image the readback read |
///
/// It asserts equality last, so a failure prints the whole report above the panic.
#[test]
#[ignore = "needs a Vulkan Video AV1 decode device (fleet boxes; see module docs)"]
fn av1_frame0_pixels_say_which_plane_and_how_badly() {
    let _gpu = common::gpu_lock();
    // SAFETY: `_gpu` holds the binary-wide GPU lock (`common::gpu_lock`); the parity legs are
    // this variable's only writers and readers, and they run one at a time under that lock.
    unsafe { std::env::set_var("PF_VKD_TEST_READBACK", "1") };

    let aus = common::split_av1_aus(common::TEST_25FPS_AV1);
    assert_eq!(
        aus.len(),
        FRAME_COUNT,
        "the vector must split into 250 units"
    );
    let ours = av1_first_frame(&aus);

    report_nv12_divergence(&ours, AV1_FRAME0, DISPLAY_AV1);
    assert_eq!(
        sha256_hex(&ours),
        golden_hashes(GOLDENS_AV1)[0],
        "AV1 frame 0 is not libavcodec's — read the report above for the class"
    );
    eprintln!("AV1 frame 0 is byte-identical to libavcodec");
}

/// `loop_filter_level[2]` and `[3]` — the U and V deblocking levels — as BIT
/// offsets from the start of the vector's FIRST access unit.
///
/// Derived rather than found: the IVF packet holds a temporal-delimiter OBU (2
/// bytes), a sequence-header OBU (2 + 11) and an `OBU_FRAME` header (1 + a 2-byte
/// leb128 size), so the uncompressed frame header starts at byte 18. Inside it
/// `loop_filter_level[0]` begins at bit 35 and the four levels are `f(6)` back to
/// back (5.9.11), which puts U at bit 47 and V at bit 53.
///
/// [`av1_frame0_probes_whether_the_driver_reads_the_chroma_deblocking_levels`]
/// re-parses the mutated unit before it decodes anything, so a re-synced vector
/// makes this fail loudly instead of poking an unrelated field.
const AV1_FRAME0_FILTER_LEVEL_U_BIT: usize = 18 * 8 + 47;
const AV1_FRAME0_FILTER_LEVEL_V_BIT: usize = 18 * 8 + 53;

/// The strongest deblocking level AV1 can code (`f(6)`), and the value the probe
/// rewrites both chroma levels to.
const MAX_LOOP_FILTER_LEVEL: u8 = 63;

/// The driver DOES read the chroma deblocking levels — and this is the test that
/// says so, after a pass of this program's history said the opposite.
///
/// ⚠⚠ **The claim "NVIDIA ignores `StdVideoAV1LoopFilter::loop_filter_level[2..3]`"
/// is refuted. Do not reintroduce it.** It was an honest reading of a real
/// measurement: the AV1 frame-0 parity leg came back `luma IDENTICAL, chroma
/// 319/38400 bytes differ, max |delta| 4`, software re-decode with both chroma
/// levels forced to zero reproduced that signature byte for byte, and this very
/// probe then came back IDENTICAL for `[8, 12]` and `[63, 63]`. Every step was
/// sound; the inference was not. The levels were reaching the driver intact — what
/// was NOT intact was the sequence header, whose `pColorConfig` block this crate
/// freed the instant `vkCreateVideoSessionParametersKHR` returned while the driver
/// went on dereferencing it at every decode. The recycled bytes read as
/// `mono_chrome = 1`, and a monochrome frame skips exactly `loop_filter_level[2..3]`
/// (AV1 7.14) — which is why rewriting them changed nothing, and why the
/// fingerprint was a perfect match for levels that were never applied.
/// `pf-vkdecode`'s `session_av1` module docs carry the capture and the fix.
///
/// So the probe survives its own refutation, with its verdict inverted: it now
/// PASSES, and it is the cheapest guard there is against that whole class coming
/// back. It decodes frame 0 twice — once from the vector as it sits, once from the
/// same unit with both chroma levels rewritten to the strongest AV1 can code — and
/// requires the pixels to differ. In software that rewrite moves 793 chroma bytes
/// with `max |delta| 29`, which no readback or crop error could hide, and it leaves
/// luma bit-identical, which is the control: a mutation that changed luma would
/// have desynchronised the header rather than changed the field.
///
/// If the two decodes are ever IDENTICAL again, the message below is the one to
/// act on — and the FIRST thing to check is not the driver but whether some block
/// the decode op points at is being freed before the op is recorded. That is what
/// it was last time, on a bug this test could not see.
#[test]
#[ignore = "needs a Vulkan Video AV1 decode device (fleet boxes; see module docs)"]
fn av1_frame0_probes_whether_the_driver_reads_the_chroma_deblocking_levels() {
    let _gpu = common::gpu_lock();
    // SAFETY: `_gpu` holds the binary-wide GPU lock (`common::gpu_lock`); the parity legs are
    // this variable's only writers and readers, and they run one at a time under that lock.
    unsafe { std::env::set_var("PF_VKD_TEST_READBACK", "1") };

    let aus = common::split_av1_aus(common::TEST_25FPS_AV1);
    assert_eq!(aus.len(), FRAME_COUNT);

    let mutated_au = av1_frame0_with_max_chroma_deblocking(aus[0]);
    let mut units: Vec<&[u8]> = aus.clone();
    units[0] = &mutated_au;

    let coded = av1_first_frame(&aus);
    let maxed = av1_first_frame(&units);

    let luma = (DISPLAY_AV1.0 * DISPLAY_AV1.1) as usize;
    eprintln!("  coded chroma levels [8, 12]  {}", sha256_hex(&coded));
    eprintln!("  chroma levels [63, 63]       {}", sha256_hex(&maxed));
    eprintln!("  libavcodec's frame 0         {}", sha256_hex(AV1_FRAME0));
    assert_eq!(
        coded[..luma],
        maxed[..luma],
        "the chroma deblocking levels must not move a luma sample — if they did, \
         the mutation desynchronised the frame header and the chroma comparison \
         below means nothing"
    );
    assert_ne!(
        coded[luma..],
        maxed[luma..],
        "the driver produced the SAME chroma from loop_filter_level[2..3] = [8, 12] \
         and from [63, 63]. This happened once before and the driver was INNOCENT: \
         a monochrome-looking sequence header makes it skip both levels, and ours \
         looked monochrome because its `pColorConfig` block had been freed and \
         reused before the decode op was recorded (see this test's docs). So audit \
         the LIFETIME of everything the submission points at — the Std sequence \
         header behind the parameters object first — before blaming the vendor"
    );
    eprintln!("the driver reads the chroma deblocking levels — the two decodes differ");
}

/// The vector's first access unit with both CHROMA deblocking levels rewritten to
/// [`MAX_LOOP_FILTER_LEVEL`] — and the proof, through the real parser, that this is
/// the only thing it changed.
///
/// The proof is not decoration. The offsets are derived from the spec's syntax
/// order rather than searched for, and a rewrite landing one field over would
/// desynchronise nothing (both neighbours are fixed-width) while silently probing
/// the wrong parameter. So every block the conversion reads is compared before and
/// after, and [`the_av1_chroma_deblocking_mutation_changes_only_those_two_levels`]
/// runs this on CPU in ordinary CI — the hardware run cannot be spent discovering
/// that the mutation was wrong.
fn av1_frame0_with_max_chroma_deblocking(au: &[u8]) -> Vec<u8> {
    let mut mutated = au.to_vec();
    for bit in [AV1_FRAME0_FILTER_LEVEL_U_BIT, AV1_FRAME0_FILTER_LEVEL_V_BIT] {
        set_bits(&mut mutated, bit, 6, MAX_LOOP_FILTER_LEVEL);
    }

    let before = av1_first_header(au);
    let after = av1_first_header(&mutated);
    assert_eq!(
        before.loop_filter_params.loop_filter_level,
        [1, 7, 8, 12],
        "the vendored vector's frame 0 codes these levels, and the whole probe is \
         built around the last two of them"
    );
    assert_eq!(
        after.loop_filter_params.loop_filter_level,
        [1, 7, MAX_LOOP_FILTER_LEVEL, MAX_LOOP_FILTER_LEVEL],
        "the rewrite must land on the two CHROMA levels and leave the luma pair \
         alone — a luma change would make the probe's control meaningless"
    );
    // Everything else the conversion reads, unchanged: a rewrite that shifted the
    // header would show up in one of these long before it showed up in pixels.
    assert_eq!(
        after.cdef_params, before.cdef_params,
        "the CDEF block follows the loop filter block and is what a shifted rewrite \
         would corrupt first"
    );
    assert_eq!(after.quantization_params, before.quantization_params);
    assert_eq!(after.tile_info, before.tile_info);
    assert_eq!(
        after.loop_restoration_params,
        before.loop_restoration_params
    );
    assert_eq!(after.segmentation_params, before.segmentation_params);
    assert_eq!(
        (
            after.loop_filter_params.loop_filter_sharpness,
            after.loop_filter_params.loop_filter_ref_deltas,
            after.loop_filter_params.loop_filter_mode_deltas,
        ),
        (
            before.loop_filter_params.loop_filter_sharpness,
            before.loop_filter_params.loop_filter_ref_deltas,
            before.loop_filter_params.loop_filter_mode_deltas,
        ),
        "the rest of the loop filter block rides after the levels and must survive"
    );
    assert_ne!(mutated, au, "the rewrite must actually change bytes");
    mutated
}

/// [`av1_frame0_with_max_chroma_deblocking`] on CPU, so the GPU probe's mutation is
/// known-good before any device time is spent on it.
#[test]
fn the_av1_chroma_deblocking_mutation_changes_only_those_two_levels() {
    let aus = common::split_av1_aus(common::TEST_25FPS_AV1);
    let mutated = av1_frame0_with_max_chroma_deblocking(aus[0]);
    // One byte may carry bits of both fields (U ends mid-byte), so the rewrite
    // touches two or three bytes and no more — a whole-unit difference would mean
    // `set_bits` walked off its field.
    let changed = aus[0].iter().zip(&mutated).filter(|(a, b)| a != b).count();
    assert!(
        (1..=3).contains(&changed),
        "twelve bits spanning at most three bytes, and {changed} bytes changed"
    );
}

/// Overwrite the `bits`-wide big-endian bitfield at `bit` in `data`.
///
/// AV1's `f(n)` is MSB-first from the start of the OBU payload, which is what the
/// probe above needs to rewrite a syntax element in place: same width, same
/// position, so nothing after it shifts.
fn set_bits(data: &mut [u8], bit: usize, bits: usize, value: u8) {
    for i in 0..bits {
        let at = bit + i;
        let mask = 1u8 << (7 - (at % 8));
        let set = (value >> (bits - 1 - i)) & 1 == 1;
        if set {
            data[at / 8] |= mask;
        } else {
            data[at / 8] &= !mask;
        }
    }
}

/// The parsed frame header of the FIRST frame in one access unit.
fn av1_first_header(au: &[u8]) -> pf_bitstream::av1::ParsedFrameHeader {
    let mut planner = pf_bitstream::av1::Av1Planner::new();
    let plans = planner.plan_au(au).expect("the unit plans");
    let plan = plans.first().expect("the unit carries a frame");
    (*plan.header).clone()
}

/// Decode `aus` only as far as the FIRST delivered frame, and read it back as
/// tightly packed NV12 — the device half of both frame-0 legs.
///
/// Brings its own device up and tears it down, so one test may call it more than
/// once; the GPU lock and the readback hook are the caller's.
fn av1_first_frame(aus: &[&[u8]]) -> Vec<u8> {
    let setup = common::bring_up(&common::Request {
        codec: common::AV1,
        graphics: common::Graphics::Required,
        report_families: true,
    });
    let handles = setup.handles();

    let ours = {
        // SAFETY: as the parity legs — `setup` outlives this block and was created
        // with the AV1 decode extension + timeline/sync2 features.
        let mut decoder = unsafe { VkAv1Decoder::new(&handles, Box::new(NoopQueueLock)) }
            .expect("wrap the device");
        decoder
            .probe_stream_support(1, 8, false)
            .expect("the box must host AV1 Main 4:2:0 8-bit, no film grain");
        // SAFETY: as the parity legs — live instance/device, queue 0 of `graphics_qf`.
        let readback = unsafe {
            Readback::new(
                &setup.instance,
                setup.pd,
                &setup.device,
                setup.graphics_qf,
                DISPLAY_AV1,
                EXPECTED_FORMAT,
            )
        };

        // The FIRST delivered frame and no further: the first temporal unit is a
        // key frame that shows, so this is one decode.
        let mut first: Option<Vec<u8>> = None;
        for (index, au) in aus.iter().enumerate() {
            let frame = decoder
                .decode(au)
                .unwrap_or_else(|e| panic!("AU {index}: decode failed: {e}"));
            if let Some(frame) = frame {
                assert_eq!(
                    decoder.wait_status(&frame),
                    DecodeStatus::Ok,
                    "frame 0: decode op not COMPLETE\n  state: {}",
                    decoder.debug_snapshot()
                );
                assert_eq!(frame.format, EXPECTED_FORMAT, "frame 0: pool format");
                // SAFETY: the frame is delivered and unreleased on the readback's
                // device, the pool carries TRANSFER_SRC, and the test is serialized.
                first = Some(unsafe { readback.read_nv12(&frame) });
                decoder
                    .release_frame(&frame, true)
                    .expect("frame 0: release");
                // A temporal unit may carry more than one frame, and this leg stops
                // at the first. Anything else the unit made ready is handed straight
                // back — with `false`, because no presenter signalled its timeline
                // (nothing read it) — rather than left held while the decoder drops.
                while let Some(spare) = decoder.take_ready() {
                    decoder
                        .release_frame(&spare, false)
                        .expect("release an unread frame of the same temporal unit");
                }
                break;
            }
        }
        // SAFETY: every readback was fence-waited inside `read_nv12`.
        unsafe { readback.destroy() };
        first.expect("the vector's first temporal unit shows a frame")
    };

    // SAFETY: as the parity legs — the decoder and readback are gone.
    unsafe { setup.destroy() };
    ours
}

/// Per-plane statistics of `ours` against `want`, printed rather than asserted.
///
/// Everything here answers a question a hash cannot: WHICH plane, whether the
/// difference is a displacement or a value error, and how big. See
/// [`av1_frame0_pixels_say_which_plane_and_how_badly`] for how to read it.
fn report_nv12_divergence(ours: &[u8], want: &[u8], display: (u32, u32)) {
    let (width, height) = (display.0 as usize, display.1 as usize);
    let luma = width * height;
    assert_eq!(ours.len(), want.len(), "both frames are the same layout");
    assert_eq!(ours.len(), luma * 3 / 2, "tightly packed NV12");

    eprintln!(
        "--- AV1 frame 0: {width}x{height} NV12, {} bytes ---",
        ours.len()
    );
    eprintln!("  ours   {}", sha256_hex(ours));
    eprintln!("  golden {}", sha256_hex(want));

    // A plane that never varies means nothing was decoded into the image at all,
    // which is a different failure from decoding it wrongly.
    let flat = |plane: &[u8]| plane.iter().all(|b| *b == plane[0]);
    if flat(&ours[..luma]) {
        eprintln!(
            "  ⚠ our LUMA is constant ({}) — nothing decoded here",
            ours[0]
        );
    }
    if flat(&ours[luma..]) {
        eprintln!(
            "  ⚠ our CHROMA is constant ({}) — nothing decoded here",
            ours[luma]
        );
    }

    for (name, ours, want) in [
        ("luma  ", &ours[..luma], &want[..luma]),
        ("chroma", &ours[luma..], &want[luma..]),
    ] {
        if ours == want {
            eprintln!("  {name}: IDENTICAL ({} bytes)", ours.len());
            continue;
        }
        let mut differing = 0usize;
        let mut max_delta = 0u32;
        let mut total_delta = 0u64;
        // |delta| buckets: 1, 2, 3-4, 5-8, 9-16, 17-64, 65+.
        let mut buckets = [0usize; 7];
        let mut first: Vec<(usize, u8, u8)> = Vec::new();
        for (i, (a, b)) in ours.iter().zip(want.iter()).enumerate() {
            if a == b {
                continue;
            }
            let delta = u32::from(a.abs_diff(*b));
            differing += 1;
            max_delta = max_delta.max(delta);
            total_delta += u64::from(delta);
            let bucket = match delta {
                1 => 0,
                2 => 1,
                3..=4 => 2,
                5..=8 => 3,
                9..=16 => 4,
                17..=64 => 5,
                _ => 6,
            };
            buckets[bucket] += 1;
            if first.len() < 8 {
                first.push((i, *a, *b));
            }
        }
        let percent = 100.0 * differing as f64 / ours.len() as f64;
        eprintln!(
            "  {name}: {differing}/{} bytes differ ({percent:.2}%), max |delta| {max_delta}, \
             mean |delta| over the differing bytes {:.2}",
            ours.len(),
            total_delta as f64 / differing as f64
        );
        eprintln!(
            "    |delta| histogram  1:{} 2:{} 3-4:{} 5-8:{} 9-16:{} 17-64:{} 65+:{}",
            buckets[0], buckets[1], buckets[2], buckets[3], buckets[4], buckets[5], buckets[6]
        );
        // One ROW is `width` bytes in both planes — luma because it is `width`
        // samples wide, interleaved chroma because it is `width / 2` samples wide
        // and two bytes per sample. So one formula serves both, and the chroma
        // coordinates it prints are in chroma units.
        let positions: Vec<String> = first
            .iter()
            .map(|(i, a, b)| format!("(x{},y{}) {a}≠{b}", i % width, i / width))
            .collect();
        eprintln!("    first differing: {}", positions.join("  "));
    }

    // A displacement, not a difference: does our luma equal the reference read a
    // few rows or columns over? That is what a wrong crop origin or a copy extent
    // taken from the pool rather than the render region looks like, and the shift
    // that matches IS the error.
    if ours[..luma] != want[..luma] {
        let mut best: Option<(i32, i32, f64)> = None;
        for dy in -4i32..=4 {
            for dx in -8i32..=8 {
                if (dy, dx) == (0, 0) {
                    continue;
                }
                let (mut hit, mut seen) = (0usize, 0usize);
                for y in 8..height - 8 {
                    for x in 8..width - 8 {
                        let sy = (y as i32 + dy) as usize;
                        let sx = (x as i32 + dx) as usize;
                        seen += 1;
                        if ours[y * width + x] == want[sy * width + sx] {
                            hit += 1;
                        }
                    }
                }
                let score = hit as f64 / seen as f64;
                if best.is_none_or(|(_, _, b)| score > b) {
                    best = Some((dy, dx, score));
                }
            }
        }
        // The identity's own score, for scale: a decode that is merely slightly
        // wrong still matches most bytes in place, so a shift only means something
        // when it beats staying put.
        let (mut hit, mut seen) = (0usize, 0usize);
        for y in 8..height - 8 {
            for x in 8..width - 8 {
                seen += 1;
                if ours[y * width + x] == want[y * width + x] {
                    hit += 1;
                }
            }
        }
        let identity = hit as f64 / seen as f64;
        if let Some((dy, dx, score)) = best {
            eprintln!(
                "  luma shift probe: in place {:.3} · best shift dy{dy:+} dx{dx:+} {score:.3}{}",
                identity,
                if score > identity + 0.05 {
                    "  ⚠ A SHIFT FITS BETTER — this is readback geometry, not decode"
                } else {
                    "  (no shift fits better: the pixels are in the right place and \
                     carry the wrong values)"
                }
            );
        }
    }
}

// ---------------------------------------------------------------------------
// CPU coherence guards — NOT `#[ignore]`d.
//
// The legs above only run on the fleet, so without these nothing in ordinary CI
// notices that a re-synced vendored vector, a golden regeneration or an edit to
// `common`'s AU splitters has made the two disagree. They would then fail on the
// fleet as a frame-count mismatch, which reads like a decoder defect and costs a
// hardware round trip to disprove.
//
// Each guard pins the whole chain the parity verdict rests on: the AU split, the
// planner's output count, the vector's shape and the golden set — with NO GPU
// involved. And they are what make the verdicts non-vacuous: a comparison of zero
// frames, or of 250 copies of one digest, would otherwise "pass" on any hardware
// (see [`assert_goldens_are_a_real_set`]).
// ---------------------------------------------------------------------------

#[test]
fn h265_goldens_and_au_split_agree_with_the_planner() {
    use pf_bitstream::h265::H265Planner;

    let goldens = golden_hashes(GOLDENS_H265);
    assert_goldens_are_a_real_set(&goldens, FRAME_COUNT, "data/test-25fps-h265.nv12.sha256");

    // The AU split the parity leg feeds the decoder. `common::split_h265_aus` is
    // the copy of pf-bitstream's private splitter, and it keys on HEVC's 2-byte
    // NAL header — a `+ 1` there (H.264's offset) silently merges or splits AUs.
    let aus = common::split_h265_aus(common::TEST_25FPS_H265);
    assert_eq!(
        aus.len(),
        FRAME_COUNT,
        "the vendored H.265 vector is {FRAME_COUNT} access units \
         (pf-bitstream's own planner test pins the same number)"
    );

    // Walk the CPU planner over the same AUs: it is the authority on how many
    // frames the GPU leg can possibly deliver, because the decoder builds exactly
    // one delivered frame per `dpb.outputs` id (plus the flush tail).
    let mut planner = H265Planner::new();
    let mut outputs = 0usize;
    let mut iraps = 0usize;
    for (index, au) in aus.iter().enumerate() {
        let plan = planner.plan_au(au).unwrap_or_else(|e| {
            panic!(
                "AU {index}: the clean vector must plan without errors, got {e:?} \
                 — if this is RaslSkipped the vector has gained CRA/RASL pictures \
                 and the parity legs' expected frame count needs rederiving"
            );
        });
        outputs += plan.dpb.outputs.len();
        iraps += usize::from(plan.picture.is_irap);
        // Pin the picture shape the H.265 legs hard-code. They call
        // `probe_stream_support(1, 0)` (4:2:0, 8-bit) and assert the NV12 output
        // format; a re-synced Main-10 or 4:4:4 vector would make both of those
        // silently probe and expect the WRONG profile on the fleet, which is a
        // confusing hardware-only failure. Fail here, on CPU, with the reason.
        assert_eq!(
            (
                plan.picture.chroma_format_idc,
                plan.picture.bit_depth_luma_minus8
            ),
            (1, 0),
            "AU {index}: the vendored H.265 vector must stay Main 4:2:0 8-bit — \
             the parity and smoke legs hard-code probe_stream_support(1, 0) and \
             an NV12 output format, so a re-synced vector of another shape needs \
             both legs updated, not just the goldens"
        );
        if index == 0 {
            assert!(plan.picture.is_idr, "the vector opens with an IDR");
            assert_eq!(
                (plan.picture.coded_width, plan.picture.coded_height),
                DISPLAY_H265,
                "the vector is 320x240"
            );
            assert_eq!(
                (
                    plan.picture.display_crop.x,
                    plan.picture.display_crop.y,
                    plan.picture.display_crop.width,
                    plan.picture.display_crop.height,
                ),
                (0, 0, DISPLAY_H265.0, DISPLAY_H265.1),
                "the vector carries NO conformance window — coded size IS display \
                 size (the golden header's claim, and what `Readback` asserts)"
            );
        }
    }
    outputs += planner.flush().outputs.len();

    assert_eq!(
        outputs,
        goldens.len(),
        "the planner outputs {outputs} pictures but the goldens carry {} hashes — \
         the parity leg's frame-count assertion would fail on hardware for a \
         reason that has nothing to do with the GPU",
        goldens.len()
    );
    // No CRA/BLA anywhere means `PlanError::RaslSkipped` — the Ok-skip that
    // returns `Ok(None)` rather than an error (h265 module docs, and
    // `VkH265Decoder::decode`'s RASL arm) — is UNREACHABLE on this vector, so the
    // count above cannot be perturbed by it. If a re-synced vector ever opens with
    // a CRA, this assertion fires first and says where to look.
    assert_eq!(
        iraps, 1,
        "the vector holds exactly one IRAP (the opening IDR); a CRA/BLA would make \
         RASL skips reachable and the expected frame count needs rederiving"
    );
}

#[test]
fn h264_goldens_and_au_split_agree_with_the_planner() {
    use pf_bitstream::h264::H264Planner;

    let goldens = golden_hashes(GOLDENS_H264);
    assert_goldens_are_a_real_set(&goldens, FRAME_COUNT, "data/test-25fps.nv12.sha256");

    let aus = common::split_h264_aus(common::TEST_25FPS_H264);
    assert_eq!(
        aus.len(),
        FRAME_COUNT,
        "the vendored H.264 vector is {FRAME_COUNT} access units"
    );

    let mut planner = H264Planner::new();
    let mut outputs = 0usize;
    for (index, au) in aus.iter().enumerate() {
        let plan = planner
            .plan_au(au)
            .unwrap_or_else(|e| panic!("AU {index}: the clean vector must plan, got {e:?}"));
        outputs += plan.dpb.outputs.len();
    }
    outputs += planner.flush().outputs.len();
    assert_eq!(
        outputs,
        goldens.len(),
        "the planner outputs {outputs} pictures but the goldens carry {} hashes",
        goldens.len()
    );
}

/// The low-delay stream's own CPU guard, plus the property that makes it worth
/// vendoring at all.
///
/// The goldens/AU/output agreement is the same three-way check
/// [`h264_goldens_and_au_split_agree_with_the_planner`] does. What is extra here is
/// the last assertion: this stream must actually REACH the aliasing precondition —
/// a picture removed by the same access unit whose `dpb_refs` still names it — on
/// nearly every access unit. If a re-generation ever produced a stream that did not,
/// the GPU leg above would still pass 120/120 while proving nothing the vendored
/// vector does not already prove, and nothing else would say so.
#[test]
fn the_low_delay_stream_agrees_with_its_goldens_and_still_exercises_the_aliasing_shape() {
    use pf_bitstream::h264::H264Planner;

    let goldens = golden_hashes(GOLDENS_LOWDELAY);
    assert_goldens_are_a_real_set(
        &goldens,
        LOWDELAY_FRAME_COUNT,
        "data/lowdelay-640x480.nv12.sha256",
    );

    let aus = common::split_h264_aus(LOWDELAY_H264);
    assert_eq!(aus.len(), LOWDELAY_FRAME_COUNT);

    let mut planner = H264Planner::new();
    let mut outputs = 0usize;
    let mut both = 0usize;
    let mut first_sps = None;
    for (index, au) in aus.iter().enumerate() {
        let plan = planner
            .plan_au(au)
            .unwrap_or_else(|e| panic!("AU {index}: the low-delay stream must plan, got {e:?}"));
        outputs += plan.dpb.outputs.len();
        both += plan
            .dpb
            .removed
            .iter()
            .filter(|id| plan.dpb_refs.iter().any(|r| r.id == **id))
            .count();
        first_sps.get_or_insert((
            plan.sps.max_num_ref_frames,
            plan.picture.max_dpb_frames,
            plan.sps.vui_parameters.max_num_reorder_frames,
        ));
    }
    outputs += planner.flush().outputs.len();
    assert_eq!(
        outputs,
        goldens.len(),
        "the planner outputs {outputs} pictures but the goldens carry {} hashes",
        goldens.len()
    );

    // The three SPS facts that make the shape reachable, pinned so a regenerated
    // stream from a different encoder cannot quietly stop being low-delay.
    assert_eq!(
        first_sps,
        Some((3, 3, 0)),
        "max_num_ref_frames, DPB depth and max_num_reorder_frames — a DPB exactly as \
         deep as the reference count, with no reordering, is what puts the unmarking \
         and the eviction in one access unit"
    );
    assert_eq!(
        both, 117,
        "the stream must still remove pictures its own reference lists name — that is \
         the ONLY reason it is vendored, and without it the GPU leg is a duplicate of \
         the conformance one"
    );
}

/// The HEVC low-delay stream's CPU guard — the twin of the H.264 one above, with the
/// extra assertion HEVC needs and H.264 does not.
///
/// H.264's guard pins that the stream still ALIASES (117 of 120), because its GPU leg
/// exists to catch a defect. HEVC's leg exists to keep an exemption from rotting, so
/// pinning `both == 0` alone would be exactly the vacuous check `fd6241a2` called out:
/// zero is also what a stream that never removes anything reports, and what a stream
/// that reorders reports. So this pins three numbers instead:
///
/// - **115 access units remove a picture** — the stream reaches the DPB pressure at all;
/// - **0 of them intersect `dpb_refs`** — the exemption, measured;
/// - **115 of them WOULD intersect** a snapshot taken before `decode_rps`.
///
/// The third is what makes the second worth having. `pre_rps_marked(N)` is exact rather
/// than approximate: `begin_picture` runs `decode_rps` → `update_dpb_before_decoding` →
/// `dpb_snapshot`, and the only thing that happens between AU N-1's snapshot and AU N's
/// `decode_rps` is `finish_picture(N-1)` storing its picture marked short-term. So the
/// marked set AU N's RPS sees is exactly `dpb_refs(N-1) ∪ {stored(N-1)}`, which is what
/// `dpb_snapshot()` would have returned from the other side of that call.
#[test]
fn the_low_delay_h265_stream_agrees_with_its_goldens_and_keeps_the_exemption_falsifiable() {
    use pf_bitstream::h265::H265Planner;

    let goldens = golden_hashes(GOLDENS_LOWDELAY_H265);
    assert_goldens_are_a_real_set(
        &goldens,
        LOWDELAY_H265_FRAME_COUNT,
        "data/lowdelay-640x480-h265.nv12.sha256",
    );

    let aus = common::split_h265_aus(LOWDELAY_H265);
    assert_eq!(aus.len(), LOWDELAY_H265_FRAME_COUNT);

    let mut planner = H265Planner::new();
    let mut outputs = 0usize;
    let mut iraps = 0usize;
    let mut with_removals = 0usize;
    let mut both = 0usize;
    let mut would_alias = 0usize;
    let mut first_sps = None;
    // The marked DPB as AU N's `decode_rps` finds it: AU N-1's snapshot plus the
    // picture AU N-1 stored. See the doc comment for why this is exact.
    let mut pre_rps_marked: Vec<u64> = Vec::new();
    for (index, au) in aus.iter().enumerate() {
        let plan = planner.plan_au(au).unwrap_or_else(|e| {
            panic!("AU {index}: the low-delay HEVC stream must plan, got {e:?}")
        });
        outputs += plan.dpb.outputs.len();
        iraps += usize::from(plan.picture.is_irap);
        if !plan.dpb.removed.is_empty() {
            with_removals += 1;
        }
        both += plan
            .dpb
            .removed
            .iter()
            .filter(|id| plan.dpb_refs.iter().any(|r| r.id == **id))
            .count();
        would_alias += plan
            .dpb
            .removed
            .iter()
            .filter(|id| pre_rps_marked.contains(id))
            .count();

        // The picture shape both HEVC legs hard-code: `probe_stream_support(1, 0)`
        // and an NV12 pool. Fail here, on CPU, rather than as a confusing
        // hardware-only refusal.
        assert_eq!(
            (
                plan.picture.chroma_format_idc,
                plan.picture.bit_depth_luma_minus8
            ),
            (1, 0),
            "AU {index}: the low-delay HEVC stream must stay Main 4:2:0 8-bit"
        );
        if index == 0 {
            assert!(plan.picture.is_idr, "the stream opens with an IDR");
            assert_eq!(
                (plan.picture.coded_width, plan.picture.coded_height),
                DISPLAY_LOWDELAY_H265,
                "the stream is 640x480"
            );
            assert_eq!(
                (
                    plan.picture.display_crop.x,
                    plan.picture.display_crop.y,
                    plan.picture.display_crop.width,
                    plan.picture.display_crop.height,
                ),
                (0, 0, DISPLAY_LOWDELAY_H265.0, DISPLAY_LOWDELAY_H265.1),
                "640 and 480 are both multiples of MinCbSizeY, so there is no \
                 conformance window and the coded size IS what the goldens hashed"
            );
        }
        first_sps.get_or_insert((
            plan.picture.max_dpb_frames,
            plan.sps.max_num_reorder_pics[usize::from(plan.sps.max_sub_layers_minus1)],
        ));

        pre_rps_marked = plan.dpb_refs.iter().map(|r| r.id).collect();
        if let Some(id) = plan.dpb.stored {
            assert!(
                plan.picture.is_reference,
                "AU {index}: every picture of this stream is a reference — a \
                 sub-layer non-reference picture would break the pre-RPS \
                 reconstruction below"
            );
            pre_rps_marked.push(id);
        }
    }
    outputs += planner.flush().outputs.len();

    assert_eq!(
        outputs,
        goldens.len(),
        "the planner outputs {outputs} pictures but the goldens carry {} hashes",
        goldens.len()
    );
    assert_eq!(
        iraps, 1,
        "the stream holds exactly one IRAP (the opening IDR); a CRA/BLA would make \
         RASL skips reachable and the expected frame count needs rederiving"
    );
    assert_eq!(
        first_sps,
        Some((5, 0)),
        "DPB depth and sps_max_num_reorder_pics — a five-picture DPB against the four \
         pictures 8.3.2 keeps marked, with no reordering, is what puts an RPS drop and \
         the eviction it causes in one access unit"
    );

    assert_eq!(
        with_removals, 115,
        "the stream must still retire a picture on nearly every access unit; without \
         that the two numbers below are both trivially zero"
    );
    assert_eq!(
        both, 0,
        "{both} picture(s) are in an access unit's own marked DPB AND removed by it. \
         That is the H.264/AV1 aliasing precondition, and HEVC is supposed to be \
         structurally incapable of it — `H265Planner`'s snapshot has moved ahead of \
         `decode_rps`. Restore the ordering, or give the HEVC conversions the \
         `release_after_decode` deferral the other two carry; do NOT relax this number"
    );
    assert_eq!(
        would_alias, 115,
        "the fixture must stay CAPABLE of exposing the defect it is here to rule out. \
         A regenerated stream that reordered, or that carried a DPB deeper than its \
         reference count, would report 0 here — and the zero above would then prove \
         nothing at all, exactly as `test-25fps.h264` proved nothing for two milestones"
    );
}

/// The AV1 low-delay stream's CPU guard, and the property it was vendored for: **more
/// than one tile**.
///
/// A regenerated fixture could lose that in two silent ways — a re-run at a lower
/// resolution (1440p and below are single-tile on this encoder) or a driver/encoder
/// change that stopped splitting — and in both cases the GPU leg would go on passing
/// 60/60 while duplicating what the vendored vector already covers. So the tile shape
/// is asserted per frame, not sampled.
///
/// It also pins AV1's frame accounting explicitly rather than by derivation. The
/// vendored vector is 250 units / 274 coded / 24 hidden / 250 shown; this stream is
/// 60 / 60 / 0 / 60. Neither is the general case, and a leg that assumed either would
/// break on the other for reasons that look like a decoder defect.
#[test]
fn the_low_delay_av1_stream_agrees_with_its_goldens_and_still_carries_two_tiles() {
    use pf_bitstream::av1::Av1Planner;

    let goldens = golden_hashes(GOLDENS_LOWDELAY_AV1);
    assert_goldens_are_a_real_set(
        &goldens,
        LOWDELAY_AV1_FRAME_COUNT,
        "data/lowdelay-3840x2160-av1.nv12.sha256",
    );

    let aus = common::split_av1_aus(LOWDELAY_AV1);
    assert_eq!(
        aus.len(),
        LOWDELAY_AV1_UNIT_COUNT,
        "the low-delay AV1 stream is {LOWDELAY_AV1_UNIT_COUNT} temporal units"
    );
    assert!(
        aus.iter().all(|au| !au.is_empty()),
        "no temporal unit is empty — an IVF reader returning empty packets would make \
         the parity leg decode nothing and blame the decoder"
    );

    let mut planner = Av1Planner::new();
    let mut outputs = 0usize;
    let mut coded_frames = 0usize;
    let mut multi_frame_units = 0usize;
    let mut hidden = 0usize;
    let mut show_existing = 0usize;
    let mut keys = 0usize;
    let mut with_removals = 0usize;
    let mut aliasing_shape = 0usize;
    for (index, au) in aus.iter().enumerate() {
        let plans = planner.plan_au(au).unwrap_or_else(|e| {
            panic!("temporal unit {index}: the low-delay stream must plan, got {e:?}")
        });
        if plans.len() > 1 {
            multi_frame_units += 1;
        }
        for plan in &plans {
            coded_frames += 1;
            outputs += plan.dpb.outputs.len();
            keys += usize::from(plan.picture.is_key);
            hidden += usize::from(!plan.picture.show_frame);
            if plan.dpb.stored.is_none() {
                show_existing += 1;
            }
            assert!(
                plan.warnings.is_empty(),
                "temporal unit {index}: a clean stream plans without warnings, got {:?}",
                plan.warnings
            );

            // THE PROPERTY. Two tile ROWS, one tile COLUMN, both tiles in a single
            // Tile Group OBU — the 4K split-encode shape, on every frame including
            // the key frame.
            let tile = &plan.header.tile_info;
            assert_eq!(
                (tile.tile_cols, tile.tile_rows),
                (1, 2),
                "frame {coded_frames} (unit {index}): this fixture exists because our \
                 encoder emits TWO TILE ROWS at 4K. A single-tile stream here means it \
                 was regenerated at a lower resolution (1440p and below measured \
                 single-tile) or the encoder stopped splitting — either way the GPU leg \
                 below is now a duplicate of the vendored vector's and this fixture's \
                 260 KB buys nothing. Regenerate at 3840x2160; do NOT relax this"
            );
            assert_eq!(
                (
                    tile.width_in_sbs_minus_1[0],
                    tile.height_in_sbs_minus_1[0],
                    tile.height_in_sbs_minus_1[1],
                ),
                (59, 16, 16),
                "frame {coded_frames}: the per-tile superblock sizing the conversions \
                 copy into their tile arrays"
            );
            assert_eq!(
                plan.tiles.len(),
                1,
                "frame {coded_frames}: both tiles ride in ONE Tile Group OBU"
            );
            assert_eq!(
                (plan.tiles[0].tg_start, plan.tiles[0].tg_end),
                (0, 1),
                "frame {coded_frames}: the single tile group covers tiles 0..=1 — a \
                 range of 0..=0 is the truncation shape the host once shipped"
            );

            // The picture shape both AV1 legs hard-code (`probe_stream_support(1, 8,
            // false)` plus an NV12 pool). Film grain especially: it is part of the
            // Vulkan decode PROFILE, so a grain-bearing stream is a different device
            // requirement, not merely different pixels.
            assert_eq!(
                (
                    plan.picture.chroma_format_idc,
                    plan.picture.bit_depth,
                    plan.sequence.film_grain_params_present,
                ),
                (1, 8, false),
                "frame {coded_frames}: Main 4:2:0 8-bit, no film grain"
            );
            if coded_frames == 1 {
                assert!(plan.picture.is_key, "the stream opens on a key frame");
                assert_eq!(
                    (plan.picture.render_width, plan.picture.render_height),
                    DISPLAY_LOWDELAY_AV1,
                    "the render region the readback crops to and the goldens hash"
                );
                assert_eq!(
                    (plan.picture.upscaled_width, plan.picture.frame_height),
                    DISPLAY_LOWDELAY_AV1,
                    "no superres and no AV1 conformance-window equivalent — the coded \
                     picture IS the render region"
                );
            }

            if !plan.dpb.removed.is_empty() {
                with_removals += 1;
            }
            aliasing_shape += plan
                .dpb
                .removed
                .iter()
                .filter(|id| plan.dpb_refs.iter().any(|r| r.id == **id))
                .count();
        }
    }

    // AV1's frame accounting, pinned rather than derived. This stream is the SIMPLE
    // shape — one shown frame per temporal unit — which is exactly why it must be
    // stated: the vendored vector is not, and a leg that learned its habits from one
    // of them silently mis-counts the other.
    assert_eq!(
        (
            coded_frames,
            outputs,
            multi_frame_units,
            hidden,
            show_existing,
            keys
        ),
        (
            LOWDELAY_AV1_FRAME_COUNT,
            LOWDELAY_AV1_FRAME_COUNT,
            0,
            0,
            0,
            1
        ),
        "coded / displayed / multi-frame units / hidden / show_existing / key frames — \
         our host emits one shown frame per temporal unit and one key frame at the \
         head, against the vendored vector's 274 / 250 / 24 / 24 / 0 / 1"
    );
    assert_eq!(
        outputs,
        goldens.len(),
        "the planner outputs {outputs} pictures but the goldens carry {}",
        goldens.len()
    );

    // Not the reason this fixture exists — the vendored vector already aliases on 268
    // of its 274 frames — but recorded so a regeneration cannot quietly drop below the
    // vector's coverage while claiming to be the host-shaped stream.
    assert_eq!(
        (with_removals, aliasing_shape),
        (55, 55),
        "55 of the 60 frames displace a reference they still name, which is the \
         precondition `release_after_decode` exists for"
    );
}

#[test]
fn the_main10_vector_is_ten_bit_and_agrees_with_its_goldens() {
    use pf_bitstream::h265::H265Planner;

    let goldens = golden_hashes(GOLDENS_MAIN10);
    assert_goldens_are_a_real_set(&goldens, MAIN10_FRAME_COUNT, "data/test-main10.p010.sha256");

    let aus = common::split_h265_aus(TEST_MAIN10_H265);
    assert_eq!(
        aus.len(),
        MAIN10_FRAME_COUNT,
        "the Main 10 vector is {MAIN10_FRAME_COUNT} access units"
    );

    let mut planner = H265Planner::new();
    let mut outputs = 0usize;
    for (index, au) in aus.iter().enumerate() {
        let plan = planner.plan_au(au).unwrap_or_else(|e| {
            panic!("AU {index}: the Main 10 vector must plan without errors, got {e:?}")
        });
        // The whole reason this vector exists. Every other golden set in this
        // program is eight-bit; a regenerated vector that came out eight-bit would
        // turn the ten-bit parity leg into a second run of the eight-bit path, and
        // it would PASS, because its goldens would have been regenerated with it.
        assert_eq!(
            (
                plan.picture.chroma_format_idc,
                plan.picture.bit_depth_luma_minus8,
                plan.picture.bit_depth_chroma_minus8,
            ),
            (1, 2, 2),
            "AU {index}: the Main 10 vector must stay 4:2:0 at ten bits"
        );
        if index == 0 {
            assert!(plan.picture.is_idr, "the vector opens with an IDR");
            assert_eq!(
                (plan.picture.coded_width, plan.picture.coded_height),
                (320, 240),
                "the goldens hash a 320x240 picture"
            );
        }
        outputs += plan.dpb.outputs.len();
    }
    outputs += planner.flush().outputs.len();
    assert_eq!(
        outputs,
        goldens.len(),
        "the planner outputs {outputs} pictures but the goldens carry {}",
        goldens.len()
    );
}

/// The AV1 leg's whole chain, with no GPU: the golden set, the IVF split, the shape
/// the leg hard-codes, and — the one that matters — that **250 goldens is the
/// DISPLAY count of a 274-frame vector**, re-derived from the planner rather than
/// asserted from a comment.
///
/// Every number here is a way the fleet run could otherwise fail for a reason that
/// is not the decoder:
///
/// - a golden file regenerated per CODED frame would carry 274 hashes and the leg
///   would report a frame-count mismatch that reads exactly like dropped frames;
/// - an IVF reader that lost packets would feed a short AU list and the leg would
///   report the same thing;
/// - a re-synced vector at another bit depth, sampling, or with film grain would
///   make `probe_stream_support(1, 8, false)` probe the WRONG Vulkan profile and the
///   readback expect the wrong format, which on hardware surfaces as a caps refusal
///   or half a hashed picture;
/// - and if the 24 hidden frames ever disappeared, the leg would still pass while
///   having quietly stopped exercising multi-frame temporal units at all — the one
///   thing AV1 has that neither H.26x vector does.
#[test]
fn av1_goldens_and_the_ivf_split_agree_with_the_planner() {
    use pf_bitstream::av1::Av1Planner;

    let goldens = golden_hashes(GOLDENS_AV1);
    assert_goldens_are_a_real_set(&goldens, FRAME_COUNT, "data/test-25fps-av1.nv12.sha256");

    // The AU split the parity leg feeds the decoder: one IVF packet per temporal
    // unit. AV1 carries no start codes, so this is the container's framing rather
    // than something a scan could get subtly wrong — but a truncated or re-muxed
    // vector would still shorten it silently.
    let aus = common::split_av1_aus(common::TEST_25FPS_AV1);
    assert_eq!(
        aus.len(),
        FRAME_COUNT,
        "the vendored AV1 vector is {FRAME_COUNT} temporal units"
    );
    assert!(
        aus.iter().all(|au| !au.is_empty()),
        "no temporal unit is empty — an IVF reader that returned empty packets would \
         make the parity leg decode nothing and blame the decoder"
    );

    // Walk the CPU planner over the same temporal units. It is the authority on how
    // many frames the GPU leg can possibly deliver: the decoder builds exactly one
    // delivered frame per `dpb.outputs` id, and AV1's planner has no `flush` tail.
    let mut planner = Av1Planner::new();
    let mut outputs = 0usize;
    let mut coded_frames = 0usize;
    let mut multi_frame_units = 0usize;
    let mut show_existing = 0usize;
    let mut warnings = 0usize;
    for (index, au) in aus.iter().enumerate() {
        let plans = planner.plan_au(au).unwrap_or_else(|e| {
            panic!("temporal unit {index}: the clean vector must plan without errors, got {e:?}")
        });
        if plans.len() > 1 {
            multi_frame_units += 1;
        }
        for plan in &plans {
            coded_frames += 1;
            outputs += plan.dpb.outputs.len();
            warnings += plan.warnings.len();
            // A `show_existing_frame` decodes nothing and stores nothing.
            if plan.dpb.stored.is_none() {
                show_existing += 1;
            }
            // Pin the picture shape both AV1 legs hard-code. They call
            // `probe_stream_support(1, 8, false)` and assert an NV12 output format;
            // a re-synced Main-10, 4:4:4 or film-grain vector would make both
            // silently probe and expect the WRONG Vulkan decode profile on the
            // fleet — grain synthesis is part of the PROFILE, not a per-frame
            // toggle, so a grain-bearing vector is a different device requirement,
            // not merely different pixels.
            assert_eq!(
                (
                    plan.picture.chroma_format_idc,
                    plan.picture.bit_depth,
                    plan.sequence.film_grain_params_present,
                ),
                (1, 8, false),
                "frame {coded_frames} (temporal unit {index}): the vendored AV1 vector \
                 must stay Main 4:2:0 8-bit with no film grain"
            );
            if coded_frames == 1 {
                assert!(plan.picture.is_key, "the vector opens on a key frame");
                // What `Readback` crops to, and what the goldens hash.
                assert_eq!(
                    (plan.picture.render_width, plan.picture.render_height),
                    DISPLAY_AV1,
                    "the display (render) region the readback asserts against"
                );
                // …and what the pool allocates. Equal to the render region here, so
                // the vector needs no AV1 conformance-window equivalent — the golden
                // header's claim.
                assert_eq!(
                    (plan.picture.upscaled_width, plan.picture.frame_height),
                    DISPLAY_AV1,
                    "the decoded (post-superres) picture IS the display region for \
                     this vector — coded size and render size coincide"
                );
            }
        }
    }

    assert_eq!(
        outputs,
        goldens.len(),
        "the planner outputs {outputs} pictures but the goldens carry {} hashes — the \
         parity leg's frame-count assertion would fail on hardware for a reason that \
         has nothing to do with the GPU",
        goldens.len()
    );
    assert_eq!(
        coded_frames,
        AV1_CODED_FRAME_COUNT,
        "the vendored AV1 vector codes {AV1_CODED_FRAME_COUNT} frames; {} of them are \
         hidden, which is why the goldens are {FRAME_COUNT} and not {coded_frames}",
        AV1_CODED_FRAME_COUNT - FRAME_COUNT
    );
    assert_eq!(
        multi_frame_units, 24,
        "24 temporal units carry two frames each — the hidden ALTREFs, and the only \
         reason AV1's `plan_au` returns a vector at all. If this reaches 0 the parity \
         leg has stopped exercising multi-frame temporal units while still passing"
    );
    assert_eq!(
        show_existing, 0,
        "this vector uses no `show_existing_frame`; if that ever changes, frames start \
         being displayed by a route the decoder handles differently and the display \
         order the goldens assume needs rederiving"
    );
    assert_eq!(
        warnings, 0,
        "a clean conformance vector must plan without concealment — any warning here \
         means the parity leg would be hashing concealed pixels against a clean \
         reference"
    );
}

/// The vendored frame-0 pixels ARE the first golden — not a second opinion about it.
///
/// [`AV1_FRAME0`] is the one place in this file where reference PIXELS live rather
/// than hashes, and pixels are exactly the kind of file that rots: regenerate the
/// goldens from a re-synced vector and this blob keeps describing the old one, while
/// the diagnostic leg that reads it goes on confidently naming the wrong cause. So
/// its digest is re-derived here and compared against `GOLDENS_AV1`'s first line —
/// the trusted, three-way cross-checked set — on every platform, with no GPU.
///
/// It also pins the layout the diagnostic's arithmetic assumes: 320x240 tightly
/// packed NV12 is 115200 bytes, luma first.
#[test]
fn the_av1_frame0_reference_is_the_first_golden() {
    let (width, height) = (DISPLAY_AV1.0 as usize, DISPLAY_AV1.1 as usize);
    assert_eq!(
        AV1_FRAME0.len(),
        width * height * 3 / 2,
        "data/test-25fps-av1.frame0.nv12 must be one tightly packed NV12 frame of \
         the vector's render region"
    );
    let goldens = golden_hashes(GOLDENS_AV1);
    assert_goldens_are_a_real_set(&goldens, FRAME_COUNT, "data/test-25fps-av1.nv12.sha256");
    assert_eq!(
        sha256_hex(AV1_FRAME0),
        goldens[0],
        "the vendored frame-0 pixels must hash to the AV1 golden set's FIRST entry — \
         if they no longer do, the blob is from a different decode than the goldens \
         and `av1_frame0_pixels_say_which_plane_and_how_badly` would attribute a \
         divergence to the wrong cause. Regenerate it alongside the goldens: decode \
         the vector with `-f rawvideo -pix_fmt nv12 -fps_mode passthrough` and take \
         the first 115200 bytes (the golden file's header carries the full command)"
    );
    // Not a flat blob: a frame of one repeated byte would satisfy a length check and
    // make every per-plane statistic in the diagnostic meaningless.
    let luma = &AV1_FRAME0[..width * height];
    let chroma = &AV1_FRAME0[width * height..];
    assert!(
        luma.iter().any(|b| *b != luma[0]) && chroma.iter().any(|b| *b != chroma[0]),
        "both planes must carry real picture content"
    );
}

/// Count Annex-B start codes in `stream` as `(total, three_byte)`.
///
/// Emulation prevention guarantees `00 00 01` cannot occur inside a NAL payload,
/// so every hit is a real prefix; a hit not preceded by a zero byte is a
/// three-byte one.
fn annexb_prefixes(stream: &[u8]) -> (usize, usize) {
    let mut total = 0;
    let mut three_byte = 0;
    for i in 0..stream.len().saturating_sub(2) {
        if stream[i..i + 3] == [0x00, 0x00, 0x01] {
            total += 1;
            if i == 0 || stream[i - 1] != 0x00 {
                three_byte += 1;
            }
        }
    }
    (total, three_byte)
}

// The two guards below are what stop the four-byte hardware legs from passing
// vacuously. Those legs assert that a rewritten vector decodes to the SAME
// goldens as the original — which is trivially true if the rewrite quietly
// returned its input, or dropped NALs the planner never missed. Nothing on the
// fleet would notice; these notice in ordinary CI, with the reason.

#[test]
fn the_h264_four_byte_rewrite_changes_prefixes_and_nothing_else() {
    use pf_bitstream::h264::H264Planner;

    let original = common::TEST_25FPS_H264;
    let rewritten = common::h264_four_byte_start_codes(original);

    let (original_total, original_three) = annexb_prefixes(original);
    let (rewritten_total, rewritten_three) = annexb_prefixes(&rewritten);

    assert!(
        original_three > 0,
        "the vendored H.264 vector is supposed to carry THREE-byte start codes; \
         if it no longer does, `h264_four_byte_start_codes_decode_bit_identically` \
         is feeding the hardware the same bytes as the leg above it and proves \
         nothing"
    );
    assert_eq!(
        rewritten_three, 0,
        "every start code in the rewritten stream must be four-byte — {rewritten_three} \
         of {rewritten_total} are not"
    );
    assert_eq!(
        rewritten_total, original_total,
        "the rewrite must preserve the NAL count exactly ({original_total}), not \
         drop or invent units"
    );
    assert!(
        rewritten.len() > original.len(),
        "widening every prefix cannot shrink the stream"
    );

    // Same access units, same planner verdict: the rewrite changed the framing
    // and nothing the decoder acts on.
    let aus = common::split_h264_aus(&rewritten);
    assert_eq!(
        aus.len(),
        common::split_h264_aus(original).len(),
        "the rewritten stream must split into the same access units"
    );
    assert_eq!(
        aus.len(),
        FRAME_COUNT,
        "…and there are {FRAME_COUNT} of them"
    );

    let mut planner = H264Planner::new();
    let mut outputs = 0usize;
    for (index, au) in aus.iter().enumerate() {
        let plan = planner.plan_au(au).unwrap_or_else(|e| {
            panic!("AU {index}: the four-byte rewrite must plan as the original does, got {e:?}")
        });
        outputs += plan.dpb.outputs.len();
    }
    outputs += planner.flush().outputs.len();
    assert_eq!(
        outputs, FRAME_COUNT,
        "the rewritten vector must still output {FRAME_COUNT} pictures"
    );
}

#[test]
fn the_h265_four_byte_rewrite_changes_prefixes_and_nothing_else() {
    use pf_bitstream::h265::H265Planner;

    let original = common::TEST_25FPS_H265;
    let rewritten = common::h265_four_byte_start_codes(original);

    let (original_total, original_three) = annexb_prefixes(original);
    let (rewritten_total, rewritten_three) = annexb_prefixes(&rewritten);

    assert!(
        original_three > 0,
        "the vendored H.265 vector is supposed to carry THREE-byte start codes; \
         if it no longer does, `h265_four_byte_start_codes_decode_bit_identically` \
         proves nothing"
    );
    assert_eq!(
        rewritten_three, 0,
        "every start code in the rewritten stream must be four-byte — {rewritten_three} \
         of {rewritten_total} are not"
    );
    assert_eq!(
        rewritten_total, original_total,
        "the rewrite must preserve the NAL count exactly ({original_total})"
    );
    assert!(
        rewritten.len() > original.len(),
        "widening every prefix cannot shrink the stream"
    );

    let aus = common::split_h265_aus(&rewritten);
    assert_eq!(
        aus.len(),
        common::split_h265_aus(original).len(),
        "the rewritten stream must split into the same access units"
    );
    assert_eq!(
        aus.len(),
        FRAME_COUNT,
        "…and there are {FRAME_COUNT} of them"
    );

    let mut planner = H265Planner::new();
    let mut outputs = 0usize;
    for (index, au) in aus.iter().enumerate() {
        let plan = planner.plan_au(au).unwrap_or_else(|e| {
            panic!("AU {index}: the four-byte rewrite must plan as the original does, got {e:?}")
        });
        outputs += plan.dpb.outputs.len();
    }
    outputs += planner.flush().outputs.len();
    assert_eq!(
        outputs, FRAME_COUNT,
        "the rewritten vector must still output {FRAME_COUNT} pictures"
    );
}
