rusty_vp9 0.1.0

Pure-Rust VP9 video decoder + encoder, no C/FFI. Decoder bit-exact against all 315 official libvpx conformance vectors.
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
//! VP9 encoder — the push/pull driver around [`FrameEncoder`] (Floor 3, brick C3).
//!
//! Wraps [`FrameEncoder`] in a send/receive interface (FFmpeg-style: push
//! frames in, pull [`EncodedPacket`]s out) and converts an incoming YUV 4:2:0
//! frame (display size) into the coded grid (rounded up to 8, edge-replicated)
//! the frame encoder expects.

use std::collections::VecDeque;

use crate::{Error, Result};

use super::frameenc::FrameEncoder;

/// Frame-level rate controller (R2): a leaky-bucket feedback that nudges the
/// per-frame `qindex` toward a target bits-per-frame. Higher `qindex` ⇒ coarser
/// quantization ⇒ fewer bits, so an over-budget frame raises `q` for the next.
struct RateCtl {
    target_per_frame: f64, // bits
    q: f64,                // current qindex (kept fractional for smooth control)
}

impl RateCtl {
    /// Pick the qindex for the next frame.
    fn qindex(&self) -> u32 {
        self.q.round().clamp(4.0, 220.0) as u32
    }
    /// Feed back the bits a frame actually spent.
    fn update(&mut self, actual_bits: f64) {
        // Integral control: accumulate the relative over/undershoot into q.
        let err = (actual_bits - self.target_per_frame) / self.target_per_frame;
        self.q = (self.q + 10.0 * err.clamp(-1.0, 4.0)).clamp(4.0, 220.0);
    }
}

/// Concatenate coded frames into a VP9 superframe: the frames back-to-back followed
/// by a superframe index (marker, each frame's byte length, marker). A hidden ALT-REF
/// must ride in a superframe with the next shown frame, else a lenient decoder emits it
/// as its own displayed frame.
fn pack_superframe(frames: &[Vec<u8>]) -> Vec<u8> {
    let max = frames.iter().map(|f| f.len()).max().unwrap_or(0);
    let mag: usize = if max < (1 << 8) {
        1
    } else if max < (1 << 16) {
        2
    } else if max < (1 << 24) {
        3
    } else {
        4
    };
    let marker = 0xc0u8 | (((mag - 1) as u8) << 3) | ((frames.len() - 1) as u8);
    let mut out = Vec::new();
    for f in frames {
        out.extend_from_slice(f);
    }
    out.push(marker);
    for f in frames {
        let sz = f.len();
        for b in 0..mag {
            out.push(((sz >> (8 * b)) & 0xff) as u8);
        }
    }
    out.push(marker);
    out
}

/// SAD of the `bs×bs` luma block at `(bx,by)` in `anchor` against `neigh` shifted by
/// integer `(mvr,mvc)`, clamping reference reads to the plane border.
#[allow(clippy::too_many_arguments)]
fn tf_sad(
    anchor: &[u16],
    neigh: &[u16],
    w: usize,
    h: usize,
    bx: usize,
    by: usize,
    bs: usize,
    mvr: i32,
    mvc: i32,
) -> u64 {
    let mut sad = 0u64;
    for dy in 0..bs {
        let ay = by + dy;
        if ay >= h {
            break;
        }
        for dx in 0..bs {
            let ax = bx + dx;
            if ax >= w {
                break;
            }
            let sx = (ax as i32 + mvc).clamp(0, w as i32 - 1) as usize;
            let sy = (ay as i32 + mvr).clamp(0, h as i32 - 1) as usize;
            let d = anchor[ay * w + ax] as i64 - neigh[sy * w + sx] as i64;
            sad += d.unsigned_abs();
        }
    }
    sad
}

/// Best integer-pel MV `(row,col)` aligning `neigh`'s block to `anchor`'s (full search
/// ±`range`, ties toward the shorter vector).
#[allow(clippy::too_many_arguments)]
fn tf_search(
    anchor: &[u16],
    neigh: &[u16],
    w: usize,
    h: usize,
    bx: usize,
    by: usize,
    bs: usize,
    range: i32,
) -> (i32, i32) {
    let mut best = (0i32, 0i32);
    let mut best_sad = tf_sad(anchor, neigh, w, h, bx, by, bs, 0, 0);
    for mvr in -range..=range {
        for mvc in -range..=range {
            let sad = tf_sad(anchor, neigh, w, h, bx, by, bs, mvr, mvc);
            let shorter = mvr.abs() + mvc.abs() < best.0.abs() + best.1.abs();
            if sad < best_sad || (sad == best_sad && shorter) {
                best_sad = sad;
                best = (mvr, mvc);
            }
        }
    }
    best
}

/// Mean absolute consecutive-frame luma difference over a group (subsampled) —
/// a cheap motion proxy. High motion ⇒ the hidden ALT-REF pays off (temporal
/// bracketing helps prediction); low motion ⇒ the ARF is pure overhead. Measured
/// crossover ≈ 8 (akiyo 0.4 / foreman 5.2 code plain; bus 21 / crowd_run 12 use ARF).
fn group_motion(frames: &[([Vec<u16>; 3], u32, u32)]) -> f64 {
    if frames.len() < 2 {
        return 0.0;
    }
    let (mut tot, mut cnt) = (0u64, 0u64);
    for w in frames.windows(2) {
        let (a, b) = (&w[0].0[0], &w[1].0[0]);
        let n = a.len().min(b.len());
        let mut i = 0;
        while i < n {
            tot += (a[i] as i64 - b[i] as i64).unsigned_abs();
            cnt += 1;
            i += 37; // stride-subsample for speed
        }
    }
    tot as f64 / cnt.max(1) as f64
}

