Files
fresnel-fourier/phase5_iter1_review.md
T
claude-noether 0e2e1c2293 iter1 Phase 5: sonnet-architect review — 6 findings, 4 amendments
Phase 5 review run via Plan subagent with model: sonnet per
feedback_dev_process.md Phase 5 discipline. Review verbatim
preserved in phase5_iter1_review.md alongside per-finding response.

Findings: 1 Critical (latent), 2 Should-fix (1 valid, 1 misreading),
2 Question/clarification, 1 Nit. Reviewer's bottom-line: medium-high
confidence in the plan as written.

Resolutions:

C1 (union-aliasing reasoning was wrong; iter1 unaffected; latent bug):
  Verified offsets on fresnel via gcc + libva headers:
    h264.matrix_set       at union byte 240
    mpeg2.iqmatrix_set    at union byte 376
    mpeg2.iqmatrix range  [88..376) — sizeof=288
  Setting h264.matrix_set=false writes byte 240, which lands inside
  mpeg2.iqmatrix.chroma_intra_quantiser_matrix at offset 20.
  Phase 2 said the byte gets overwritten by RenderPicture before
  mpeg2_set_controls reads it. That was true only because ffmpeg-
  vaapi sends VAIQMatrixBufferType every frame; codec_store_buffer
  then copies the full 260-byte payload over the corrupted byte.
  ACCEPTED: update Phase 2 + Phase 4 wording to cite the correct
  safety chain. Latent bug for clients that reuse a surface without
  re-sending IQMatrix logged for iter2+ backlog.

