Files
fresnel-fourier/phase0_findings_iter3.md
claude-noether ea2413e957 iter3 Phase 0 + Phase 1 lock: VP8 on hantro-vpu-dec
Opens iter3 of the fresnel-fourier campaign immediately after iter2
close (df787a6). Targets VP8 as the fourth codec to pass boolean-
correctness on fresnel via libva-v4l2-request-fourier.

Locked research question:
  mpv --hwdec=vaapi bbb_720p10s_vp8.webm engages backend cleanly and
  DMA-BUF GL import yields HW pixels byte-identical to SW reference.

Five Phase 1 boolean criteria:
  1. vainfo enumerates VAProfileVP8Version0_3 on hantro env binding
  2. vaCreateConfig(VAProfileVP8Version0_3, VLD) = SUCCESS
  3. ffmpeg -hwaccel vaapi VP8 decode exit 0
  4. mpv --hwdec=vaapi --vo=image @ +02s seek: HW=SW byte-identical
     for 2 distinct frames; frame1 != frame2
  5. THREE-codec regression block: iter1 MPEG-2 + iter2 HEVC + T4
     H.264 reference hashes all hold

Substrate carry-forward (re-verified):
  - fork master tip post-iter2-close (cca539d + 8d71e20)
  - /usr/lib/dri/v4l2_request_drv_video.so SHA256 9e27...6258
  - linux-eos-arm 6.19.9-99-eos-arm (post linux-7 headers-only upgrade)
  - bbb_720p10s_vp8.webm fixture on fresnel ~/fourier-test/ (2.4 MB)
  - hantro-vpu-dec OUTPUT_MPLANE VP8F + vp8_frame_parameters control
  - cross-validator anchor confirmed: ffmpeg-v4l2request VP8 = exit 0