/// Simplified libvpx temporal filter (`vp9_temporal_filter`): denoise the ALT-REF
/// `anchor` by blending each `neighbor` — motion-compensated to the anchor — with a
/// per-pixel weight that decays with the aligned difference, so static regions are
/// averaged (noise cancels) while moving/occluded regions keep the anchor. Luma drives
/// the 16×16 motion; chroma reuses the halved MV over the co-located 8×8 block. The
/// result is a cleaner long-term reference: on noisy content it both codes cheaper and
/// predicts the group's P frames better. Purely a source transform — no bitstream
/// effect, so the encode/decode path is unchanged.
fn temporal_filter(
    anchor: &[Vec<u16>; 3],
    neighbors: &[&[Vec<u16>; 3]],
    cw: usize,
    ch: usize,
    strength: f64,
) -> [Vec<u16>; 3] {
    const BS: usize = 16;
    const RANGE: i32 = 8;
    const MAX_W: f64 = 16.0;
    let (cwc, chc) = (cw / 2, ch / 2);
    // Post-alignment a matched pixel differs only by noise, so the kernel must stay
    // wide enough that such neighbors get real weight (else the anchor dominates and
    // nothing is averaged). `strength` scales the Gaussian σ; occlusions still fall off.
    let sigma = (strength * 6.0).max(1.0);
    let two_sig2 = 2.0 * sigma * sigma;
    let weight = |mc: u16, a: u16| -> f64 {
        let d = mc as f64 - a as f64;
        MAX_W * (-(d * d) / two_sig2).exp()
    };
    let mut out = [anchor[0].clone(), anchor[1].clone(), anchor[2].clone()];
    let mut by = 0;
    while by < ch {
        let mut bx = 0;
        while bx < cw {
            // Luma accumulators for this 16×16 block (anchor weighted MAX_W).
            let mut acc = [0f64; BS * BS];
            let mut wsum = [0f64; BS * BS];
            // Chroma accumulators for the co-located 8×8 block, per plane.
            let mut cacc = [[0f64; 64]; 2];
            let mut cwsum = [[0f64; 64]; 2];
            for dy in 0..BS {
                for dx in 0..BS {
                    let (ax, ay) = (bx + dx, by + dy);
                    if ax < cw && ay < ch {
                        acc[dy * BS + dx] = anchor[0][ay * cw + ax] as f64 * MAX_W;
                        wsum[dy * BS + dx] = MAX_W;
                    }
                }
            }
            let (cbx, cby) = (bx / 2, by / 2);
            for dy in 0..8 {
                for dx in 0..8 {
                    let (ax, ay) = (cbx + dx, cby + dy);
                    if ax < cwc && ay < chc {
                        for p in 0..2 {
                            cacc[p][dy * 8 + dx] = anchor[p + 1][ay * cwc + ax] as f64 * MAX_W;
                            cwsum[p][dy * 8 + dx] = MAX_W;
                        }
                    }
                }
            }
            for nb in neighbors {
                let (mvr, mvc) = tf_search(&anchor[0], &nb[0], cw, ch, bx, by, BS, RANGE);
                for dy in 0..BS {
                    for dx in 0..BS {
                        let (ax, ay) = (bx + dx, by + dy);
                        if ax >= cw || ay >= ch {
                            continue;
                        }
                        let sx = (ax as i32 + mvc).clamp(0, cw as i32 - 1) as usize;
                        let sy = (ay as i32 + mvr).clamp(0, ch as i32 - 1) as usize;
                        let mc = nb[0][sy * cw + sx];
                        let wgt = weight(mc, anchor[0][ay * cw + ax]);
                        acc[dy * BS + dx] += mc as f64 * wgt;
                        wsum[dy * BS + dx] += wgt;
                    }
                }
                let (cmr, cmc) = (mvr / 2, mvc / 2);
                for dy in 0..8 {
                    for dx in 0..8 {
                        let (ax, ay) = (cbx + dx, cby + dy);
                        if ax >= cwc || ay >= chc {
                            continue;
                        }
                        let sx = (ax as i32 + cmc).clamp(0, cwc as i32 - 1) as usize;
                        let sy = (ay as i32 + cmr).clamp(0, chc as i32 - 1) as usize;
                        for p in 0..2 {
                            let mc = nb[p + 1][sy * cwc + sx];
                            let wgt = weight(mc, anchor[p + 1][ay * cwc + ax]);
                            cacc[p][dy * 8 + dx] += mc as f64 * wgt;
                            cwsum[p][dy * 8 + dx] += wgt;
                        }
                    }
                }
            }
            for dy in 0..BS {
                for dx in 0..BS {
                    let (ax, ay) = (bx + dx, by + dy);
                    if ax < cw && ay < ch {
                        out[0][ay * cw + ax] =
                            (acc[dy * BS + dx] / wsum[dy * BS + dx]).round() as u16;
                    }
                }
            }
            for dy in 0..8 {
                for dx in 0..8 {
                    let (ax, ay) = (cbx + dx, cby + dy);
                    if ax < cwc && ay < chc {
                        for p in 0..2 {
                            out[p + 1][ay * cwc + ax] =
                                (cacc[p][dy * 8 + dx] / cwsum[p][dy * 8 + dx]).round() as u16;
                        }
                    }
                }
            }
            bx += BS;
        }
        by += BS;
    }
    out
}

/// One coded VP9 packet out of the encoder. `data` is a raw coded frame (or a
/// superframe when a hidden ALT-REF rides with the next shown frame) — exactly
/// the payload an IVF/WebM muxer stores per packet. Packets are emitted in
/// coded order; `pts` is currently always `None` (callers timestamp packets by
/// order). `keyframe` is derived from the leading frame-type bits.
#[derive(Debug, Clone)]
pub struct EncodedPacket {
    pub data: Vec<u8>,
    pub pts: Option<i64>,
    pub keyframe: bool,
}

/// Wrap coded bytes as an [`EncodedPacket`]. Key frame iff the first coded
/// frame's `show_existing_frame` and `frame_type` bits are both 0 (this
/// encoder emits profile 0, where they sit at bits 3..2 of byte 0).
fn packet(data: Vec<u8>) -> EncodedPacket {
    let keyframe = data.first().is_some_and(|&b| b & 0x0c == 0);
    EncodedPacket {
        data,
        pts: None,
        keyframe,
    }
}

/// Configuration for [`Vp9Encoder::configure`] — the native mirror of the
/// ffmpeg-style options the rff adapter parses (`-qp`/`-crf`/`-q`, `-b:v`+`-r`,
/// `-lag`, `arnr-strength`, `dispatch-budget`, `-pass 2`/`twopass=1`,
/// `-cpu-used`/`-speed`). `None` leaves the encoder's default untouched.
#[derive(Debug, Clone, Default)]
pub struct Vp9EncoderConfig {
    /// VP9 qindex directly (0..255); qindex 0 would mean lossless.
    pub qindex: Option<u32>,
    /// A 0..63 quality mapped onto the qindex (`crf * 4`, clamped away from 0).
    pub crf: Option<u32>,
    /// Alias for a direct qindex (checked after `qindex` and `crf`).
    pub q: Option<u32>,
    /// Target bitrate in bits/sec — engages frame-level rate control.
    pub bitrate_bps: Option<f64>,
    /// Frame rate for the per-frame bit budget (values ≤ 0 ignored; default 30).
    pub fps: Option<f64>,
    /// ALT-REF lookahead group size (0 ⇒ off; capped at 32).
    pub lag: Option<usize>,
    /// ALT-REF temporal-filter strength (0 disables; clamped at 0).
    pub arnr_strength: Option<f64>,
    /// Per-frame decision-pass time budget in milliseconds for the
    /// content-adaptive dispatch controller; ≤ 0 disables (overriding the
    /// `VP9_DISPATCH_BUDGET` env default).
    pub dispatch_budget_ms: Option<f64>,
    /// Two-pass rate control (buffer the clip, probe, encode at a solved q).
    pub two_pass: bool,
    /// Speed preset, 0 best..6 fastest (values above 6 are clamped).
    pub speed: Option<u32>,
}