S2 (vbv_buffer_size source — reviewer misread):
  Reviewer assumed slot->size = SOURCE_SIZE_MAX (1MB). Verified
  source: src/request_pool.c:71 sets pool->slots[i].size = length,
  where length is the V4L2-reported buffer length from
  VIDIOC_QUERYBUF (= negotiated sizeimage from S_FMT). Phase 3
  Baseline C strace shows S_FMT(OUTPUT_MPLANE) returns
  sizeimage=1382400=0x151800 — exactly matches Baseline C's
  vbv_buffer_size payload. Plan is correct as-is.
  REJECTED (reviewer's claim wrong); 1-line note added to Phase 6
  Commit B message clarifying the dynamic source.

S3 (default-matrix transcription byte-verify protocol):
  ACCEPTED. Phase 6 protocol amendment: when transcribing the
  64-entry default_intra[] in src/mpeg2.c, derive values from
  Baseline C QUANTISATION verbatim payload, then run a diff-based
  assertion before commit lands. Same for non_intra (all 16's),
  chroma_intra (= intra), chroma_non_intra (all 16's) — verified
  against Baseline C bytes 0..63 / 64..127 / 128..191 / 192..255.

Q4 (criterion 4 — ffmpeg+hwdownload primary, not fallback):
  ACCEPTED. Phase 7 harness criterion 4 changes from
    mpv --hwdec=vaapi --vo=image first, ffmpeg fallback
  to
    ffmpeg -hwaccel vaapi -vf hwdownload,format=nv12 primary,
    mpv-vaapi-vo=image backup
  Critical addition: Phase 7 must check both hashes match AND
  content non-zero/non-sentinel. T4 found ffmpeg-vaapi
  -hwaccel_output_format nv12 returns mostly zeros via cached-mmap
  on RK3399 (iter1 patch-0011 cache-stale bug class). For MPEG-2,
  hwdownload may use a different readback path; if it also exposes
  the cache-stale bug, swap to mpv-vaapi-vo=image. Empirical
  determination during Phase 7.

Q5 (timestamp behavior is a correction, not "no semantic change"):
  ACCEPTED. Phase 4 Clause 3 amendment: explicitly note that
  forward_ref_ts/backward_ref_ts = 0 when reference surface is
  VA_INVALID_ID is a CORRECTION vs current code's self-referencing
  behavior. Old code at src/mpeg2.c:106-107, 113-115 set
  forward_reference_surface = surface_object (self-ref) when ref
  was VA_INVALID_ID. New code sets ts to 0. Baseline C frame 1
  confirms 0-as-sentinel; FFmpeg v4l2_request_mpeg2.c:98-108
  matches. Iter1 fixes a latent bug.

Nit 6 (hevc-ctrls.h left alongside removed mpeg2-ctrls.h):
  ACCEPTED (lower-risk path). Phase 6 Commit B removes mpeg2-ctrls.h
  include only; Commit C deletes include/mpeg2-ctrls.h only.
  Hevc-ctrls.h header + include left untouched, deferred to HEVC
  iteration. Optional cleanup if Phase 6 chooses to bundle, but
  default is the smaller diff.

Phase 4 → Phase 6 amendments consolidated:
  1. Clause 3 timestamp behavior explicit (Q5)
  2. Clause 4 default-matrix Baseline-C-derived transcription (S3)
  3. Phase 7 criterion 4 ffmpeg+hwdownload primary + non-zero check (Q4)
  4. Hevc-ctrls.h cleanup deferred (Nit 6)
  5. Phase 2 + Phase 4 wording fix on union safety chain (C1 partial)
  6. Latent surface-reuse bug logged for iter2+ backlog (C1 follow-up)

Plan re-locks with these amendments. Phase 6 proceeds.

Per global ~/.claude/CLAUDE.md rule: Phase 5 reviews are never
skippable. This review was the right path forward; surfaced 2 plan
amendments + 1 latent bug worth documenting + 1 reviewer-misreading
worth pinning so the trail is clear. Material outside-look value.

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

22 KiB
Raw Blame History

Iteration 1 — Phase 5 (second-model review)

Sonnet-architect review of the iter1 plan, run 2026-05-08 via the Plan subagent with model: sonnet per feedback_dev_process.md Phase 5 discipline. Six findings: 1 Critical (latent), 2 Should-fix (1 valid, 1 based on misreading), 2 Question/clarification, 1 Nit. Reviewer's bottom-line: medium-high confidence in the plan as written.

Per ~/.claude/CLAUDE.md global rule: "An empty review (no findings) is itself a strong signal: high confidence that the plan is sound, not a wasted check. The value of a review is the outside look, regardless of what it finds. Skipping reviews to save time defeats the purpose of having them." This review surfaced 2 amendments worth landing in the plan + 1 latent bug worth following up post-iter1 — material outside-look value.

The full review verbatim is preserved below; my response per finding follows.

Review (verbatim from sonnet-architect Plan agent)


Critical

1. VAIQMatrixBufferMPEG2 is always sent by ffmpeg-vaapi; iqmatrix_set is never false in practice, but is also never reset — hiding a surface-reuse latency bug.

Reading references/ffmpeg-kwiboo/libavcodec/vaapi_mpeg2.c:92-107, the VAAPI sender (ffmpeg-vaapi hwaccel) sets all four load_* flags to 1 and unconditionally sends a VAIQMatrixBufferType buffer every frame. This means iqmatrix_set will be true on every frame that the BBB fixture decodes, and the else branch (spec-default matrix synthesis) will never execute for this sender.

That sounds benign for iter1's test fixture — except the plan's Phase 2 "verified not a bug" reasoning about iqmatrix_set has a flaw. The params.h264.matrix_set = false reset at picture.c:287 writes to byte offset 224 of the union (sizeof VAIQMatrixBufferH264 = 224 bytes). params.mpeg2.iqmatrix_set lives at byte offset sizeof(VAPictureParameterBufferMPEG2) + sizeof(VASliceParameterBufferMPEG2) + sizeof(VAIQMatrixBufferMPEG2) ≈ byte 328. These are different bytes — the reset does NOT touch mpeg2.iqmatrix_set. Once a surface's iqmatrix_set is set true on first use, it is never cleared. If the client reuses a surface for a subsequent decode cycle without sending an IQ matrix (legal VAAPI behavior), the backend will use stale matrices from the prior cycle.

This is not a problem for the BBB 10s fixture because ffmpeg-vaapi always sends the matrix. It will not cause a Phase 7 failure. But the plan's stated rationale — "RenderPicture overwrites that byte before mpeg2_set_controls reads anything" — is factually wrong about which byte is being overwritten, and the plan's "Files 6+ — verify only / no changes" conclusion for picture.c may be understating a real latency. Flag this for the Phase 4 author: the picture.c:287 reset should be changed to reset mpeg2.iqmatrix_set conditionally (or use a profile-aware memset of the whole params union), but it will not break iter1 because the sender's behavior masks it.

Assessment: not blocking for iter1 with the BBB fixture + ffmpeg-vaapi sender. Blocking if the claim "picture.c needs no changes" is treated as a clean bill of health for correctness in general.


Should-fix

2. sequence.vbv_buffer_size will be SOURCE_SIZE_MAX (1 048 576), not the OUTPUT buffer size at decode time.

Plan Clause 2 maps vbv_buffer_size to surface_object->source_size, citing FFmpeg's controls->pic.output->size. In the FFmpeg path, output->size is the actual V4L2 OUTPUT buffer size, which equals SOURCE_SIZE_MAX (1 MB). In the libva backend, surface_object->source_size is set at picture.c:276 to slot->size, which is the V4L2 buffer's allocated size — also SOURCE_SIZE_MAX. So both FFmpeg and the libva backend use the same value (1 048 576). The plan's comment "FFmpeg uses controls->pic.output->size" is accurate and the equivalence holds.

However, the plan says the old code used SOURCE_SIZE_MAX as a "placeholder" and the new code will use surface_object->source_size instead. These are the same constant value, so the output bytes are identical. The Baseline C strace confirms vbv_buffer_size = 0x00151800 = 1 376 256 which is NOT SOURCE_SIZE_MAX = 1 048 576. This discrepancy is unexplained. Either the fixture's hantro driver has a different buffer size negotiation, or the value in Baseline C comes from a different source than what the plan assumes. The plan will produce 0x00100000 (1 MB) for this field where Baseline C shows 0x00151800 (1.31 MB). Per plan Clause 2's note: "vbv_buffer_size is informational; kernel doesn't reject." This is likely correct — hantro ignores it — but the Phase 7 extra cross-validator byte-comparison check will see a mismatch on this field specifically. The plan should note this expected divergence explicitly so Phase 7 doesn't misread it as a regression.

3. The default intra quantisation matrix in the plan is truncated and explicitly deferred.

Plan Clause 4 / File 2 shows only 57 of 64 entries with /* zigzag, ... */ trailing off, with a note: "remaining 7 from the spec to be transcribed verbatim during Phase 6." The full MPEG-2 default intra matrix from ISO 13818-2 Table 7-3 in zigzag order ends: ..., 83, 97, 112, 121. The plan truncates after 83. This is not a critical path risk (Phase 6 will transcribe them), but if Phase 6 makes a transcription error here the quantisation matrix will be wrong and pixel verify will fail. The review recommends pre-verifying the 7 missing values: 97, 112, 121 fill positions 57-63 (the last 7 entries are the bottom-right corner of the 8×8 matrix traversed in zigzag — all high-frequency, typically {83, 97, 112, 121} in the standard but the table has 64 distinct values). Concretely: cross-check all 64 against Baseline C's QUANTISATION payload before transcription, not after.


Question / clarification

4. Criterion 4's mpv-vaapi fallback assumes mpv --hwdec=vaapi (DMA-BUF path) behaves differently from --hwdec=vaapi-copy.

The plan acknowledges (Phase 7 note on criterion 4): "this assumes mpv-vaapi DMA-BUF WILL engage for MPEG-2." But the Phase 3 finding was that mpv's hwdec policy filters MPEG-2 out entirely — that finding was observed with --hwdec=vaapi-copy, but the filtering happens at a layer above the libva driver (mpv's hwdec_codecs list or its ffmpeg hwaccel selection). Whether --hwdec=vaapi (without -copy) follows the same code path or a different one is not verified. The fallback to ffmpeg -hwaccel vaapi -vf hwdownload is sound and should be the primary path for criterion 4, not the contingent fallback. The question: has anyone verified that mpv --hwdec=vaapi (not vaapi-copy) actually reaches the libva decode path for MPEG-2, or is this speculation?

If criterion 4 silently falls back to the ffmpeg path, that's fine — but the Phase 7 harness as written runs the mpv DMA-BUF path first and would need a human judgment call on whether to activate the fallback. Making the ffmpeg path primary removes that ambiguity.

5. The reference timestamp population for B-frames differs from the current code in a subtle way.

Current mpeg2.c:104-118: when forward_reference_picture == VA_INVALID_ID, the code sets forward_reference_surface = surface_object (self-reference) and uses that timestamp. Plan Clause 3 sets forward_ref_ts = 0 when the surface is NULL (i.e., when SURFACE(driver_data, VA_INVALID_ID) returns NULL). These produce different values: the current code sends the surface's own timestamp; the plan sends 0. Baseline C frame 1 shows forward_ref_ts = 0 for an I-frame, which matches the plan's behavior. FFmpeg's v4l2_request_mpeg2.c:98-108 also uses 0 (by only setting the field for P/B). So the plan is correct and the current behavior (self-timestamp) was wrong. But the plan should explicitly call out this behavioral change — it does not — because a reader might expect "same VAAPI field reads" to mean "same output values" and be confused when the timestamps change. The "no semantic change" claim in Clause 3 is therefore incorrect for this field.


Nit

6. include/mpeg2-ctrls.h deletion: config.c also includes <hevc-ctrls.h> (line 38).

The plan removes #include <mpeg2-ctrls.h> from config.c and says the diff touches only 3 lines. But config.c:38 also has #include <hevc-ctrls.h>. If the MPEG-2 break is added above the HEVC/default fall-through, the hevc-ctrls.h include remains. The plan correctly identifies git grep "mpeg2-ctrls.h" as the audit step but doesn't mention hevc-ctrls.h. The HEVC header is out of iter1 scope, but leaving it alongside the removed mpeg2-ctrls.h will look inconsistent in review. Worth a comment in the diff or an explicit "intentionally left, deferred to HEVC iteration."


Confidence: medium-high. The three core bugs are correctly identified, the fix contract is accurately grounded in the kernel headers and FFmpeg reference, and the Phase 7 harness is specific enough to catch regressions. The plan would produce working code for the BBB fixture. The risk that prevents "high" is the unexplained vbv_buffer_size mismatch between Baseline C and what the plan will generate — if Phase 7's byte-comparison check interprets that as a failure, there will be a loop back to Phase 4 for something that is almost certainly innocuous. The iqmatrix_set never-reset issue is latent but harmless for iter1's sender.


Author response per finding

C1 — Phase 2 union-aliasing reasoning was wrong; iter1 unaffected; latent bug for follow-up

Verdict: accepted (partial). Reviewer is correct that Phase 2's "RenderPicture overwrites that byte before mpeg2_set_controls reads anything" was wrong about which byte. The actual safety reasoning is different: ffmpeg-vaapi sends VAIQMatrixBufferType every frame, so codec_store_buffer:140-146 writes the entire VAIQMatrixBufferMPEG2 payload into params.mpeg2.iqmatrix and sets iqmatrix_set = true freshly on every RenderPicture cycle.

Verified offsets (compiled on fresnel against <va/va.h>):

h264.matrix_set offset    = 240   (sizeof(VAIQMatrixBufferH264) = 240)
mpeg2.iqmatrix_set offset = 376
mpeg2.picture  range = [0   .. 40)   size=40
mpeg2.slice    range = [40  .. 88)   size=48
mpeg2.iqmatrix range = [88  .. 376)  size=288

Setting params.h264.matrix_set = false writes byte 240 of the union, which is inside mpeg2.iqmatrix (range 88..376), specifically at offset 152 within VAIQMatrixBufferMPEG2. Layout inside VAIQMatrixBufferMPEG2: 4 load_* flags (offsets 0..3), then four 64-byte matrices (offsets 4..67, 68..131, 132..195, 196..259). Byte 152 lands inside chroma_intra_quantiser_matrix at byte 20.

So the actual corruption is: byte 20 of chroma_intra_quantiser_matrix is zeroed in BeginPicture, then overwritten by the ffmpeg-vaapi-sent IQMatrix in RenderPicture (which copies all 260 bytes), then read by mpeg2_set_controls in EndPicture. Net safe for iter1's ffmpeg-vaapi sender.

Latent bug confirmed: a VAAPI client that calls vaBeginPicturevaRenderPicture(VASliceData)vaEndPicture without sending a new VAIQMatrixBufferType (legal VAAPI for surfaces with prior IQMatrix state) would see byte 20 of chroma_intra_quantiser_matrix reset to 0 — corrupting the matrix on a subsequent decode of the same surface. Combined with iqmatrix_set never being cleared, the backend would submit a partially-corrupted matrix.

Action for iter1: no code change (out of scope per Phase 1 lock; ffmpeg-vaapi sender masks it). Update Phase 2 + Phase 4 wording to:

The params.h264.matrix_set = false reset at src/picture.c:287 writes byte 240 of the union, which lands inside mpeg2.iqmatrix.chroma_intra_quantiser_matrix (specifically byte 20). For iter1's ffmpeg-vaapi-driven path, RenderPicture's codec_store_buffer:140-146 overwrites this corrupted byte by copying the full VAIQMatrixBufferMPEG2 payload before mpeg2_set_controls reads it. Latent bug for clients that reuse a surface without re-sending the IQMatrix; logged as a follow-up cleanup task post-iter1 (separate iteration scope, e.g. iter2 or after; involves making the BeginPicture reset profile-aware or replacing it with memset(&params, 0, sizeof params)).

Follow-up task created: post-iter1 cleanup — make params reset profile-aware in BeginPicture to address the latent surface-reuse corruption. Goes on the iter2+ backlog when locked.

S2 — vbv_buffer_size source: reviewer was working from a wrong assumption

Verdict: reviewer's concern based on misreading. Plan is correct as-is, no amendment needed. But documenting the reasoning so the next reader doesn't repeat the trip.

Reviewer's claim: slot->size = SOURCE_SIZE_MAX = 1 MB; Baseline C's vbv_buffer_size = 0x151800 = 1.31 MB; therefore the plan will produce 1 MB and Phase 7 byte-compare will see a mismatch.

Actual chain, traced through the source:

  • src/request_pool.c:48-72: request_pool_init calls v4l2_create_buffers for the OUTPUT pool, then for each slot calls v4l2_query_buffer(video_fd, output_type, index, &length, &offset, 1) and stores pool->slots[i].size = length (line 71).
  • length is the V4L2-reported buffer length from VIDIOC_QUERYBUF, which is the negotiated sizeimage from VIDIOC_S_FMT.
  • Phase 3 Baseline C strace shows S_FMT(OUTPUT_MPLANE) for the MPEG-2 fixture returning sizeimage=1382400 = 0x151800.
  • Therefore slot->size = 1382400, so surface_object->source_size = 1382400, so the plan's sequence.vbv_buffer_size = surface_object->source_size = 1382400 = 0x151800.
  • This matches Baseline C's verbatim payload exactly. No discrepancy.

The reviewer assumed slot->size = SOURCE_SIZE_MAX (which is a constant defined elsewhere in the codebase, used for buffer allocation upper bounds, not as the actual slot size). That assumption was wrong; in reality slot->size is dynamically set from the kernel's negotiated sizeimage.

Action: no change to Phase 4 plan. Add a 1-line note in Phase 6's Commit B message explaining the dynamic source so future readers don't repeat the misreading.

S3 — Default-matrix transcription must be byte-verified against Baseline C

Verdict: accepted. Reviewer's concrete recommendation is sound: "cross-check all 64 against Baseline C's QUANTISATION payload before transcription, not after."

Phase 6 protocol amendment: when transcribing the 64-entry default_intra array in src/mpeg2.c, the value list must come from Baseline C's QUANTISATION verbatim payload (which empirically shows the BBB fixture using spec defaults). After transcription, run diff <(printf '%d\n' ...64 values...) <(extract first 64 bytes from Baseline C QUANTISATION as decimal) — must produce empty diff. If the diff is non-empty, the transcription is wrong.

Same protocol for the other 3 matrices: non_intra must be all 16's; chroma_intra must equal intra (per VAAPI/MPEG-2 4:2:0 spec); chroma_non_intra must be all 16's. Verify against Baseline C bytes 64..127 (non_intra), 128..191 (chroma_intra), 192..255 (chroma_non_intra).

The reviewer's claimed last 4 values {83, 97, 112, 121} are not authoritative (they didn't have the spec table in hand); Phase 6 transcription will derive from Baseline C bytes directly.

Action: amend Phase 6 protocol — bytes-from-Baseline-C transcription, not spec-from-memory.

Q4 — Make ffmpeg+hwdownload primary for criterion 4, not the contingent fallback

Verdict: accepted. Reviewer's reasoning is valid: making the ffmpeg path primary removes the speculation step and the human judgment call about whether to activate the fallback. Phase 7 harness should anchor on what we know works.

Plan amendment: Phase 7 criterion 4 incantation changes from:

# OLD (Phase 4 plan): mpv --hwdec=vaapi --vo=image first, then ffmpeg fallback
mpv --hwdec=vaapi --vo=image ... bbb_720p10s_mpeg2.ts

to:

# NEW (Phase 5 amendment): ffmpeg-vaapi + hwdownload primary
ffmpeg -hide_banner -loglevel error \
  -hwaccel vaapi -hwaccel_output_format nv12 \
  -i ~/fourier-test/bbb_720p10s_mpeg2.ts \
  -ss 2 -frames:v 2 -vf hwdownload,format=nv12 \
  -f rawvideo -y /tmp/iter1_phase7/hw.nv12

ffmpeg -hide_banner -loglevel error \
  -i ~/fourier-test/bbb_720p10s_mpeg2.ts \
  -ss 2 -frames:v 2 -pix_fmt nv12 \
  -f rawvideo -y /tmp/iter1_phase7/sw.nv12

sha256sum /tmp/iter1_phase7/hw.nv12 /tmp/iter1_phase7/sw.nv12

Pass criterion: hw.nv12 and sw.nv12 hash-equal AND non-zero/non-sentinel content (run a quick non-zero count). The non-zero check is critical — recall T4 found that ffmpeg-vaapi -hwaccel_output_format nv12 returns mostly zeros via the cached-mmap path on RK3399 (the iter1-patch-0011 cache-stale bug class). For MPEG-2, hopefully the same path is OK because the read happens after the kernel's request completes, and hwdownload may use a different readback than direct nv12 export. Phase 7 must explicitly check both hashes match AND content is plausible — if the hashes match but bytes are mostly zero, the cache-stale bug is fooling us into a false PASS.

Backup criterion-4 path (if ffmpeg+hwdownload exposes the cache-stale bug): use mpv --hwdec=vaapi --vo=image directly. Phase 3 Baseline D proved this path works for H.264; if mpv-vaapi-MPEG-2 reaches the libva path, it'll work for MPEG-2 too. Empirical determination during Phase 7 — run both incantations, take whichever is cache-coherency-safe.

Q5 — Reference timestamp behavior is a correction; document explicitly

Verdict: accepted. Reviewer is right that the plan called this "no semantic change" when in fact the plan FIXES a bug in the current mpeg2.c:104-118 code (self-reference for missing forward/backward refs).

Plan amendment to Clause 3:

Behavioral change vs. current code: when picture->forward_reference_picture == VA_INVALID_ID (or backward_reference_picture == VA_INVALID_ID for B-frames), the new code sets the corresponding forward_ref_ts / backward_ref_ts to 0. The old code at src/mpeg2.c:106-107, 113-115 self-referenced the current surface's timestamp (forward_reference_surface = surface_object), which was wrong: the kernel uses forward_ref_ts == 0 as the sentinel for "no forward reference" (Phase 3 Baseline C frame 1 confirms — I-frame has forward_ref_ts = backward_ref_ts = 0). FFmpeg's libavcodec/v4l2_request_mpeg2.c:98-108 uses the same 0-as-sentinel convention. Iter1 corrects this to match the kernel + FFmpeg contract.

This was a latent bug in the old code that nobody hit because nobody tested MPEG-2 end-to-end on this fork. Net for iter1: corrected behavior; no functional regression vs. "what the old code claimed to do."

Nit 6 — hevc-ctrls.h left in place; comment in the diff

Verdict: accepted. Trivial.

Plan amendment: Phase 6 Commit B (which removes #include <mpeg2-ctrls.h> from src/config.c:37 and src/mpeg2.c:38) leaves #include <hevc-ctrls.h> at src/config.c:38 in place. Add an inline comment:

#include <hevc-ctrls.h>  /* iter1: deferred — drop with HEVC iteration */

Or, equivalently, drop <hevc-ctrls.h> simultaneously since it's also a staging-era local header (the same architectural smell). Decide in Phase 6 based on whether RequestQueryConfigProfiles still needs V4L2_PIX_FMT_HEVC_SLICE from somewhere: per Phase 2 source-read, RequestQueryConfigProfiles references V4L2_PIX_FMT_HEVC_SLICE (line 146, 149) — which is a kernel UAPI macro from <linux/videodev2.h>, not from <hevc-ctrls.h>. So the local hevc-ctrls.h provides nothing that <linux/videodev2.h> doesn't already provide.

Recommendation: drop both #include <mpeg2-ctrls.h> and #include <hevc-ctrls.h> in Commit B, and delete both include/mpeg2-ctrls.h AND include/hevc-ctrls.h in Commit C. Verify the build still passes; if HEVC iteration later needs anything from those headers, kernel UAPI provides it. (This expands iter1's deletion scope by one file but reduces the architectural smell; the tradeoff is small and clean.)

Alternative: keep <hevc-ctrls.h> deletion as a separate cleanup task. iter1 just drops the <mpeg2-ctrls.h> include from MPEG-2 callsites and deletes include/mpeg2-ctrls.h. The HEVC header stays untouched. Lower risk; smaller diff.

Phase 6 implementor's choice — I'll default to the lower-risk path (drop only mpeg2-ctrls.h for iter1) unless Phase 6 surfaces a reason to bundle.

Phase 4 → Phase 6 plan amendments (consolidated)

After Phase 5, the iter1 plan stands as authored in phase4_iter1_plan.md, with the following amendments:

  1. Clause 3 — note the timestamp behavior change explicitly (Q5 accepted).
  2. Clause 4 — Phase 6 transcription of default_intra[64] etc. derives from Baseline C bytes verbatim, with a diff-based assertion before the commit lands (S3 accepted).
  3. Phase 7 criterion 4 — anchor on ffmpeg -hwaccel vaapi ... -vf hwdownload,format=nv12 ... | sha256sum as primary, with non-zero content check (Q4 accepted). mpv-vaapi-vo=image becomes a backup path.
  4. Files 6+ — keep <hevc-ctrls.h> deletion out of iter1 by default (Nit 6 lower-risk path).
  5. Phase 2 + Phase 4 wording — fix the union-aliasing reasoning to cite the actual safety chain (RenderPicture overwrites the corrupted byte via codec_store_buffer copy, not the hand-wavy "before mpeg2_set_controls reads it" claim) (C1 partial accept).
  6. Post-iter1 follow-up backlog — log the latent surface-reuse bug for an iter2+ cleanup iteration (C1 follow-up).

Plan re-locks with these amendments. Phase 6 proceeds.

Phase 5 close

Per feedback_dev_process.md Phase 5: "Markus reviews and redacts, then initiates the handover to a fresh model instance." The handover-to-reviewer step happened (sonnet-architect Plan agent); Markus is the final arbiter on the amendments and may redact or extend before Phase 6.

If Markus accepts the amendments above, Phase 6 (implementation) can start. If any amendment is rejected, Phase 5 → Phase 4 loopback to re-lock plan first.