# Iteration 5b — Phase 8 (close) Closes 2026-05-12. iter5b = backend OUTPUT-format profile-derivation (option β refactor). PARTIAL PASS per Phase 7 v2: VP9 directly unblocked, MPEG-2 maintained, HEVC + VP8 expose previously-masked bugs. ## Summary | Metric | Value | |---|---| | Iteration target | Backend Bug 2 (`surface.c:173` hardcoded H264_SLICE) via option β (CreateContext-centric OUTPUT lifecycle) | | Hardware | RK3399 rkvdec (`/dev/video1+/dev/media0` this boot) + hantro-vpu-dec (`/dev/video3+/dev/media1`) | | Fork tip start (iter4 close) | `692eaa0` | | Fork tip mid-iter (α' rejected) | `4b2288f` → 3-revert → `6bc29ec` | | Fork tip end (iter5b-β close) | `70196f8` | | Commits authored | 3 β commits + 3 α'-reverts + 1 Commit D fix-forward + 3 α' commits in history = 10 fork commits net 4 visible β changes | | LOC delta (β + D vs iter4 tip) | +320 / -293 across 8 files + 2 new | | Phase 1 criteria | 1/4 PARTIAL · 3/4 PASS | | Phase 7 → Phase 4 loopbacks | 1 (α' rejected, β chosen) | | Phase 6 fix-forwards | 1 (Commit D for vaapi-copy late-surface flow) | | Phase 5 review findings | iter5b-α' Phase 5: 1 CRIT (mechanical) + 3 IMP. iter5b-β Phase 5 v2: 2 CRIT (NULL guard, missing request_pool_destroy) + 3 IMP. All amendments incorporated. | | Campaign scoreboard | 5/5 with **1 newly direct** (VP9) + 1 maintained direct (MPEG-2) + 3 mixed (H.264 keyframe-partial, VP8 partial-new-Bug-6, HEVC pre-existing-Bug-5) | ## Commits shipped ### Fork (libva-v4l2-request-fourier) **β-final commits** (the actual iter5b shipped code): | SHA | Files | LOC | Description | |---|---|---|---| | `1c548b1` (A) | `src/codec.h` (new) · `src/codec.c` (new) · `src/meson.build` | +102 / -2 | `pixelformat_for_profile()` helper mapping VAProfile → V4L2 OUTPUT FOURCC | | `cc077a0` (B) | `src/config.c` | +11 | Wire `object_config->pixelformat` at CreateConfig | | `7055b14` (C) | `src/surface.c` · `src/surface.h` · `src/context.c` · `src/request.h` | +238 / -280 | β refactor: CreateSurfaces2 stripped, CreateContext owns OUTPUT lifecycle. CRIT-1 (NULL guard) + CRIT-2 (request_pool_destroy) + IMP-1 (probe order) + IMP-2 (dead field cleanup) all incorporated | | `70196f8` (D) | `src/context.c` · `src/surface.c` · `src/surface.h` · `src/request.h` | +136 / -35 | Fix-forward: format-uniform cache in driver_data, walk surface_heap, lazy-fill in CreateSurfaces2. Closes the ffmpeg-vaapi-copy surfaces_count=0 gap | **α' history** (rejected, kept in commit log): | SHA | Status | |---|---| | `ce304ef` (α' A) | Reverted by `6bc29ec` | | `f8256e6` (α' B) | Reverted by `9a7f888` | | `4b2288f` (α' C) | Reverted by `709ab34` | ### Campaign repo (fresnel-fourier) | Commit | Phase | Description | |---|---|---| | `8acfca3` | Phase 0 v1 | Lock Candidate B (vb2_dma_resv) — later corrected via Phase 2 source-read | | `31b9255` | Phase 0 amend | Bug 3 collapsed, criteria 5→4 | | `9941523` | Phase 2 v1 | Situation, RFC v2 patches | | `3c05564` | Phase 3 | 5-codec baseline sweep + all-zero cap_pool init signature | | `a809e9c` | Phase 4 v1 | Option α' plan (rejected later) | | `7d1c44b` | Phase 5 v1 | α' review | | `550bb81` | Phase 6 v1 | α' commits landed (also reverted later) | | `864af25` | Phase 7 v1 | α' FAIL (HEVC SIGSEGV) → loopback | | `5abea73` | Phase 4 v2 | Option β plan | | `3508a2c` | Phase 5 v2 | β review (CRIT-1, CRIT-2) | | `311411b` | Phase 6 v2 | β commits landed | | `c773c3d` | Phase 7 v2 | β PARTIAL PASS verdict | ## What worked - **Option β architecture** delivered its core claim: CreateSurfaces2 touches no V4L2 device state, CreateContext owns the OUTPUT lifecycle. The α' destructive-teardown failure mode is structurally eliminated. - **VP9 unblocked directly**: `libva == kdirect == sw` for `bbb_720p10s_vp9.webm`. The iter4 transitive proof becomes a direct proof. - **MPEG-2 not regressed**: after Commit D, libva == kdirect (HW≠SW is codec precision drift, unrelated to Bug 2). - **H.264 keyframe path preserved**: same `71ac099b…` hash as pre-iter5b. Bug 4 deferred cleanly. - **Phase 5 reviewer's empirical-over-theoretical discipline saved Phase 6 twice**: - α' Phase 5 caught a compile-failing iterator type bug. - β Phase 5 v2 caught CRIT-1 (NULL guard) + CRIT-2 (missing `request_pool_destroy`) before either would have shipped broken. ## What didn't work / surfaced ### Bug 5 — HEVC libva decode rejected by kernel rkvdec Pre-iter5b AND post-β: every DQBUF on HEVC libva path has `V4L2_BUF_FLAG_ERROR`. Kernel rkvdec rejects the decode regardless of OUTPUT pixel format (β fixed format, ERROR persisted). iter2 closed HEVC as PASS via transitive proof. The proof verified backend's `VIDIOC_S_EXT_CTRLS` payload byte-matched kdirect's payload — which is correct as far as it goes, but doesn't catch V4L2 protocol-level differences (request_fd binding sequencing, ioctl ordering, sequence numbers, etc.). **Reframing**: iter2's HEVC "PASS" was a partial proof. The control payload is correct; what's wrong is something else in the libva backend's V4L2 protocol use that kernel-direct ffmpeg-v4l2request does differently. Out of iter5b scope; deferred to iter6+ as Bug 5. ### Bug 6 — VP8 libva decode produces non-zero but non-matching output Pre-iter5b: all-zero. Post-β: real content (256 unique bytes, plausible BBB keyframe pixels at `93 8e 8a 89 …`) but doesn't byte-match kdirect. Some frames differ from others (motion content). 74.8% bytes are 0x00 — suggests partial fill. Plausible causes: slot-rotation bug, partial-buffer-fill, or per-frame DPB binding issue. Hadn't surfaced pre-β because all-zero output masked it. Out of iter5b scope; deferred to iter6+ as Bug 6. ### Bug 4 (carried from iter4 Phase 7 doc) — H.264 inter-frame race-loss Persisted unchanged through iter5b. H.264 keyframe decodes; inter frames return zero. Some race or sync issue specific to H.264 inter. Out of iter5b-β scope by Phase 1 lock. ## Phase 1 success criteria — final reckoning | # | Criterion | Verdict | Evidence | |---|---|---|---| | 1 | HEVC + VP9 + VP8 libva == kdirect | **PARTIAL (1 of 3)** | VP9 ✓, HEVC ✗ (Bug 5), VP8 ✗ (Bug 6) | | 2 | MPEG-2 unchanged | **PASS** | `libva_mpeg2 == kdirect_mpeg2 == 19eefbf4…` post-Commit-D | | 3 | H.264 keyframe still decodes | **PASS** | `libva_h264 == 71ac099b…` unchanged (Bug 4 deferred) | | 4 | Control-payload anchors hold | **PASS** | β touched no control-handling code | Net: 3 of 4 PASS. Criterion 1 hit 1/3 of its goal. iter5b-β does not close fully. Bugs 4/5/6 are explicit findings handed off to iter6+. ## Lessons distilled ### Phase 5 reviewer track-record (both reviews) | Review | Findings | Author re-verified | Estimated savings | |---|---|---|---| | α' Phase 5 | 1 CRIT (mechanical) + 3 IMP | All correct | 1 failed `meson compile` cycle | | β Phase 5 v2 | 2 CRIT (NULL guard, request_pool_destroy) + 3 IMP | All correct | β would have crashed at first CreateContext without CRIT-1; second CreateContext would have hung on stale pool without CRIT-2. Half a session at least. | Both reviews paid off. Empirical-over-theoretical (Direction 2) worked: reviewers grepped, enumerated, and read source rather than trusting the plan's surface claims. ### α' failure: dismissing the TODO comment The Phase 4 v1 plan dismissed `surface.c:164-171`'s TODO comment as targeting multi-resolution-mid-stream only, on the grounds that iter5b's bug was "initial CreateSurfaces2" not "multi-resolution mid-stream." Empirically wrong: ffmpeg-vaapi's multi-CreateSurfaces2 pattern hit the same gate, triggered destructive teardown mid-stream, and SIGSEGV'd HEVC. The Phase 2 v1 reviewer's original recommendation (option β) was correct. iter5b's Phase 4 v1 author (claude) overrode it on insufficient grounds. **Trust Phase 2 architectural recommendations when smaller alternatives carry lifecycle ordering risk.** ### Commit D surprise: vaapi-copy passes surfaces_count=0 The iter6 comment at `context.c:262` explicitly documented that ffmpeg-vaapi-copy passes `surfaces_count=0` to vaCreateContext. Phase 4 v2 plan acknowledged the comment but didn't trace the implication for the new destination_* walk in CreateContext. **Plans that touch lifecycle code must enumerate ALL existing comments AND grep ALL call sites for affected symbols.** ### Transitive proof's blind spot iter1-4 closed HEVC/VP8/VP9 via "transitive proof": backend control bytes == kdirect control bytes AND kdirect decode == SW. The proof verified the BACKEND ↔ KDIRECT contract on a SPECIFIC ARTIFACT (control payloads). It did NOT verify the actual decode outcome through the backend. iter5b-β Phase 7 confirmed: - VP9: transitive PASS held under direct verification. - HEVC: transitive PASS did NOT hold under direct verification — Bug 5 was masked. - VP8: transitive PASS partially held (decode runs, output diverges) — Bug 6 was masked. **Transitive proofs are useful but partial.** They prove the part they verify. They don't prove what they don't verify. Future "transitive PASS" claims should also state explicitly what is NOT verified. ## Memory rule updates ### Extend `feedback_review_empirical_over_theoretical.md` Existing rule: "lean empirical, not theoretical (BOTH directions) — empirical wins over source-read in BOTH author-rebuttal AND author-adopting-reviewer-amendment directions. Test-compile reviewer-suggested field mappings before incorporating into the amended plan." Add: when a plan claims "no change needed" for a code region, the author must grep all affected symbols/functions/macros and enumerate the call sites. The iter5b-β Phase 4 v2 plan's `DestroyContext — no change needed` claim missed that `request_pool_destroy` had only ONE per-session caller (the line surface.c:220 which β strips), invisible from a surface read. Phase 5 v2 reviewer's grep caught it. ### Sibling rule from Commit D `feedback_trust_iter_comments_for_lifecycle.md` (proposed): when refactoring lifecycle code (CreateContext, CreateSurfaces2, BeginPicture, DestroyContext), enumerate ALL inline comments in the affected functions. Each comment was placed because some prior iteration discovered a specific bug; the comment encodes the contract that bug discovery established. A plan that doesn't trace the implication of each comment risks re-introducing the bug. Specific example: `context.c:262` documented ffmpeg-vaapi-copy's `surfaces_count==0` pattern from iter6. Phase 4 v2 read the comment but didn't trace "what does my new code do when surfaces_count==0?" Phase 7 revealed the answer. Defer the memory-rule write to a separate commit alongside this Phase 8 close. ### Transitive-proof discipline note Add to `reference_dmabuf_resv_blocker.md` (which documented the transitive-proof pattern): explicit caveat that transitive proofs verify the AXIS proven (e.g., control payload byte-equality) but don't verify other axes (decode-actually-succeeds, output-actually-matches). When using transitive proof, state what's verified AND what's not. ## Phase 4 cross-cutting backlog status (iter5b-β increment) Inherited from iter4: - **iter4-B1** auto-detect device discrimination — STILL OPEN. Device numbering shuffles per boot continue to cost a few minutes per session. - **iter4-B2** mpv-vaapi `Could not create device` — STILL OPEN. Consumer-side. - **iter4-Q6**, **COLOR_RANGE** — STILL OPEN. - **B3** picture.c BeginPicture profile-aware reset — STILL OPEN. - **B4** context.c log suppression for unsupported codec controls — STILL OPEN. - **B5** mpeg2 vbv_buffer_size — STILL OPEN. - **B6** h265 SPS bitstream-parse fidelity gap — STILL OPEN. - **L3** vaDeriveImage cache-stale — STILL OPEN. iter5b NEW backlog items: - **Bug 4** H.264 inter-frame race-loss (carried from iter4 P7 doc). - **Bug 5** HEVC libva decode rejected by kernel (DQBUF FLAG_ERROR). Pre-existing but only now explicitly named. - **Bug 6** VP8 libva decode runs but output diverges (74.8% zero with real keyframe content). - **`object_heap_first/next` iterator pattern** is canonicalized — Commit D uses it correctly per the iter5b-α' Phase 5 review CRIT-1 amendment. Worth documenting in a memory entry if not already covered. ## Iter5b → iter6 handoff Substrate state at iter5b-β close: - Fork tip `70196f8` on noether + fresnel + gitea. - Backend installed SHA `2c6ff82cbdc156ff8910d0c7fe58e75eeecdfd6e6a1caabb049c8adf43a098b8` on fresnel. - Kernel `linux-fresnel-fourier 7.0-1` (unchanged). - Test fixtures unchanged. - Phase 3 baseline anchors at `iter5_phase3_baseline.tgz` still valid for iter6 Phase 3 reference. - Phase 7 v2 artifacts at fresnel `/tmp/iter5b_p7v2/`. iter6 candidate research questions (for user lock): - **Candidate F**: Bug 5 — fix HEVC libva decode (whatever V4L2 protocol contract aspect the backend gets wrong vs kdirect). - **Candidate G**: Bug 6 — fix VP8 libva partial output (slot rotation, partial fill, or DPB binding). - **Candidate H**: Bug 4 — fix H.264 inter-frame race-loss. - **Candidate I**: Re-anchor regression hashes on the iter5b-β substrate so iter6+ can use direct verification anchors (now that VP9 + MPEG-2 work, the per-codec stable hashes serve as the regression-test baseline). - **Candidate J**: iter4-B1 auto-detect harden (device discrimination, eliminate per-boot env-override). ## Campaign scoreboard update ``` Codec | Site | Iter | Status | Verifier path ========|===========|========|===============|==================================== H.264 | rkvdec | T4 | PARTIAL | mpv keyframe-seek (Bug 4 inter race) MPEG-2 | hantro | iter1 | PASS direct | ffmpeg-vaapi-hwdownload HEVC | rkvdec | iter2 | DEGRADED * | transitive PASS / direct FAIL (Bug 5) VP8 | hantro | iter3 | PARTIAL | transitive PASS / direct partial (Bug 6) VP9 | rkvdec | iter4→iter5b | PASS direct ** | ffmpeg-vaapi-hwdownload (iter5b-β fix) ``` \* HEVC: iter2 closed as transitive PASS. iter5b-β empirically verified direct-mode decode fails (DQBUF ERROR). iter2's transitive-proof claim was technically true but masks a separate bug. \*\* VP9: iter4 closed as transitive PASS (criterion-4 via transitive). iter5b-β upgrades to direct PASS. Net iter5b-β contribution to campaign: - **+1 direct verification** (VP9). - **+0 codecs in fresh broken state** (all regressions caught + reverted in-iteration). - **+2 named bugs** (5 and 6) for iter6+ work. - **+1 architectural cleanup** (CreateSurfaces2 is now ~70 LOC, single-responsibility). ## Phase 8 commit + close This document records iter5b-β close. Fork is at `70196f8`, backend on fresnel at SHA `2c6ff82c…`, campaign repo updated through Phase 7 v2 at `c773c3d`. Memory rule updates land as separate commits per the lessons above. iter6 Phase 0 awaits user research-question lock.