/// In-house VP9 encoder: the first frame is a key frame, subsequent frames are
/// P frames (ZEROMV, single-reference LAST) against the previous reconstruction.
pub struct Vp9Encoder {
    qindex: u32,
    packets: VecDeque<EncodedPacket>,
    eof: bool,
    /// Previous frame's reconstruction (coded size) + its dimensions, used as the
    /// LAST reference; `None` ⇒ the next frame is coded as a key frame.
    reference: Option<([Vec<u16>; 3], u32, u32)>,
    /// The most recent key frame's reconstruction (+ dims), installed as the GOLDEN
    /// reference on every P frame (a stable long-term anchor the per-block RD may pick).
    golden: Option<([Vec<u16>; 3], u32, u32)>,
    /// Active when `-b:v` sets a target bitrate; overrides the fixed `qindex`.
    rc: Option<RateCtl>,
    /// ALT-REF lookahead group size (`-lag N`, 0 ⇒ off). When >1, frames are buffered
    /// and each group is coded key/P… + a hidden future ALT-REF shown last.
    lag: usize,
    /// Two-pass rate control (`-pass 2` / `twopass=1` with `-b:v`): buffer the clip,
    /// probe its size, then encode at a global constant qindex that hits the target.
    twopass: bool,
    /// ALT-REF temporal-filter strength (`arnr-strength`, 0 ⇒ off). Denoises the hidden
    /// ALT-REF source by blending motion-compensated neighbor frames.
    tf_strength: f64,
    /// Buffered input frames (coded-size YUV) awaiting an ALT-REF group or two-pass flush.
    lookahead: VecDeque<([Vec<u16>; 3], u32, u32)>,
    /// Physical VP9 reference slots (0..2 used) for the cross-GOP ALT-REF chain, and the
    /// ping-pong assignment of GOLDEN (previous ARF) / ALTREF (current ARF) slots. Only
    /// the first group is key-started; later groups chain through these slots.
    slots: [Option<[Vec<u16>; 3]>; 3],
    golden_slot: usize,
    arf_slot: usize,
    /// Frame dims of the current chain; a change forces a fresh key-started group.
    group_dims: Option<(u32, u32)>,
    /// F3 context chaining (`VP9_CHAIN`): a companion copy of our own conformant
    /// decoder tracks the adapted FrameContext + prev-frame MVs; each new P frame
    /// chains onto them (error_resilient=0). Bit-exact adaptation by construction.
    chain: bool,
    companion: Option<Box<crate::Vp9Decoder>>,
    /// Previous chained frame was a shown, same-size P (the decoder's
    /// `use_prev_mvs` precondition).
    chain_prev_p: bool,
    /// Reconstruction of the most recent slotted-coded frame (for the recon oracle).
    last_recon: Option<[Vec<u16>; 3]>,
    /// Frame counter for the `VP9_RECON_CHECK` oracle.
    recon_check_n: usize,
    /// Persistent oracle decoder (holds the reference chain).
    recon_check_dec: Option<Box<crate::Vp9Decoder>>,
    /// Active while coding an ALT-REF group with chaining on.
    group_chain: bool,
    /// Active during two-pass PASS 2: chaining engages (pass 1 is a throwaway
    /// probe that must not touch the companion).
    pass2_chaining: bool,
    /// Content-adaptive-lag motion threshold: ALT-REF groups below this mean
    /// inter-frame luma diff are coded as plain chained P frames instead (the ARF
    /// is overhead on low-motion content). `VP9_LAG_MOTION_THRESH` overrides.
    lag_motion_thresh: f64,
    /// ARF q scale (`VP9_ARF_QSCALE`): the hidden ALT-REF is a long-term reference,
    /// so libvpx codes it at LOWER q (higher quality) — the extra bits pay off across
    /// every P frame that predicts from it. 1.0 = code at the frame q (the old,
    /// net-loss behavior); <1 = the arf boost.
    arf_qscale: f64,
    /// Encoder speed preset (`-cpu-used`/`-speed`, 0 = best quality/slowest .. 4 =
    /// fastest). Higher levels progressively drop RD tools (sub-8×8, forward-prob
    /// two-pass, trellis, tx-search) for a graceful quality→speed trade. See
    /// [`FrameEncoder::set_speed`](super::frameenc::FrameEncoder::set_speed).
    speed: u32,
    /// Lever 2 — time-budget controller target: the per-frame decision-pass wall
    /// time (µs) the content-adaptive dispatch should hold. `Some` engages the
    /// controller (`VP9_DISPATCH_BUDGET=<ms>`); then `dispatch_q_state` is
    /// steered per frame to hit this, making per-frame
    /// encode time content-INVARIANT (bus/mobile route more to the variance
    /// partition, akiyo less) rather than merely flatter. `None` = fixed-q dispatch.
    dispatch_budget_us: Option<u64>,
    /// Lever 2 — the controller's live route fraction, persisted across frames
    /// (a `FrameEncoder` is per-frame, so the state lives here). Fed into each
    /// frame via [`FrameEncoder::set_dispatch_q`] and nudged after by the measured
    /// decision time. Seeded at 0.5; the integral update drives steady-state error
    /// to zero when the budget is reachable within `[0, 0.95]`.
    dispatch_q_state: f64,
}

