## Iteration 29/30/31 — Phase 8 (close): HEVC frame 2+ FIXED

Closes 2026-05-14, second campaign-day session after iter27/28 partial close. Three sub-iterations: iter29 probe (refuted 40-byte inflation theory), iter30 timestamp-magnitude sweep (refuted timestamp hypothesis), **iter31 α-29 (FIX)**.

### Final result: 3/3 PASS (anchors that go through libva single-device probe)

| Codec | 10F result | Status |
|---|---|---|
| H.264 | byte-equal to SW (10 frames) | **PASS** (Bug 4 already fixed iter25 α-25) |
| HEVC  | byte-equal to SW (10 frames) | **PASS** (Bug 5 NOW FIXED iter31 α-29) |
| VP9   | byte-equal to SW (10 frames) | **PASS** (unchanged) |
| MPEG-2| untestable through libva | pre-existing single-device probe limitation |
| VP8   | untestable through libva | same |

### Root cause: wrong V4L2 field

α-26 had set `decode_params->short_term_ref_pic_set_size = picture->st_rps_bits` based on the FIELD NAME. But `picture->st_rps_bits` from VAAPI is documented as the bit-count of `short_term_ref_pic_set()` in the **slice segment header** (per `/usr/include/va/va_dec_hevc.h:177-185`). The V4L2 SLICE-PARAMS field of the same name is what rkvdec reads. The V4L2 DECODE-PARAMS field with this name (where α-26 wrote it) refers to the SPS-side bit count, which rkvdec does not use.

`rkvdec_hevc.c::assemble_sw_rps` (lines 386-389) reads `sl_params->short_term_ref_pic_set_size`:
```c
if (!(decode_params->flags & V4L2_HEVC_DECODE_PARAM_FLAG_IDR_PIC)) {
    if (sl_params->short_term_ref_pic_set_size)
        st_bit_offset = sl_params->short_term_ref_pic_set_size;
    else if (sps->num_short_term_ref_pic_sets > 1)
        st_bit_offset = fls(sps->num_short_term_ref_pic_sets - 1);
}
```

For BBB's `num_short_term_ref_pic_sets == 1`, the fallback (`fls(0) = 0`) is wrong — the HW reads slice-header bits from offset 0, treats the st_ref_pic_set() bytes as long-term-RPS / slice-header continuation, and the entropy decoder spins onto garbage state for every non-IDR slice. IDR is gated out by `V4L2_HEVC_DECODE_PARAM_FLAG_IDR_PIC` → frame 1 unaffected → consistent with prior iter25 "frame 1 PASS, frame 2+ FAIL" observation.

### Fix: α-29

```c
// src/h265.c::h265_fill_slice_params, line 476:
- slice_params->short_term_ref_pic_set_size = 0;  /* VAAPI doesn't expose */
+ slice_params->short_term_ref_pic_set_size = picture->st_rps_bits;
```

Backend commit `23eb1bd`. Build, deploy, re-test → 10/10 PASS for HEVC.

### Investigation path

- **iter29** added env-gated dump of last 80 bytes of HEVC slice_data. Trailing 80 bytes look like real entropy — refuted the "40-byte ffmpeg-vaapi inflation" hypothesis from iter27/28 close.
- **iter30** added `LIBVA_TS_SCALE` env-gated timestamp multiplier. Tested scales 1/1000/1000000. All produced identical (wrong) frame-2 output. Refuted timestamp-magnitude hypothesis.
- **iter31** extended kernel iter27 printk to dump `dpb[2..3]` (offsets 96..128) and `sl[32..64]`. Direct libva-vs-kdirect comparison for POC=1 (display frame 2, decode #4) revealed all slice_params[0..64] bytes match EXCEPT `num_entry_point_offsets` (known unused by rkvdec). Re-reading rkvdec's assemble_sw_rps located the `sl_params->short_term_ref_pic_set_size` read site, then traced backward to ffmpeg-v4l2request's `sh->short_term_ref_pic_set_size` source. VAAPI's `picture->st_rps_bits` is the semantic equivalent.

### Substrate state at iter31 close

- Backend fork tip `23eb1bd` (α-25 through α-29 + iter29 dump + iter30 ts-scale env probes; α-29 is the load-bearing fix; others are env-gated and inactive by default).
- Kernel `7.0-10` with iter17 + iter20 + iter21 + iter22 + iter23 + iter27 + iter31 printks (production kernel would revert all these — outstanding cleanup item).
- 5-codec status: 3 PASS, 2 untestable. MPEG-2/VP8 reachability requires libva multi-device-probe rework (separate iter — orthogonal to Bug 4/5).

### Campaign milestone

Bug 4 fix landed iter25 α-25. Bug 5 frame 1 fix landed same iter25 α-25. Bug 5 remainder (frames 2+) localized over iter26-28 (correctly: rkvdec reads field X, libva sends 0), mis-fixed in α-26 (right value, wrong field), refuted in iter29-30 (rule-outs), correctly fixed in α-29 (right value to right field). Total: 1 day for full pixel correctness across the three rkvdec-routed codecs.

### Memory entries to update

- `feedback_rkvdec_image_fmt_pre_seed.md` — note the remainder-fix landed (no longer "deferred").
- `feedback_libva_byte_correct_kernel_bug.md` — fully overturned now (Bug 5 is fully a libva-side fix, not kernel-side).
- New: `feedback_va_st_rps_bits_is_slice_field.md` — VAAPI's picture->st_rps_bits goes into slice_params not decode_params (the field NAME is identical, semantics differ).
