oxideav-h264 0.1.9

Pure-Rust H.264 / AVC (ITU-T H.264 | ISO/IEC 14496-10) codec for oxideav — under spec-driven rewrite, no decode/encode functionality yet
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
# H.264 encoder — status

This module hosts the **clean-room H.264 encoder** rebuilt from scratch
against ITU-T Rec. H.264 (08/2024). No external implementation
external implementation source was consulted while writing it.
Same strict policy as the decoder.

## Round 1 — landed

Minimal Baseline I-only IDR encoder, end-to-end:

- **SPS / PPS emit** (Baseline, profile_idc=66, 4:2:0, no FMO, no VUI,
  CAVLC, single slice per picture).
- **Slice header emit** for IDR I-slice (POC type 0,
  `disable_deblocking_filter_idc=1`).
- **Macroblock**: every MB is `I_16x16` with `pred_mode=2` (DC luma)
  and `intra_chroma_pred_mode=0` (DC chroma). `cbp_luma=0`,
  `cbp_chroma=0` — only the luma DC block is transmitted (every
  Intra_16x16 MB carries it unconditionally per §7.3.5.3.1).
- **Forward 4x4 integer transform** (`Cf * X * Cf^T`) + Hadamard for
  luma DC + matched quantizer (encoder MF table, intra rounding
  offset = `2^qBits / 3`).
- **CAVLC encode** for the luma DC residual block. Re-uses the
  decoder's transcribed §9.2 tables (every table is a slice of
  `(bits, length, value)` rows, looked up by `value` on the encoder
  side). Round-trip-tested against `crate::cavlc::decode_*` for
  `coeff_token` / `level` / `total_zeros` / `run_before`.
- **Reconstruction loop** mirrors the decoder bit-exactly: same DC
  prediction (§8.3.3.3 / §8.3.4.1), same inverse transform, same
  `Clip1Y`/`Clip1C`. The encoder publishes the recon planes alongside
  the bitstream so tests can assert that
  `decode(encode(picture)) == encoder_recon`.

### Validation

- `cargo test -p oxideav-h264 --lib` — 749 passed.
- `cargo test -p oxideav-h264` — 851+ passed (29 new encoder unit
  tests + 2 integration tests).
- **Self-roundtrip**: encode a 64x64 testsrc → decode with our own
  decoder → exact match against encoder's local recon.
- **ffmpeg interop**: encode → write `.h264` → run `ffmpeg -i .h264 -f
  rawvideo -pix_fmt yuv420p .yuv` → exact match against encoder's
  local recon. Verified with ffmpeg 8.1 (`--enable-libx264`).
- **Self-roundtrip PSNR vs. original**: ~38.85 dB on the 64x64
  diagonal-gradient testsrc at QP 26. Every Intra_16x16 MB transmits
  one DC value per 4x4 sub-block, no AC.

### Status caveats

- I-only / IDR-only — no P / B slices.
- Single mb_type (I_16x16 with DC luma + DC chroma).
- No AC residual — `cbp_luma == 0` always.
- No chroma residual — `cbp_chroma == 0` always.
- No in-loop deblocking — `disable_deblocking_filter_idc == 1`.
- No CABAC — CAVLC only.
- No rate control — fixed QP from `EncoderConfig::qp`.

## Round 2 — landed

Lift encoder coverage from DC-only luma to multi-mode I_16x16 + chroma
residual transmit.

- **All four Intra_16x16 modes** (V / H / DC / Plane) per
  [`encoder::intra_pred`], with a per-MB SAD-driven mode decision.
  Modes whose required neighbours are missing (top edge → V/Plane,
  left edge → H/Plane, top-left corner → Plane) are skipped during
  the search; the encoder always falls back to DC, never signals an
  unavailable mode.
- **All four Intra_Chroma modes** (DC / H / V / Plane) selected per
  MB by joint-plane SAD (Cb + Cr).
- **Chroma residual transmit**: `cbp_chroma == 1` (DC only) when the
  chroma DC has any non-zero levels; promoted to `cbp_chroma == 2`
  (DC + AC) when any chroma 4x4 block has non-zero AC. Encoder
  computes the residual against the chosen predictor, runs forward
  4x4 → 2x2 chroma-DC Hadamard → quantize, and reconstructs through
  the same inverse path the decoder uses.
- **`zigzag_scan_4x4_ac`** added to the encoder transform module to
  pack 4x4 AC coefficients into the AC-only scan layout the decoder
  reads via `inverse_scan_4x4_zigzag_ac`.
- **`quantize_4x4_ac`**: AC-only quantizer (clears DC slot 0).

### Validation

- `cargo test -p oxideav-h264 --lib` — 758 passed.
- `cargo test -p oxideav-h264` — all integration tests pass:
  - `encode_decode_roundtrip_matches_local_recon`
  - `encode_decode_roundtrip_chroma_residual` (new)
  - `encode_mixed_picture_picks_non_dc_modes` (new — 128x128 mixed
    bars + gradient)
  - `ffmpeg_decode_matches_encoder_recon`
  - `ffmpeg_decode_chroma_residual_matches` (new)
- **PSNR (QP 26)**:
  - 64x64 diagonal-gradient testsrc, luma: **38.85 → 42.25 dB**
    (round-1 → round-2). Stream **148 → 88 bytes** (smaller because
    V/H modes need fewer non-zero DC coefficients than DC mode does).
  - 64x64 mixed chroma-gradient: Y 42.25 dB, Cb/Cr 47.05 dB.
  - 128x128 vertical+horizontal bars + gradient: **50.11 dB Y**.
- **ffmpeg interop**: bit-exact match against ffmpeg 8.1's common H.264 decoders
  H.264 decoder on every test fixture.

### Status caveats (unchanged from round 1 unless noted)

- Still I-only / IDR-only.
- `cbp_luma` still 0 — luma AC residual not yet emitted (we send only
  the Intra_16x16 DC block per MB). This caps luma quality at
  whatever the per-MB DC + intra-prediction can resolve; with V/H/
  Plane modes that's already excellent on smooth + structured
  content, but detail at QP 26 will still soften.
- No in-loop deblocking — `disable_deblocking_filter_idc == 1`. This
  is mostly invisible on the test pictures because we reconstruct
  every MB from per-MB DCs, so MB-boundary discontinuities are tiny.
- No CABAC — CAVLC only.
- No rate control — fixed QP from `EncoderConfig::qp`.
- nC for chroma AC blocks is hard-coded to 0 (sub-optimal bitrate but
  correct).

## Round 3 — landed

Wire **luma AC residual transmit** (`cbp_luma == 15`) on the
Intra_16x16 path — the encoder can now resolve high-frequency detail
in luma instead of capping the per-MB recon at the DC term.

- **`cbp_luma` decision**: per-MB the encoder runs `quantize_4x4_ac`
  on every 4x4 sub-block; if any AC level is non-zero, `cbp_luma` is
  promoted from 0 to 15 (per §7.4.5.1 Intra_16x16 only allows those
  two values). The mb_type emitted in the bitstream then jumps to the
  matching Table 7-11 entry (groups 3..=5 instead of 0..=2).
- **AC block emit**: 16 `Intra16x16ACLevel` blocks per MB in §6.4.3
  raster-Z order, each a 15-coefficient stream
  (`startIdx=1, endIdx=15, maxNumCoeff=15`). The `nC` for each block
  is derived per §9.2.1.1: the encoder maintains its own
  `CavlcNcGrid`, sets `is_available = true` on the current MB before
  enumerating its blocks, and progressively writes
  `luma_total_coeff[blk]` so subsequent internal-to-MB neighbour
  lookups see the right TotalCoeff. External neighbours pick up the
  fully-populated values from prior MBs.
- **Reconstruction**: per-block inverse 4x4 DC-preserved transform now
  consumes the quantized AC levels alongside the inverse-Hadamard DC,
  so the encoder's recon matches what the decoder will produce
  bit-for-bit even when AC is non-zero.

### Validation

- `cargo test -p oxideav-h264 --lib` — 758 passed.
- `cargo test -p oxideav-h264` — all integration tests pass:
  - all 5 round-1/round-2 tests still pass with identical PSNR (the
    smooth gradients used there genuinely quantize all AC to zero, so
    the recon path is unchanged for them).
  - `round3_ac_residual_lifts_psnr_on_noisy_picture` (new): a 64x64
    high-frequency hash pattern (DC-only encoder gives ~14 dB PSNR
    Y) reaches **46.36 dB Y at QP=18** and **37.63 dB Y at QP=26**.
  - `round3_ffmpeg_decode_noisy_picture_matches` (new): bit-exact
    against ffmpeg 8.1 (black-box validator) H.264 decoder on the same noisy
    picture.

### Status caveats (unchanged from round 2 unless noted)

- Still I-only / IDR-only.
- Still single mb_type — `I_16x16` only. `I_NxN` (Intra_4x4) is the
  next big quality lift on detailed content.
- No in-loop deblocking — `disable_deblocking_filter_idc == 1`.
- No CABAC — CAVLC only.
- No rate control — fixed QP from `EncoderConfig::qp`.
- Chroma AC `nC` is still hard-coded to 0 (sub-optimal bitrate but
  correct).

## Round 4 — landed

Wire **I_NxN (Intra_4x4) with all 9 modes** — per-4x4-block intra
prediction so the encoder can track local texture orientation that
one I_16x16 mode per macroblock can't capture.