impl Default for Vp9Encoder {
    fn default() -> Vp9Encoder {
        Vp9Encoder {
            qindex: 64, // a middle-quality default
            packets: VecDeque::new(),
            eof: false,
            reference: None,
            golden: None,
            rc: None,
            lag: 0,
            twopass: false,
            tf_strength: 3.0, // default ARNR strength; only used when lag>1
            lookahead: VecDeque::new(),
            slots: [None, None, None],
            golden_slot: 1,
            arf_slot: 2,
            group_dims: None,
            // Default speed preset. 3 = the balanced default (2026-07-18): on top
            // of speed 2 it adds the libvpx-cpu3-derived speed features (split
            // early-termination, adaptive mode-skip, 64×64 partition gate) for
            // +1.02% BD-rate at ~1.3× faster — a clean Pareto step. Speed 2 (the
            // prior default, byte-identical to the pinned corpus) and speed 0 (the
            // exhaustive-RD anchor) remain selectable. `-speed`/`-cpu-used N` overrides.
            speed: 3,
            chain: std::env::var("VP9_NO_CHAIN").is_err(), // F3: corpus-gated −11.15% BD
            companion: None,
            chain_prev_p: false,
            last_recon: None,
            recon_check_n: 0,
            recon_check_dec: None,
            group_chain: false,
            pass2_chaining: false,
            lag_motion_thresh: std::env::var("VP9_LAG_MOTION_THRESH")
                .ok()
                .and_then(|v| v.parse().ok())
                .unwrap_or(8.0),
            // 0.5 = the ARF q-boost (measured -8.87% BD vs plain IPPP on 1080p
            // motion). 1.0 restores the old un-boosted, net-loss ARF.
            arf_qscale: std::env::var("VP9_ARF_QSCALE")
                .ok()
                .and_then(|v| v.parse().ok())
                .unwrap_or(0.5),
            dispatch_budget_us: std::env::var("VP9_DISPATCH_BUDGET")
                .ok()
                .and_then(|v| v.parse::<f64>().ok())
                .filter(|ms| *ms > 0.0)
                .map(|ms| (ms * 1000.0) as u64),
            dispatch_q_state: 0.5,
        }
    }
}

/// Copy a `u8` plane (display size) into a coded-size `u16` buffer, replicating
/// the last in-frame row/column into the padding (libvpx `extend_frame`).
fn to_coded(plane: &[u8], stride: usize, dw: usize, dh: usize, cw: usize, ch: usize) -> Vec<u16> {
    let mut out = vec![0u16; cw * ch];
    for y in 0..ch {
        let sy = y.min(dh - 1);
        for x in 0..cw {
            let sx = x.min(dw - 1);
            out[y * cw + x] = plane[sy * stride + sx] as u16;
        }
    }
    out
}

impl Vp9Encoder {
    /// Apply a [`Vp9EncoderConfig`]. `None` fields leave the current value
    /// untouched, so this mirrors the incremental ffmpeg-option semantics.
    pub fn configure(&mut self, cfg: &Vp9EncoderConfig) -> Result<()> {
        // `qindex` sets the VP9 qindex directly (0..255); `crf` maps a 0..63
        // quality onto it. qindex 0 would mean lossless — clamp away from it.
        if let Some(qp) = cfg.qindex {
            self.qindex = qp.min(255);
        } else if let Some(crf) = cfg.crf {
            self.qindex = (crf * 4).clamp(1, 255);
        } else if let Some(q) = cfg.q {
            self.qindex = q.min(255);
        }
        // A target bitrate engages rate control toward `bitrate_bps` bits/sec.
        // The per-frame budget needs a frame rate; honour `fps`, else assume 30.
        if let Some(bps) = cfg.bitrate_bps {
            let fps = cfg.fps.filter(|&f| f > 0.0).unwrap_or(30.0);
            self.rc = Some(RateCtl {
                target_per_frame: bps / fps,
                q: self.qindex as f64,
            });
        }
        // `lag` (aka lag-in-frames) turns on ALT-REF lookahead with a group size of
        // `N` (each group is coded key/P… + one hidden future ALT-REF shown last).
        if let Some(lag) = cfg.lag {
            self.lag = lag.min(32);
        }
        // ALT-REF temporal-filter strength (`arnr-strength`, 0 disables).
        if let Some(s) = cfg.arnr_strength {
            self.tf_strength = s.max(0.0);
        }
        // `dispatch_budget_ms` engages the content-adaptive time-budget controller:
        // per frame, the variance-partition route fraction is steered to hold the
        // decision pass near MS milliseconds — a per-frame latency/throughput target
        // that caps encode time on complex content while easy content stays full-RD.
        // 0/absent = off. Overrides the `VP9_DISPATCH_BUDGET` env default.
        if let Some(ms) = cfg.dispatch_budget_ms {
            self.dispatch_budget_us = if ms > 0.0 {
                Some((ms * 1000.0) as u64)
            } else {
                None
            };
        }
        // Two-pass: ffmpeg's `-pass 2` (`-pass 1` is a discardable analysis pass we
        // fold into pass 2 internally) or an explicit `twopass=1`. Needs a bitrate.
        if cfg.two_pass {
            self.twopass = true;
        }
        // Speed preset: `cpu-used`/`speed` (0 best..4 fastest), à la libvpx.
        // Higher = progressively drop RD tools for a graceful quality→speed trade.
        if let Some(sp) = cfg.speed {
            // 0–3 = the RD-quality ladder; 4–6 = the realtime rungs (content-adaptive
            // variance-partition dispatcher, rising route fraction). See `set_speed`.
            self.speed = sp.min(6);
        }
        Ok(())
    }

    /// Feed one 8-bit YUV 4:2:0 frame: `planes`/`strides` are the display-size
    /// Y, U, V rows (chroma dimensions rounded up). Packets may become available
    /// immediately or only after later frames / [`flush`](Vp9Encoder::flush)
    /// (lookahead and two-pass buffer internally).
    pub fn push_frame(
        &mut self,
        planes: [&[u8]; 3],
        strides: [usize; 3],
        width: u32,
        height: u32,
    ) -> Result<()> {
        let (w, h) = (width as usize, height as usize);
        let mi_cols = (w + 7) >> 3;
        let mi_rows = (h + 7) >> 3;
        let (cw, ch) = (mi_cols * 8, mi_rows * 8);
        let (cwc, chc) = (cw / 2, ch / 2);
        let (dwc, dhc) = (w.div_ceil(2), h.div_ceil(2));

        let y = to_coded(planes[0], strides[0], w, h, cw, ch);
        let u = to_coded(planes[1], strides[1], dwc, dhc, cwc, chc);
        let v = to_coded(planes[2], strides[2], dwc, dhc, cwc, chc);

        if self.twopass {
            // Two-pass needs the whole clip before it can solve for the qindex — buffer.
            self.lookahead.push_back(([y, u, v], width, height));
            return Ok(());
        }
        if self.lag > 1 {
            // ALT-REF lookahead: buffer, and emit a group once it is `lag` frames long.
            self.lookahead.push_back(([y, u, v], width, height));
            if self.lookahead.len() >= self.lag {
                let group: Vec<_> = self.lookahead.drain(..).collect();
                self.code_altref_group(group);
            }
            return Ok(());
        }

        // Default path: code immediately as KEY (first frame / resize) or P (+ GOLDEN).
        let bytes = self.code_frame([y, u, v], width, height, None);
        self.packets.push_back(packet(bytes));
        Ok(())
    }

