Files
fresnel-fourier/phase3_iter2_baseline.md
claude-noether d35a247948 iter2 Phase 3: baselines — substrate verified post-upgrade, HEVC anchor captured
Phase 3 baselines for iter2 HEVC. Substrate-update verification
ran first (post pacman -Syu rolling upgrade), then iter2-specific
HEVC cross-validator anchor + Bug 1 scratch.

Pre-Phase-3 substrate event: pacman -Syu landed 71 packages.
The "scheduled for linux-7" upgrade was headers-only —
linux-eos-arm-headers 6.19.9-99 → 7.0.3-1, but linux-eos-arm
kernel binary stayed at 6.19.9-99 (EOS-ARM repo hasn't
published the matching 7.x kernel yet). Userland refreshed:
qt6-base epoch bump, libdrm 2.4.131 → 2.4.133, chromium
147 → 148, KDE 26.04.1 batch, mkinitcpio 41-3, etc. OC DTB
intact (sha256 unchanged). mfritsche Plasma session active
throughout, no SDDM regression on this kernel boot.
eos-reboot-recommended marker installed; reboot deferred.

Baseline A (substrate validation post-upgrade):

  T4 H.264 +30s and iter1 MPEG-2 +02s reference hashes all
  8 match exactly:
    H.264 HW1=SW1=f623d5f7a41697f67dd227275c6f1b21ffc257f65626d32fde8229357f8764c9
    H.264 HW2=SW2=7d7bc6f2146dda8b2d223bba622c4b9fbe9674181ff1e02afe286b620342e0a8
    MPEG-2 HW1=SW1=6e7873030dbf0403c67f35dd106ebef3c7909a0fd12433b82ad758e7fee9f092
    MPEG-2 HW2=SW2=ccc7ce08810d4a96e9ba7a19f4f95bbf6cc861bda9337604b5c668ad52bef7de
  Userland upgrade did not regress kernel-side decode or
  DMA-BUF GL readback.