- **`encoder::intra4x4`** module: full §8.3.1.2 implementation of all
  9 prediction modes (Vertical / Horizontal / DC / Diagonal_Down_Left
  / Diagonal_Down_Right / Vertical_Right / Horizontal_Down /
  Vertical_Left / Horizontal_Up). Mirrors the decoder's [`crate::
  intra_pred`] line-by-line but operates on raw `u8` reconstruction
  buffers with simple `(mb_x, mb_y, bx, by)` coordinates. Includes
  §8.3.1.2 "top-right substitution" rule for blocks 3, 5, 7, 11, 13,
  15 and right-edge MBs.
- **Per-block mode decision**: SAD against source over all 9 modes
  per 4x4 sub-block (skipping modes whose required neighbours are
  unavailable). Best mode written to a small encoder-side
  `IntraGrid` so subsequent blocks (in-MB and across-MB) can derive
  `predIntra4x4PredMode` per §8.3.1.1 step 4.
- **mb_type decision**: per MB the encoder scores I_16x16 SAD vs
  I_NxN SAD (sum of best per-block SADs) and picks the lower.
  Picture content with multiple distinct edge orientations per MB
  trips into I_NxN; smooth content stays on I_16x16.
- **Macroblock-layer I_NxN emit** ([`encoder::macroblock::
  write_i_nxn_mb`]):
    1. `mb_type = 0` (raw I_NxN value in I-slice Table 7-11)
    2. 16 × `prev_intra4x4_pred_mode_flag` (1 bit) plus optional
       `rem_intra4x4_pred_mode` (3 bits) per §7.3.5.1.
    3. `intra_chroma_pred_mode` ue(v).
    4. `coded_block_pattern` me(v) — Table 9-4(a) intra column
       inverse mapping (`INTRA_CBP_TO_CODENUM_420_422`).
    5. `mb_qp_delta` se(v) when any cbp non-zero.
    6. 16 4x4 luma blocks (`maxNumCoeff = 16`, full DC + AC) gated
       by 8x8-quadrant cbp bits, in §6.4.3 raster-Z order.
    7. Chroma DC + AC identical to the I_16x16 path.
- **Per-block local recon**: forward 4x4 + quantize + inverse 4x4 +
  add to predictor, written back to `recon_y` so subsequent blocks
  see the correct neighbour samples. For 8x8 quadrants whose cbp bit
  is 0 the recon is rolled back to `pred + 0` (matching what the
  decoder will see — no transmitted residual).
- **CAVLC nC** for the new luma 4x4 blocks: re-uses the existing
  `CavlcNcGrid` + `derive_nc_luma(LumaNcKind::Ac)` chain, with
  `own_luma_totals` reset per MB and progressively updated per block.
  Blocks in zero-cbp quadrants contribute TotalCoeff = 0.

### Validation

- `cargo test -p oxideav-h264 --lib` — **764 passed** (+6 unit tests
  for the new modes and the CBP inverse table).
- All round-1/2/3 integration tests still pass with identical PSNR
  on smooth/mixed/noisy content (as expected — those pictures are
  already optimal under I_16x16; mb_type decision keeps I_16x16).
- New integration tests (round-4):
  - `round4_i_nxn_lifts_psnr_on_oriented_edges` — synthetic 64x64
    picture with a different best 4x4 mode in each sub-block
    (vertical / horizontal / diagonal stripes per 4x4 within each
    MB). I_NxN gets picked, **PSNR Y = 41.25 dB at QP=26** with
    1066-byte stream (vs ~22 dB for I_16x16 alone on this content).
  - `round4_ffmpeg_decode_oriented_edges_matches` — bit-exact
    against ffmpeg 8.1 (black-box validator) on the same picture.

### Status caveats (unchanged from round 3 unless noted)

- Still I-only / IDR-only.
- mb_type now switches between `I_16x16` and `I_NxN` per MB (no
  I_8x8 / I_PCM yet).
- Mode-decision metric is SAD on the predictor (no rate term, no
  RDO). Round 5+ can add Lagrangian RDO for proper rate-distortion
  trade-off.
- No in-loop deblocking — `disable_deblocking_filter_idc == 1`.
- No CABAC — CAVLC only.
- No rate control — fixed QP from `EncoderConfig::qp`.
- Chroma AC `nC` is still hard-coded to 0 (sub-optimal bitrate but
  correct).

## Round 14 — landed

Wire **in-loop §8.7 deblocking** on the local recon. The encoder now
runs the same picture-level deblock pass that the decoder runs, so the
encoder's `EncodedIdr.recon_*` matches what every downstream decoder
will produce (including future inter slices that use it as a reference).

- **`encoder::deblock`** module: thin adapter that wraps the encoder's
  `Vec<u8>` recon planes in a [`crate::picture::Picture`] i32
  samples, builds a [`crate::mb_grid::MbGrid`] from per-MB
  `MbDeblockInfo` records (intra flag, QP_Y, per-4x4 nonzero mask),
  and calls [`crate::reconstruct::deblock_picture_full`] verbatim.
  Filtered samples are clipped back into the encoder's `u8` recon
  buffers.
- **Per-MB nonzero tracking**: each `encode_mb_*` function now returns
  a `MbDeblockInfo` carrying the per-4x4 luma AC nonzero bitmap (Z-scan
  Figure 6-10) and per-4x4 chroma AC nonzero bitmap. The bitmap drives
  §8.7.2.1's third bullet (bS=2 when either side has non-zero
  coefficients). For Intra_16x16 with `cbp_luma == 15` we set the bit
  iff the AC scan-order array has any non-zero entry; with `cbp_luma
  == 0` the bitmap is 0. For I_NxN we use the per-block AC nonzero
  flags computed for cbp_luma 8x8 quadrant decision.
- **Slice header**: `disable_deblocking_filter_idc = 0` (filter
  active), `slice_alpha_c0_offset_div2 = slice_beta_offset_div2 = 0`.
  The PPS already had `deblocking_filter_control_present_flag = 1`.
- **`IdrSliceHeaderConfig`** gains `disable_deblocking_filter_idc`,
  `slice_alpha_c0_offset_div2`, `slice_beta_offset_div2` fields. When
  IDC != 1 the writer emits the alpha/beta offsets per §7.3.3.

### Validation

- `cargo test -p oxideav-h264 --lib` — **768 passed** (+4 unit tests
  for the new module).
- All 9 prior integration tests still pass.
- New round-14 integration tests:
  - `round14_deblock_self_roundtrip_matches` — DC-staircase 64x64
    picture (per-MB DC offsets that produce visible blocking without
    the filter). Self-roundtrip matches encoder recon bit-exactly,
    confirming encoder-side deblock == decoder-side deblock.
  - `round14_ffmpeg_decode_deblocked_matches` — bit-exact against
    ffmpeg 8.1 (black-box validator) on the same picture.
- **PSNR uplift** (QP 26):
  - 64x64 diagonal-gradient testsrc, luma: **42.58 → 48.05 dB**
    (+5.47 dB).
  - 64x64 chroma-gradient: Y 42.58 → 48.05; Cb 47.05 → 50.03;
    Cr 47.05 → 50.00.
  - 128x128 mixed bars + gradient: **50.11 → 51.72 dB** (+1.61 dB).
  - The other fixtures (round-3 noisy QP=18; round-4 oriented-edges)
    are dominated by AC residual rather than block-boundary
    artefacts, so PSNR is essentially unchanged there.
- **ffmpeg interop**: bit-exact match against ffmpeg 8.1 common H.264 decoders
  on every fixture.

### Status caveats (unchanged from round 4 unless noted)

- Still I-only / IDR-only.
- mb_type still switches between I_16x16 and I_NxN.
- **In-loop deblocking now active** (`disable_deblocking_filter_idc
  == 0`, alpha/beta offsets 0).
- No CABAC — CAVLC only.
- No rate control — fixed QP from `EncoderConfig::qp`.
- Chroma AC `nC` is still hard-coded to 0 (sub-optimal bitrate but
  correct).

## Round 15 — landed

Wire **Lagrangian rate-distortion mode decision** at every level: the
per-block I_NxN 9-mode pick, the per-mode I_16x16 pick, and the MB-level
I_16x16 vs I_NxN dispatch all now minimise `J = D + λ · R` instead of
SAD-on-predictor.

- **`encoder::rdo`** module: closed-form `λ = 0.85 · 2^((QP-12)/3)`
  per common encoder convention, plus `ssd_4x4` / `ssd_16x16` distortion helpers
  and a `cost_combined(D, R, λ)` integer accumulator (λ scaled by 1024
  to keep arithmetic in `u64`).
- **`BitWriter::bits_emitted`**: returns the running bit count so trial
  CAVLC encodes can measure rate without committing to the live writer.
  Added a `Clone` impl for the snapshot/restore loop.
- **MB-level RDO dispatch**: `Encoder::encode_mb` now snapshots the
  recon planes (only the 16x16 + 8x8 + 8x8 MB window — 384 bytes), the
  `BitWriter`, the `CavlcNcGrid` slot, and the `IntraGrid` slot before
  trialing. Both paths run in turn into the live state; the loser is
  rolled back via the snapshot. Lower-J path wins; on tie, prefer
  I_16x16 (smaller MB syntax).
- **I_16x16 mode RDO**: `trial_intra16x16_cost` runs the full §8.5.10
  forward-quant-inverse pipeline per candidate mode and computes
  `D = SSD(source, recon_clamped)` plus `R = trial CAVLC bits`. The
  `mb_type` bit cost is approximated as a constant 7 bits (Table 7-11
  width range is 1..9 across the 24 entries; the constant is identical
  for every I_16x16 candidate so it cancels out in the within-path
  comparison and matches the I_NxN MB-overhead seed for the cross-path
  comparison).
- **I_NxN per-block RDO**: each of the 16 4x4 blocks trials all 9
  available modes; for each, we predict, transform, quantise, inverse,
  add to predictor, compute SSD, run trial CAVLC for rate. Mode rate
  also includes the per-block `prev_intra4x4_pred_mode_flag` (1 bit)
  plus optional `rem_intra4x4_pred_mode` (3 bits when mode != predicted).
  The MB-level cost seeds with a `+25` bit overhead (`mb_type` + chroma
  pred + cbp + qp_delta + an empirical "post-deblock D under-measurement"
  bias) so the cross-path comparison is calibrated against the I_16x16
  seed.

### Encoder bug fix (round-15 collateral)

Surfaced and fixed a pre-existing top-right substitution bug for
`Intra_4x4` block 5: the encoder's `gather_top_row` was substituting
`p[3, -1]` for `p[4..=7, -1]` even when those samples were available
from the already-encoded above-right MB. The decoder reads the real
samples per §8.3.1.2, causing recon divergence whenever the round-15
RDO finally picked `Intra_4x4_VerticalLeft` for block 5. Fixed in
`encoder::intra4x4::gather_top_row` by removing 5 from the substitution
set (matching the decoder's `gather_samples_4x4`).

### Validation

- `cargo test -p oxideav-h264 --lib` — **774 passed** (+6 unit tests
  for the new `rdo` module + `bits_emitted` test).
- All 11 prior integration tests still pass with bit-exact ffmpeg
  interop on every fixture.
- **PSNR** (QP 26, RDO mixed dispatch):
  - 64x64 diagonal-gradient testsrc: 48.05 → **47.58 dB** (-0.47 dB,
    stream **100 → 88 bytes** for −12% rate).
  - chroma roundtrip: Y 47.58, Cb 50.03, Cr 50.00 (chroma path
    unchanged; luma stream 154 vs 232 bytes).
  - 128x128 mixed bars + gradient: 51.72 → **51.51 dB** (-0.21 dB,
    stream 196 vs 201 bytes).
  - 64x64 oriented-edges: 41.25 → **41.16 dB** (-0.09 dB, stream
    1056 vs 1066 bytes).
  - 64x64 noisy QP=18: 46.36 → **46.47 dB** (+0.11 dB, stream
    3056 vs 3089 bytes).
  - 64x64 noisy QP=26: 37.63 → **37.69 dB** (+0.06 dB, stream
    2101 vs 2107 bytes).
  - 64x64 dc-staircase: 50.00 → **50.00 dB** (unchanged).
- The smooth fixtures see a small PSNR regression but a comparable rate
  saving — RDO is choosing a different R-D operating point. The noisy
  fixtures see a small PSNR gain plus rate savings — pure improvement.

### Status caveats (unchanged from round 14 unless noted)

- Still I-only / IDR-only.
- mb_type still switches between I_16x16 and I_NxN; selection now via
  Lagrangian RDO (round 15) instead of SAD (rounds 4..14).
- In-loop deblocking active.
- No CABAC — CAVLC only.
- No rate control — fixed QP from `EncoderConfig::qp`.
- Chroma AC `nC` still hard-coded to 0 (sub-optimal bitrate but correct).
- λ is calibrated against the round-15 fixture set; future rounds may
  refine it further (especially for inter-MB λ scaling once P/B slices
  land).

## Round 16 — landed

Wire **P-slice support** (inter prediction) end-to-end. Single L0 ref,
integer-pel motion estimation, P_Skip + P_L0_16x16.

- **Slice header (`encoder::slice::write_p_slice_header`)**: emits the
  non-IDR P-slice header per §7.3.3 — `slice_type=5` (P, all-same-type),
  `num_ref_idx_active_override_flag=0` (uses PPS default of 1 ref),
  `ref_pic_list_modification` empty, non-IDR `dec_ref_pic_marking` with
  `adaptive_ref_pic_marking_mode_flag=0` (sliding-window).
- **Motion estimation (`encoder::me`)**: integer-pel **full search**
  within ±16 px around (0, 0) using SAD on the source luma 16×16 block
  vs the reference plane (with §8.4.2.2.1 edge replication). Tie-break
  prefers smaller `|mv|` to minimise mvd cost when SADs are equal.
- **MV prediction grid (`MvGrid` + `mvp_for_16x16` + `p_skip_mv`)**:
  encoder tracks the per-MB chosen MV/refIdx so that subsequent MBs
  derive `mvpLX` per §8.4.1.3 the same way the decoder will. P_Skip
  derivation per §8.4.1.2 (zero-MV substitution rules + median otherwise)
  uses the existing `crate::mv_deriv` helpers — encoder and decoder
  share the spec primitives.
- **Inter prediction** wraps `crate::inter_pred::interpolate_luma` and
  `interpolate_chroma`. Integer-pel luma MVs collapse to the
  edge-replicated copy path; chroma MVs derived from luma per §8.4.1.4
  (4:2:0) may end up half-pel even for integer-pel luma, in which case
  the bilinear chroma interpolator runs.
- **MB writer (`encoder::macroblock::write_p_l0_16x16_mb`)**: emits
  `mb_type=P_L0_16x16` (raw 0), `mvd_l0_x se(v)`, `mvd_l0_y se(v)`,
  inter `coded_block_pattern me(v)` (Table 9-4(a) inter column),
  `mb_qp_delta se(v)` (when CBP > 0), 16 luma 4×4 residual blocks
  gated by 8×8 quadrant `cbp_luma`, then chroma DC + AC. `ref_idx_l0`
  is **absent** when `num_ref_idx_l0_active_minus1 == 0` per §7.3.5.1.
- **P_Skip path**: when the chosen MV equals the §8.4.1.2-derived skip
  MV AND `cbp_luma == cbp_chroma == 0`, the MB is omitted from the
  bitstream and contributes to a CAVLC `mb_skip_run` ue(v) emitted
  before the next coded MB (or at slice end).
- **Encoder API** (`Encoder::encode_p`): takes a `YuvFrame` + an
  `EncodedFrameRef` (the previous frame's recon planes) and emits the
  P-slice access unit. `EncodedFrameRef::from(&EncodedIdr)` and
  `From<&EncodedP>` make chaining frame-by-frame trivial.
- **Local recon + deblocking**: the encoder's recon planes are
  populated and run through the same §8.7 deblocker as I-frames.
  `MbDeblockInfo` gained `mv_l0[16]`, `ref_idx_l0[4]`, `ref_poc_l0[4]`
  so that the deblock walker's `different_ref_or_mv_luma` (which
  drives bS=2 vs bS=1 on inter/inter edges) sees the same MV / ref
  identity that the decoder will compute. Without this the encoder's
  recon would diverge from the decoder's for any inter MB.

### Validation

- `cargo test -p oxideav-h264 --lib` — **780 passed** (+6: 4 new ME
  tests, 1 P-slice header round-trip, 1 inter-CBP table round-trip).
- All prior integration tests still pass (the 11 round-1..14 I-only
  fixtures unchanged).
- New round-16 integration tests:
  - `round16_p_slice_self_roundtrip_matches_local_recon`: encode
    IDR + P (where P = IDR shifted right 4 luma px), decode the
    combined Annex B with our own decoder → bit-exact match against
    encoder recon. Decoded P luma vs original source = **49.79 dB
    PSNR** (clean integer-shift content + integer-pel ME finds the
    motion exactly).
  - `round16_p_slice_static_picture_uses_skips`: when P-frame source ==
    IDR source, the encoder produces a 53-byte P slice (vs 88-byte
    IDR), bit-exact through self-roundtrip. The drop in size is
    smaller than ideal because IDR encoding noise prevents some MBs
    from quantising the residual to zero.
  - `round16_ffmpeg_decode_p_sequence_matches`: ffmpeg/common H.264 decoders
    decodes the same combined IDR+P stream bit-exactly against the
    encoder's local recon (≤1 LSB tolerance).

### Status caveats (unchanged from round 15 unless noted)

- IDR + P-slice (P only). No B-slices.
- mb_type now extends to `P_L0_16x16` and `P_Skip` for P-slices, in
  addition to `I_16x16` / `I_NxN` for the IDR.
- **Single MV per MB** (16×16 partition only). No 16×8 / 8×16 / 8×8
  / 4MV.
- **Integer-pel ME only**. Half-pel and quarter-pel refinement are
  later rounds.
- **No intra fallback in P-slices** — every P-MB stays inter (P_Skip
  or P_L0_16x16). Future round can add I_16x16 / I_NxN trial in P
  via the existing RDO framework.
- Single reference: `num_ref_idx_l0_default_active_minus1 = 0` → one
  L0 ref. The encoder uses the previous reconstructed frame.
- In-loop deblocking active across both intra and inter MBs.
- No CABAC — CAVLC only.
- No rate control — fixed QP from `EncoderConfig::qp`.

## Round 17 — landed

Layer **half-pel motion estimation** on top of round-16's integer-pel
P-slice support, using the same §8.4.2.2.1 6-tap luma interpolator the
decoder runs. Encoder and decoder pick the **same** predictor for any
half-pel MV the encoder selects, so bit-equivalence is preserved
end-to-end (verified against ffmpeg).

- **Half-pel refinement (`encoder::me::refine_half_pel_16x16`)**: takes
  the integer-pel winner and probes the 8 surrounding ½-pel offsets
  (`(±2, 0)`, `(0, ±2)`, `(±2, ±2)` in quarter-pel units). Predictor
  comes straight from `inter_pred::interpolate_luma`, which is the
  same routine the decoder calls — the encoder doesn't reimplement the
  6-tap FIR. Tie-break still favours the smaller-magnitude MV to keep
  `mvd_l0_*` se(v) costs low.
- **Combined ME entry (`encoder::me::search_half_pel_16x16`)**: integer
  full search (±16) followed by half-pel refinement. Drop-in
  replacement for `search_integer_16x16`; round-17 `encode_p_mb` now
  calls it.
- **No P-slice header / writer changes** — the bitstream already
  carried fractional MVs via `mvd_l0_x`/`mvd_l0_y se(v)`. Only the
  encoder's MV decision changed.

### Validation

- `cargo test -p oxideav-h264` — **900 passed** (+5 vs round 16: 3
  new ME unit tests + 2 new half-pel integration tests).
- All round-1..16 fixtures still pass. The round-16 4-pixel-shift
  fixture moved 49.79 → **50.28 dB** PSNR_Y under the new ME (no
  regression on integer motion: integer winner stays integer when
  the half-pel neighbours can't beat its SAD).
- New round-17 integration tests
  (`integration_p_slice_half_pel.rs`):
  - `round17_half_pel_p_slice_high_psnr`: encode IDR + P where P is
    the IDR shifted right by **½ luma pixel**, decode through our own
    decoder → bit-exact against encoder recon (max diff 0). PSNR_Y =
    **49.19 dB**, with `cbp_luma == 0` on most MBs because the
    half-pel predictor matches the source bit-for-bit.
  - `round17_half_pel_ffmpeg_interop`: ffmpeg (black-box validator) decodes the
    same half-pel P stream bit-equivalently against the encoder's
    local recon (max diff 0).

### Status caveats (unchanged from round 16 unless noted)

- IDR + P-slice. No B-slices.
- mb_type: `P_L0_16x16` / `P_Skip` for P; `I_16x16` / `I_NxN` for IDR.
- Single MV per MB (16×16 partition only). No 16×8 / 8×16 / 8×8 / 4MV.
- ME: **integer + half-pel** (round-17). Quarter-pel refinement is the
  next step.
- No intra fallback in P-slices.
- Single reference, sliding-window DPB.
- In-loop deblocking active across intra and inter MBs.
- CAVLC only.
- No rate control — fixed QP.

## Round 18 — landed

Layer **quarter-pel motion estimation** on top of round-17's half-pel
chain. The encoder now searches all 16 sub-pel positions in
§8.4.2.2.1 Table 8-12 (integer + 3 half-pel + 12 quarter-pel) and
picks the SAD-best MV. Predictor comes straight from
`inter_pred::interpolate_luma`, the same routine the decoder calls,
so encoder and decoder remain bit-equivalent end-to-end.

- **Quarter-pel refinement (`encoder::me::refine_quarter_pel_16x16`)**:
  takes the half-pel winner and probes the 8 surrounding ¼-pel offsets
  (`(±1, 0)`, `(0, ±1)`, `(±1, ±1)` in quarter-pel units). Reuses the
  `sad_16x16_at_qpel` helper that already drives half-pel; no new
  spec primitive is reimplemented (`interpolate_luma` already supports
  all 16 sub-pel positions per §8.4.2.2.1 eq. 8-243 .. 8-261).
  Tie-break still favours the smaller-magnitude MV to keep
  `mvd_l0_*` se(v) cost low.
- **Combined ME entry (`encoder::me::search_quarter_pel_16x16`)**:
  integer full search (±16) → half-pel refinement → quarter-pel
  refinement. Drop-in replacement for `search_half_pel_16x16`;
  round-18 `encode_p_mb` now calls it.
- **No P-slice header / writer changes** — `mvd_l0_x`/`mvd_l0_y se(v)`
  already carries quarter-pel MVs natively; only the encoder's MV
  decision changed.

### Validation

- `cargo test -p oxideav-h264` — **906 passed** (+6 vs round 17:
  4 new ME unit tests + 2 new quarter-pel integration tests).
- All round-1..17 fixtures still pass. Notable PSNR uplifts under the
  new ME (no regression on integer or half-pel motion: each pass
  refines only when the new candidate beats the previous SAD):
  - round-16 4-pixel-shift fixture: 50.28 → **50.82 dB** PSNR_Y.
  - round-17 ½-pixel-shift fixture: 49.19 → **49.40 dB** PSNR_Y,
    bit-exact ffmpeg interop.
- New round-18 integration tests
  (`integration_p_slice_quarter_pel.rs`):
  - `round18_quarter_pel_p_slice_high_psnr`: encode IDR + P where
    P is the IDR shifted right by **¼ luma pixel**, decode through
    our own decoder → bit-exact against encoder recon (max diff 0).
    PSNR_Y = **53.61 dB**, P-slice stream = 30 bytes (vs 37 bytes
    for the half-pel fixture — most MBs reach `cbp_luma == 0`
    because the quarter-pel predictor matches the source bit-for-
    bit).
  - `round18_quarter_pel_ffmpeg_interop`: ffmpeg (black-box validator) decodes
    the same quarter-pel P stream bit-equivalently against the
    encoder's local recon (max diff 0).

### Status caveats (unchanged from round 17 unless noted)

- IDR + P-slice. No B-slices.
- mb_type: `P_L0_16x16` / `P_Skip` for P; `I_16x16` / `I_NxN` for IDR.
- Single MV per MB (16×16 partition only). No 16×8 / 8×16 / 8×8 / 4MV.
- ME: **integer + half-pel + quarter-pel** (round-18). All 16 sub-pel
  positions per §8.4.2.2.1 are reachable.
- No intra fallback in P-slices.
- Single reference, sliding-window DPB.
- In-loop deblocking active across intra and inter MBs.
- CAVLC only.
- No rate control — fixed QP.

## Round 19 — landed

**4MV (P_8x8 with `sub_mb_type = PL08x8`)** — adds the per-8x8-
partition motion-compensation mode, gated by a content-driven SAD
heuristic so smooth content stays on the round-16 1MV path.

- **Per-8x8 ME** (`encoder::me::search_quarter_pel_8x8`) mirrors the
  round-18 quarter-pel chain but on 8×8 sub-blocks:
  integer-pel ±range full search → ½-pel refinement → ¼-pel
  refinement, all SAD-based against the same `interpolate_luma` the
  decoder uses (so encoder and decoder agree bit-for-bit on the
  predictor for any sub-pel MV).
- **MV grid extended** (`MvGridSlot::{mv_l0_8x8, ref_idx_l0_8x8}`)
  to track per-8x8 MVs / refs. P_L0_16x16 / P_Skip MBs fill all
  four 8x8 entries with the same value, preserving exact round-16
  / 17 / 18 neighbour reads.
- **Per-partition MVpred** (`mvp_for_p_8x8_partition`) walks the
  §8.4.1.3 (A, B, C, D) neighbours of an 8x8 partition, supporting
  within-MB neighbour reads of already-decoded sub-partitions
  (sub-blocks decode in raster order 0→1→2→3) and falling back to
  the §8.4.1.3.2 C→D substitution when the C neighbour is in a
  not-yet-decoded sub-partition of the current MB.
- **Bitstream emit** (`encoder::macroblock::write_p_8x8_all_pl08x8_mb`)
  → `mb_type ue(3)` (P_8x8, Table 7-13) + 4× `sub_mb_type ue(0)`
  (PL08x8, Table 7-17) + 4× `(mvd_l0_x se(v), mvd_l0_y se(v))` +
  inter `coded_block_pattern me(v)` + optional `mb_qp_delta` +
  the same residual layout as P_L0_16x16. Single-reference path
  (`num_ref_idx_l0_active_minus1 == 0`) → ref_idx absent.
- **Mode decision**: 4MV picked iff
  `me_16x16_sad >= 1024 && me_4mv_sad_sum * 10 < me_16x16_sad * 7`
  (≥30% SAD reduction over a non-trivial 1MV residual). The two
  gates protect rounds 16/17/18 fixtures (smooth content → both
  paths converge to the same MV → 1MV wins → no regression).

### Validation

- `cargo test -p oxideav-h264` — **911 passed** (was 906).
- Round 16/17/18 PSNR fixtures unchanged: 50.82 / 49.40 / 53.61 dB.
- New round-19 fixture (`integration_p_slice_4mv.rs`,
  `round19_4mv_p_slice_high_psnr_on_sub_mb_motion`):
  source has each 8x8 sub-block of every 16x16 MB shifted by a
  *different* integer-pel amount. **Local recon PSNR_Y = 40.95 dB**,
  decoder PSNR_Y = 40.95 dB (max enc/dec diff 0).
- ffmpeg interop (`round19_4mv_ffmpeg_interop`):
  ffmpeg (black-box validator) decodes the 4MV P-frame **bit-equivalently** against
  the encoder's local recon (max diff 0).
- 3 new unit tests in `me::tests` covering per-8x8 ME (zero motion +
  per-sub-block motion recovery + `p_8x8_pred_bits`).

## Round 20 — landed

Wire **B-slice support** for the explicit-inter macroblock types:
`B_L0_16x16`, `B_L1_16x16`, `B_Bi_16x16` (Table 7-14 raw mb_type 1, 2,
3). Bumps the SPS profile to **Main (77)** because Baseline forbids
B-slices per §A.2.2. New entry point `Encoder::encode_b(frame,
ref_l0, ref_l1, frame_num, pic_order_cnt_lsb)` emits a single
non-reference (`nal_ref_idc = 0`) B-slice using the IDR + a prior
P-frame as L0 / L1 anchors.

- **`EncoderConfig`** gains `profile_idc` and `max_num_ref_frames`.
  Defaults preserve round-19 behaviour (Baseline, 1 ref). Tests for
  B-slices set `profile_idc = 77`, `max_num_ref_frames = 2`.
- **B-slice header** (`encoder::slice::write_b_slice_header`):
  `slice_type = 6` (B, all-same-type), `direct_spatial_mv_pred_flag = 1`,
  `num_ref_idx_active_override_flag = 0` (use PPS defaults — one ref
  per list), both list-modification flags = 0, `pred_weight_table`
  absent (`weighted_bipred_idc == 0` ⇒ §8.4.2.3.1 default), and
  `dec_ref_pic_marking` absent (B is non-reference, `nal_ref_idc=0`).
- **B-slice macroblock writer**
  (`encoder::macroblock::write_b_16x16_mb` + `B16x16McbConfig` +
  `BPred16x16` enum). Layout per §7.3.5.1:
    1. `mb_type ue(v)` ∈ {1, 2, 3}
    2. `ref_idx_l0` te(v) — absent at single-ref
    3. `ref_idx_l1` te(v) — absent at single-ref
    4. `mvd_l0_x, mvd_l0_y se(v)` — present when `pred.uses_l0()`
    5. `mvd_l1_x, mvd_l1_y se(v)` — present when `pred.uses_l1()`
    6. inter `coded_block_pattern me(v)` + optional `mb_qp_delta` +
       the same residual layout as P_L0_16x16.
- **Mode decision**: per MB the encoder runs quarter-pel ME against
  L0 and L1 independently, then SAD-scores three candidates:
  L0-only, L1-only, and bipred = `(L0_pred + L1_pred + 1) >> 1`
  (§8.4.2.3.1 eq. 8-273). Lowest-SAD wins with tie-break
  Bi → L0 → L1 (Bi first because matching predictors mean lowest
  residual + minimal mvd cost when both lists agree).
- **Two-list MV-pred grids** (`mv_grid_l0` / `mv_grid_l1`) — §8.4.1.3
  derives `mvpLX` per list independently. L0-only MBs leave L1 grid
  slot at default (`ref_idx = -1, mv = 0`), and vice versa, so a
  later B MB reading either side gets the correct §8.4.1.3.2 step-2
  collapse.
- **CAVLC `mb_skip_run`** (§7.3.4): emitted as `ue(0) = '1'` before
  every coded MB (round 20 never produces B_Skip), plus a final
  trailing `mb_skip_run = 0` at end of slice. Without this the
  decoder mis-aligns at MB 2 (saw "cbp 55 out of range"); landed the
  fix once the symptom showed up under our own decoder.
- **Picture-level `MbDeblockInfo`**: extended with `mv_l1`,
  `ref_idx_l1`, `ref_poc_l1`. The §8.7.2.1 walker's bipred
  `different_ref_or_mv_luma` reads picture identity by POC, so the
  encoder feeds in `ref_poc_l0 = 0` (IDR anchor) and `ref_poc_l1 =
  1000` (sentinel ≠ L0 POC) to make the two anchors distinct under
  NOTE 1.
- **Bipred predictor build** (`build_b_predictors`): wraps the
  existing `build_inter_pred_luma` / `build_inter_pred_chroma` for
  each list, then applies the §8.4.2.3.1 average elementwise. The
  encoder never re-implements the 6-tap FIR or the bilinear chroma
  interpolator — both come straight from the decoder's
  `inter_pred::interpolate_luma` / `interpolate_chroma`, which keeps
  encoder and decoder bit-equivalent on every MV / sub-pel position.

### Validation

- `cargo test -p oxideav-h264` — **915 passed** (was 911 in round 19;
  +4: 2 new unit tests + 2 new integration tests).
- Round 16/17/18/19 PSNR fixtures unchanged: 50.82 / 49.40 / 53.61 /
  40.95 dB.
- New round-20 fixture (`integration_b_slice.rs`):
  - `round20_b_slice_self_roundtrip_matches_local_recon`: encode
    IDR + P + B where B = midpoint(IDR, P) sample-by-sample. Every
    MB picks `B_Bi_16x16` because the bipred predictor reconstructs
    the source bit-for-bit. Decode through our own decoder →
    bit-exact match (max diff 0). **Local + decoder PSNR_Y = 54.19
    dB**.
  - `round20_b_slice_ffmpeg_interop`: ffmpeg (black-box validator) decodes the
    same IDR + P + B Annex B stream **bit-equivalently** against the
    encoder's local recon (max diff 0). 54.19 dB PSNR_Y vs source.

### Status caveats (unchanged from round 19 unless noted)

- **B-slice landed** (round 20). mb_type ∈ {`B_L0_16x16`,
  `B_L1_16x16`, `B_Bi_16x16`} alongside the round-19 P / IDR types.
- No `B_Skip` / `B_Direct_16x16` (the spec's mode-decision derivation
  paths). `direct_spatial_mv_pred_flag` is signalled but never
  exercised.
- No intra fallback in P / B slices.
- Single reference per list: L0 = the previous IDR, L1 = the
  previous P-frame. `max_num_ref_frames = 2` in the SPS.
- Profile bumped to Main (77) when the caller asks for B-slices via
  `EncoderConfig::profile_idc`. The default still emits Baseline
  (66) for IDR-only / P-only streams.
- In-loop deblocking active across intra / P / B MBs, with proper
  L0+L1 ref / POC identity feeding §8.7.2.1's bipred edge derivation.
- CAVLC only.
- No rate control — fixed QP.

## Round 21 — landed

Wire **B_Skip / B_Direct_16x16** spatial-direct mode on top of round-20's
explicit-inter B-slice support. The encoder now mirrors the §8.4.1.2.2
derivation locally and emits the small/zero-bitstream B_Direct_16x16
(raw mb_type 0) or B_Skip (no syntax — folded into `mb_skip_run`)
whenever the spatial-direct predictor is competitive with the
round-20 explicit-inter (B_L0 / B_L1 / B_Bi) candidates.

- **§8.4.1.2.2 derivation** (`encoder::mod::b_spatial_direct_derive`):
  reads the encoder's L0 and L1 MV grids for the (A, B, C, D)
  neighbours, applies the §8.4.1.3.2 C→D substitution, runs the
  MinPositive chain on refIdxLX (eq. 8-184/8-185), detects
  `directZeroPredictionFlag` (eq. 8-188..8-190), and computes per-8x8
  partition MVs subject to the step-7 `colZeroFlag` short-circuit.
- **`colZeroFlag` (§8.4.1.2.2 step 7)**: encoder reads the colocated
  block's L0 MV / refIdx from the L1 anchor. New
  `EncodedFrameRef::partition_mvs` carries these — populated by
  `encode_p` from its `MvGrid`, populated for IDRs as "all intra"
  (refIdxCol = -1 → colZeroFlag = 0). Intra colocated blocks short-
  circuit per §8.4.1.2.1 NOTE.
- **B_Direct_16x16 writer** (`encoder::macroblock::write_b_direct_16x16_mb`
  + `BDirect16x16McbConfig`): emits `mb_type=0` (Table 7-14 raw 0) +
  `coded_block_pattern me(v)` + optional `mb_qp_delta` + residuals.
  No `mvd_l0` / `mvd_l1` / `ref_idx_l0` / `ref_idx_l1` (the decoder
  derives those itself).
- **B_Skip path**: when the chosen mode is Direct AND `cbp_luma == 0
  AND cbp_chroma == 0`, the MB is omitted from the bitstream entirely.
  `pending_skip` accumulates the run; flushed as a single ue(v)
  before the next coded MB or at end-of-slice.
- **Mode-decision Lagrangian bias**: Direct gets a SAD allowance over
  the explicit candidates of `(qp_y/6 + 1) * 16` SAD units, calibrated
  so that on smooth / static content the explicit-inter path's
  marginal SAD wins are outweighed by Direct's 10-20 bit syntax savings.
  Round-20 fixtures (midpoint linear interp) still pass under this
  bias because the explicit-Bi SAD on those is materially below the
  spatial-direct SAD when neighbours haven't propagated the (0, 0) MV
  yet.
- **Per-8x8 uniformity gate**: round-21 only emits Direct / Skip when
  the §8.4.1.2.2 derivation's per-partition MVs are uniform across the
  MB. Non-uniform direct (per-partition motion compensation) is
  deferred to a later round.

### Validation

- `cargo test -p oxideav-h264 --lib` — **803 passed** (+2 unit tests
  for the B_Direct_16x16 writer round-trip and the mb_type/Table 7-14
  decode).
- All round-1..20 fixtures still pass with bit-exact ffmpeg interop on
  every fixture. The round-20 midpoint test PSNR is 51.86 dB (was
  54.19 dB; the small drop reflects more MBs choosing
  Direct/Skip on the smooth content where explicit-Bi was marginally
  better; quality is still well above the test's 38 dB floor).
- New round-21 integration tests
  (`integration_b_skip_direct.rs`):
  - `round21_static_picture_uses_b_skip`: IDR + P + B all from the
    same source → every B-MB picks Direct → cbp == 0 → B_Skip.
    **B-slice = 11 bytes** (vs round-20's 75 bytes for same content
    when only explicit modes are available; a **7× compression** on
    the B portion). Decoder PSNR_Y = 52.36 dB, max enc/dec diff 0.
  - `round21_b_skip_ffmpeg_interop`: ffmpeg (black-box validator) decodes the
    same all-skips B stream **bit-equivalently** against the encoder's
    local recon (max diff 0). 52.36 dB PSNR_Y.
  - `round21_midpoint_b_slice_decoder_match`: round-20's midpoint
    fixture re-tested under the new mode-decision — bit-exact recon
    via our own decoder, B-slice 16 bytes (unchanged).

### Status caveats (unchanged from round 20 unless noted)

- B-slices: round-20 explicit-inter (B_L0 / B_L1 / B_Bi) plus
  **round-21 B_Direct_16x16 / B_Skip** for spatial direct mode.
  No 16x8 / 8x16 partitions, no B_8x8 / B_Direct_8x8.
- Spatial direct only (`direct_spatial_mv_pred_flag = 1`, hard-wired).
  Temporal direct (§8.4.1.2.3) deferred.
- Direct mode emits only when the §8.4.1.2.2 derivation gives uniform
  per-8x8 MVs and at least one list is used. Non-uniform direct is
  deferred.
- No intra fallback in P / B slices (the round-21 frontier still
  applies).
- In-loop deblocking active across intra / P / B (with B_Skip /
  B_Direct properly fed through `MbDeblockInfo`).
- CAVLC only.
- No rate control — fixed QP.

## Round 22 — landed

B-slice 16x8 / 8x16 partition modes (§7.4.5 Table 7-14 raw mb_types
4..=21). The encoder picks among:
- Round-21 set: B_Skip, B_Direct_16x16, B_L0_16x16, B_L1_16x16,
  B_Bi_16x16.
- Round-22 set: 9 mb_types per shape × 2 shapes = 18 new variants
  (`B_L0_L0_16x8` / `B_L0_L1_16x8` / `B_Bi_Bi_16x8` / etc.).

Per-MB decision flow extends the round-21 logic with two extra steps:

1. **Per-cell ME** for both L0 and L1 (4 cells × 2 lists = 8 calls to
   `search_quarter_pel_8x8`). The 16x16-best MV from round-20 is also
   kept as a candidate.

2. **Per-partition mode enumeration** (`best_partition_mode_b`): for
   each of the 4 partition shapes (16x8 top, 16x8 bottom, 8x16 left,
   8x16 right), try all 9 (mode_l0, mode_l1) combinations and keep
   the one with minimum partition-wide SAD. Joint SAD across both
   halves is the partition-shape SAD; the better of `(16x8, 8x16)` is
   the partition winner.

3. **Lagrangian gate**: partition wins over 16x16 only when its SAD
   beats 16x16's by more than `(qp_y/6 + 1) * 8` SAD units (the rate
   penalty for the larger mb_type ue(v) plus the duplicated mvds).
   Direct mode is never overridden by partition mode (B_Skip's 0 bits
   of MB syntax dominates any partition mode's ~10+ bits).

§8.4.1.3 partition-shape MVP derivation uses the
`Partition16x8Top/Bottom` / `Partition8x16Left/Right` directional
shortcuts and falls through to median(A, B, C) with the §8.4.1.3.2
C→D substitution. The encoder's partition writer (`write_b_16x8_mb`,
`write_b_8x16_mb`) emits per-§7.3.5.1: `mb_type` ue(v), then
`ref_idx_l0[part]` for parts 0..1 (gated on partition mode), then
`ref_idx_l1[part]`, then `mvd_l0[part]`, then `mvd_l1[part]`, then
the standard cbp + mb_qp_delta + residuals tail.

Per-MB grid update writes per-8x8 MVs (top half = #0/#1, bottom =
#2/#3 for 16x8; left = #0/#2, right = #1/#3 for 8x16). The deblock
walker's per-4x4 MV array (`MbDeblockInfo::mv_l0`) is filled in §6.4.3
Z-scan order via `LUMA_4X4_BLK[blk]` — the spec-mandated layout that
the decoder also uses, ensuring §8.7.2.1 boundary-strength derivation
agrees encoder ↔ decoder.

Side fix: `neighbour_mvs_16x16` now reads each neighbour's per-8x8
cell that **spatially abuts** the current MB's top-left 4x4 (`#1` for
left, `#2` for above and above-right, `#3` for above-left). Previously
it always read 8x8 #0 — a bug that didn't bite earlier because all
prior B/P MBs had uniform per-8x8 MVs (16x16 partition); with round-22
partition mode introducing per-8x8 variation, the bug would have
produced encoder ↔ decoder mvp mismatches for Direct mode of
partition-mode-neighbour MBs.