    /// Pull the next coded packet. [`Error::Again`] means push more frames (or
    /// [`flush`](Vp9Encoder::flush)); [`Error::Eof`] means fully drained.
    pub fn next_packet(&mut self) -> Result<EncodedPacket> {
        if let Some(p) = self.packets.pop_front() {
            Ok(p)
        } else if self.eof {
            Err(Error::Eof)
        } else {
            Err(Error::Again)
        }
    }

    /// Signal end of input: codes any buffered lookahead / two-pass group, after
    /// which [`next_packet`](Vp9Encoder::next_packet) drains to [`Error::Eof`].
    pub fn flush(&mut self) {
        if !self.lookahead.is_empty() {
            let group: Vec<_> = self.lookahead.drain(..).collect();
            if self.twopass {
                self.two_pass_encode(group);
            } else {
                self.code_altref_group(group);
            }
        }
        self.eof = true;
    }
}

impl Vp9Encoder {
    /// Pick the qindex for the next frame (rate control, else the fixed value).
    fn next_qindex(&self) -> u32 {
        match &self.rc {
            Some(rc) => rc.qindex(),
            None => self.qindex,
        }
    }

    /// Lever 2 (time-budget controller) — feed the current adapted route fraction
    /// into a frame encoder before it encodes. No-op unless the budget is engaged.
    fn budget_apply(&self, enc: &mut FrameEncoder) {
        if self.dispatch_budget_us.is_some() {
            enc.set_dispatch_q(self.dispatch_q_state);
        }
    }

    /// Lever 2 — after a frame encodes, steer the route fraction toward the target
    /// decision-pass time from the measured cost. The key frame's intra cost is
    /// anomalous, so it feeds q in but never drives the controller.
    fn budget_update(&mut self, enc: &FrameEncoder, is_key: bool) {
        if let Some(budget) = self.dispatch_budget_us {
            if !is_key {
                // Integral controller on q: it accumulates the normalized error, so
                // steady-state error → 0 when the target is reachable within [0,0.95].
                let err = (enc.decision_us() as f64 - budget as f64) / budget as f64;
                const K: f64 = 0.4; // gain: settles in a few frames without ringing
                let prev = self.dispatch_q_state;
                self.dispatch_q_state = (prev + K * err).clamp(0.0, 0.95);
                if std::env::var("VP9_BUDGET_DEBUG").is_ok() {
                    eprintln!(
                        "BUDGET decision={:.1}ms target={:.1}ms q {:.3}->{:.3}",
                        enc.decision_us() as f64 / 1000.0,
                        budget as f64 / 1000.0,
                        prev,
                        self.dispatch_q_state
                    );
                }
            }
        }
    }

    /// Code one shown KEY or P frame at the single-pass qindex, feeding the rate
    /// controller the bits it spent.
    fn code_frame(
        &mut self,
        coded: [Vec<u16>; 3],
        w: u32,
        h: u32,
        altref: Option<&[Vec<u16>; 3]>,
    ) -> Vec<u8> {
        let q = self.next_qindex();
        let bytes = self.code_frame_q(coded, w, h, altref, q);
        if let Some(rc) = &mut self.rc {
            rc.update(bytes.len() as f64 * 8.0);
        }
        bytes
    }

    /// Code one shown KEY or P frame at an *explicit* qindex, chaining the reference:
    /// a P installs the GOLDEN anchor (and the group's ALT-REF, if any), refreshes
    /// LAST, and updates the chain. Returns the coded bytes (no rate-control feedback —
    /// the two-pass driver measures bits itself).
    fn code_frame_q(
        &mut self,
        coded: [Vec<u16>; 3],
        w: u32,
        h: u32,
        altref: Option<&[Vec<u16>; 3]>,
        qindex: u32,
    ) -> Vec<u8> {
        let reference = match self.reference.take() {
            Some((planes, rw, rh)) if rw == w && rh == h => Some(planes),
            _ => None,
        };
        let is_key = reference.is_none();
        let mut enc = FrameEncoder::new(w, h, qindex, coded, reference);
        enc.set_speed(self.speed);
        self.budget_apply(&mut enc);
        let chaining = self.chain && ((self.lag == 0 && !self.twopass) || self.pass2_chaining);
        if chaining && !is_key {
            if let Some(c) = &self.companion {
                let mvs = if self.chain_prev_p {
                    c.prev_mvs.clone()
                } else {
                    None
                };
                enc.set_chain(c.frame_contexts[0].clone(), mvs);
            }
        }
        if !is_key {
            if let Some((g, gw, gh)) = &self.golden {
                if *gw == w && *gh == h {
                    enc.set_golden(g.clone());
                }
            }
            if let Some(a) = altref {
                enc.set_altref(a.clone());
            }
        }
        let bytes = enc.encode_frame();
        self.budget_update(&enc, is_key);
        if chaining {
            let comp = self
                .companion
                .get_or_insert_with(|| Box::new(crate::Vp9Decoder::default()));
            let ok = comp
                .push(&bytes, None)
                .and_then(|_| comp.next_frame())
                .is_ok();
            if ok {
                self.chain_prev_p = !is_key;
            } else {
                // Companion desync: fail safe back to independent frames.
                self.chain = false;
                self.companion = None;
                self.chain_prev_p = false;
            }
        }
        let recon = enc.recon_owned();
        // Encoder/decoder reconstruction oracle (`VP9_RECON_CHECK=1`). The encoder feeds
        // its OWN `recon` forward as the next frame's reference, so if that ever differs
        // from what a decoder produces from the emitted bytes, prediction diverges and
        // the error compounds frame over frame — the worst failure mode this encoder
        // has, and invisible to both the decoder conformance vectors (they test the
        // decoder against libvpx streams) and a byte-diff (the bitstream is perfectly
        // self-consistent; it is the encoder's belief about it that is wrong).
        //
        // Our decoder is 315/315 bit-exact against libvpx, so it is a trustworthy oracle.
        if std::env::var("VP9_RECON_CHECK").is_ok() {
            self.recon_check(&bytes, &recon, w, h);
        }
        if is_key {
            self.golden = Some((recon.clone(), w, h));
        }
        self.reference = Some((recon, w, h));
        bytes
    }