Baseline B (HEVC cross-validator verbatim contract anchor):

  ffmpeg -hwaccel v4l2request decoded bbb_720p10s_hevc.mp4
  -frames:v 5 cleanly. Per-frame submission shape:

    VIDIOC_S_EXT_CTRLS, ctrl_class=V4L2_CTRL_CLASS_CODEC_STATELESS,
                        count=5
      0xa40a90 SPS            size=40
      0xa40a91 PPS            size=64
      0xa40a92 SLICE_PARAMS   size=N (dynamic-array)
      0xa40a93 SCALING_MATRIX size=M
      0xa40a94 DECODE_PARAMS  size=328
    Plus init device-wide:
      0xa40a95 DECODE_MODE    (menu, set once)
      0xa40a96 START_CODE     (menu, set once)

  Key Phase 2 amendments from Phase 3 evidence:
    - Per-frame batch is 5 controls (not "up to 6" — BBB
      doesn't trigger ENTRY_POINT_OFFSETS / EXT_SPS_*).
    - SCALING_MATRIX is sent unconditionally for BBB. FFmpeg
      gates on ctx->has_scaling_matrix from kernel
      VIDIOC_QUERY_EXT_CTRL at init, NOT on per-frame
      bitstream flags. Phase 4 plan amends: query kernel for
      SCALING_MATRIX availability at init, submit if available.

  SPS payload field-decoded (40 bytes verbatim from BBB
  fixture): 1280x720, 8-bit, 4:2:0, no PCM, flags = SAO |
  STRONG_INTRA_SMOOTHING. PPS + DECODE_PARAMS + SLICE_PARAMS +
  SCALING_MATRIX payloads captured for Phase 4 transcription.

Baseline C (slice-count probe): deferred. ffprobe confirms
1 video stream HEVC Main 1280x720 24fps 10s. Per-frame
slice-count not directly extracted; assume 1 slice/frame for
x265 ultrafast preset until Phase 6 verifies. Kernel
advertises slice_params dynamic-array max 600 entries
(phase0 v4l2_inventory), so multi-slice frames are supported
by the contract.

Baseline D (Bug 1 scratch test, collateral safety):

  Applied Bug 1 (config.c break for HEVCMain) on throwaway
  branch; h265.c stayed disabled. Built + installed.
    H.264 HW frames @ +30s: f623d5f7..., 7d7bc6f2... (match T4)
    MPEG-2 HW frames @ +02s: 6e7873030dbf..., ccc7ce08810d...
                              (match iter1)
  Bug 1 in isolation does not regress H.264 or MPEG-2.

  HEVC behavior with Bug 1 only:
    libva trace: vaCreateConfig SUCCESS for VAProfileHEVCMain
    ffmpeg: Task finished with error code: -5 (Input/output error)
  Decode fails downstream because picture.c:204-206 still has
  the explicit case VAProfileHEVCMain: return UNSUPPORTED_PROFILE
  reject (Bug 2). Confirms Phase 2 prediction; Bug 2 fix
  requires h265_set_controls to exist (Bug 3-6: enable +
  rewrite). Bug 2 lands together with the h265.c rewrite in
  Commit B (analogous to iter1 Commit B).

  Scratch state cleaned: git checkout + rebuild + reinstall
  master backend. H.264 + MPEG-2 still pass. Back to Baseline-A-
  equivalent state.

Phase 4 plan inputs updated:
  - Per-frame batch: 5 controls (not "up to 6")
  - SCALING_MATRIX: unconditional iff kernel advertises (init
    QUERY_EXT_CTRL probe), not bitstream-conditional
  - SLICE_PARAMS: dynamic-array (max 600 elems per kernel UAPI)
  - DECODE_MODE + START_CODE: 2 device-wide menus at init
  - Phase 7 harness anchors on mpv-vaapi-vo=image (DMA-BUF GL
    cache-coherency-safe path per
    feedback_rockchip_pixel_verify_path.md)
  - Phase 7 bonus: byte-compare post-fix S_EXT_CTRLS payload
    against Baseline B (per feedback_review_empirical_over_
    theoretical.md — empirical wins)

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

271 lines
17 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 2 — Phase 3 (baseline measurements)
Phase 3 baselines for iter2 HEVC, executed 2026-05-08 across two stages: (i) substrate-update verification after a rolling `pacman -Syu` userland upgrade, (ii) HEVC cross-validator re-anchor + Bug 1 pre-fix scratch test. All four baselines green; iter1 + T4 references hold byte-identical post-upgrade; HEVC contract anchor captured for Phase 4 transcription.
Per `feedback_dev_process.md` Phase 3 anti-fabrication: raw output leads, derived numbers anchor to visible tool invocations.
## Substrate state at Phase 3 start
```
$ ssh fresnel 'uname -r; pacman -Q linux-eos-arm linux-eos-arm-headers libdrm qt6-base mesa chromium libva mpv ffmpeg-v4l2-request-git'
6.19.9-99-eos-arm
linux-eos-arm 6.19.9-99
linux-eos-arm-headers 7.0.3-1
libdrm 2.4.133-1
qt6-base 1:6.11.0-2
mesa 1:26.0.3-1
chromium 148.0.7778.96-1
libva 2.23.0-1
libva-utils 2.22.0-1
mpv 1:0.41.0-3
ffmpeg-v4l2-request-git 2:8.1.r123329.b57fbbe-2
```
The "scheduled for linux-7" upgrade landed as **headers-only**: `linux-eos-arm-headers` jumped from 6.19.9-99 → 7.0.3-1, but the kernel binary package `linux-eos-arm` stayed at `6.19.9-99` (the EOS-ARM repo hasn't published the matching kernel package yet). Running kernel unchanged. 71 packages upgraded total (qt6-base epoch bump, libdrm minor, chromium 147→148, KDE 26.04.1 batch, mkinitcpio 41-3, etc.).
OC DTB at `/boot/dtbs/rockchip/rk3399-pinebook-pro.dtb` was preserved (pre-upgrade snapshot at `*.oc-backup-2026-05-08`; sha256 `b436ed029...` unchanged after upgrade — pacman didn't touch it because `linux-eos-arm` itself wasn't in the upgrade queue).
`eos-reboot-recommended` package marker installed; mfritsche's Plasma session active throughout (tty1, no SDDM regression on this kernel boot). Reboot deferred — substrate verifies pre-reboot.
Device numbering this boot:
- rkvdec = `/dev/video1` + `/dev/media0` (HEVC + H.264)
- hantro-vpu-dec = `/dev/video3` + `/dev/media1` (MPEG-2)
## Baseline A — substrate verified intact post-upgrade
**Goal**: re-run iter1 Phase 7 criterion 4 + 5 reference verifications post `pacman -Syu`. T4 H.264 + iter1 MPEG-2 reference hashes must match exactly. If divergent, substrate regression — investigate before iter2 proceeds.
**Verbatim hash output**:
```
H.264 +30s seek (rkvdec /dev/video1 + /dev/media0):
/tmp/iter2_phase3_substrate/h264_hw/00000001.jpg
f623d5f7a41697f67dd227275c6f1b21ffc257f65626d32fde8229357f8764c9
/tmp/iter2_phase3_substrate/h264_hw/00000002.jpg
7d7bc6f2146dda8b2d223bba622c4b9fbe9674181ff1e02afe286b620342e0a8
/tmp/iter2_phase3_substrate/h264_sw/00000001.jpg
f623d5f7a41697f67dd227275c6f1b21ffc257f65626d32fde8229357f8764c9
/tmp/iter2_phase3_substrate/h264_sw/00000002.jpg
7d7bc6f2146dda8b2d223bba622c4b9fbe9674181ff1e02afe286b620342e0a8
MPEG-2 +02s seek (hantro /dev/video3 + /dev/media1):
/tmp/iter2_phase3_substrate/mpeg2_hw/00000001.jpg
6e7873030dbf0403c67f35dd106ebef3c7909a0fd12433b82ad758e7fee9f092
/tmp/iter2_phase3_substrate/mpeg2_hw/00000002.jpg
ccc7ce08810d4a96e9ba7a19f4f95bbf6cc861bda9337604b5c668ad52bef7de
/tmp/iter2_phase3_substrate/mpeg2_sw/00000001.jpg
6e7873030dbf0403c67f35dd106ebef3c7909a0fd12433b82ad758e7fee9f092
/tmp/iter2_phase3_substrate/mpeg2_sw/00000002.jpg
ccc7ce08810d4a96e9ba7a19f4f95bbf6cc861bda9337604b5c668ad52bef7de
```
**All 8 hashes match references exactly.** T4 H.264 + iter1 MPEG-2 substrate intact post-upgrade. Userland upgrade did not regress kernel-side decode or DMA-BUF GL readback.
(Aside: mesa version reads `1:26.0.3-1` while the iter1 SDDM watchpoint history showed `26.0.3 → 26.0.5` upgrade on 2026-04-28. Either the 26.0.5 was rolled back from the repo without explicit pacman log entry, or the 04-28 entry recorded a bumped revision that resolved to the same 26.0.3 binary. Not iter2-relevant — references match exactly regardless.)
## Baseline B — HEVC cross-validator verbatim contract anchor
**Goal**: extract the byte-level `VIDIOC_S_EXT_CTRLS` payload that ffmpeg-v4l2request submits per HEVC frame. This is the contract Phase 4 implementation must match.
**Invocation**:
```bash
strace -ff -tt -y -v -s 8192 -e trace=ioctl \
-o /tmp/iter2_phase3/ffmpeg_v4l2req.strace \
ffmpeg -hide_banner -loglevel error -hwaccel v4l2request \
-i ~/fourier-test/bbb_720p10s_hevc.mp4 -frames:v 5 -f null -
```
ffmpeg exit 0; 5 frames decoded clean.
**Per-frame submission shape** (verbatim, frame 1):
```
ioctl(/dev/video1, VIDIOC_S_EXT_CTRLS,
{ctrl_class=0xf010000 /* V4L2_CTRL_CLASS_CODEC_STATELESS */,
count=5,
controls=[
{id=0xa40a90 /* V4L2_CID_STATELESS_HEVC_SPS */, size=40, ...},
{id=0xa40a91 /* V4L2_CID_STATELESS_HEVC_PPS */, size=64, ...},
{id=0xa40a92 /* V4L2_CID_STATELESS_HEVC_SLICE_PARAMS */, size=N, ...},
{id=0xa40a93 /* V4L2_CID_STATELESS_HEVC_SCALING_MATRIX */, size=M, ...},
{id=0xa40a94 /* V4L2_CID_STATELESS_HEVC_DECODE_PARAMS */, size=328, ...}
]}) = 0
```
Five controls per frame. Full 5-control batched submission ≠ Phase 2 prediction of "up to 6 with conditional ENTRY_POINT_OFFSETS / EXT_SPS_*"; the BBB fixture doesn't trigger the conditional ones.
**SCALING_MATRIX is sent unconditionally** for the BBB HEVC fixture — this updates Phase 2's "conditional on SPS scaling_list_enabled" assumption. FFmpeg gates SCALING_MATRIX on `ctx->has_scaling_matrix` derived from kernel `VIDIOC_QUERY_EXT_CTRL` at init (rather than on any per-frame VAAPI bit). For the libva backend, the equivalent is to query whether the kernel exposes the SCALING_MATRIX control via `VIDIOC_QUERY_EXT_CTRL` once at init and submit it conditionally on that probe. Phase 4 plan adjusts.
**Init device-wide submissions** (not in the per-frame batch, set once at decoder init):
- `id=0xa40a95` (V4L2_CID_STATELESS_HEVC_DECODE_MODE) — 2 calls visible (one set, one query/confirm).
- `id=0xa40a96` (V4L2_CID_STATELESS_HEVC_START_CODE) — 2 calls visible.
**Verbatim SPS payload** (40 bytes, frame 1; same across all frames since SPS is per-stream):
```
"\0\0\0\5\320\2\0\0\4\4\2\4\1\1\0\3\0\0\377\377\375\0\0\0\1\0\0\0\0\0\0\0\200\1\0\0\0\0\0\0"
```
Field-by-field decode (per `<linux/v4l2-controls.h>:2096-2125`, struct `v4l2_ctrl_hevc_sps`):
| Bytes (LE) | Value | Field |
|---|---|---|
| `\0` | 0 | `video_parameter_set_id` |
| `\0` | 0 | `seq_parameter_set_id` |
| `\0\5` | 0x0500 = 1280 | `pic_width_in_luma_samples` |
| `\320\2` | 0x02d0 = 720 | `pic_height_in_luma_samples` |
| `\0` | 0 | `bit_depth_luma_minus8` (8-bit) |
| `\0` | 0 | `bit_depth_chroma_minus8` |
| `\4` | 4 | `log2_max_pic_order_cnt_lsb_minus4` |
| `\4` | 4 | `sps_max_dec_pic_buffering_minus1` |
| `\2` | 2 | `sps_max_num_reorder_pics` |
| `\4` | 4 | `sps_max_latency_increase_plus1` |
| `\1` | 1 | `log2_min_luma_coding_block_size_minus3` |
| `\1` | 1 | `log2_diff_max_min_luma_coding_block_size` |
| `\0` | 0 | `log2_min_luma_transform_block_size_minus2` |
| `\3` | 3 | `log2_diff_max_min_luma_transform_block_size` |
| `\0` | 0 | `max_transform_hierarchy_depth_inter` |
| `\0` | 0 | `max_transform_hierarchy_depth_intra` |
| `\377` | 0xFF | `pcm_sample_bit_depth_luma_minus1` (PCM disabled, so default) |
| `\377` | 0xFF | `pcm_sample_bit_depth_chroma_minus1` |
| `\375` | 0xFD | `log2_min_pcm_luma_coding_block_size_minus3` |
| `\0\0\0` | 0,0,0 | `log2_diff_max_min_pcm_luma_coding_block_size`, `num_short_term_ref_pic_sets`, `num_long_term_ref_pics_sps` |
| `\1` | 1 | `chroma_format_idc` (4:2:0) |
| `\0` | 0 | `sps_max_sub_layers_minus1` |
| `\0\0\0\0\0\0` | 0 | `reserved[6]` |
| `\200\1\0\0\0\0\0\0` | 0x180 = SAMPLE_ADAPTIVE_OFFSET (0x08) \| STRONG_INTRA_SMOOTHING_ENABLED (0x100) | `flags` (u64) |
So BBB-720p10s_hevc.mp4 has SAO + strong intra smoothing flagged, no PCM, no scaling-list-enabled in SPS-side flags. The unconditional SCALING_MATRIX submission therefore must be triggered by PPS-side flag or by FFmpeg's runtime probe — not bitstream-driven for SPS.
**Verbatim PPS payload** (64 bytes — `v4l2_ctrl_hevc_pps`, will field-decode in Phase 4 transcription).
**Verbatim DECODE_PARAMS payload** (328 bytes — `v4l2_ctrl_hevc_decode_params`, contains DPB array + POC info, will field-decode in Phase 4).
**Verbatim SLICE_PARAMS payload** (variable size N — dynamic-array of `v4l2_ctrl_hevc_slice_params`; likely 1 slice per frame for x265 ultrafast; size = `sizeof(v4l2_ctrl_hevc_slice_params) * num_slices`).
**Verbatim SCALING_MATRIX payload** (variable size M — `v4l2_ctrl_hevc_scaling_matrix` if scaling_list_data is present; will field-decode in Phase 4).
Raw strace files (gitignored): `phase0_evidence/2026-05-08/iter2_phase3/ffmpeg_v4l2req.strace.*`. Re-run incantation in this document.
**ioctl frequency summary** (verbatim):
```
60 ioctl(/dev/video1, VIDIOC_DQBUF
30 ioctl(/dev/video1, VIDIOC_QBUF
17 ioctl(/dev/video1, VIDIOC_S_EXT_CTRLS
17 ioctl(/dev/video1, VIDIOC_CREATE_BUFS
15 ioctl(/dev/video1, VIDIOC_QUERYBUF
11 ioctl(/dev/video1, VIDIOC_EXPBUF
5 ioctl(/dev/video1, VIDIOC_QUERY_EXT_CTRL (HEVC slice_params dynamic-array introspection)
4 ioctl(/dev/media0, MEDIA_IOC_REQUEST_ALLOC (request_fd pool of 4)
4 MEDIA_REQUEST_IOC_REINIT × 3 fds + MEDIA_REQUEST_IOC_QUEUE × 3 fds
```
17 S_EXT_CTRLS = 2 init device-wide (DECODE_MODE + START_CODE) + 15 per-frame (probably 5 frames × 3 calls each? Or 1 batched per frame × 5 + 10 something else; actual layout is observed-counted, not split-aware — re-extract per-frame breakdown if Phase 6 needs it). Frequency summary is sufficient as an anchor.
## Baseline C — slice-count probe (deferred)
Quick `ffprobe` examination of `bbb_720p10s_hevc.mp4` confirms 1 video stream, hevc Main, 1280×720, 24 fps, 10s. Per-frame slice-count not directly extracted (would require a bitstream-filter pass like `hevc_metadata` to count slices per access unit). For x265 ultrafast preset on 720p, default is 1 slice per frame; assume that until Phase 6 verifies. If Phase 6 finds multi-slice frames, the libva backend's slice_params accumulation logic (Phase 2 Bug 4) handles up to 600 entries per frame per kernel UAPI advertising.
## Baseline D — Bug 1 pre-fix scratch test (collateral safety)
**Goal**: apply Bug 1 fix (config.c break for HEVCMain) on a throwaway branch with `h265.c` + Bug 2 NOT applied, verify H.264 + MPEG-2 reference hashes hold post-rebuild + reinstall. Confirm no collateral damage from the trivial config.c change.
**Patch applied**:
```diff
@@ -68,6 +68,8 @@ VAStatus RequestCreateConfig(VADriverContextP context, VAProfile profile,
// submission time.
break;
case VAProfileHEVCMain:
+ // iter2 Phase 3 scratch — would break here in real fix
+ break;
default:
return VA_STATUS_ERROR_UNSUPPORTED_PROFILE;
```
Build: clean. Install: clean.
**Hash verification** (post Bug 1 scratch, h265.c stays disabled):
```
H.264 +30s:
HW frame 1: f623d5f7a41697f67dd227275c6f1b21ffc257f65626d32fde8229357f8764c9 (matches T4)
HW frame 2: 7d7bc6f2146dda8b2d223bba622c4b9fbe9674181ff1e02afe286b620342e0a8 (matches T4)
MPEG-2 +02s:
HW frame 1: 6e7873030dbf0403c67f35dd106ebef3c7909a0fd12433b82ad758e7fee9f092 (matches iter1)
HW frame 2: ccc7ce08810d4a96e9ba7a19f4f95bbf6cc861bda9337604b5c668ad52bef7de (matches iter1)
```
**Result: ✅ no collateral damage.** Bug 1 fix in isolation does not regress H.264 or MPEG-2.
**HEVC behavior with Bug 1 only** (Bug 2 + Bug 3-6 not applied; h265.c still disabled):
```
[hevc] Task finished with error code: -5 (Input/output error)
```
libva trace verbatim:
```
[49437.712028] vaQueryConfigProfiles ret = VA_STATUS_SUCCESS
[49437.712059] va_TraceCreateConfig profile = 17, VAProfileHEVCMain
[49437.712118] vaCreateConfig ret = VA_STATUS_SUCCESS, success (no error) <-- Bug 1 fix advances past this
[49437.712139] vaQuerySurfaceAttributes ret = VA_STATUS_SUCCESS
... (further surface attribute queries)
... (decode fails downstream because picture.c::codec_set_controls hits the
case VAProfileHEVCMain: return VA_STATUS_ERROR_UNSUPPORTED_PROFILE;
reject at line 204-206 — that's Bug 2, not yet patched in this scratch)
```
So Bug 1 alone advances `vaCreateConfig` to SUCCESS; the next failure is Bug 2's explicit reject at picture.c. Confirms Phase 2 prediction. Bug 2 fix requires h265_set_controls to exist (Bug 3-6: enable + rewrite), so Bug 2 lands together with the h265.c rewrite in Commit B (analogous to iter1 Commit B).
Scratch state cleaned: `git checkout -- src/config.c && rebuild && reinstall`. Master backend back; H.264 + MPEG-2 still pass; HEVC fails at vaCreateConfig (back to Baseline-A-equivalent state — Bug 1 not applied on master).
## What Phase 3 confirms / refutes from Phase 2
| Phase 2 claim | Phase 3 evidence | Status |
|---|---|---|
| Bug 1: config.c HEVCMain falls through to default → vaCreateConfig=12 | Baseline D libva trace: pre-patch fails, post-patch SUCCESS | ✅ confirmed |
| Bug 2: picture.c case VAProfileHEVCMain explicit UNSUPPORTED_PROFILE reject | Baseline D libva log: ffmpeg fails decode -5 (I/O error) post-Bug-1, before reaching h265.c (h265.c disabled) | ✅ confirmed (effect observed, even though h265.c isn't enabled to dispatch) |
| Bug 3: h265.c uses staging-era CIDs that don't exist on modern kernels | Phase 2 source-read + test-compile already verified | ✅ confirmed |
| Phase 2 prediction: per-frame batch has 6 controls | Baseline B verbatim: 5 controls (no ENTRY_POINT_OFFSETS for BBB fixture) | partially refuted: 5 not 6 |
| Phase 2 prediction: SCALING_MATRIX is conditional on bitstream | Baseline B: SCALING_MATRIX always present in BBB's per-frame batch | refuted: FFmpeg gates on kernel-advertised control availability, not bitstream flag |
| Phase 2 prediction: slice_params is dynamic-array | Baseline B: SLICE_PARAMS variable-size (N entries × 1 slice/frame for x265 ultrafast) | ✅ confirmed |
| Substrate intact post pacman -Syu (kernel didn't change) | Baseline A: T4 + iter1 hashes match exactly | ✅ confirmed |
| `linux-eos-arm` kernel binary stays at 6.19.9-99 (only headers jump to 7.0.3) | pacman.log verifies; running `uname -r` confirms | ✅ confirmed |
| OC DTB intact through upgrade | sha256 pre/post upgrade unchanged | ✅ confirmed |
## Phase 4 plan inputs (updated)
Phase 4 plan should specify:
1. **Diff scope** (Phase 2 mostly correct; minor amendments below):
- `src/config.c:64-69` — add `break;` for `VAProfileHEVCMain` (3 lines, mirrors iter1).
- `src/picture.c:204-206` — replace explicit reject with dispatch to `h265_set_controls()`.
- `src/h265.c` — rewrite against new V4L2_CID_STATELESS_HEVC_* split API. Per-frame batch is **5 controls** (SPS, PPS, SLICE_PARAMS dynamic-array, SCALING_MATRIX, DECODE_PARAMS). DECODE_MODE + START_CODE device-wide menus set once at init.
- `src/picture.c::codec_store_buffer` (HEVC VASliceParameterBufferType case) — append-not-overwrite for slice_params accumulation.
- `src/surface.h::params.h265` — add slice_params array + count.
- `src/meson.build` — uncomment `h265.c` (line 50) + `h265.h` (line 73).
2. **SCALING_MATRIX gating** (amended from Phase 2): query kernel via `VIDIOC_QUERY_EXT_CTRL` for `V4L2_CID_STATELESS_HEVC_SCALING_MATRIX` once at init; submit unconditionally per-frame if kernel advertises (matches FFmpeg `ctx->has_scaling_matrix` pattern). For BBB on RK3399, the kernel advertises it, so SCALING_MATRIX always in the batch.
3. **Default scaling matrices** if `iqmatrix_set==false`: per ISO/IEC 23008-2 Table 4-1 default scaling lists (4×4, 8×8, 16×16, 32×32 with separate luma/chroma variants). Phase 6 transcription from FFmpeg's `default_scaling_list_intra` / `default_scaling_list_inter` arrays in `libavcodec/hevc.h` — verify against Baseline B SCALING_MATRIX verbatim payload (all-default values for BBB fixture).
4. **Contract anchor citations** (per Phase 6 contract-before-code):
- Linux mainline UAPI `<linux/v4l2-controls.h>:2090-2300` for the 5+2 stateless HEVC control struct definitions and flag constants.
- FFmpeg `libavcodec/v4l2_request_hevc.c:505-565` (`v4l2_request_hevc_queue_decode`) for batched submission shape.
- Phase 3 Baseline B verbatim 5-control payload (this document).
- VAAPI `<va/va_dec_hevc.h>` for `VAPictureParameterBufferHEVC`, `VASliceParameterBufferHEVC`, `VAIQMatrixBufferHEVC` source field shapes.
5. **Phase 7 verification harness**: re-use iter1's 5-criterion shape, anchor on `mpv --hwdec=vaapi --vo=image` DMA-BUF GL path (cache-coherency-safe per memory `feedback_rockchip_pixel_verify_path.md`). HEVC fixture at +02s seek; HW vs SW byte-identical hashes for 2 distinct frames; iter1 MPEG-2 + T4 H.264 still match. Bonus: byte-compare post-fix S_EXT_CTRLS payload against Baseline B (per `feedback_review_empirical_over_theoretical.md` — empirical wins).
## Open question for Phase 6 close
The iter2 fix-forward pattern from iter1 Commit D (header deletion completeness check via clean rebuild, not grep) applies to iter2's `mpeg2-ctrls.h` re-grep audit during Phase 6 (defensive — even though iter1 cleaned it up). Per `feedback_header_deletion_check.md`. iter2 does NOT delete `include/hevc-ctrls.h` (it's a thin shim, not a duplication header per Phase 2 Bug 7).
## Phase 3 → Phase 4 close
All four baselines green. Substrate intact post-upgrade. HEVC contract anchor captured for Phase 4 transcription. Bug 1 scratch confirms collateral safety. Phase 4 can proceed; estimated h265.c rewrite ~250-400 lines (per Phase 2 estimate; Phase 3 confirms 5-control batch + dynamic-array slice_params + unconditional SCALING_MATRIX as the structural facts to plan against).