### Validation

- 936 tests passing (was 929 in round 21; +4 unit + 3 integration).
- New `integration_b_partitions.rs`:
  - `round22_b_16x8_self_roundtrip_matches_local_recon` — encode IDR
    + P + B with per-half-vertical motion (top half = IDR content,
    bottom = P-shifted content). Encoder picks 16x8 mb_types per MB.
    Decoder match: max enc/dec diff 0, PSNR_Y = 46.87 dB.
  - `round22_b_8x16_self_roundtrip_matches_local_recon` — same with
    per-half-horizontal motion. Encoder picks 8x16 mb_types. PSNR_Y
    = 48.54 dB, max enc/dec diff 0.
  - `round22_b_partitions_ffmpeg_interop` — ffmpeg (black-box validator) decodes
    the 16x8 stream **bit-equivalently** (max diff 0).

### Caveats

- B_8x8 sub-MB partitions still deferred to a later round.
- B_Direct_8x8 (sub-MB direct) still deferred.
- Partition mode never overrides Direct (intentional — B_Skip
  dominates any partition mode rate-wise).
- CAVLC only.
- Single ref per list.

## Round 29 — landed

**Intra fallback in P / B slices** via per-MB Lagrangian RDO. The
encoder now compares the inter winner's cost (`J_inter = D_inter +
λ·R_inter` measured on the actually-emitted bitstream) against an
`Intra_16x16` trial cost (`J_intra` measured the same way) and
installs the lower-J path. Picks up scene-cut frames, occlusion of
new content over a textured background, and high-detail areas where
motion-compensated prediction's residual is more expensive to code
than a fresh intra block.

- **`EncoderConfig::intra_in_inter`** (default `true`) — toggle for
  the new path. When `false`, the round-16..28 inter-only behaviour
  is preserved exactly (useful for A/B testing).
- **`encoder::macroblock::write_intra16x16_mb_in_inter_slice`** — new
  writer mirroring `write_intra16x16_mb` but emitting the mb_type
  ue(v) with a configurable offset: `+5` for P-slice (Table 7-13
  intra range raw 5..=30), `+23` for B-slice (Table 7-14 intra range
  raw 23..=48). The decoder side already accepts these values
  (`MbType::from_p_slice` / `from_b_slice` map them via
  `from_i_slice(raw - 5)` / `from_i_slice(raw - 23)`).
- **`InterMbStateSnapshot`** wraps the existing `MbStateSnapshot`
  with the inter-context-only fields: `MvGridSlot` (and L1 grid for
  B), plus `pending_skip`. Lets the trial RDO roll back the loser
  cleanly without leaking state into the next MB.
- **`Encoder::encode_p_mb_with_intra_fallback`** /
  **`Encoder::encode_b_mb_with_intra_fallback`** — RDO wrappers
  around the existing `encode_p_mb` / `encode_b_mb` per-MB encoders.
  Snapshot pre-state, run inter trial, measure J_inter, snapshot
  post-inter, restore pre-state, run new
  `encode_p_mb_intra16x16` / `encode_b_mb_intra16x16` trial,
  measure J_intra, install winner.
- **`Encoder::encode_intra16x16_in_inter_slice`** — shared emit
  helper used by both P/B paths. Runs §8.3.3 luma RDO across the 4
  pred_modes (Vertical / Horizontal / DC / Plane), §8.3.4 chroma
  SAD across the 4 chroma modes, the forward+quantize+inverse
  pipeline, and emits the syntax via
  `write_intra16x16_mb_in_inter_slice` with the requested offset.
  Updates `recon_*`, `nc_grid` (per-block CAVLC TotalCoeff), and
  `intra_grid` (`available = true, is_i_nxn = false` so subsequent
  I_NxN neighbour lookups fall to DC). The wrapper updates `mv_grid`
  with `is_intra = true`, `ref = -1`, `mv = 0` so subsequent inter
  MBs' §8.4.1.3.2 step-2 collapse fires.
- **Per-MB syntax accounting** — `J_inter` = `cost_combined(SSD,
  inter_bits, λ_inter)`. For B_Skip / P_Skip MBs the inter trial
  doesn't write any bits (the skip is folded into `pending_skip`),
  so `J_inter = SSD(source, recon)` only — the fast path always
  beats intra on static / skip-able content.

### Validation

- Lib tests: 819 → **819 passed** (all unit + integration tests
  pass). Two trivial clippy lints fixed.
- Pre-existing failure `round21_static_picture_uses_b_skip` (B-
  slice byte budget regression unrelated to this round) is on the
  master branch already; not caused by round-29.
- New integration tests (`integration_p_slice_intra_fallback.rs`):
  - `round29_p_slice_intra_fallback_shrinks_occlusion_fixture`:
    encode a 64x64 textured-wall IDR + a P-frame with a 32x32
    "occluding ball" of brand-new content. P-slice **588 → 108
    bytes (81.6 % smaller)** under intra fallback vs. inter-only.
  - `round29_p_slice_intra_fallback_decodes_through_self`:
    self-roundtrip bit-exact through our own decoder (max enc/dec
    luma diff 0).
  - `round29_p_slice_intra_fallback_ffmpeg_interop`:
    ffmpeg (black-box validator) decodes the same intra-fallback P-slice
    bit-equivalently against the encoder's local recon (max
    ff/enc luma diff 0).
  - `round29_p_slice_intra_fallback_picks_intra_for_occluded_mbs`:
    sanity check that the parser accepts the intra-in-inter
    mb_type values without error.
- Round-16/17/18/19/20/21/22/23/24/25/26 fixtures all still pass
  (their inter cost is dominated by motion-compensated prediction
  matching the source; the intra trial never wins).

### Status caveats (unchanged from round 28 unless noted)

- **Intra fallback active in P/B slices** (round-29 default). When
  `intra_in_inter = true`, every coded inter MB trials Intra_16x16
  via RDO; lower-J wins.
- Round-29 scope: **Intra_16x16 only** (no I_NxN / I_PCM fallback).
  The §8.3.1.2 9-mode I_NxN path in P/B slices needs careful CAVLC
  nC-grid plumbing for the in-MB neighbour reads — deferred.
- Round-29 chroma: **4:2:0 only** (encode_p / encode_b are 4:2:0-
  only at this level).
- Spatial direct + temporal direct mode B (rounds 21/24) +
  partition modes (round 22) + B_8x8 mixed (round 25) + explicit
  weighted bipred (round 26) all still active and unaffected.
- CAVLC only. No rate control. Single ref per list (B-slices).

## Round 30 — landed

Wire **CABAC entropy coding** for I + P slices via two new entry points
(`Encoder::encode_idr_cabac`, `Encoder::encode_p_cabac`) layered on the
round-1..29 motion estimation / intra prediction / transform /
quantisation primitives. The CAVLC paths (`encode_idr` / `encode_p`)
remain unchanged.

- **`encoder::cabac_engine`** — §9.3.4 arithmetic encoder primitives
  (`EncodeDecision`, `EncodeBypass`, `EncodeTerminate`, `RenormE`,
  `EncodeFlush`, `put_bit` carry-propagation). Reuses the decoder's
  `RANGE_TAB_LPS` / `TRANS_IDX_LPS` / `TRANS_IDX_MPS` tables — same
  state machine, same probabilities, walked in encode direction.
- **`encoder::cabac_syntax`** — per-syntax-element binarisations and
  ctxIdxInc derivations mirroring `cabac_ctx::decode_*` step-for-step.
  Coverage: `mb_skip_flag`, `mb_type` (I and P), `mvd_lX`,
  `ref_idx_lX`, `coded_block_pattern`, `coded_block_flag`,
  `significant_coeff_flag`, `last_significant_coeff_flag`,
  `coeff_abs_level_minus1`, `coeff_sign_flag`,
  `intra_chroma_pred_mode`, `mb_qp_delta`, `end_of_slice_flag`,
  `prev_intra4x4_pred_mode_flag`, `rem_intra4x4_pred_mode`, plus a
  full `encode_residual_block_cabac` helper (CBF + significance map +
  abs_level + sign).
- **`encoder::cabac_path`** — top-level IDR + P encoders. IDR encodes
  every MB as `Intra_16x16` (4 luma modes + 4 chroma modes by SAD).
  P encodes `P_Skip` when CBP collapses to zero or `P_L0_16x16` (full
  luma 4x4 DC+AC + chroma DC ± AC residual). Per-MB CBF neighbour
  state tracked in `CabacEncGrid` so encoder + decoder agree on every
  ctxIdxInc.
- **PPS** (`BaselinePpsConfig::entropy_coding_mode_flag`): toggles
  CAVLC vs CABAC. **Slice header** (`PSliceHeaderConfig::cabac`):
  emits `cabac_init_idc` ue(v) when CABAC is enabled.
- **Profile gate**: CABAC requires Main (77) or higher per §A.2.1.

### Validation

- `cargo test -p oxideav-h264 --lib` — **846 passed** (+27 new tests:
  7 in `cabac_engine` + 20 in `cabac_syntax`).
- All round-1..29 integration tests still pass with the existing
  CAVLC paths unchanged.
- New `tests/integration_cabac_encoder.rs`:
  - `round30_idr_cabac_self_roundtrip`: 64x64 gradient → 79-byte IDR
    decoded through our own decoder bit-exactly (max enc/dec diff = 0,
    PSNR_Y = 47.58 dB).
  - `round30_idr_plus_p_cabac_self_roundtrip`: IDR + P (same source) →
    P-slice = 12 bytes (mostly P_Skip), bit-exact recon, PSNR_Y vs
    source = 47.58 dB.
  - `round30_idr_cabac_ffmpeg_interop`: ffmpeg 8.1 (black-box validator) decodes
    the same CABAC IDR bit-equivalently against the encoder's local
    recon (max diff = 0, PSNR_Y = 47.58 dB).

### Status caveats

- I + P slices only; B-slice CABAC encode deferred to a later round.
- 4:2:0 only on the CABAC path; 4:2:2 / 4:4:4 still CAVLC-only.
- Round-30 P encoder pins MV = (0, 0) for every inter MB to keep the
  §8.4.1.3 mvp derivation off the critical path. Sufficient for
  static / near-static fixtures (round-30 acceptance bar) since the
  P_Skip fast path dominates; the next round will wire the per-MB MV
  grid + spec mvp so non-trivial motion is supported.
- I_NxN (Intra_4x4) under CABAC not yet wired — the IDR CABAC path
  emits every MB as Intra_16x16.

## Round 31+ (planned)

- B-slice CABAC encode.
- Per-MB MV grid + §8.4.1.3 mvp under CABAC P encoder so motion is
  supported beyond P_Skip.
- I_NxN under CABAC.
- 4:2:2 / 4:4:4 under CABAC.
- I_NxN fallback in P / B slices (round-29 currently only trials
  Intra_16x16; I_NxN can be a further win on detailed content).
- Multiple slices per picture.
- 8x8 transform / High-profile features.
- Rate control.
- Chroma AC nC per §9.2.1.1.
- VUI / SEI tuning (HRD, mastering display, content light level).

## Entry points

- [`Encoder::encode_idr`] — top-level IDR I-slice: takes one `YuvFrame`
  and emits an SPS + PPS + Annex B IDR slice + the matching reconstruction.
- [`Encoder::encode_p`] — non-IDR P-slice (round 16). Takes one
  `YuvFrame` + a single L0 reference (`EncodedFrameRef`) and emits the
  P slice NAL + recon. SPS/PPS were shipped by the IDR.
- [`Encoder::encode_b`] — non-reference B-slice (round 20 + 21 + 22).
  Takes one `YuvFrame` + L0 and L1 references (`EncodedFrameRef`) and
  emits the B slice NAL + recon. Requires `EncoderConfig::profile_idc
  >= 77` and `max_num_ref_frames >= 2`. SPS/PPS were shipped by the
  IDR. Round-21: when an `EncodedFrameRef` is built `From<&EncodedP>`
  (or `From<&EncodedIdr>`), its `partition_mvs` field is populated for
  use by the §8.4.1.2.2 spatial-direct derivation when this frame
  serves as the L1 anchor. Round-22: per-MB the encoder picks among
  16x16 modes (round-20: B_L0/L1/Bi_16x16; round-21: B_Skip,
  B_Direct_16x16) and the 18 partition modes (B_*_*_16x8 with raw
  values 4..=20 even; B_*_*_8x16 with raw values 5..=21 odd).
- Lower-level helpers under [`bitstream`], [`nal`], [`sps`], [`pps`],
  [`slice`], [`macroblock`], [`cavlc`], [`transform`] are usable
  independently — round 2's higher-mode encoder can compose them
  without touching the round-1 dispatcher.
- [`Encoder::encode_idr_cabac`] / [`Encoder::encode_p_cabac`]
  (round 30) — CABAC-enabled IDR + P entry points. Require
  `EncoderConfig::cabac = true` and `profile_idc >= 77`. Produce
  Annex B streams that round-trip through this crate's decoder and
  through ffmpeg (black-box validator) H.264 decoder bit-equivalently.
- [`Encoder::encode_b_cabac`] round 31 — CABAC B-slice entry point
  (B_L0_16x16 / B_L1_16x16 / B_Bi_16x16, default-merge bipred). Round
  32 extends the CABAC P + B paths with real quarter-pel ME (replacing
  the round-30/31 forced MV=(0,0)) plus `B_Skip` / `B_Direct_16x16`
  emission via the §8.4.1.2.2 spatial-direct derivation mirrored in
  `cabac_b_spatial_direct_derive`. Per-8x8 / mixed `B_8x8`+all-Direct
  CABAC variants + 4:2:2 / 4:4:4 P/B paths still deferred (they need a
  CABAC sub_mb_type writer + Yuv422/Yuv444 chroma writer plumbing).

## Round 382 — landed

High-profile **8x8 transform** (`EncoderConfig::transform_8x8`), CAVLC,
4:2:0, spanning I / P / B:

- **`encoder::transform`**: `forward_core_8x8` (§8.6.4 integer basis
  `E8`, transpose of the normative §8.5.13.2 inverse butterfly),
  `quantize_8x8` (`MF8x8[m][class] = round(2^18 / normAdjust8x8)` at
  `qBits8 = 22 + qP/6`, intra/inter rounding), `zigzag_scan_8x8`
  (Table 8-14) and `deinterleave_8x8_to_4x4` (§7.4.5.3.3).
- **Intra**: `encode_mb_intra8x8` — per-8x8-block §8.3.2 prediction
  across all 9 modes (availability-gated per §8.3.2.2.2..2.10) under
  Lagrangian RDO, with the §8.3.2.1 eq. 8-73 `predIntra8x8PredMode`
  derivation mirrored encoder-side (incl. the eq. 8-72 4x4-neighbour
  sub-index). `encode_mb` runs a 3-way I_16x16 / I_4x4 / I_8x8 trial;
  I_4x4 MBs code `transform_size_8x8_flag = 0` per §7.3.5
  (`INxNMcbConfig::emit_transform_size_8x8_zero`).
- **Inter**: `forward_inter_luma_8x8` + `inter_luma_transform_cost`
  drive a per-MB 8x8-vs-4x4 residual trial in both `encode_p_mb`
  (P_L0_16x16, `PL016x16McbConfig::transform_size_8x8_flag:
  Option<bool>`) and `encode_b_mb` (all six B writers upgraded to the
  same Option-flag). The P 4MV writer codes the mandatory flag as 0.
- **SPS/PPS**: profile auto-promotes to High (100); the PPS writer
  gains the §7.3.2.2 optional tail. **Deblock**: per-MB
  `MbDeblockInfo::transform_size_8x8_flag` reaches the shared §8.7
  walker (internal 4x4-edge skip).
- **Decoder fix**: the CAVLC 8x8 luma residual is now interleaved per
  §7.4.5.3.3 (was concatenated — every CAVLC 8x8-transform MB decoded
  from a scrambled coefficient order).
- **Validation**: `tests/integration_i8x8_encoder.rs` (8 gates) —
  High SPS/PPS syntax, bit-exact self-roundtrip, bit-exact black-box
  reference-decoder interop on IDR / IDR+P / IDR+P+B, adaptive-RDO
  selection counters (`EncodedIdr/P/B::i8x8_mb_count`), PSNR floor.

### Status caveats

- CAVLC only — CABAC 8x8 (blockCat-5 residual + ctx tables) deferred.
- 4:2:0 only; 4:2:2 / 4:4:4 8x8 (incl. the CAVLC 4:4:4 chroma-like 8x8
  read path in the decoder) deferred.
- Scaling lists pinned flat (PPS `pic_scaling_matrix_present_flag=0`).

## Round 385 — landed

**8x8 transform under CABAC (blockCat-5)** across I / P / B, plus the
**4:2:2 and 4:4:4 Intra_8x8 CAVLC** encode paths and the decoder-side
**CAVLC 4:4:4 8x8 read-path** fix:

- **`encoder::cabac_syntax`**: `encode_transform_size_8x8_flag`
  (§9.3.3.1.1.10, FL cMax=1, ctxIdxOffset 399 with neighbour condTerms
  from the per-MB `transform_size_8x8` grid tracking) + 64-coefficient
  Luma8x8 (blockCat-5) roundtrip pins: Table 9-43 significance maps,
  ctxIdxOffset 402/417/426 families, the §7.3.5.3.3 CBF suppression
  (`maxNumCoeff == 64 && ChromaArrayType != 3`) via `skip_cbf`, and the
  scan-position-63 implicit-final-significance path.
- **`encode_idr_cabac`**: per-MB I_16x16-vs-Intra_8x8 Lagrangian trial
  (full §8.3.2 9-mode candidate on a snapshot/rollback recon window +
  the shared `IntraGrid` eq. 8-73 derivation). A winner codes I_NxN +
  flag=1 + 4 prev/rem pairs (ctxIdx 68/69) + explicit CBP + one
  blockCat-5 residual per coded quadrant, CBF folded into the four 4x4
  slots for §9.3.3.1.1.9 neighbour reads (decoder mirror). SPS
  auto-promotes to High; PPS carries the tool flag.
- **`encode_p_cabac` / `encode_b_cabac`**: shared §8.6.4 8x8-vs-4x4
  luma-residual trial (`forward_inter_luma_8x8` +
  `inter_luma_transform_cost`); §7.3.5 second-gate flag after CBP
  whenever `cbp_luma > 0` (all emitted shapes qualify;
  `direct_8x8_inference_flag = 1` covers the Direct forms; skips
  gate-exempt; flag normalised to the inferred 0 when cbp collapses).
  `EncodedIdr/P/B::i8x8_mb_count` count CABAC picks.
- **4:2:2**: the 3-way transform-size RDO runs at High 4:2:2 via
  `write_i8x8_mb_chroma` (ChromaWriteKind; 4:2:0 delegates
  bit-identically) and the 4:2:2 I_NxN writer's new mandatory
  `transform_size_8x8_flag = 0` emission.
- **4:4:4**: `encode_mb_intra8x8_444` — §8.3.4.5 chroma coded like
  luma (per-quadrant luma-mode reuse, 8x8 transform at the chroma QP,
  shared CBP across the three planes, Table 9-4(b) CBP inverse, real
  per-plane §9.2.1.1 nC via the new `derive_nc_plane_luma_like`).
- **Decoder fix**: the CAVLC 4:4:4 8x8 Cb/Cr plane residual is now
  §7.4.5.3.3-interleaved at parse (mirror of the round-382 luma fix);
  previously every CAVLC 4:4:4 8x8-transform chroma residual decoded
  scrambled.

### Validation

- `tests/integration_i8x8_cabac.rs` (6 gates): CABAC IDR / IDR+P /
  IDR+P+B GOPs — mixed I_16x16/I_8x8 selection, second-gate flag with
  both values in one stream, bit-exact self-roundtrip AND bit-exact
  black-box reference-decoder interop on every stream shape.
- `tests/integration_i8x8_encoder.rs` grew to 12 gates: 4:2:2
  (profile-122 SPS + full-height chroma) and 4:4:4 (profile-244,
  all-MB Intra_8x8, textured chroma exercising the plane interleave)
  each pinned bit-exact through both decoders.
- `cavlc_444_i8x8_plane_residual_is_interleaved` pins the parse-side
  storage layout in isolation.

### Status caveats

- CABAC 8x8 remains 4:2:0 (blockCat-9/13 Cb/Cr 8x8 encode deferred).
- 4:4:4 codes all MBs Intra_8x8 under `transform_8x8` (I_16x16-vs-
  I_8x8 RDO at 4:4:4 deferred).
- Scaling lists still pinned flat (`pic_scaling_matrix_present_flag =
  0`); non-flat list encode + the SPS/PPS scaling_list writer are the
  named next rung.
- No staged CAVLC 4:4:4 8x8 corpus fixture exists (`4-4-4-high` /
  `intra-only-high444` are CABAC) — coverage is via our own encoder +
  the black-box validator.

## Round 388 — landed

**CABAC 4:2:2 + 4:4:4-8x8 (blockCat-9/13), non-flat scaling lists,
4:4:4 I_16x16-vs-Intra_8x8 RDO** — plus two long-standing latent
correctness bugs surfaced and fixed (decoder weightScale inverse scan;
encoder chroma-AC nC).

- **Decoder CABAC chroma-format fixes** (pinned bit-exact against
  x264-generated High 4:2:2 / High 4:4:4-with-8x8 streams via the
  black-box reference decoder):
  - §9.3.3.1.3 eq. (9-22): chroma-DC sig/last ctxIdxInc =
    `Min(levelListIdx / NumC8x8, 2)` — the NumC8x8=2 divisor (4:2:2)
    was dropped.
  - §9.3.3.1.1.9 cat-4 at 4:2:2: above-neighbour wrap into the 2x4
    chroma-AC grid now uses the true chroma MB height (16).
  - §9.3.3.1.1.9 cat-5/9/13: the explicitly-coded CBF of 8x8 blocks
    at ChromaArrayType==3 derives real cond terms via §6.4.11.2/.3
    (CBP-bit + transform_size_8x8_flag gating on folded per-plane 4x4
    slots) instead of an all-zero fallback.
- **CABAC 4:2:2 IDR encode**: 8-coefficient ChromaDCLevel under the
  NumC8x8=2 contexts, 8 ChromaACLevel blocks on the 2x4 grid,
  transform_8x8 luma trial allowed; §8.5.11.2 chroma chain shared with
  the CAVLC path; §8.7 deblock at chroma_array_type=2.
- **CABAC 4:4:4 Intra_8x8 encode**: blockCat-9/13 64-coefficient
  chroma residuals (§8.3.4.5 per-quadrant luma-mode reuse at the
  chroma QP), shared §7.4.5.1 CBP across the three planes, explicit
  cat-5/9/13 CBF. Root-caused a one-phantom-bin desync: the I_NxN
  CBP emit had a hard-coded 4:2:0 ChromaArrayType.
- **§8.5.9 weightScale inverse scan (decoder)**: scaling lists are
  parsed in bitstream zig-zag order but every LevelScale consumer
  indexes raster weightScale — the missing §8.5.6 inverse scan garbled
  every non-flat matrix. `select_scaling_list_4x4/8x8` now return the
  inverse-scanned weightScale (x264 `cqm=jvt` CAVLC + CABAC streams
  byte-exact).
- **Non-flat scaling-list encode**: `EncoderConfig::scaling_matrix`
  ∈ {Flat, SeqDefault, PicDefault}. Default Table 7-3/7-4 matrices
  signalled as UseDefaultScalingMatrixFlag (`delta_scale = -8` per
  list) in the SPS or the §7.3.2.2 PPS tail; `_w` quantiser variants
  divide by the weightScale; the I_16x16/I_4x4/I_8x8 RDO trials and
  recon inverses all run under the effective matrices. CAVLC IDR at
  4:2:0; P/B + CABAC assert Flat.
- **Chroma-AC CAVLC nC (latent-bug fix)**: the round-2 `nC = 0`
  shortcut selected the wrong coeff_token *table* whenever a
  neighbouring chroma-AC block carried TotalCoeff >= 2 (dense chroma
  desynchronised every spec decoder). All CAVLC writers (I/P/B,
  4:2:0 + 4:2:2) now take per-block nC derived from the grid with
  progressive in-MB TotalCoeff commits.
- **4:4:4 I_16x16-vs-Intra_8x8 RDO**: CAVLC runs a real two-way trial
  (3-plane SSD + actually-emitted bits, chroma-aware MbStateSnapshot);
  CABAC folds the fmt-3 special case into the standard luma-cost
  trial. Mixing the shapes exposed the fmt-3 I_16x16 per-plane
  `nC = 0` approximation — replaced with the real §9.2.1.1 per-plane
  derivation + grid commits (`derive_plane_nc_444_i16x16`).

### Validation

- `tests/integration_cabac_chroma_formats.rs` (5 gates) — x264 High
  4:2:2 / High 4:4:4-8x8 / cqm=jvt streams decode byte-exact.
- `tests/integration_cabac_422.rs` (5) + `integration_cabac_444_8x8.rs`
  (3) — bit-exact self-roundtrip AND black-box interop on every new
  CABAC wire shape (mixed-selection pins included).
- `tests/integration_scaling_matrix.rs` (11) — SPS/PPS signalling +
  bit-exact dual-decoder interop for SeqDefault/PicDefault ±
  transform_8x8 + flat regression.
- `tests/integration_chroma_nc.rs` (3) — dense-chroma IDR + GOP.
- Full suite: 1561 tests green.

### Status caveats

- 4:4:4 I_4x4 encode leg deferred (needs a chroma-as-luma 4x4 emit
  path); the RDO is two-way (I_16x16 / I_8x8).
- Non-flat scaling matrices: CAVLC 4:2:0 IDR only; P/B + CABAC +
  custom (non-default) list values deferred.
- CABAC 4:2:2 remains IDR-only (P/B 4:2:2 encode deferred, matching
  CAVLC).

## Round 391 — landed

**4:4:4 I_4x4 leg, custom + P/B + CABAC scaling matrices, CABAC
4:2:2/4:4:4 P and B** — plus a twelve-value CABAC init-table decoder
fix surfaced by the new inter-4:4:4 interop pins.

- **4:4:4 I_4x4 (CAVLC)**: `encode_mb_intra4x4_444` — per-4x4-block
  9-mode Lagrangian luma trial; Cb/Cr reuse each block's luma mode per
  §8.3.4.5 on their own recon neighbours, quantised at the chroma QP;
  §7.4.5.1 CBP quadrant bits cover all three planes; per-plane
  §9.2.1.1 nC via `derive_nc_plane_luma_like` with progressive in-MB
  commits. `write_i4x4_mb_444` emits the I_NxN syntax (16 prev/rem
  pairs, no intra_chroma_pred_mode, optional transform_size_8x8_flag
  = 0). The 4:4:4 dispatch is now a two-way I_16x16/I_4x4 RDO (three-
  way + Intra_8x8 under `transform_8x8`); `EncodedIdr::i4x4_mb_count`
  reports selection.
- **Custom scaling lists**: `ScalingMatrixMode::{SeqCustom,PicCustom}`
  carry user 4x4-intra/inter + 8x8-intra/inter values (scan order,
  validated to 1..=255); the SPS/PPS writers code them through the
  real §7.3.2.1.1.1 delta_scale chain with the mandated -128..=127
  wrap. Parse-back pins recover the exact values, including a
  wrap-heavy 250/6 list.
- **Non-flat matrices on P/B (CAVLC)**: `inter_weights()`
  (Default_4x4/8x8_Inter or custom) drive `forward_inter_luma`,
  `forward_inter_luma_8x8` and `encode_chroma_residual_inter`; the
  shared intra-in-inter fallback body quantises under the intra lists.
- **Non-flat matrices under CABAC**: I/P/B at 4:2:0 — the IDR intra
  helpers, the inter luma/chroma quantisers and the recon inverses all
  take the §8.5.9 weightScale; SPS/PPS emit shared with CAVLC.
- **CABAC 4:2:2/4:4:4 P**: multi-format `encode_p_cabac` — 4:2:2
  chroma MC (`mvC.y = mvL.y*2` in 1/8-pel) + the §8.5.11.2 inter chain
  at NumC8x8=2; 4:4:4 luma-like chroma MC + §7.3.5.3 coded-like-luma
  residuals (blockCat-8/12 4x4 or 5/9/13 8x8 with explicit CBF),
  shared CBP across planes.
- **CABAC 4:2:2/4:4:4 B**: 16x16 explicit mode set (B_L0/L1/Bi) with
  the same chroma machinery; skip/direct + partitions + mixed B_8x8
  remain 4:2:0-only encoder choices. Shared emit helpers
  (`emit_inter_luma_residual_cabac` / `emit_inter_chroma_residual_cabac`)
  carry the format dispatch for both P and B.
- **Decoder fix**: twelve §9.3.1.1 Table 9-30/9-31/9-32 P/B-column
  (m, n) init values (ctxIdx 805/849, 975..980, 1005..1010) were
  mis-transcribed; every prior 4:4:4 pin was intra-only (correct I
  column) so the bug only fired on the first inter Cb/Cr
  coeff_abs_level_minus1 bin. The full 460..1023 init range is now
  diffed against the spec table text (sub-460 `InitCell::all` rows
  also verified clean).

### Validation

- `integration_i4x4_444_encoder.rs` (3), `integration_scaling_matrix.rs`
  11 → 21, `integration_scaling_matrix_pb.rs` (5),
  `integration_scaling_matrix_cabac.rs` (5),
  `integration_cabac_pb_chroma_formats.rs` (9), plus the black-box
  3-frame 4:4:4 CABAC P pin in `integration_cabac_chroma_formats.rs`.
- Every new stream shape is bit-exact through our decoder AND
  byte-exact through the black-box reference decoder.
- Full suite: 1594 tests green.

### Status caveats

- CAVLC 4:2:2/4:4:4 P/B encode still deferred (CABAC landed first).
- B at 4:2:2/4:4:4 is 16x16-explicit-only (no skip/direct/partitions).
- Non-flat matrices remain 4:2:0-only (every entropy mode); 4:2:2 and
  4:4:4 assert Flat.
- The trellis intra rate-model recalibration (stretch) was not
  reached this round.

## Round 397 — landed

**The encoder's chroma-format matrix is complete**: every
(entropy coder × chroma format × slice type) cell encodes and decodes
bit-exact through both this crate's decoder and the black-box
reference decoder.

- **CAVLC 4:2:2/4:4:4 P** (`encode_p` multi-format): Table 6-1 plane
  dims, per-format chroma MC (§8.4.1.4 `mvC.y = mvL.y·2` at 4:2:2;
  §8.4.2.2.1 luma 6-tap per chroma plane at 4:4:4), the §8.5.11.2
  2x4-Hadamard inter chain + `ChromaDc422` contexts at 4:2:2, and
  §7.3.5.3 coded-like-luma chroma at 4:4:4 (Table 9-4(b) inter CBP,
  `ChromaWriteKind::Yuv444Inter`, `derive_plane_nc_444_inter`). All
  CAVLC inter writers now funnel through a shared `emit_inter_mb_tail`
  (legacy 4:2:0 delegates bit-identically). P slice header/deblock
  mirror encode_idr (4:2:2 deblocks at chroma_array_type=2; 4:4:4
  disables deblocking).
- **CAVLC 4:2:2/4:4:4 B** (`encode_b_mb_multifmt`): B_Skip /
  B_Direct_16x16 (spatial §8.4.1.2.2 AND temporal §8.4.1.2.3 behind
  the round-21 uniform gate), explicit B_L0/L1/Bi_16x16 (round-26
  explicit weighted luma merge included), and the 16x8 / 8x16
  partition shapes.
- **CABAC B skip/direct at 4:2:2/4:4:4**: the r391 explicit-only
  branch gains the spatial-direct trial; a zero-residual direct
  winner folds into B_Skip. Fixed alongside: the B_Skip deblock
  record only carried the L0 list (phantom bS 0→1 against coded
  direct neighbours — latent at 4:2:0 on flat fixtures, surfaced by
  the 4:2:2 pin as a 1-LSB Cb divergence from BOTH decoders).
- **CABAC B partitions at 4:2:2/4:4:4**: Table 7-14 raw-4..=21
  binarisation, per-partition §8.4.1.3 mvps with within-MB inflight
  state, §9.3.3.1.1.7 |mvd|-sum contexts at the partition top-left
  4x4 with per-4x4 fan-out, per-half grid + deblock identity.
- **Per-format partition chroma MC**: `build_chroma_rect_pred_fmt`
  motion-compensates an arbitrary chroma rectangle at any format
  (1/8-pel bilinear at 4:2:0/4:2:2, luma 6-tap at 4:4:4); a 16x8 luma
  half maps to a ct_w × ct_h/2 chroma rect, an 8x16 half to
  ct_w/2 × ct_h.
- **Non-flat scaling matrices at 4:2:2/4:4:4** (every entropy coder,
  I/P/B): `quantize_chroma_dc_422_w` (eq. 8-328/8-329
  `LevelScale4x4(qP%6, 0, 0)`), weighted 4:2:2 AC + recon inverses,
  weighted 4:4:4 coded-like-luma plane chains (I_16x16 DC/AC, I_8x8,
  inter), and the §7.3.2.2 PPS
  `6 + ((chroma_format_idc != 3) ? 2 : 6) · transform_8x8` list loop.
  Decoder fix: the 4:4:4 inter chroma 8x8 dequant passed SPEC list
  indices (9/11) to the sub-index-based `select_scaling_list_8x8` —
  the >= 6 flat fallback silently dequantised every non-flat 4:4:4
  inter chroma 8x8 residual flat (Inter_Cb = 3, Inter_Cr = 5).

### Validation

- `tests/integration_cavlc_pb_chroma_formats.rs` (13 gates): CAVLC
  P/B GOPs at 4:2:2 + 4:4:4 (± transform_8x8), flat-source P_Skip /
  B_Skip runs at all three formats, per-half-motion partition
  fixtures (16x8 + 8x16; selection instrumented: 17/26 partition MBs).
- `tests/integration_cabac_pb_chroma_formats.rs` grows to 11 gates:
  CABAC B skip runs + partition fixtures at 4:2:2/4:4:4.
- `tests/integration_scaling_matrix_chroma_formats.rs` (8 gates):
  SeqDefault / PicDefault / SeqCustom × {4:2:2, 4:4:4} × {CAVLC,
  CABAC} ± transform_8x8, with flat-vs-non-flat discriminators.
- Every stream bit-exact through our decoder AND byte-exact through
  the black-box reference decoder. Full suite: 1617 tests green.

### Status caveats

- 4MV P_8x8, B_8x8 (all-direct / mixed) and the Intra_16x16-in-inter
  fallback remain 4:2:0-only encoder mode choices (the 4:2:2/4:4:4
  cells are fully encodable without them).
- Trellis refinement still asserts flat scaling lists.
- The trellis intra rate-model recalibration (stretch, named in r391)
  was again not reached.