    /// Feed one coded frame to the oracle decoder and, if it emits a picture, diff that
    /// picture against the reconstruction the encoder believes it produced.
    ///
    /// `expect = None` means "this frame is HIDDEN (an ALT-REF) — advance the decoder's
    /// state but do not compare", because a hidden frame emits no picture. The ARF is
    /// verified later, when `show_existing_frame` displays it and `expect` is its recon.
    /// Without this the whole ALT-REF path was invisible to the oracle: `recon_check` ran
    /// only from `code_frame_q`, and the lag>1 path goes through `code_altref_group`.
    fn recon_verify(&mut self, bytes: &[u8], expect: Option<&[Vec<u16>; 3]>, w: u32, h: u32) {
        if std::env::var("VP9_RECON_CHECK").is_err() {
            return;
        }
        match expect {
            Some(r) => self.recon_check(bytes, r, w, h),
            None => {
                let dec = self
                    .recon_check_dec
                    .get_or_insert_with(|| Box::new(crate::Vp9Decoder::default()));
                let _ = dec.push(bytes, None);
                while dec.next_frame().is_ok() {}
                eprintln!("RECON_CHECK frame {}: hidden (fed, not compared)", self.recon_check_n);
                self.recon_check_n += 1;
            }
        }
    }

    /// Decode `bytes` with a scratch decoder and compare against the encoder's own
    /// reconstruction. Reports the first differing sample per frame; counts frames.
    fn recon_check(&mut self, bytes: &[u8], recon: &[Vec<u16>; 3], w: u32, h: u32) {
        // PERSISTENT across frames. A fresh decoder per frame holds no reference
        // buffers, so every inter frame decodes to garbage and the check reports a
        // 100% mismatch that says nothing about the encoder.
        let dec = self
            .recon_check_dec
            .get_or_insert_with(|| Box::new(crate::Vp9Decoder::default()));
        let got = dec.push(bytes, None).ok().and_then(|_| dec.next_frame().ok());
        let Some(vf) = got else {
            eprintln!("RECON_CHECK frame {}: DECODE FAILED", self.recon_check_n);
            self.recon_check_n += 1;
            return;
        };
        let mut bad = 0usize;
        let mut first = None;
        for p in 0..3 {
            let (pw, ph) = if p == 0 {
                (w as usize, h as usize)
            } else {
                ((w as usize).div_ceil(2), (h as usize).div_ceil(2))
            };
            // The encoder's recon is stored at the CODED size (mi-aligned); the decoded
            // frame is the display crop, so compare only the visible region row by row.
            let cw = recon[p].len() / ph.max(1);
            for y in 0..ph {
                for x in 0..pw {
                    let e = recon[p].get(y * cw + x).copied().unwrap_or(0) as u8;
                    let d = vf.planes[p].get(y * vf.strides[p] + x).copied().unwrap_or(0);
                    if e != d {
                        bad += 1;
                        if first.is_none() {
                            first = Some((p, x, y, e, d));
                        }
                    }
                }
            }
        }
        if bad > 0 {
            let (p, x, y, e, d) = first.unwrap();
            eprintln!(
                "RECON_CHECK frame {}: {} samples DIVERGE (first plane{} ({},{}) enc={} dec={})",
                self.recon_check_n, bad, p, x, y, e, d
            );
            // Level 2: a 64x64-superblock map of where luma diverges, so the pattern
            // (tile column, SB column, scattered, edge) is visible instead of guessed.
            if std::env::var("VP9_RECON_CHECK").ok().as_deref() == Some("2") {
                let (pw, ph) = (w as usize, h as usize);
                let cw = recon[0].len() / ph.max(1);
                let (sbx, sby) = (pw.div_ceil(64), ph.div_ceil(64));
                let mut map = vec![0u32; sbx * sby];
                for yy in 0..ph {
                    for xx in 0..pw {
                        let e = recon[0].get(yy * cw + xx).copied().unwrap_or(0) as u8;
                        let dv = vf.planes[0].get(yy * vf.strides[0] + xx).copied().unwrap_or(0);
                        if e != dv {
                            map[(yy / 64) * sbx + xx / 64] += 1;
                        }
                    }
                }
                eprintln!("  SB map ({}x{} superblocks, '.'=clean, #=count/410):", sbx, sby);
                for r in 0..sby {
                    let row: String = (0..sbx)
                        .map(|c| match map[r * sbx + c] {
                            0 => '.',
                            n if n < 410 => '1',
                            n if n < 1640 => '2',
                            n if n < 3277 => '3',
                            _ => '#',
                        })
                        .collect();
                    eprintln!("   {r:2} {row}");
                }
            }
        } else {
            eprintln!("RECON_CHECK frame {}: ok", self.recon_check_n);
        }
        self.recon_check_n += 1;
    }

    /// Two-pass rate control: pass 1 codes every buffered frame at a probe qindex to
    /// measure the clip's true size, then a single global qindex is derived (from the
    /// `bits ≈ 2^(-q/Q_PER_2X)` model) that lands the pass-2 total on the target — a
    /// constant-quality encode that hits the size, without single-pass's startup
    /// transient or per-frame swings. Key frames get a small qindex bonus (they anchor
    /// the group). Both passes reset the reference chain (their recon differs by q).
    fn two_pass_encode(&mut self, frames: Vec<([Vec<u16>; 3], u32, u32)>) {
        let n = frames.len();
        if n == 0 {
            return;
        }
        let target_per_frame = self.rc.as_ref().map(|rc| rc.target_per_frame);
        // No target ⇒ nothing to solve for; fall back to fixed-q coding.
        let Some(tpf) = target_per_frame else {
            for (c, w, h) in frames {
                let b = self.code_frame_q(c, w, h, None, self.qindex);
                self.packets.push_back(packet(b));
            }
            return;
        };
        const Q_PROBE: u32 = 128;
        const Q_PER_2X: f64 = 100.0; // qindex step that ~halves the coded size (measured)
        const KEY_BONUS: f64 = 16.0; // key frames coded a little finer

        // Pass 1: probe the true size at a fixed qindex.
        self.reference = None;
        self.golden = None;
        let mut probe_bits = 0.0f64;
        for (c, w, h) in &frames {
            let b = self.code_frame_q(c.clone(), *w, *h, None, Q_PROBE);
            probe_bits += b.len() as f64 * 8.0;
        }
        let target_total = tpf * n as f64;
        let ratio = (probe_bits / target_total).clamp(1.0 / 32.0, 32.0);
        let q2 = (Q_PROBE as f64 + Q_PER_2X * ratio.log2()).clamp(4.0, 220.0);
        let q_key = (q2 - KEY_BONUS).clamp(4.0, 220.0).round() as u32;
        let q_inter = q2.round() as u32;
        if std::env::var("VP9_2PASS_DBG").is_ok() {
            eprintln!(
                "2pass: probe_bits={probe_bits:.0} target_total={target_total:.0} ratio={ratio:.3} q2={q2:.1} q_key={q_key} q_inter={q_inter}"
            );
        }

        // Pass 2: emit at the derived qindex (fresh reference chain). Context
        // chaining engages here (pass 1 was a throwaway probe): fresh companion,
        // each frame coded against the previous frame's adapted context/MVs.
        self.reference = None;
        self.golden = None;
        self.companion = None;
        self.chain_prev_p = false;
        self.pass2_chaining = self.chain;
        for (i, (c, w, h)) in frames.into_iter().enumerate() {
            let q = if i == 0 { q_key } else { q_inter };
            let b = self.code_frame_q(c, w, h, None, q);
            self.packets.push_back(packet(b));
        }
        self.pass2_chaining = false;
    }