Predicted scope (smaller than iter1+iter2):
  - config.c: ADD VP8 enumeration block + RequestCreateConfig case
    + RequestQueryConfigEntrypoints case (3 sites; iter1+iter2
    only had 1-2 existing-but-broken case labels)
  - src/vp8.c NEW file (~150-250 lines vs iter2's 588 h265.c)
  - src/vp8.h NEW file
  - src/meson.build add 'vp8.c' + 'vp8.h' entries
  - picture.c codec_set_controls VP8 dispatch + codec_store_buffer
    cases for 4 VAAPI VP8 buffer types (Picture, Slice, Probability,
    IQMatrix)
  - surface.h params union extend with vp8 member
  - context.c: NO changes (VP8 has no DECODE_MODE/START_CODE menus
    on hantro per Phase 0 v4l2_inventory)

VP8 contract surface: single V4L2_CID_STATELESS_VP8_FRAME control
per frame (no batch); no slice_params dynamic-array (frame-mode);
no SCALING_MATRIX (entropy + quant carried in v4l2_ctrl_vp8_frame
sub-structs).

Phase 2 source-read targets queued: config.c enumeration pattern,
picture.c dispatch + per-buffer-type cases, surface.h params union,
VAAPI <va/va_dec_vp8.h>, kernel UAPI <linux/v4l2-controls.h>
v4l2_ctrl_vp8_frame, kernel hantro_vp8.c driver, FFmpeg
v4l2_request_vp8.c.

Memory carry-forward (all five entries apply unchanged):
  feedback_gitea_as_claude_noether
  feedback_no_session_termination_attempts
  feedback_header_deletion_check
  feedback_review_empirical_over_theoretical (BOTH directions)
  feedback_rockchip_pixel_verify_path

Refs:
  phase0_findings_iter1.md (iter1 MPEG-2 lock template)
  phase0_findings_iter2.md (iter2 HEVC lock template)
  phase8_iteration2_close.md (immediate predecessor close)
  phase0_evidence/2026-05-07/v4l2_inventory_findings.md (hantro VP8
    capability)
  phase0_evidence/2026-05-07/cross_validator_traces.md (VP8 kernel
    decode path proven)
  phase0_evidence/2026-05-07/test_fixtures.md (bbb_720p10s_vp8.webm
    provenance)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 15:49:28 +00:00

173 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Iteration 3 — Phase 0 (substrate / motivation / inventory) → Phase 1 lock
Opens 2026-05-08 immediately after iter2 close ([`phase8_iteration2_close.md`](phase8_iteration2_close.md), commit `df787a6`). Per `feedback_dev_process.md` Phase 0, this document captures iter3's locked research question + substrate + scope, ending with the Phase 1 measurable success criterion.
## Locked research question (iteration 3)
> **"Make VP8 the fourth codec to pass boolean-correctness on fresnel via the libva-v4l2-request-fourier path — `mpv --hwdec=vaapi bbb_720p10s_vp8.webm` engages the backend cleanly and DMA-BUF GL import yields HW pixels byte-identical to a software-decoded reference for the same frames."**
Pass/fail (boolean):
1. **Profile enumeration**. `vainfo` with the hantro env binding (`LIBVA_V4L2_REQUEST_VIDEO_PATH=/dev/video<hantro-vpu-dec>`, `LIBVA_V4L2_REQUEST_MEDIA_PATH=/dev/media<hantro-vpu>` — re-verify per-boot device numbering) lists `VAProfileVP8Version0_3`. **Currently NOT enumerated** — config.c has no VP8 enumeration block at all (unlike iter1 MPEG-2 + iter2 HEVC where the case labels existed but fell through). iter3 must ADD the enumeration logic in `RequestQueryConfigProfiles` + the validation case in `RequestCreateConfig`.
2. **Config creation succeeds.** `vaCreateConfig(VAProfileVP8Version0_3, VAEntrypointVLD)` returns `VA_STATUS_SUCCESS`.
3. **End-to-end ffmpeg-direct decode**. `ffmpeg -hwaccel vaapi -i ~/fourier-test/bbb_720p10s_vp8.webm -frames:v 5 -f null -` (with hantro env binding) shows the libva chain in stderr, libva trace shows `vaCreateConfig SUCCESS`, no `Failed to create decode configuration`, no `EINVAL` from `VIDIOC_S_EXT_CTRLS`, exits 0. Phase 1 criterion #3 wording matches iter1/iter2 (ffmpeg-direct anchor; mpv `--hwdec=vaapi-copy` may filter VP8 out — fall-forward to mpv-vaapi-vo=image for criterion 4).
4. **Cache-safe pixel verification matches SW reference.** `mpv --hwdec=vaapi --vo=image --frames=2 --start=00:00:02 ~/fourier-test/bbb_720p10s_vp8.webm` and the `--hwdec=no` SW run produce JPEGs whose `sha256sum` outputs match for both frame 1 and frame 2. Frames 1 and 2 must hash-differ (real motion content) AND hash-equal across HW vs SW. Per [`memory/feedback_rockchip_pixel_verify_path.md`](../../.claude/projects/-home-mfritsche-src-fresnel-fourier/memory/feedback_rockchip_pixel_verify_path.md): DMA-BUF GL is the cache-coherency-safe verifier; do NOT use ffmpeg-vaapi+hwdownload.
5. **Three-codec regression**. iter1 MPEG-2 + iter2 HEVC + T4 H.264 reference hashes all hold:
- MPEG-2 +02s: `6e7873030dbf...` and `ccc7ce08810d...`
- HEVC +02s: `47a5f3850df5...` and `a467b3bc9d7b...`
- H.264 +30s: `f623d5f7...` and `7d7bc6f2...`
iter3 must not regress any prior codec.
A clean iter3 close has all five checks green. Phase 7 → Phase 4 loopback per `feedback_dev_process.md` if any fail.
## Mechanism the question targets
Phase 0 cross-validator sweep ([`phase0_evidence/2026-05-07/cross_validator_traces.md`](phase0_evidence/2026-05-07/cross_validator_traces.md)) established that the kernel + hantro driver path works for VP8: `ffmpeg -hwaccel v4l2request -i bbb_720p10s_vp8.webm -frames:v 2 -f null - = exit 0`. The broken link is the libva backend at four distinct sites:
- **`src/config.c::RequestQueryConfigProfiles`** — no VP8 enumeration block. Existing blocks for MPEG-2 (lines 119-128), H.264 (lines 130-142), HEVC (lines 144-151) probe `v4l2_find_format(...V4L2_PIX_FMT_X_SLICE)`. iter3 needs an analogous block probing `V4L2_PIX_FMT_VP8_FRAME` and adding `VAProfileVP8Version0_3` to the enumerated list.
- **`src/config.c::RequestCreateConfig`** — `case VAProfileVP8Version0_3:` doesn't exist. Need to add with a `break;` (mirroring iter1 + iter2 pattern).
- **`src/config.c::RequestQueryConfigEntrypoints`** — switch case for VAProfileVP8Version0_3 missing; add to the entry-point case list (lines 164-171).
- **`src/vp8.c`** — file doesn't exist in fork. Different from iter2 where `src/h265.c` already existed (just disabled). iter3 creates the file from scratch.
- **`src/meson.build`** — `'vp8.c'` + `'vp8.h'` aren't in the sources/headers lists. iter3 adds them.
- **`src/picture.c::codec_set_controls`** — no `VAProfileVP8Version0_3` case. Need to add with dispatch to `vp8_set_controls()`.
- **`src/picture.c::codec_store_buffer`** — no VP8 cases for `VAPictureParameterBufferType`, `VASliceParameterBufferType`, `VAProbabilityDataBufferType`, `VAIQMatrixBufferType`. Need to add.
- **`src/surface.h`** — `params.h264`/`params.mpeg2`/`params.h265` union has no VP8 member. Need to add `params.vp8` with VAAPI VP8 buffer types.
VP8 contract surface from Phase 0 V4L2 inventory:
```
hantro-vpu-dec /dev/video5 (boot-dependent path):
OUTPUT_MPLANE codec_format VP8F (V4L2_PIX_FMT_VP8_FRAME, compressed)
Stateless control: vp8_frame_parameters 0x00a409c8 (vp8-frame)
value=unsupported payload type flags=has-payload
```
**Single control per frame.** No DECODE_MODE/START_CODE menus (VP8 has no Annex B equivalent — VP8 frames have their own header format). No SCALING_MATRIX. No DECODE_PARAMS-equivalent (VP8 keeps DPB info in the frame struct itself). Slice_params is single-instance (VP8 is frame-mode, no multi-slice).
Predicted iter3 scope: **smaller than iter1 + iter2** — single control struct, no dynamic-array, no conditional batching. Likely closer to iter1's mpeg2.c (~120 lines) or smaller.
Phase 4 plan must cite the contract before patching (`feedback_dev_process.md` Phase 6 contract-before-code): read kernel `drivers/media/platform/verisilicon/hantro_vp8.c`, read FFmpeg `libavcodec/v4l2_request_vp8.c`, read kernel UAPI `<linux/v4l2-controls.h>` for `V4L2_CID_STATELESS_VP8_FRAME` and `struct v4l2_ctrl_vp8_frame`, state the contract explicitly before any code lands.
## Predecessor carry-over (iter2 → iter3)
### State that carries forward (re-verified at iter3 open)
- **Hardware**: fresnel RK3399, kernel `linux-eos-arm 6.19.9-99-eos-arm`. Custom OC kernel preserved across iter2's `pacman -Syu` headers-only userland upgrade.
- **hantro-vpu-dec node**: device numbering shuffles per boot. iter1 Phase 7 had `/dev/video5+/dev/media2`; iter2 Phase 7 had `/dev/video3+/dev/media1`. iter3 binding cells re-verify via `v4l2-ctl --info` at session start.
- **Decoder formats** (hantro-vpu-dec, from Phase 0 v4l2_inventory): OUTPUT_MPLANE = `MG2S` (MPEG-2) + `VP8F` (VP8). CAPTURE_MPLANE = `NV12`.
- **Kernel UAPI**: `V4L2_CID_STATELESS_VP8_FRAME` + `struct v4l2_ctrl_vp8_frame` available in `/usr/include/linux/v4l2-controls.h` (verified Phase 0 build smoke; same kernel headers as iter1+iter2).
- **Backend build state**: libva-v4l2-request-fourier master tip post-iter2-close. Includes:
- iter1 commits e7dad7a..229d6d1 (config.c + mpeg2.c + delete include/mpeg2-ctrls.h)
- iter2 commits cca539d (config.c HEVCMain break) + 8d71e20 (h265.c rewrite + picture.c HEVC dispatch + slice_params accumulation + surface.h slices[64] + context.c HEVC device-init + meson.build h265 re-enable)
- SHA256 `9e27043847998c197a46a1a26b2f77f22880bb7b3a62aa4d60d8fcaec0ae6258` of `/usr/lib/dri/v4l2_request_drv_video.so` on fresnel.
- **Test fixture**: `~/fourier-test/bbb_720p10s_vp8.webm` on fresnel (2.4 MB, VP8 Profile 0, 1280×720@24fps yuv420p, 10s). Provenance in [`phase0_evidence/2026-05-07/test_fixtures.md`](phase0_evidence/2026-05-07/test_fixtures.md).
- **Reference fixtures for regression**: bbb_1080p30_h264.mp4, bbb_720p10s_mpeg2.ts, bbb_720p10s_hevc.mp4 (all on fresnel).
- **Reference hashes for criterion 5**:
- H.264 (T4) at +30s: `f623d5f7a41697f67dd227275c6f1b21ffc257f65626d32fde8229357f8764c9` + `7d7bc6f2146dda8b2d223bba622c4b9fbe9674181ff1e02afe286b620342e0a8`
- MPEG-2 (iter1) at +02s: `6e7873030dbf0403c67f35dd106ebef3c7909a0fd12433b82ad758e7fee9f092` + `ccc7ce08810d4a96e9ba7a19f4f95bbf6cc861bda9337604b5c668ad52bef7de`
- HEVC (iter2) at +02s: `47a5f3850df5d8c732767a227830c2272ff78402a7b6adeea329e29838808be5` + `a467b3bc9d7b6374b6786ecfac46932d6c7bb932ab11d311edaa233d7863e656`
- **Cross-validator anchor**: ffmpeg-v4l2request VP8 trace from [`phase0_evidence/2026-05-07/cross_validator/vp8/`](phase0_evidence/2026-05-07/cross_validator/vp8/). 10 `S_EXT_CTRLS` for 2 frames (single VP8 frame control per frame; ~5 init + 5 per-frame? Phase 3 will re-capture verbatim payloads). 4 `MEDIA_IOC_REQUEST_ALLOC`. 52 ftrace v4l2 events.
- **Cache-safe verify path**: `mpv --hwdec=vaapi --vo=image` (DMA-BUF GL import). Confirmed for H.264 (T4) + MPEG-2 (iter1) + HEVC (iter2) on RK3399; iter3 expects same path to work for VP8.
### Data that does NOT carry forward (re-acquire if needed)
- ohm/RK3568 hantro VP8 behaviour — ohm hantro DOES decode VP8 (per phase0_findings.md ohm context: "hantro (H.264 + MPEG-2 + VP8)") but the libva-multiplanar campaign never tested VP8 end-to-end. Reference history only.
- Pre-iter1 VP8 trace data — none exists in this fork.
### Open questions inherited from iter2 close
- **iter1 B3 latent surface-reuse bug**: `picture.c:287` `params.h264.matrix_set = false` writes union byte 240. For VP8 surfaces, byte 240 lands inside the new `params.vp8` struct (whatever layout we add). Phase 2 source-read action item: verify whether byte 240 lands in a meaningful VP8 field. If so, the same masking-by-RenderPicture-overwrite mechanism used by iter1+iter2 should hold for VP8 (ffmpeg-vaapi sends VAPictureParameterBufferType per frame for VP8 too, expected).
- **iter2 B6 SPS field-fidelity gap**: HEVC-specific (sps_max_num_reorder_pics, etc.); doesn't apply to VP8. VP8 has its own per-frame fidelity surface.
- **Phase 4 cross-cutting backlog** (vaDeriveImage cache-stale fix, V4L2 device-discovery, BeginPicture profile-aware reset, context.c log suppression, mpeg2 vbv_buffer_size polish, h265 SPS bitstream-parse fidelity): not iter3 scope; iter3 inherits the same workarounds.
## Tooling and measurement-instrument inventory (live verification)
Re-verified at iter3 open (carries forward from iter1+iter2; all proven working):
- `strace -ff -tt -y -v -e trace=ioctl,openat,close` — V4L2 + media-request ioctl tracing.
- `LIBVA_TRACE` environment variable — vaCreate/vaQuery/vaInitialize call traces.
- `sudo sh -c "echo 1 > /sys/kernel/tracing/events/v4l2/enable"` — kernel v4l2 tracepoints.
- `mpv --hwdec=vaapi --vo=image` — cache-coherency-safe pixel verifier.
- `ffmpeg -hwaccel v4l2request` — independent V4L2 client cross-validator.
- Backend build: `ninja -C ~/src/libva-v4l2-request-fourier/build && sudo ninja -C build install`.
- Empirical VP8 decode of `bbb_720p10s_vp8.webm` via ffmpeg-v4l2request DRM_PRIME path — proven in cross-validator sweep (Phase 0).
- gcc test-compile for VAAPI field-availability checks — per [`memory/feedback_review_empirical_over_theoretical.md`](../../.claude/projects/-home-mfritsche-src-fresnel-fourier/memory/feedback_review_empirical_over_theoretical.md) Direction 2 protocol.
## In-scope (LOCKED 2026-05-08 for iteration 3)
- libva-v4l2-request-fourier backend VP8 path on hantro-vpu-dec.
- `src/config.c` — ADD VP8 enumeration block in `RequestQueryConfigProfiles`; ADD `case VAProfileVP8Version0_3:` with `break;` in `RequestCreateConfig`; ADD case in `RequestQueryConfigEntrypoints`. Mirrors iter1+iter2 patterns + adds the enumerator block (different from iter1+iter2 which had existing-but-broken case labels).
- `src/picture.c::codec_set_controls` — ADD `VAProfileVP8Version0_3` dispatch to `vp8_set_controls()`.
- `src/picture.c::codec_store_buffer` — ADD VP8 cases for VAPictureParameterBufferType, VASliceParameterBufferType, VAProbabilityDataBufferType, VAIQMatrixBufferType (the 4 VP8-specific buffer types VAAPI consumers send).
- `src/vp8.c` — NEW file. Implements `vp8_set_controls()` against `V4L2_CID_STATELESS_VP8_FRAME` + `struct v4l2_ctrl_vp8_frame`. Single control per frame; not batched (just one CID).
- `src/vp8.h` — NEW file. Declares `vp8_set_controls()`.
- `src/surface.h` — extend `params` union with `params.vp8` struct holding VAAPI VP8 buffer types + iqmatrix_set flag.
- `src/meson.build` — add `'vp8.c'` + `'vp8.h'` to sources/headers lists.
- iter3 binding-cell test harness: re-run iter1's Phase 7 5-criterion shape with VP8 fixture substituted + 3-codec regression block.
- Cache-safe pixel verify uses DMA-BUF GL import (memory rule).
## Out-of-scope (LOCKED 2026-05-08 for iteration 3)
- VP9 (iter4 — separate iteration; rkvdec, NOT hantro).
- VP8 decode_mode menu selection (kernel only exposes single-mode for VP8 per Phase 0; no choice).
- Performance metrics.
- Long-duration VP8 stress (>10s).
- Phase 4 cross-cutting backlog items (B1 device-discovery, B3 BeginPicture profile-aware reset, B4 context.c log suppression, B5 vbv_buffer_size, B6 SPS fidelity, L3 vaDeriveImage cache-stale).
- chromium-fourier 149 install on fresnel.
- `src/context.c` — no changes (VP8 has no DECODE_MODE/START_CODE menus per Phase 0 inventory; nothing to add to device-init block).
- WebRTC-specific VP8 features (temporal layers, simulcast) — out of campaign codec scope.
- Upstream Linux engagement.
## Phase 1 success criterion (LOCKED 2026-05-08)
The five Pass/fail bullets at the top of this document are the iter3 success criterion. Phase 3 baseline measurements feed Phase 4 plan; Phase 7 verification re-runs all five against the patched backend.
If Phase 3 baseline reveals the chosen criterion is the wrong target (Phase 3 → Phase 1 loopback per `feedback_dev_process.md`), the criterion will be rewritten and re-locked. Plausible reasons that would trigger the loopback:
- VP8 fixture is malformed in a way that exposes a fixture-side bug rather than a backend-side bug. (Mitigation: ffmpeg-v4l2request decoded the fixture cleanly per Phase 0 cross-validator; unlikely.)
- VAAPI's VP8 buffer types include something not exposed by mpv-vaapi consumer chain (e.g., probability buffer not sent, requiring iter3 to derive from bitstream). Phase 3 baseline LIBVA_TRACE will surface.
- mpv `--hwdec=vaapi` (DMA-BUF) filters VP8 out (analogous to MPEG-2 vaapi-copy filter from iter1). Mitigation: fall-forward to ffmpeg-v4l2request DRM_PRIME path for criterion 4 verification.
- Hantro VP8 quirk on RK3399: silicon supports VP8 but kernel's hantro_vp8 driver may have RK3399-specific assumptions different from RK3568. Phase 0 cross-validator decoded the fixture, so this is unlikely.
The other four Phase 1 criteria hold as locked.
## Phase 2 source-read targets
For the upcoming Phase 2 situation analysis:
- `src/config.c``RequestQueryConfigProfiles` (lines 112-156): pattern for adding VP8 enumeration block; `RequestCreateConfig` (lines 45-95): pattern for adding `case VAProfileVP8Version0_3:` with `break;`; `RequestQueryConfigEntrypoints` (lines 158-182): adding to entry-point case list.
- `src/picture.c::codec_set_controls` (lines 178-213): pattern for VP8 dispatch (mirror MPEG-2 dispatch shape from iter1).
- `src/picture.c::codec_store_buffer` (lines 54-176): patterns for adding VP8 cases per VAAPI buffer type. VAAPI VP8 sends 4 distinct buffer types per frame (not iter1's 2 or iter2's 4-with-array).
- `src/surface.h` (lines 89-110): `params` union pattern for adding `vp8` member.
- VAAPI `<va/va_dec_vp8.h>` — VAAPI VP8 buffer struct definitions: `VAPictureParameterBufferVP8`, `VASliceParameterBufferVP8`, `VAProbabilityDataBufferVP8`, `VAIQMatrixBufferVP8`.
- Kernel UAPI `<linux/v4l2-controls.h>``V4L2_CID_STATELESS_VP8_FRAME` + `struct v4l2_ctrl_vp8_frame` + sub-structs (segment, lf, quant, entropy, coder_state).
- Linux mainline kernel `drivers/media/platform/verisilicon/hantro_vp8.c` — hantro-vpu-dec VP8 driver source (probably available; verify in Phase 2).
- FFmpeg downstream `libavcodec/v4l2_request_vp8.c` — independent V4L2 client implementation. Submission shape, per-frame field mapping.
## What "iteration 3 close" looks like
A clean iter3 close per `feedback_dev_process.md` Phase 8:
- All 5 Phase 1 criteria green.
- `phase8_iteration3_close.md` summarizing the bug, contract, fix, binding-cell numbers.
- Fourth-codec passing on the campaign-level scoreboard: 3/5 → 4/5.
- Memory entries distilled for any new lessons (likely none — iter3 expected to be a smaller iter1-shape, no novel constructs).
- Debug-instrumentation sweep at close.
- Phase 5 sonnet-architect review pass signed off.
- Commits all authored as `claude-noether` per memory `feedback_gitea_as_claude_noether.md`.
- `src/vp8.c` + `src/vp8.h` added to repo, enabled in meson.build.
Predicted iter3 difficulty vs iter1+iter2: **smaller** than both. iter1 was 3 controls + 1 file rewrite + 1 file delete; iter2 was 5 controls + 1 file rewrite + slice_params dynamic-array + surface.h extension. **iter3 is 1 control + 1 new file + simpler structure**, but does require ADD-rather-than-modify in config.c (4 sites: enumerator block, CreateConfig case, QueryConfigEntrypoints case, plus 4 buffer-type cases in picture.c).
Net file count similar to iter2 (6-7 modified files) but each modification is smaller. Predicted h265.c-equivalent at ~150-250 lines for vp8.c (vs h265.c's 588).
If Phase 7 misses a check, most likely culprits:
1. **Probability buffer mishandling**: VAAPI sends VAProbabilityDataBufferVP8 separately from picture/slice; iter3 must wire it into the frame control. FFmpeg v4l2_request_vp8.c is the canonical pattern.
2. **Quantization buffer mishandling**: VAIQMatrixBufferVP8 contains the quant indices; iter3 must populate `v4l2_ctrl_vp8_frame.quant.*` fields.
3. **Frame header parsing**: VP8 has a 3-byte uncompressed frame header (frame_type, version, show_frame, first_part_size). Some fields may need to be parsed from the slice bitstream similar to HEVC NAL header.
4. **DPB references**: VP8 has 3 reference frames (last, golden, altref). VAAPI exposes them in VAPictureParameterBufferVP8; iter3 maps to v4l2_ctrl_vp8_frame's last_frame_ts, golden_frame_ts, alt_frame_ts.
5. **mpv-vaapi filtering**: mpv may silently filter VP8 out of vaapi/vaapi-copy candidates. Phase 3 baseline confirms; criterion 4 falls forward to ffmpeg-vaapi+hwdownload if so (BUT that path returns cache-stale zeros on RK3399 per memory — would need ffmpeg-v4l2request DRM_PRIME instead).
Phase 5 review will catch most of these in advance per iter1+iter2 precedent.