# Iteration 2 — Phase 0 (substrate / motivation / inventory) → Phase 1 lock

Opens 2026-05-08 immediately after iter1 close ([`phase8_iteration1_close.md`](phase8_iteration1_close.md), commit `dc69378`). Per `feedback_dev_process.md` Phase 0, this document captures iter2's locked research question + substrate + scope, ending with the Phase 1 measurable success criterion.

## Locked research question (iteration 2)

> **"Make HEVC Main the third codec to pass boolean-correctness on fresnel via the libva-v4l2-request-fourier path — `mpv --hwdec=vaapi bbb_720p10s_hevc.mp4` 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 regression check.** `vainfo` with the rkvdec env binding (`LIBVA_V4L2_REQUEST_VIDEO_PATH=/dev/video<N>`, `LIBVA_V4L2_REQUEST_MEDIA_PATH=/dev/media<M>` where `N`/`M` are this-boot's rkvdec node numbers — see device-numbering caveat below) continues to list `VAProfileHEVCMain`. (The enumerator already advertises HEVCMain unconditionally on rkvdec, gated only by the V4L2 `V4L2_PIX_FMT_HEVC_SLICE` format probe; this exists to confirm iter2 work doesn't strip the enumeration.)
2. **Config creation succeeds.** `vaCreateConfig(VAProfileHEVCMain, VAEntrypointVLD)` returns `VA_STATUS_SUCCESS`. (Today returns `12 = VA_STATUS_ERROR_UNSUPPORTED_PROFILE` per [`phase0_evidence/2026-05-07/cross_validator_traces.md`](phase0_evidence/2026-05-07/cross_validator_traces.md) — same fall-through bug iter1 fixed for MPEG-2.)
3. **End-to-end decode engages the backend.** `ffmpeg -hwaccel vaapi -i ~/fourier-test/bbb_720p10s_hevc.mp4 -frames:v 5 -f null -` (with rkvdec 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 is ffmpeg-direct, mirroring iter1 Phase 5 Q4 amendment — mpv `--hwdec=vaapi-copy` may also silently filter HEVC out before libva is loaded; ffmpeg-direct exercises the path. mpv-driven testing follows in criterion #4 via DMA-BUF GL.)
4. **Cache-safe pixel verification matches SW reference.** `mpv --hwdec=vaapi --vo=image --frames=2 --start=00:00:02` against `bbb_720p10s_hevc.mp4` and the equivalent `--hwdec=no` SW run produce JPEGs whose `sha256sum` outputs match for both frame 1 and frame 2. Frames 1 and 2 must hash-differ between each other (real motion content, not solid color) 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 (vaDeriveImage cache-stale class returns all-zero on RK3399).
5. **Regression check on iter1 MPEG-2 AND T4 H.264.** Both prior-iteration boolean cells continue to pass:
   - MPEG-2 hashes at +02s seek still `6e7873030dbf...` and `ccc7ce08810d...` (iter1 Phase 7 reference).
   - T4 H.264 hashes at +30s seek still `f623d5f7...` and `7d7bc6f2...` (iter1 Phase 7 + T4 reference).
   Iter2 must not regress either codec.

A clean iter2 close has all five checks green. Anything less loops back to Phase 4 per `feedback_dev_process.md` Phase 7 → Phase 4 edge.

## 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 the kernel + rkvdec driver path works for HEVC: `ffmpeg -hwaccel v4l2request -i bbb_720p10s_hevc.mp4 -frames:v 2 -f null - = exit 0`. So the broken link is the libva backend. From iter1 Phase 2 source-read of the master tip:

- `src/config.c:55-69` `RequestCreateConfig` — `case VAProfileHEVCMain:` falls through to `default:` returning `VA_STATUS_ERROR_UNSUPPORTED_PROFILE`. iter1 added a `break;` for the MPEG-2 cases; HEVC stays in the fall-through bucket.
- `src/picture.c:204-206` `codec_set_controls` — `case VAProfileHEVCMain:` explicitly returns `VA_STATUS_ERROR_UNSUPPORTED_PROFILE` with the comment `/* Fourier-local: HEVC stripped, no HW support on RK3566. */`. The "no HW support on RK3566" rationale was for ohm (PineTab2 RK3566/RK3568) at libva-multiplanar iter5 close; on RK3399 fresnel rkvdec, HEVC IS supported in silicon (cross-validator confirms).
- `src/meson.build` — `# 'h265.c'` is commented out in the `sources` list. Re-enable.
- `src/h265.c` — uncompiled today; likely uses staging-era control IDs from `include/hevc-ctrls.h` (analogous to mpeg2.c using staging-era `mpeg2-ctrls.h` before iter1). The new mainline kernel UAPI for HEVC stateless controls lives at `include/uapi/linux/v4l2-controls.h:2110+` with control IDs `V4L2_CID_STATELESS_HEVC_{SPS,PPS,SLICE_PARAMS,SCALING_MATRIX,DECODE_PARAMS,DECODE_MODE,START_CODE,ENTRY_POINT_OFFSETS,EXT_SPS_ST_RPS,EXT_SPS_LT_RPS}` (10 control IDs total — substantially more than MPEG-2's 3).
- `include/hevc-ctrls.h` — local fork header, staging-era. iter1 Phase 5 Nit 6 deferred this deletion; iter2 closes that loop.

iter2 likely encounters **four bugs** structurally analogous to iter1, plus possibly more:

- **B1 (analogous to iter1 Bug 1)**: config.c HEVCMain case fall-through. Fix shape: 3-line `break;` addition.
- **B2 (analogous to iter1 Bug 2)**: src/h265.c using staging-era CIDs. Fix shape: rewrite against the new V4L2_CID_STATELESS_HEVC_* split API. Larger than iter1's mpeg2.c rewrite because HEVC has 10 control IDs vs MPEG-2's 3.
- **B3 (analogous to iter1 Bug 3)**: `include/hevc-ctrls.h` is the staging-era local header. Fix shape: delete the file; verify no remaining includes via clean rebuild after include-removal patches (per [`memory/feedback_header_deletion_check.md`](../../.claude/projects/-home-mfritsche-src-fresnel-fourier/memory/feedback_header_deletion_check.md) — grep alone is insufficient).
- **B4 (NEW)**: picture.c::codec_set_controls explicitly returns UNSUPPORTED_PROFILE for HEVCMain. Fix shape: replace the explicit reject with a dispatch to `h265_set_controls`.
- **B5 (meson)**: src/meson.build comments out `h265.c`. Fix shape: uncomment.

Plus possibly:
- **B6**: VAAPI VAPicture/VASlice/VAIQMatrix HEVC structs may not map 1:1 to the new V4L2 control structs. Phase 2 source-read of `<va/va_dec_hevc.h>` + the kernel UAPI HEVC structs will identify any mismatches.
- **B7**: HEVC slice_params is a dynamic-array control (kernel UAPI `v4l2-controls.h:2117` `V4L2_CID_STATELESS_HEVC_SLICE_PARAMS` is per-slice; rkvdec accepts up to 600 entries per Phase 0 v4l2-ctl inventory). The libva backend must accumulate per-slice params and submit them as a dynamic-array. Different shape from H.264 (which uses single VASliceParameter per RenderPicture call) and MPEG-2 (which doesn't have slice params at all in the new API).

Phase 4 plan must cite the contract before patching (`feedback_dev_process.md` Phase 6 contract-before-code): read kernel `drivers/staging/media/rkvdec/rkvdec_hevc.c` (the rkvdec HEVC driver source), read FFmpeg downstream `libavcodec/v4l2_request_hevc.c`, read mainline `include/uapi/linux/v4l2-controls.h:2110+`, state the HEVC control-submission contract explicitly before any code lands.

## Predecessor carry-over (iter1 → iter2)

### State that carries forward (re-verified at iter2 open)

- **Hardware**: fresnel RK3399, kernel `6.19.9-99-eos-arm`. ftrace + tracing enabled. mpv 0.41.0 stock, ffmpeg n8.1-13 (Kwiboo branch), libva 1.23.0.
- **rkvdec node**: Phase 7 verified `/dev/video1` + `/dev/media0` on the boot of 2026-05-08 morning. **Caveat**: V4L2 `/dev/videoN` numbering shuffles across reboots (per iter1 Phase 8 backlog B1). iter2 binding cells should re-verify `v4l2-ctl --info` at session start. Long-term fix candidate: backend-side device-discovery probe (Phase 4 cross-cutting, not iter2).
- **Decoder formats** (rkvdec, from [`phase0_evidence/2026-05-07/v4l2_inventory.txt`](phase0_evidence/2026-05-07/v4l2_inventory.txt)): OUTPUT_MPLANE = `S265` (HEVC Parsed Slice Data) + `S264` (H.264) + `VP9F` (VP9). CAPTURE_MPLANE = `NV12`.
- **Stateless control payloads** kernel exposes (rkvdec, HEVC): `hevc_sequence_parameter_set` (`0x00a40a90`), `hevc_picture_parameter_set` (`0x00a40a91`), `hevc_slice_parameters` (`0x00a40a92`, dynamic-array max 600 elems), `hevc_scaling_matrix` (`0x00a40a93`), `hevc_decode_parameters` (`0x00a40a94`), `hevc_decode_mode` (`0x00a40a95`, menu, FRAME_BASED only), `hevc_start_code` (`0x00a40a96`, menu, ANNEX_B only).
- **HEVC profile menu** (rkvdec): Main, Main Still Picture, Main 10. RK3399 silicon support for Main 10 (10-bit) is unverified; iter2 fixture is 8-bit Main only.
- **Backend build state**: libva-v4l2-request-fourier master tip post-iter1-close, including:
  - iter1 commits: `e7dad7a` (config.c break) + `5fe873c` (mpeg2.c rewrite) + `3aab187` (delete include/mpeg2-ctrls.h) + `229d6d1` (commit D fix-forward).
  - iter1's MPEG-2 path is a working reference for the patch shape (config.c break + new-API rewrite + header delete + grep-audit incompleteness fix).
  - `src/h265.c` is **already in the source tree** (267 lines per Phase 0 inventory) — Phase 2 will audit whether it uses staging-era or modern API. If staging-era, rewrite is the bulk of iter2 work. If somehow already on modern API, the work is much smaller.
- **Test fixture**: `~/fourier-test/bbb_720p10s_hevc.mp4` on fresnel (620 KB, HEVC Main, 1280×720@24fps, yuv420p, 10s, generated 2026-05-07 23:34 from H.264 master via libx265 ultrafast). Provenance: [`phase0_evidence/2026-05-07/test_fixtures.md`](phase0_evidence/2026-05-07/test_fixtures.md).
- **Reference fixtures for regression**: `~/fourier-test/bbb_1080p30_h264.mp4` (T4 reference) and `~/fourier-test/bbb_720p10s_mpeg2.ts` (iter1 reference).
- **iter1 + T4 reference hashes for regression criterion 5**:
  - H.264 (T4) at +30s: HW frame 1 `f623d5f7a41697f67dd227275c6f1b21ffc257f65626d32fde8229357f8764c9`, frame 2 `7d7bc6f2146dda8b2d223bba622c4b9fbe9674181ff1e02afe286b620342e0a8`.
  - MPEG-2 (iter1) at +02s: HW frame 1 `6e7873030dbf0403c67f35dd106ebef3c7909a0fd12433b82ad758e7fee9f092`, frame 2 `ccc7ce08810d4a96e9ba7a19f4f95bbf6cc861bda9337604b5c668ad52bef7de`.
- **Cross-validator anchor**: ffmpeg-v4l2request HEVC contract from [`phase0_evidence/2026-05-07/cross_validator/hevc/`](phase0_evidence/2026-05-07/cross_validator/hevc/). 14 `S_EXT_CTRLS`, 5 `QUERY_EXT_CTRL` (HEVC slice_params dynamic-array introspection — unique among the 5 codecs), 4 `MEDIA_IOC_REQUEST_ALLOC`. 60 ftrace v4l2 events for 2 frames.
- **Cache-safe verify path**: `mpv --hwdec=vaapi --vo=image` (DMA-BUF GL import). Confirmed for H.264 (T4) and MPEG-2 (iter1 Phase 7); iter2 expects the same path to work for HEVC.

### Data that does NOT carry forward (re-acquire if needed)

- ohm/RK3568 hantro HEVC behavior — there is none; RK3568 hantro doesn't decode HEVC. RK3399 rkvdec is the first time the libva-v4l2-request-fourier fork sees HEVC on real hardware. Reference history: zero.
- libva-multiplanar iter1-iter5 close docs' "HEVC stripped" disposition — that was an ohm-side build-cleanly decision, not an empirical "HEVC doesn't work" finding. iter2 reverses the strip.

### Open questions inherited from iter1 close

- **B3 latent surface-reuse bug** ([`phase8_iteration1_close.md`](phase8_iteration1_close.md) backlog): `picture.c:287` `params.h264.matrix_set = false` writes union byte 240, lands inside `mpeg2.iqmatrix.chroma_intra_quantiser_matrix[20]`. For HEVC, the union member is `params.h265.{picture, slice, iqmatrix, iqmatrix_set}`. The `params.h265` struct layout differs from `params.mpeg2` (per [`src/surface.h:90-109`](../libva-multiplanar/libva-v4l2-request-fourier/src/surface.h)). iter2 should verify whether byte 240 lands inside `h265.iqmatrix` or elsewhere; if it lands in a meaningful HEVC field, the latent bug becomes acute for iter2's binding cells. **Phase 2 source-read action item**.
- **Phase 4 cross-cutting work items** (vaDeriveImage fix, V4L2 device-discovery, BeginPicture profile-aware reset, context.c log suppression): not iter2 scope; iter2 works around them via DMA-BUF GL verify path and re-checked device numbers.

## Tooling and measurement-instrument inventory (live verification)

Re-verified at iter2 open (carries forward from iter1, all proven working):

- `strace -ff -tt -y -v -e trace=ioctl,openat,close` — captures V4L2 + media-request ioctl sequence per thread.
- `LIBVA_TRACE` environment variable — captures vaCreate/vaQuery/vaInitialize call traces with status return codes.
- `sudo sh -c "echo 1 > /sys/kernel/tracing/events/v4l2/enable"` — captures kernel v4l2 tracepoints (qbuf/dqbuf events).
- `mpv --hwdec=vaapi --vo=image` — cache-coherency-safe pixel verifier (DMA-BUF GL import).
- `ffmpeg -hwaccel v4l2request` — independent V4L2 client cross-validator (different code path than libva).
- Backend build harness on fresnel: `ninja -C ~/src/libva-v4l2-request-fourier/build && sudo ninja -C build install`.
- Empirical HEVC decode of `bbb_720p10s_hevc.mp4` via ffmpeg-v4l2request DRM_PRIME path — proven works in cross-validator sweep.

iter2 will likely add per-source debug `printf`/`fprintf(stderr, ...)` instrumentation in `src/h265.c` during Phase 6. That instrumentation is iter2-internal scratch; clean sweep at iter2 close per Phase 5 review precedent.

## In-scope (LOCKED 2026-05-08 for iteration 2)

- libva-v4l2-request-fourier backend HEVC Main path on rkvdec.
- `src/config.c::RequestCreateConfig` — add `break;` for `VAProfileHEVCMain` case (analogous to iter1 Commit A pattern for MPEG-2).
- `src/picture.c::codec_set_controls` — replace the explicit `case VAProfileHEVCMain: return UNSUPPORTED_PROFILE` with a dispatch to `h265_set_controls`.
- `src/h265.c` — Phase 2 source-read decides the scope: if it uses staging-era CIDs, full rewrite against new split API. If somehow already modern, the audit produces a delta. Plus any fixes needed for VAAPI ↔ V4L2 control struct mapping (slice_params dynamic-array semantics, scaling_matrix conditional, decode_params per-frame).
- `include/hevc-ctrls.h` — DELETE (iter1 Phase 5 Nit 6 deferred this; iter2 closes the loop). Apply [`memory/feedback_header_deletion_check.md`](../../.claude/projects/-home-mfritsche-src-fresnel-fourier/memory/feedback_header_deletion_check.md) discipline: drop `#include` from all callsites + clean rebuild + then `git rm`.
- `src/meson.build` — uncomment `'h265.c'` in the sources list.
- iter2 binding-cell test harness: re-run iter1's Phase 7 5-criterion harness substituting HEVC fixture, plus iter1's MPEG-2 + T4 H.264 regression checks.
- Cache-safe pixel verify must use DMA-BUF GL import (not vaDeriveImage), per memory `feedback_rockchip_pixel_verify_path.md`.

## Out-of-scope (LOCKED 2026-05-08 for iteration 2)

- VP9, VP8 work (iter3/iter4).
- HEVC Main 10 (10-bit). Silicon support unverified; campaign codec scope is 8-bit yuv420p.
- HEVC Main Still Picture profile (rkvdec advertises it via the menu, but the campaign fixture is Main, not Main Still Picture; defer).
- Performance metrics (Phase 1+ separate iteration).
- Long-duration HEVC stress (>10s).
- HEVC tile / wavefront parallel processing (`ENTRY_POINT_OFFSETS` is in the kernel surface but the campaign fixture doesn't use tiles; defer).
- Phase 4 cross-cutting backlog items B1 (V4L2 device-discovery), B3 (BeginPicture profile-aware reset), B4 (context.c log suppression), B5 (vbv_buffer_size negotiation), L3 (vaDeriveImage cache-stale fix). All carry forward from iter1; not iter2 scope.
- chromium-fourier 149 install on fresnel (carry-over from iter1 deferred).
- `src/context.c` — no changes (the H.264 device-init unconditional-EINVAL is auxiliary noise per iter1 Phase 7; benign for HEVC binding cells).
- Upstream Linux engagement (per `feedback_no_upstream.md`). Kernel-side works; nothing to file.

## Phase 1 success criterion (LOCKED 2026-05-08)

The five Pass/fail bullets at the top of this document are the iter2 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:

- The HEVC fixture is malformed in a way that exposes a fixture-side bug rather than a backend-side bug. (Mitigation: ffmpeg-v4l2request decodes the same fixture cleanly per Phase 0 cross-validator sweep, so this is unlikely.)
- HEVC slice_params dynamic-array semantics on rkvdec require a different submission shape than expected (e.g., per-slice `S_EXT_CTRLS` vs single batched submission with a slice_params array). Phase 3 baseline of ffmpeg-v4l2request HEVC trace will resolve.
- mpv `--hwdec=vaapi` (DMA-BUF) ALSO filters HEVC out (Phase 5 Q4 amendment for iter1 ended up being non-blocking because mpv-vaapi engages MPEG-2; we'll see if HEVC behaves the same). Mitigation: fall-forward to ffmpeg-v4l2request DRM_PRIME + hwdownload pixel verify (we know that works; criterion holds, harness adapts).
- VAAPI HEVC consumer pattern requires VAEncSliceParameterBufferType to be sent in some specific order; iter1 didn't have to handle this. Phase 2 source-read will identify.

The other four Phase 1 criteria (vainfo enumeration, vaCreateConfig success, ffmpeg-direct exit 0, MPEG-2+T4 regression) hold as locked — no adjustment expected.

## Phase 2 source-read targets

For the upcoming Phase 2 situation analysis:

- `src/h265.c` (entire file, ~267 lines) — current state, what API it targets, what VAAPI buffers it reads, what V4L2 controls it submits.
- `src/picture.c:204-206` — the explicit HEVC reject; verify the surrounding context.
- `src/config.c:55-69` — confirm HEVC fall-through (already inspected during iter1 Phase 2; nothing should have changed).
- `src/surface.h:103-108` (`params.h265` struct) — VAAPI HEVC buffers in the surface union.
- `include/hevc-ctrls.h` (entire file) — staging-era definitions; identify which CIDs and structs are referenced from `src/h265.c`.
- `src/meson.build` — confirm `h265.c` commented out; identify any conditional compile flags.
- Kernel UAPI `<linux/v4l2-controls.h>` lines 2110+ — modern HEVC stateless control IDs and struct layouts.
- Linux mainline kernel `drivers/staging/media/rkvdec/rkvdec_hevc.c` — the rkvdec HEVC driver source. Specifically: which controls are mandatory, which are optional, what's the slice_params dynamic-array contract, what's the decode_params contract.
- FFmpeg downstream `libavcodec/v4l2_request_hevc.c` — independent V4L2 client implementation. Submission shape, per-slice params batching, scaling_matrix conditionality.
- VAAPI `<va/va_dec_hevc.h>` — VAAPI HEVC buffer struct definitions (especially `VAPictureParameterBufferHEVC`, `VASliceParameterBufferHEVC` (and any 422/444 ext variants), `VAIQMatrixBufferHEVC`).

## What "iteration 2 close" looks like

A clean iter2 close per `feedback_dev_process.md` Phase 8:

- All 5 Phase 1 criteria green.
- `phase8_iteration2_close.md` summarizing the bug, contract, fix, binding-cell numbers.
- Third-codec passing on the campaign-level scoreboard: 2/5 → 3/5.
- Memory entries distilled for any new lessons (iter1 produced 3; iter2 may produce 0 if the work mirrors iter1's MPEG-2 pattern cleanly, or more if HEVC surfaces novel issues).
- 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`](../../.claude/projects/-home-mfritsche-src-fresnel-fourier/memory/feedback_gitea_as_claude_noether.md).
- `include/hevc-ctrls.h` deleted (closing iter1 Phase 5 Nit 6).
- `src/h265.c` re-enabled in `src/meson.build`, modernized against new V4L2 stateless API, dispatched correctly from picture.c.

Predicted iter2 difficulty vs iter1: similar shape (config break + new-API rewrite + header delete + grep-audit completeness), larger scope (10 control IDs vs 3), one novel construct (slice_params dynamic-array). Expect Phase 6 to take longer than iter1 (~120 lines mpeg2.c rewrite vs probably 250-400 lines for h265.c). Phase 7 verification harness re-uses iter1's pattern verbatim.