    /// Code a display-order group as an ALT-REF GOP. The **first** group is key-started
    /// (KEY fills all slots); **subsequent** groups chain — no key frame, they predict
    /// from the previous group's reconstructed frames via the physical ref slots.
    ///
    /// Slot ping-pong (LAST=slot0 rolls; GOLDEN=previous ARF; ALTREF=new ARF): each
    /// group codes the hidden ALT-REF (F_last) into `arf_slot`, its shown P frames
    /// (which may reference LAST/GOLDEN/ALTREF) refreshing slot0, then a
    /// `show_existing_frame(arf_slot)`. GOLDEN and ALTREF slots swap for the next group,
    /// so the just-coded ARF becomes the next group's GOLDEN anchor.
    /// Chain args for the next group frame from the persistent companion decoder.
    fn chain_args(
        &self,
    ) -> Option<(
        crate::decode::FrameContext,
        Option<std::sync::Arc<Vec<crate::mv::MvRef>>>,
    )> {
        let c = self.companion.as_ref()?;
        let temporal_ok = !c.last_intra_only && c.last_show_frame && !c.last_frame_key;
        let mvs = if temporal_ok { c.prev_mvs.clone() } else { None };
        Some((c.frame_contexts[0].clone(), mvs))
    }

    /// Feed one emitted coded frame to the persistent companion, advancing its
    /// adapted-context / temporal-MV / ref-slot state. A hidden ALT-REF decodes
    /// then yields `Again` (no shown output) - not a desync. A hard error fails
    /// safe to independent frames.
    fn companion_feed(&mut self, bytes: &[u8]) {
        if !self.group_chain {
            return;
        }
        let comp = self
            .companion
            .get_or_insert_with(|| Box::new(crate::Vp9Decoder::default()));
        if comp.push(bytes, None).is_err() {
            self.group_chain = false;
            self.chain = false;
            self.companion = None;
            return;
        }
        loop {
            match comp.next_frame() {
                Ok(_) => {}
                Err(Error::Again) | Err(Error::Eof) => break,
                Err(_) => {
                    self.group_chain = false;
                    self.chain = false;
                    self.companion = None;
                    return;
                }
            }
        }
    }

    fn code_altref_group(&mut self, frames: Vec<([Vec<u16>; 3], u32, u32)>) {
        let n = frames.len();
        if n == 0 {
            return;
        }
        let (w, h) = (frames[0].1, frames[0].2);
        // The first group, or any resize, restarts with a key frame + fresh slots.
        let need_key = self.slots[0].is_none() || self.group_dims != Some((w, h));
        self.group_dims = Some((w, h));
        self.group_chain = self.chain && !self.twopass;

        // Content-adaptive lag: a hidden ALT-REF pays off on high-motion content
        // (bus -27% BD) but is pure overhead on static/low-motion (akiyo +31%),
        // so code low-motion groups as plain chained P frames. Also the fallback
        // for groups too short for an ARF.
        let plain = n <= 2 || group_motion(&frames) < self.lag_motion_thresh;
        if plain {
            for (i, (c, fw, fh)) in frames.into_iter().enumerate() {
                let b = if need_key && i == 0 {
                    self.code_key_slotted(c, fw, fh)
                } else {
                    self.code_p_slotted(c, fw, fh, false)
                };
                self.companion_feed(&b);
                let r = self.last_recon.take();
                self.recon_verify(&b, r.as_ref(), fw, fh);
                self.packets.push_back(packet(b));
            }
            return;
        }

        // First shown frame of this group; a key-started group consumes F0 as the key.
        let mut i0 = 0;
        if need_key {
            self.golden_slot = 1;
            self.arf_slot = 2;
            let (c0, kw, kh) = frames[0].clone();
            let kb = self.code_key_slotted(c0, kw, kh);
            self.companion_feed(&kb);
            let r = self.last_recon.clone();
            self.recon_verify(&kb, r.as_ref(), kw, kh);
            self.packets.push_back(packet(kb));
            i0 = 1;
        }

        // Hidden ALT-REF = the group's last frame, temporally filtered with the frames
        // just before it. References LAST/GOLDEN, refreshes `arf_slot`; displayed last.
        let (aw, ah) = (frames[n - 1].1, frames[n - 1].2);
        let carf = if self.tf_strength > 0.0 {
            let window = (n - 1).saturating_sub(4).max(i0);
            let neighbors: Vec<&[Vec<u16>; 3]> =
                frames[window..n - 1].iter().map(|f| &f.0).collect();
            let cw = (aw as usize).div_ceil(8) * 8;
            let ch = (ah as usize).div_ceil(8) * 8;
            temporal_filter(&frames[n - 1].0, &neighbors, cw, ch, self.tf_strength)
        } else {
            frames[n - 1].0.clone()
        };
        let ab = self.code_arf_slotted(carf, aw, ah);
        self.companion_feed(&ab);
        // The ARF is HIDDEN: feed it so the oracle's decoder tracks the reference slots,
        // and keep its recon to compare when `show_existing_frame` finally displays it.
        let arf_recon = self.last_recon.take();
        self.recon_verify(&ab, None, aw, ah);

        // First shown P (F_i0) packed WITH the hidden ARF into one superframe.
        let (c1, w1, h1) = frames[i0].clone();
        let pb1 = self.code_p_slotted(c1, w1, h1, true);
        self.companion_feed(&pb1);
        let r = self.last_recon.take();
        self.recon_verify(&pb1, r.as_ref(), w1, h1);
        self.packets.push_back(packet(pack_superframe(&[ab, pb1])));

        // Remaining shown P frames F_{i0+1}..F_{n-2}.
        for f in frames.iter().take(n - 1).skip(i0 + 1) {
            let (ci, iw, ih) = f.clone();
            let pb = self.code_p_slotted(ci, iw, ih, true);
            self.companion_feed(&pb);
            let r = self.last_recon.take();
            self.recon_verify(&pb, r.as_ref(), iw, ih);
            self.packets.push_back(packet(pb));
        }

        // Display the ALT-REF, then swap GOLDEN↔ALTREF so this ARF anchors the next group.
        let se = FrameEncoder::encode_show_existing_frame(self.arf_slot as u32);
        self.companion_feed(&se);
        // show_existing displays the hidden ARF — the one chance to verify it.
        self.recon_verify(&se, arf_recon.as_ref(), aw, ah);
        self.packets.push_back(packet(se));
        std::mem::swap(&mut self.golden_slot, &mut self.arf_slot);
    }

    /// Key frame: fills every reference slot with its reconstruction.
    fn code_key_slotted(&mut self, coded: [Vec<u16>; 3], w: u32, h: u32) -> Vec<u8> {
        let q = self.next_qindex();
        let mut enc = FrameEncoder::new(w, h, q, coded, None);
        enc.set_speed(self.speed);
        self.budget_apply(&mut enc);
        let bytes = enc.encode_frame();
        self.budget_update(&enc, true);
        let recon = enc.recon_owned();
        self.last_recon = Some(recon.clone());
        self.slots = [Some(recon.clone()), Some(recon.clone()), Some(recon)];
        bytes
    }

    /// Hidden ALT-REF: predicts from LAST(slot0)/GOLDEN(golden_slot), refreshes
    /// `arf_slot`. Returns the coded bytes (stores its recon in `arf_slot`).
    fn code_arf_slotted(&mut self, coded: [Vec<u16>; 3], w: u32, h: u32) -> Vec<u8> {
        // ARF q boost, RATE-AWARE. `arf_qscale` is a MULTIPLIER on qindex, so a fixed
        // 0.5 gives a mild absolute boost at low q and an enormous one at high q —
        // i.e. it spends hardest exactly when the budget is smallest. Measured on
        // akiyo_cif at crf 60 the hidden ARF took 5,481 B of a 7,845 B stream (70%)
        // while ordinary frames cost 31 B, so the group could not reach low rates at
        // all and BD-rate collapsed (+46% over the low-quality half of the ladder).
        //
        // Fade the boost out as qindex rises: full `arf_qscale` at q=0, none at the
        // top of the usable range. Measured BD vs no-ARF (+ = ARF worse):
        //   akiyo  qscale 0.5 -> +21.31%   1.0 -> +1.15%   rate-aware -> see below
        //   bus    qscale 0.5 -> +37.11%   1.0 -> +14.31%
        // `VP9_ARF_QSCALE_FLAT=1` restores the old flat multiplier (the A/B oracle).
        let base = self.next_qindex() as f64;
        let qs = if std::env::var("VP9_ARF_QSCALE_FLAT").is_ok() {
            self.arf_qscale
        } else {
            const Q_MAX: f64 = 220.0; // top of the qindex range rate control uses
            let headroom = ((Q_MAX - base) / Q_MAX).clamp(0.0, 1.0);
            1.0 - (1.0 - self.arf_qscale) * headroom
        };
        let q = ((base * qs).round() as u32).clamp(4, 255);
        let idx = [0, self.golden_slot, self.arf_slot];
        let mut enc = FrameEncoder::new(w, h, q, coded, self.slots[0].clone());
        enc.set_speed(self.speed);
        self.budget_apply(&mut enc);
        // Compound default-on for the whole ARF group (quality tier): the ARF frame does
        // LAST+GOLDEN, the shown P frames that reference it do true bi-prediction — kept
        // consistent across the group so libvpx accepts it. `VP9_NO_COMPOUND` opts out.
        enc.set_compound(self.speed <= 3);
        if self.group_chain {
            if let Some((fc, mvs)) = self.chain_args() {
                enc.set_chain(fc, mvs);
            }
        }
        enc.set_golden(self.slots[self.golden_slot].clone().unwrap());
        enc.set_ref_frame_idx(idx);
        enc.set_hidden_altref(self.arf_slot);
        let bytes = enc.encode_frame();
        self.budget_update(&enc, false);
        let arf_recon = enc.recon_owned();
        self.last_recon = Some(arf_recon.clone());
        self.slots[self.arf_slot] = Some(arf_recon);
        bytes
    }

    /// Shown P frame: predicts from LAST(slot0)/GOLDEN(golden_slot)/ALTREF(arf_slot when
    /// `with_altref`), refreshes LAST(slot0). Returns the coded bytes.
    fn code_p_slotted(
        &mut self,
        coded: [Vec<u16>; 3],
        w: u32,
        h: u32,
        with_altref: bool,
    ) -> Vec<u8> {
        let q = self.next_qindex();
        let idx = [0, self.golden_slot, self.arf_slot];
        let mut enc = FrameEncoder::new(w, h, q, coded, self.slots[0].clone());
        enc.set_speed(self.speed);
        self.budget_apply(&mut enc);
        // Compound default-on across the ARF group (a shown P referencing the future ARF
        // does true bi-prediction). Quality tier only. `VP9_NO_COMPOUND` opts out.
        enc.set_compound(self.speed <= 3);
        if self.group_chain && self.slots[0].is_some() {
            if let Some((fc, mvs)) = self.chain_args() {
                enc.set_chain(fc, mvs);
            }
        }
        if self.slots[0].is_some() {
            enc.set_golden(self.slots[self.golden_slot].clone().unwrap());
            if with_altref {
                if let Some(a) = &self.slots[self.arf_slot] {
                    enc.set_altref(a.clone());
                }
            }
            enc.set_ref_frame_idx(idx);
            enc.set_refresh_frame_flags(1); // refresh LAST (slot 0)
        }
        let bytes = enc.encode_frame();
        self.budget_update(&enc, self.slots[0].is_none());
        let recon = enc.recon_owned();
        self.last_recon = Some(recon.clone());
        self.slots[0] = Some(recon);
        bytes
    }
}