Skip to main content

rusty_h264_encoder/
mb16.rs

1//! I_16x16 macroblock encoding (DC prediction) — the compressing intra path.
2//!
3//! Index-based loops below drive pixel/block-position arithmetic and read
4//! clearer than iterator adapters for this raster math.
5#![allow(clippy::needless_range_loop)]
6//!
7//! Each macroblock is DC-predicted from already-reconstructed neighbors, the
8//! residual is transformed/quantized (luma DC via the secondary Hadamard), the
9//! coefficients are CAVLC-coded, and the macroblock is reconstructed so the next
10//! one can predict from it. `nnz` grids feed the CAVLC `nC` context exactly as a
11//! conforming decoder derives it.
12
13use crate::cabac::CabacEncoder;
14use crate::config::EncoderConfig;
15use rusty_h264_common::cavlc::{
16    encode_residual_block, scan_4x4_ac, scan_4x4_dcac, write_cbp_inter, write_cbp_intra,
17};
18use rusty_h264_common::inter::{
19    inter_partitions, mc_chroma, mc_luma, predict_mv, predict_partition_mv, MvNeighbor,
20};
21use rusty_h264_common::predict::{
22    add_residual_4x4, add_residual_8x8, chroma8x8_pred, chroma_mode_available, chroma_qp,
23    intra4x4_pred, intra8x8_pred, luma16x16_pred, reconstruct_4x4, I16Mode, CHROMA_4X4_SCAN_XY,
24    LUMA_4X4_SCAN_XY,
25};
26use rusty_h264_common::transform::{
27    dequantize, forward_core, forward_core_8x8, forward_dct_blocks, forward_quant_chroma_dc,
28    forward_quant_luma_dc, inverse_dct_blocks, inverse_quant_8x8, inverse_quant_chroma_dc,
29    inverse_quant_luma_dc, quantize, quantize_8x8, satd_4x4_sum,
30};
31use rusty_h264_common::aligned::AlignedBytes;
32use rusty_h264_common::{BitWriter, YuvFrame};
33
34/// A/B switch for the batched full-pel rescue grid (`RFF_ME_BATCH=0` disables).
35///
36/// Read ONCE per process, not per call: this sits inside the motion-search rescue
37/// path, and `std::env::var` allocates a `String` and takes the process-wide
38/// environment lock every time. A runtime switch inside a hot loop is its own
39/// measurable tax — cache it.
40
41
42/// λ-normalised threshold for the partition-split search gate (U2).
43///
44/// The existing `split_gate` is a function of qstep ALONE, so it does not scale with
45/// the rate/distortion trade the search is actually making. Normalising the null arm
46/// by λ — the king feature for any search-skip gate — makes one constant transfer
47/// across content AND the whole QP ladder, and in the SAFE direction: the feature is
48/// small exactly where the 16×16 null arm is already good, so easy content skips more.
49///
50/// Harvested over 36 k gated macroblocks (4 clips): at T = 400 the split search is
51/// skipped on 2.9–22.5% of them while keeping **100.00%** of the achievable cost gain
52/// on every clip; T = 600 skips 11–79% for 93–99% kept. `RFF_SPLIT_T=0` disables.
53pub(crate) static DEFER_SUBPEL: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
54
55pub(crate) static SPLIT_T: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
56
57fn split_t() -> f64 {
58    let v = SPLIT_T.load(std::sync::atomic::Ordering::Relaxed);
59    if v != u32::MAX {
60        return v as f64;
61    }
62    let d: u32 = std::env::var("RFF_SPLIT_T").ok().and_then(|s| s.parse().ok()).unwrap_or(0);
63    SPLIT_T.store(d, std::sync::atomic::Ordering::Relaxed);
64    d as f64
65}
66
67/// Observe-only HARVEST for the PARTITION-split gate (U2/U5).
68///
69/// The 16×16 search is the null arm and runs first; the 2-way splits and P_8x8 are
70/// the expensive arm (7 further `best_part` calls, each with its own full-pel search
71/// AND sub-pel refinement). Today they are gated by a fixed `split_gate` formula.
72/// Records the null-arm cost, the best split cost, and which won, so the
73/// skip-rate-vs-gain-kept ceiling can be swept before any threshold is touched.
74mod split_harvest {
75    use std::fs::File;
76    use std::io::Write;
77    use std::sync::{Mutex, OnceLock};
78
79    fn sink() -> &'static Option<Mutex<File>> {
80        static S: OnceLock<Option<Mutex<File>>> = OnceLock::new();
81        S.get_or_init(|| {
82            std::env::var("RFF_SPLIT_HARVEST").ok().and_then(|p| {
83                let mut f = File::create(p).ok()?;
84                let _ = writeln!(f, "c16,best,lambda,gate,won");
85                Some(Mutex::new(f))
86            })
87        })
88    }
89
90    #[inline]
91    pub fn enabled() -> bool {
92        sink().is_some()
93    }
94
95    pub fn record(c16: i64, best: i64, lambda: f64, gate: i64, won: u8) {
96        if let Some(m) = sink() {
97            if let Ok(mut f) = m.lock() {
98                let _ = writeln!(f, "{c16},{best},{lambda:.4},{gate},{won}");
99            }
100        }
101    }
102}
103
104/// Descent C escape hatch: `RFF_HPEL_REF=0` restores the copy-then-SATD half-pel path
105/// (byte-identical to it either way — this exists as a bisection anchor).
106fn hpel_ref_enabled() -> bool {
107    static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
108    *E.get_or_init(|| std::env::var("RFF_HPEL_REF").map(|v| v != "0").unwrap_or(true))
109}
110
111/// H-23: smooth (x264-shape) mvd cost table, in quarter-bit units scaled to the
112/// same magnitude as the Exp-Golomb model so λ stays calibrated. `RFF_MVCOST=1`.
113static MV_COST_TAB: std::sync::OnceLock<Vec<u16>> = std::sync::OnceLock::new();
114fn build_mv_cost() -> Vec<u16> {
115    (0..4096u32)
116        .map(|a| {
117            let c = 2.0 * ((a + 1) as f64).log2() + 0.718 + if a != 0 { 1.0 } else { 0.0 };
118            // Round to quarter-bits then express in the caller's integer "bits"
119            // domain by keeping 4× resolution — λ is rescaled to match below.
120            (c * 4.0).round() as u16
121        })
122        .collect()
123}
124/// H-24: 0 = off (Exp-Golomb step, byte-identical), 1 = DISPATCHED per frame by
125/// the `b2_mgain` motion probe, 2 = force-on. The BD sign-flip (bus −1.31 /
126/// football −0.24 vs foreman +0.23 / akiyo +0.11) tracks MOTION: the smooth
127/// curve pays where |mvd| is large enough to leave the first bracket, and only
128/// adds noise where every vector already sits inside it.
129static MV_SMOOTH: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
130pub fn set_mv_smooth(on: bool) {
131    MV_SMOOTH.store(if on { 2 } else { 0 }, core::sync::atomic::Ordering::Relaxed)
132}
133pub fn set_mv_smooth_mode(m: u32) {
134    MV_SMOOTH.store(m.min(3), core::sync::atomic::Ordering::Relaxed)
135}
136/// Dispatch threshold on the per-frame mgain probe (`RFF_MVCOST_T`, default 0.10).
137/// Calibrated on the DEPLOYED probe: bus min-frame 0.185 and football med 0.208
138/// route ON; foreman med 0.164 is the boundary case, akiyo ~0.00 routes OFF.
139/// H-26: the measured TRUE table plus a COHERENCE BIAS on every d≠0 entry —
140/// the cheap scalar form of the MV-field externality H-25 root-caused (a chosen
141/// vector that leaves the predictor degrades the neighbours' medians; truth
142/// per-vector under-prices that shared damage). `RFF_MVCOST_BIAS` in bits
143/// (default 0 = pure truth), read once at first use.
144static MV_TRUE_BIASED: std::sync::OnceLock<Vec<u16>> = std::sync::OnceLock::new();
145fn build_true_biased() -> Vec<u16> {
146    let bias_q4 = (std::env::var("RFF_MVCOST_BIAS")
147        .ok()
148        .and_then(|v| v.parse::<f64>().ok())
149        .unwrap_or(1.0)
150        * 4.0)
151        .round() as u16;
152    crate::mvd_cost_tab::MVD_TRUE_COST4
153        .iter()
154        .enumerate()
155        .map(|(d, &c)| if d == 0 { c } else { c.saturating_add(bias_q4) })
156        .collect()
157}
158
159fn mv_smooth_t() -> f64 {
160    static T: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
161    *T.get_or_init(|| std::env::var("RFF_MVCOST_T").ok().and_then(|v| v.parse().ok()).unwrap_or(0.10))
162}
163/// 0 = step model, 1 = smooth (this frame routed on), 2 = smooth (forced),
164/// 3 = the MEASURED true-cost table (H-25) — no dispatch needed if it wins
165/// everywhere, since it is the truth both analytic models approximate.
166/// `frame_smooth` is the per-frame probe decision, carried on the frame state
167/// (like `sadfp`) — a process-global here races under the GOP-parallel encode.
168#[inline]
169fn mv_cost_kind(frame_smooth: bool) -> u32 {
170    match mv_smooth_mode() {
171        0 => 0,
172        // H-26 verdict: smooth/truth/truth+bias shuffle within ±0.2 BD fit-noise
173        // of each other at dispatch (bus prefers truth, football prefers smooth,
174        // none dominates), so the dispatch keeps its ORIGINALLY-GATED smooth
175        // ON-model; the measured/biased tables remain as modes 2/3 for research.
176        1 => frame_smooth as u32,
177        2 => 1, // archaeology: the x264 smooth curve, forced
178        _ => 2, // the biased-truth table, forced
179    }
180}
181#[inline]
182fn mv_smooth_mode() -> u32 {
183    match MV_SMOOTH.load(core::sync::atomic::Ordering::Relaxed) {
184        m @ 0..=3 => m,
185        _ => {
186            static E: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
187            // DEFAULT 1 = DISPATCHED (H-24). Owner's call: mean −0.27% BD is
188            // taken over minimax, accepting a known, bounded +0.16-0.18% on
189            // foreman-class content. `RFF_MVCOST=0` restores the pre-H-23 bytes.
190            *E.get_or_init(|| {
191                std::env::var("RFF_MVCOST").ok().and_then(|v| v.parse().ok()).unwrap_or(1)
192            })
193        }
194    }
195}
196
197/// Challenge-1 A3 escape hatch: `RFF_SATD_AVG=0` restores the materialize-then-SATD
198/// quarter-pel cost path (byte-identical either way — a bisection anchor, like
199/// `RFF_HPEL_REF`).
200/// H-14 R3 escape hatch: `RFF_MECTX=0` restores the per-eval safe dispatch
201/// (byte-identical either way — MeCtx returns exactly the safe path's values).
202fn mectx_enabled() -> bool {
203    static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
204    *E.get_or_init(|| std::env::var("RFF_MECTX").map(|v| v != "0").unwrap_or(true))
205}
206
207fn satd_avg_enabled() -> bool {
208    static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
209    *E.get_or_init(|| std::env::var("RFF_SATD_AVG").map(|v| v != "0").unwrap_or(true))
210}
211
212/// Track-B B2 (docs/lets-win-optimize.md): run the FULL-PEL phase of the non-fast
213/// motion search in the SAD domain (`psadbw`-class, ~3-4× cheaper per candidate) and
214/// reprice the winner in SATD before the rescue/sub-pel phases — the cost split
215/// every x264 preset uses (SAD fpel, SATD from subme≥2). ⚠ BITSTREAM-CHANGING (a
216/// different full-pel winner can emerge), so it ships opt-in until the per-clip
217/// 4-QP BD gate clears it. `set_me_sadfp` overrides; unset → `RFF_ME_SADFP` env,
218/// default OFF (off = byte-identical to the pre-B2 encoder).
219/// Modes: 0 = off (byte-identical), 1 = DISPATCHED per frame by the `b2_mgain`
220/// probe (the shipping shape), 2 = force-on everywhere (the truth-table A/B arm).
221static ME_SADFP: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
222pub fn set_me_sadfp(on: bool) {
223    // Harness semantics preserved: `true` = the force-on arm truth tables measure.
224    ME_SADFP.store(if on { 2 } else { 0 }, core::sync::atomic::Ordering::Relaxed)
225}
226pub fn set_me_sadfp_mode(m: u32) {
227    ME_SADFP.store(m.min(2), core::sync::atomic::Ordering::Relaxed)
228}
229fn me_sadfp_mode() -> u32 {
230    match ME_SADFP.load(core::sync::atomic::Ordering::Relaxed) {
231        u32::MAX => {
232            static INIT: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
233            // DEFAULT = 1 (dispatched) since the H-3 gate: 16-clip corpus mean
234            // −0.26% BD, wins bus −1.71 / football −1.84 / foreman −0.44 /
235            // shields −0.22, every former loss 0.00; residual tail (soccer +0.09,
236            // harbour +0.06) is BD-fit noise — it responds NON-monotonically to
237            // threshold changes (less B2 made soccer read WORSE, +0.18).
238            // `RFF_ME_SADFP=0` is the escape hatch reproducing the pre-B2 bytes.
239            *INIT.get_or_init(|| {
240                std::env::var("RFF_ME_SADFP").ok().and_then(|v| v.parse().ok()).unwrap_or(1)
241            })
242        }
243        m => m,
244    }
245}
246
247/// B2 dispatch threshold on the per-frame `b2_mgain` probe (`RFF_ME_SADT`).
248/// Calibrated on the DEPLOYED estimator (recon reference, sampled MBs), not the
249/// offline source-frame probe — the recurring R6 law.
250fn me_sadt() -> f64 {
251    static T: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
252    *T.get_or_init(|| std::env::var("RFF_ME_SADT").ok().and_then(|s| s.parse().ok()).unwrap_or(0.13))
253}
254fn me_sadt_dbg() -> bool {
255    static D: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
256    *D.get_or_init(|| std::env::var_os("RFF_ME_SADT_DBG").is_some())
257}
258
259/// Fixed-centre batched diamond passes on SAD-routed frames (`RFF_ME_FC=0` falls
260/// back to the cascading scalar walk — the bisection anchor). Fixed-centre differs
261/// from the cascade only when 2+ points improve in one pass, so it rides B2's BD
262/// gate; dispatched-OFF frames never take this path and stay byte-identical.
263/// ③ Sub-pel ring FC: fixed-centre argmin passes for the HALF-PEL step, batched
264/// through `satd_16x16_x4p` (two calls cover the 8-ring; candidates resolve to
265/// h/h/v/v and c/c/c/c plane reads from an integer centre). Quarter-step and any
266/// declined pass keep the cascading walk. Bitstream-changing → own gate
267/// (`AB_SPFC`), `RFF_SP_FC=0` anchor.
268static SP_FC: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
269pub fn set_sp_fc(on: bool) {
270    SP_FC.store(on as u32, core::sync::atomic::Ordering::Relaxed)
271}
272fn sp_fc_enabled() -> bool {
273    match SP_FC.load(core::sync::atomic::Ordering::Relaxed) {
274        0 => false,
275        1 => true,
276        _ => {
277            static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
278            *E.get_or_init(|| std::env::var("RFF_SP_FC").map(|v| v != "0").unwrap_or(false))
279        }
280    }
281}
282
283static ME_FC: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
284pub fn set_me_fc(on: bool) {
285    ME_FC.store(on as u32, core::sync::atomic::Ordering::Relaxed)
286}
287fn me_fc_enabled() -> bool {
288    match ME_FC.load(core::sync::atomic::Ordering::Relaxed) {
289        0 => false,
290        1 => true,
291        _ => {
292            static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
293            *E.get_or_init(|| std::env::var("RFF_ME_FC").map(|v| v != "0").unwrap_or(true))
294        }
295    }
296}
297
298/// H-13 SPLIT DISPATCH — measured and REFUTED as a free dispatch, shipped as an
299/// OPT-IN rung (default 0 = off = byte-identical). The premise "splits buy
300/// ~nothing on near-static frames" is FALSE: at T=0.03 akiyo read +2.45% BD,
301/// akiyo_qcif +2.02%, FourPeople +2.00% for only 1.10-1.15× — partition splits
302/// EARN BD on every measured content class (the third death of the split-gate
303/// idea: U2 T=400, the sum-weighted ceiling, now the mgain axis). foreman/bus
304/// route ON at any sane T (min frame mgain 0.061/0.185) and stay byte-identical.
305/// `RFF_SPLIT_MG` (fraction) / `set_split_mg` (milli): a priced speed rung, not
306/// a free lunch.
307static SPLIT_MG: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
308pub fn set_split_mg(milli: u32) {
309    SPLIT_MG.store(milli, core::sync::atomic::Ordering::Relaxed)
310}
311fn split_mg() -> f64 {
312    match SPLIT_MG.load(core::sync::atomic::Ordering::Relaxed) {
313        u32::MAX => {
314            static E: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
315            *E.get_or_init(|| {
316                std::env::var("RFF_SPLIT_MG").ok().and_then(|v| v.parse().ok()).unwrap_or(0.0)
317            })
318        }
319        m => m as f64 / 1000.0,
320    }
321}
322
323/// The flash veto: frames whose zero-MV residual is DC-shift-dominated beyond this
324/// fraction route OFF even at high mgain (`RFF_ME_SADDC`). Calibrated on the
325/// DEPLOYED per-frame values: crew's harmful ON-frames read dc 0.843–0.859 (the
326/// camera flashes) while every good ON-frame on bus/football/foreman reads ≤ 0.478
327/// — a 1.76× natural gap; 0.6 sits mid-gap with margin both ways.
328fn me_sad_dcmax() -> f64 {
329    static T: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
330    *T.get_or_init(|| std::env::var("RFF_ME_SADDC").ok().and_then(|s| s.parse().ok()).unwrap_or(0.6))
331}
332
333/// The B2 dispatch signal: mean over ~24 sampled interior MBs of
334/// `(SAD@zeroMV − bestSAD over a ±8 step-4 full-pel grid) / SAD@zeroMV` — how much
335/// a plain TRANSLATIONAL full-pel search improves on zero motion, i.e. exactly the
336/// surface B2's SAD diamond exploits. Offline (b2_signals, 16-clip truth table) it
337/// separates every B2 loss (crew flash 0.070, city 0.110, tempete 0.008) from
338/// every meaningful win (bus 0.323, football/foreman 0.164/0.165, shields 0.361);
339/// notably `me_wide_headroom` CANNOT be reused here — crew's headroom is high (20)
340/// but B2 loses there, because SAD overprices the DC shifts of its camera flashes.
341/// Returns `(mgain, dcfrac)`. `dcfrac` — mean `|Σcur − Σref| / SAD0` per sampled
342/// block — is the FLASH detector: under an illumination change the zero-MV residual
343/// is mostly a DC shift, which SAD prices fully but the Hadamard largely discounts,
344/// so SAD misranks candidates exactly there. Justified by the one clip the
345/// single-term gate got wrong (crew: high mgain on its motion frames, +0.54 BD).
346fn b2_mgain(sy: &[u8], cw: usize, ch: usize, ref_y: &[u8]) -> (f64, f64) {
347    const WIDE: isize = 8;
348    const STEP: isize = 4;
349    const TARGET: usize = 24;
350    let sad16 = |bx: usize, by: usize, rx: isize, ry: isize| -> Option<u32> {
351        if rx < 0 || ry < 0 || rx as usize + 16 > cw || ry as usize + 16 > ch {
352            return None;
353        }
354        let (rx, ry) = (rx as usize, ry as usize);
355        let mut s = 0u32;
356        for dy in 0..16 {
357            let a = &sy[(by + dy) * cw + bx..][..16];
358            let b = &ref_y[(ry + dy) * cw + rx..][..16];
359            s += a.iter().zip(b).map(|(&p, &q)| p.abs_diff(q) as u32).sum::<u32>();
360        }
361        Some(s)
362    };
363    let (mbw, mbh) = (cw / 16, ch / 16);
364    if mbw < 6 || mbh < 6 {
365        return (0.0, 0.0);
366    }
367    let inner = (mbw - 4) * (mbh - 4);
368    let stride = (inner / TARGET).max(1);
369    let (mut acc, mut dc, mut n) = (0.0f64, 0.0f64, 0u32);
370    let mut i = 0usize;
371    while i < inner {
372        let (mx, my) = (2 + i % (mbw - 4), 2 + i / (mbw - 4));
373        let (bx, by) = (mx * 16, my * 16);
374        if let Some(s0) = sad16(bx, by, bx as isize, by as isize) {
375            let (mut ms, mut mr) = (0u32, 0u32);
376            for dy in 0..16 {
377                ms += sy[(by + dy) * cw + bx..][..16].iter().map(|&v| v as u32).sum::<u32>();
378                mr += ref_y[(by + dy) * cw + bx..][..16].iter().map(|&v| v as u32).sum::<u32>();
379            }
380            dc += ms.abs_diff(mr) as f64 / (s0 + 1) as f64;
381            let mut best = s0;
382            let mut dy = -WIDE;
383            while dy <= WIDE {
384                let mut dx = -WIDE;
385                while dx <= WIDE {
386                    if let Some(s) = sad16(bx, by, bx as isize + dx, by as isize + dy) {
387                        best = best.min(s);
388                    }
389                    dx += STEP;
390                }
391                dy += STEP;
392            }
393            acc += (s0 - best) as f64 / (s0 + 1) as f64;
394            n += 1;
395        }
396        i += stride;
397    }
398    if n == 0 { (0.0, 0.0) } else { (acc / n as f64, dc / n as f64) }
399}
400
401/// Track-B B3: cap on sub-pel ring ITERATIONS per step (`RFF_SP_MAXIT` /
402/// `set_sp_maxit`). 0 = unlimited (the default — byte-identical to the walk-to-
403/// convergence encoder); N caps each step's walk at N passes, the bounded budget
404/// x264's subme levels have always had. Bitstream-changing when set → BD-gated.
405static SP_MAXIT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
406pub fn set_sp_maxit(n: u32) {
407    SP_MAXIT.store(n, core::sync::atomic::Ordering::Relaxed)
408}
409fn sp_maxit() -> u32 {
410    match SP_MAXIT.load(core::sync::atomic::Ordering::Relaxed) {
411        u32::MAX => {
412            static INIT: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
413            *INIT.get_or_init(|| {
414                std::env::var("RFF_SP_MAXIT").ok().and_then(|v| v.parse().ok()).unwrap_or(0)
415            })
416        }
417        n => n,
418    }
419}
420
421/// B2 calibration: λ multiplier for the SAD-domain full-pel phase (`RFF_ME_SADL`,
422/// default 1.0). SATD distortion runs ~2× SAD's scale, so λ tuned for SATD weighs
423/// the rate term ~2× heavier in the SAD domain — 0.5 restores the SATD-era
424/// rate/distortion balance. Read once per process (hoisted per search).
425fn me_sadfp_lambda() -> f64 {
426    static E: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
427    // 0.5 = the calibrated default (SATD ≈ 2× SAD's scale; at 1.0 the rate term
428    // weighs double and foreman flips to a BD loss). Rides with the mode-1 default.
429    *E.get_or_init(|| {
430        std::env::var("RFF_ME_SADL").ok().and_then(|v| v.parse().ok()).unwrap_or(0.5)
431    })
432}
433
434/// Descent D: sub-pel ring census — evals/improvements by (step, ring position) and
435/// by loop ITERATION, so a position or an iteration that never pays is visible rather
436/// than assumed.
437#[cfg(feature = "profile")]
438pub mod spstats {
439    use core::sync::atomic::{AtomicU64, Ordering};
440    /// [step 0=half,1=quarter][position 0..8][0=evals,1=improvements]
441    pub static POS: [AtomicU64; 2 * 8 * 2] = [const { AtomicU64::new(0) }; 32];
442    /// [step][iteration 1..=6 clamped][0=evals,1=improvements]
443    pub static IT: [AtomicU64; 2 * 6 * 2] = [const { AtomicU64::new(0) }; 24];
444    #[inline]
445    pub fn ev(st: usize, pos: usize, it: u32) {
446        POS[(st * 8 + pos.min(7)) * 2].fetch_add(1, Ordering::Relaxed);
447        IT[(st * 6 + (it.max(1) as usize - 1).min(5)) * 2].fetch_add(1, Ordering::Relaxed);
448    }
449    #[inline]
450    pub fn imp(st: usize, pos: usize, it: u32) {
451        POS[(st * 8 + pos.min(7)) * 2 + 1].fetch_add(1, Ordering::Relaxed);
452        IT[(st * 6 + (it.max(1) as usize - 1).min(5)) * 2 + 1].fetch_add(1, Ordering::Relaxed);
453    }
454    /// Sub-pel evaluations that re-price an MV already evaluated in the SAME refinement.
455    pub static REDUNDANT: AtomicU64 = AtomicU64::new(0);
456    #[inline]
457    pub fn redundant() { REDUNDANT.fetch_add(1, Ordering::Relaxed); }
458    pub fn reset() {
459        for c in POS.iter() { c.store(0, Ordering::Relaxed); }
460        for c in IT.iter() { c.store(0, Ordering::Relaxed); }
461        REDUNDANT.store(0, Ordering::Relaxed);
462    }
463    pub fn snapshot() -> (Vec<u64>, Vec<u64>) {
464        (POS.iter().map(|c| c.load(Ordering::Relaxed)).collect(),
465         IT.iter().map(|c| c.load(Ordering::Relaxed)).collect())
466    }
467    pub fn redundant_count() -> u64 { REDUNDANT.load(Ordering::Relaxed) }
468}
469
470/// Descent B: which path does each ME cost evaluation actually take?
471#[cfg(feature = "profile")]
472pub mod satdpath {
473    use core::sync::atomic::{AtomicU64, Ordering};
474    pub static C: [AtomicU64; 3] = [const { AtomicU64::new(0) }; 3];
475    #[inline]
476    pub fn bump(i: usize) { C[i].fetch_add(1, Ordering::Relaxed); }
477    pub fn reset() { for c in C.iter() { c.store(0, Ordering::Relaxed); } }
478    pub fn snapshot() -> Vec<u64> { C.iter().map(|c| c.load(Ordering::Relaxed)).collect() }
479}
480
481/// The coarse-to-fine step ladder. DEFAULT `[16,8,4]` — the 64 and 32 rungs were
482/// REMOVED after the per-rung census showed they are ~39% of full-pel evaluations at a
483/// 0.05-0.84% hit rate, and the 20-clip 4-QP BD curve showed those rare hits are actively
484/// HARMFUL: a coarse jump finds a distant MV with marginally lower SATD, but it costs
485/// more mvd bits AND breaks the spatial coherence of the MV field, degrading every
486/// downstream neighbour's predictor. `lambda*mvbits` prices the first effect and is blind
487/// to the second. Dropping them is mean -0.93% BD-PSNR / -1.09% BD-SSIM with a WORST clip
488/// of +0.00%/+0.00% over 20 clips, and 1.15-1.57x fewer ME cost evaluations.
489///
490/// The 8 rung is load-bearing: `[16,4]` reads marginally better BD but makes football_cif
491/// do 1.55x MORE work, because the step-4 walk then has to crawl the distance the 8 rung
492/// covered in one hop. Reach and stride both matter; only the useless TOP is removed.
493///
494/// Bit i of the mask enables rung i of [64,32,16,8,4]. `RFF_DIA_LADDER=64,32,16,8,4`
495/// restores the pre-change ladder byte-for-byte; `set_dia_mask` overrides at runtime so a
496/// single process can measure several ladders.
497pub const DIA_RUNGS: [i32; 5] = [64, 32, 16, 8, 4];
498/// Rungs walked by default: `[16,8,4]`.
499pub const DIA_DEFAULT: u32 = 0b11100;
500pub static DIA_MASK: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
501pub fn set_dia_mask(m: u32) { DIA_MASK.store(m, core::sync::atomic::Ordering::Relaxed) }
502fn dia_mask() -> u32 {
503    let m = DIA_MASK.load(core::sync::atomic::Ordering::Relaxed);
504    if m != u32::MAX { return m; }
505    static INIT: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
506    *INIT.get_or_init(|| match std::env::var("RFF_DIA_LADDER") {
507        Ok(v) => {
508            let want: Vec<i32> = v.split(',').filter_map(|t| t.trim().parse().ok()).collect();
509            let mut m = 0u32;
510            for (i, r) in DIA_RUNGS.iter().enumerate() {
511                if want.contains(r) { m |= 1 << i; }
512            }
513            if m == 0 { DIA_DEFAULT } else { m }
514        }
515        Err(_) => DIA_DEFAULT,
516    })
517}
518
519/// Descent A: per-STEP-SIZE census of the coarse-to-fine diamond. The ladder is
520/// [64,32,16,8,4] quarter-pel (i.e. 16,8,4,2,1 full-pel) and each step walks until it
521/// stops improving. Counts evaluations AND improvements per step so a step that never
522/// pays can be identified rather than assumed.
523#[cfg(feature = "profile")]
524pub mod diastats {
525    use core::sync::atomic::{AtomicU64, Ordering};
526    /// [step_index][0]=evals, [1]=improvements
527    pub static C: [AtomicU64; 12] = [const { AtomicU64::new(0) }; 12];
528    #[inline]
529    pub fn ev(i: usize) { C[i * 2].fetch_add(1, Ordering::Relaxed); }
530    #[inline]
531    pub fn imp(i: usize) { C[i * 2 + 1].fetch_add(1, Ordering::Relaxed); }
532    pub fn reset() { for c in C.iter() { c.store(0, Ordering::Relaxed); } }
533    pub fn snapshot() -> Vec<(u64, u64)> {
534        (0..6).map(|i| (C[i * 2].load(Ordering::Relaxed), C[i * 2 + 1].load(Ordering::Relaxed))).collect()
535    }
536}
537
538/// Sub-pel refinement PATTERN (U1). Bit 0 = 4-point diamond ring instead of the
539/// 8-point square; bit 1 = single pass instead of walking to convergence.
540///
541/// Harvested from 280 k real refinements: ~29 evaluations each, but the LAST
542/// improvement lands at eval ~14–15 — **half of every refinement is spent confirming
543/// an answer already found** — and the first ring alone captures 64–72% of the total
544/// gain. An 8-point ring pays 8 evaluations for that confirmation; a 4-point diamond
545/// (what x264's subme uses) pays 4.
546///
547/// `RFF_SUBPEL_PAT`: 0 = 8-point + iterate (the pre-U1 default), 1 = 4-point +
548/// iterate, 2 = 8-point single pass, 3 = 4-point single pass.
549pub(crate) static SUBPEL_PAT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
550
551/// Learning-window size and ring-1 threshold (percent) for the U1 online dispatcher.
552/// `RFF_SUBPEL_DISPATCH=0` disables it (pure `RFF_SUBPEL_PAT` behaviour).
553pub(crate) static SP_DISPATCH: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
554
555fn sp_dispatch_cfg() -> (u32, i64) {
556    use std::sync::OnceLock;
557    let forced = SP_DISPATCH.load(std::sync::atomic::Ordering::Relaxed);
558    if forced == 0 {
559        return (0, 0);
560    }
561    static C: OnceLock<(u32, i64)> = OnceLock::new();
562    *C.get_or_init(|| {
563        // DEFAULT OFF — measured and refuted (see the U1 entry in
564        // docs/WHYS-speed-gap.md). It only delivers speed where a blanket pattern
565        // change already would (bus 1.47x) while costing BD where it delivers none
566        // (foreman +0.97% for 1.04x, mobile +0.33% for 0.98x), and mixing refinement
567        // quality across frames measured WORSE than a uniform cut (bus +0.81%
568        // dispatched vs +0.30% pat2-always) — the refinement feeds the reference
569        // chain, so per-frame inconsistency propagates.
570        let on = std::env::var("RFF_SUBPEL_DISPATCH").map(|s| s != "0").unwrap_or(false);
571        if !on {
572            return (0, 0);
573        }
574        let k = std::env::var("RFF_SUBPEL_LEARN").ok().and_then(|s| s.parse().ok()).unwrap_or(200);
575        let t = std::env::var("RFF_SUBPEL_T").ok().and_then(|s| s.parse().ok()).unwrap_or(67);
576        (k, t)
577    })
578}
579
580/// Explicit override only; `None` means "use the preset's default".
581fn subpel_pattern_override() -> Option<u32> {
582    let v = SUBPEL_PAT.load(std::sync::atomic::Ordering::Relaxed);
583    if v != u32::MAX {
584        return Some(v);
585    }
586    if let Some(e) = std::env::var("RFF_SUBPEL_PAT").ok().and_then(|s| s.parse::<u32>().ok()) {
587        SUBPEL_PAT.store(e, std::sync::atomic::Ordering::Relaxed);
588        return Some(e);
589    }
590    None
591}
592
593fn subpel_pattern() -> u32 {
594    let v = SUBPEL_PAT.load(std::sync::atomic::Ordering::Relaxed);
595    if v != u32::MAX {
596        return v;
597    }
598    // Unset -> take the env default once and latch it, so the hot path stays a
599    // relaxed load rather than an env lookup.
600    let d = std::env::var("RFF_SUBPEL_PAT").ok().and_then(|s| s.parse().ok()).unwrap_or(0);
601    SUBPEL_PAT.store(d, std::sync::atomic::Ordering::Relaxed);
602    d
603}
604
605/// Observe-only HARVEST for the sub-pel refinement skip-gate (U1).
606///
607/// `me-subpel` is 141 ms of a 320 ms quality encode — 44% — at 241 candidate
608/// evaluations per macroblock. This tap records, per refinement, the NULL-ARM cost
609/// (the full-pel winner, i.e. what we would keep if we skipped) against the cost the
610/// refinement actually reached, so the skip-rate-vs-gain-kept ceiling can be swept
611/// offline before any gate is written. Writes nothing unless `RFF_SUBPEL_HARVEST`
612/// names a file.
613mod subpel_harvest {
614    use std::fs::File;
615    use std::io::Write;
616    use std::sync::{Mutex, OnceLock};
617
618    fn sink() -> &'static Option<Mutex<File>> {
619        static S: OnceLock<Option<Mutex<File>>> = OnceLock::new();
620        S.get_or_init(|| {
621            std::env::var("RFF_SUBPEL_HARVEST").ok().and_then(|p| {
622                let mut f = File::create(p).ok()?;
623                let _ = writeln!(f, "pre,post,lambda,w,h,evals,to_best,ring1");
624                Some(Mutex::new(f))
625            })
626        })
627    }
628
629    #[inline]
630    pub fn enabled() -> bool {
631        sink().is_some()
632    }
633
634    #[allow(clippy::too_many_arguments)]
635    pub fn record(pre: i64, post: i64, lambda: f64, w: usize, h: usize, evals: u32, to_best: u32, ring1: i64) {
636        if let Some(m) = sink() {
637            if let Ok(mut f) = m.lock() {
638                let _ = writeln!(f, "{pre},{post},{lambda:.4},{w},{h},{evals},{to_best},{ring1}");
639            }
640        }
641    }
642}
643
644/// A/B switch for serving B-direct 4×4 MC from the cached half-pel planes
645/// (`RFF_BDIRECT_PLANES=0` restores the direct `mc_luma` 6-tap). Byte-identical
646/// either way; the knob exists so the arm can be measured in one binary.
647fn bdirect_planes_enabled() -> bool {
648    use std::sync::OnceLock;
649    static ON: OnceLock<bool> = OnceLock::new();
650    *ON.get_or_init(|| std::env::var("RFF_BDIRECT_PLANES").map(|s| s != "0").unwrap_or(true))
651}
652
653fn me_batch_enabled() -> bool {
654    use std::sync::OnceLock;
655    static ON: OnceLock<bool> = OnceLock::new();
656    *ON.get_or_init(|| std::env::var("RFF_ME_BATCH").map(|s| s != "0").unwrap_or(true))
657}
658
659/// A 16-byte-aligned 16×16 luma block — the aligned `op1` openh264's SSE2 SAD/SATD
660/// kernels require (`movdqa`). Safe to construct (`forbid(unsafe)` holds); the asm
661/// FFI that consumes it lives in `rusty_h264-accel`. Only used on the `asm` feature.
662#[cfg(accel)]
663#[repr(align(16))]
664struct AlignedMb([u8; 256]);
665
666/// A B-slice 16×16 inter-coding spec: the prediction direction and the motion it
667/// uses. `dir` 1 = `B_L0_16x16`, 2 = `B_L1_16x16`, 3 = `B_Bi_16x16` (spec Table
668/// 7-14). List-0 is `refs[0]` (nearest past anchor); `l1` is List-1 (nearest
669/// future anchor). `mv0`/`mv1` are the List-0/List-1 motion vectors (quarter-pel).
670#[derive(Clone, Copy)]
671struct BInter<'a> {
672    dir: u8,
673    l1: &'a crate::RefFrame,
674    mv0: (i32, i32),
675    mv1: (i32, i32),
676}
677
678/// 16-byte-aligned 256-`i16` DCT/coefficient buffer — the in-place `movdqa` quant
679/// kernel (`WelsQuantFour4x4_sse2`) requires aligned coefficients. `asm`-feature only.
680#[cfg(accel)]
681#[repr(align(16))]
682struct AlignedDct([i16; 256]);
683
684/// Luma variance of the 16×16 source MB at (mb_x, mb_y) — the content signal for
685/// the adaptive SAD↔SATD cost dispatch (high variance = detail = SAD misprices).
686/// `256·variance` scale (the /256 of the mean-square is kept integer); only the
687/// RELATIVE ordering matters for the per-frame percentile, so the constant drops.
688fn mb_variance(sy: &[u8], cw: usize, mb_x: usize, mb_y: usize) -> i64 {
689    let base = mb_y * 16 * cw + mb_x * 16;
690    // Accumulate in u32, not i64: the sum of 256 bytes maxes at 65280 and the sum
691    // of squares at 16.6M, so 64-bit accumulators (and a 64-bit multiply per
692    // pixel) were pure width — and they stop LLVM vectorising what is otherwise a
693    // textbook pair of reductions over 16 contiguous bytes.
694    let (mut s, mut ss) = (0u32, 0u32);
695    for r in 0..16 {
696        let row = &sy[base + r * cw..base + r * cw + 16];
697        for &p in row {
698            let v = p as u32;
699            s += v;
700            ss += v * v;
701        }
702    }
703    // Widen once at the end: s*s reaches 4.26e9, which only just fits u32.
704    ss as i64 - (s as i64) * (s as i64) / 256 // 256·variance, monotone in variance
705}
706
707/// Adaptive-Quantization per-MB QP map: flat (low-variance) macroblocks get a FINER
708/// QP (where blocking/banding is visible), busy ones a COARSER QP (where the eye
709/// masks error) — moving bits to where they're seen. The shift is `strength ·
710/// (log2 var − frame mean log2 var)`, so it's relative to THIS frame's texture
711/// distribution (content-invariant), rounded to an integer QP step and clamped.
712/// `strength == 0` → uniform base QP (byte-identical: every `mb_qp_delta` is 0).
713fn aq_qp_map(sy: &[u8], cw: usize, mb_w: usize, mb_h: usize, base_qp: u8, strength: f64) -> Vec<u8> {
714    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncAq);
715    const AQ_DQP_MAX: i32 = 4;
716    let n = mb_w * mb_h;
717    if strength == 0.0 || n == 0 {
718        return vec![base_qp; n];
719    }
720    // Per-MB variance (the bit-cost weight) and its log2 (+1 avoids log2(0) on a flat
721    // MB → reads as maximally flat → finest QP).
722    let mut var = Vec::with_capacity(n);
723    let mut lv = Vec::with_capacity(n);
724    for my in 0..mb_h {
725        for mx in 0..mb_w {
726            let v = (mb_variance(sy, cw, mx, my) + 1) as f64;
727            var.push(v);
728            lv.push(v.log2());
729        }
730    }
731    let mean_lv = lv.iter().sum::<f64>() / n as f64;
732    // CONTENT-ADAPTIVE STRENGTH: back off where the log-variance SPREAD is high. A
733    // wide/bimodal spread means synthetic-ish content (flat regions beside detailed
734    // patterns) where "busy = maskable" FAILS and the patterns are salient — full AQ
735    // there costs PSNR. Natural content's spread is ~1 (keeps full strength); a
736    // synthetic pan's is ~6 (heavily reduced). Ramp 1.0→`AQ_SPREAD_MIN` over
737    // [`AQ_SPREAD_LO`, `AQ_SPREAD_HI`].
738    const AQ_SPREAD_LO: f64 = 1.5;
739    const AQ_SPREAD_HI: f64 = 5.0;
740    const AQ_SPREAD_MIN: f64 = 0.0; // extreme spread (pathological synthetic) → AQ OFF
741    let std_lv = (lv.iter().map(|&l| (l - mean_lv).powi(2)).sum::<f64>() / n as f64).sqrt();
742    let factor = (1.0 - (std_lv - AQ_SPREAD_LO) / (AQ_SPREAD_HI - AQ_SPREAD_LO)).clamp(AQ_SPREAD_MIN, 1.0);
743    let eff_strength = strength * factor;
744    // Per-MB QP shift (clamped): busy (log-var above mean) coarser, flat finer.
745    let dqp: Vec<i32> = lv
746        .iter()
747        .map(|&l| (eff_strength * (l - mean_lv)).round() as i32)
748        .map(|d| d.clamp(-AQ_DQP_MAX, AQ_DQP_MAX))
749        .collect();
750    // RATE COMPENSATION: AQ nets a rate change (coarsening a busy MB saves more bits
751    // than fining a flat one adds), so shift the whole frame's QP by `c` to restore
752    // the un-AQ rate — keeping `qp` meaningful. Bit model `bits_i ∝ var_i·2^(−qp_i/6)`
753    // (variance as the per-MB cost proxy): `c = 6·log2(Σ var·2^(−dqp/6) / Σ var)`.
754    let sum_v: f64 = var.iter().sum();
755    // `dqp` is clamped to [-AQ_DQP_MAX, AQ_DQP_MAX], so 2^(-d/6) has only nine
756    // possible values — but it was being recomputed with a `powf` for every
757    // macroblock of every frame. Same expression, evaluated once per offset:
758    // bit-identical, and it retires a transcendental from a per-macroblock loop.
759    let qstep: [f64; (2 * AQ_DQP_MAX + 1) as usize] =
760        std::array::from_fn(|i| 2f64.powf(-((i as i32 - AQ_DQP_MAX) as f64) / 6.0));
761    let sum_vs: f64 = var
762        .iter()
763        .zip(&dqp)
764        .map(|(&v, &d)| v * qstep[(d + AQ_DQP_MAX) as usize])
765        .sum();
766    let c = (6.0 * (sum_vs / sum_v).log2()).round() as i32;
767    dqp.iter()
768        .map(|&d| (base_qp as i32 + c + d).clamp(0, 51) as u8)
769        .collect()
770}
771
772/// Mean per-sampled-pixel residual after GLOBAL-motion compensation of `sy` from
773/// `ref_y` (coarse ±12 global ME + ±3 refine, subsampled interior). ~0 on a PURE pan
774/// (a single MV predicts the whole frame) — precisely the content where the local ME
775/// diamond never genuinely STALLS (its seed = the median = the pan MV is already
776/// right), so the `me_wide` rescue can only find SPURIOUS MVs that wreck the B-frame
777/// spatial-direct predictors. Gates `me_wide` off there — non-uniform content
778/// (real stalls, where me_wide wins) reads well above 0.
779/// Per-frame HEAD-ROOM probe for the `me_wide` rescue: on a small subsample of
780/// blocks, how much does a WIDE full-pel search beat a PREDICTOR-LOCAL one?
781///
782/// This measures what the rescue actually buys, before the macroblock loop and
783/// without committing any vector — unlike the online payoff gate, which scores its
784/// own SATD cost-cut *after* committing MVs and so only ever separated static
785/// content. Returns the mean relative SAD improvement, in percent.
786///
787/// Calibrated against the 20-clip per-clip BD truth table (docs/WHYS-speed-gap.md
788/// R5): me_wide earns its 1.4–5.1× on high-head-room content (bus +4.57, blue_sky
789/// +4.70, football +1.51, park_joy +0.91) and REGRESSES on low-head-room content
790/// (foreman_qcif −1.08, foreman_cif −0.16, tempete −0.12, mobile −0.03).
791///
792/// Deliberately PER-FRAME, not per-clip: cross-frame adaptive state is
793/// nondeterministic under the GOP-parallel encode path (a lesson already paid for
794/// by the rescue's own learning window).
795/// Head-room threshold (percent) for the `me_wide` frame gate. DEFAULT-ON at 16.
796///
797/// Calibrated on the DEPLOYED estimator (not the offline probe — they differ) and
798/// gated on the full 20-clip `video-tests` corpus plus four synthesized boundary
799/// clips, 4-QP BD-rate on PSNR and SSIM:
800///
801/// | | me_wide always-on | gated at 16 |
802/// |---|---|---|
803/// | real-corpus mean | +0.62% | +0.547% (88% retained) |
804/// | **worst clip** | **−1.08%** (foreman_qcif) | **0.00%** |
805/// | clips paying 1.1–3.6× for ~nothing | 13 | 0 |
806///
807/// Wins preserved: blue_sky +4.70, bus +4.37, park_joy +0.94, football +0.64,
808/// shields +0.20; synthesized fast-pan +6.73, rotation +1.72, zoom +1.11.
809/// Monotone non-regression — no clip is negative — which is what promotes this from
810/// a speed trade to a default.
811///
812/// `RFF_ME_HR=0` disables the gate and reproduces the pre-gate bytes exactly (the
813/// escape hatch / bisection anchor). Thresholds 13 and 16 both clear the boundary
814/// clip (foreman_cif +0.07 / +0.03); 10 does NOT (−0.23) — the threshold is
815/// calibrated on a narrow boundary pair, so treat it as re-tunable, not settled.
816fn me_wide_hr_thresh() -> f64 {
817    use std::sync::OnceLock;
818    static T: OnceLock<f64> = OnceLock::new();
819    *T.get_or_init(|| std::env::var("RFF_ME_HR").ok().and_then(|s| s.parse().ok()).unwrap_or(16.0))
820}
821
822/// Cached, because it is read per frame — an `env::var` there is its own tax.
823fn me_wide_hr_dbg() -> bool {
824    use std::sync::OnceLock;
825    static D: OnceLock<bool> = OnceLock::new();
826    *D.get_or_init(|| std::env::var_os("RFF_ME_HR_DBG").is_some())
827}
828
829fn me_wide_headroom(sy: &[u8], cw: usize, ch: usize, ref_y: &[u8]) -> f64 {
830    const LOCAL: isize = 2; // a well-seeded diamond's effective reach
831    const WIDE: isize = 24; // the rescue grid's half-extent
832    const STEP: isize = 4; // coarse: this is a frame-level statistic, not a search
833    const TARGET: usize = 24; // samples per frame — keep the probe ~0.5% of a frame
834    let sad16 = |bx: usize, by: usize, rx: isize, ry: isize| -> Option<u32> {
835        if rx < 0 || ry < 0 || rx as usize + 16 > cw || ry as usize + 16 > ch {
836            return None;
837        }
838        let (rx, ry) = (rx as usize, ry as usize);
839        let mut s = 0u32;
840        for dy in 0..16 {
841            let a = &sy[(by + dy) * cw + bx..][..16];
842            let b = &ref_y[(ry + dy) * cw + rx..][..16];
843            s += a.iter().zip(b).map(|(&p, &q)| p.abs_diff(q) as u32).sum::<u32>();
844        }
845        Some(s)
846    };
847    // Interior blocks only (the probe must not measure edge clamping), spread over
848    // the frame so one moving object cannot dominate.
849    let (mbw, mbh) = (cw / 16, ch / 16);
850    if mbw < 6 || mbh < 6 {
851        return 0.0;
852    }
853    let inner = (mbw - 4) * (mbh - 4);
854    let stride = (inner / TARGET).max(1);
855    let (mut acc, mut n) = (0.0f64, 0u32);
856    let mut i = 0usize;
857    while i < inner {
858        let (mx, my) = (2 + i % (mbw - 4), 2 + i / (mbw - 4));
859        let (bx, by) = (mx * 16, my * 16);
860        let mut best_local = u32::MAX;
861        for dy in -LOCAL..=LOCAL {
862            for dx in -LOCAL..=LOCAL {
863                if let Some(s) = sad16(bx, by, bx as isize + dx, by as isize + dy) {
864                    best_local = best_local.min(s);
865                }
866            }
867        }
868        let mut best_wide = best_local;
869        let mut dy = -WIDE;
870        while dy <= WIDE {
871            let mut dx = -WIDE;
872            while dx <= WIDE {
873                if let Some(s) = sad16(bx, by, bx as isize + dx, by as isize + dy) {
874                    best_wide = best_wide.min(s);
875                }
876                dx += STEP;
877            }
878            dy += STEP;
879        }
880        if best_local > 0 {
881            acc += (best_local - best_wide) as f64 / best_local as f64;
882            n += 1;
883        }
884        i += stride;
885    }
886    if n == 0 {
887        0.0
888    } else {
889        100.0 * acc / n as f64
890    }
891}
892
893fn global_mc_residual(sy: &[u8], cw: usize, ch: usize, ref_y: &[u8]) -> f64 {
894    if cw < 48 || ch < 48 {
895        return f64::INFINITY;
896    }
897    let sad = |dx: isize, dy: isize| -> u64 {
898        let mut s = 0u64;
899        let mut y = 16;
900        while y < ch - 16 {
901            let cbase = (y * cw) as isize;
902            let rbase = (y as isize + dy) * cw as isize + dx;
903            let mut x = 16isize;
904            while x < (cw - 16) as isize {
905                let c = sy[(cbase + x) as usize] as i32;
906                let r = ref_y[(rbase + x) as usize] as i32;
907                s += (c - r).unsigned_abs() as u64;
908                x += 8;
909            }
910            y += 8;
911        }
912        s
913    };
914    let (mut best, mut bc) = ((0isize, 0isize), u64::MAX);
915    let mut dy = -12;
916    while dy <= 12 {
917        let mut dx = -12;
918        while dx <= 12 {
919            let c = sad(dx, dy);
920            if c < bc {
921                bc = c;
922                best = (dx, dy);
923            }
924            dx += 4;
925        }
926        dy += 4;
927    }
928    for dy in best.1 - 3..=best.1 + 3 {
929        for dx in best.0 - 3..=best.0 + 3 {
930            let c = sad(dx, dy);
931            if c < bc {
932                bc = c;
933            }
934        }
935    }
936    let nx = (16..cw - 16).step_by(8).count();
937    let ny = (16..ch - 16).step_by(8).count();
938    bc as f64 / (nx * ny).max(1) as f64
939}
940
941/// Adds the mb-tree per-MB QP offset (TEMPORAL AQ — [`crate::mbtree`]) to the
942/// spatial-AQ `aq_qp` map in place. An empty `qpo` (mb-tree off) or a length
943/// mismatch is a no-op → byte-identical. Shared by the CAVLC and CABAC slice paths.
944fn apply_mbtree_qpo(aq_qp: &mut [u8], qpo: &[i32]) {
945    if qpo.len() == aq_qp.len() {
946        for (q, &o) in aq_qp.iter_mut().zip(qpo) {
947            *q = (*q as i32 + o).clamp(0, 51) as u8;
948        }
949    }
950}
951
952/// IMPLICIT bi-prediction weights `(w0, w1)` from POC distances (spec §8.4.2.3.2,
953/// `weighted_bipred_idc == 2`), IDENTICAL to the decoder's `implicit_weights`. The
954/// closer anchor gets more weight; an equidistant B (`bframes == 1`) yields 32:32,
955/// i.e. the plain average. `(32, 32)` fallback for the degenerate/out-of-range cases
956/// the decoder also averages (no long-term refs here).
957fn implicit_bi_weights(cur_poc: i32, l0_poc: i32, l1_poc: i32) -> (i32, i32) {
958    let td = (l1_poc - l0_poc).clamp(-128, 127);
959    let tb = (cur_poc - l0_poc).clamp(-128, 127);
960    if td == 0 {
961        return (32, 32);
962    }
963    let tx = (16384 + td.abs() / 2) / td;
964    let dsf = ((tb * tx + 32) >> 6).clamp(-1024, 1023);
965    let w1 = dsf >> 2;
966    if !(-64..=128).contains(&w1) {
967        return (32, 32);
968    }
969    (64 - w1, w1)
970}
971
972/// Bi-prediction blend of two motion-compensated samples `p` (List-0) and `q`
973/// (List-1) under weights `(w0, w1)` — the decoder's `b_mc` blend. `(32, 32)` is the
974/// plain `(p+q+1)>>1` average.
975#[inline(always)]
976fn bi_blend(p: i32, q: i32, w: (i32, i32)) -> u8 {
977    ((p * w.0 + q * w.1 + 32) >> 6).clamp(0, 255) as u8
978}
979
980/// Zig-zag scan of a raster i16 4×4 block into scan-order i32 — the fused-path
981/// twin of `scan_4x4_dcac(&q_blocks[..])`, reading quantized levels straight from
982/// the hot i16 DCT buffer. Byte-identical: the i16→i32 widening of a quant level
983/// is exact (levels always fit i16, being the input to the i16 idct kernel).
984#[cfg(accel)]
985#[inline]
986fn scan_4x4_dcac_i16(d: &[i16]) -> [i32; 16] {
987    [
988        d[0] as i32, d[1] as i32, d[4] as i32, d[8] as i32, d[5] as i32, d[2] as i32,
989        d[3] as i32, d[6] as i32, d[9] as i32, d[12] as i32, d[13] as i32, d[10] as i32,
990        d[7] as i32, d[11] as i32, d[14] as i32, d[15] as i32,
991    ]
992}
993
994
995/// Per-frame intra encoder state: reconstructed planes (coded size) and the
996/// per-4×4-block non-zero-coefficient counts used for CAVLC context.
997pub struct FrameEncoder {
998    mb_w: usize,
999    mb_h: usize,
1000    qp: u8,  // the CURRENT macroblock's target QPy (AQ varies it per MB)
1001    qpc: u8, // chroma QP for `qp`
1002    /// Running QPy of the last macroblock that coded an `mb_qp_delta` (spec QPY_PREV).
1003    /// `mb_qp_delta = qp − cur_qp`; a skip / cbp==0 MB codes no delta and inherits it.
1004    cur_qp: u8,
1005    /// Implicit bi-prediction weights `(w0, w1)` for the current B-frame (from its
1006    /// L0/L1 anchor POC distances). `(32, 32)` = plain average (P/I frames, `bframes
1007    /// == 1`); unequal for `bframes > 1`.
1008    bi_w: (i32, i32),
1009    cw: usize, // coded luma width
1010    ccw: usize, // coded chroma width
1011    // 16-byte aligned (the openh264 deblock/MC/intra asm load aligned row chunks).
1012    rec_y: AlignedBytes,
1013    rec_u: AlignedBytes,
1014    rec_v: AlignedBytes,
1015    nnz_y: Vec<u8>,    // (mb_w*4) x (mb_h*4)
1016    nnz_c: [Vec<u8>; 2], // each (mb_w*2) x (mb_h*2)
1017    modes_y: Vec<u8>,  // intra4x4 mode per 4×4 block (2=DC for I_16x16 blocks)
1018    coded_y: Vec<bool>, // whether each 4×4 block is reconstructed (top-right avail)
1019    mv_y: Vec<(i32, i32)>, // motion vector per 4×4 block (quarter-pel) — List-0
1020    inter_y: Vec<bool>, // whether each 4×4 block is inter-coded
1021    ref_idx_y: Vec<i32>, // reference index per 4×4 block (-1 = intra/uncoded) — List-0
1022    // B-slice List-1 motion field (empty for P/I). B_L1/B_Bi commit here so a later
1023    // partition's List-1 median predictor sees it, mirroring the decoder's
1024    // `mv_neighbors_list(.., 1)` over `mv1`/`ref_idx1`.
1025    mv1_y: Vec<(i32, i32)>,
1026    ref_idx1_y: Vec<i32>,
1027    idz: i64, // intra dead-zone divisor: 2 for all-intra, 3 when frames reference each other
1028    rdoq_strength: f64, // CABAC trellis (RDOQ) strength; 0 = off (hard quantize, CAVLC path)
1029    transform_8x8: bool, // High-profile 8x8 transform enabled (transform_8x8_mode_flag)
1030    sub8x8: bool, // P_8x8 sub-partition motion (four 8x8 MVs per MB)
1031    me_wide: bool, // adaptive wide ME grid search rescue (diamond stalls on flat surfaces)
1032    /// Track-B B2 for THIS frame: SAD-domain full-pel phase. Set at construction
1033    /// (force mode), or per frame by the `b2_mgain` dispatcher (mode 1).
1034    sadfp: bool,
1035    /// H-24 mv-cost SHAPE routing for THIS frame (mv_smooth mode 1), set by the
1036    /// same `b2_mgain` probe. Per-frame state, NOT a global: the GOP-parallel
1037    /// encode runs frames concurrently and a global store races across workers.
1038    mv_smooth: bool,
1039    /// H-13: search partition splits this frame (routed off on near-static frames).
1040    do_splits: bool,
1041    me_wide_var: u64, // per-pixel source variance below which a block is "flat"
1042    me_rescue: i64, // per-pixel residual SATD (on a flat block) that flags a diamond stall
1043    me_wide_coh: f64, // gate me_wide off when the frame's global-MC residual is below this (pure pan)
1044    me_range: i32, // rescue grid half-range in px (16 = ±16; wider reaches FAST motion the diamond misses)
1045    me_fast: bool, // also fire the rescue on HIGH-VARIANCE high-residual blocks (fast-motion stalls, not just flat)
1046    // ONLINE per-frame rescue-payoff gate (adaptive; WITHIN-frame so it stays
1047    // deterministic under the frame-parallel encode). Run the real rescue on the
1048    // first `me_learn` stalls of a frame, count how many the fine grid improves by
1049    // ≥6.25%, and if that fraction is below `me_payoff_pct`% disable the rescue for
1050    // the rest of the frame. This separates genuine diamond stalls (tsrc/zoom fine
1051    // grid improves ~33% of fires) from IRREDUCIBLE residual (rotation/fractal ~5-8%)
1052    // using the ACTUAL neighbour-seeded diamond — the only faithful signal (a cheap
1053    // SAD proxy from (0,0) inverts it: rot reads as highest-payoff). Frame-level, so
1054    // no per-block selection concentrates the B-direct-poisoning spurious MVs.
1055    me_learn: u32,
1056    me_payoff_pct: u32,
1057    /// U1 online sub-pel dispatcher (within-frame, so it stays deterministic under
1058    /// GOP-parallel encode). For the first `SP_LEARN` refinements of a frame we run
1059    /// the full 8-point+iterate pattern and accumulate how much of the total gain the
1060    /// FIRST ring captured; once the window fills, a frame whose gain is concentrated
1061    /// in ring 1 switches to the single-pass pattern for the rest of the frame.
1062    ///
1063    /// Harvested justification: ring-1 captures 63.7% of the gain on foreman (which
1064    /// loses +2.34% BD to a blanket single-pass) against 69.9–71.9% on bus/mobile
1065    /// (which lose only +0.30/+0.74% and gain 1.08–1.31×). The fraction separates the
1066    /// content that can afford the cut from the content that cannot.
1067    sp_single_pass: bool,
1068    /// U5-struct: when set, `motion_search` returns its FULL-PEL winner and skips
1069    /// sub-pel refinement entirely. The partition driver uses this to search all
1070    /// candidate shapes cheaply, pick one, and refine ONLY the winner's sub-blocks.
1071    /// Measured ceiling: 3.4–6.4× less sub-pel work (the losing shapes' refinements
1072    /// are pure waste), i.e. ~1.42× whole-encode at 44% sub-pel share.
1073    sp_defer: std::cell::Cell<bool>,
1074    sp_learn_n: std::cell::Cell<u32>,
1075    sp_ring1: std::cell::Cell<i64>,
1076    sp_total: std::cell::Cell<i64>,
1077    sp_1pass: std::cell::Cell<bool>,
1078    resc_n: std::cell::Cell<u32>,   // stalls the fine grid ran on this frame (learning phase)
1079    resc_big: std::cell::Cell<u32>, // of those, how many it improved ≥6.25%
1080    resc_off: std::cell::Cell<bool>, // rescue disabled for the rest of this frame
1081    inter8x8: u8, // inter 8x8-transform dispatch: 0=off, 1=always-RD, 2=content-adaptive
1082    inter8_pen: i64, // extra rate charge (nonzero-equiv) on the inter 8x8 candidate
1083    fast: bool, // Preset::Fast — SATD mode decision (no RDO), 16×16/I_16x16 only
1084    skip_accel_check: bool, // A/B knob: whole-MB psadbw gate in the P_Skip free-check
1085    coded_path_v2: bool,    // A/B knob: route inter coding through encode_inter_mb_v2
1086    tune_lambda_scale: f64, // tuning knob: scale on the RD λ (1.0 = standard)
1087    tune_intra_penalty: f64,
1088    satd_q: f64,               // adaptive: fraction of high-variance MBs routed to SATD cost
1089    subpel_force: bool,        // force sub-pel refinement even in the fast preset
1090    me_snap: bool,             // snap the diamond centre to integer-pel (see config)
1091    me_subpel_iter: bool,      // walk the sub-pel refine to convergence
1092    greedy_skip: bool,         // quality preset's SAD-thresholded P_Skip (PredictSadSkip)
1093    greedy_min_free: u32,      // online free-skip % gating greedy_skip on this frame
1094    rd_skip: bool,             // decide P_Skip by J = SSD + lambda*bits, not exact-zero residual
1095    rd_skip_min_free: u32,     // online free-skip % gating rd_skip on this frame
1096    rd_skip_fast_t: f64,       // skip-gate on SSD(skip)/lambda; <= 0 prices every candidate
1097    satd_var_thresh: i64,      // per-frame variance threshold for the routing (set in a pre-pass)
1098    aq_strength: f64,          // adaptive quantization: per-MB QP modulation strength (0 = off)
1099    mb_use_satd: bool,         // per-MB: this MB uses the SATD cost this decision
1100    // Per-MB luma nnz prediction cache (openh264 scan8 style): a padded 5×5 grid,
1101    // block (lbx,lby) at (lby+1)*5+(lbx+1); row 0 = top neighbours, col 0 = left.
1102    // Unavailable edges hold the sentinel 0x80, so the nnz predict is branchless.
1103    nnz_l_cache: [u8; 25],
1104    // Same, per chroma plane: a padded 3×3 grid for the 2×2 chroma blocks.
1105    nnz_c_cache: [[u8; 9]; 2],
1106    // openh264 predicted-SAD skip apparatus (per MB, mb_w×mb_h): the P_Skip
1107    // prediction's luma SAD, and whether the MB was actually skipped. The greedy
1108    // skip threshold for an MB is the median of its skip *neighbours'* skip SADs
1109    // (`PredictSadSkip`) — so skip propagates only from already-skip regions
1110    // (seeded by free skips) and self-limits, instead of a fixed bound that drifts.
1111    mb_skip_sad: Vec<u32>,
1112    mb_was_skip: Vec<bool>,
1113}
1114
1115/// A chosen inter coding for a macroblock: `mb_type` and, per partition, the
1116/// reference index and motion vector.
1117type InterChoice = (u8, Vec<(i32, (i32, i32))>);
1118
1119/// Approximate marginal rate (bits) of one `P_Skip` — it only lengthens the
1120/// surrounding `mb_skip_run` Exp-Golomb code slightly.
1121const SKIP_RATE_BITS: f64 = 1.0;
1122
1123
1124
1125/// EXTERNAL MV SCORING (`RFF_MV_CMP=1`). Holds another encoder's motion field
1126/// (per frame, 4x4-block raster) so our own coder can price ITS vectors against
1127/// ours under REAL coded bits instead of SATD — the only way to tell a bad search
1128/// from a bad cost function.
1129pub static EXT_MV: std::sync::Mutex<Vec<Vec<(i32, i32)>>> = std::sync::Mutex::new(Vec::new());
1130/// [n, our bits, ext bits, our SSD, ext SSD, ext won on J, MVs differing]
1131pub static MVCMP: [std::sync::atomic::AtomicU64; 7] = {
1132    const Z: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1133    [Z; 7]
1134};
1135pub static MVCMP_FRAME: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
1136/// Replace our chosen vector with the external field's, for EVERY macroblock where
1137/// that field used a single 16x16 partition. Transplanting one vector in isolation
1138/// is meaningless — `mvd` is coded against the NEIGHBOURS' vectors, so a lone
1139/// foreign vector prices against the wrong predictor. Only a whole coherent field
1140/// can be compared fairly.
1141fn mv_force_on() -> bool {
1142    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1143    *ON.get_or_init(|| std::env::var("RFF_MV_FORCE").map_or(false, |v| v != "0"))
1144}
1145fn mv_cmp_on() -> bool {
1146    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1147    *ON.get_or_init(|| std::env::var("RFF_MV_CMP").map_or(false, |v| v != "0"))
1148}
1149
1150/// [full-pel SATD evals, INTERPOLATED SATD evals] — `RFF_MC_COUNT=1`.
1151/// x264 precomputes half-pel planes once per frame; we run the 6-tap filter per
1152/// candidate, so this ratio prices that difference.
1153pub static MC_COUNT: [std::sync::atomic::AtomicU64; 2] = [
1154    std::sync::atomic::AtomicU64::new(0),
1155    std::sync::atomic::AtomicU64::new(0),
1156];
1157fn mc_count_on() -> bool {
1158    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1159    *ON.get_or_init(|| std::env::var("RFF_MC_COUNT").map_or(false, |v| v != "0"))
1160}
1161
1162/// [n, sum our cost, sum oracle cost, blocks the oracle beat us on, cost() evals]
1163pub static ME_PROBE: [std::sync::atomic::AtomicU64; 7] = {
1164    const Z: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1165    [Z; 7]
1166};
1167
1168/// Cached — an `env::var` inside the ME loop inflated it 4x when probed naively.
1169fn me_oracle_on() -> bool {
1170    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1171    *ON.get_or_init(|| std::env::var("RFF_ME_ORACLE").map_or(false, |v| v != "0"))
1172}
1173
1174/// RDO early-termination gate. Sub-partitions (16×8 / 8×16) only help at motion
1175/// boundaries, which show up as a heavy 16×16 residual; below this many coded bits
1176/// the 16×16 already fits, so skip their motion search and trials. (Intra is *not*
1177/// gated — it can win even against a cheap inter prediction, so gating it on inter
1178/// cost regresses compression badly on textured content.)
1179const SPLIT_GATE_BITS: f64 = 60.0;
1180
1181/// Fast preset: signalling-cost penalty (in bits, SATD-weighted by √λ) charged to
1182/// the intra candidate so it only wins a P-macroblock when its prediction is
1183/// clearly better than inter — intra's `mb_type` + modes cost more to signal.
1184const FAST_INTRA_PENALTY_BITS: f64 = 24.0;
1185
1186/// A snapshot of one macroblock's per-block grids and reconstruction region,
1187/// used to roll back a trial encode during RD mode decision.
1188///
1189/// Every field is a `Vec`, so building one from scratch is ten heap allocations.
1190/// The RD skip decision snapshots on EVERY candidate macroblock, which made that
1191/// allocation traffic the decision's dominant cost — hence
1192/// [`save_mb_into`](FrameEncoder::save_mb_into), which refills a reused buffer.
1193#[derive(Default)]
1194struct MbState {
1195    rec_y: Vec<u8>,
1196    rec_u: Vec<u8>,
1197    rec_v: Vec<u8>,
1198    nnz_y: Vec<u8>,
1199    nnz_c: [Vec<u8>; 2],
1200    mv_y: Vec<(i32, i32)>,
1201    inter_y: Vec<bool>,
1202    ref_idx_y: Vec<i32>,
1203    coded_y: Vec<bool>,
1204    modes_y: Vec<u8>,
1205    /// QPY_PREV. `qp_delta()` MUTATES this as a side effect of coding
1206    /// `mb_qp_delta`, so a trial encode advances it; without restoring it the
1207    /// real encode then codes its delta against the wrong predecessor and the
1208    /// decoder's QP diverges from the encoder's — a silent stream corruption,
1209    /// not a quality tweak.
1210    cur_qp: u8,
1211}
1212
1213/// Edge-clamped, coded-size source planes (luma, Cb, Cr).
1214/// Fast-preset pruned I4x4 mode search ({MPM, DC, V, H} instead of all 9 — the
1215/// x264-ultrafast-style candidate set). DEFAULT ON for the fast preset (gated:
1216/// +0.5% size at +0.02 dB on all-intra, +17% all-intra speed); RUSTY_FAST_INTRA=0
1217/// restores the exhaustive 9-mode search (the pre-flip bitstream).
1218fn fast_intra_enabled() -> bool {
1219    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1220    *ON.get_or_init(|| std::env::var("RUSTY_FAST_INTRA").map_or(true, |v| v != "0"))
1221}
1222
1223fn coded_source(cfg: &EncoderConfig, frame: &YuvFrame) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
1224    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncSource);
1225    let cw = cfg.mb_width() * 16;
1226    let ch = cfg.mb_height() * 16;
1227    // MB-aligned frame: the clamp is the identity — a plane memcpy (clone) replaces
1228    // the per-pixel clamp loop (bit-exact: same bytes).
1229    if frame.width == cw && frame.height == ch {
1230        return (frame.y.clone(), frame.u.clone(), frame.v.clone());
1231    }
1232    let y = clamp_plane(&frame.y, frame.width, frame.height, cw, ch);
1233    let u = clamp_plane(&frame.u, frame.chroma_width(), frame.chroma_height(), cw / 2, ch / 2);
1234    let v = clamp_plane(&frame.v, frame.chroma_width(), frame.chroma_height(), cw / 2, ch / 2);
1235    (y, u, v)
1236}
1237
1238/// Edge-extends `plane` from `w`×`h` to the coded `ow`×`oh`, replicating the last
1239/// row/column — the source form the MB grid needs.
1240///
1241/// Row-wise, because the per-pixel form is O(pixels) of scalar `min`+multiply and is
1242/// the DOMINANT cost of `enc-source-copy`: every frame whose height is not a multiple
1243/// of 16 takes this path, which includes all 1080p content (1080/16 = 67.5 → coded
1244/// height 1088). The stage measured 579 ms over the corpus while the three plane
1245/// clones on the MB-aligned fast path account for only ~135 ms of it.
1246///
1247/// Byte-identical to the per-pixel form (`clamp_plane_per_pixel`, kept as the test
1248/// oracle): `x.min(w-1)` is the identity below `w` and pins to the last column above
1249/// it, so a row is a `copy_from_slice` plus a `fill`; `y.min(h-1)` makes the
1250/// overhanging rows copies of the final row. Both lower to memcpy/memset.
1251fn clamp_plane(plane: &[u8], w: usize, h: usize, ow: usize, oh: usize) -> Vec<u8> {
1252    let mut out = vec![0u8; ow * oh];
1253    for y in 0..oh {
1254        let sy = y.min(h - 1);
1255        let src = &plane[sy * w..sy * w + w];
1256        let dst = &mut out[y * ow..y * ow + ow];
1257        if ow <= w {
1258            dst.copy_from_slice(&src[..ow]);
1259        } else {
1260            dst[..w].copy_from_slice(src);
1261            dst[w..].fill(src[w - 1]);
1262        }
1263    }
1264    out
1265}
1266
1267/// The original per-pixel edge extension — kept as the correctness oracle for
1268/// [`clamp_plane`], per the scalar-twin discipline.
1269#[cfg(test)]
1270fn clamp_plane_per_pixel(plane: &[u8], w: usize, h: usize, ow: usize, oh: usize) -> Vec<u8> {
1271    let mut out = vec![0u8; ow * oh];
1272    for y in 0..oh {
1273        for x in 0..ow {
1274            out[y * ow + x] = plane[y.min(h - 1) * w + x.min(w - 1)];
1275        }
1276    }
1277    out
1278}
1279
1280#[cfg(test)]
1281mod source_tests {
1282    use super::*;
1283
1284    #[test]
1285    fn clamp_plane_matches_per_pixel_oracle() {
1286        let mut s: u32 = 0xDEAD_BEEF;
1287        let mut rnd = || {
1288            s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
1289            (s >> 24) as u8
1290        };
1291        // Real coded geometries plus adversarial ones: width-only overhang,
1292        // height-only overhang (the 1080p case), both, and neither.
1293        let cases = [
1294            (1920usize, 1080usize, 1920usize, 1088usize), // 1080p luma
1295            (960, 540, 960, 544),                         // 1080p chroma
1296            (352, 288, 352, 288),                         // exactly aligned
1297            (100, 100, 112, 112),                         // both axes overhang
1298            (37, 5, 48, 16),                              // tiny + ragged
1299            (16, 1, 16, 16),                              // single source row
1300            (1, 1, 16, 16),                               // single sample
1301        ];
1302        for (w, h, ow, oh) in cases {
1303            let plane: Vec<u8> = (0..w * h).map(|_| rnd()).collect();
1304            assert_eq!(
1305                clamp_plane(&plane, w, h, ow, oh),
1306                clamp_plane_per_pixel(&plane, w, h, ow, oh),
1307                "clamp mismatch for {w}x{h} -> {ow}x{oh}"
1308            );
1309        }
1310    }
1311}
1312
1313impl FrameEncoder {
1314    fn new(cfg: &EncoderConfig) -> Self {
1315        let (mb_w, mb_h) = (cfg.mb_width(), cfg.mb_height());
1316        let (cw, ch) = (mb_w * 16, mb_h * 16);
1317        let (ccw, cch) = (cw / 2, ch / 2);
1318        Self {
1319            mb_w,
1320            mb_h,
1321            qp: cfg.qp,
1322            qpc: chroma_qp(cfg.qp),
1323            cur_qp: cfg.qp,
1324            bi_w: (32, 32),
1325            cw,
1326            ccw,
1327            rec_y: AlignedBytes::zeroed(cw * ch),
1328            rec_u: AlignedBytes::zeroed(ccw * cch),
1329            rec_v: AlignedBytes::zeroed(ccw * cch),
1330            nnz_y: vec![0; (mb_w * 4) * (mb_h * 4)],
1331            nnz_c: [vec![0; (mb_w * 2) * (mb_h * 2)], vec![0; (mb_w * 2) * (mb_h * 2)]],
1332            modes_y: vec![2; (mb_w * 4) * (mb_h * 4)],
1333            coded_y: vec![false; (mb_w * 4) * (mb_h * 4)],
1334            mv_y: vec![(0, 0); (mb_w * 4) * (mb_h * 4)],
1335            inter_y: vec![false; (mb_w * 4) * (mb_h * 4)],
1336            ref_idx_y: vec![-1; (mb_w * 4) * (mb_h * 4)],
1337            mv1_y: vec![(0, 0); (mb_w * 4) * (mb_h * 4)],
1338            ref_idx1_y: vec![-1; (mb_w * 4) * (mb_h * 4)],
1339            // All-intra (no inter references) tolerates the larger dead-zone; in
1340            // an I+P stream the IDR is a reference, so keep the standard offset.
1341            idz: if cfg.gop_size <= 1 { 2 } else { 3 },
1342            rdoq_strength: 0.0, // set >0 only in the CABAC slice coders
1343            transform_8x8: cfg.transform_8x8,
1344            // sub8x8 stays OPT-IN: the four P_8x8 sub-MVs feed the B-frames'
1345            // spatial-direct predictor, so on DIVERGENT motion (rotation/zoom/mixed)
1346            // it regresses with B-frames (mixed +0.24%, rot +0.42%, zoom +0.40%) —
1347            // a global effect its local RD gate can't see, and no clean dispatch
1348            // signal separates it yet (unlike me_wide's pure-pan coherence gate).
1349            // DEFAULT-ON for Quality (net real-content win; a 6-channel discovery
1350            // harvest proved no cheap gate beats always-on). Quality-only (Fast never
1351            // runs it). env RFF_SUB8X8 (0/1) > cfg.sub_8x8 (Some) > preset default.
1352            sub8x8: std::env::var("RFF_SUB8X8").ok().map(|s| s == "1")
1353                .or(cfg.sub_8x8)
1354                .unwrap_or(cfg.preset == crate::config::Preset::Quality),
1355            // me_wide is DEFAULT-ON for the Quality preset. VALIDATED 2026-07-27 on the
1356            // full 20-clip `video-tests` Derf corpus (4-QP BD-rate, PSNR+SSIM, anchor =
1357            // me_wide ON): **mean +0.62% BD-PSNR / +0.69% BD-SSIM**, i.e. turning it off
1358            // costs that much. Biggest wins blue_sky +4.70, bus +4.57, football +1.51,
1359            // park_joy +0.91; synthesized boundary content (smooth fast-pan / rotation /
1360            // zoom) reaches +2.6..+6.7%. The static clips (akiyo, FourPeople) sit at
1361            // exactly 0.00 at ~1.0x — the online payoff gate correctly disables it there.
1362            //
1363            // ⚠ UNFINISHED DISPATCH — the per-clip BD SIGN-FLIPS (+4.70 blue_sky ..
1364            // -1.08 foreman_qcif), and the cost when it fires is 1.0-5.1x. Worst value:
1365            // soccer_4cif 1.70x for +0.00, park_joy 5.08x for +0.91. `me_range` is NOT
1366            // the separating axis — it is a compromise dial (foreman_qcif loses at EVERY
1367            // range 24/16/8/4 = -1.08/-0.55/-0.50/-0.19 while blue_sky wins at every one
1368            // = +4.70/+3.10/+0.73), so shrinking it just trades the win away. The real
1369            // fix is a content signal that predicts the sign; the truth table for it is
1370            // in docs/WHYS-speed-gap.md.
1371            //
1372            // Quality-only (Fast never runs it). Precedence:
1373            // env RFF_ME_WIDE (0/1, for A/B) > cfg.me_wide (Some) > preset default.
1374            sadfp: me_sadfp_mode() == 2,
1375            mv_smooth: false,
1376            do_splits: true,
1377            me_wide: std::env::var("RFF_ME_WIDE").ok().map(|s| s == "1")
1378                .or(cfg.me_wide)
1379                .unwrap_or(cfg.preset == crate::config::Preset::Quality),
1380            me_wide_var: std::env::var("RFF_ME_WIDE_VAR").ok().and_then(|s| s.parse().ok()).unwrap_or(800),
1381            me_rescue: std::env::var("RFF_ME_RESCUE").ok().and_then(|s| s.parse().ok()).unwrap_or(3),
1382            me_wide_coh: std::env::var("RFF_ME_COH").ok().and_then(|s| s.parse().ok()).unwrap_or(4.0),
1383            me_range: std::env::var("RFF_ME_RANGE").ok().and_then(|s| s.parse().ok()).unwrap_or(24),
1384            me_fast: std::env::var("RFF_ME_FASTMO").map(|s| s != "0").unwrap_or(true),
1385            me_learn: std::env::var("RFF_ME_LEARN").ok().and_then(|s| s.parse().ok()).unwrap_or(40),
1386            me_payoff_pct: std::env::var("RFF_ME_PAYOFF").ok().and_then(|s| s.parse().ok()).unwrap_or(15),
1387            // U3: `balanced` runs SINGLE-PASS sub-pel. Measured on the 4-QP corpus,
1388            // a single pass captures 95.5–99.4% of the full refinement's BD benefit
1389            // (foreman −38.14 vs −39.94, mobile −49.38 vs −49.66, akiyo −26.10 vs
1390            // −26.43) for 1.03–1.31× less time — a straight Pareto improvement on the
1391            // preset. `RFF_SUBPEL_PAT=0` restores the full walk-to-convergence.
1392            sp_single_pass: cfg.preset == crate::config::Preset::Balanced,
1393            sp_defer: std::cell::Cell::new({
1394                let a = DEFER_SUBPEL.load(std::sync::atomic::Ordering::Relaxed) != 0
1395                    || std::env::var("RFF_DEFER_SUBPEL").map(|v| v != "0").unwrap_or(false);
1396                // ONLY the Quality preset runs the multi-shape partition driver. On the
1397                // fast/balanced path there is a single 16×16 candidate, so there is no
1398                // losing shape to skip — deferring there does not save the refinement,
1399                // it DELETES it (measured +91..+145% BD before this guard).
1400                a && cfg.preset == crate::config::Preset::Quality
1401            }),
1402            sp_learn_n: std::cell::Cell::new(0),
1403            sp_ring1: std::cell::Cell::new(0),
1404            sp_total: std::cell::Cell::new(0),
1405            sp_1pass: std::cell::Cell::new(false),
1406            resc_n: std::cell::Cell::new(0),
1407            resc_big: std::cell::Cell::new(0),
1408            resc_off: std::cell::Cell::new(false),
1409            inter8x8: std::env::var("RFF_INTER8")
1410                .ok()
1411                .and_then(|s| s.parse().ok())
1412                .unwrap_or(1),
1413            // ~2 bits per 8x8 luma block (×4) of CAVLC-8x8 overhead the level-aware
1414            // rate still under-charges (no native 8x8 entropy model in CAVLC). Keeps
1415            // the per-MB transform RD from over-picking 8x8 on fine-texture MBs where
1416            // it doesn't compact — content-adaptive: only decisively-favorable MBs win.
1417            inter8_pen: std::env::var("RFF_INTER8_PEN")
1418                .ok()
1419                .and_then(|s| s.parse().ok())
1420                .unwrap_or(8),
1421            // Balanced shares Fast's decision path; only sub-pel differs.
1422            fast: cfg.preset != crate::config::Preset::Quality,
1423            skip_accel_check: cfg.tune_skip_accel_check,
1424            coded_path_v2: cfg.coded_path_v2,
1425            aq_strength: cfg.aq_strength,
1426            tune_lambda_scale: cfg.tune_lambda_scale,
1427            tune_intra_penalty: cfg.tune_intra_penalty,
1428            satd_q: cfg.tune_satd_q,
1429            subpel_force: cfg.tune_subpel || cfg.preset == crate::config::Preset::Balanced,
1430            me_snap: cfg.tune_me_snap,
1431            me_subpel_iter: cfg.tune_me_subpel_iter,
1432            greedy_skip: cfg.tune_greedy_skip,
1433            greedy_min_free: cfg.tune_greedy_skip_min_free.unwrap_or(85),
1434            rd_skip: cfg.tune_rd_skip,
1435            rd_skip_fast_t: cfg.tune_rd_skip_fast_t.unwrap_or(0.0),
1436            rd_skip_min_free: cfg.tune_rd_skip_min_free.unwrap_or(
1437                if cfg.preset == crate::config::Preset::Fast { 60 } else { 90 },
1438            ),
1439            satd_var_thresh: i64::MAX,
1440            mb_use_satd: false,
1441            nnz_l_cache: [0x80; 25],
1442            nnz_c_cache: [[0x80; 9]; 2],
1443            mb_skip_sad: vec![0; mb_w * mb_h],
1444            mb_was_skip: vec![false; mb_w * mb_h],
1445        }
1446    }
1447
1448    /// openh264 `PredictSadSkip`: the greedy P_Skip threshold = the median of the
1449    /// skip SADs of the *skip* neighbours (left A, top B, top-right C, top-left
1450    /// fallback for C). Non-skip neighbours contribute 0, so with no skip neighbour
1451    /// the threshold is 0 (no greedy skip). This makes the skip self-calibrating —
1452    /// it only spreads where a neighbour already skipped at a comparable SAD.
1453    fn pred_skip_sad(&self, mb_x: usize, mb_y: usize) -> u32 {
1454        let mbw = self.mb_w;
1455        let at = |x: isize, y: isize| -> Option<(bool, u32)> {
1456            if x < 0 || y < 0 || x >= mbw as isize {
1457                return None;
1458            }
1459            let i = y as usize * mbw + x as usize;
1460            Some((self.mb_was_skip[i], self.mb_skip_sad[i]))
1461        };
1462        let a = at(mb_x as isize - 1, mb_y as isize); // left
1463        let b = at(mb_x as isize, mb_y as isize - 1); // top
1464        let c = at(mb_x as isize + 1, mb_y as isize - 1) // top-right
1465            .or_else(|| at(mb_x as isize - 1, mb_y as isize - 1)); // top-left fallback
1466        let sad = |n: Option<(bool, u32)>| n.filter(|&(s, _)| s).map_or(0, |(_, v)| v);
1467        let (sa, sb, sc) = (sad(a), sad(b), sad(c));
1468        // B and C unavailable but A available → A only.
1469        if b.is_none() && c.is_none() && a.is_some() {
1470            return sa;
1471        }
1472        match (
1473            a.is_some_and(|(s, _)| s),
1474            b.is_some_and(|(s, _)| s),
1475            c.is_some_and(|(s, _)| s),
1476        ) {
1477            (true, false, false) => sa,
1478            (false, true, false) => sb,
1479            (false, false, true) => sc,
1480            _ => sb.max(sa.min(sc)).min(sa.max(sc)), // median(sa, sb, sc)
1481        }
1482    }
1483
1484    /// The `mb_qp_delta` for the current macroblock (`qp − cur_qp`) and commits the
1485    /// running QPy — called ONLY where the syntax actually codes a delta (I_16x16
1486    /// always; inter / I_4x4 when `cbp != 0`), so a skip / cbp==0 MB leaves `cur_qp`
1487    /// unchanged and inherits it, exactly as the decoder's `step_qp` does.
1488    fn qp_delta(&mut self) -> i32 {
1489        let d = self.qp as i32 - self.cur_qp as i32;
1490        self.cur_qp = self.qp;
1491        d
1492    }
1493
1494    /// MV-predictor neighbors (left, above, above-right) for the 16×16 partition
1495    /// of macroblock `(mb_x, mb_y)`, read from the per-4×4-block grids.
1496    fn mv_neighbors(&self, mb_x: usize, mb_y: usize) -> [MvNeighbor; 3] {
1497        let w4 = self.mb_w * 4;
1498        let get = |avail: bool, bx: isize, by: isize| {
1499            if avail {
1500                let idx = by as usize * w4 + bx as usize;
1501                MvNeighbor {
1502                    available: true,
1503                    mv: self.mv_y[idx],
1504                    ref_idx: self.ref_idx_y[idx],
1505                }
1506            } else {
1507                MvNeighbor::NONE
1508            }
1509        };
1510        let (bx, by) = (mb_x as isize * 4, mb_y as isize * 4);
1511        let a = get(mb_x > 0, bx - 1, by);
1512        let b = get(mb_y > 0, bx, by - 1);
1513        // C = above-right; if unavailable, fall back to D = above-left.
1514        let c = if mb_y > 0 && mb_x + 1 < self.mb_w {
1515            get(true, bx + 4, by - 1)
1516        } else {
1517            get(mb_x > 0 && mb_y > 0, bx - 1, by - 1)
1518        };
1519        [a, b, c]
1520    }
1521
1522    /// The `P_Skip` motion vector (spec §8.4.1.1). P_Skip always references
1523    /// index 0 (the most recent picture).
1524    fn skip_mv(&self, mb_x: usize, mb_y: usize) -> (i32, i32) {
1525        let [a, b, c] = self.mv_neighbors(mb_x, mb_y);
1526        if !a.available
1527            || !b.available
1528            || (a.ref_idx == 0 && a.mv == (0, 0))
1529            || (b.ref_idx == 0 && b.mv == (0, 0))
1530        {
1531            (0, 0)
1532        } else {
1533            predict_mv(a, b, c, 0)
1534        }
1535    }
1536
1537    /// Records a macroblock's per-4×4-block motion state (`ref` = reference index
1538    /// for inter, ignored for intra where `inter` is false).
1539    fn set_mb_mv(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32), inter: bool, refi: i32) {
1540        let w4 = self.mb_w * 4;
1541        for dy in 0..4 {
1542            for dx in 0..4 {
1543                let idx = (mb_y * 4 + dy) * w4 + (mb_x * 4 + dx);
1544                self.mv_y[idx] = mv;
1545                self.inter_y[idx] = inter;
1546                self.ref_idx_y[idx] = if inter { refi } else { -1 };
1547            }
1548        }
1549    }
1550
1551    /// Block-level MV-predictor neighbors for a partition whose top-left 4×4
1552    /// block is `(pbx, pby)` and which is `pwb` blocks wide. Availability uses
1553    /// the decoded-block grid, so in-macroblock partitions see earlier ones.
1554    fn mv_neighbors_block(&self, pbx: isize, pby: isize, pwb: isize) -> [MvNeighbor; 3] {
1555        let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
1556        let get = |bx: isize, by: isize| -> MvNeighbor {
1557            if bx < 0 || by < 0 || bx >= w4 || by >= h4 || !self.coded_y[(by * w4 + bx) as usize] {
1558                MvNeighbor::NONE
1559            } else {
1560                let idx = (by * w4 + bx) as usize;
1561                MvNeighbor { available: true, mv: self.mv_y[idx], ref_idx: self.ref_idx_y[idx] }
1562            }
1563        };
1564        let a = get(pbx - 1, pby);
1565        let b = get(pbx, pby - 1);
1566        let mut c = get(pbx + pwb, pby - 1);
1567        if !c.available {
1568            c = get(pbx - 1, pby - 1); // D fallback
1569        }
1570        [a, b, c]
1571    }
1572
1573    /// List-aware block MV-predictor neighbors (`list` 0 or 1), for the B-slice
1574    /// per-list `mvd` predictor. Identical geometry to [`Self::mv_neighbors_block`]
1575    /// but reads the List-1 motion grid when `list == 1`, matching the decoder's
1576    /// `mv_neighbors_list`. A neighbor not coded in this list reads `ref_idx = -1`
1577    /// (so `predict_partition_mv` treats it as non-matching, exactly as the decoder).
1578    fn mv_neighbors_block_list(&self, pbx: isize, pby: isize, pwb: isize, list: usize) -> [MvNeighbor; 3] {
1579        let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
1580        let (mvg, refg): (&[(i32, i32)], &[i32]) = if list == 0 {
1581            (&self.mv_y, &self.ref_idx_y)
1582        } else {
1583            (&self.mv1_y, &self.ref_idx1_y)
1584        };
1585        let get = |bx: isize, by: isize| -> MvNeighbor {
1586            if bx < 0 || by < 0 || bx >= w4 || by >= h4 || !self.coded_y[(by * w4 + bx) as usize] {
1587                MvNeighbor::NONE
1588            } else {
1589                let idx = (by * w4 + bx) as usize;
1590                MvNeighbor { available: true, mv: mvg[idx], ref_idx: refg[idx] }
1591            }
1592        };
1593        let a = get(pbx - 1, pby);
1594        let b = get(pbx, pby - 1);
1595        let mut c = get(pbx + pwb, pby - 1);
1596        if !c.available {
1597            c = get(pbx - 1, pby - 1); // D fallback
1598        }
1599        [a, b, c]
1600    }
1601
1602    /// SATD cost of a motion-compensated `rw`×`rh` luma region against the source —
1603    /// THE per-candidate ME cost function (Challenge-1 A2 shape: the per-search
1604    /// invariants arrive as parameters instead of being re-derived per candidate).
1605    /// `hp` is the already-resolved plane cache (`None` ⇔ the fast preset, whose
1606    /// SATD path never reads planes), `hr_on` the hoisted `RFF_HPEL_REF` knob,
1607    /// `src_row` the hoisted source slice base. Dispatch order (interior full-pel →
1608    /// in-place plane read → fused avg+SATD → materialize → `mc_luma` fallback) is
1609    /// the historical `mc_satd` order, so the accepted candidate set — and the
1610    /// bitstream — are byte-identical to it.
1611    #[allow(clippy::too_many_arguments)]
1612    #[inline]
1613    fn mc_satd_hp(
1614        &self,
1615        reference: &crate::RefFrame,
1616        hp: Option<&rusty_h264_common::inter::HpelPlanes>,
1617        hr_on: bool,
1618        // `hr_on && RFF_SATD_AVG` (and accel compiled in) — hoisted per search like
1619        // `hr_on`, so the fused-kernel gate costs zero OnceLock loads per candidate.
1620        // Unused (and always false) on non-accel builds.
1621        sa_on: bool,
1622        src_row: &[u8],
1623        lx: usize,
1624        ly: usize,
1625        rw: usize,
1626        rh: usize,
1627        mv: (i32, i32),
1628    ) -> i64 {
1629        #[cfg(not(accel))]
1630        let _ = sa_on;
1631        #[cfg(feature = "profile")]
1632        let _site = rusty_h264_common::inter::mcstats::SiteTag::new(2);
1633        let ch = self.mb_h * 16;
1634        let cw = self.cw;
1635        let (ix0, iy0) = (lx as isize + (mv.0 >> 2) as isize, ly as isize + (mv.1 >> 2) as isize);
1636        let interior_fullpel = mv.0 & 3 == 0
1637            && mv.1 & 3 == 0
1638            && ix0 >= 0
1639            && iy0 >= 0
1640            && ix0 + rw as isize <= cw as isize
1641            && iy0 + rh as isize <= ch as isize;
1642        #[cfg(feature = "profile")]
1643        {
1644            let fullpel = mv.0 & 3 == 0 && mv.1 & 3 == 0;
1645            satdpath::bump(if interior_fullpel { 0 } else if fullpel { 1 } else { 2 });
1646        }
1647        if interior_fullpel {
1648            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeCost);
1649            let (rx0, ry0) = (ix0 as usize, iy0 as usize);
1650            return satd_px(src_row, cw, &reference.y[ry0 * cw + rx0..], cw, rw, rh);
1651        }
1652        if let Some(hp) = hp {
1653            if hr_on {
1654                if let Some((plane, base, stride)) =
1655                    rusty_h264_common::inter::hpel_ref(hp, lx, ly, rw, rh, mv.0, mv.1)
1656                {
1657                    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeCost);
1658                    return satd_px(src_row, cw, &plane[base..], stride, rw, rh);
1659                }
1660            }
1661            // A3: QUARTER-pel — fuse the two-plane (a+b+1)>>1 average into the
1662            // SATD kernel itself (no 256-byte materialize + reload, no FFI hop).
1663            // `satd_avg` returns the exact `Σ|H·d|` that `satd_px` computes on
1664            // the materialized average, so the cost value — and the bitstream —
1665            // are byte-identical; on non-AVX2 (or a declined size) it returns
1666            // `None` and the old materialize path below runs unchanged.
1667            #[cfg(accel)]
1668            if sa_on {
1669                if let Some((pa, ba, pb, bb, stride)) =
1670                    rusty_h264_common::inter::hpel_qpel_refs(hp, lx, ly, rw, rh, mv.0, mv.1)
1671                {
1672                    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeCost);
1673                    if let Some(v) = rusty_h264_accel::satd_avg(
1674                        src_row, cw, &pa[ba..], &pb[bb..], stride, rw, rh,
1675                    ) {
1676                        return v as i64;
1677                    }
1678                }
1679            }
1680            let mut pred = [0u8; 256];
1681            if rusty_h264_common::inter::hpel_block(hp, lx, ly, rw, rh, mv.0, mv.1, &mut pred) {
1682                return satd_px(src_row, cw, &pred, rw, rw, rh);
1683            }
1684            mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
1685            return satd_px(src_row, cw, &pred, rw, rw, rh);
1686        }
1687        let mut pred = [0u8; 256];
1688        mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
1689        satd_px(src_row, cw, &pred, rw, rw, rh)
1690    }
1691
1692    /// Track-B B2.1: the SAD twin of `mc_satd_hp` — the SAME dispatch ladder
1693    /// (interior full-pel → in-place plane read → fused avg → materialize →
1694    /// `mc_luma`), with SAD (`psadbw`-class) distortion. `mc_sad` (the fast
1695    /// preset's function) had NONE of the SATD path's accumulated wins, so the
1696    /// first B2 cut measured 61% MORE `mc_luma` fallbacks; this is the parity fix.
1697    /// Every arm reads the same samples the materializing path would, so the SAD
1698    /// value — and therefore the B2-on bitstream — is unchanged by this function.
1699    #[allow(clippy::too_many_arguments)]
1700    #[inline]
1701    fn mc_sad_hp(
1702        &self,
1703        reference: &crate::RefFrame,
1704        hp: Option<&rusty_h264_common::inter::HpelPlanes>,
1705        hr_on: bool,
1706        src_row: &[u8],
1707        lx: usize,
1708        ly: usize,
1709        rw: usize,
1710        rh: usize,
1711        mv: (i32, i32),
1712        _asrc: Option<&[u8; 256]>,
1713    ) -> i64 {
1714        #[cfg(feature = "profile")]
1715        let _site = rusty_h264_common::inter::mcstats::SiteTag::new(2);
1716        let ch = self.mb_h * 16;
1717        let cw = self.cw;
1718        let (ix0, iy0) = (lx as isize + (mv.0 >> 2) as isize, ly as isize + (mv.1 >> 2) as isize);
1719        let interior_fullpel = mv.0 & 3 == 0
1720            && mv.1 & 3 == 0
1721            && ix0 >= 0
1722            && iy0 >= 0
1723            && ix0 + rw as isize <= cw as isize
1724            && iy0 + rh as isize <= ch as isize;
1725        if interior_fullpel {
1726            let (rx0, ry0) = (ix0 as usize, iy0 as usize);
1727            #[cfg(accel)]
1728            if rw == 16 && rh == 16 {
1729                if let Some(src) = _asrc {
1730                    return rusty_h264_accel::sad_16x16(src, 16, &reference.y[ry0 * cw + rx0..], cw)
1731                        as i64;
1732                }
1733            }
1734            return sad_strided(src_row, cw, &reference.y[ry0 * cw + rx0..], cw, rw, rh);
1735        }
1736        if let Some(hp) = hp {
1737            if hr_on {
1738                // Single-plane phases (h/v/c half-pel AND edge full-pel via the
1739                // padded `f` plane — the E-3 move, which `mc_sad` never had).
1740                if let Some((plane, base, stride)) =
1741                    rusty_h264_common::inter::hpel_ref(hp, lx, ly, rw, rh, mv.0, mv.1)
1742                {
1743                    return sad_strided(src_row, cw, &plane[base..], stride, rw, rh);
1744                }
1745                // Quarter-pel: fused (a+b+1)>>1 + SAD, no materialize.
1746                if let Some((pa, ba, pb, bb, stride)) =
1747                    rusty_h264_common::inter::hpel_qpel_refs(hp, lx, ly, rw, rh, mv.0, mv.1)
1748                {
1749                    return sad_avg_strided(src_row, cw, &pa[ba..], &pb[bb..], stride, rw, rh);
1750                }
1751            }
1752            let mut pred = [0u8; 256];
1753            if rusty_h264_common::inter::hpel_block(hp, lx, ly, rw, rh, mv.0, mv.1, &mut pred) {
1754                return sad_strided(src_row, cw, &pred, rw, rw, rh);
1755            }
1756            mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
1757            return sad_strided(src_row, cw, &pred, rw, rw, rh);
1758        }
1759        let mut pred = [0u8; 256];
1760        mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
1761        sad_strided(src_row, cw, &pred, rw, rw, rh)
1762    }
1763
1764    /// SAD (sum of absolute differences) of a motion-compensated `rw`×`rh` luma
1765    /// region against the source — the **fast** preset's motion-search cost.
1766    ///
1767    /// SAD is far cheaper than SATD (no Hadamard transform), and the inner loop is
1768    /// written as `Σ a.abs_diff(b)` over `u8` slices, the exact pattern LLVM
1769    /// auto-vectorizes to the `psadbw` SAD instruction — the same instruction
1770    /// x264's hand-written assembly uses, but reached without any `unsafe`. (x264's
1771    /// fast presets use SAD for the full-pel search for precisely this reason.)
1772    #[allow(clippy::too_many_arguments)]
1773    fn mc_sad(
1774        &self,
1775        reference: &crate::RefFrame,
1776        sy: &[u8],
1777        lx: usize,
1778        ly: usize,
1779        rw: usize,
1780        rh: usize,
1781        mv: (i32, i32),
1782        // 16-aligned source MB (built once per search) for the asm SAD; `None`
1783        // (and unused) on the scalar build.
1784        _asrc: Option<&[u8; 256]>,
1785    ) -> i64 {
1786        // Descent E depth-6: tag WHO is calling mc_luma. The search's edge fallback and
1787        // reconstruction land in the same `inter-mc` bucket; pricing a recon-side lever
1788        // against the merged total is pricing the wrong population.
1789        #[cfg(feature = "profile")]
1790        let _site = rusty_h264_common::inter::mcstats::SiteTag::new(2);
1791        let ch = self.mb_h * 16;
1792        let cw = self.cw;
1793        let (ix0, iy0) = (lx as isize + (mv.0 >> 2) as isize, ly as isize + (mv.1 >> 2) as isize);
1794        let interior_fullpel = mv.0 & 3 == 0
1795            && mv.1 & 3 == 0
1796            && ix0 >= 0
1797            && iy0 >= 0
1798            && ix0 + rw as isize <= cw as isize
1799            && iy0 + rh as isize <= ch as isize;
1800        // Full-pel interior 16×16: openh264's `psadbw` SAD of the aligned source vs
1801        // the (movdqu) reference block. SAD is exact, so this is byte-identical to the
1802        // scalar path — a pure ME speedup (~2.4× the kernel).
1803        #[cfg(accel)]
1804        if interior_fullpel && rw == 16 && rh == 16 {
1805            if let Some(src) = _asrc {
1806                let (rx0, ry0) = (ix0 as usize, iy0 as usize);
1807                return rusty_h264_accel::sad_16x16(src, 16, &reference.y[ry0 * cw + rx0..], cw)
1808                    as i64;
1809            }
1810        }
1811        let mut sad = 0u32;
1812        if interior_fullpel {
1813            // Direct from the reference (a copy at full-pel) — no interpolation.
1814            let (rx0, ry0) = (ix0 as usize, iy0 as usize);
1815            let refy = &reference.y;
1816            for dy in 0..rh {
1817                let s = &sy[(ly + dy) * cw + lx..][..rw];
1818                let r = &refy[(ry0 + dy) * cw + rx0..][..rw];
1819                sad += s.iter().zip(r).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
1820            }
1821        } else {
1822            let mut pred = [0u8; 256];
1823            // Same plane-cache read (and same preset gate) as `mc_satd`.
1824            let from_planes = !self.fast
1825                && rusty_h264_common::inter::hpel_block(
1826                    reference.hpel(cw, ch),
1827                    lx,
1828                    ly,
1829                    rw,
1830                    rh,
1831                    mv.0,
1832                    mv.1,
1833                    &mut pred,
1834                );
1835            if !from_planes {
1836                mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
1837            }
1838            for dy in 0..rh {
1839                let s = &sy[(ly + dy) * cw + lx..][..rw];
1840                let p = &pred[dy * rw..][..rw];
1841                sad += s.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
1842            }
1843        }
1844        sad as i64
1845    }
1846
1847    /// Luma distortion of a `B_Bi` 16×16 prediction: motion-compensate `l0`/`l1`,
1848    /// average `(p+q+1)>>1` (the decoder's `b_mc` blend at `weighted_bipred_idc=0`),
1849    /// and score vs the source with the SAME metric the per-list searches used —
1850    /// SAD on the fast path, SATD when this MB is SATD-routed — so `J_bi` compares
1851    /// directly against `J0`/`J1`.
1852    fn bi_dist(
1853        &self,
1854        l0: &crate::RefFrame,
1855        l1: &crate::RefFrame,
1856        sy: &[u8],
1857        lx: usize,
1858        ly: usize,
1859        mv0: (i32, i32),
1860        mv1: (i32, i32),
1861    ) -> i64 {
1862        // Descent E/F: identify this mc_luma population by call site.
1863        #[cfg(feature = "profile")]
1864        let _site = rusty_h264_common::inter::mcstats::SiteTag::new(4);
1865        let ch = self.mb_h * 16;
1866        let (mut a, mut b) = ([0u8; 256], [0u8; 256]);
1867        mc_luma(&l0.y, self.cw, ch, lx, ly, 16, 16, mv0.0, mv0.1, &mut a);
1868        mc_luma(&l1.y, self.cw, ch, lx, ly, 16, 16, mv1.0, mv1.1, &mut b);
1869        let mut avg = [0u8; 256];
1870        for i in 0..256 {
1871            avg[i] = bi_blend(a[i] as i32, b[i] as i32, self.bi_w);
1872        }
1873        if self.fast && !self.mb_use_satd {
1874            let mut sad = 0u32;
1875            for dy in 0..16 {
1876                let s = &sy[(ly + dy) * self.cw + lx..][..16];
1877                let p = &avg[dy * 16..][..16];
1878                sad += s.iter().zip(p).map(|(&x, &y)| x.abs_diff(y) as u32).sum::<u32>();
1879            }
1880            sad as i64
1881        } else {
1882            satd_px(&sy[ly * self.cw + lx..], self.cw, &avg, 16, 16, 16)
1883        }
1884    }
1885
1886    /// Distortion of a pre-formed 16×16 luma prediction vs the source (SAD on the
1887    /// fast path, SATD when SATD-routed) — the mode-decision cost for `B_Direct`,
1888    /// on the same scale as the per-list search J so they compare directly.
1889    fn pred_dist(&self, sy: &[u8], lx: usize, ly: usize, pred: &[u8; 256]) -> i64 {
1890        if self.fast && !self.mb_use_satd {
1891            let mut sad = 0u32;
1892            for dy in 0..16 {
1893                let s = &sy[(ly + dy) * self.cw + lx..][..16];
1894                let p = &pred[dy * 16..][..16];
1895                sad += s.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
1896            }
1897            sad as i64
1898        } else {
1899            satd_px(&sy[ly * self.cw + lx..], self.cw, pred, 16, 16, 16)
1900        }
1901    }
1902
1903    /// `colZeroFlag` for absolute 4×4 block `(bx, by)` (spec §8.4.1.2.2): true when
1904    /// the co-located picture `RefPicList1[0]` (`l1`) is short-term (always, here —
1905    /// we use no long-term refs) and its co-located block uses List-0 reference 0
1906    /// with a near-zero (|·| ≤ 1) motion vector. Must match the decoder's `col_zero`.
1907    fn col_zero(&self, l1: &crate::RefFrame, bx: usize, by: usize) -> bool {
1908        if l1.w4 == 0 {
1909            return false;
1910        }
1911        let idx = by * l1.w4 + bx;
1912        if idx >= l1.ref_idx.len() {
1913            return false;
1914        }
1915        l1.ref_idx[idx] == 0 && l1.mv[idx].0.abs() <= 1 && l1.mv[idx].1.abs() <= 1
1916    }
1917
1918    /// Bi-predictive MC of one small region into `pred_y`/`c_pred` at MB-relative
1919    /// offset `(dx, dy)` — the per-4×4 primitive the spatial-direct derivation uses.
1920    /// Mirrors the decoder's `b_mc` (average `(p+q+1)>>1` for bi, copy for uni).
1921    #[allow(clippy::too_many_arguments)]
1922    fn b_mc_block(
1923        &self,
1924        l0: &crate::RefFrame,
1925        l1: &crate::RefFrame,
1926        mb_x: usize,
1927        mb_y: usize,
1928        dx: usize,
1929        dy: usize,
1930        refi0: i32,
1931        m0: (i32, i32),
1932        refi1: i32,
1933        m1: (i32, i32),
1934        pred_y: &mut [u8; 256],
1935        c_pred: &mut [[u8; 64]; 2],
1936    ) {
1937        // Descent E/F: identify this mc_luma population by call site.
1938        #[cfg(feature = "profile")]
1939        let _site = rusty_h264_common::inter::mcstats::SiteTag::new(4);
1940        let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
1941        let (px, py) = (mb_x * 16 + dx, mb_y * 16 + dy);
1942        let (mut a, mut b) = ([0u8; 16], [0u8; 16]);
1943        // 4-wide luma exists ONLY here: P partitions bottom out at 8×8, so B-frame
1944        // spatial-direct is the encoder's only 4×4 MC. With B-frames on it is ~8% of
1945        // all MC calls and HALF of all sub-pel ones — and `luma_h`/`luma_v` dispatch
1946        // to asm only at width 16/8, so 4-wide otherwise runs the scalar 6-tap.
1947        // Serving it from the cached half-pel planes (bit-identical, and they are
1948        // already built for this reference by the motion search) is strictly better
1949        // than adding a 4-wide asm kernel.
1950        let mc4 = |r: &crate::RefFrame, mv: (i32, i32), out: &mut [u8; 16]| {
1951            if !self.fast
1952                && bdirect_planes_enabled()
1953                && rusty_h264_common::inter::hpel_block(
1954                    r.hpel(self.cw, ch), px, py, 4, 4, mv.0, mv.1, out,
1955                )
1956            {
1957                return;
1958            }
1959            mc_luma(&r.y, self.cw, ch, px, py, 4, 4, mv.0, mv.1, out);
1960        };
1961        if refi0 >= 0 {
1962            mc4(l0, m0, &mut a);
1963        }
1964        if refi1 >= 0 {
1965            mc4(l1, m1, &mut b);
1966        }
1967        for yy in 0..4 {
1968            for xx in 0..4 {
1969                let i = yy * 4 + xx;
1970                let v = match (refi0 >= 0, refi1 >= 0) {
1971                    (true, true) => bi_blend(a[i] as i32, b[i] as i32, self.bi_w),
1972                    (true, false) => a[i],
1973                    _ => b[i],
1974                };
1975                pred_y[(dy + yy) * 16 + (dx + xx)] = v;
1976            }
1977        }
1978        // Chroma: the co-located 2×2 block at half resolution.
1979        let (cpx, cpy) = (mb_x * 8 + dx / 2, mb_y * 8 + dy / 2);
1980        for c in 0..2 {
1981            let (r0, r1) = if c == 0 { (&l0.u, &l1.u) } else { (&l0.v, &l1.v) };
1982            let (mut ca, mut cb) = ([0u8; 4], [0u8; 4]);
1983            if refi0 >= 0 {
1984                mc_chroma(r0, self.ccw, cch, cpx, cpy, 2, 2, m0.0, m0.1, &mut ca);
1985            }
1986            if refi1 >= 0 {
1987                mc_chroma(r1, self.ccw, cch, cpx, cpy, 2, 2, m1.0, m1.1, &mut cb);
1988            }
1989            for yy in 0..2 {
1990                for xx in 0..2 {
1991                    let i = yy * 2 + xx;
1992                    let v = match (refi0 >= 0, refi1 >= 0) {
1993                        (true, true) => bi_blend(ca[i] as i32, cb[i] as i32, self.bi_w),
1994                        (true, false) => ca[i],
1995                        _ => cb[i],
1996                    };
1997                    c_pred[c][(dy / 2 + yy) * 8 + (dx / 2 + xx)] = v;
1998                }
1999            }
2000        }
2001    }
2002
2003    /// Spatial-direct (`direct_spatial_mv_pred_flag == 1`) prediction for a 16×16 B
2004    /// macroblock — the shared basis of `B_Skip` and `B_Direct_16x16`. Returns the
2005    /// prediction and the per-4×4 `(refIdxL0, mvL0, refIdxL1, mvL1)` motion the
2006    /// decoder's `decode_b_direct` derives (so the caller commits identical motion).
2007    fn b_direct(
2008        &self,
2009        l0: &crate::RefFrame,
2010        l1: &crate::RefFrame,
2011        mb_x: usize,
2012        mb_y: usize,
2013    ) -> ([u8; 256], [[u8; 64]; 2], [(i32, (i32, i32), i32, (i32, i32)); 16]) {
2014        let (nbx, nby) = ((mb_x * 4) as isize, (mb_y * 4) as isize);
2015        let n0 = self.mv_neighbors_block_list(nbx, nby, 4, 0);
2016        let n1 = self.mv_neighbors_block_list(nbx, nby, 4, 1);
2017        let min_pos = |a: i32, b: i32| if a < 0 { b } else if b < 0 { a } else { a.min(b) };
2018        let rid = |n: &[MvNeighbor; 3]| min_pos(min_pos(n[0].ref_idx, n[1].ref_idx), n[2].ref_idx);
2019        let (mut refi0, mut refi1) = (rid(&n0), rid(&n1));
2020        let direct_zero = refi0 < 0 && refi1 < 0;
2021        if direct_zero {
2022            refi0 = 0;
2023            refi1 = 0;
2024        }
2025        let mv0 = if refi0 >= 0 && !direct_zero { predict_mv(n0[0], n0[1], n0[2], refi0) } else { (0, 0) };
2026        let mv1 = if refi1 >= 0 && !direct_zero { predict_mv(n1[0], n1[1], n1[2], refi1) } else { (0, 0) };
2027        let mut pred_y = [0u8; 256];
2028        let mut c_pred = [[0u8; 64]; 2];
2029        let mut motion = [(0i32, (0i32, 0i32), 0i32, (0i32, 0i32)); 16];
2030        for sby in 0..4 {
2031            for sbx in 0..4 {
2032                let cz = !direct_zero && self.col_zero(l1, mb_x * 4 + sbx, mb_y * 4 + sby);
2033                let m0 = if refi0 == 0 && cz { (0, 0) } else { mv0 };
2034                let m1 = if refi1 == 0 && cz { (0, 0) } else { mv1 };
2035                motion[sby * 4 + sbx] = (refi0, m0, refi1, m1);
2036                self.b_mc_block(l0, l1, mb_x, mb_y, sbx * 4, sby * 4, refi0, m0, refi1, m1, &mut pred_y, &mut c_pred);
2037            }
2038        }
2039        (pred_y, c_pred, motion)
2040    }
2041
2042    /// Commits a spatial-direct MB's per-4×4 motion into the List-0/List-1 grids so
2043    /// later MBs' neighbor predictors see it (mirrors the decoder's `b_set_motion`).
2044    fn commit_direct_motion(&mut self, mb_x: usize, mb_y: usize, motion: &[(i32, (i32, i32), i32, (i32, i32)); 16]) {
2045        let w4 = self.mb_w * 4;
2046        for sby in 0..4 {
2047            for sbx in 0..4 {
2048                let (refi0, m0, refi1, m1) = motion[sby * 4 + sbx];
2049                let idx = (mb_y * 4 + sby) * w4 + (mb_x * 4 + sbx);
2050                self.inter_y[idx] = true;
2051                self.coded_y[idx] = true;
2052                self.mv_y[idx] = m0;
2053                self.ref_idx_y[idx] = refi0;
2054                self.mv1_y[idx] = m1;
2055                self.ref_idx1_y[idx] = refi1;
2056            }
2057        }
2058    }
2059
2060    /// Rate-aware motion search for a luma region: full-pel diamond + half/
2061    /// quarter-pel refinement minimizing `J = SATD + λ·bits(mvd)`, where the
2062    /// motion cost is measured against `predictors[0]` (the MV predictor the
2063    /// `mvd` will actually be coded against). The search is seeded from every
2064    /// entry in `predictors` plus `(0,0)`. Returns the best MV and its `J`.
2065    ///
2066    /// The rate term is only a *search heuristic* — whatever MV it picks is still
2067    /// coded as a correct `mvd`, so this never affects decodability.
2068    #[allow(clippy::too_many_arguments)]
2069    /// ME ORACLE PROBE (`RFF_ME_ORACLE=1`): does our search actually FIND the best
2070    /// motion vector available to it? Accumulates our chosen cost against an
2071    /// exhaustive +-24 full-pel search refined by the identical sub-pel pass, so a
2072    /// gap is attributable to the SEARCH, not to the cost function or precision.
2073    /// [n, sum(our cost), sum(oracle cost), blocks where oracle won, cost() evals]
2074    fn motion_search(
2075        &self,
2076        reference: &crate::RefFrame,
2077        sy: &[u8],
2078        lx: usize,
2079        ly: usize,
2080        rw: usize,
2081        rh: usize,
2082        predictors: &[(i32, i32)],
2083        lambda_me: f64,
2084        // Some(mv) => skip the full-pel search entirely and refine THIS vector. The
2085        // starting COST is recomputed here rather than passed in, so the baseline the
2086        // refinement must beat is priced by the same closure as every candidate.
2087        start: Option<(i32, i32)>,
2088    ) -> ((i32, i32), i64) {
2089        // Bit length of `se(d)` (Exp-Golomb), i.e. what an `mvd` component costs.
2090        // Branchless closed form of the old `while n > 1 { n >>= 1; len += 2 }` loop:
2091        // that loop yields `len = 1 + 2·floor(log2(codenum+1))`, and for x ≥ 1
2092        // `floor(log2(x)) == 31 - x.leading_zeros()`. Removes a data-dependent branch
2093        // from the innermost ME cost — bit-identical (verified over the d range).
2094        let mvk = mv_cost_kind(self.mv_smooth);
2095        let mvbits = |d: i32| -> u32 {
2096            // H-23: the ME rate model. `RFF_MVCOST=1` swaps the Exp-Golomb STEP
2097            // function for x264's smooth curve `2·log2(|d|+1) + 0.718 + (d!=0)`.
2098            // The step function is FLAT inside a power-of-two bracket — it prices
2099            // d=4 and d=7 identically, so the search takes the far end of a
2100            // bracket for free, inflating |mvd| (and with it the sign+prefix bits
2101            // the accountant found are ~14% of the payload). λ cannot fix this:
2102            // scaling a flat region leaves it flat. Table is in WHOLE bits to keep
2103            // the caller's integer arithmetic; ×4 internally then rounded, so the
2104            // curve's ordering survives quantization.
2105            match mvk {
2106                1 => {
2107                    let a = d.unsigned_abs().min(4095) as usize;
2108                    MV_COST_TAB.get_or_init(build_mv_cost)[a] as u32
2109                }
2110                2 => {
2111                    let a = d.unsigned_abs().min(4095) as usize;
2112                    MV_TRUE_BIASED.get_or_init(build_true_biased)[a] as u32
2113                }
2114                _ => {
2115                    let codenum = if d > 0 { (2 * d - 1) as u32 } else { (-2 * d) as u32 };
2116                    1 + 2 * (31 - (codenum + 1).leading_zeros())
2117                }
2118            }
2119        };
2120        let center = predictors[0];
2121        let probe = me_oracle_on();
2122        // Track-B B2: the full-pel phase (seeds/snap/diamond) prices candidates in
2123        // the SAD domain; the winner is repriced in SATD before rescue/sub-pel.
2124        // Refine-only searches have no full-pel phase, so B2 does not apply there.
2125        // `self.sadfp` is force-mode at construction or the per-frame `b2_mgain`
2126        // dispatcher's routing (mode 1).
2127        let sadfp = !self.fast && start.is_none() && self.sadfp;
2128        // Build the 16-aligned source MB ONCE per search for the asm SAD path (fast
2129        // preset — and B2's SAD full-pel phase — full 16×16). Amortized over every
2130        // candidate's SAD; the reference block stays unaligned (movdqu). Scalar
2131        // build does no copy.
2132        #[cfg(accel)]
2133        let asrc_buf = if (self.fast || sadfp) && rw == 16 && rh == 16 {
2134            let mut a = AlignedMb([0u8; 256]);
2135            for dy in 0..16 {
2136                a.0[dy * 16..dy * 16 + 16].copy_from_slice(&sy[(ly + dy) * self.cw + lx..][..16]);
2137            }
2138            Some(a)
2139        } else {
2140            None
2141        };
2142        #[cfg(accel)]
2143        let asrc: Option<&[u8; 256]> = asrc_buf.as_ref().map(|a| &a.0);
2144        #[cfg(not(accel))]
2145        let asrc: Option<&[u8; 256]> = None;
2146        // Challenge-1 A2: hoist the SATD path's per-search invariants OUT of the
2147        // per-candidate closure. `mc_satd` re-derived, for EVERY candidate: the
2148        // plane-cache OnceLock (an acquire load + branch, twice on the quarter-pel
2149        // arm), the `RFF_HPEL_REF` OnceLock, and the source-row slice base (a bounds
2150        // check). All are constant across the ~20-50 evaluations of one search.
2151        // `mc_satd_hp` is the same dispatch with those values passed in — the same
2152        // arms in the same order, so the accepted candidate set is byte-identical.
2153        let use_sad = self.fast && !self.mb_use_satd;
2154        let cw = self.cw;
2155        // Every non-fast search sub-pel-refines at the end, so the planes are built
2156        // for any reference a search touches — hoisting the get_or_init here does not
2157        // build planes a lazy path would have avoided.
2158        let hp: Option<&rusty_h264_common::inter::HpelPlanes> =
2159            if !self.fast { Some(reference.hpel(cw, self.mb_h * 16)) } else { None };
2160        let hr_on = hpel_ref_enabled();
2161        // A3 gate, hoisted with the rest (`RFF_HPEL_REF=0` restores the FULL pre-C/A3
2162        // copy path, so the fused kernel rides the same master anchor).
2163        let sa_on = cfg!(accel) && hr_on && satd_avg_enabled();
2164        let src_row = &sy[ly * cw + lx..];
2165        // H-14 R3: the MeCtx fast evaluator — ONE geometry validation per search,
2166        // then per-eval integer bounds + direct kernel (collects the measured
2167        // ~23 ns/eval dispatch chain). Values are exactly the safe path's, so a
2168        // candidate served here cannot change the bitstream; out-of-window
2169        // candidates fall back to `mc_satd_hp` (equal values there too).
2170        #[cfg(accel)]
2171        let mectx = if !use_sad && mectx_enabled() {
2172            hp.and_then(|p| {
2173                rusty_h264_accel::MeCtx::new(
2174                    src_row, cw, &p.f, &p.h, &p.v, &p.c, p.stride, p.pad, p.pw, p.ph,
2175                    lx, ly, rw, rh,
2176                )
2177            })
2178        } else {
2179            None
2180        };
2181        let cost = |mv: (i32, i32)| -> i64 {
2182            let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
2183            // The smooth table carries 4× resolution; fold that into λ so the
2184            // rate/distortion balance is unchanged and only the SHAPE differs.
2185            let lam_r = if mvk != 0 { lambda_me * 0.25 } else { lambda_me };
2186            // Fast preset: SAD (psadbw — asm kernel on `--features asm`, else auto-vec)
2187            // — far cheaper than SATD, the single biggest reason x264 fast out-runs us.
2188            let dist = if use_sad {
2189                self.mc_sad(reference, sy, lx, ly, rw, rh, mv, asrc)
2190            } else {
2191                #[cfg(accel)]
2192                {
2193                    match mectx.as_ref().and_then(|c| c.eval(mv.0, mv.1)) {
2194                        Some(d) => d as i64,
2195                        None => {
2196                            self.mc_satd_hp(reference, hp, hr_on, sa_on, src_row, lx, ly, rw, rh, mv)
2197                        }
2198                    }
2199                }
2200                #[cfg(not(accel))]
2201                {
2202                    self.mc_satd_hp(reference, hp, hr_on, sa_on, src_row, lx, ly, rw, rh, mv)
2203                }
2204            };
2205            dist + (lam_r * rate as f64) as i64
2206        };
2207        // B2's full-pel-phase cost: SAD distortion, λ scaled to the SAD domain
2208        // (`RFF_ME_SADL`, hoisted). Falls through to `cost` (SATD) whenever B2 is
2209        // off, so every pre-B2 path is untouched.
2210        let lam_fp = lambda_me * if sadfp { me_sadfp_lambda() } else { 1.0 };
2211        let cost_fp = |mv: (i32, i32)| -> i64 {
2212            if !sadfp {
2213                return cost(mv);
2214            }
2215            let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
2216            self.mc_sad_hp(reference, hp, hr_on, src_row, lx, ly, rw, rh, mv, asrc)
2217                + (lam_fp * rate as f64) as i64
2218        };
2219        // Seed from (0,0) and each predictor; keep the cheapest.
2220        let refine_only = start.is_some();
2221        let (mut best, mut best_c) = match start {
2222            Some(mv) => (mv, cost(mv)),
2223            None => {
2224                let mut b = (0, 0);
2225                let mut bc = cost_fp(b);
2226                for &p in predictors {
2227                    let pc = cost_fp(p);
2228                    if pc < bc {
2229                        bc = pc;
2230                        b = p;
2231                    }
2232                }
2233                (b, bc)
2234            }
2235        };
2236        // SNAP THE DIAMOND CENTRE TO INTEGER-PEL. The diamond below steps by whole
2237        // pels, so a fractional centre makes EVERY candidate fractional and forces
2238        // all of them through `mc_luma`'s 6-tap filter — measured at 84-90% of all
2239        // SATD evaluations. Snapping puts the whole full-pel phase on the direct
2240        // (no-interpolation) SATD path. The pre-snap seed is kept and re-compared
2241        // after refinement, so this can only change WHERE we search, never make the
2242        // returned vector worse than the seed we started from.
2243        let (seed_mv, mut seed_c) = (best, best_c);
2244        if !refine_only && self.me_snap && (best.0 & 3 != 0 || best.1 & 3 != 0) {
2245            let snapped = ((best.0 + 2).div_euclid(4) * 4, (best.1 + 2).div_euclid(4) * 4);
2246            best_c = cost_fp(snapped);
2247            best = snapped;
2248        }
2249        // Coarse-to-fine full-pel search: a 4-point diamond walked at each step
2250        // size from 16 px down to 1 px (steps in quarter-pel units: 64,32,…,4).
2251        // The larger initial steps reach fast motion the predictor missed; the
2252        // diamond stays orthogonal (no diagonals) — diagonal probes were found to
2253        // chase equally-good far matches on ambiguous motion, wrecking MV-field
2254        // coherence and the neighbor predictors.
2255        // The fast preset trusts the neighbour MV predictor and refines locally
2256        // (one coarse reach + fine), like x264's `me=dia`; quality sweeps the full
2257        // coarse-to-fine range. Each step's diamond still walks until no
2258        // improvement, so even fast reaches far motion — just in smaller hops.
2259        // Descent A: the coarse rungs are ~76-80% of full-pel evals at a 0.05-1.0% hit
2260        // rate (near-equal eval counts per rung = the walk almost never walks, so each
2261        // rung is a flat ~4-eval toll). RFF_DIA_LADDER selects which rungs to pay for.
2262        let mut ladder = [0i32; 5];
2263        let mut nladder = 0usize;
2264        let steps: &[i32] = if self.fast {
2265            &[16, 4]
2266        } else {
2267            let m = dia_mask();
2268            for (i, r) in DIA_RUNGS.iter().enumerate() {
2269                if m & (1 << i) != 0 {
2270                    ladder[nladder] = *r;
2271                    nladder += 1;
2272                }
2273            }
2274            &ladder[..nladder]
2275        };
2276        let _gd = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeDiamond);
2277        // FC: batch a fixed-centre diamond pass through the x4 kernels when every
2278        // candidate is an interior full-pel 16×16 read — one source band covers all
2279        // four candidates. Applies to BOTH cost domains (`sad_16x16_x4` on
2280        // SAD-routed frames, `satd_16x16_x4` otherwise); the fast preset keeps its
2281        // own untouched path. Argmin-of-4 replaces the first-improver cascade —
2282        // measured BD-POSITIVE on the SAD domain (bus −1.71→−2.61) and gated on the
2283        // corpus for the SATD domain the same way. `RFF_ME_FC=0` restores cascade.
2284        let fc = !self.fast && cfg!(accel) && me_fc_enabled()
2285            && matches!((rw, rh), (16, 16) | (16, 8) | (8, 16) | (8, 8));
2286        let ch_px = self.mb_h as isize * 16;
2287        for (_si, &step) in steps.iter().enumerate() {
2288            if refine_only {
2289                break;
2290            }
2291            loop {
2292                #[cfg(accel)]
2293                if fc && best.0 & 3 == 0 && best.1 & 3 == 0 {
2294                    // All four candidates full-pel; interior iff the ±step box is.
2295                    let s = (step >> 2) as isize;
2296                    let (bx, by) = (lx as isize + (best.0 >> 2) as isize, ly as isize + (best.1 >> 2) as isize);
2297                    if bx - s >= 0 && by - s >= 0 && bx + s + rw as isize <= cw as isize && by + s + rh as isize <= ch_px {
2298                        let offs = [
2299                            (by * cw as isize + bx + s) as usize,
2300                            (by * cw as isize + bx - s) as usize,
2301                            ((by + s) * cw as isize + bx) as usize,
2302                            ((by - s) * cw as isize + bx) as usize,
2303                        ];
2304                        // 16-wide shapes go through the batch kernel; 8-wide ones
2305                        // measured SLOWER batched than the per-candidate Wels asm
2306                        // (H-8 speed gate), so they evaluate individually inside the
2307                        // SAME argmin — identical values, identical comparisons,
2308                        // identical bitstream.
2309                        let batch = if rw != 16 {
2310                            None
2311                        } else if sadfp {
2312                            rusty_h264_accel::sad_x4(src_row, cw, &reference.y, offs, cw, rw, rh)
2313                        } else {
2314                            rusty_h264_accel::satd_x4(src_row, cw, &reference.y, offs, cw, rw, rh)
2315                        };
2316                        {
2317                            let ring = [(step, 0), (-step, 0), (0, step), (0, -step)];
2318                            let (mut bi, mut bc) = (usize::MAX, best_c);
2319                            for (i, &(dx, dy)) in ring.iter().enumerate() {
2320                                let mv = (best.0 + dx, best.1 + dy);
2321                                let cc = match batch {
2322                                    Some(sads) => {
2323                                        let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
2324                                        sads[i] as i64 + (lam_fp * rate as f64) as i64
2325                                    }
2326                                    None => cost_fp(mv),
2327                                };
2328                                #[cfg(feature = "profile")]
2329                                diastats::ev(_si);
2330                                if cc < bc {
2331                                    bc = cc;
2332                                    bi = i;
2333                                }
2334                            }
2335                            if bi == usize::MAX {
2336                                break;
2337                            }
2338                            best_c = bc;
2339                            best = (best.0 + ring[bi].0, best.1 + ring[bi].1);
2340                            #[cfg(feature = "profile")]
2341                            diastats::imp(_si);
2342                            continue;
2343                        }
2344                    }
2345                }
2346                let mut improved = false;
2347                for &(dx, dy) in &[(step, 0), (-step, 0), (0, step), (0, -step)] {
2348                    let c = (best.0 + dx, best.1 + dy);
2349                    let cc = cost_fp(c);
2350                    #[cfg(feature = "profile")]
2351                    diastats::ev(_si);
2352                    if cc < best_c {
2353                        best_c = cc;
2354                        best = c;
2355                        improved = true;
2356                        #[cfg(feature = "profile")]
2357                        diastats::imp(_si);
2358                    }
2359                }
2360                if !improved {
2361                    break;
2362                }
2363            }
2364        }
2365        // DIAMOND-STALLED RESCUE (content-adaptive: fires on the FAILURE, not a proxy).
2366        // The gradient-descent diamond stalls at a plateau on FLAT cost surfaces and
2367        // never reaches the far-but-better MV that exists within ±16 (measured: ~+22%
2368        // BD-rate vs x264's simple dia on smooth content). The precise stall signal is
2369        // the CONJUNCTION: a FLAT source block (low variance) whose diamond match STILL
2370        // has a high residual — because on a flat surface the RIGHT MV predicts near-
2371        // perfectly, so a high residual there means the diamond missed it (a stall).
2372        // (Residual alone fires on busy blocks where a high residual is inherent — that
2373        // was 3.3× slower on mand for nothing; variance alone fires on flat-but-well-
2374        // predicted blocks. The AND targets exactly the stalls.) Then a FINE ±16 step-2
2375        // grid reaches the true minimum. Fires on a fraction of blocks → affordable.
2376        // Quality preset only.
2377        drop(_gd);
2378        // B2: the full-pel phase priced in the SAD domain — reprice the winner AND
2379        // the pre-snap seed into the SATD domain the rescue + sub-pel phases (and the
2380        // final seed-vs-refined comparison) trade in. Two SATD evaluations per
2381        // search, against the ~20-50 candidate evaluations the SAD domain cheapened.
2382        if sadfp {
2383            best_c = cost(best);
2384            seed_c = cost(seed_mv);
2385        }
2386        let _gr = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeRescue);
2387        // H-14 R1 brick 1: `me_fast` defaults TRUE, which makes `flat`'s value
2388        // IRRELEVANT to the gate below on every default search — yet the full
2389        // rw×rh sum+sum-of-squares walk (256 pixel loads + muls) ran EAGERLY per
2390        // search. Lazy-evaluate it: same boolean outcome in every case (me_fast
2391        // short-circuits first), the dead variance pass simply never runs.
2392        let flat = |sself: &Self| {
2393            !refine_only && {
2394                let (mut s, mut ss) = (0u64, 0u64);
2395                for dy in 0..rh {
2396                    for dx in 0..rw {
2397                        let v = sy[(ly + dy) * sself.cw + lx + dx] as u64;
2398                        s += v;
2399                        ss += v * v;
2400                    }
2401                }
2402                let n = (rw * rh) as u64;
2403                (ss - s * s / n) / n < sself.me_wide_var
2404            }
2405        };
2406        // The online payoff gate may have disabled the rescue for the rest of this
2407        // frame (irreducible-residual content — rotation/fractal — where the fine grid
2408        // fixes almost nothing; measured 2.25× on rot for a ~0% BD gain). A gated-off
2409        // frame runs exactly the diamond → identical to me_wide-off → never worse.
2410        // FAST-MOTION extension: the flat gate targets smooth-surface stalls, but the
2411        // diamond ALSO stalls on FAST motion (bus/football: an exhaustive ±24 search
2412        // recovers 6-15% BD) — those blocks are high-VARIANCE (detail) so `flat` misses
2413        // them. `me_fast` also fires on any high-residual block; the online payoff gate
2414        // then keeps it only where a wider search actually pays off (fast motion), and
2415        // disables it on irreducible-residual detail — the same self-tuning as flat.
2416        if self.me_wide && !self.fast && (self.me_fast || flat(self)) && !self.resc_off.get() {
2417            // H-14 R1 brick 2: `best` was priced by the SAME `dist + (λ·rate) as
2418            // i64` formula on every path that can reach here (cost, cost_fp after
2419            // the B2 reprice, the FC batch with lam_fp == λ off SAD frames), so
2420            // its distortion is recoverable EXACTLY by subtraction — the extra
2421            // full SATD kernel call per search was pure recompute (the
2422            // codec-eliminate-redundancy "return the already-computed value").
2423            let rate_b = mvbits(best.0 - center.0) + mvbits(best.1 - center.1);
2424            let dist = best_c - (lambda_me * rate_b as f64) as i64;
2425            if dist / (rw * rh).max(1) as i64 > self.me_rescue {
2426                // FINE ±16 step-2 grid + ±1 refine — recover the true minimum the
2427                // diamond missed. Fires only on flat-block stalls, so it is affordable.
2428                // SNAP THE GRID CENTRE TO INTEGER-PEL: the diamond seed can be sub-pel
2429                // (sub-pel neighbour predictors), and since every grid point shares
2430                // cx&3, a sub-pel centre forces the WHOLE ±16 grid through mc_luma
2431                // interpolation — measured 89% of zoom's rescue cost. The rescue only
2432                // needs the right REGION (a far MV the diamond missed); the sub-pel
2433                // refine that follows recovers the fraction. Integer centre → the grid
2434                // hits the fast full-pel SATD path (no interpolation).
2435                let pre_c = best_c;
2436                let (cx, cy) = ((best.0 + 2).div_euclid(4) * 4, (best.1 + 2).div_euclid(4) * 4);
2437                let mut gb = best;
2438                // BATCHED FULL-PEL GRID (accel): now that the grid centre is integer-pel
2439                // (all points interior full-pel), hoist the interior/bounds check out of
2440                // the loop and call the AVX2 SATD directly — skipping mc_satd's per-point
2441                // interior test + satd_px dispatch. BYTE-IDENTICAL to the cost() path
2442                // (same 2·satd_16x16 + rate), so it is default-on (RFF_ME_BATCH=0 to A/B
2443                // it off). ~+7% zoom / +4% tsrc on top of the snap; the SATD kernel itself
2444                // is already AVX2 and its transform can't amortise across the grid, so
2445                // this per-call-overhead trim is the ceiling for an "asm grid kernel".
2446                let cw = self.cw;
2447                let r = self.me_range;
2448                let batched = rw == 16 && rh == 16 && cfg!(accel) && {
2449                    let (icdx, icdy) = (cx >> 2, cy >> 2);
2450                    lx as i32 + icdx >= r
2451                        && lx as i32 + icdx + r + 16 <= cw as i32
2452                        && ly as i32 + icdy >= r
2453                        && ly as i32 + icdy + r + 16 <= (self.mb_h * 16) as i32
2454                        && me_batch_enabled()
2455                };
2456                #[cfg(accel)]
2457                if batched {
2458                    let (icdx, icdy) = ((cx >> 2), (cy >> 2));
2459                    let src = &sy[ly * cw + lx..];
2460                    let mut dy = -r;
2461                    while dy <= r {
2462                        let rby = (ly as i32 + icdy + dy) as usize;
2463                        let mut dx = -r;
2464                        while dx <= r {
2465                            let rbx = (lx as i32 + icdx + dx) as usize;
2466                            let satd =
2467                                2 * rusty_h264_accel::satd_16x16(src, cw, &reference.y[rby * cw + rbx..], cw) as i64;
2468                            let mv = (cx + dx * 4, cy + dy * 4);
2469                            let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
2470                            let cc = satd + (lambda_me * rate as f64) as i64;
2471                            if cc < best_c {
2472                                best_c = cc;
2473                                gb = mv;
2474                            }
2475                            dx += 2;
2476                        }
2477                        dy += 2;
2478                    }
2479                }
2480                if !batched {
2481                    let mut dy = -r;
2482                    while dy <= r {
2483                        let mut dx = -r;
2484                        while dx <= r {
2485                            let cc = cost((cx + dx * 4, cy + dy * 4));
2486                            if cc < best_c {
2487                                best_c = cc;
2488                                gb = (cx + dx * 4, cy + dy * 4);
2489                            }
2490                            dx += 2;
2491                        }
2492                        dy += 2;
2493                    }
2494                }
2495                best = gb;
2496                for dy in -1..=1 {
2497                    for dx in -1..=1 {
2498                        let c = (best.0 + dx * 4, best.1 + dy * 4);
2499                        let cc = cost(c);
2500                        if cc < best_c {
2501                            best_c = cc;
2502                            best = c;
2503                        }
2504                    }
2505                }
2506                // LEARNING PHASE: for the first `me_learn` stalls of the frame, tally
2507                // whether the grid actually paid off (≥6.25% cost cut). Once the window
2508                // fills, if too few paid off the residual is irreducible on this content
2509                // → disable the rescue for the rest of the frame. The window's own MVs
2510                // are committed, but they're a small spatially-clustered set (not
2511                // improvement-selected), so on net-neutral content (rot) they can't
2512                // regress — only frame-level on/off avoids the per-block B-direct
2513                // selection effect.
2514                let n = self.resc_n.get();
2515                if n < self.me_learn {
2516                    self.resc_n.set(n + 1);
2517                    if best_c * 16 <= pre_c * 15 {
2518                        self.resc_big.set(self.resc_big.get() + 1);
2519                    }
2520                    if n + 1 == self.me_learn
2521                        && self.resc_big.get() * 100 < self.me_learn * self.me_payoff_pct
2522                    {
2523                        self.resc_off.set(true);
2524                    }
2525                }
2526            }
2527        }
2528        drop(_gr);
2529        let _gs = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeSubpel);
2530        // Sub-pel refinement uses the 6-tap/bilinear interpolation — the expensive
2531        // per-pixel `mc_luma` path that profiling pinned at ~55% of the entire
2532        // encode. The fast preset skips it (integer-pel only, like x264's fastest
2533        // presets `subme=0`): ~3× faster, trading a little quality on sub-pixel
2534        // motion. The quality preset does the full half-pel + quarter-pel rings.
2535        if probe {
2536            // Exhaustive +-24 full-pel around the same centre, then the SAME sub-pel
2537            // pass, so only the full-pel search strategy differs.
2538            let mut ob = center;
2539            let mut oc = i64::MAX;
2540            for gy in -24i32..=24 {
2541                for gx in -24i32..=24 {
2542                    let c = (center.0 + gx * 4, center.1 + gy * 4);
2543                    let cc = cost(c);
2544                    if cc < oc {
2545                        oc = cc;
2546                        ob = c;
2547                    }
2548                }
2549            }
2550            let fullpel_best = ob;
2551            for &st in &[2i32, 1] {
2552                for &(dx, dy) in &[(st, 0), (-st, 0), (0, st), (0, -st)] {
2553                    let c = (ob.0 + dx, ob.1 + dy);
2554                    let cc = cost(c);
2555                    if cc < oc {
2556                        oc = cc;
2557                        ob = c;
2558                    }
2559                }
2560            }
2561            // EXHAUSTIVE sub-pel: every quarter-pel offset in +-3 around the full-pel
2562            // winner. Our own pass is a single 4-point probe at half then quarter, so
2563            // this is what separates a sub-pel deficiency from a full-pel one.
2564            let mut oc_sp = oc;
2565            for dy in -3i32..=3 {
2566                for dx in -3i32..=3 {
2567                    let c = (fullpel_best.0 + dx, fullpel_best.1 + dy);
2568                    let cc = cost(c);
2569                    if cc < oc_sp {
2570                        oc_sp = cc;
2571                    }
2572                }
2573            }
2574            // our own sub-pel pass has not run yet; replicate it for a fair compare
2575            let (mut mb_, mut mc_) = (best, best_c);
2576            for &st in &[2i32, 1] {
2577                for &(dx, dy) in &[(st, 0), (-st, 0), (0, st), (0, -st)] {
2578                    let c = (mb_.0 + dx, mb_.1 + dy);
2579                    let cc = cost(c);
2580                    if cc < mc_ {
2581                        mc_ = cc;
2582                        mb_ = c;
2583                    }
2584                }
2585            }
2586            use std::sync::atomic::Ordering::Relaxed;
2587            ME_PROBE[0].fetch_add(1, Relaxed);
2588            ME_PROBE[1].fetch_add(mc_.max(0) as u64, Relaxed);
2589            ME_PROBE[2].fetch_add(oc.max(0) as u64, Relaxed);
2590            ME_PROBE[3].fetch_add((mc_ > oc) as u64, Relaxed);
2591            ME_PROBE[5].fetch_add(oc_sp.max(0) as u64, Relaxed);
2592            ME_PROBE[6].fetch_add((mc_ > oc_sp) as u64, Relaxed);
2593        }
2594        let subpel: &[i32] = if (self.fast && !self.subpel_force) || (self.sp_defer.get() && !refine_only) {
2595            &[]
2596        } else {
2597            &[2, 1]
2598        };
2599        // U1 harvest: the null arm is the full-pel winner we would keep on a skip.
2600        let (hv_pre, mut hv_evals) = (best_c, 0u32);
2601        // `to_best` = eval index of the LAST improvement; `ring1` = the cost after the
2602        // first 8-point half-pel ring. Together they answer "how many of these 29
2603        // evaluations actually matter", which is the ceiling for any cheaper pattern.
2604        let (mut hv_to_best, mut hv_ring1) = (0u32, i64::MIN);
2605        let mut pat = subpel_pattern_override()
2606            .unwrap_or(if self.sp_single_pass { 2 } else { 0 });
2607        let (sp_learn, sp_t) = sp_dispatch_cfg();
2608        // Only dispatch when the caller has not pinned a pattern (pat 0 = default).
2609        let sp_dispatching = sp_learn > 0 && pat == 0 && !subpel.is_empty();
2610        if sp_dispatching && self.sp_learn_n.get() >= sp_learn && self.sp_1pass.get() {
2611            pat = 2;
2612        }
2613        // Descent D-2 MEMO. The ring walks around a MOVING centre, so iteration N+1's
2614        // ring necessarily re-contains the previous centre and several previous ring
2615        // points: 27-44% of sub-pel evaluations re-price an MV this refinement already
2616        // priced. `cost()` is PURE in `mv` (rate from mv-centre; distortion from the
2617        // fixed reference/source/block captures), so memoizing is EXACT -- identical
2618        // costs, identical comparisons, identical chosen MV, byte-identical output.
2619        // A miss simply recomputes, so the table's hit rate is a SPEED property only.
2620        //
2621        // 64-entry direct-mapped on the low bits of the MV, tagged with the full MV so
2622        // a collision is a miss rather than a wrong answer. Stack-resident (1 KiB) and
2623        // re-initialized per refinement: measured cheaper than a thread-local + RefCell
2624        // borrow on every evaluation, since ~60% of lookups miss.
2625        const SP_MEMO_N: usize = 64;
2626        #[inline(always)]
2627        fn sp_slot(mv: (i32, i32)) -> usize {
2628            ((mv.0 & 7) as usize) | (((mv.1 & 7) as usize) << 3)
2629        }
2630        let mut memo_mv = [(i32::MIN, i32::MIN); SP_MEMO_N];
2631        let mut memo_c = [0i64; SP_MEMO_N];
2632        if !subpel.is_empty() {
2633            let s0 = sp_slot(best);
2634            memo_mv[s0] = best;
2635            memo_c[s0] = best_c;
2636        }
2637        // Descent D-2 census: the ring walks around a MOVING centre, so iteration N+1's ring
2638        // necessarily re-contains the previous centre and several previous ring points.
2639        // Count how many sub-pel evaluations price an MV this refinement ALREADY priced
2640        // -- redundant recompute is byte-identically removable, unlike dropping work.
2641        #[cfg(feature = "profile")]
2642        let mut seen: Vec<(i32, i32)> = Vec::with_capacity(64);
2643        #[cfg(feature = "profile")]
2644        {
2645            seen.push(best);
2646        }
2647        // Track-B B3: the sub-pel iteration BUDGET. The ring walks until no
2648        // improvement; Descent D's census says iteration 1 carries 55% of evals at
2649        // an 11-13% hit rate, iteration 2 another 35-40% at 1.5-2.5%, and the tail
2650        // past that almost never pays — but under B2's SAD-chosen starts the tail
2651        // GROWS (+27% ns/search), eating the SAD savings. A cap bounds the walk the
2652        // way x264's fixed subme budget does. 0 (default) = unlimited =
2653        // byte-identical; bitstream-changing otherwise → BD-gated, opt-in.
2654        let sp_cap = sp_maxit();
2655        // ③: batched fixed-centre half-pel ring (see `sp_fc_enabled`).
2656        let sp_fc = sp_fc_enabled() && !self.fast && cfg!(accel)
2657            && matches!((rw, rh), (16, 16) | (16, 8) | (8, 16) | (8, 8));
2658        for &step in subpel {
2659            // Snapping starts this refine from an integer centre instead of the
2660            // seed's own fractional lattice, so a single 8-point pass can leave
2661            // precision behind. Walk it until it stops improving to compensate —
2662            // the snap is what pays for the extra probes.
2663            let ring8 = [
2664                (step, 0), (-step, 0), (0, step), (0, -step),
2665                (step, step), (-step, -step), (step, -step), (-step, step),
2666            ];
2667            let ring4 = [(step, 0), (-step, 0), (0, step), (0, -step)];
2668            let ring: &[(i32, i32)] = if pat & 1 != 0 { &ring4 } else { &ring8 };
2669            let mut _iter = 0u32;
2670            loop {
2671                // ③: from an INTEGER centre at step 2, all 8 ring candidates are
2672                // single-plane reads (h/h/v/v axes, c/c/c/c diagonals) — batch them
2673                // as two x4 kernel calls and take the argmin (first-wins in ring
2674                // order). Any decline (edge, half-pel centre, ring4 pattern) falls
2675                // through to the cascading walk for this pass.
2676                // ③b: the QUARTER step — every ±1 offset makes a component odd, so
2677                // all 8 candidates are two-plane average pairs regardless of the
2678                // centre's phase; two `satd_avg_x4` calls cover the ring.
2679                #[cfg(accel)]
2680                if sp_fc && step == 1 && pat & 1 == 0 {
2681                    _iter += 1;
2682                    let hp8 = hp.expect("sp_fc implies non-fast, which resolves hp");
2683                    let ring8 = [
2684                        (1, 0), (-1, 0), (0, 1), (0, -1),
2685                        (1, 1), (-1, -1), (1, -1), (-1, 1),
2686                    ];
2687                    let mut prs: [Option<(&[u8], usize, &[u8], usize, usize)>; 8] = [None; 8];
2688                    let mut all = true;
2689                    for (i, &(dx, dy)) in ring8.iter().enumerate() {
2690                        prs[i] = rusty_h264_common::inter::hpel_qpel_refs(
2691                            hp8, lx, ly, rw, rh, best.0 + dx, best.1 + dy,
2692                        );
2693                        all &= prs[i].is_some();
2694                    }
2695                    if all {
2696                        let stride = prs[0].unwrap().4;
2697                        // Batch kernel for 16-wide only (8-wide measured slower
2698                        // batched than the per-candidate fused path — H-8 gate);
2699                        // either way the SAME argmin over the SAME values.
2700                        let pack = |a: usize, b: usize, c2: usize, d: usize| {
2701                            if rw != 16 {
2702                                return None;
2703                            }
2704                            let g = |i: usize| {
2705                                let (pa, oa, pb, ob, _) = prs[i].unwrap();
2706                                (pa, oa, pb, ob)
2707                            };
2708                            rusty_h264_accel::satd_avg_x4(
2709                                src_row, cw, [g(a), g(b), g(c2), g(d)], stride, rw, rh,
2710                            )
2711                        };
2712                        {
2713                            let (ax, di) = (pack(0, 1, 2, 3), pack(4, 5, 6, 7));
2714                            let (mut bi, mut bc) = (usize::MAX, best_c);
2715                            for i in 0..8 {
2716                                let (dx, dy) = ring8[i];
2717                                let mv = (best.0 + dx, best.1 + dy);
2718                                let cc = match (i < 4, &ax, &di) {
2719                                    (true, Some(ax), _) => {
2720                                        let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
2721                                        ax[i] as i64 + (lambda_me * rate as f64) as i64
2722                                    }
2723                                    (false, _, Some(di)) => {
2724                                        let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
2725                                        di[i - 4] as i64 + (lambda_me * rate as f64) as i64
2726                                    }
2727                                    _ => cost(mv),
2728                                };
2729                                hv_evals += 1;
2730                                if cc < bc {
2731                                    bc = cc;
2732                                    bi = i;
2733                                }
2734                            }
2735                            if hv_ring1 == i64::MIN {
2736                                hv_ring1 = if bi == usize::MAX { best_c } else { bc };
2737                            }
2738                            if bi == usize::MAX
2739                                || !self.me_subpel_iter
2740                                || pat & 2 != 0
2741                                || (sp_cap != 0 && _iter >= sp_cap)
2742                            {
2743                                if bi != usize::MAX {
2744                                    best_c = bc;
2745                                    best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
2746                                    hv_to_best = hv_evals;
2747                                }
2748                                break;
2749                            }
2750                            best_c = bc;
2751                            best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
2752                            hv_to_best = hv_evals;
2753                            continue;
2754                        }
2755                    }
2756                    _iter -= 1;
2757                }
2758                #[cfg(accel)]
2759                if sp_fc && step == 2 && best.0 & 3 == 0 && best.1 & 3 == 0 && pat & 1 == 0 {
2760                    _iter += 1;
2761                    let hp8 = hp.expect("sp_fc implies non-fast, which resolves hp");
2762                    let ring8 = [
2763                        (step, 0), (-step, 0), (0, step), (0, -step),
2764                        (step, step), (-step, -step), (step, -step), (-step, step),
2765                    ];
2766                    let mut refs8: [Option<(&[u8], usize, usize)>; 8] = [None; 8];
2767                    let mut all = true;
2768                    for (i, &(dx, dy)) in ring8.iter().enumerate() {
2769                        refs8[i] = rusty_h264_common::inter::hpel_ref(
2770                            hp8, lx, ly, rw, rh, best.0 + dx, best.1 + dy,
2771                        );
2772                        all &= refs8[i].is_some();
2773                    }
2774                    if all {
2775                        let stride = refs8[0].unwrap().2;
2776                        // 16-wide batches; 8-wide evaluates per candidate (H-8 gate)
2777                        // — identical values, identical argmin, identical bitstream.
2778                        let pack = |a: usize, b: usize, c2: usize, d: usize| {
2779                            if rw != 16 {
2780                                return None;
2781                            }
2782                            let g = |i: usize| {
2783                                let (p, o, _) = refs8[i].unwrap();
2784                                (p, o)
2785                            };
2786                            rusty_h264_accel::satd_x4p(
2787                                src_row, cw, [g(a), g(b), g(c2), g(d)], stride, rw, rh,
2788                            )
2789                        };
2790                        {
2791                            let (ax, di) = (pack(0, 1, 2, 3), pack(4, 5, 6, 7));
2792                            let (mut bi, mut bc) = (usize::MAX, best_c);
2793                            for i in 0..8 {
2794                                let (dx, dy) = ring8[i];
2795                                let mv = (best.0 + dx, best.1 + dy);
2796                                let cc = match (i < 4, &ax, &di) {
2797                                    (true, Some(ax), _) => {
2798                                        let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
2799                                        ax[i] as i64 + (lambda_me * rate as f64) as i64
2800                                    }
2801                                    (false, _, Some(di)) => {
2802                                        let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
2803                                        di[i - 4] as i64 + (lambda_me * rate as f64) as i64
2804                                    }
2805                                    _ => cost(mv),
2806                                };
2807                                hv_evals += 1;
2808                                if cc < bc {
2809                                    bc = cc;
2810                                    bi = i;
2811                                }
2812                            }
2813                            if hv_ring1 == i64::MIN {
2814                                hv_ring1 = if bi == usize::MAX { best_c } else { bc };
2815                            }
2816                            if bi == usize::MAX
2817                                || !self.me_subpel_iter
2818                                || pat & 2 != 0
2819                                || (sp_cap != 0 && _iter >= sp_cap)
2820                            {
2821                                if bi != usize::MAX {
2822                                    best_c = bc;
2823                                    best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
2824                                    hv_to_best = hv_evals;
2825                                }
2826                                break;
2827                            }
2828                            best_c = bc;
2829                            best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
2830                            hv_to_best = hv_evals;
2831                            continue;
2832                        }
2833                    }
2834                    _iter -= 1; // declined — the cascade pass below re-counts it
2835                }
2836                let mut improved = false;
2837                _iter += 1;
2838                for (_pi, &(dx, dy)) in ring.iter().enumerate() {
2839                    let c = (best.0 + dx, best.1 + dy);
2840                    let slot = sp_slot(c);
2841                    let cc = if memo_mv[slot] == c {
2842                        memo_c[slot]
2843                    } else {
2844                        let v = cost(c);
2845                        memo_mv[slot] = c;
2846                        memo_c[slot] = v;
2847                        v
2848                    };
2849                    hv_evals += 1;
2850                    // Descent D: which ring POSITION and which ITERATION actually pay?
2851                    // Same census that showed the diamond's coarse rungs were noise,
2852                    // aimed at the stage that is now 41% of encode.
2853                    #[cfg(feature = "profile")]
2854                    {
2855                        spstats::ev(if step == 2 { 0 } else { 1 }, _pi, _iter);
2856                        if seen.contains(&c) {
2857                            spstats::redundant();
2858                        } else {
2859                            seen.push(c);
2860                        }
2861                    }
2862                    if cc < best_c {
2863                        best_c = cc;
2864                        best = c;
2865                        improved = true;
2866                        hv_to_best = hv_evals;
2867                        #[cfg(feature = "profile")]
2868                        spstats::imp(if step == 2 { 0 } else { 1 }, _pi, _iter);
2869                    }
2870                }
2871                if hv_ring1 == i64::MIN {
2872                    hv_ring1 = best_c;
2873                }
2874                if !improved
2875                    || !self.me_subpel_iter
2876                    || pat & 2 != 0
2877                    || (sp_cap != 0 && _iter >= sp_cap)
2878                {
2879                    break;
2880                }
2881            }
2882        }
2883        if sp_dispatching {
2884            let n = self.sp_learn_n.get();
2885            if n < sp_learn {
2886                self.sp_learn_n.set(n + 1);
2887                if hv_ring1 != i64::MIN {
2888                    self.sp_ring1.set(self.sp_ring1.get() + (hv_pre - hv_ring1).max(0));
2889                    self.sp_total.set(self.sp_total.get() + (hv_pre - best_c).max(0));
2890                }
2891                if n + 1 == sp_learn {
2892                    let tot = self.sp_total.get();
2893                    // Concentrated in ring 1 -> the later rings are affordable to drop.
2894                    self.sp_1pass.set(tot > 0 && self.sp_ring1.get() * 100 >= tot * sp_t);
2895                }
2896            }
2897        }
2898        if !subpel.is_empty() && subpel_harvest::enabled() {
2899            subpel_harvest::record(hv_pre, best_c, lambda_me, rw, rh, hv_evals, hv_to_best, hv_ring1);
2900        }
2901        // The snap moved the search off the seed; if the seed was better after all,
2902        // keep it. This is what makes the snap safe by construction.
2903        if self.me_snap && seed_c < best_c {
2904            best = seed_mv;
2905            best_c = seed_c;
2906        }
2907        (best, best_c)
2908    }
2909
2910    /// Encodes macroblock `(mb_x, mb_y)` as an inter macroblock of the given
2911    /// `mode` (0 = P_L0_16x16, 1 = P_16x8, 2 = P_8x16) with one motion vector
2912    /// per partition: motion-compensate each partition, code the macroblock
2913    /// residual, and reconstruct.
2914    #[allow(clippy::too_many_arguments)]
2915    /// Dispatch to the current coded path (`_v1`) or the isolated fused path
2916    /// (`_v2`), selected by the hidden `coded_path_v2` A/B knob. Both produce
2917    /// byte-identical bitstreams (gated by the `coded_path_ab` test); the split
2918    /// exists so the two run side-by-side in one binary for honest timing.
2919    #[allow(clippy::too_many_arguments)]
2920    fn encode_inter_mb(
2921        &mut self,
2922        w: &mut BitWriter,
2923        refs: &[crate::RefFrame],
2924        sy: &[u8],
2925        su: &[u8],
2926        sv: &[u8],
2927        mb_x: usize,
2928        mb_y: usize,
2929        mode: u8,
2930        parts: &[(i32, (i32, i32))],
2931    ) {
2932        if self.coded_path_v2 {
2933            self.encode_inter_mb_v2(w, refs, sy, su, sv, mb_x, mb_y, mode, parts);
2934        } else {
2935            self.encode_inter_mb_v1(w, refs, sy, su, sv, mb_x, mb_y, mode, parts);
2936        }
2937    }
2938
2939    /// Isolated, coefficient-fused inter coding path (A/B twin of `_v1`). The
2940    /// quantized luma levels stay in the hot 16-byte-aligned i16 DCT buffer for the
2941    /// whole MB; the i32 form is materialized on demand only for *coded* blocks
2942    /// (CAVLC scan + recon dequant), so uncoded quads never pay the conversion and
2943    /// there is no 256-word i32 `q_blocks` round-trip. Byte-identical to `_v1`
2944    /// (gated by `coded_path_ab`). Accel-only optimization; the scalar build reuses
2945    /// `_v1` unchanged.
2946    #[allow(clippy::too_many_arguments)]
2947    fn encode_inter_mb_v2(
2948        &mut self,
2949        w: &mut BitWriter,
2950        refs: &[crate::RefFrame],
2951        sy: &[u8],
2952        su: &[u8],
2953        sv: &[u8],
2954        mb_x: usize,
2955        mb_y: usize,
2956        mode: u8,
2957        parts: &[(i32, (i32, i32))],
2958    ) {
2959        // Descent E/F: identify this mc_luma population by call site.
2960        #[cfg(feature = "profile")]
2961        let _site = rusty_h264_common::inter::mcstats::SiteTag::new(1);
2962        #[cfg(not(accel))]
2963        {
2964            self.encode_inter_mb_v1(w, refs, sy, su, sv, mb_x, mb_y, mode, parts);
2965        }
2966        #[cfg(accel)]
2967        {
2968            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncInterCode);
2969            let (qp, qpc) = (self.qp, self.qpc);
2970            let w4 = self.mb_w * 4;
2971            let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
2972
2973            // ---- per-partition motion compensation + MV prediction (== v1) ----
2974            let mut pred_y = [0u8; 256];
2975            let mut c_pred = [[0u8; 64]; 2];
2976            let mut mvds = [(0i32, 0i32); 4];
2977            let mut n_mvd = 0;
2978            let _g_mc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
2979            for (part, &(rx, ry, rw, rh)) in inter_partitions(mode).iter().enumerate() {
2980                let (refi, mv) = parts[part];
2981                let reference = &refs[refi as usize];
2982                let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
2983                let [a, b, c] = self.mv_neighbors_block(pbx, pby, (rw / 4) as isize);
2984                let pmv = predict_partition_mv(mode, part, a, b, c, refi);
2985                mvds[n_mvd] = (mv.0 - pmv.0, mv.1 - pmv.1);
2986                n_mvd += 1;
2987                for by in ry / 4..ry / 4 + rh / 4 {
2988                    for bx in rx / 4..rx / 4 + rw / 4 {
2989                        let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
2990                        self.mv_y[idx] = mv;
2991                        self.inter_y[idx] = true;
2992                        self.ref_idx_y[idx] = refi;
2993                        self.coded_y[idx] = true;
2994                    }
2995                }
2996                if rw == 16 && rh == 16 {
2997                    self.mc_luma_cached(reference, mb_x * 16, mb_y * 16, 16, 16, mv.0, mv.1, &mut pred_y);
2998                } else {
2999                    let mut tmp = [0u8; 256];
3000                    self.mc_luma_cached(reference, mb_x * 16 + rx, mb_y * 16 + ry, rw, rh, mv.0, mv.1, &mut tmp);
3001                    // H-17: the per-pixel re-stride was the runtime-width copy trap
3002                    // (a bounds-checked store per pixel); const-width row copies are
3003                    // byte-identical and lower to inline moves.
3004                    if rw == 8 {
3005                        for dy in 0..rh {
3006                            pred_y[(ry + dy) * 16 + rx..][..8].copy_from_slice(&tmp[dy * 8..][..8]);
3007                        }
3008                    } else {
3009                        for dy in 0..rh {
3010                            pred_y[(ry + dy) * 16 + rx..][..16].copy_from_slice(&tmp[dy * 16..][..16]);
3011                        }
3012                    }
3013                }
3014                let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
3015                for cc in 0..2 {
3016                    let rc = if cc == 0 { &reference.u } else { &reference.v };
3017                    if crw == 8 && crh == 8 {
3018                        mc_chroma(rc, self.ccw, cch, mb_x * 8, mb_y * 8, 8, 8, mv.0, mv.1, &mut c_pred[cc]);
3019                    } else {
3020                        let mut tc = [0u8; 64];
3021                        mc_chroma(rc, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv.0, mv.1, &mut tc);
3022                        // H-17: same const-width row-copy fix as luma.
3023                        if crw == 4 {
3024                            for dy in 0..crh {
3025                                c_pred[cc][(cry + dy) * 8 + crx..][..4].copy_from_slice(&tc[dy * 4..][..4]);
3026                            }
3027                        } else {
3028                            for dy in 0..crh {
3029                                c_pred[cc][(cry + dy) * 8 + crx..][..8].copy_from_slice(&tc[dy * 8..][..8]);
3030                            }
3031                        }
3032                    }
3033                }
3034            }
3035
3036            // ---- luma residual + quantization: keep levels in the i16 buffer ----
3037            let mut dctw = AlignedDct([0i16; 256]);
3038            let dct = &mut dctw.0;
3039            let mut cbp_luma = 0u32;
3040            drop(_g_mc);
3041            let _g_tq = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncTq);
3042            let base = mb_y * 16 * self.cw + mb_x * 16;
3043            for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
3044                rusty_h264_accel::dct_four_t4(
3045                    &mut dct[qi * 64..qi * 64 + 64],
3046                    &sy[base + qy * self.cw + qx..],
3047                    self.cw,
3048                    &pred_y[qy * 16 + qx..],
3049                    16,
3050                );
3051            }
3052            let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
3053            let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
3054            for qi in 0..4 {
3055                rusty_h264_accel::quant_four_4x4(&mut dct[qi * 64..qi * 64 + 64], &ff, mf);
3056            }
3057            // cbp per quad straight from the i16 levels (no i32 q_blocks copy).
3058            for blk in 0..16 {
3059                if dct[blk * 16..blk * 16 + 16].iter().any(|&v| v != 0) {
3060                    cbp_luma |= 1 << (blk / 4);
3061                }
3062            }
3063
3064            // ---- chroma residual (identical to v1: c_q stays i32) ----
3065            let mut c_dc_levels = [[0i32; 4]; 2];
3066            let mut c_recon_dc = [[0i32; 4]; 2];
3067            let mut c_q = [[[0i32; 16]; 4]; 2];
3068            let (mut any_ac, mut any_dc) = (false, false);
3069            for c in 0..2 {
3070                let src = if c == 0 { su } else { sv };
3071                let dc2x2 = {
3072                    #[repr(align(16))]
3073                    struct A([i16; 64]);
3074                    let mut cdct = A([0i16; 64]);
3075                    rusty_h264_accel::dct_four_t4(
3076                        &mut cdct.0,
3077                        &src[(mb_y * 8) * self.ccw + mb_x * 8..],
3078                        self.ccw,
3079                        &c_pred[c],
3080                        8,
3081                    );
3082                    let dc = [cdct.0[0] as i32, cdct.0[16] as i32, cdct.0[32] as i32, cdct.0[48] as i32];
3083                    let ffc = rusty_h264_common::transform::quant_dz_ff(qpc, 6);
3084                    let mfc = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
3085                    rusty_h264_accel::quant_four_4x4(&mut cdct.0, &ffc, mfc);
3086                    for i in 0..4 {
3087                        let q = &mut c_q[c][i];
3088                        q[0] = 0;
3089                        for j in 1..16 {
3090                            let v = cdct.0[i * 16 + j] as i32;
3091                            q[j] = v;
3092                            if v != 0 {
3093                                any_ac = true;
3094                            }
3095                        }
3096                    }
3097                    dc
3098                };
3099                let dl = forward_quant_chroma_dc(&dc2x2, qpc, false);
3100                if dl.iter().any(|&v| v != 0) {
3101                    any_dc = true;
3102                }
3103                c_recon_dc[c] = inverse_quant_chroma_dc(&dl, qpc);
3104                c_dc_levels[c] = dl;
3105            }
3106            let cbp_chroma: u32 = if any_ac { 2 } else if any_dc { 1 } else { 0 };
3107            let cbp = cbp_luma | (cbp_chroma << 4);
3108
3109            // ---- emit syntax (== v1) ----
3110            drop(_g_tq);
3111            let _g_syn = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
3112            w.write_ue(mode as u32);
3113            let num_refs = refs.len();
3114            if num_refs > 1 {
3115                for &(refi, _) in parts {
3116                    write_ref_idx(w, refi, num_refs);
3117                }
3118            }
3119            for &(mvdx, mvdy) in &mvds[..n_mvd] {
3120                w.write_se(mvdx);
3121                w.write_se(mvdy);
3122            }
3123            write_cbp_inter(w, cbp);
3124            if cbp != 0 {
3125                w.write_se(self.qp_delta()); // mb_qp_delta (AQ per-MB QPy)
3126            }
3127            self.nnz_cache_load(mb_x, mb_y);
3128            drop(_g_syn);
3129
3130            // ---- CAVLC: scan straight from the i16 levels for coded blocks ----
3131            let _g_scan = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Scatter);
3132            for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
3133                let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
3134                let total = if cbp_luma & (1 << (blk / 4)) != 0 {
3135                    let nc = self.nc_pred(lbx, lby);
3136                    let scan16 = scan_4x4_dcac_i16(&dct[blk * 16..blk * 16 + 16]);
3137                    encode_residual_block(w, &scan16, 16, nc) as u8
3138                } else {
3139                    0
3140                };
3141                self.nnz_cache_set(lbx, lby, total);
3142                self.nnz_y[by * w4 + bx] = total;
3143            }
3144            if cbp_chroma != 0 {
3145                for c in 0..2 {
3146                    encode_residual_block(w, &c_dc_levels[c], 4, -1);
3147                }
3148            }
3149            if cbp_chroma == 2 {
3150                self.chroma_cache_load(mb_x, mb_y);
3151                let w2 = self.mb_w * 2;
3152                for c in 0..2 {
3153                    for &(bx, by) in &CHROMA_4X4_SCAN_XY {
3154                        let nc = self.chroma_nc_pred(c, bx, by);
3155                        let ac = scan_4x4_ac(&c_q[c][by * 2 + bx]);
3156                        let total = encode_residual_block(w, &ac, 15, nc) as u8;
3157                        self.chroma_nnz_cache_set(c, bx, by, total);
3158                        self.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total;
3159                    }
3160                }
3161            }
3162            drop(_g_scan);
3163
3164            // ---- reconstruction: dequantize luma straight from the i16 levels ----
3165            let _g_rec = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
3166            #[repr(align(16))]
3167            struct Align16([i16; 64]);
3168            let mut dct_in = Align16([0i16; 64]);
3169            for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
3170                let rec_off = base + qy * self.cw + qx;
3171                if cbp_luma & (1 << qi) == 0 {
3172                    for r in 0..8 {
3173                        let (dsti, srci) = (rec_off + r * self.cw, (qy + r) * 16 + qx);
3174                        self.rec_y[dsti..dsti + 8].copy_from_slice(&pred_y[srci..srci + 8]);
3175                    }
3176                    continue;
3177                }
3178                for k in 0..4 {
3179                    let blk = qi * 4 + k;
3180                    let mut lvl = [0i32; 16];
3181                    for i in 0..16 {
3182                        lvl[i] = dct[blk * 16 + i] as i32;
3183                    }
3184                    let deq = dequantize(&lvl, qp);
3185                    for i in 0..16 {
3186                        dct_in.0[k * 16 + i] = deq[i] as i16;
3187                    }
3188                }
3189                rusty_h264_accel::idct_four_t4_rec(
3190                    &mut self.rec_y[rec_off..],
3191                    self.cw,
3192                    &pred_y[qy * 16 + qx..],
3193                    16,
3194                    &dct_in.0,
3195                );
3196            }
3197            // chroma recon (identical to v1)
3198            for c in 0..2 {
3199                let base_c = (mb_y * 8) * self.ccw + mb_x * 8;
3200                let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
3201                if cbp_chroma == 0 {
3202                    for r in 0..8 {
3203                        let dsti = base_c + r * self.ccw;
3204                        plane[dsti..dsti + 8].copy_from_slice(&c_pred[c][r * 8..r * 8 + 8]);
3205                    }
3206                } else {
3207                    #[repr(align(16))]
3208                    struct A([i16; 64]);
3209                    let mut d = A([0i16; 64]);
3210                    for i in 0..4 {
3211                        let deq = dequantize(&c_q[c][i], qpc);
3212                        for j in 0..16 {
3213                            d.0[i * 16 + j] = deq[j] as i16;
3214                        }
3215                        d.0[i * 16] = c_recon_dc[c][i] as i16;
3216                    }
3217                    rusty_h264_accel::idct_four_t4_rec(&mut plane[base_c..], self.ccw, &c_pred[c], 8, &d.0);
3218                }
3219            }
3220            for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
3221                self.modes_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 2;
3222            }
3223        }
3224    }
3225
3226    #[allow(clippy::too_many_arguments)]
3227    fn encode_inter_mb_v1(
3228        &mut self,
3229        w: &mut BitWriter,
3230        refs: &[crate::RefFrame],
3231        sy: &[u8],
3232        su: &[u8],
3233        sv: &[u8],
3234        mb_x: usize,
3235        mb_y: usize,
3236        mode: u8,
3237        parts: &[(i32, (i32, i32))],
3238    ) {
3239        self.encode_inter_mb_v1_b(w, refs, sy, su, sv, mb_x, mb_y, mode, parts, None);
3240    }
3241
3242    /// As [`Self::encode_inter_mb_v1`], but `b_mode` selects B-slice framing: the
3243    /// macroblock is coded as `B_L0_16x16` (`mb_type == 1`) instead of the P-slice
3244    /// `mb_type == mode`. Everything else — the single List-0 partition, the median
3245    /// `mvd_l0` predictor, the residual, and the reconstruction — is byte-identical
3246    /// to `P_L0_16x16`, so the caller passes `mode == 0`, `refs == &[L0_anchor]`
3247    /// (length 1 ⇒ no `ref_idx` coded), and `parts == &[(0, mv)]`.
3248    /// Decide + reconstruct one inter macroblock (motion compensation, residual,
3249    /// quantize, reconstruct, commit motion grids) — everything except entropy
3250    /// coding. Returns an [`InterPlan`] coded by either backend, so CAVLC and CABAC
3251    /// share this whole path bit-for-bit (the P/B analogue of [`plan_mb`]).
3252    #[allow(clippy::too_many_arguments)]
3253    fn plan_inter_mb(
3254        &mut self,
3255        refs: &[crate::RefFrame],
3256        sy: &[u8],
3257        su: &[u8],
3258        sv: &[u8],
3259        mb_x: usize,
3260        mb_y: usize,
3261        mode: u8,
3262        parts: &[(i32, (i32, i32))],
3263        bspec: Option<BInter>,
3264    ) -> InterPlan {
3265        // Descent E/F: identify this mc_luma population by call site.
3266        #[cfg(feature = "profile")]
3267        let _site = rusty_h264_common::inter::mcstats::SiteTag::new(1);
3268        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncInterCode);
3269        let (qp, qpc) = (self.qp, self.qpc);
3270        let w4 = self.mb_w * 4;
3271        let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
3272
3273        // ---- per-partition motion compensation + MV prediction ----
3274        let mut pred_y = [0u8; 256];
3275        let mut c_pred = [[0u8; 64]; 2];
3276        let mut mvds = [(0i32, 0i32); 4]; // ≤4 partitions; no per-MB Vec alloc
3277        let mut plan_refs = [0i32; 4]; // per-partition ref_idx_l0 (0 for B / 1-ref)
3278        let mut n_mvd = 0;
3279        let _g_mc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
3280        if let Some(b) = bspec.filter(|b| b.dir == 0) {
3281            // ---- B_Direct_16x16 (mb_type 0): spatial-direct prediction, no mvd ----
3282            let (dp, dc, motion) = self.b_direct(&refs[0], b.l1, mb_x, mb_y);
3283            pred_y = dp;
3284            c_pred = dc;
3285            self.commit_direct_motion(mb_x, mb_y, &motion);
3286        } else if let Some(b) = bspec {
3287            // ---- B 16×16 prediction: List-0 / List-1 / Bi ----
3288            let use0 = b.dir == 1 || b.dir == 3;
3289            let use1 = b.dir == 2 || b.dir == 3;
3290            let (lx, ly) = (mb_x * 16, mb_y * 16);
3291            let (cx, cy) = (mb_x * 8, mb_y * 8);
3292            let (pbx, pby) = ((mb_x * 4) as isize, (mb_y * 4) as isize);
3293            // Per-list `mvd` against the median predictor over that list's neighbors.
3294            if use0 {
3295                let [a, c0, c1] = self.mv_neighbors_block_list(pbx, pby, 4, 0);
3296                let p = predict_partition_mv(0, 0, a, c0, c1, 0);
3297                mvds[n_mvd] = (b.mv0.0 - p.0, b.mv0.1 - p.1);
3298                n_mvd += 1;
3299            }
3300            if use1 {
3301                let [a, c0, c1] = self.mv_neighbors_block_list(pbx, pby, 4, 1);
3302                let p = predict_partition_mv(0, 0, a, c0, c1, 0);
3303                mvds[n_mvd] = (b.mv1.0 - p.0, b.mv1.1 - p.1);
3304                n_mvd += 1;
3305            }
3306            // Motion compensation. L0/L1 write straight into pred; Bi averages
3307            // (p+q+1)>>1 — the decoder's `b_mc` blend with weighted_bipred_idc=0.
3308            let mut a_y = [0u8; 256];
3309            let mut b_y = [0u8; 256];
3310            let mut a_c = [[0u8; 64]; 2];
3311            let mut b_c = [[0u8; 64]; 2];
3312            if use0 {
3313                mc_luma(&refs[0].y, self.cw, ch, lx, ly, 16, 16, b.mv0.0, b.mv0.1, &mut a_y);
3314                mc_chroma(&refs[0].u, self.ccw, cch, cx, cy, 8, 8, b.mv0.0, b.mv0.1, &mut a_c[0]);
3315                mc_chroma(&refs[0].v, self.ccw, cch, cx, cy, 8, 8, b.mv0.0, b.mv0.1, &mut a_c[1]);
3316            }
3317            if use1 {
3318                mc_luma(&b.l1.y, self.cw, ch, lx, ly, 16, 16, b.mv1.0, b.mv1.1, &mut b_y);
3319                mc_chroma(&b.l1.u, self.ccw, cch, cx, cy, 8, 8, b.mv1.0, b.mv1.1, &mut b_c[0]);
3320                mc_chroma(&b.l1.v, self.ccw, cch, cx, cy, 8, 8, b.mv1.0, b.mv1.1, &mut b_c[1]);
3321            }
3322            match (use0, use1) {
3323                (true, true) => {
3324                    for i in 0..256 {
3325                        pred_y[i] = bi_blend(a_y[i] as i32, b_y[i] as i32, self.bi_w);
3326                    }
3327                    for c in 0..2 {
3328                        for i in 0..64 {
3329                            c_pred[c][i] = bi_blend(a_c[c][i] as i32, b_c[c][i] as i32, self.bi_w);
3330                        }
3331                    }
3332                }
3333                (true, false) => {
3334                    pred_y = a_y;
3335                    c_pred = a_c;
3336                }
3337                _ => {
3338                    pred_y = b_y;
3339                    c_pred = b_c;
3340                }
3341            }
3342            // Commit per-list motion so later MBs' per-list predictors see it.
3343            for by in 0..4 {
3344                for bx in 0..4 {
3345                    let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
3346                    self.inter_y[idx] = true;
3347                    self.coded_y[idx] = true;
3348                    self.mv_y[idx] = if use0 { b.mv0 } else { (0, 0) };
3349                    self.ref_idx_y[idx] = if use0 { 0 } else { -1 };
3350                    self.mv1_y[idx] = if use1 { b.mv1 } else { (0, 0) };
3351                    self.ref_idx1_y[idx] = if use1 { 0 } else { -1 };
3352                }
3353            }
3354        } else {
3355        for (part, &(rx, ry, rw, rh)) in inter_partitions(mode).iter().enumerate() {
3356            let (refi, mv) = parts[part];
3357            plan_refs[part] = refi; // per-partition ref_idx_l0 → carried to the CABAC emit
3358            let reference = &refs[refi as usize];
3359            let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
3360            let [a, b, c] = self.mv_neighbors_block(pbx, pby, (rw / 4) as isize);
3361            let pmv = predict_partition_mv(mode, part, a, b, c, refi);
3362            mvds[n_mvd] = (mv.0 - pmv.0, mv.1 - pmv.1);
3363            n_mvd += 1;
3364            // Commit this partition's motion so later partitions can predict from it.
3365            for by in ry / 4..ry / 4 + rh / 4 {
3366                for bx in rx / 4..rx / 4 + rw / 4 {
3367                    let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
3368                    self.mv_y[idx] = mv;
3369                    self.inter_y[idx] = true;
3370                    self.ref_idx_y[idx] = refi;
3371                    self.coded_y[idx] = true;
3372                }
3373            }
3374            // Luma MC into the partition's sub-region. A full-MB (16×16) partition is
3375            // the whole `pred_y`, so MC straight into it — no scratch + repack copy.
3376            if rw == 16 && rh == 16 {
3377                self.mc_luma_cached(reference, mb_x * 16, mb_y * 16, 16, 16, mv.0, mv.1, &mut pred_y);
3378            } else {
3379                let mut tmp = [0u8; 256];
3380                self.mc_luma_cached(reference, mb_x * 16 + rx, mb_y * 16 + ry, rw, rh, mv.0, mv.1, &mut tmp);
3381                // H-17: const-width row copies (see the v2 twin).
3382                if rw == 8 {
3383                    for dy in 0..rh {
3384                        pred_y[(ry + dy) * 16 + rx..][..8].copy_from_slice(&tmp[dy * 8..][..8]);
3385                    }
3386                } else {
3387                    for dy in 0..rh {
3388                        pred_y[(ry + dy) * 16 + rx..][..16].copy_from_slice(&tmp[dy * 16..][..16]);
3389                    }
3390                }
3391            }
3392            // Chroma MC (half-resolution region); 8×8 = the whole plane prediction.
3393            let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
3394            for cc in 0..2 {
3395                let rc = if cc == 0 { &reference.u } else { &reference.v };
3396                if crw == 8 && crh == 8 {
3397                    mc_chroma(rc, self.ccw, cch, mb_x * 8, mb_y * 8, 8, 8, mv.0, mv.1, &mut c_pred[cc]);
3398                } else {
3399                    let mut tc = [0u8; 64];
3400                    mc_chroma(rc, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv.0, mv.1, &mut tc);
3401                    // H-17: const-width row copies (see the v2 twin).
3402                    if crw == 4 {
3403                        for dy in 0..crh {
3404                            c_pred[cc][(cry + dy) * 8 + crx..][..4].copy_from_slice(&tc[dy * 4..][..4]);
3405                        }
3406                    } else {
3407                        for dy in 0..crh {
3408                            c_pred[cc][(cry + dy) * 8 + crx..][..8].copy_from_slice(&tc[dy * 8..][..8]);
3409                        }
3410                    }
3411                }
3412            }
3413        }
3414        } // end P per-partition formation (else of the B branch)
3415
3416        // ---- luma residual + quantization ----
3417        let mut q_blocks = [[0i32; 16]; 16]; // raster, levels
3418        let mut cbp_luma = 0u32;
3419        // Inter 8x8-transform candidate (High profile, scalar path). Filled by the
3420        // per-MB 4x4-vs-8x8 RD below; false/zero means the 4x4 residual is used.
3421        #[allow(unused_mut)]
3422        let mut t8x8 = false;
3423        #[allow(unused_mut)]
3424        let mut q8 = [[0i32; 64]; 4];
3425        drop(_g_mc);
3426        let _g_tq = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncTq);
3427        #[cfg(accel)]
3428        {
3429            // openh264 `WelsDctFourT4_sse2` (fused residual+DCT) → i16, then
3430            // `WelsQuantFour4x4_sse2` in place — the whole DCT→quant chain stays in i16,
3431            // no i32 round-trip. Quant is openh264's structure carrying OUR deadzone
3432            // (`quant_dz_ff` + `QUANT_MF_OH`), so levels are bit-identical to `quantize`.
3433            let mut dctw = AlignedDct([0i16; 256]);
3434            let dct = &mut dctw.0;
3435            let base = mb_y * 16 * self.cw + mb_x * 16;
3436            for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
3437                rusty_h264_accel::dct_four_t4(
3438                    &mut dct[qi * 64..qi * 64 + 64],
3439                    &sy[base + qy * self.cw + qx..],
3440                    self.cw,
3441                    &pred_y[qy * 16 + qx..],
3442                    16,
3443                );
3444            }
3445            let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
3446            let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
3447            for qi in 0..4 {
3448                rusty_h264_accel::quant_four_4x4(&mut dct[qi * 64..qi * 64 + 64], &ff, mf);
3449            }
3450            for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
3451                let mut nz = false;
3452                for i in 0..16 {
3453                    let v = dct[blk * 16 + i] as i32;
3454                    q_blocks[lby * 4 + lbx][i] = v;
3455                    nz |= v != 0;
3456                }
3457                if nz {
3458                    cbp_luma |= 1 << (blk / 4);
3459                }
3460            }
3461        }
3462        #[cfg(not(accel))]
3463        {
3464            // Scalar/`wide`: gather all 16 residual blocks, batched forward-DCT, quantize.
3465            let mut res_blocks = [[0i32; 16]; 16]; // raster
3466            for lby in 0..4 {
3467                for lbx in 0..4 {
3468                    let b = &mut res_blocks[lby * 4 + lbx];
3469                    for dy in 0..4 {
3470                        for dx in 0..4 {
3471                            let sx = mb_x * 16 + lbx * 4 + dx;
3472                            let syy = mb_y * 16 + lby * 4 + dy;
3473                            b[dy * 4 + dx] = sy[syy * self.cw + sx] as i32
3474                                - pred_y[(lby * 4 + dy) * 16 + (lbx * 4 + dx)] as i32;
3475                        }
3476                    }
3477                }
3478            }
3479            let mut coeffs = [[0i32; 16]; 16];
3480            forward_dct_blocks(&res_blocks, &mut coeffs);
3481            for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
3482                let q = rdoq(&coeffs[lby * 4 + lbx], qp, 6, self.rdoq_strength, 0);
3483                if q.iter().any(|&v| v != 0) {
3484                    cbp_luma |= 1 << (blk / 4);
3485                }
3486                q_blocks[lby * 4 + lbx] = q;
3487            }
3488        }
3489
3490        // Per-MB transform-size RD (runs in scalar AND accel builds — q_blocks +
3491        // cbp_luma are filled by whichever quant path ran; the 8x8 candidate + its
3492        // recon are pure Rust). One 8x8 DCT per 8x8 block vs four 4x4s. Every inter
3493        // partition here is >= 8x8, so transform_size_8x8_flag is always allowed.
3494        // Content-adaptive by construction — the winner is chosen per MB.
3495        {
3496            if self.transform_8x8 && self.inter8x8 != 0 {
3497                let lambda =
3498                    0.85 * self.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
3499                let mut ssd4 = 0i64;
3500                let mut rate4 = 0f64;
3501                for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
3502                    let mut predb = [0i32; 16];
3503                    for dy in 0..4 {
3504                        for dx in 0..4 {
3505                            predb[dy * 4 + dx] =
3506                                pred_y[(lby * 4 + dy) * 16 + (lbx * 4 + dx)] as i32;
3507                        }
3508                    }
3509                    let deq = dequantize(&q_blocks[lby * 4 + lbx], qp);
3510                    let s = reconstruct_4x4(&deq, &predb);
3511                    for dy in 0..4 {
3512                        for dx in 0..4 {
3513                            let sx = mb_x * 16 + lbx * 4 + dx;
3514                            let syy = mb_y * 16 + lby * 4 + dy;
3515                            let d = s[dy * 4 + dx] as i64 - sy[syy * self.cw + sx] as i64;
3516                            ssd4 += d * d;
3517                        }
3518                    }
3519                    for &l in &q_blocks[lby * 4 + lbx] {
3520                        if l != 0 {
3521                            rate4 += rdoq_rate((l as i64).abs());
3522                        }
3523                    }
3524                }
3525                let (q8c, cbp8, rate8, _rec8, ssd8) =
3526                    plan_inter8_luma(sy, self.cw, mb_x, mb_y, &pred_y, qp);
3527                // Both candidates priced with the SAME level-aware rate (Σ rdoq_rate);
3528                // `inter8_pen` is an optional extra bias (default 0) on the 8x8 flag.
3529                let j4 = ssd4 as f64 + lambda * (rate4 + 16.0);
3530                let j8 = ssd8 as f64 + lambda * (rate8 + 16.0 + self.inter8_pen as f64);
3531                if cbp8 > 0 && j8 < j4 {
3532                    t8x8 = true;
3533                    cbp_luma = cbp8;
3534                    q8 = q8c;
3535                }
3536            }
3537        }
3538
3539        // ---- chroma residual (prediction already built per partition) ----
3540        let mut c_dc_levels = [[0i32; 4]; 2];
3541        let mut c_recon_dc = [[0i32; 4]; 2];
3542        let mut c_q = [[[0i32; 16]; 4]; 2];
3543        let (mut any_ac, mut any_dc) = (false, false);
3544        for c in 0..2 {
3545            let src = if c == 0 { su } else { sv };
3546            // Fast path: one dct_four_t4 covers the whole 8x8 chroma region (all 4
3547            // blocks, residual+DCT fused straight from the planes); block b's pre-quant
3548            // DC is dct[b*16] (quad z-scan == 2x2 raster); quant_four_4x4 with our
3549            // FF/MF is bit-identical to scalar `quantize`. Same pairing the P_Skip
3550            // free-check proved byte-identical over the corpus.
3551            #[cfg(accel)]
3552            let (mut dc2x2, applied) = {
3553                #[repr(align(16))]
3554                struct A([i16; 64]);
3555                let mut dct = A([0i16; 64]);
3556                rusty_h264_accel::dct_four_t4(
3557                    &mut dct.0,
3558                    &src[(mb_y * 8) * self.ccw + mb_x * 8..],
3559                    self.ccw,
3560                    &c_pred[c],
3561                    8,
3562                );
3563                let dc = [
3564                    dct.0[0] as i32,
3565                    dct.0[16] as i32,
3566                    dct.0[32] as i32,
3567                    dct.0[48] as i32,
3568                ];
3569                let ffc = rusty_h264_common::transform::quant_dz_ff(qpc, 6);
3570                let mfc = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
3571                rusty_h264_accel::quant_four_4x4(&mut dct.0, &ffc, mfc);
3572                for i in 0..4 {
3573                    let q = &mut c_q[c][i];
3574                    q[0] = 0;
3575                    for j in 1..16 {
3576                        let v = dct.0[i * 16 + j] as i32;
3577                        q[j] = v;
3578                        if v != 0 {
3579                            any_ac = true;
3580                        }
3581                    }
3582                }
3583                (dc, true)
3584            };
3585            #[cfg(not(accel))]
3586            let (mut dc2x2, applied) = ([0i32; 4], false);
3587            if !applied {
3588                // Scalar/`wide` twin: gather, batch forward DCT, quantize per block.
3589                let mut res_blocks = [[0i32; 16]; 4];
3590                for by in 0..2 {
3591                    for bx in 0..2 {
3592                        let b = &mut res_blocks[by * 2 + bx];
3593                        for dy in 0..4 {
3594                            for dx in 0..4 {
3595                                let sx = mb_x * 8 + bx * 4 + dx;
3596                                let syy = mb_y * 8 + by * 4 + dy;
3597                                b[dy * 4 + dx] = src[syy * self.ccw + sx] as i32
3598                                    - c_pred[c][(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
3599                            }
3600                        }
3601                    }
3602                }
3603                let mut coeffs = [[0i32; 16]; 4];
3604                forward_dct_blocks(&res_blocks, &mut coeffs);
3605                for i in 0..4 {
3606                    dc2x2[i] = coeffs[i][0];
3607                    let mut q = rdoq(&coeffs[i], qpc, 6, self.rdoq_strength, 1);
3608                    q[0] = 0;
3609                    if q[1..].iter().any(|&v| v != 0) {
3610                        any_ac = true;
3611                    }
3612                    c_q[c][i] = q;
3613                }
3614            }
3615            let dl = forward_quant_chroma_dc(&dc2x2, qpc, false);
3616            if dl.iter().any(|&v| v != 0) {
3617                any_dc = true;
3618            }
3619            c_recon_dc[c] = inverse_quant_chroma_dc(&dl, qpc);
3620            c_dc_levels[c] = dl;
3621        }
3622        let cbp_chroma: u32 = if any_ac { 2 } else if any_dc { 1 } else { 0 };
3623        let cbp = cbp_luma | (cbp_chroma << 4);
3624
3625        drop(_g_tq);
3626        let _g_rec = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
3627        // ---- reconstruction (luma) ----
3628        #[cfg(accel)]
3629        if t8x8 {
3630            // 8x8-transform recon is pure Rust (no asm 8x8 kernels yet); inverse of
3631            // the decoder's t8x8 inter path. Same code as the scalar branch below.
3632            let weight = [16i32; 64];
3633            for b8 in 0..4usize {
3634                let (b8x, b8y) = (b8 % 2, b8 / 2);
3635                let res_r = inverse_quant_8x8(&q8[b8], qp, &weight);
3636                let predb: [i32; 64] = std::array::from_fn(|i| {
3637                    pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32
3638                });
3639                let recon = add_residual_8x8(&res_r, &predb);
3640                for dy in 0..8 {
3641                    for dx in 0..8 {
3642                        let px = mb_x * 16 + b8x * 8 + dx;
3643                        let py = mb_y * 16 + b8y * 8 + dy;
3644                        self.rec_y[py * self.cw + px] = recon[dy * 8 + dx];
3645                    }
3646                }
3647            }
3648        } else {
3649            // Dequantize all 16 blocks into the 4-quadrant int16 layout (16-byte
3650            // aligned — the kernel uses movdqa coeff loads), then inverse-DCT + add
3651            // prediction + clip per quadrant via openh264. The inverse butterfly +
3652            // (x+32)>>6 is bit-identical to reconstruct_4x4 (verified in accel).
3653            // An 8x8 quad whose cbp bit is clear has ZERO residual: reconstruction
3654            // IS the prediction (the decoder's own uncoded-region fast path) — a row
3655            // copy replaces dequant + convert + idct for that quad. Byte-identical:
3656            // idct of an all-zero block adds (0+32)>>6 = 0 to pred, clip is identity.
3657            #[repr(align(16))]
3658            struct Align16([i16; 64]);
3659            let mut dct_in = Align16([0i16; 64]);
3660            let base = mb_y * 16 * self.cw + mb_x * 16;
3661            for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
3662                let rec_off = base + qy * self.cw + qx;
3663                if cbp_luma & (1 << qi) == 0 {
3664                    for r in 0..8 {
3665                        let (dsti, srci) = (rec_off + r * self.cw, (qy + r) * 16 + qx);
3666                        self.rec_y[dsti..dsti + 8].copy_from_slice(&pred_y[srci..srci + 8]);
3667                    }
3668                    continue;
3669                }
3670                for k in 0..4 {
3671                    let blk = qi * 4 + k;
3672                    let (lbx, lby) = LUMA_4X4_SCAN_XY[blk];
3673                    let deq = dequantize(&q_blocks[lby * 4 + lbx], qp);
3674                    for i in 0..16 {
3675                        dct_in.0[k * 16 + i] = deq[i] as i16;
3676                    }
3677                }
3678                rusty_h264_accel::idct_four_t4_rec(
3679                    &mut self.rec_y[rec_off..],
3680                    self.cw,
3681                    &pred_y[qy * 16 + qx..],
3682                    16,
3683                    &dct_in.0,
3684                );
3685            }
3686        }
3687        #[cfg(not(accel))]
3688        if t8x8 {
3689            // 8x8-transform reconstruction (inverse of the decoder's t8x8 inter path).
3690            let weight = [16i32; 64];
3691            for b8 in 0..4usize {
3692                let (b8x, b8y) = (b8 % 2, b8 / 2);
3693                let res_r = inverse_quant_8x8(&q8[b8], qp, &weight);
3694                let predb: [i32; 64] = std::array::from_fn(|i| {
3695                    pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32
3696                });
3697                let recon = add_residual_8x8(&res_r, &predb);
3698                for dy in 0..8 {
3699                    for dx in 0..8 {
3700                        let px = mb_x * 16 + b8x * 8 + dx;
3701                        let py = mb_y * 16 + b8y * 8 + dy;
3702                        self.rec_y[py * self.cw + px] = recon[dy * 8 + dx];
3703                    }
3704                }
3705            }
3706        } else {
3707            for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
3708                let mut predb = [0i32; 16];
3709                for dy in 0..4 {
3710                    for dx in 0..4 {
3711                        predb[dy * 4 + dx] = pred_y[(lby * 4 + dy) * 16 + (lbx * 4 + dx)] as i32;
3712                    }
3713                }
3714                let deq = dequantize(&q_blocks[lby * 4 + lbx], qp);
3715                let s = reconstruct_4x4(&deq, &predb);
3716                store(&mut self.rec_y, self.cw, mb_x * 16 + lbx * 4, mb_y * 16 + lby * 4, &s);
3717            }
3718        }
3719        for c in 0..2 {
3720            // Fast path: dequantize into the quad i16 layout (raster == the kernel's
3721            // z-order for a 2x2) with the Hadamard DC injected, then ONE
3722            // idct+add-pred+clip kernel writes the 8x8 straight into the plane —
3723            // bit-identical to the scalar tail below (verified kernel pairing).
3724            #[cfg(accel)]
3725            {
3726                let base = (mb_y * 8) * self.ccw + mb_x * 8;
3727                let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
3728                if cbp_chroma == 0 {
3729                    // No chroma residual at all: recon = prediction (row copies).
3730                    for r in 0..8 {
3731                        let dsti = base + r * self.ccw;
3732                        plane[dsti..dsti + 8].copy_from_slice(&c_pred[c][r * 8..r * 8 + 8]);
3733                    }
3734                } else {
3735                    #[repr(align(16))]
3736                    struct A([i16; 64]);
3737                    let mut d = A([0i16; 64]);
3738                    for i in 0..4 {
3739                        let deq = dequantize(&c_q[c][i], qpc);
3740                        for j in 0..16 {
3741                            d.0[i * 16 + j] = deq[j] as i16;
3742                        }
3743                        d.0[i * 16] = c_recon_dc[c][i] as i16;
3744                    }
3745                    rusty_h264_accel::idct_four_t4_rec(&mut plane[base..], self.ccw, &c_pred[c], 8, &d.0);
3746                }
3747            }
3748            #[cfg(not(accel))]
3749            {
3750                // Dequantize the 4 blocks (raster, DC overridden by the 2×2-Hadamard
3751                // recon), then batch the inverse DCT and share the add+clip tail.
3752                let mut deq_blocks = [[0i32; 16]; 4];
3753                for i in 0..4 {
3754                    deq_blocks[i] = dequantize(&c_q[c][i], qpc);
3755                    deq_blocks[i][0] = c_recon_dc[c][i];
3756                }
3757                let mut res = [[0i32; 16]; 4];
3758                inverse_dct_blocks(&deq_blocks, &mut res);
3759                let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
3760                for by in 0..2 {
3761                    for bx in 0..2 {
3762                        let mut predb = [0i32; 16];
3763                        for dy in 0..4 {
3764                            for dx in 0..4 {
3765                                predb[dy * 4 + dx] = c_pred[c][(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
3766                            }
3767                        }
3768                        let s = add_residual_4x4(&res[by * 2 + bx], &predb);
3769                        store(plane, self.ccw, mb_x * 8 + bx * 4, mb_y * 8 + by * 4, &s);
3770                    }
3771                }
3772            }
3773        }
3774        // MV grid + coded flags were set per partition; mark modes as DC.
3775        for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
3776            self.modes_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 2;
3777        }
3778        InterPlan { mvds, plan_refs, n_mvd, cbp, q_blocks, c_dc_levels, c_q, t8x8, q8 }
3779    }
3780
3781    /// Code one planned inter macroblock as CAVLC (the original `encode_inter_mb_v1_b`
3782    /// tail). `plan_inter_mb` already committed the reconstruction + motion grids.
3783    #[allow(clippy::too_many_arguments)]
3784    fn encode_inter_mb_v1_b(
3785        &mut self,
3786        w: &mut BitWriter,
3787        refs: &[crate::RefFrame],
3788        sy: &[u8],
3789        su: &[u8],
3790        sv: &[u8],
3791        mb_x: usize,
3792        mb_y: usize,
3793        mode: u8,
3794        parts: &[(i32, (i32, i32))],
3795        bspec: Option<BInter>,
3796    ) {
3797        let plan = self.plan_inter_mb(refs, sy, su, sv, mb_x, mb_y, mode, parts, bspec);
3798        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncEmit);
3799        self.emit_inter_cavlc(w, refs.len(), mb_x, mb_y, mode, parts, bspec, &plan);
3800    }
3801
3802    /// CAVLC entropy coding for a planned inter macroblock.
3803    #[allow(clippy::too_many_arguments)]
3804    fn emit_inter_cavlc(
3805        &mut self,
3806        w: &mut BitWriter,
3807        num_refs: usize,
3808        mb_x: usize,
3809        mb_y: usize,
3810        mode: u8,
3811        parts: &[(i32, (i32, i32))],
3812        bspec: Option<BInter>,
3813        plan: &InterPlan,
3814    ) {
3815        let w4 = self.mb_w * 4;
3816        let (cbp, cbp_luma, cbp_chroma) = (plan.cbp, plan.cbp & 15, plan.cbp >> 4);
3817        // mb_pred order (spec 7.3.5.1): mb_type, then all ref_idx_l0, then all mvd_l0.
3818        // B-slice mb_type = the B direction 1/2/3; P-slice uses `mode`. ref_idx coded
3819        // only when >1 reference is active.
3820        w.write_ue(bspec.map_or(mode as u32, |b| b.dir as u32)); // inter mb_type
3821        // P_8x8 (mb_type 3): sub_mb_type per 8×8 (spec 7.3.5.2, before ref_idx/mvd).
3822        // 0 = P_L0_8x8 (one MV) — the only shape emitted for now.
3823        if mode == 3 {
3824            for _ in 0..4 {
3825                w.write_ue(0);
3826            }
3827        }
3828        if num_refs > 1 {
3829            for &(refi, _) in parts {
3830                write_ref_idx(w, refi, num_refs);
3831            }
3832        }
3833        for &(mvdx, mvdy) in &plan.mvds[..plan.n_mvd] {
3834            w.write_se(mvdx);
3835            w.write_se(mvdy);
3836        }
3837        write_cbp_inter(w, cbp);
3838        // transform_size_8x8_flag: after cbp, before mb_qp_delta, present only when
3839        // luma has coefficients and the 8x8 transform is enabled. Every inter partition
3840        // here is >= 8x8, so the spec's allow_8x8 (all partitions >= 8x8) always holds.
3841        if cbp_luma > 0 && self.transform_8x8 {
3842            w.write_bit(plan.t8x8);
3843        }
3844        if cbp != 0 {
3845            w.write_se(self.qp_delta()); // mb_qp_delta (AQ per-MB QPy)
3846        }
3847        self.nnz_cache_load(mb_x, mb_y);
3848        if plan.t8x8 {
3849            // 8x8 residual: four interleaved 4x4 CAVLC sub-blocks per 8x8 block
3850            // (coeff k of sub s -> 8x8 scan position 4k+s), the inverse of the
3851            // decoder's t8x8 inter luma read. nnz set per 4x4 sub-block.
3852            for b8 in 0..4usize {
3853                let (b8x, b8y) = (b8 % 2, b8 / 2);
3854                let scan8 = scan_8x8_fwd(&plan.q8[b8]);
3855                for sub in 0..4usize {
3856                    let (cx, cy) = (b8x * 2 + sub % 2, b8y * 2 + sub / 2);
3857                    let (bx, by) = (mb_x * 4 + cx, mb_y * 4 + cy);
3858                    let total = if cbp_luma & (1 << b8) != 0 {
3859                        let nc = self.nc_pred(cx, cy);
3860                        let blk: [i32; 16] = std::array::from_fn(|k| scan8[4 * k + sub]);
3861                        encode_residual_block(w, &blk, 16, nc) as u8
3862                    } else {
3863                        0
3864                    };
3865                    self.nnz_cache_set(cx, cy, total);
3866                    self.nnz_y[by * w4 + bx] = total;
3867                }
3868            }
3869        } else {
3870            for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
3871                let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
3872                let total = if cbp_luma & (1 << (blk / 4)) != 0 {
3873                    let nc = self.nc_pred(lbx, lby);
3874                    let scan16 = scan_4x4_dcac(&plan.q_blocks[lby * 4 + lbx]);
3875                    encode_residual_block(w, &scan16, 16, nc) as u8
3876                } else {
3877                    0
3878                };
3879                self.nnz_cache_set(lbx, lby, total);
3880                self.nnz_y[by * w4 + bx] = total;
3881            }
3882        }
3883        if cbp_chroma != 0 {
3884            for c in 0..2 {
3885                encode_residual_block(w, &plan.c_dc_levels[c], 4, -1);
3886            }
3887        }
3888        if cbp_chroma == 2 {
3889            self.chroma_cache_load(mb_x, mb_y);
3890            let w2 = self.mb_w * 2;
3891            for c in 0..2 {
3892                for &(bx, by) in &CHROMA_4X4_SCAN_XY {
3893                    let nc = self.chroma_nc_pred(c, bx, by);
3894                    let ac = scan_4x4_ac(&plan.c_q[c][by * 2 + bx]);
3895                    let total = encode_residual_block(w, &ac, 15, nc) as u8;
3896                    self.chroma_nnz_cache_set(c, bx, by, total);
3897                    self.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total;
3898                }
3899            }
3900        }
3901    }
3902
3903    /// Descent F: reconstruction / skip-check MC through the cached half-pel planes
3904    /// instead of the per-pixel 6-tap. `hpel_block` is proven bit-identical to `mc_luma`
3905    /// (`hpel_block_matches_mc_luma_exactly`) and the `f` plane is the padded,
3906    /// edge-replicated reference, so both paths are BYTE-IDENTICAL; anything outside the
3907    /// padded plane still falls back to `mc_luma`.
3908    ///
3909    /// Census that motivated it: with the search's edge fallback fixed, `mc_luma` is
3910    /// 3.8-5.2% of encode and splits recon ~56-67% / skip-check ~24-35%, the latter at a
3911    /// content-independent one call per macroblock.
3912    #[inline]
3913    fn mc_luma_cached(
3914        &self,
3915        reference: &crate::RefFrame,
3916        x0: usize,
3917        y0: usize,
3918        bw: usize,
3919        bh: usize,
3920        mvx: i32,
3921        mvy: i32,
3922        out: &mut [u8],
3923    ) {
3924        let ch = self.mb_h * 16;
3925        let cw = self.cw;
3926        if !self.fast {
3927            let p = reference.hpel(cw, ch);
3928            if rusty_h264_common::inter::hpel_block(p, x0, y0, bw, bh, mvx, mvy, out) {
3929                return;
3930            }
3931            if let Some((plane, base, stride)) =
3932                rusty_h264_common::inter::hpel_ref(p, x0, y0, bw, bh, mvx, mvy)
3933            {
3934                for r in 0..bh {
3935                    out[r * bw..r * bw + bw].copy_from_slice(&plane[base + r * stride..][..bw]);
3936                }
3937                return;
3938            }
3939        }
3940        mc_luma(&reference.y, cw, ch, x0, y0, bw, bh, mvx, mvy, out);
3941    }
3942
3943    /// Motion-compensates the `P_Skip` prediction (luma + both chroma) from
3944    /// reference 0 at the skip MV.
3945    /// Luma half of the P_Skip prediction. Split out so the fast path can test the
3946    /// luma residual first and only motion-compensate chroma when luma is free —
3947    /// for the majority of (non-free) macroblocks the chroma MC is never needed.
3948    fn skip_predict_luma(
3949        &self,
3950        refs: &[crate::RefFrame],
3951        mb_x: usize,
3952        mb_y: usize,
3953        mv: (i32, i32),
3954    ) -> [u8; 256] {
3955        // Descent E/F: identify this mc_luma population by call site.
3956        #[cfg(feature = "profile")]
3957        let _site = rusty_h264_common::inter::mcstats::SiteTag::new(3);
3958        let reference = &refs[0]; // P_Skip always references index 0
3959        let ch = self.mb_h * 16;
3960        let mut pred_y = [0u8; 256];
3961        self.mc_luma_cached(reference, mb_x * 16, mb_y * 16, 16, 16, mv.0, mv.1, &mut pred_y);
3962        pred_y
3963    }
3964
3965    /// Chroma half of the P_Skip prediction (see [`Self::skip_predict_luma`]).
3966    fn skip_predict_chroma(
3967        &self,
3968        refs: &[crate::RefFrame],
3969        mb_x: usize,
3970        mb_y: usize,
3971        mv: (i32, i32),
3972    ) -> [[u8; 64]; 2] {
3973        let reference = &refs[0];
3974        let cch = self.mb_h * 8;
3975        let mut pred_c = [[0u8; 64]; 2];
3976        for c in 0..2 {
3977            let rc = if c == 0 { &reference.u } else { &reference.v };
3978            mc_chroma(rc, self.ccw, cch, mb_x * 8, mb_y * 8, 8, 8, mv.0, mv.1, &mut pred_c[c]);
3979        }
3980        pred_c
3981    }
3982
3983    /// Whether the luma half of the P_Skip prediction has an all-zero quantized
3984    /// residual. Tested first and independently so the caller can defer the chroma
3985    /// MC + test for the common case where luma already disqualifies the skip (a
3986    /// "free", exact P_Skip costs no bits and is strictly beneficial).
3987    fn skip_luma_is_free(&self, sy: &[u8], mb_x: usize, mb_y: usize, pred_y: &[u8; 256]) -> bool {
3988        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncFree);
3989        let qp = self.qp;
3990        // Fast path (deployment): the SAME asm kernels the coding path uses —
3991        // `dct_four_t4` computes the 4x4 DCTs of (src - pred) for an 8x8 quad
3992        // STRAIGHT FROM THE PLANES (no scalar gather), `quant_four_4x4` quantizes
3993        // with the identical FF/MF math as scalar `quantize` (bit-identical), and
3994        // "free" = all 64 levels zero, which is order-independent. Per-quad early
3995        // exit. The knob interleaves this against the scalar twin for A/B.
3996        #[cfg(accel)]
3997        if self.skip_accel_check {
3998            #[repr(align(16))]
3999            struct Align16([i16; 64]);
4000            let mut dct = Align16([0i16; 64]);
4001            let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
4002            let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
4003            for &(qx, qy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8)] {
4004                rusty_h264_accel::dct_four_t4(
4005                    &mut dct.0,
4006                    &sy[(mb_y * 16 + qy) * self.cw + mb_x * 16 + qx..],
4007                    self.cw,
4008                    &pred_y[qy * 16 + qx..],
4009                    16,
4010                );
4011                rusty_h264_accel::quant_four_4x4(&mut dct.0, &ff, mf);
4012                if dct.0.iter().any(|&v| v != 0) {
4013                    return false;
4014                }
4015            }
4016            return true;
4017        }
4018        // Exact quantize-to-zero bounds (mirrors `quantize`: level != 0 iff
4019        // (|c| + ff[p])·mf_oh[p] >= 2^16). With |C_ij| <= 4·SAD (max |H| entry = 2)
4020        // and C_DC = Σres, most blocks are decided by one SAD/sum pass — the full
4021        // scalar DCT+quant proof only runs for the rare undecided middle band.
4022        // BIT-EXACT: both shortcuts are sufficient conditions of the exact check.
4023        let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
4024        let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
4025        let mut t_min = i32::MAX;
4026        for p in 0..8 {
4027            let t = (65536 + mf[p] as i32 - 1) / mf[p] as i32 - ff[p] as i32;
4028            t_min = t_min.min(t);
4029        }
4030        let t_dc = (65536 + mf[0] as i32 - 1) / mf[0] as i32 - ff[0] as i32;
4031        // Whole-MB gate: SAD(any 4x4) <= SAD(MB), so 4*SAD_MB < T_min proves all 16
4032        // blocks quantize to zero from ONE (psadbw) SAD. On skip-heavy content most
4033        // free MBs are exact/near-exact copies (SAD_MB ~ 0) - they skip the whole
4034        // per-block walk. Not-free MBs pay one extra SAD (~2% of their check).
4035        for by in 0..4 {
4036            for bx in 0..4 {
4037                let mut res = [0i32; 16];
4038                let (mut sad, mut dc) = (0i32, 0i32);
4039                for dy in 0..4 {
4040                    for dx in 0..4 {
4041                        let sx = mb_x * 16 + bx * 4 + dx;
4042                        let syy = mb_y * 16 + by * 4 + dy;
4043                        let d = sy[syy * self.cw + sx] as i32
4044                            - pred_y[(by * 4 + dy) * 16 + (bx * 4 + dx)] as i32;
4045                        res[dy * 4 + dx] = d;
4046                        sad += d.abs();
4047                        dc += d;
4048                    }
4049                }
4050                if 4 * sad < t_min {
4051                    continue; // every |C| <= 4·SAD < T_min → all levels zero
4052                }
4053                if dc.abs() >= t_dc {
4054                    return false; // DC level provably nonzero
4055                }
4056                if quantize(&forward_core(&res), qp, 6).iter().any(|&v| v != 0) {
4057                    return false;
4058                }
4059            }
4060        }
4061        true
4062    }
4063
4064    /// Chroma half of [`Self::skip_is_free`].
4065    fn skip_chroma_is_free(
4066        &self,
4067        su: &[u8],
4068        sv: &[u8],
4069        mb_x: usize,
4070        mb_y: usize,
4071        pred_c: &[[u8; 64]; 2],
4072    ) -> bool {
4073        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncFree);
4074        let qpc = self.qpc;
4075        // Fast path: one dct_four_t4 covers the whole 8x8 chroma plane region (all 4
4076        // blocks, residual+DCT fused, no scalar gather). Block order is the quad's
4077        // z-scan == raster for 2x2, so block b's DC (pre-quant) sits at dct[b*16] —
4078        // exactly the dc2x2 the Hadamard check needs. quant_four_4x4 with our FF/MF
4079        // is bit-identical to scalar `quantize`; AC-free = positions 1..16 all zero.
4080        #[cfg(accel)]
4081        if self.skip_accel_check {
4082            #[repr(align(16))]
4083            struct Align16C([i16; 64]);
4084            let mut dct = Align16C([0i16; 64]);
4085            let ff = rusty_h264_common::transform::quant_dz_ff(qpc, 6);
4086            let mf = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
4087            for c in 0..2 {
4088                let src = if c == 0 { su } else { sv };
4089                rusty_h264_accel::dct_four_t4(
4090                    &mut dct.0,
4091                    &src[(mb_y * 8) * self.ccw + mb_x * 8..],
4092                    self.ccw,
4093                    &pred_c[c],
4094                    8,
4095                );
4096                let dc2x2 = [
4097                    dct.0[0] as i32,
4098                    dct.0[16] as i32,
4099                    dct.0[32] as i32,
4100                    dct.0[48] as i32,
4101                ];
4102                rusty_h264_accel::quant_four_4x4(&mut dct.0, &ff, mf);
4103                for b in 0..4 {
4104                    if dct.0[b * 16 + 1..b * 16 + 16].iter().any(|&v| v != 0) {
4105                        return false;
4106                    }
4107                }
4108                if forward_quant_chroma_dc(&dc2x2, qpc, false).iter().any(|&v| v != 0) {
4109                    return false;
4110                }
4111            }
4112            return true;
4113        }
4114        for c in 0..2 {
4115            let src = if c == 0 { su } else { sv };
4116            let mut dc2x2 = [0i32; 4];
4117            for &(bx, by) in &CHROMA_4X4_SCAN_XY {
4118                let mut res = [0i32; 16];
4119                for dy in 0..4 {
4120                    for dx in 0..4 {
4121                        let sx = mb_x * 8 + bx * 4 + dx;
4122                        let syy = mb_y * 8 + by * 4 + dy;
4123                        res[dy * 4 + dx] = src[syy * self.ccw + sx] as i32
4124                            - pred_c[c][(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
4125                    }
4126                }
4127                let coeffs = forward_core(&res);
4128                dc2x2[by * 2 + bx] = coeffs[0];
4129                if quantize(&coeffs, qpc, 6)[1..].iter().any(|&v| v != 0) {
4130                    return false;
4131                }
4132            }
4133            if forward_quant_chroma_dc(&dc2x2, qpc, false).iter().any(|&v| v != 0) {
4134                return false;
4135            }
4136        }
4137        true
4138    }
4139
4140    /// SSD between the source and a macroblock prediction (luma + chroma).
4141    #[allow(clippy::too_many_arguments)]
4142    fn pred_ssd(
4143        &self,
4144        sy: &[u8],
4145        su: &[u8],
4146        sv: &[u8],
4147        mb_x: usize,
4148        mb_y: usize,
4149        pred_y: &[u8; 256],
4150        pred_c: &[[u8; 64]; 2],
4151    ) -> i64 {
4152        let mut ssd = 0i64;
4153        for dy in 0..16 {
4154            for dx in 0..16 {
4155                let d = sy[(mb_y * 16 + dy) * self.cw + mb_x * 16 + dx] as i64
4156                    - pred_y[dy * 16 + dx] as i64;
4157                ssd += d * d;
4158            }
4159        }
4160        for c in 0..2 {
4161            let src = if c == 0 { su } else { sv };
4162            for dy in 0..8 {
4163                for dx in 0..8 {
4164                    let d = src[(mb_y * 8 + dy) * self.ccw + mb_x * 8 + dx] as i64
4165                        - pred_c[c][dy * 8 + dx] as i64;
4166                    ssd += d * d;
4167                }
4168            }
4169        }
4170        ssd
4171    }
4172
4173    /// SSD between the *reconstructed* macroblock and the source.
4174    fn mb_ssd(&self, sy: &[u8], su: &[u8], sv: &[u8], mb_x: usize, mb_y: usize) -> i64 {
4175        let mut ssd = 0i64;
4176        for dy in 0..16 {
4177            for dx in 0..16 {
4178                let i = (mb_y * 16 + dy) * self.cw + mb_x * 16 + dx;
4179                let d = sy[i] as i64 - self.rec_y[i] as i64;
4180                ssd += d * d;
4181            }
4182        }
4183        for c in 0..2 {
4184            let (src, rec) = if c == 0 { (su, &self.rec_u) } else { (sv, &self.rec_v) };
4185            for dy in 0..8 {
4186                for dx in 0..8 {
4187                    let i = (mb_y * 8 + dy) * self.ccw + mb_x * 8 + dx;
4188                    let d = src[i] as i64 - rec[i] as i64;
4189                    ssd += d * d;
4190                }
4191            }
4192        }
4193        ssd
4194    }
4195
4196    /// Reconstructs a `P_Skip` macroblock (reconstruction *is* the prediction —
4197    /// no residual coded) and records its motion state.
4198    #[allow(clippy::too_many_arguments)]
4199    fn commit_skip_probe_marker(&self) {}
4200    fn commit_skip(
4201        &mut self,
4202        mb_x: usize,
4203        mb_y: usize,
4204        mv: (i32, i32),
4205        pred_y: &[u8; 256],
4206        pred_c: &[[u8; 64]; 2],
4207    ) {
4208        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MvGrid);
4209        // Skip recon = the prediction verbatim: straight row copies (byte-identical
4210        // to the old per-4x4 gather + store scatter, ~5x fewer ops).
4211        let base = mb_y * 16 * self.cw + mb_x * 16;
4212        for r in 0..16 {
4213            let d = base + r * self.cw;
4214            self.rec_y[d..d + 16].copy_from_slice(&pred_y[r * 16..r * 16 + 16]);
4215        }
4216        let cbase = mb_y * 8 * self.ccw + mb_x * 8;
4217        for c in 0..2 {
4218            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
4219            for r in 0..8 {
4220                let d = cbase + r * self.ccw;
4221                plane[d..d + 8].copy_from_slice(&pred_c[c][r * 8..r * 8 + 8]);
4222            }
4223        }
4224        self.set_mb_mv(mb_x, mb_y, mv, true, 0);
4225        let w4 = self.mb_w * 4;
4226        for row in 0..4 {
4227            let st = (mb_y * 4 + row) * w4 + mb_x * 4;
4228            self.modes_y[st..st + 4].fill(2);
4229            self.coded_y[st..st + 4].fill(true);
4230        }
4231    }
4232
4233    /// Trial-encodes an inter macroblock to measure its rate-distortion cost
4234    /// `(SSD, bits)` without committing: snapshot the macroblock's grid + recon
4235    /// region, run the real `encode_inter_mb` into a scratch writer, read the
4236    /// bit count and reconstruction SSD, then restore. Neighbor CAVLC context is
4237    /// read (not mutated), so the bit count is accurate.
4238    #[allow(clippy::too_many_arguments)]
4239    fn trial_inter(
4240        &mut self,
4241        refs: &[crate::RefFrame],
4242        sy: &[u8],
4243        su: &[u8],
4244        sv: &[u8],
4245        mb_x: usize,
4246        mb_y: usize,
4247        mode: u8,
4248        parts: &[(i32, (i32, i32))],
4249    ) -> (i64, usize) {
4250        let snap = self.save_mb(mb_x, mb_y);
4251        let mut scratch = BitWriter::new();
4252        self.encode_inter_mb(&mut scratch, refs, sy, su, sv, mb_x, mb_y, mode, parts);
4253        let bits = scratch.bit_len();
4254        let ssd = self.mb_ssd(sy, su, sv, mb_x, mb_y);
4255        self.load_mb(mb_x, mb_y, &snap);
4256        (ssd, bits)
4257    }
4258
4259    /// Trial-encodes the macroblock as **intra** (`encode_mb` runs its own
4260    /// I_16x16-vs-I_4x4 decision), measuring `(SSD, bits)` without committing —
4261    /// the intra candidate for the RD mode decision.
4262    fn trial_intra(
4263        &mut self,
4264        sy: &[u8],
4265        su: &[u8],
4266        sv: &[u8],
4267        mb_x: usize,
4268        mb_y: usize,
4269        is_p: bool,
4270    ) -> (i64, usize) {
4271        let snap = self.save_mb(mb_x, mb_y);
4272        let mut scratch = BitWriter::new();
4273        encode_mb(self, &mut scratch, mb_x, mb_y, sy, su, sv, is_p);
4274        let bits = scratch.bit_len();
4275        let ssd = self.mb_ssd(sy, su, sv, mb_x, mb_y);
4276        self.load_mb(mb_x, mb_y, &snap);
4277        (ssd, bits)
4278    }
4279
4280    /// Best `(ref_idx, mv, cost)` for one partition by `SATD + λ·bits`, searched
4281    /// across every reference (`cost` is that SATD-domain rate-distortion cost).
4282    /// `extra` seeds the search with already-found MVs (e.g. the 16×16 result when
4283    /// refining a sub-partition).
4284    #[allow(clippy::too_many_arguments)]
4285    fn best_part(
4286        &self,
4287        refs: &[crate::RefFrame],
4288        sy: &[u8],
4289        nb: &[MvNeighbor; 3],
4290        num_refs: usize,
4291        rx: usize,
4292        ry: usize,
4293        rw: usize,
4294        rh: usize,
4295        extra: &[(i32, i32)],
4296        lme: f64,
4297    ) -> (i32, (i32, i32), i64) {
4298        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMe);
4299        let [a, b, c] = *nb;
4300        let (mut br, mut bmv, mut bc) = (0i32, (0, 0), i64::MAX);
4301        for r in 0..num_refs {
4302            let mut seeds = vec![predict_mv(a, b, c, r as i32)];
4303            seeds.extend_from_slice(extra);
4304            let (mv, cost) = self.motion_search(&refs[r], sy, rx, ry, rw, rh, &seeds, lme, None);
4305            let cost = cost + (lme * ref_bits(r, num_refs) as f64) as i64;
4306            if cost < bc {
4307                bc = cost;
4308                br = r as i32;
4309                bmv = mv;
4310            }
4311        }
4312        (br, bmv, bc)
4313    }
4314
4315    /// Sub-pel-refines ONE already-chosen partition, reusing `motion_search`'s cost
4316    /// closure via its `start` hook so the rate term and predictor centre are exactly
4317    /// the ones the full search used. Companion to `best_part` under `sp_defer`.
4318    #[allow(clippy::too_many_arguments)]
4319    fn refine_part(
4320        &self,
4321        refs: &[crate::RefFrame],
4322        sy: &[u8],
4323        nb: &[MvNeighbor; 3],
4324        num_refs: usize,
4325        rx: usize,
4326        ry: usize,
4327        rw: usize,
4328        rh: usize,
4329        lme: f64,
4330        r: i32,
4331        mv: (i32, i32),
4332    ) -> ((i32, i32), i64) {
4333        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMe);
4334        let [a, b, c] = *nb;
4335        let rb = (lme * ref_bits(r as usize, num_refs) as f64) as i64;
4336        let seeds = [predict_mv(a, b, c, r)];
4337        let (m, cc) = self.motion_search(&refs[r as usize], sy, rx, ry, rw, rh, &seeds, lme, Some(mv));
4338        (m, cc + rb)
4339    }
4340
4341    /// Cheapest `I_16x16` prediction's SAD over the four whole-block modes, using
4342    /// the already-reconstructed top/left neighbours — the intra candidate's cost
4343    /// in the fast (SAD) mode decision, without the full `I_4x4` search.
4344    fn best_i16_sad(&self, sy: &[u8], mb_x: usize, mb_y: usize) -> i64 {
4345        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncIntraCost);
4346        let (lx, ly) = (mb_x * 16, mb_y * 16);
4347        let (avail_top, avail_left) = (mb_y > 0, mb_x > 0);
4348        let mut top = [0u8; 16];
4349        let mut left = [0u8; 16];
4350        if avail_top {
4351            for i in 0..16 {
4352                top[i] = self.rec_y[(ly - 1) * self.cw + lx + i];
4353            }
4354        }
4355        if avail_left {
4356            for i in 0..16 {
4357                left[i] = self.rec_y[(ly + i) * self.cw + lx - 1];
4358            }
4359        }
4360        let corner = if avail_top && avail_left {
4361            self.rec_y[(ly - 1) * self.cw + lx - 1]
4362        } else {
4363            0
4364        };
4365        let mut best = i64::MAX;
4366        for mode in [I16Mode::Dc, I16Mode::Vertical, I16Mode::Horizontal, I16Mode::Plane] {
4367            if !mode.available(avail_top, avail_left) {
4368                continue;
4369            }
4370            let pred = i16_pred(self, mode, avail_top, avail_left, &top, &left, corner, lx, ly);
4371            best = best.min(sad_16x16(sy, self.cw, lx, ly, &pred));
4372        }
4373        best
4374    }
4375
4376    /// SATD sibling of [`Self::best_i16_sad`] — the intra candidate's cost in the
4377    /// quality preset's SATD mode decision (openh264's `WelsMdI16x16`).
4378    fn best_i16_satd(&self, sy: &[u8], mb_x: usize, mb_y: usize) -> i64 {
4379        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncIntraCost);
4380        let (lx, ly) = (mb_x * 16, mb_y * 16);
4381        let (avail_top, avail_left) = (mb_y > 0, mb_x > 0);
4382        let mut top = [0u8; 16];
4383        let mut left = [0u8; 16];
4384        if avail_top {
4385            for i in 0..16 {
4386                top[i] = self.rec_y[(ly - 1) * self.cw + lx + i];
4387            }
4388        }
4389        if avail_left {
4390            for i in 0..16 {
4391                left[i] = self.rec_y[(ly + i) * self.cw + lx - 1];
4392            }
4393        }
4394        let corner = if avail_top && avail_left {
4395            self.rec_y[(ly - 1) * self.cw + lx - 1]
4396        } else {
4397            0
4398        };
4399        let mut best = i64::MAX;
4400        for mode in [I16Mode::Dc, I16Mode::Vertical, I16Mode::Horizontal, I16Mode::Plane] {
4401            if !mode.available(avail_top, avail_left) {
4402                continue;
4403            }
4404            let pred = i16_pred(self, mode, avail_top, avail_left, &top, &left, corner, lx, ly);
4405            best = best.min(satd_16x16(sy, self.cw, lx, ly, &pred));
4406        }
4407        best
4408    }
4409
4410    /// Snapshots the per-block grids and reconstruction for one macroblock, so a
4411    /// trial encode can be rolled back.
4412    fn save_mb(&self, mb_x: usize, mb_y: usize) -> MbState {
4413        let mut d = MbState::default();
4414        self.save_mb_into(mb_x, mb_y, &mut d);
4415        d
4416    }
4417
4418    /// [`save_mb`](Self::save_mb) into an existing buffer, reusing its allocations.
4419    /// The per-macroblock region is a fixed size, so after the first call every
4420    /// `Vec` already has the capacity it needs and refilling is a pure copy.
4421    fn save_mb_into(&self, mb_x: usize, mb_y: usize, d: &mut MbState) {
4422        let w4 = self.mb_w * 4;
4423        let w2 = self.mb_w * 2;
4424        macro_rules! reg4 {
4425            ($v:expr, $o:expr) => {{
4426                $o.clear();
4427                for dy in 0..4 {
4428                    for dx in 0..4 {
4429                        $o.push($v[(mb_y * 4 + dy) * w4 + mb_x * 4 + dx]);
4430                    }
4431                }
4432            }};
4433        }
4434        macro_rules! regn {
4435            ($v:expr, $o:expr, $n:expr, $ox:expr, $oy:expr, $stride:expr) => {{
4436                $o.clear();
4437                for dy in 0..$n {
4438                    for dx in 0..$n {
4439                        $o.push($v[($oy + dy) * $stride + $ox + dx]);
4440                    }
4441                }
4442            }};
4443        }
4444        regn!(self.rec_y, d.rec_y, 16, mb_x * 16, mb_y * 16, self.cw);
4445        regn!(self.rec_u, d.rec_u, 8, mb_x * 8, mb_y * 8, self.ccw);
4446        regn!(self.rec_v, d.rec_v, 8, mb_x * 8, mb_y * 8, self.ccw);
4447        reg4!(self.nnz_y, d.nnz_y);
4448        regn!(self.nnz_c[0], d.nnz_c[0], 2, mb_x * 2, mb_y * 2, w2);
4449        regn!(self.nnz_c[1], d.nnz_c[1], 2, mb_x * 2, mb_y * 2, w2);
4450        reg4!(self.mv_y, d.mv_y);
4451        reg4!(self.inter_y, d.inter_y);
4452        reg4!(self.ref_idx_y, d.ref_idx_y);
4453        reg4!(self.coded_y, d.coded_y);
4454        reg4!(self.modes_y, d.modes_y);
4455        d.cur_qp = self.cur_qp;
4456    }
4457
4458    /// Restores a macroblock's grids + reconstruction from a [`save_mb`] snapshot.
4459    fn load_mb(&mut self, mb_x: usize, mb_y: usize, s: &MbState) {
4460        let w4 = self.mb_w * 4;
4461        let w2 = self.mb_w * 2;
4462        macro_rules! put4 {
4463            ($v:expr, $src:expr) => {
4464                for dy in 0..4 {
4465                    for dx in 0..4 {
4466                        $v[(mb_y * 4 + dy) * w4 + mb_x * 4 + dx] = $src[dy * 4 + dx];
4467                    }
4468                }
4469            };
4470        }
4471        macro_rules! putn {
4472            ($v:expr, $src:expr, $n:expr, $ox:expr, $oy:expr, $stride:expr) => {
4473                for dy in 0..$n {
4474                    for dx in 0..$n {
4475                        $v[($oy + dy) * $stride + $ox + dx] = $src[dy * $n + dx];
4476                    }
4477                }
4478            };
4479        }
4480        putn!(self.rec_y, s.rec_y, 16, mb_x * 16, mb_y * 16, self.cw);
4481        putn!(self.rec_u, s.rec_u, 8, mb_x * 8, mb_y * 8, self.ccw);
4482        putn!(self.rec_v, s.rec_v, 8, mb_x * 8, mb_y * 8, self.ccw);
4483        put4!(self.nnz_y, s.nnz_y);
4484        putn!(self.nnz_c[0], s.nnz_c[0], 2, mb_x * 2, mb_y * 2, w2);
4485        putn!(self.nnz_c[1], s.nnz_c[1], 2, mb_x * 2, mb_y * 2, w2);
4486        put4!(self.mv_y, s.mv_y);
4487        put4!(self.inter_y, s.inter_y);
4488        put4!(self.ref_idx_y, s.ref_idx_y);
4489        put4!(self.coded_y, s.coded_y);
4490        put4!(self.modes_y, s.modes_y);
4491        self.cur_qp = s.cur_qp;
4492    }
4493
4494    /// Loads the per-MB luma nnz prediction cache (openh264 `scan8` style): the top
4495    /// row from the macroblock above and the left column from the macroblock to the
4496    /// left (both already in `nnz_y`), with `0x80` at the picture edges. After this,
4497    /// neighbour nnz reads are branchless cache indexing — no bounds-checked `Option`.
4498    fn nnz_cache_load(&mut self, mb_x: usize, mb_y: usize) {
4499        let w4 = self.mb_w * 4;
4500        for lbx in 0..4 {
4501            self.nnz_l_cache[1 + lbx] = if mb_y == 0 {
4502                0x80
4503            } else {
4504                self.nnz_y[(mb_y * 4 - 1) * w4 + (mb_x * 4 + lbx)]
4505            };
4506        }
4507        for lby in 0..4 {
4508            self.nnz_l_cache[(lby + 1) * 5] = if mb_x == 0 {
4509                0x80
4510            } else {
4511                self.nnz_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 - 1)]
4512            };
4513        }
4514    }
4515
4516    /// Branchless nnz prediction (`nC`) for luma block `(lbx,lby)` from the cache —
4517    /// the `0x80` sentinel + `& 0x7f` mask collapse the four availability cases
4518    /// (matches the scalar nnz predict). Call after the block's left/top are cached.
4519    #[inline]
4520    fn nc_pred(&self, lbx: usize, lby: usize) -> i32 {
4521        let left = self.nnz_l_cache[(lby + 1) * 5 + lbx] as i32; // (lbx-1)+1
4522        let top = self.nnz_l_cache[lby * 5 + (lbx + 1)] as i32; // (lby-1)+1
4523        let r = left + top;
4524        if r < 0x80 {
4525            (r + 1) >> 1
4526        } else {
4527            r & 0x7f
4528        }
4529    }
4530
4531    /// Records a luma block's nnz into the per-MB cache (for later neighbour reads).
4532    #[inline]
4533    fn nnz_cache_set(&mut self, lbx: usize, lby: usize, total: u8) {
4534        self.nnz_l_cache[(lby + 1) * 5 + (lbx + 1)] = total;
4535    }
4536
4537    /// Loads the per-MB chroma nnz prediction cache (both planes) from the chroma
4538    /// blocks above/left, `0x80` at the picture edges — the chroma analogue of
4539    /// [`Self::nnz_cache_load`] (2×2 blocks → padded 3×3 grid).
4540    fn chroma_cache_load(&mut self, mb_x: usize, mb_y: usize) {
4541        let w2 = self.mb_w * 2;
4542        for c in 0..2 {
4543            for bx in 0..2 {
4544                self.nnz_c_cache[c][1 + bx] = if mb_y == 0 {
4545                    0x80
4546                } else {
4547                    self.nnz_c[c][(mb_y * 2 - 1) * w2 + (mb_x * 2 + bx)]
4548                };
4549            }
4550            for by in 0..2 {
4551                self.nnz_c_cache[c][(by + 1) * 3] = if mb_x == 0 {
4552                    0x80
4553                } else {
4554                    self.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 - 1)]
4555                };
4556            }
4557        }
4558    }
4559
4560    /// Branchless chroma nnz prediction (`nC`) for plane `c`, block `(bx,by)`.
4561    #[inline]
4562    fn chroma_nc_pred(&self, c: usize, bx: usize, by: usize) -> i32 {
4563        let left = self.nnz_c_cache[c][(by + 1) * 3 + bx] as i32;
4564        let top = self.nnz_c_cache[c][by * 3 + (bx + 1)] as i32;
4565        let r = left + top;
4566        if r < 0x80 {
4567            (r + 1) >> 1
4568        } else {
4569            r & 0x7f
4570        }
4571    }
4572
4573    /// Records a chroma block's nnz into the per-MB cache.
4574    #[inline]
4575    fn chroma_nnz_cache_set(&mut self, c: usize, bx: usize, by: usize, total: u8) {
4576        self.nnz_c_cache[c][(by + 1) * 3 + (bx + 1)] = total;
4577    }
4578}
4579
4580/// Encodes a slice's macroblocks then RBSP trailing bits, returning the
4581/// **deblocked** reconstruction to serve as the next frame's reference.
4582///
4583/// `is_p` selects P-slice framing (`mb_skip_run` prefix + intra `mb_type` +5
4584/// offset). In phase 4a every macroblock is still coded intra; motion-compensated
4585/// macroblocks arrive in 4b (using `reference`).
4586/// Boundary strengths for one macroblock, derived from the encoder's own grids
4587/// the moment it finishes coding.
4588///
4589/// `ref_idx_y` holds raw indices (-1 for intra) rather than the deblocker's
4590/// `NO_REF` sentinel; safe because reference identity is only compared between
4591/// two INTER blocks, which always carry a valid index.
4592// NOT inlined: this sits at three exits of the hottest loop in the encoder, and
4593// inlining it there costs more in I-cache and register pressure on the
4594// surrounding code than the call saves (measured: the loop grew ~2x the
4595// derivation's own cost).
4596#[inline(never)]
4597fn derive_mb_bs_from(
4598    fe: &FrameEncoder,
4599    mb_x: usize,
4600    mb_y: usize,
4601    kind: rusty_h264_common::deblock::MbKind,
4602) -> rusty_h264_common::deblock::MbBs {
4603    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncBs);
4604    let view = rusty_h264_common::deblock::BlockInfo {
4605        inter: &fe.inter_y,
4606        nnz: &fe.nnz_y,
4607        mv: &fe.mv_y,
4608        ref_id: &fe.ref_idx_y,
4609        mv1: &[],
4610        ref_id1: &[],
4611        w4: fe.mb_w * 4,
4612        t8x8: &[],
4613        bs: &[],
4614    };
4615    rusty_h264_common::deblock::derive_mb_kind(&view, mb_x, mb_y, kind)
4616}
4617
4618pub fn encode_slice_data(
4619    w: &mut BitWriter,
4620    cfg: &EncoderConfig,
4621    frame: &YuvFrame,
4622    qp: u8,
4623    is_p: bool,
4624    refs: &[crate::RefFrame],
4625    qpo: &[i32],
4626) -> crate::RefFrame {
4627    let _g_prep = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncPrep);
4628    let mut fe = FrameEncoder::new(cfg);
4629    let precomp = rusty_h264_common::deblock::precomputed_bs_enabled();
4630    let mut bs_grid =
4631        vec![rusty_h264_common::deblock::MbBs::UNSET; if precomp { fe.mb_w * fe.mb_h } else { 0 }];
4632    fe.qp = qp;
4633    fe.qpc = chroma_qp(qp);
4634    fe.cur_qp = qp;
4635    if cfg.cabac_dz_div > 0 {
4636        fe.idz = cfg.cabac_dz_div; // CABAC-specific dead-zone override
4637    } // QPY_PREV starts at the slice QP so the first mb_qp_delta is 0
4638    let (sy, su, sv) = coded_source(cfg, frame);
4639    let lambda = 0.85 * fe.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
4640    let num_refs = refs.len();
4641    // me_wide CONTENT GATE: on a pure PAN the global-MC residual ≈ 0, so the diamond's
4642    // seed (median = pan MV) is already right and the wide rescue only over-fits
4643    // (spurious MVs that hurt the B-frames' spatial-direct — the panc regression).
4644    // Gate it off there; non-uniform content (real stalls) reads well above 0.
4645    if is_p && fe.me_wide && !refs.is_empty()
4646        && global_mc_residual(&sy, fe.cw, fe.mb_h * 16, &refs[0].y) < fe.me_wide_coh
4647    {
4648        fe.me_wide = false;
4649    }
4650    // me_wide HEAD-ROOM GATE (the dispatcher the truth table asked for). The rescue
4651    // only pays where a wide search actually beats a predictor-local one; measure
4652    // that directly per frame and route the frame. `RFF_ME_HR` sets the threshold
4653    // (percent); 0 disables the gate and restores the always-on behaviour.
4654    // Skip the probe entirely when the gate is disabled: it must not tax the
4655    // default path (`RFF_ME_HR=0`), which stays byte-identical to pre-gate output.
4656    if fe.me_wide && !refs.is_empty() && (me_wide_hr_thresh() > 0.0 || me_wide_hr_dbg()) {
4657        let hr = me_wide_headroom(&sy, fe.cw, fe.mb_h * 16, &refs[0].y);
4658        if me_wide_hr_dbg() {
4659            eprintln!("ME_HR qp{qp} headroom={hr:.2}");
4660        }
4661        if me_wide_hr_thresh() > 0.0 && hr < me_wide_hr_thresh() {
4662            fe.me_wide = false;
4663        }
4664    }
4665    // Track-B B2 DISPATCH (WHYS H-2): SAD full-pel wins where a plain full-pel
4666    // translational search actually improves on zero motion (`b2_mgain`) and loses
4667    // on flash/fine-detail content. Probe per frame, route the frame — per-frame,
4668    // not cross-frame, so it stays deterministic under GOP-parallel encode.
4669    if me_sadfp_mode() == 1 && !fe.fast && !refs.is_empty() {
4670        let (mg, dc) = b2_mgain(&sy, fe.cw, fe.mb_h * 16, &refs[0].y);
4671        if me_sadt_dbg() {
4672            eprintln!("B2_MG qp{qp} mgain={mg:.3} dcfrac={dc:.3}");
4673        }
4674        fe.sadfp = mg >= me_sadt() && dc <= me_sad_dcmax();
4675        // H-24: the mv-cost SHAPE rides the same probe (its BD sign-flip tracks
4676        // motion for the same physical reason B2's does).
4677        if mv_smooth_mode() == 1 {
4678            // dcfrac veto mirrors B2's: crew-class FLASH frames satisfy the mgain
4679            // test but SAD/mvd statistics mislead there (H-13/H-26).
4680            fe.mv_smooth = mg >= mv_smooth_t() && dc <= me_sad_dcmax();
4681        }
4682        // H-13: near-static frames skip the split searches entirely.
4683        let smg = split_mg();
4684        if smg > 0.0 {
4685            fe.do_splits = mg >= smg;
4686        }
4687    }
4688    // Content-adaptive cost-function dispatch (codec-content-adaptive-dispatch): the
4689    // fast preset prices modes by cheap SAD, which is rate-blind on detailed MBs;
4690    // route the top `satd_q` fraction of highest-VARIANCE MBs to the rate-faithful
4691    // SATD cost. A per-frame PERCENTILE threshold makes the routed fraction — hence
4692    // the speed/quality split — content-invariant (same q → same fraction on any
4693    // clip). `satd_q == 0` leaves the threshold at MAX (pure SAD, byte-identical).
4694    if is_p && fe.satd_q > 0.0 {
4695        let mut vars: Vec<i64> = (0..fe.mb_h)
4696            .flat_map(|my| (0..fe.mb_w).map(move |mx| (mx, my)))
4697            .map(|(mx, my)| mb_variance(&sy, fe.cw, mx, my))
4698            .collect();
4699        vars.sort_unstable();
4700        let idx = (((1.0 - fe.satd_q) * vars.len() as f64) as usize).min(vars.len() - 1);
4701        fe.satd_var_thresh = vars[idx];
4702    }
4703    // Adaptive Quantization: per-MB target QPy from content (finer on flat MBs,
4704    // coarser on busy ones). `mb_qpy` records each MB's ACTUAL QPy (a skip / cbp==0
4705    // MB inherits `cur_qp`), for the deblock filter. `strength 0` → uniform → the
4706    // mb_qp_delta stays 0, byte-identical.
4707    let mut aq_qp = aq_qp_map(&sy, fe.cw, fe.mb_w, fe.mb_h, qp, fe.aq_strength);
4708    apply_mbtree_qpo(&mut aq_qp, qpo); // mb-tree temporal AQ (empty = byte-identical)
4709    fe.cur_qp = qp;
4710    let mut mb_qpy = vec![qp; fe.mb_w * fe.mb_h];
4711    let mut skip_run = 0u32;
4712    // ---- adaptive RD-skip gate -------------------------------------------
4713    // RD P_Skip is a large win on temporally redundant content and a large LOSS
4714    // on detailed content (SSIM: akiyo -13.1%, FourPeople -5.6% vs in_to_tree
4715    // +34.0%, stockholm +95.7%). The separating signal is the content's own
4716    // FREE-skip rate — how much of it is already exactly redundant — and the gap
4717    // is wide (winners >=58.7%, losers <=6.4%). Measure it ONLINE over the first
4718    // slice of the frame and enable RD skip for the remainder only if it clears
4719    // the bar. Within-frame, so it stays deterministic under GOP-parallel encode.
4720    if is_p && mv_cmp_on() {
4721        MVCMP_FRAME.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
4722    }
4723    // Reused across every RD-skip candidate — see `MbState`.
4724    let mut rdskip_snap = MbState::default();
4725    let mut rdskip_free = 0usize;
4726    let mut rdskip_seen = 0usize;
4727    let mut rdskip_on = false;
4728    let mut greedy_on = fe.greedy_min_free == 0; // 0 = ungated (historic behaviour)
4729    let rdskip_learn = (fe.mb_w * fe.mb_h / 8).max(64);
4730    let rdskip_min_free = fe.rd_skip_min_free as usize;
4731
4732    drop(_g_prep);
4733    let _g_loop = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMbLoop);
4734    for mb_y in 0..fe.mb_h {
4735        for mb_x in 0..fe.mb_w {
4736            let mb_idx = mb_y * fe.mb_w + mb_x;
4737            fe.qp = aq_qp[mb_idx];
4738            fe.qpc = chroma_qp(aq_qp[mb_idx]);
4739            // P_Skip: motion-compensate from the most-recent reference; accept if free.
4740            // Chosen inter coding: (mb_type, per-partition (ref_idx, mv)).
4741            let mut inter: Option<InterChoice> = None;
4742            // Bits of an inter macroblock already encoded by the skip decision
4743            // below. When present the emit path splices them instead of encoding
4744            // the same macroblock a second time.
4745            let mut coded: Option<BitWriter> = None;
4746            if is_p {
4747                if num_refs > 0 {
4748                    // P_Skip prediction (reference 0). A free skip (zero residual) is
4749                    // taken immediately; the quality preset also takes a greedy P_Skip
4750                    // when its SAD is below the neighbour-predicted bound (below).
4751                    let _g_skip = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncSkip);
4752                    let _g_smc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Neighbors);
4753                    rdskip_seen += 1;
4754                    if rdskip_seen >= rdskip_learn {
4755                        rdskip_on = rdskip_free * 100 >= rdskip_seen * rdskip_min_free;
4756                        greedy_on = fe.greedy_min_free == 0
4757                            || rdskip_free * 100 >= rdskip_seen * fe.greedy_min_free as usize;
4758                    }
4759                    let mv_skip = fe.skip_mv(mb_x, mb_y);
4760                    let skip_y = fe.skip_predict_luma(refs, mb_x, mb_y, mv_skip);
4761                    drop(_g_smc);
4762                    let luma_free = fe.skip_luma_is_free(&sy, mb_x, mb_y, &skip_y);
4763                    // Chroma MC only when it can matter: luma already free (so the
4764                    // skip might be taken) or the quality path needs it below.
4765                    let skip_c = if luma_free || !fe.fast {
4766                        fe.skip_predict_chroma(refs, mb_x, mb_y, mv_skip)
4767                    } else {
4768                        [[0u8; 64]; 2]
4769                    };
4770                    let is_free =
4771                        luma_free && fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, &skip_c);
4772                    // Skip-prediction luma SAD (the quality preset's predicted-SAD apparatus).
4773                    let skip_sad = if fe.fast {
4774                        0
4775                    } else {
4776                        let (lx, ly) = (mb_x * 16, mb_y * 16);
4777                        let mut s = 0u32;
4778                        for dy in 0..16 {
4779                            let src = &sy[(ly + dy) * fe.cw + lx..][..16];
4780                            let p = &skip_y[dy * 16..][..16];
4781                            s += src.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
4782                        }
4783                        s
4784                    };
4785                    if is_free {
4786                        fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_c);
4787                        if !fe.fast {
4788                            fe.mb_was_skip[mb_idx] = true;
4789                            fe.mb_skip_sad[mb_idx] = skip_sad;
4790                        }
4791                        mb_qpy[mb_idx] = fe.cur_qp; // skip inherits QPy
4792                        rdskip_free += 1;
4793                        if precomp {
4794                            bs_grid[mb_idx] = derive_mb_bs_from(&fe, mb_x, mb_y, rusty_h264_common::deblock::MbKind::Skip);
4795                        }
4796                        skip_run += 1;
4797                        continue;
4798                    }
4799                    drop(_g_skip);
4800                    let (lx, ly) = (mb_x * 16, mb_y * 16);
4801                    let nb = {
4802                        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMvPred);
4803                        fe.mv_neighbors_block(mb_x as isize * 4, mb_y as isize * 4, 4)
4804                    };
4805                    let lme = lambda.sqrt();
4806
4807                    if fe.fast {
4808                        // Fast preset: pick the cheapest *prediction* by SATD (no
4809                        // trial-encoding), then always code its residual — P_16x16 vs
4810                        // I_16x16 only, no sub-partitions. Crucially it does NOT make a
4811                        // SATD skip-vs-code decision: P_Skip is taken only for a truly
4812                        // free (zero-residual) macroblock, handled above. Pricing skip
4813                        // by SATD would drop residual the QP wants coded and tank PSNR;
4814                        // like x264's fast presets, fast trades *efficiency* (more bits)
4815                        // for speed, not quality. The faster ME is what makes it fast.
4816                        // Adaptive dispatch: high-variance MBs price by SATD (both
4817                        // inter — via `mb_use_satd` in `best_part` — and intra), the
4818                        // rest by cheap SAD. Set the per-MB flag before best_part.
4819                        fe.mb_use_satd = fe.satd_q > 0.0
4820                            && mb_variance(&sy, fe.cw, mb_x, mb_y) >= fe.satd_var_thresh;
4821                        let (r16, mv16, cost_inter) =
4822                            fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
4823                        let cost_intra = if fe.mb_use_satd {
4824                            fe.best_i16_satd(&sy, mb_x, mb_y)
4825                        } else {
4826                            fe.best_i16_sad(&sy, mb_x, mb_y)
4827                        } + (lme * fe.tune_intra_penalty) as i64;
4828                        inter = if cost_intra < cost_inter {
4829                            None // intra wins → encode_mb below
4830                        } else {
4831                            Some((0, vec![(r16, mv16)]))
4832                        };
4833                    } else {
4834                        // Quality preset: openh264's mode-decision model — SATD + λ·mvbits
4835                        // cost ESTIMATE (no per-candidate trial-encode); modes are ranked
4836                        // by that cost and only the winner is encoded (once) below. This
4837                        // removes ~the 93%-of-quality re-encode cost.
4838
4839                        // Greedy P_Skip (openh264 `PredictSadSkip`): take the skip when its
4840                        // luma SAD is below the neighbour-predicted skip SAD. The threshold
4841                        // is what skip neighbours achieved, so the skip propagates from the
4842                        // free skips and self-limits — no fixed bound, no inter-chain drift.
4843                        if fe.greedy_skip && greedy_on && skip_sad < fe.pred_skip_sad(mb_x, mb_y) {
4844                            fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_c);
4845                            fe.mb_was_skip[mb_idx] = true;
4846                            fe.mb_skip_sad[mb_idx] = skip_sad;
4847                            mb_qpy[mb_idx] = fe.cur_qp; // skip inherits QPy
4848                            if precomp {
4849                                bs_grid[mb_idx] = derive_mb_bs_from(&fe, mb_x, mb_y, rusty_h264_common::deblock::MbKind::Skip);
4850                            }
4851                            skip_run += 1;
4852                            continue;
4853                        }
4854
4855                        // 16×16 baseline (SATD + λ·bits, with sub-pel refinement).
4856                        let (r16, mv16, c16) =
4857                            fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
4858                        let mut best_c = c16;
4859                        let mut pick: Option<InterChoice> = Some((0, vec![(r16, mv16)]));
4860
4861                        // Sub-partitions, ranked by SATD, gated on a heavy 16×16 (a likely
4862                        // motion boundary — the 4 sub-pel searches are the expensive part).
4863                        const QSTEP16: [i64; 6] = [10, 11, 13, 14, 16, 18];
4864                        let qstep16 = QSTEP16[(fe.qp % 6) as usize] << (fe.qp / 6);
4865                        let split_gate = ((30 * (qstep16 + 160)) >> 3) * 2;
4866                        let split_t = split_t();
4867                        if fe.do_splits && c16 > split_gate && (split_t <= 0.0 || (c16 as f64) >= split_t * lme) {
4868                            let (rt, mvt, ct) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 8, &[mv16], lme);
4869                            let (rb, mvb, cb) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly + 8, 16, 8, &[mv16], lme);
4870                            let (rl, mvl, cl) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 8, 16, &[mv16], lme);
4871                            let (rr, mvr, cr) = fe.best_part(refs, &sy, &nb, num_refs, lx + 8, ly, 8, 16, &[mv16], lme);
4872                            if ct + cb < best_c {
4873                                best_c = ct + cb;
4874                                pick = Some((1u8, vec![(rt, mvt), (rb, mvb)]));
4875                            }
4876                            if cl + cr < best_c {
4877                                best_c = cl + cr;
4878                                pick = Some((2u8, vec![(rl, mvl), (rr, mvr)]));
4879                            }
4880
4881                            // P_8x8: four independent 8×8 sub-partitions (finer motion
4882                            // granularity — the win on complex/boundary motion). Each 8×8
4883                            // seeded by the 16×16 MV; the exact chained MVD is computed in
4884                            // plan_inter_mb. Same heavy-16×16 gate as the 2-way splits.
4885                            if fe.sub8x8 {
4886                                let mut c8 = (lme * 4.0) as i64; // ~4 sub_mb_type bits
4887                                let mut p8 = Vec::with_capacity(4);
4888                                for &(qx, qy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8)] {
4889                                    let (r, mv, c) = fe.best_part(
4890                                        refs, &sy, &nb, num_refs, lx + qx, ly + qy, 8, 8, &[mv16], lme,
4891                                    );
4892                                    c8 += c;
4893                                    p8.push((r, mv));
4894                                }
4895                                if c8 < best_c {
4896                                    best_c = c8;
4897                                    pick = Some((3u8, p8));
4898                                }
4899                            }
4900                        }
4901
4902                        // U5-struct: everything above searched FULL-PEL only when
4903                        // `sp_defer` is set. Now that a shape has won, refine just its
4904                        // sub-blocks — the losing shapes' refinements were the waste
4905                        // (measured 3.4–6.4× more refinement than necessary).
4906                        if fe.sp_defer.get() {
4907                            if let Some((mode, parts)) = pick.as_mut() {
4908                                let regions: &[(usize, usize, usize, usize)] = match mode {
4909                                    1 => &[(0, 0, 16, 8), (0, 8, 16, 8)],
4910                                    2 => &[(0, 0, 8, 16), (8, 0, 8, 16)],
4911                                    3 => &[(0, 0, 8, 8), (8, 0, 8, 8), (0, 8, 8, 8), (8, 8, 8, 8)],
4912                                    _ => &[(0, 0, 16, 16)],
4913                                };
4914                                let mut tot = if *mode == 3 { (lme * 4.0) as i64 } else { 0 };
4915                                for (i, &(qx, qy, pw, ph)) in regions.iter().enumerate() {
4916                                    let (r, mv) = parts[i];
4917                                    let (m2, c2) = fe.refine_part(
4918                                        refs, &sy, &nb, num_refs, lx + qx, ly + qy, pw, ph, lme, r, mv,
4919                                    );
4920                                    parts[i] = (r, m2);
4921                                    tot += c2;
4922                                }
4923                                best_c = tot;
4924                            }
4925                        }
4926                        if split_harvest::enabled() {
4927                            let won = match pick.as_ref().map(|p| p.0) {
4928                                Some(0) | None => 0u8,
4929                                Some(m) => m,
4930                            };
4931                            split_harvest::record(c16, best_c, lme, split_gate, won);
4932                        }
4933                        // Intra is ALWAYS a candidate (textured / occluded content):
4934                        // I_16x16 SATD + λ·mode bits.
4935                        let c_intra = fe.best_i16_satd(&sy, mb_x, mb_y)
4936                            + (lme * fe.tune_intra_penalty) as i64;
4937                        inter = if c_intra < best_c { None } else { pick };
4938                        fe.mb_was_skip[mb_idx] = false;
4939                        fe.mb_skip_sad[mb_idx] = skip_sad;
4940                    }
4941
4942                    // ---- RD P_Skip ----------------------------------------
4943                    // The default criterion skips only when the residual quantizes
4944                    // to EXACTLY zero. That matches x264 at both extremes (akiyo
4945                    // 72.5% vs 73.6%, mobile 1.0% vs 1.4%) but falls 17-23 points
4946                    // short in the middle (foreman 6.4% vs 23.6%), because x264
4947                    // also skips macroblocks whose residual is small-but-nonzero.
4948                    // Decide it properly: trial-encode the chosen mode for real
4949                    // bits + reconstruction SSD, and compare J = SSD + lambda*R
4950                    // against the skip. Raw-SAD versions of this comparison fail
4951                    // badly (coding REPAIRS the residual, skipping keeps it), so
4952                    // the distortion term has to come from the reconstruction.
4953                    if fe.rd_skip && rdskip_on && inter.is_some() {
4954                        let skip_cp = fe.skip_predict_chroma(refs, mb_x, mb_y, mv_skip);
4955                        // A P_Skip carries no residual, so its RECONSTRUCTION *is*
4956                        // its prediction — the skip SSD needs no state mutation at
4957                        // all. The commit / mb_ssd / restore round trip this
4958                        // replaces cost a full macroblock save+restore on every
4959                        // candidate, including the ones that go on to code.
4960                        let ssd_s = fe.pred_ssd(&sy, &su, &sv, mb_x, mb_y, &skip_y, &skip_cp);
4961                        debug_assert_eq!(ssd_s, {
4962                            let snap = fe.save_mb(mb_x, mb_y);
4963                            fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_cp);
4964                            let v = fe.mb_ssd(&sy, &su, &sv, mb_x, mb_y);
4965                            fe.load_mb(mb_x, mb_y, &snap);
4966                            v
4967                        }, "skip prediction SSD must equal the committed-skip reconstruction SSD");
4968                        // A skip inside a run costs ~1 bit of mb_skip_run.
4969                        let j_skip = ssd_s as f64 + lambda;
4970                        // Search-skip gate: when the null arm is this cheap it
4971                        // almost always wins, so take it without pricing the coded
4972                        // arm at all. This is where the decision's remaining cost
4973                        // lives — the coded arm is encoded and then discarded on
4974                        // 55-80% of candidates.
4975                        let take_skip = if fe.rd_skip_fast_t > 0.0
4976                            && (ssd_s as f64) <= lambda * fe.rd_skip_fast_t
4977                        {
4978                            true
4979                        } else {
4980                        // Otherwise encode ONCE, into scratch, and KEEP the state.
4981                        // If the skip loses, those are the real bits and they splice
4982                        // straight into the slice. The previous shape trial-encoded,
4983                        // threw the result away, and then encoded again — paying
4984                        // twice on the path that actually codes.
4985                            fe.save_mb_into(mb_x, mb_y, &mut rdskip_snap);
4986                            let mut scratch = BitWriter::new();
4987                            {
4988                                let (m, p) = inter.as_ref().unwrap();
4989                                fe.encode_inter_mb(
4990                                    &mut scratch, refs, &sy, &su, &sv, mb_x, mb_y, *m, p,
4991                                );
4992                            }
4993                            let bits_c = scratch.bit_len();
4994                            let ssd_c = fe.mb_ssd(&sy, &su, &sv, mb_x, mb_y);
4995                            let won = j_skip <= ssd_c as f64 + lambda * bits_c as f64;
4996                            if won {
4997                                fe.load_mb(mb_x, mb_y, &rdskip_snap); // undo it; take the skip
4998                                true
4999                            } else {
5000                                coded = Some(scratch); // keep it — no second encode
5001                                false
5002                            }
5003                        };
5004                        if take_skip {
5005                            fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_cp);
5006                            if !fe.fast {
5007                                fe.mb_was_skip[mb_idx] = true;
5008                                fe.mb_skip_sad[mb_idx] = skip_sad;
5009                            }
5010                            mb_qpy[mb_idx] = fe.cur_qp;
5011                            if precomp {
5012                                bs_grid[mb_idx] = derive_mb_bs_from(
5013                                    &fe, mb_x, mb_y,
5014                                    rusty_h264_common::deblock::MbKind::Skip,
5015                                );
5016                            }
5017                            skip_run += 1;
5018                            continue;
5019                        }
5020                    }
5021                }
5022                w.write_ue(skip_run); // run of skipped macroblocks before this one
5023                skip_run = 0;
5024            }
5025            if mv_force_on() && is_p && inter.is_some() {
5026                let fi = MVCMP_FRAME.load(std::sync::atomic::Ordering::Relaxed);
5027                let ext = EXT_MV.lock().unwrap();
5028                if let Some(field) = ext.get(fi) {
5029                    let w4 = fe.mb_w * 4;
5030                    let b0 = (mb_y * 4) * w4 + mb_x * 4;
5031                    // uniform 16x16 only: a sub-partitioned macroblock has no single
5032                    // vector to transplant, so leave those to our own decision
5033                    let uniform = (0..4).all(|r| {
5034                        (0..4).all(|c| field.get(b0 + r * w4 + c) == field.get(b0))
5035                    });
5036                    if uniform {
5037                        if let Some(&emv) = field.get(b0) {
5038                            inter = Some((0, vec![(0, emv)]));
5039                            MVCMP[6].fetch_add(1, std::sync::atomic::Ordering::Relaxed);
5040                        }
5041                    }
5042                }
5043            }
5044            if mv_cmp_on() && is_p {
5045                if let Some((mode, parts)) = inter.as_ref() {
5046                    let fi = MVCMP_FRAME.load(std::sync::atomic::Ordering::Relaxed);
5047                    let ext = EXT_MV.lock().unwrap();
5048                    if let Some(field) = ext.get(fi) {
5049                        let bidx = (mb_y * 4) * (fe.mb_w * 4) + mb_x * 4;
5050                        if let Some(&emv) = field.get(bidx) {
5051                            let (mode, parts) = (*mode, parts.clone());
5052                            drop(ext);
5053                            // Both priced through the SAME pipeline: MC, transform,
5054                            // quantize, CAVLC. Real bits, real reconstruction SSD.
5055                            let (so, bo) =
5056                                fe.trial_inter(refs, &sy, &su, &sv, mb_x, mb_y, mode, &parts);
5057                            let (se, be) = fe.trial_inter(
5058                                refs, &sy, &su, &sv, mb_x, mb_y, 0, &[(0, emv)],
5059                            );
5060                            let jo = so as f64 + lambda * bo as f64;
5061                            let je = se as f64 + lambda * be as f64;
5062                            use std::sync::atomic::Ordering::Relaxed;
5063                            MVCMP[0].fetch_add(1, Relaxed);
5064                            MVCMP[1].fetch_add(bo as u64, Relaxed);
5065                            MVCMP[2].fetch_add(be as u64, Relaxed);
5066                            MVCMP[3].fetch_add(so.max(0) as u64, Relaxed);
5067                            MVCMP[4].fetch_add(se.max(0) as u64, Relaxed);
5068                            MVCMP[5].fetch_add((je < jo) as u64, Relaxed);
5069                            MVCMP[6].fetch_add((parts[0].1 != emv) as u64, Relaxed);
5070                        }
5071                    }
5072                }
5073            }
5074            // Capture the kind before `inter` is consumed: the deblocking
5075            // strengths of an intra macroblock are pure constants.
5076            let mb_kind = match &inter {
5077                // A single partition covers the whole macroblock with one
5078                // (ref, mv), which collapses the internal derivation to nnz.
5079                Some((_, parts)) if parts.len() == 1 => {
5080                    rusty_h264_common::deblock::MbKind::InterUniform
5081                }
5082                Some(_) => rusty_h264_common::deblock::MbKind::Inter,
5083                None => rusty_h264_common::deblock::MbKind::Intra,
5084            };
5085            match inter {
5086                Some((mode, parts)) => match coded {
5087                    // Encoded already, during the skip decision — splice the bits in
5088                    // rather than encoding this macroblock for a second time.
5089                    Some(sc) => w.append(&sc),
5090                    None => {
5091                        fe.encode_inter_mb(w, refs, &sy, &su, &sv, mb_x, mb_y, mode, &parts)
5092                    }
5093                },
5094                None => encode_mb(&mut fe, w, mb_x, mb_y, &sy, &su, &sv, is_p),
5095            }
5096            mb_qpy[mb_idx] = fe.cur_qp; // ACTUAL QPy (updated iff an mb_qp_delta was coded)
5097            if precomp {
5098                bs_grid[mb_idx] = derive_mb_bs_from(&fe, mb_x, mb_y, mb_kind);
5099            }
5100        }
5101    }
5102    debug_assert!(
5103        !precomp || bs_grid.iter().all(|b| *b != rusty_h264_common::deblock::MbBs::UNSET),
5104        "a macroblock loop exit failed to store its boundary strengths"
5105    );
5106    if is_p && skip_run > 0 {
5107        w.write_ue(skip_run); // trailing skipped macroblocks
5108    }
5109    w.rbsp_trailing_bits();
5110
5111    // Deblock the reconstruction; the result is the inter reference. Baseline: the
5112    // intra mask is `!inter_y` (passed directly, no alloc); no B (List-1 empty); no
5113    // 8×8 transform (t8x8 empty). ref_id is each block's List-0 ref index.
5114    drop(_g_loop);
5115    let _g_fin = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncFinal);
5116    // No NO_REF-mapping collect: it ran over every 4x4 block every frame (~1.9 MB
5117    // of allocation + map at 1080p) to produce a grid that is only ever read for
5118    // INTER-vs-INTER comparisons, where the encoder's raw indices are already
5119    // equivalent. Intra blocks short-circuit before reference identity is touched.
5120    let info = rusty_h264_common::deblock::BlockInfo {
5121        inter: &fe.inter_y,
5122        nnz: &fe.nnz_y,
5123        mv: &fe.mv_y,
5124        ref_id: &fe.ref_idx_y,
5125        mv1: &[],
5126        ref_id1: &[],
5127        w4: fe.mb_w * 4,
5128        t8x8: &[],
5129        bs: &bs_grid,
5130    };
5131    // Per-MB actual QPy (AQ varies it; `mb_qp_delta`-driven). With `aq_strength 0`
5132    // this is uniform, reproducing the old scalar-QP filtering exactly.
5133    drop(_g_fin);
5134    rusty_h264_common::deblock::filter_frame(
5135        &mut fe.rec_y,
5136        &mut fe.rec_u,
5137        &mut fe.rec_v,
5138        fe.mb_w,
5139        fe.mb_h,
5140        &mb_qpy,
5141        0, // chroma_qp_index_offset — the encoder emits 0
5142        0, // slice_alpha_c0_offset — the encoder always signals zero offsets
5143        0, // slice_beta_offset
5144        &info,
5145    );
5146    let w4 = fe.mb_w * 4;
5147    crate::RefFrame {
5148        y: fe.rec_y,
5149        u: fe.rec_u,
5150        v: fe.rec_v,
5151        poc: 0,       // set by the caller (it knows the display order)
5152        frame_num: 0, // set by the caller
5153        // List-0 motion field, for a later B-frame's spatial-direct colZeroFlag.
5154        mv: fe.mv_y,
5155        ref_idx: fe.ref_idx_y,
5156        w4,
5157        // Filtered lazily on first sub-pel search use (see `RefFrame::hpel`).
5158        hpel: std::sync::OnceLock::new(),
5159    }
5160}
5161
5162/// Codes a B-slice's macroblock layer. B-frames are **non-reference**, so the
5163/// reconstruction is computed (the CAVLC nnz predictor needs it) but discarded.
5164///
5165/// This brick: every MB is coded `B_L0_16x16` (`mb_type == 1`) — a real
5166/// motion-compensated prediction from `l0` (the nearest PAST anchor, List-0 index
5167/// 0) plus a coded residual. Because every MB is List-0-only with `ref_idx`
5168/// inferred 0, the per-4×4 List-0 motion field and its median `mvd` predictor are
5169/// byte-identical to the P-slice `P_L0_16x16` path — so this reuses
5170/// [`FrameEncoder::encode_inter_mb_v1_b`] verbatim, differing from P only in the
5171/// `mb_type` value. `l1` (nearest future anchor) is unused until `B_Bi` lands.
5172#[allow(clippy::too_many_arguments)]
5173#[allow(clippy::too_many_arguments)]
5174pub fn encode_slice_data_b(
5175    w: &mut BitWriter,
5176    cfg: &EncoderConfig,
5177    frame: &YuvFrame,
5178    qp: u8,
5179    poc: i32,
5180    l0: &crate::RefFrame,
5181    l1: &crate::RefFrame,
5182    qpo: &[i32],
5183) {
5184    let mut fe = FrameEncoder::new(cfg);
5185    fe.qp = qp;
5186    fe.qpc = chroma_qp(qp);
5187    fe.cur_qp = qp;
5188    if cfg.cabac_dz_div > 0 {
5189        fe.idz = cfg.cabac_dz_div; // CABAC-specific dead-zone override
5190    } // QPY_PREV starts at the slice QP so the first mb_qp_delta is 0
5191    // Implicit bi-prediction weights from the anchor POC distances (matches the
5192    // decoder). Equidistant B (bframes==1) → 32:32 (plain average); unequal → weighted.
5193    fe.bi_w = implicit_bi_weights(poc, l0.poc, l1.poc);
5194    let (sy, su, sv) = coded_source(cfg, frame);
5195    let lambda = 0.85 * fe.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
5196    let lme = lambda.sqrt();
5197    let refs = std::slice::from_ref(l0); // List-0 = [nearest past anchor]
5198    // Same content-adaptive SAD→SATD dispatch as the P path (codec-content-adaptive-
5199    // dispatch): the top `satd_q` fraction of highest-variance MBs price by SATD.
5200    if fe.satd_q > 0.0 {
5201        let mut vars: Vec<i64> = (0..fe.mb_h)
5202            .flat_map(|my| (0..fe.mb_w).map(move |mx| (mx, my)))
5203            .map(|(mx, my)| mb_variance(&sy, fe.cw, mx, my))
5204            .collect();
5205        vars.sort_unstable();
5206        let idx = (((1.0 - fe.satd_q) * vars.len() as f64) as usize).min(vars.len() - 1);
5207        fe.satd_var_thresh = vars[idx];
5208    }
5209    let mut skip_run = 0u32; // run of consecutive B_Skip MBs pending a coded MB
5210    for mb_y in 0..fe.mb_h {
5211        for mb_x in 0..fe.mb_w {
5212            let (lx, ly) = (mb_x * 16, mb_y * 16);
5213            let (pbx, pby) = (mb_x as isize * 4, mb_y as isize * 4);
5214            fe.mb_use_satd =
5215                fe.satd_q > 0.0 && mb_variance(&sy, fe.cw, mb_x, mb_y) >= fe.satd_var_thresh;
5216            // Per-list median MV predictors — the search-rate center AND the actual
5217            // `mvd` predictor (identical to the decoder's `predict_partition_mv`).
5218            let n0 = fe.mv_neighbors_block_list(pbx, pby, 4, 0);
5219            let n1 = fe.mv_neighbors_block_list(pbx, pby, 4, 1);
5220            let pmv0 = predict_partition_mv(0, 0, n0[0], n0[1], n0[2], 0);
5221            let pmv1 = predict_partition_mv(0, 0, n1[0], n1[1], n1[2], 0);
5222            // Independent List-0 / List-1 motion searches (their J already includes
5223            // the mvd rate against the matching predictor, so J0/J1 compare directly).
5224            // Spatial-direct prediction (basis of B_Skip and B_Direct_16x16).
5225            let (dp, dc, dmotion) = fe.b_direct(l0, l1, mb_x, mb_y);
5226            // B_Skip: take the direct prediction with NO coded residual (~1 bit in
5227            // the mb_skip_run) only when it is truly FREE — its residual quantizes to
5228            // zero at the B QP, so skipping loses nothing. (A looser SATD-threshold
5229            // skip was measured strictly WORSE: on B's derived prediction the SATD
5230            // proxy over-values the skip, dropping residual the quantizer wanted —
5231            // the same proxy-vs-quantization gap seen on sub-pel. So skip only when
5232            // provably free; the rest goes through the L0/L1/Bi/Direct RD decision.)
5233            if fe.skip_luma_is_free(&sy, mb_x, mb_y, &dp)
5234                && fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, &dc)
5235            {
5236                fe.commit_direct_motion(mb_x, mb_y, &dmotion);
5237                skip_run += 1;
5238                continue;
5239            }
5240            let d_direct = fe.pred_dist(&sy, lx, ly, &dp);
5241            let (mv0, j0) = fe.motion_search(l0, &sy, lx, ly, 16, 16, &[pmv0], lme, None);
5242            let (mv1, j1) = fe.motion_search(l1, &sy, lx, ly, 16, 16, &[pmv1], lme, None);
5243            // Bi: average the two winners' predictions; rate = both mvds.
5244            let d_bi = fe.bi_dist(l0, l1, &sy, lx, ly, mv0, mv1);
5245            let r_bi = mvd_bits(mv0.0 - pmv0.0) + mvd_bits(mv0.1 - pmv0.1)
5246                + mvd_bits(mv1.0 - pmv1.0) + mvd_bits(mv1.1 - pmv1.1);
5247            let j_bi = d_bi + (lme * r_bi as f64) as i64;
5248            // B_Direct (mb_type 0): spatial-direct prediction, NO coded MV — so its
5249            // J (d_direct, computed above) carries zero mvd rate and it wins wherever
5250            // the derived motion predicts as well as an explicit vector.
5251            // Pick the cheapest of {0=Direct, 1=L0, 2=L1, 3=Bi}; Direct wins ties.
5252            let (mut dir, mut best) = (0u8, d_direct);
5253            if j0 < best { dir = 1; best = j0; }
5254            if j1 < best { dir = 2; best = j1; }
5255            if j_bi < best { dir = 3; best = j_bi; }
5256            let _ = best;
5257            w.write_ue(skip_run); // run of B_Skips preceding this coded MB
5258            skip_run = 0;
5259            let bspec = BInter { dir, l1, mv0, mv1 };
5260            fe.encode_inter_mb_v1_b(w, refs, &sy, &su, &sv, mb_x, mb_y, 0, &[], Some(bspec));
5261        }
5262    }
5263    if skip_run > 0 {
5264        w.write_ue(skip_run); // trailing B_Skip run
5265    }
5266    w.rbsp_trailing_bits();
5267}
5268
5269/// `se(d)` Exp-Golomb bit length — the `mvd`-component rate for the B mode
5270/// decision. Same closed form as `motion_search`'s private `mvbits` (kept separate
5271/// so the P search's heuristic — and thus P output — is untouched).
5272#[inline(always)]
5273fn mvd_bits(d: i32) -> u32 {
5274    let codenum = if d > 0 { (2 * d - 1) as u32 } else { (-2 * d) as u32 };
5275    1 + 2 * (31 - (codenum + 1).leading_zeros())
5276}
5277
5278/// Reads a 4×4 residual block (source minus a raster prediction block).
5279/// Writes `ref_idx_l0` (spec: `te(v)` when two references are active — a single
5280/// flag — else `ue(v)`). Only called when more than one reference is active.
5281fn write_ref_idx(w: &mut BitWriter, refi: i32, num_refs: usize) {
5282    if num_refs == 2 {
5283        w.write_bit(refi == 0); // te(v): value = !bit
5284    } else {
5285        w.write_ue(refi as u32);
5286    }
5287}
5288
5289/// Approximate bit cost of coding `ref_idx = r` with `num_refs` active, for the
5290/// motion-estimation rate term. Zero with a single reference (no `ref_idx` coded).
5291fn ref_bits(r: usize, num_refs: usize) -> u32 {
5292    if num_refs <= 1 {
5293        0
5294    } else if num_refs == 2 {
5295        1
5296    } else {
5297        let mut n = r as u32 + 1;
5298        let mut len = 1;
5299        while n > 1 {
5300            n >>= 1;
5301            len += 2;
5302        }
5303        len
5304    }
5305}
5306
5307fn residual(src: &[u8], stride: usize, x0: usize, y0: usize, pred: &[i32; 16]) -> [i32; 16] {
5308    let mut r = [0i32; 16];
5309    for dy in 0..4 {
5310        for dx in 0..4 {
5311            r[dy * 4 + dx] = src[(y0 + dy) * stride + (x0 + dx)] as i32 - pred[dy * 4 + dx];
5312        }
5313    }
5314    r
5315}
5316
5317/// Writes reconstructed samples back into a plane.
5318fn store(plane: &mut [u8], stride: usize, x0: usize, y0: usize, s: &[u8; 16]) {
5319    for dy in 0..4 {
5320        for dx in 0..4 {
5321            plane[(y0 + dy) * stride + (x0 + dx)] = s[dy * 4 + dx];
5322        }
5323    }
5324}
5325
5326/// Extracts the 4×4 raster prediction block at `(bx, by)` from a 16×16 (256-sample)
5327/// luma prediction.
5328fn pred_block(pred: &[u8; 256], bx: usize, by: usize) -> [i32; 16] {
5329    let mut p = [0i32; 16];
5330    for dy in 0..4 {
5331        for dx in 0..4 {
5332            p[dy * 4 + dx] = pred[(by * 4 + dy) * 16 + (bx * 4 + dx)] as i32;
5333        }
5334    }
5335    p
5336}
5337
5338/// Sum of absolute transformed differences over a 16×16 luma macroblock — the
5339/// mode-decision cost (correlates with coded bits better than plain SAD).
5340/// SATD of a `w`×`h` luma block: `src` (stride `ss`) vs `pred` (stride `ps`).
5341///
5342/// With `--features asm` and a supported size this is `2 · WelsSampleSatd_sse2`, which
5343/// is **byte-identical** to the scalar `Σ|H·d|` Hadamard: the openh264 kernel returns
5344/// `(Σ+1)>>1`, and `Σ` is always even (every 4×4 Hadamard coefficient shares the block
5345/// sum's parity, so 16 of them sum even), so `×2` recovers `Σ` exactly — proven over
5346/// 20 k random blocks at 4×4/8×8/16×16 in `tests/satd_asm_compare.rs`. Without asm (or
5347/// for an unsupported size) it falls back to the scalar Hadamard — the original path.
5348#[inline]
5349pub(crate) fn satd_px(src: &[u8], ss: usize, pred: &[u8], ps: usize, w: usize, h: usize) -> i64 {
5350    #[cfg(accel)]
5351    {
5352        let asm = match (w, h) {
5353            (16, 16) => Some(rusty_h264_accel::satd_16x16(src, ss, pred, ps)),
5354            (16, 8) => Some(rusty_h264_accel::satd_16x8(src, ss, pred, ps)),
5355            (8, 16) => Some(rusty_h264_accel::satd_8x16(src, ss, pred, ps)),
5356            (8, 8) => Some(rusty_h264_accel::satd_8x8(src, ss, pred, ps)),
5357            (4, 4) => Some(rusty_h264_accel::satd_4x4(src, ss, pred, ps)),
5358            _ => None,
5359        };
5360        if let Some(v) = asm {
5361            return 2 * v as i64;
5362        }
5363    }
5364    // Scalar Hadamard (also the no-asm path): Σ over the 4×4 sub-blocks.
5365    let (nbx, nby) = (w / 4, h / 4);
5366    let mut blocks = [[0i32; 16]; 16];
5367    let mut bi = 0;
5368    for by in 0..nby {
5369        for bx in 0..nbx {
5370            let blk = &mut blocks[bi];
5371            for dy in 0..4 {
5372                for dx in 0..4 {
5373                    blk[dy * 4 + dx] =
5374                        src[(by * 4 + dy) * ss + bx * 4 + dx] as i32 - pred[(by * 4 + dy) * ps + bx * 4 + dx] as i32;
5375                }
5376            }
5377            bi += 1;
5378        }
5379    }
5380    satd_4x4_sum(&blocks[..nbx * nby])
5381}
5382
5383/// SAD of a `w`×`h` block: `src` (stride `ss`) vs a strided region `r` (stride `rs`)
5384/// — the openh264 `psadbw` kernels for the shapes that ship them (they take strides,
5385/// so in-place plane reads need NO materialize), scalar `Σ abs_diff` rows otherwise
5386/// (LLVM lowers the idiom to `psadbw` for contiguous rows).
5387#[inline]
5388fn sad_strided(src: &[u8], ss: usize, r: &[u8], rs: usize, w: usize, h: usize) -> i64 {
5389    #[cfg(accel)]
5390    {
5391        match (w, h) {
5392            (16, 16) => return rusty_h264_accel::sad_16x16(src, ss, r, rs) as i64,
5393            (16, 8) => return rusty_h264_accel::sad_16x8(src, ss, r, rs) as i64,
5394            (8, 16) => return rusty_h264_accel::sad_8x16(src, ss, r, rs) as i64,
5395            _ => {}
5396        }
5397    }
5398    let mut sad = 0u32;
5399    for dy in 0..h {
5400        let a = &src[dy * ss..][..w];
5401        let b = &r[dy * rs..][..w];
5402        sad += a.iter().zip(b).map(|(&x, &y)| x.abs_diff(y) as u32).sum::<u32>();
5403    }
5404    sad as i64
5405}
5406
5407/// Fused `SAD(src, (a+b+1)>>1)` — the quarter-pel SAD without materializing the
5408/// average (the B2 sibling of the A3 `satd_avg` kernel, scalar because the avg+SAD
5409/// idiom auto-vectorizes and quarter-phase SAD evals are seed-frequency only).
5410#[inline]
5411fn sad_avg_strided(src: &[u8], ss: usize, a: &[u8], b: &[u8], rs: usize, w: usize, h: usize) -> i64 {
5412    let mut sad = 0u32;
5413    for dy in 0..h {
5414        let s = &src[dy * ss..][..w];
5415        let pa = &a[dy * rs..][..w];
5416        let pb = &b[dy * rs..][..w];
5417        for i in 0..w {
5418            let p = ((pa[i] as u16 + pb[i] as u16 + 1) >> 1) as u8;
5419            sad += s[i].abs_diff(p) as u32;
5420        }
5421    }
5422    sad as i64
5423}
5424
5425fn satd_16x16(src: &[u8], stride: usize, lx: usize, ly: usize, pred: &[u8; 256]) -> i64 {
5426    satd_px(&src[ly * stride + lx..], stride, pred, 16, 16, 16)
5427}
5428
5429/// SAD over a 16×16 luma macroblock against a prediction — the fast preset's
5430/// intra cost, kept in the same (SAD) domain as its inter cost. `Σ a.abs_diff(b)`
5431/// over `u8` slices auto-vectorizes to `psadbw`.
5432fn sad_16x16(src: &[u8], stride: usize, lx: usize, ly: usize, pred: &[u8; 256]) -> i64 {
5433    let mut sad = 0u32;
5434    for dy in 0..16 {
5435        let s = &src[(ly + dy) * stride + lx..][..16];
5436        let p = &pred[dy * 16..][..16];
5437        sad += s.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
5438    }
5439    sad as i64
5440}
5441
5442/// SATD over an 8×8 chroma block (four 4×4 sub-blocks) against a prediction.
5443fn satd_8x8(src: &[u8], stride: usize, x0: usize, y0: usize, pred: &[u8; 64]) -> i64 {
5444    satd_px(&src[y0 * stride + x0..], stride, pred, 8, 8, 8)
5445}
5446
5447/// SATD of one 4×4 luma block against a prediction.
5448fn satd_4x4(src: &[u8], stride: usize, px: usize, py: usize, pred: &[u8; 16]) -> i64 {
5449    satd_px(&src[py * stride + px..], stride, pred, 4, 4, 4)
5450}
5451
5452/// Whether an `Intra_4x4` mode is usable given top/left neighbor availability.
5453fn i4_mode_available(mode: u8, top: bool, left: bool) -> bool {
5454    match mode {
5455        0 | 3 | 7 => top,        // vertical, diag-down-left, vertical-left
5456        1 | 8 => left,           // horizontal, horizontal-up
5457        2 => true,               // DC
5458        _ => top && left,        // diag-down-right, vertical-right, horizontal-down
5459    }
5460}
5461
5462/// Result of planning an I_4x4 macroblock (luma). Reconstruction has already
5463/// been written into the frame's `rec_y` and `coded_y` by [`plan_i4x4`].
5464struct I4Plan {
5465    modes: [u8; 16],       // per-block intra4x4 mode, raster [lby*4+lbx]
5466    q: [[i32; 16]; 16],    // per-block quantized coefficients (full, raster)
5467    cbp_luma: u32,         // 4-bit coded-block-pattern (one bit per 8×8 region)
5468    nonzero: i64,          // total non-zero coefficients (rate proxy)
5469}
5470
5471/// A fully-decided intra macroblock: the mode decision, the quantized coefficients,
5472/// and the committed reconstruction. Produced by [`plan_mb`] (which reuses the
5473/// entire mode-decision + transform + reconstruct path), then consumed by an
5474/// entropy backend — `emit_mb_cavlc` or `emit_mb_cabac` — so the two coders share
5475/// every non-entropy decision bit-for-bit (the bringup-encoder reuse guarantee).
5476struct MbPlan {
5477    use_i4: bool,
5478    // I_16x16 (when !use_i4): prediction mode, whether any AC is coded (cbp_luma=15),
5479    // luma DC levels (block order), per-4×4 quantized AC (raster).
5480    i16_mode: I16Mode,
5481    i16_cbp15: bool,
5482    i16_dc_levels: [i32; 16],
5483    i16_q: [[i32; 16]; 16],
5484    // I_4x4 (when use_i4 && i8 is None): the sub-plan, already reconstructed.
5485    i4: Option<I4Plan>,
5486    // I_8x8 (High profile; when use_i4 && i8 is Some): the sub-plan, already
5487    // reconstructed. use_i4 means "I_NxN"; i8 present disambiguates 8x8 from 4x4.
5488    i8: Option<I8Plan>,
5489    // Chroma (shared by both luma types).
5490    chroma_mode: u8,
5491    cbp_chroma: u32,
5492    c_dc_levels: [[i32; 4]; 2],
5493    c_q_blocks: [[[i32; 16]; 4]; 2],
5494}
5495
5496/// A fully-decided inter macroblock: the per-partition motion residuals, coded
5497/// block pattern, and quantized residual, with the reconstruction + motion grids
5498/// already committed. Produced by [`FrameEncoder::plan_inter_mb`] (which reuses the
5499/// whole MC + residual + reconstruct path), then coded by `emit_inter_cavlc` or
5500/// `emit_inter_cabac` — so the two entropy backends share every non-entropy
5501/// decision bit-for-bit (the P/B analogue of [`MbPlan`]).
5502struct InterPlan {
5503    mvds: [(i32, i32); 4], // per-partition mvd (P: mvd_l0; B: mvd_l0 then mvd_l1)
5504    plan_refs: [i32; 4],   // per-partition ref_idx_l0 (multi-ref P; 0 for B / single-ref)
5505    n_mvd: usize,
5506    cbp: u32,
5507    q_blocks: [[i32; 16]; 16], // luma quantized levels (raster) — used when !t8x8
5508    c_dc_levels: [[i32; 4]; 2],
5509    c_q: [[[i32; 16]; 4]; 2],
5510    t8x8: bool,           // transform_size_8x8_flag (High profile, 8x8 luma residual)
5511    q8: [[i32; 64]; 4],   // per-8x8-block quantized levels (raster) — used when t8x8
5512}
5513
5514/// Gathers the 4×4 luma intra neighbors at pixel `(px, py)` from `rec_y`.
5515fn gather_i4(
5516    fe: &FrameEncoder,
5517    px: usize,
5518    py: usize,
5519    avail_top: bool,
5520    avail_left: bool,
5521    bx: usize,
5522    by: usize,
5523) -> ([u8; 8], [u8; 4], u8) {
5524    let (cw, w4) = (fe.cw, fe.mb_w * 4);
5525    let mut top = [0u8; 8];
5526    let mut left = [0u8; 4];
5527    let mut corner = 0;
5528    if avail_top {
5529        for i in 0..4 {
5530            top[i] = fe.rec_y[(py - 1) * cw + px + i];
5531        }
5532        let tr_avail = bx + 1 < w4 && fe.coded_y[(by - 1) * w4 + (bx + 1)];
5533        for i in 0..4 {
5534            top[4 + i] = if tr_avail {
5535                fe.rec_y[(py - 1) * cw + px + 4 + i]
5536            } else {
5537                top[3]
5538            };
5539        }
5540    }
5541    if avail_left {
5542        for i in 0..4 {
5543            left[i] = fe.rec_y[(py + i) * cw + px - 1];
5544        }
5545    }
5546    if avail_top && avail_left {
5547        corner = fe.rec_y[(py - 1) * cw + px - 1];
5548    }
5549    (top, left, corner)
5550}
5551
5552/// Plans an I_4x4 macroblock: picks a mode per 4×4 block (lowest-SATD available
5553/// mode), quantizes, and reconstructs serially into `rec_y` so each block can
5554/// predict from the previous one.
5555/// Neighbour 4x4 block intra mode for the MPM candidate: in-MB blocks read the
5556/// in-progress local `modes`; blocks in earlier MBs read `fe.modes_y`. (bx, by)
5557/// are the current block's absolute 4x4 grid coords.
5558#[inline]
5559fn modes_at(fe: &FrameEncoder, modes: &[u8; 16], lbx: usize, lby: usize, dx: isize, dy: isize, bx: usize, by: usize) -> u8 {
5560    let (nx, ny) = (lbx as isize + dx, lby as isize + dy);
5561    if (0..4).contains(&nx) && (0..4).contains(&ny) {
5562        modes[ny as usize * 4 + nx as usize]
5563    } else {
5564        let w4 = fe.mb_w * 4;
5565        let gx = (bx as isize + dx) as usize;
5566        let gy = (by as isize + dy) as usize;
5567        fe.modes_y[gy * w4 + gx]
5568    }
5569}
5570
5571fn plan_i4x4(fe: &mut FrameEncoder, sy: &[u8], mb_x: usize, mb_y: usize, qp: u8) -> I4Plan {
5572    let w4 = fe.mb_w * 4;
5573    let mut modes = [2u8; 16];
5574    let mut q = [[0i32; 16]; 16];
5575    let mut cbp_luma = 0u32;
5576    let mut nonzero = 0i64;
5577
5578    for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
5579        let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
5580        let (px, py) = (bx * 4, by * 4);
5581        let avail_top = by > 0;
5582        let avail_left = bx > 0;
5583        let (top, left, corner) = gather_i4(fe, px, py, avail_top, avail_left, bx, by);
5584
5585        // Pick the lowest-SATD available mode. RUSTY_FAST_INTRA prunes the
5586        // candidate set to {MPM, DC, V, H} (x264-ultrafast-style); the H.264
5587        // predicted mode (min of left/top block modes, DC on the edge) keeps the
5588        // 1-bit prev_intra4x4_pred_mode signalling cheap for the common winner.
5589        let mut best_m = 2u8;
5590        let mut best_cost = i64::MAX;
5591        if fe.fast && fast_intra_enabled() {
5592            let lm = if bx > 0 { modes_at(fe, &modes, lbx, lby, -1, 0, bx, by) } else { 2 };
5593            let tm = if by > 0 { modes_at(fe, &modes, lbx, lby, 0, -1, bx, by) } else { 2 };
5594            let mpm = lm.min(tm);
5595            let mut cands = [mpm, 2u8, 0, 1];
5596            for i in 1..4 {
5597                for j in 0..i {
5598                    if cands[i] == cands[j] {
5599                        cands[i] = 255;
5600                    }
5601                }
5602            }
5603            for &m in cands.iter() {
5604                if m == 255 || !i4_mode_available(m, avail_top, avail_left) {
5605                    continue;
5606                }
5607                let pred = intra4x4_pred(m, avail_top, avail_left, &top, &left, corner);
5608                let cost = satd_4x4(sy, fe.cw, px, py, &pred);
5609                if cost < best_cost {
5610                    best_cost = cost;
5611                    best_m = m;
5612                }
5613            }
5614        } else {
5615            for m in 0..9u8 {
5616                if !i4_mode_available(m, avail_top, avail_left) {
5617                    continue;
5618                }
5619                let pred = intra4x4_pred(m, avail_top, avail_left, &top, &left, corner);
5620                let cost = satd_4x4(sy, fe.cw, px, py, &pred);
5621                if cost < best_cost {
5622                    best_cost = cost;
5623                    best_m = m;
5624                }
5625            }
5626        }
5627
5628        // Quantize + reconstruct with the chosen mode.
5629        let pred = intra4x4_pred(best_m, avail_top, avail_left, &top, &left, corner);
5630        let mut predb = [0i32; 16];
5631        for i in 0..16 {
5632            predb[i] = pred[i] as i32;
5633        }
5634        let res = residual(sy, fe.cw, px, py, &predb);
5635        let qb = rdoq(&forward_core(&res), qp, fe.idz, fe.rdoq_strength, 0); // full 16 incl DC
5636        let s = reconstruct_4x4(&dequantize(&qb, qp), &predb);
5637        store(&mut fe.rec_y, fe.cw, px, py, &s);
5638        fe.coded_y[by * w4 + bx] = true;
5639
5640        let nz = qb.iter().filter(|&&v| v != 0).count();
5641        if nz > 0 {
5642            cbp_luma |= 1 << ((lby / 2) * 2 + (lbx / 2));
5643        }
5644        nonzero += nz as i64;
5645        modes[lby * 4 + lbx] = best_m;
5646        q[lby * 4 + lbx] = qb;
5647    }
5648    I4Plan {
5649        modes,
5650        q,
5651        cbp_luma,
5652        nonzero,
5653    }
5654}
5655
5656/// A planned I_8x8 macroblock (High profile): one intra8x8 mode + one 8x8 DCT per
5657/// 8x8 block. Reconstructed serially into `rec_y` (each block predicts from the
5658/// previous), and `modes_y` written per block so later blocks' MPM sees earlier.
5659struct I8Plan {
5660    modes: [u8; 4],     // per-8x8-block intra8x8 mode (raster b8 0..3)
5661    q: [[i32; 64]; 4],  // per-8x8-block quantized levels (raster)
5662    cbp_luma: u32,      // 4-bit coded-block-pattern (one bit per 8x8 block)
5663    nonzero: i64,       // rate proxy
5664}
5665
5666/// Forward zig-zag scan of a raster 8x8 block: `scan[i] = raster[ZIGZAG_8X8[i]]`
5667/// (the inverse of the decoder's `un_scan_8x8`).
5668const ZIGZAG_8X8: [usize; 64] = [
5669    0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20,
5670    13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59,
5671    52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
5672];
5673
5674#[inline]
5675fn scan_8x8_fwd(raster: &[i32; 64]) -> [i32; 64] {
5676    std::array::from_fn(|i| raster[ZIGZAG_8X8[i]])
5677}
5678
5679/// Gather the 8x8 intra reference samples (top[16] incl top-right, left[8], corner)
5680/// from `rec_y` — the encoder counterpart of the decoder's `gather_i8`.
5681fn gather_i8_enc(
5682    fe: &FrameEncoder,
5683    px: usize,
5684    py: usize,
5685    avail_top: bool,
5686    avail_left: bool,
5687    bx: usize,
5688    by: usize,
5689) -> ([u8; 16], [u8; 8], u8, bool) {
5690    let (cw, w4) = (fe.cw, fe.mb_w * 4);
5691    let mut top = [0u8; 16];
5692    let mut left = [0u8; 8];
5693    let mut corner = 0;
5694    if avail_top {
5695        for i in 0..8 {
5696            top[i] = fe.rec_y[(py - 1) * cw + px + i];
5697        }
5698        let tr_avail = bx + 2 < w4 && fe.coded_y[(by - 1) * w4 + (bx + 2)];
5699        for i in 0..8 {
5700            top[8 + i] = if tr_avail {
5701                fe.rec_y[(py - 1) * cw + px + 8 + i]
5702            } else {
5703                top[7]
5704            };
5705        }
5706    }
5707    if avail_left {
5708        for i in 0..8 {
5709            left[i] = fe.rec_y[(py + i) * cw + px - 1];
5710        }
5711    }
5712    let avail_corner = avail_top && avail_left;
5713    if avail_corner {
5714        corner = fe.rec_y[(py - 1) * cw + px - 1];
5715    }
5716    (top, left, corner, avail_corner)
5717}
5718
5719/// Plans an I_8x8 macroblock: per 8x8 block, picks the lowest-SATD intra8x8 mode,
5720/// 8x8-forward-transforms + quantizes, and reconstructs serially into `rec_y`.
5721fn plan_i8x8(fe: &mut FrameEncoder, sy: &[u8], mb_x: usize, mb_y: usize, qp: u8) -> I8Plan {
5722    let w4 = fe.mb_w * 4;
5723    let mut modes = [2u8; 4];
5724    let mut q = [[0i32; 64]; 4];
5725    let mut cbp_luma = 0u32;
5726    let mut nonzero = 0i64;
5727    let weight = [16i32; 64];
5728
5729    for b8 in 0..4usize {
5730        let (b8x, b8y) = (b8 % 2, b8 / 2);
5731        let (px, py) = (mb_x * 16 + b8x * 8, mb_y * 16 + b8y * 8);
5732        let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2); // top-left 4x4 cell
5733        let avail_top = b8y > 0 || mb_y > 0;
5734        let avail_left = b8x > 0 || mb_x > 0;
5735        let (top, left, corner, avail_corner) =
5736            gather_i8_enc(fe, px, py, avail_top, avail_left, bx, by);
5737
5738        // Mode decision: lowest-SATD available intra8x8 mode (same 9 modes / avail
5739        // rules as intra4x4). The MPM (predict_i4_mode on the top-left 4x4) keeps the
5740        // 1-bit prev-mode signalling cheap; a small penalty biases toward it.
5741        let predicted = predict_i4_mode(fe, bx, by);
5742        let mut best_m = 2u8;
5743        let mut best_cost = i64::MAX;
5744        for m in 0..9u8 {
5745            if !i4_mode_available(m, avail_top, avail_left) {
5746                continue;
5747            }
5748            let pred = intra8x8_pred(m, avail_top, avail_left, avail_corner, &top, &left, corner);
5749            let mut cost = satd_8x8(sy, fe.cw, px, py, &pred);
5750            if m != predicted {
5751                cost += 4 * fe.qp as i64; // ~mode-signal penalty (rem vs prev flag)
5752            }
5753            if cost < best_cost {
5754                best_cost = cost;
5755                best_m = m;
5756            }
5757        }
5758        modes[b8] = best_m;
5759
5760        // Forward 8x8 transform + quantize + reconstruct (shared decoder primitives).
5761        let pred = intra8x8_pred(best_m, avail_top, avail_left, avail_corner, &top, &left, corner);
5762        let mut res = [0i32; 64];
5763        for dy in 0..8 {
5764            for dx in 0..8 {
5765                res[dy * 8 + dx] =
5766                    sy[(py + dy) * fe.cw + (px + dx)] as i32 - pred[dy * 8 + dx] as i32;
5767            }
5768        }
5769        let levels = quantize_8x8(&forward_core_8x8(&res), qp, &weight, fe.idz);
5770        let nz = levels.iter().filter(|&&v| v != 0).count();
5771        if nz > 0 {
5772            cbp_luma |= 1 << b8;
5773        }
5774        nonzero += nz as i64;
5775        q[b8] = levels;
5776
5777        let res_r = inverse_quant_8x8(&levels, qp, &weight);
5778        let predb: [i32; 64] = std::array::from_fn(|i| pred[i] as i32);
5779        let recon = add_residual_8x8(&res_r, &predb);
5780        for dy in 0..8 {
5781            for dx in 0..8 {
5782                fe.rec_y[(py + dy) * fe.cw + (px + dx)] = recon[dy * 8 + dx];
5783            }
5784        }
5785        // Publish the mode into all four 4x4 cells + mark coded — so the next 8x8
5786        // block's MPM (and later MBs' neighbours) see it, exactly as the decoder does.
5787        for sry in 0..2 {
5788            for srx in 0..2 {
5789                fe.modes_y[(by + sry) * w4 + (bx + srx)] = best_m;
5790                fe.coded_y[(by + sry) * w4 + (bx + srx)] = true;
5791            }
5792        }
5793    }
5794    I8Plan {
5795        modes,
5796        q,
5797        cbp_luma,
5798        nonzero,
5799    }
5800}
5801
5802/// Inter 8×8-transform luma candidate. Forward-8×8 + quantize + reconstruct each of
5803/// the four 8×8 blocks of the motion-compensated residual `(source − pred_y)`, the
5804/// pure inverse of the decoder's t8x8 inter luma path (`inv_quant8` ∘ `un_scan_8x8`
5805/// ∘ `add_residual_8x8`). Returns the quantized levels, `cbp_luma`, a LEVEL-AWARE rate
5806/// estimate (Σ `rdoq_rate(|level|)` — charges the 8×8's fewer-but-larger coeffs at
5807/// their true bit cost, not a blind count), the 256-sample reconstruction, and its
5808/// SSD vs source. Inter deadzone `dz_div = 6`; scaling list flat (16).
5809#[allow(clippy::too_many_arguments)]
5810fn plan_inter8_luma(
5811    sy: &[u8],
5812    cw: usize,
5813    mb_x: usize,
5814    mb_y: usize,
5815    pred_y: &[u8; 256],
5816    qp: u8,
5817) -> ([[i32; 64]; 4], u32, f64, [u8; 256], i64) {
5818    let weight = [16i32; 64];
5819    let mut q8 = [[0i32; 64]; 4];
5820    let mut cbp = 0u32;
5821    let mut rate = 0f64;
5822    let mut rec = [0u8; 256];
5823    let mut ssd = 0i64;
5824    for b8 in 0..4usize {
5825        let (b8x, b8y) = (b8 % 2, b8 / 2);
5826        let mut res = [0i32; 64];
5827        for dy in 0..8 {
5828            for dx in 0..8 {
5829                let sx = mb_x * 16 + b8x * 8 + dx;
5830                let syy = mb_y * 16 + b8y * 8 + dy;
5831                let p = pred_y[(b8y * 8 + dy) * 16 + (b8x * 8 + dx)] as i32;
5832                res[dy * 8 + dx] = sy[syy * cw + sx] as i32 - p;
5833            }
5834        }
5835        let levels = quantize_8x8(&forward_core_8x8(&res), qp, &weight, 6);
5836        let mut nz = false;
5837        for &l in &levels {
5838            if l != 0 {
5839                nz = true;
5840                rate += rdoq_rate((l as i64).abs());
5841            }
5842        }
5843        if nz {
5844            cbp |= 1 << b8;
5845        }
5846        q8[b8] = levels;
5847
5848        let res_r = inverse_quant_8x8(&levels, qp, &weight);
5849        let predb: [i32; 64] =
5850            std::array::from_fn(|i| pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32);
5851        let recon = add_residual_8x8(&res_r, &predb);
5852        for dy in 0..8 {
5853            for dx in 0..8 {
5854                let ri = (b8y * 8 + dy) * 16 + (b8x * 8 + dx);
5855                rec[ri] = recon[dy * 8 + dx];
5856                let sx = mb_x * 16 + b8x * 8 + dx;
5857                let syy = mb_y * 16 + b8y * 8 + dy;
5858                let d = recon[dy * 8 + dx] as i64 - sy[syy * cw + sx] as i64;
5859                ssd += d * d;
5860            }
5861        }
5862    }
5863    (q8, cbp, rate, rec, ssd)
5864}
5865
5866/// 16×16 luma intra prediction. For interior MBs (both neighbors available) this
5867/// dispatches to openh264's `WelsI16x16LumaPred*_sse2` (bit-identical to the spec
5868/// predictor); edge MBs (partial availability → C-only DC variants) use the scalar
5869/// path. The scalar `top`/`left`/`corner` are gathered by the caller regardless.
5870#[inline]
5871fn i16_pred(
5872    fe: &FrameEncoder,
5873    mode: I16Mode,
5874    avail_top: bool,
5875    avail_left: bool,
5876    top: &[u8; 16],
5877    left: &[u8; 16],
5878    corner: u8,
5879    lx: usize,
5880    ly: usize,
5881) -> [u8; 256] {
5882    #[cfg(accel)]
5883    if avail_top && avail_left {
5884        let mode_n = match mode {
5885            I16Mode::Vertical => 0,
5886            I16Mode::Horizontal => 1,
5887            I16Mode::Dc => 2,
5888            I16Mode::Plane => 3,
5889        };
5890        let mut p = AlignedMb([0; 256]);
5891        rusty_h264_accel::i16x16_luma_pred(mode_n, &mut p.0, &fe.rec_y[..], ly * fe.cw + lx, fe.cw);
5892        return p.0;
5893    }
5894    let _ = (fe, lx, ly);
5895    luma16x16_pred(mode, avail_top, avail_left, top, left, corner)
5896}
5897
5898/// 8×8 chroma intra prediction. Interior MBs use openh264's `WelsIChromaPred{V,Plane}_sse2`
5899/// for the V/Plane modes (bit-identical); DC/Horizontal (C-only in openh264) and edge MBs
5900/// use the scalar path.
5901#[inline]
5902#[allow(clippy::too_many_arguments)]
5903fn chroma_pred(
5904    fe: &FrameEncoder,
5905    mode: u8,
5906    avail_top: bool,
5907    avail_left: bool,
5908    c: usize,
5909    top: &[u8; 8],
5910    left: &[u8; 8],
5911    corner: u8,
5912    cx: usize,
5913    cy: usize,
5914) -> [u8; 64] {
5915    #[cfg(accel)]
5916    if avail_top && avail_left && (mode == 2 || mode == 3) {
5917        let plane = if c == 0 { &fe.rec_u } else { &fe.rec_v };
5918        let mut p = AlignedMb([0; 256]);
5919        rusty_h264_accel::chroma8x8_pred(mode, &mut p.0[..64], &plane[..], cy * fe.ccw + cx, fe.ccw);
5920        let mut out = [0u8; 64];
5921        out.copy_from_slice(&p.0[..64]);
5922        return out;
5923    }
5924    let _ = (fe, c, cx, cy);
5925    chroma8x8_pred(mode, avail_top, avail_left, top, left, corner)
5926}
5927
5928/// Predicted `Intra_4x4` mode for the block at absolute coords `(bx, by)` —
5929/// `min` of the left/top neighbor modes, or DC if either is unavailable.
5930fn predict_i4_mode(fe: &FrameEncoder, bx: usize, by: usize) -> u8 {
5931    if bx == 0 || by == 0 {
5932        return 2;
5933    }
5934    let w4 = fe.mb_w * 4;
5935    fe.modes_y[by * w4 + (bx - 1)].min(fe.modes_y[(by - 1) * w4 + bx])
5936}
5937
5938#[allow(clippy::too_many_arguments)]
5939/// Zig-zag scan: block (raster 4×4) index at scan position i.
5940const RDOQ_ZZ: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
5941
5942/// Approximate CABAC bit cost of coding one residual coefficient at magnitude
5943/// `level`: significant_coeff_flag (~1) + coeff_abs_level_minus1 bins (gt1 + UEG0)
5944/// + sign (~1); `level == 0` is significant_coeff_flag = 0 (~1). A coarse model —
5945/// the transform-norm/bin-to-bit scaling is absorbed into the calibrated strength.
5946#[inline]
5947fn rdoq_rate(level: i64) -> f64 {
5948    if level == 0 {
5949        1.0
5950    } else if level == 1 {
5951        3.0 // sig(1) + gt1=0 (1) + sign(1)
5952    } else {
5953        // sig(1) + gt1=1 (1) + UEG0(level-2) prefix (~level-1, capped) + sign(1)
5954        3.0 + (level - 1).min(13) as f64
5955    }
5956}
5957
5958/// Rate-distortion optimized quantization (CABAC trellis, RDOQ) for one 4×4 residual
5959/// block. Refines the hard-decision levels toward min over {|q|, |q|-1} of
5960/// `SSD_coef + λ·R_cabac` per coefficient (coefficient-domain distortion
5961/// `(|coeff| - level·deq_step)²`; `λ = strength·2^((qp-12)/3)`). `strength == 0`
5962/// returns the hard quantization unchanged (the CAVLC path). `first` = 1 skips the
5963/// DC (AC-only categories: I_16x16 AC, chroma AC), else 0.
5964fn rdoq(coeffs: &[i32; 16], qp: u8, dz_div: i64, strength: f64, first: usize) -> [i32; 16] {
5965    let mut q = quantize(coeffs, qp, dz_div);
5966    if strength <= 0.0 {
5967        return q;
5968    }
5969    let lambda = strength * 2f64.powf((qp as f64 - 12.0) / 3.0);
5970    // Distortion is measured in the QUANTIZER-INPUT (forward-transform) domain, where
5971    // level L reconstructs to L·qstep, qstep = 2^16 / MF (the inverse of the forward
5972    // quant scale). The transform norm (forward↔pixel) folds into `strength`.
5973    let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
5974    const POS: [usize; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7];
5975    let dist = |p: usize, level: i64| -> f64 {
5976        let e = coeffs[p].unsigned_abs() as f64 - level as f64 * (65536.0 / mf[POS[p]] as f64);
5977        e * e
5978    };
5979    // Pass 1: per-coefficient level lowering (|q| → |q|-1) minimizing D + λ·R.
5980    for i in first..16 {
5981        let p = RDOQ_ZZ[i];
5982        let m = q[p].unsigned_abs() as i64;
5983        if m == 0 {
5984            continue;
5985        }
5986        let j_keep = dist(p, m) + lambda * rdoq_rate(m);
5987        let j_down = dist(p, m - 1) + lambda * rdoq_rate(m - 1);
5988        if j_down < j_keep {
5989            let nl = (m - 1) as i32;
5990            q[p] = if q[p] < 0 { -nl } else { nl };
5991        }
5992    }
5993    // Pass 2: last-significant-position trimming. Zeroing the trailing significant
5994    // coefficient frees its own bits AND the last_significant flag + every sig=0 flag
5995    // between it and the previous significant coefficient (positions past the new last
5996    // aren't coded at all) — the dominant RDOQ gain on sparse (inter) residuals.
5997    loop {
5998        let Some(li) = (first..16).rev().find(|&i| q[RDOQ_ZZ[i]] != 0) else {
5999            break;
6000        };
6001        let p = RDOQ_ZZ[li];
6002        let m = q[p].unsigned_abs() as i64;
6003        let prev = (first..li).rev().find(|&i| q[RDOQ_ZZ[i]] != 0);
6004        let base = prev.map_or(first, |j| j + 1);
6005        let bits = rdoq_rate(m) + 1.0 + (li - base) as f64; // coeff + last-flag + freed sig=0
6006        let d_add = dist(p, 0) - dist(p, m);
6007        if d_add < lambda * bits {
6008            q[p] = 0;
6009        } else {
6010            break;
6011        }
6012    }
6013    q
6014}
6015
6016/// Decide one intra macroblock (I_16x16 vs I_4x4, prediction modes, chroma),
6017/// forward-transform + quantize, and commit the reconstruction + neighbour mode
6018/// state — everything except entropy coding. The returned [`MbPlan`] is coded by
6019/// either entropy backend, so CAVLC and CABAC share this whole path bit-for-bit.
6020fn plan_mb(
6021    fe: &mut FrameEncoder,
6022    mb_x: usize,
6023    mb_y: usize,
6024    sy: &[u8],
6025    su: &[u8],
6026    sv: &[u8],
6027) -> MbPlan {
6028    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncIntraCode);
6029    let qp = fe.qp;
6030    let qpc = fe.qpc;
6031    // Lagrangian λ for rate-distortion decisions (standard H.264 form).
6032    let lambda = 0.85 * fe.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
6033
6034    // ---------------- luma ----------------
6035    let (lx, ly) = (mb_x * 16, mb_y * 16);
6036    let avail_top = mb_y > 0;
6037    let avail_left = mb_x > 0;
6038    let mut top = [0u8; 16];
6039    let mut left = [0u8; 16];
6040    if avail_top {
6041        for i in 0..16 {
6042            top[i] = fe.rec_y[(ly - 1) * fe.cw + lx + i];
6043        }
6044    }
6045    if avail_left {
6046        for i in 0..16 {
6047            left[i] = fe.rec_y[(ly + i) * fe.cw + lx - 1];
6048        }
6049    }
6050    let corner = if avail_top && avail_left {
6051        fe.rec_y[(ly - 1) * fe.cw + lx - 1]
6052    } else {
6053        0
6054    };
6055
6056    let w4 = fe.mb_w * 4;
6057
6058    // ============ I_16x16 plan (reconstruct into a local buffer) ============
6059    let mut i16_mode = I16Mode::Dc;
6060    let mut best_pred = i16_pred(fe, I16Mode::Dc, avail_top, avail_left, &top, &left, corner, lx, ly);
6061    let mut best_cost = satd_16x16(sy, fe.cw, lx, ly, &best_pred);
6062    for mode in [I16Mode::Vertical, I16Mode::Horizontal, I16Mode::Plane] {
6063        if !mode.available(avail_top, avail_left) {
6064            continue;
6065        }
6066        let pred = i16_pred(fe, mode, avail_top, avail_left, &top, &left, corner, lx, ly);
6067        let cost = satd_16x16(sy, fe.cw, lx, ly, &pred);
6068        if cost < best_cost {
6069            best_cost = cost;
6070            i16_mode = mode;
6071            best_pred = pred;
6072        }
6073    }
6074    // I_16x16 blocks are independent (one fixed whole-MB prediction), so batch the
6075    // forward DCT (`forward_dct_blocks` → SIMD), bit-identical to `forward_core`.
6076    let mut dc4x4 = [0i32; 16];
6077    let mut i16_q = [[0i32; 16]; 16];
6078    // Fast path: forward DCT of (src - pred) straight from the planes per 8x8 quad,
6079    // quantize with the identical FF/MF math (deadzone = fe.idz), recon via the
6080    // bit-identical idct+add+clip kernel — the same pairing encode_inter_mb and the
6081    // P_Skip free-check already use, byte-identical to the scalar twin below.
6082    #[cfg(accel)]
6083    let (i16_dc_levels, _i16_recon_dc, recon16) = {
6084        #[repr(align(16))]
6085        struct A([i16; 256]);
6086        let mut dct = A([0i16; 256]);
6087        let base = ly * fe.cw + lx;
6088        for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
6089            rusty_h264_accel::dct_four_t4(
6090                &mut dct.0[qi * 64..qi * 64 + 64],
6091                &sy[base + qy * fe.cw + qx..],
6092                fe.cw,
6093                &best_pred[qy * 16 + qx..],
6094                16,
6095            );
6096        }
6097        for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
6098            dc4x4[lby * 4 + lbx] = dct.0[blk * 16] as i32;
6099        }
6100        if fe.rdoq_strength > 0.0 {
6101            // Trellis (all-intra only): scalar RDOQ from the asm DCT output instead of
6102            // the asm hard quantizer. dct.0 keeps the raw DCT here; the recon loop below
6103            // overwrites it with the dequantized RDOQ levels.
6104            for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
6105                let coeffs: [i32; 16] = std::array::from_fn(|i| dct.0[blk * 16 + i] as i32);
6106                let mut q = rdoq(&coeffs, qp, fe.idz, fe.rdoq_strength, 1);
6107                q[0] = 0;
6108                i16_q[lby * 4 + lbx] = q;
6109            }
6110        } else {
6111            let ff = rusty_h264_common::transform::quant_dz_ff(qp, fe.idz);
6112            let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
6113            for qi in 0..4 {
6114                rusty_h264_accel::quant_four_4x4(&mut dct.0[qi * 64..qi * 64 + 64], &ff, mf);
6115            }
6116            for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
6117                let q = &mut i16_q[lby * 4 + lbx];
6118                q[0] = 0;
6119                for i in 1..16 {
6120                    q[i] = dct.0[blk * 16 + i] as i32;
6121                }
6122            }
6123        }
6124        let i16_dc_levels = forward_quant_luma_dc(&dc4x4, qp, true);
6125        let i16_recon_dc = inverse_quant_luma_dc(&i16_dc_levels, qp);
6126        // Recon: dequantize (DC injected from the Hadamard) back into quad layout,
6127        // then idct+add-pred+clip into the trial buffer.
6128        let mut recon16 = [0u8; 256];
6129        for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
6130            let mut deq = dequantize(&i16_q[lby * 4 + lbx], qp);
6131            deq[0] = i16_recon_dc[lby * 4 + lbx];
6132            for i in 0..16 {
6133                dct.0[blk * 16 + i] = deq[i] as i16;
6134            }
6135        }
6136        for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
6137            rusty_h264_accel::idct_four_t4_rec(
6138                &mut recon16[qy * 16 + qx..],
6139                16,
6140                &best_pred[qy * 16 + qx..],
6141                16,
6142                &dct.0[qi * 64..qi * 64 + 64],
6143            );
6144        }
6145        (i16_dc_levels, i16_recon_dc, recon16)
6146    };
6147    #[cfg(not(accel))]
6148    let (i16_dc_levels, _i16_recon_dc, recon16) = {
6149        let mut res_blocks = [[0i32; 16]; 16];
6150        for by in 0..4 {
6151            for bx in 0..4 {
6152                let predb = pred_block(&best_pred, bx, by);
6153                res_blocks[by * 4 + bx] = residual(sy, fe.cw, lx + bx * 4, ly + by * 4, &predb);
6154            }
6155        }
6156        let mut coeffs = [[0i32; 16]; 16];
6157        forward_dct_blocks(&res_blocks, &mut coeffs);
6158        for i in 0..16 {
6159            dc4x4[i] = coeffs[i][0];
6160            let mut q = rdoq(&coeffs[i], qp, fe.idz, fe.rdoq_strength, 1);
6161            q[0] = 0;
6162            i16_q[i] = q;
6163        }
6164        let i16_dc_levels = forward_quant_luma_dc(&dc4x4, qp, true);
6165        let i16_recon_dc = inverse_quant_luma_dc(&i16_dc_levels, qp);
6166        let mut recon16 = [0u8; 256];
6167        let mut deq_blocks = [[0i32; 16]; 16];
6168        for i in 0..16 {
6169            deq_blocks[i] = dequantize(&i16_q[i], qp);
6170            deq_blocks[i][0] = i16_recon_dc[i];
6171        }
6172        let mut idct = [[0i32; 16]; 16];
6173        inverse_dct_blocks(&deq_blocks, &mut idct);
6174        for by in 0..4 {
6175            for bx in 0..4 {
6176                let s = add_residual_4x4(&idct[by * 4 + bx], &pred_block(&best_pred, bx, by));
6177                for dy in 0..4 {
6178                    for dx in 0..4 {
6179                        recon16[(by * 4 + dy) * 16 + (bx * 4 + dx)] = s[dy * 4 + dx];
6180                    }
6181                }
6182            }
6183        }
6184        (i16_dc_levels, i16_recon_dc, recon16)
6185    };
6186    let i16_cbp15 = i16_q.iter().any(|b| b[1..].iter().any(|&c| c != 0));
6187    let i16_dc_nz = i16_dc_levels.iter().filter(|&&v| v != 0).count() as i64;
6188    let i16_ac_nz: i64 = i16_q
6189        .iter()
6190        .map(|b| b[1..].iter().filter(|&&v| v != 0).count() as i64)
6191        .sum();
6192    // I_16x16 AC is all-or-nothing: any AC ⇒ all 16 blocks pay a coeff_token.
6193    let i16_rate = i16_dc_nz + i16_ac_nz + if i16_cbp15 { 16 } else { 0 };
6194    // Reconstruction distortion (SSD) for the rate-distortion decision.
6195    let mut ssd16 = 0i64;
6196    for dy in 0..16 {
6197        for dx in 0..16 {
6198            let d = recon16[dy * 16 + dx] as i64 - sy[(ly + dy) * fe.cw + (lx + dx)] as i64;
6199            ssd16 += d * d;
6200        }
6201    }
6202
6203    // ============ chroma (shared by both luma types; commit immediately) ============
6204    let (cx, cy) = (mb_x * 8, mb_y * 8);
6205    // Gather both components' neighbors, then pick a chroma mode by combined SATD.
6206    let mut ntop = [[0u8; 8]; 2];
6207    let mut nleft = [[0u8; 8]; 2];
6208    let mut ncorner = [0u8; 2];
6209    for c in 0..2 {
6210        let rec_c = if c == 0 { &fe.rec_u } else { &fe.rec_v };
6211        if avail_top {
6212            for i in 0..8 {
6213                ntop[c][i] = rec_c[(cy - 1) * fe.ccw + cx + i];
6214            }
6215        }
6216        if avail_left {
6217            for i in 0..8 {
6218                nleft[c][i] = rec_c[(cy + i) * fe.ccw + cx - 1];
6219            }
6220        }
6221        if avail_top && avail_left {
6222            ncorner[c] = rec_c[(cy - 1) * fe.ccw + cx - 1];
6223        }
6224    }
6225    let mut chroma_mode = 0u8;
6226    let mut best_c_cost = i64::MAX;
6227    for m in 0..4u8 {
6228        if !chroma_mode_available(m, avail_top, avail_left) {
6229            continue;
6230        }
6231        let mut cost = 0i64;
6232        for c in 0..2 {
6233            let src = if c == 0 { su } else { sv };
6234            let pred8 = chroma_pred(fe, m, avail_top, avail_left, c, &ntop[c], &nleft[c], ncorner[c], cx, cy);
6235            cost += satd_8x8(src, fe.ccw, cx, cy, &pred8);
6236        }
6237        if cost < best_c_cost {
6238            best_c_cost = cost;
6239            chroma_mode = m;
6240        }
6241    }
6242
6243    let mut c_dc_levels = [[0i32; 4]; 2];
6244    let mut c_q_blocks = [[[0i32; 16]; 4]; 2];
6245    let mut any_chroma_ac = false;
6246    let mut any_chroma_dc = false;
6247    for c in 0..2 {
6248        let src = if c == 0 { su } else { sv };
6249        let pred8 =
6250            chroma_pred(fe, chroma_mode, avail_top, avail_left, c, &ntop[c], &nleft[c], ncorner[c], cx, cy);
6251        let pblk = |bx: usize, by: usize| -> [i32; 16] {
6252            let mut predb = [0i32; 16];
6253            for dy in 0..4 {
6254                for dx in 0..4 {
6255                    predb[dy * 4 + dx] = pred8[(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
6256                }
6257            }
6258            predb
6259        };
6260        // Fast path: forward DCT of (src - pred8) straight from the planes, quantize
6261        // with identical FF/MF (idz deadzone), recon via one idct+add+clip kernel —
6262        // bit-identical to the scalar twin below (proven kernel pairings).
6263        let mut dc2x2 = [0i32; 4];
6264        let mut qbs = [[0i32; 16]; 4];
6265        #[cfg(accel)]
6266        let recon_dc = {
6267            #[repr(align(16))]
6268            struct A([i16; 64]);
6269            let mut d = A([0i16; 64]);
6270            rusty_h264_accel::dct_four_t4(&mut d.0, &src[cy * fe.ccw + cx..], fe.ccw, &pred8, 8);
6271            for i in 0..4 {
6272                dc2x2[i] = d.0[i * 16] as i32;
6273            }
6274            if fe.rdoq_strength > 0.0 {
6275                // Trellis (all-intra only): scalar RDOQ from the asm chroma DCT.
6276                for i in 0..4 {
6277                    let coeffs: [i32; 16] = std::array::from_fn(|j| d.0[i * 16 + j] as i32);
6278                    let mut q = rdoq(&coeffs, qpc, fe.idz, fe.rdoq_strength, 1);
6279                    q[0] = 0;
6280                    if q[1..].iter().any(|&v| v != 0) {
6281                        any_chroma_ac = true;
6282                    }
6283                    qbs[i] = q;
6284                }
6285            } else {
6286                let ff = rusty_h264_common::transform::quant_dz_ff(qpc, fe.idz);
6287                let mf = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
6288                rusty_h264_accel::quant_four_4x4(&mut d.0, &ff, mf);
6289                for i in 0..4 {
6290                    let q = &mut qbs[i];
6291                    q[0] = 0;
6292                    for j in 1..16 {
6293                        let v = d.0[i * 16 + j] as i32;
6294                        q[j] = v;
6295                        if v != 0 {
6296                            any_chroma_ac = true;
6297                        }
6298                    }
6299                }
6300            }
6301            let dl = forward_quant_chroma_dc(&dc2x2, qpc, true);
6302            if dl.iter().any(|&v| v != 0) {
6303                any_chroma_dc = true;
6304            }
6305            let recon_dc = inverse_quant_chroma_dc(&dl, qpc);
6306            for i in 0..4 {
6307                let deq = dequantize(&qbs[i], qpc);
6308                for j in 0..16 {
6309                    d.0[i * 16 + j] = deq[j] as i16;
6310                }
6311                d.0[i * 16] = recon_dc[i] as i16;
6312            }
6313            let plane = if c == 0 { &mut fe.rec_u } else { &mut fe.rec_v };
6314            rusty_h264_accel::idct_four_t4_rec(&mut plane[cy * fe.ccw + cx..], fe.ccw, &pred8, 8, &d.0);
6315            c_dc_levels[c] = dl;
6316            recon_dc
6317        };
6318        #[cfg(not(accel))]
6319        let recon_dc = {
6320            let mut res_blocks = [[0i32; 16]; 4];
6321            for by in 0..2 {
6322                for bx in 0..2 {
6323                    res_blocks[by * 2 + bx] =
6324                        residual(src, fe.ccw, cx + bx * 4, cy + by * 4, &pblk(bx, by));
6325                }
6326            }
6327            let mut coeffs = [[0i32; 16]; 4];
6328            forward_dct_blocks(&res_blocks, &mut coeffs);
6329            for i in 0..4 {
6330                dc2x2[i] = coeffs[i][0];
6331                let mut q = rdoq(&coeffs[i], qpc, fe.idz, fe.rdoq_strength, 1);
6332                q[0] = 0;
6333                qbs[i] = q;
6334                if q[1..].iter().any(|&v| v != 0) {
6335                    any_chroma_ac = true;
6336                }
6337            }
6338            let dl = forward_quant_chroma_dc(&dc2x2, qpc, true);
6339            if dl.iter().any(|&v| v != 0) {
6340                any_chroma_dc = true;
6341            }
6342            let recon_dc = inverse_quant_chroma_dc(&dl, qpc);
6343            let mut deq_blocks = [[0i32; 16]; 4];
6344            for i in 0..4 {
6345                deq_blocks[i] = dequantize(&qbs[i], qpc);
6346                deq_blocks[i][0] = recon_dc[i];
6347            }
6348            let mut idct = [[0i32; 16]; 4];
6349            inverse_dct_blocks(&deq_blocks, &mut idct);
6350            let plane = if c == 0 { &mut fe.rec_u } else { &mut fe.rec_v };
6351            for by in 0..2 {
6352                for bx in 0..2 {
6353                    let s = add_residual_4x4(&idct[by * 2 + bx], &pblk(bx, by));
6354                    store(plane, fe.ccw, cx + bx * 4, cy + by * 4, &s);
6355                }
6356            }
6357            c_dc_levels[c] = dl;
6358            recon_dc
6359        };
6360        let _ = recon_dc;
6361        c_q_blocks[c] = qbs;
6362    }
6363    let cbp_chroma: u32 = if any_chroma_ac {
6364        2
6365    } else if any_chroma_dc {
6366        1
6367    } else {
6368        0
6369    };
6370
6371    // ============ I_NxN plan + RD: I_16x16 vs I_4x4 vs (High profile) I_8x8 ============
6372    // I_4x4 and I_8x8 both reconstruct serially into rec_y, but each block predicts
6373    // only from NEIGHBOURS + earlier blocks it fills itself — never the stale MB
6374    // content — so running I_8x8 after I_4x4 needs no restore. J = SSD + λ·R picks the
6375    // per-MB transform (the content-adaptive win: 8x8 on smooth, 4x4 on detail).
6376    let base = ly * fe.cw + lx;
6377    let i4 = if i16_rate > 2 {
6378        Some(plan_i4x4(fe, sy, mb_x, mb_y, qp))
6379    } else {
6380        None
6381    };
6382    let (j4, i4_recon) = match &i4 {
6383        Some(p) => {
6384            let mut ssd = 0i64;
6385            let mut rec = [0u8; 256];
6386            for i in 0..256 {
6387                let v = fe.rec_y[base + (i / 16) * fe.cw + i % 16];
6388                rec[i] = v;
6389                let d = v as i64 - sy[base + (i / 16) * fe.cw + i % 16] as i64;
6390                ssd += d * d;
6391            }
6392            (ssd as f64 + lambda * (p.nonzero + 16) as f64, Some(rec))
6393        }
6394        None => (f64::INFINITY, None),
6395    };
6396    let i8 = if fe.transform_8x8 {
6397        Some(plan_i8x8(fe, sy, mb_x, mb_y, qp))
6398    } else {
6399        None
6400    };
6401    let j8 = match &i8 {
6402        Some(p) => {
6403            let mut ssd = 0i64;
6404            for i in 0..256 {
6405                let d = fe.rec_y[base + (i / 16) * fe.cw + i % 16] as i64
6406                    - sy[base + (i / 16) * fe.cw + i % 16] as i64;
6407                ssd += d * d;
6408            }
6409            ssd as f64 + lambda * (p.nonzero + 16) as f64
6410        }
6411        None => f64::INFINITY,
6412    };
6413    let j16 = ssd16 as f64 + lambda * i16_rate as f64;
6414
6415    // ============ commit the RD winner's reconstruction + neighbour modes ============
6416    let (use_i4, i4, i8) = if i8.is_some() && j8 <= j4 && j8 <= j16 {
6417        // I_8x8: plan_i8x8 already committed rec_y AND modes_y (per 8x8 block).
6418        (true, None, i8)
6419    } else if i4.is_some() && j4 < j16 {
6420        // I_4x4: restore its reconstruction (I_8x8 may have overwritten rec_y), publish modes.
6421        let rec = i4_recon.unwrap();
6422        for i in 0..256 {
6423            fe.rec_y[base + (i / 16) * fe.cw + i % 16] = rec[i];
6424        }
6425        let modes = i4.as_ref().unwrap().modes;
6426        for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
6427            fe.modes_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = modes[lby * 4 + lbx];
6428        }
6429        (true, i4, None)
6430    } else {
6431        // I_16x16: commit its reconstruction, mark modes DC.
6432        for by in 0..4 {
6433            for bx in 0..4 {
6434                for dy in 0..4 {
6435                    for dx in 0..4 {
6436                        fe.rec_y[(ly + by * 4 + dy) * fe.cw + (lx + bx * 4 + dx)] =
6437                            recon16[(by * 4 + dy) * 16 + (bx * 4 + dx)];
6438                    }
6439                }
6440            }
6441        }
6442        for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
6443            fe.modes_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 2;
6444        }
6445        (false, None, None)
6446    };
6447    // Mark all luma blocks coded for the next macroblock's top-right availability.
6448    for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
6449        fe.coded_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = true;
6450    }
6451
6452    MbPlan {
6453        use_i4,
6454        i16_mode,
6455        i16_cbp15,
6456        i16_dc_levels,
6457        i16_q,
6458        i4,
6459        i8,
6460        chroma_mode,
6461        cbp_chroma,
6462        c_dc_levels,
6463        c_q_blocks,
6464    }
6465}
6466
6467/// Emit one planned intra macroblock as CAVLC (the original `encode_mb` tail). Reads
6468/// only the decided values from `plan`; `plan_mb` already committed recon + modes.
6469fn encode_mb(
6470    fe: &mut FrameEncoder,
6471    w: &mut BitWriter,
6472    mb_x: usize,
6473    mb_y: usize,
6474    sy: &[u8],
6475    su: &[u8],
6476    sv: &[u8],
6477    is_p: bool,
6478) {
6479    let plan = plan_mb(fe, mb_x, mb_y, sy, su, sv);
6480    // In a P-slice, intra macroblock types are offset by 5 (0..4 are inter).
6481    let mb_type_offset = if is_p { 5 } else { 0 };
6482    let w4 = fe.mb_w * 4;
6483    let cbp_chroma = plan.cbp_chroma;
6484
6485    // ============ emit luma ============
6486    if let Some(i8) = plan.i8.as_ref().filter(|_| plan.use_i4) {
6487        // ---- I_8x8 (High profile): mb_type = I_NxN, transform_size_8x8_flag = 1, then
6488        // one intra8x8 mode per 8x8 block, cbp, mb_qp_delta, and the 8x8 residual as
6489        // four interleaved 4x4 CAVLC sub-blocks (coeff k of sub s -> 8x8 scan 4k+s). ----
6490        let cbp = i8.cbp_luma | (cbp_chroma << 4);
6491        w.write_ue(mb_type_offset); // mb_type = I_NxN
6492        w.write_bit(true); // transform_size_8x8_flag = 1
6493        for b8 in 0..4usize {
6494            let (bx, by) = (mb_x * 4 + (b8 % 2) * 2, mb_y * 4 + (b8 / 2) * 2);
6495            let predicted = predict_i4_mode(fe, bx, by);
6496            let actual = i8.modes[b8];
6497            if actual == predicted {
6498                w.write_bit(true);
6499            } else {
6500                w.write_bit(false);
6501                let rem = if actual < predicted { actual } else { actual - 1 };
6502                w.write_bits(rem as u32, 3);
6503            }
6504        }
6505        w.write_ue(plan.chroma_mode as u32); // intra_chroma_pred_mode
6506        write_cbp_intra(w, cbp);
6507        if cbp != 0 {
6508            w.write_se(fe.qp_delta());
6509        }
6510        fe.nnz_cache_load(mb_x, mb_y);
6511        for b8 in 0..4usize {
6512            let (b8x, b8y) = (b8 % 2, b8 / 2);
6513            let scan8 = scan_8x8_fwd(&i8.q[b8]);
6514            for sub in 0..4usize {
6515                let (cx, cy) = (b8x * 2 + sub % 2, b8y * 2 + sub / 2);
6516                let (bx, by) = (mb_x * 4 + cx, mb_y * 4 + cy);
6517                let total = if i8.cbp_luma & (1 << b8) != 0 {
6518                    let nc = fe.nc_pred(cx, cy);
6519                    let blk: [i32; 16] = std::array::from_fn(|k| scan8[4 * k + sub]);
6520                    encode_residual_block(w, &blk, 16, nc) as u8
6521                } else {
6522                    0
6523                };
6524                fe.nnz_cache_set(cx, cy, total);
6525                fe.nnz_y[by * w4 + bx] = total;
6526            }
6527        }
6528    } else if plan.use_i4 {
6529        let i4 = plan.i4.as_ref().unwrap();
6530        let cbp = i4.cbp_luma | (cbp_chroma << 4);
6531        w.write_ue(mb_type_offset); // mb_type = I_4x4 (+5 in P-slices)
6532        if fe.transform_8x8 {
6533            w.write_bit(false); // transform_size_8x8_flag = 0 (this I_NxN is 4x4)
6534        }
6535        for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
6536            let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
6537            let predicted = predict_i4_mode(fe, bx, by);
6538            let actual = i4.modes[lby * 4 + lbx];
6539            if actual == predicted {
6540                w.write_bit(true);
6541            } else {
6542                w.write_bit(false);
6543                let rem = if actual < predicted { actual } else { actual - 1 };
6544                w.write_bits(rem as u32, 3);
6545            }
6546        }
6547        w.write_ue(plan.chroma_mode as u32); // intra_chroma_pred_mode
6548        write_cbp_intra(w, cbp);
6549        if cbp != 0 {
6550            w.write_se(fe.qp_delta()); // mb_qp_delta (AQ per-MB QPy)
6551        }
6552        fe.nnz_cache_load(mb_x, mb_y);
6553        for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
6554            let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
6555            let total = if i4.cbp_luma & (1 << (blk / 4)) != 0 {
6556                let nc = fe.nc_pred(lbx, lby);
6557                let scan16 = scan_4x4_dcac(&i4.q[lby * 4 + lbx]);
6558                encode_residual_block(w, &scan16, 16, nc) as u8
6559            } else {
6560                0
6561            };
6562            fe.nnz_cache_set(lbx, lby, total);
6563            fe.nnz_y[by * w4 + bx] = total;
6564        }
6565    } else {
6566        let mb_type = 1 + plan.i16_mode as u32 + 4 * cbp_chroma + if plan.i16_cbp15 { 12 } else { 0 };
6567        w.write_ue(mb_type + mb_type_offset);
6568        w.write_ue(plan.chroma_mode as u32); // intra_chroma_pred_mode
6569        w.write_se(fe.qp_delta()); // mb_qp_delta (I_16x16 always codes it; AQ per-MB QPy)
6570        fe.nnz_cache_load(mb_x, mb_y);
6571        let nc_dc = fe.nc_pred(0, 0);
6572        let dc_scan = scan_4x4_dcac(&plan.i16_dc_levels);
6573        encode_residual_block(w, &dc_scan, 16, nc_dc);
6574        for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
6575            fe.nnz_cache_set(lbx, lby, 0);
6576            fe.nnz_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 0;
6577        }
6578        if plan.i16_cbp15 {
6579            for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
6580                let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
6581                let nc = fe.nc_pred(lbx, lby);
6582                let ac = scan_4x4_ac(&plan.i16_q[lby * 4 + lbx]);
6583                let total = encode_residual_block(w, &ac, 15, nc) as u8;
6584                fe.nnz_cache_set(lbx, lby, total);
6585                fe.nnz_y[by * w4 + bx] = total;
6586            }
6587        }
6588    }
6589
6590    // ============ emit chroma residual (shared) ============
6591    if cbp_chroma != 0 {
6592        for c in 0..2 {
6593            encode_residual_block(w, &plan.c_dc_levels[c], 4, -1);
6594        }
6595    }
6596    if cbp_chroma == 2 {
6597        fe.chroma_cache_load(mb_x, mb_y);
6598        let w2 = fe.mb_w * 2;
6599        for c in 0..2 {
6600            for &(bx, by) in &CHROMA_4X4_SCAN_XY {
6601                let nc = fe.chroma_nc_pred(c, bx, by);
6602                let ac = scan_4x4_ac(&plan.c_q_blocks[c][by * 2 + bx]);
6603                let total = encode_residual_block(w, &ac, 15, nc) as u8;
6604                fe.chroma_nnz_cache_set(c, bx, by, total);
6605                fe.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total;
6606            }
6607        }
6608    }
6609}
6610
6611// ============================================================================
6612// CABAC I-slice entropy coding — the exact forward inverse of the decoder's
6613// `decode_slice_data_cabac` I-slice path (rusty_h264-decoder mb16.rs). Every
6614// binarization + context-selection here mirrors a `parse_*_cabac` there; the
6615// neighbour state (nzc cache, cbf_dc, cat, cmode, mb_cbp, last_delta_qp) is
6616// reconstructed identically so the contexts evolve bit-for-bit. Reuses `plan_mb`
6617// for the entire mode-decision/transform/recon (shared with CAVLC).
6618// ============================================================================
6619
6620// --- res-property tables (must match the decoder's mb16.rs g_kBlockCat2CtxOffset*) ---
6621const CB_NZC_CACHE: [usize; 24] = [
6622    9, 10, 17, 18, 11, 12, 19, 20, 25, 26, 33, 34, 27, 28, 35, 36, // luma
6623    14, 15, 22, 23, // Cb
6624    38, 39, 46, 47, // Cr
6625];
6626const CB_RES_MAXPOS: [i32; 11] = [0, 15, 14, 15, 3, 14, 63, 3, 3, 14, 14];
6627const CB_RES_MAXC2: [i32; 11] = [0, 4, 4, 4, 3, 4, 4, 3, 3, 4, 4];
6628const CB_RES_CBF: [usize; 11] = [0, 0, 4, 8, 12, 16, 0, 12, 12, 16, 16];
6629const CB_RES_MAP: [usize; 11] = [0, 0, 15, 29, 44, 47, 0, 44, 44, 47, 47];
6630const CB_RES_ONE: [usize; 11] = [0, 0, 10, 20, 30, 39, 0, 30, 30, 39, 39];
6631const CB_RP_I16_DC: usize = 1;
6632const CB_RP_I16_AC: usize = 2;
6633const CB_RP_LUMA_4X4: usize = 3;
6634const CB_RP_CHROMA_DC: usize = 7;
6635const CB_RP_CHROMA_AC: usize = 9;
6636
6637/// Inverse of `cabac_unary(ctx, off)`: bin0 at `ctx`; for value >= 1, `value-1` ones
6638/// then a terminating 0, all at `ctx+off`.
6639fn cb_unary(cab: &mut CabacEncoder, ctx: usize, off: usize, value: u32) {
6640    if value == 0 {
6641        cab.encode_decision(ctx, 0);
6642        return;
6643    }
6644    cab.encode_decision(ctx, 1);
6645    for _ in 0..value - 1 {
6646        cab.encode_decision(ctx + off, 1);
6647    }
6648    cab.encode_decision(ctx + off, 0);
6649}
6650
6651/// Exp-Golomb order-`k` in bypass — inverse of `cabac_exp_bypass(k)`.
6652fn cb_exp_bypass(cab: &mut CabacEncoder, mut k: i32, mut n: u32) {
6653    while n >= (1 << k) {
6654        cab.encode_bypass(1);
6655        n -= 1 << k;
6656        k += 1;
6657    }
6658    cab.encode_bypass(0);
6659    while k > 0 {
6660        k -= 1;
6661        cab.encode_bypass((n >> k) & 1);
6662    }
6663}
6664
6665/// UEG0 coeff-level suffix — inverse of `cabac_ueg_level(ctx)` (TU prefix <=13 at
6666/// `ctx`, then an EG0 bypass suffix).
6667fn cb_ueg_level(cab: &mut CabacEncoder, ctx: usize, value: u32) {
6668    if value == 0 {
6669        cab.encode_decision(ctx, 0);
6670        return;
6671    }
6672    let ones = value.min(13);
6673    for _ in 0..ones {
6674        cab.encode_decision(ctx, 1);
6675    }
6676    if value < 13 {
6677        cab.encode_decision(ctx, 0);
6678    } else {
6679        cb_exp_bypass(cab, 0, value - 13);
6680    }
6681}
6682
6683/// `mb_qp_delta` — inverse of `parse_mb_qp_delta_cabac` (ctxIdxOffset 60).
6684fn cb_mb_qp_delta(cab: &mut CabacEncoder, last_delta_qp: &mut i32, delta: i32) {
6685    const O: usize = 60;
6686    let ctx_inc = (*last_delta_qp != 0) as usize;
6687    if delta == 0 {
6688        cab.encode_decision(O + ctx_inc, 0);
6689    } else {
6690        cab.encode_decision(O + ctx_inc, 1);
6691        // code = 2|d| - (d>0); the decode's cabac_unary sees code-1.
6692        let code = 2 * delta.unsigned_abs() - (delta > 0) as u32;
6693        cb_unary(cab, O + 2, 1, code - 1);
6694    }
6695    *last_delta_qp = delta;
6696}
6697
6698/// `intra_chroma_pred_mode` (TU cMax=3) — inverse of `parse_intra_chroma_pred_mode_cabac`.
6699fn cb_chroma_pred_mode(cab: &mut CabacEncoder, ctx_inc: usize, mode: u8) {
6700    const C: usize = 64;
6701    if mode == 0 {
6702        cab.encode_decision(C + ctx_inc, 0);
6703        return;
6704    }
6705    cab.encode_decision(C + ctx_inc, 1);
6706    if mode == 1 {
6707        cab.encode_decision(C + 3, 0);
6708    } else if mode == 2 {
6709        cab.encode_decision(C + 3, 1);
6710        cab.encode_decision(C + 3, 0);
6711    } else {
6712        cab.encode_decision(C + 3, 1);
6713        cab.encode_decision(C + 3, 1);
6714    }
6715}
6716
6717/// I-slice `mb_type` — inverse of `parse_mb_type_i_cabac` (ctxIdxOffset 3).
6718fn cb_mb_type_i(
6719    cab: &mut CabacEncoder,
6720    ctx_inc: usize,
6721    use_i4: bool,
6722    i16_mode: u32,
6723    cbp_chroma: u32,
6724    cbp_luma15: bool,
6725) {
6726    const O: usize = 3;
6727    if use_i4 {
6728        cab.encode_decision(O + ctx_inc, 0); // I_NxN
6729        return;
6730    }
6731    cab.encode_decision(O + ctx_inc, 1);
6732    cab.encode_terminate(false); // not I_PCM
6733    cab.encode_decision(O + 3, cbp_luma15 as u32);
6734    if cbp_chroma != 0 {
6735        cab.encode_decision(O + 4, 1);
6736        cab.encode_decision(O + 5, (cbp_chroma == 2) as u32);
6737    } else {
6738        cab.encode_decision(O + 4, 0);
6739    }
6740    cab.encode_decision(O + 6, (i16_mode >> 1) & 1);
6741    cab.encode_decision(O + 7, i16_mode & 1);
6742}
6743
6744/// One `Intra_4x4` pred-mode — inverse of `parse_intra4x4_pred_mode_cabac` (ctx 68).
6745fn cb_intra4x4_pred_mode(cab: &mut CabacEncoder, predicted: u8, actual: u8) {
6746    const IPR: usize = 68;
6747    if actual == predicted {
6748        cab.encode_decision(IPR, 1);
6749    } else {
6750        cab.encode_decision(IPR, 0);
6751        let rem = if actual < predicted { actual } else { actual - 1 } as u32;
6752        cab.encode_decision(IPR + 1, rem & 1);
6753        cab.encode_decision(IPR + 1, (rem >> 1) & 1);
6754        cab.encode_decision(IPR + 1, (rem >> 2) & 1);
6755    }
6756}
6757
6758/// `coded_block_pattern` — inverse of `parse_cbp_cabac` (ctxIdxOffset 73).
6759fn cb_cbp(cab: &mut CabacEncoder, top: Option<u8>, left: Option<u8>, cbp: u32) {
6760    const CBP: usize = 73;
6761    let t = |m: u32| top.map_or(0u32, |c| ((c as u32 & m) == 0) as u32);
6762    let l = |m: u32| left.map_or(0u32, |c| ((c as u32 & m) == 0) as u32);
6763    let nb = |x: u32| (x == 0) as u32;
6764    let b0 = cbp & 1;
6765    let b1 = (cbp >> 1) & 1;
6766    let b2 = (cbp >> 2) & 1;
6767    let b3 = (cbp >> 3) & 1;
6768    cab.encode_decision(CBP + (l(1 << 1) + (t(1 << 2) << 1)) as usize, b0);
6769    cab.encode_decision(CBP + (nb(b0) + (t(1 << 3) << 1)) as usize, b1);
6770    cab.encode_decision(CBP + (l(1 << 3) + (nb(b0) << 1)) as usize, b2);
6771    cab.encode_decision(CBP + (nb(b2) + (nb(b1) << 1)) as usize, b3);
6772    let cbp_chroma = cbp >> 4;
6773    let ct = top.map_or(0u32, |c| ((c >> 4) != 0) as u32);
6774    let cl = left.map_or(0u32, |c| ((c >> 4) != 0) as u32);
6775    cab.encode_decision(CBP + 4 + (cl + (ct << 1)) as usize, (cbp_chroma != 0) as u32);
6776    if cbp_chroma != 0 {
6777        let ct2 = top.map_or(0u32, |c| ((c >> 4) == 2) as u32);
6778        let cl2 = left.map_or(0u32, |c| ((c >> 4) == 2) as u32);
6779        cab.encode_decision(CBP + 8 + (cl2 + (ct2 << 1)) as usize, (cbp_chroma == 2) as u32);
6780    }
6781}
6782
6783/// One residual block — inverse of `parse_residual_cabac`. `coeffs` is scan-order
6784/// (len >= maxPos+1). Returns totalCoeffNum (for the nzc cache + deblock nnz).
6785#[allow(clippy::too_many_arguments)]
6786fn cb_residual(
6787    cab: &mut CabacEncoder,
6788    nzc: &mut [u8; 48],
6789    cbf_dc: &mut u16,
6790    iz: usize,
6791    rp: usize,
6792    is_intra: bool,
6793    ndc: (Option<u16>, Option<u16>),
6794    coeffs: &[i32],
6795) -> u32 {
6796    let is_dc = rp == CB_RP_I16_DC || rp == CB_RP_CHROMA_DC || rp == CB_RP_CHROMA_DC + 1;
6797    let (mut na, mut nb) = (is_intra as u8, is_intra as u8);
6798    let scan = CB_NZC_CACHE[iz.min(23)];
6799    if is_dc {
6800        if let Some(t) = ndc.0 {
6801            nb = ((t >> rp) & 1) as u8;
6802        }
6803        if let Some(l) = ndc.1 {
6804            na = ((l >> rp) & 1) as u8;
6805        }
6806    } else {
6807        if nzc[scan - 8] != 0xff {
6808            nb = (nzc[scan - 8] != 0) as u8;
6809        }
6810        if nzc[scan - 1] != 0xff {
6811            na = (nzc[scan - 1] != 0) as u8;
6812        }
6813    }
6814    let maxpos = CB_RES_MAXPOS[rp] as usize;
6815    let coeff_num = coeffs[..=maxpos].iter().filter(|&&c| c != 0).count() as u32;
6816    let cbf = coeff_num != 0;
6817    cab.encode_decision(85 + CB_RES_CBF[rp] + (na + (nb << 1)) as usize, cbf as u32);
6818    if !cbf {
6819        if !is_dc {
6820            nzc[scan] = 0;
6821        }
6822        return 0;
6823    }
6824    if is_dc {
6825        *cbf_dc |= 1 << rp;
6826    }
6827    // significance map
6828    let map = 105 + CB_RES_MAP[rp];
6829    let last = 166 + CB_RES_MAP[rp];
6830    let lastnz = (0..=maxpos).rev().find(|&i| coeffs[i] != 0).unwrap();
6831    for i in 0..maxpos {
6832        let s = coeffs[i] != 0;
6833        cab.encode_decision(map + i, s as u32);
6834        if s {
6835            let is_last = i == lastnz;
6836            cab.encode_decision(last + i, is_last as u32);
6837            if is_last {
6838                break;
6839            }
6840        }
6841    }
6842    // levels (reverse scan)
6843    let one = 227 + CB_RES_ONE[rp];
6844    let abs = 232 + CB_RES_ONE[rp];
6845    let maxc2 = CB_RES_MAXC2[rp];
6846    let (mut c1, mut c2) = (1i32, 0i32);
6847    for i in (0..=maxpos).rev() {
6848        if coeffs[i] != 0 {
6849            let av = coeffs[i].unsigned_abs();
6850            let gt1 = av > 1;
6851            cab.encode_decision(one + c1 as usize, gt1 as u32);
6852            if gt1 {
6853                cb_ueg_level(cab, abs + c2 as usize, av - 2);
6854                c2 = (c2 + 1).min(maxc2);
6855                c1 = 0;
6856            } else if c1 != 0 {
6857                c1 = (c1 + 1).min(4);
6858            }
6859            cab.encode_bypass((coeffs[i] < 0) as u32);
6860        }
6861    }
6862    if !is_dc {
6863        nzc[scan] = coeff_num as u8;
6864    }
6865    coeff_num
6866}
6867
6868/// Build the 48-entry padded nzc cache from the top/left neighbour MB exports
6869/// (openh264 `WelsFillCacheNonZeroCount`) — identical to the decoder.
6870fn cb_build_nzc(mb_nzc: &[[u8; 24]], top: Option<usize>, left: Option<usize>) -> [u8; 48] {
6871    let mut nzc = [0xffu8; 48];
6872    if let Some(t) = top {
6873        let tn = mb_nzc[t];
6874        nzc[1..5].copy_from_slice(&tn[12..16]);
6875        (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
6876        (nzc[6], nzc[7]) = (tn[20], tn[21]);
6877        (nzc[30], nzc[31]) = (tn[22], tn[23]);
6878    }
6879    if let Some(l) = left {
6880        let ln = mb_nzc[l];
6881        (nzc[8], nzc[16], nzc[24], nzc[32]) = (ln[3], ln[7], ln[11], ln[15]);
6882        (nzc[13], nzc[21], nzc[37], nzc[45]) = (ln[17], ln[21], ln[19], ln[23]);
6883    }
6884    nzc
6885}
6886
6887/// Extract the 24-entry per-MB nzc (raster luma + chroma) for future neighbours.
6888fn cb_export_nzc(nzc: &[u8; 48]) -> [u8; 24] {
6889    let mut mn = [0u8; 24];
6890    for k in 0..4 {
6891        mn[k] = nzc[9 + k];
6892        mn[4 + k] = nzc[17 + k];
6893        mn[8 + k] = nzc[25 + k];
6894        mn[12 + k] = nzc[33 + k];
6895    }
6896    (mn[16], mn[17], mn[20], mn[21]) = (nzc[14], nzc[15], nzc[22], nzc[23]);
6897    (mn[18], mn[19], mn[22], mn[23]) = (nzc[38], nzc[39], nzc[46], nzc[47]);
6898    for v in mn.iter_mut() {
6899        if *v == 0xff {
6900            *v = 0;
6901        }
6902    }
6903    mn
6904}
6905
6906/// Per-frame CABAC neighbour state (I-slice): one entry per macroblock, mirroring
6907/// the arrays the decoder's `decode_slice_data_cabac` maintains.
6908struct CabacState {
6909    cat: Vec<u8>,          // 2 = I_16x16, 0 = I_NxN, 100 = inter (mb_type / skip ctxInc)
6910    cmode: Vec<i32>,       // per-MB chroma mode (chroma-pred ctxInc)
6911    mb_cbp: Vec<u8>,       // per-MB cbp byte (cbp ctxInc)
6912    cbf_dc: Vec<u16>,      // per-MB DC coded_block_flag mask (residual DC ctxInc)
6913    mb_nzc: Vec<[u8; 24]>, // per-MB nzc export (residual AC ctxInc)
6914    // Inter (P/B) neighbour state — mirrors the decoder's WelsFillCacheInterCabac.
6915    mb_mvd: Vec<[[i16; 2]; 16]>,  // per-MB per-4x4 List-0 mvd (raster), for the mvd ctxInc cache
6916    mb_ref: Vec<[i8; 16]>,        // per-MB per-4x4 List-0 ref idx (raster); -1 = unavailable
6917    mb_mvd1: Vec<[[i16; 2]; 16]>, // B: per-MB per-4x4 List-1 mvd
6918    mb_ref1: Vec<[i8; 16]>,       // B: per-MB per-4x4 List-1 ref idx
6919    mb_skip: Vec<bool>,           // per-MB mb_skip_flag (skip ctxInc)
6920    mb_direct: Vec<bool>,         // B: per-MB B_Direct/B_Skip (B mb_type ctxInc)
6921    last_delta_qp: i32,
6922}
6923
6924impl CabacState {
6925    fn new(n: usize) -> Self {
6926        CabacState {
6927            cat: vec![0; n],
6928            cmode: vec![0; n],
6929            mb_cbp: vec![0; n],
6930            cbf_dc: vec![0; n],
6931            mb_nzc: vec![[0u8; 24]; n],
6932            mb_mvd: vec![[[0i16; 2]; 16]; n],
6933            mb_ref: vec![[-1i8; 16]; n],
6934            mb_mvd1: vec![[[0i16; 2]; 16]; n],
6935            mb_ref1: vec![[-1i8; 16]; n],
6936            mb_skip: vec![false; n],
6937            mb_direct: vec![false; n],
6938            last_delta_qp: 0,
6939        }
6940    }
6941}
6942
6943/// Emit one planned intra macroblock as CABAC (I-slice). Mirrors the decoder's
6944/// I-slice MB body exactly: `mb_type`, then per luma-type the intra modes / cbp /
6945/// `mb_qp_delta` / residual in spec order, maintaining `cs` and `fe.nnz_y`.
6946fn emit_mb_cabac_i(
6947    fe: &mut FrameEncoder,
6948    cab: &mut CabacEncoder,
6949    cs: &mut CabacState,
6950    plan: &MbPlan,
6951    mb_x: usize,
6952    mb_y: usize,
6953) {
6954    let mb_w = fe.mb_w;
6955    let addr = mb_y * mb_w + mb_x;
6956    let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
6957    let left = if mb_x > 0 { Some(addr - 1) } else { None };
6958
6959    // ---- mb_type (I-slice prefix; carries I_16x16 pred-mode/cbp) ----
6960    let li = left.map_or(0, |a| (cs.cat[a] >= 2) as usize);
6961    let ti = top.map_or(0, |a| (cs.cat[a] >= 2) as usize);
6962    let acct = crate::bitacct::enabled();
6963    let t0 = if acct { cab.pos() } else { 0 };
6964    if plan.use_i4 {
6965        cb_mb_type_i(cab, li + ti, true, 0, 0, false);
6966    } else {
6967        cb_mb_type_i(cab, li + ti, false, plan.i16_mode as u32, plan.cbp_chroma, plan.i16_cbp15);
6968    }
6969    if acct {
6970        crate::bitacct::add(crate::bitacct::B::MbType, cab.pos() - t0);
6971    }
6972    let t1 = if acct { cab.pos() } else { 0 };
6973    emit_intra_body_cabac(fe, cab, cs, plan, mb_x, mb_y, addr, top, left);
6974    if acct {
6975        crate::bitacct::add(crate::bitacct::B::IntraBody, cab.pos() - t1);
6976    }
6977}
6978
6979/// The intra macroblock body (chroma pred mode, intra modes, cbp, mb_qp_delta,
6980/// residual) shared by I-slice intra and P/B-slice intra — everything AFTER the
6981/// slice-specific `mb_type` prefix (which already carries the I_16x16 pred-mode/cbp).
6982#[allow(clippy::too_many_arguments)]
6983fn emit_intra_body_cabac(
6984    fe: &mut FrameEncoder,
6985    cab: &mut CabacEncoder,
6986    cs: &mut CabacState,
6987    plan: &MbPlan,
6988    mb_x: usize,
6989    mb_y: usize,
6990    addr: usize,
6991    top: Option<usize>,
6992    left: Option<usize>,
6993) {
6994    let w4 = fe.mb_w * 4;
6995    let cbp_chroma = plan.cbp_chroma;
6996    // chroma-pred-mode ctxInc from neighbour chroma modes (1..=3).
6997    let cci = left.map_or(0, |a| (1..=3).contains(&cs.cmode[a]) as usize)
6998        + top.map_or(0, |a| (1..=3).contains(&cs.cmode[a]) as usize);
6999
7000    let mut nzc;
7001    let mut cbfdc = 0u16;
7002    let ndc = (top.map(|a| cs.cbf_dc[a]), left.map(|a| cs.cbf_dc[a]));
7003
7004    if !plan.use_i4 {
7005        // ---- I_16x16 ----
7006        cb_chroma_pred_mode(cab, cci, plan.chroma_mode);
7007        cs.cmode[addr] = plan.chroma_mode as i32;
7008        cs.cat[addr] = 2;
7009        cs.mb_cbp[addr] = ((cbp_chroma as u8) << 4) | if plan.i16_cbp15 { 15 } else { 0 };
7010        nzc = cb_build_nzc(&cs.mb_nzc, top, left);
7011
7012        let delta = fe.qp_delta();
7013        cb_mb_qp_delta(cab, &mut cs.last_delta_qp, delta);
7014
7015        // luma DC
7016        let dc_scan = scan_4x4_dcac(&plan.i16_dc_levels);
7017        cb_residual(cab, &mut nzc, &mut cbfdc, 0, CB_RP_I16_DC, true, ndc, &dc_scan);
7018        // luma AC
7019        for (iz, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
7020            let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
7021            let total = if plan.i16_cbp15 {
7022                let ac = scan_4x4_ac(&plan.i16_q[lby * 4 + lbx]);
7023                cb_residual(cab, &mut nzc, &mut cbfdc, iz, CB_RP_I16_AC, true, ndc, &ac)
7024            } else {
7025                nzc[CB_NZC_CACHE[iz]] = 0;
7026                0
7027            };
7028            fe.nnz_y[by * w4 + bx] = total as u8;
7029        }
7030        cb_emit_chroma_residual(cab, fe, &mut nzc, &mut cbfdc, ndc, true, plan.cbp_chroma, &plan.c_dc_levels, &plan.c_q_blocks, mb_x, mb_y);
7031    } else {
7032        // ---- I_NxN (I_4x4) ----
7033        let i4 = plan.i4.as_ref().unwrap();
7034        for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
7035            let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
7036            let predicted = predict_i4_mode(fe, bx, by);
7037            cb_intra4x4_pred_mode(cab, predicted, i4.modes[lby * 4 + lbx]);
7038        }
7039        cb_chroma_pred_mode(cab, cci, plan.chroma_mode);
7040        cs.cmode[addr] = plan.chroma_mode as i32;
7041        cs.cat[addr] = 0;
7042        let cbp = i4.cbp_luma | (cbp_chroma << 4);
7043        cb_cbp(cab, top.map(|a| cs.mb_cbp[a]), left.map(|a| cs.mb_cbp[a]), cbp);
7044        cs.mb_cbp[addr] = cbp as u8;
7045        nzc = cb_build_nzc(&cs.mb_nzc, top, left);
7046
7047        if cbp == 0 {
7048            cs.last_delta_qp = 0;
7049            for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
7050                fe.nnz_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 0;
7051            }
7052        } else {
7053            let delta = fe.qp_delta();
7054            cb_mb_qp_delta(cab, &mut cs.last_delta_qp, delta);
7055            for id8 in 0..4usize {
7056                for id4 in 0..4usize {
7057                    let iz = id8 * 4 + id4;
7058                    let (lbx, lby) = LUMA_4X4_SCAN_XY[iz];
7059                    let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
7060                    let total = if i4.cbp_luma & (1 << id8) != 0 {
7061                        let sc = scan_4x4_dcac(&i4.q[lby * 4 + lbx]);
7062                        cb_residual(cab, &mut nzc, &mut cbfdc, iz, CB_RP_LUMA_4X4, true, ndc, &sc)
7063                    } else {
7064                        nzc[CB_NZC_CACHE[iz]] = 0;
7065                        0
7066                    };
7067                    fe.nnz_y[by * w4 + bx] = total as u8;
7068                }
7069            }
7070            cb_emit_chroma_residual(cab, fe, &mut nzc, &mut cbfdc, ndc, true, plan.cbp_chroma, &plan.c_dc_levels, &plan.c_q_blocks, mb_x, mb_y);
7071        }
7072    }
7073
7074    cs.cbf_dc[addr] = cbfdc;
7075    cs.mb_nzc[addr] = cb_export_nzc(&nzc);
7076}
7077
7078/// Chroma DC + AC residual (shared by intra I_16x16/I_NxN and inter) — matches the
7079/// decoder's chroma residual order. `is_intra` selects the coded_block_flag default
7080/// (nA=nB default to is_intra). Populates the chroma nnz grid for deblock.
7081#[allow(clippy::too_many_arguments)]
7082fn cb_emit_chroma_residual(
7083    cab: &mut CabacEncoder,
7084    fe: &mut FrameEncoder,
7085    nzc: &mut [u8; 48],
7086    cbfdc: &mut u16,
7087    ndc: (Option<u16>, Option<u16>),
7088    is_intra: bool,
7089    cbp_chroma: u32,
7090    c_dc_levels: &[[i32; 4]; 2],
7091    c_q: &[[[i32; 16]; 4]; 2],
7092    mb_x: usize,
7093    mb_y: usize,
7094) {
7095    let w2 = fe.mb_w * 2;
7096    if cbp_chroma >= 1 {
7097        for i in 0..2usize {
7098            cb_residual(cab, nzc, cbfdc, 16 + i * 4, CB_RP_CHROMA_DC + i, is_intra, ndc, &c_dc_levels[i]);
7099        }
7100    }
7101    if cbp_chroma == 2 {
7102        for i in 0..2usize {
7103            for (id4, &(bx, by)) in CHROMA_4X4_SCAN_XY.iter().enumerate() {
7104                let ac = scan_4x4_ac(&c_q[i][by * 2 + bx]);
7105                let total = cb_residual(
7106                    cab, nzc, cbfdc, 16 + i * 4 + id4, CB_RP_CHROMA_AC + i, is_intra, ndc, &ac,
7107                );
7108                fe.nnz_c[i][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total as u8;
7109            }
7110        }
7111    }
7112}
7113
7114/// CABAC all-intra slice-data coder (IDR / I-slice). Mirrors `encode_slice_data`'s
7115/// setup + deblock + `RefFrame` construction, but codes every MB via `plan_mb` +
7116/// `emit_mb_cabac_i` into a CABAC bitstream. `w` already holds the byte-aligned
7117/// slice header; the CABAC bytes are appended after `cabac_alignment_one_bit`.
7118pub fn encode_slice_data_cabac_intra(
7119    w: &mut BitWriter,
7120    cfg: &EncoderConfig,
7121    frame: &YuvFrame,
7122    qp: u8,
7123    qpo: &[i32],
7124) -> crate::RefFrame {
7125    let mut fe = FrameEncoder::new(cfg);
7126    fe.qp = qp;
7127    fe.qpc = chroma_qp(qp);
7128    fe.cur_qp = qp;
7129    if cfg.cabac_dz_div > 0 {
7130        fe.idz = cfg.cabac_dz_div; // CABAC-specific dead-zone override
7131    }
7132    let (sy, su, sv) = coded_source(cfg, frame);
7133    let mut aq_qp = aq_qp_map(&sy, fe.cw, fe.mb_w, fe.mb_h, qp, fe.aq_strength);
7134    apply_mbtree_qpo(&mut aq_qp, qpo); // mb-tree temporal AQ (empty = byte-identical)
7135    fe.cur_qp = qp;
7136    let mut mb_qpy = vec![qp; fe.mb_w * fe.mb_h];
7137
7138    // CABAC trellis (RDOQ): structure-adaptive. ON only for ALL-INTRA streams
7139    // (gop_size<=1), where each IDR is independent so trading a little distortion for
7140    // rate is a clean −0.5..−1.3% BD-rate win. OFF inside a GOP: there the I-frame is
7141    // a REFERENCE, and degrading it costs the dependent P-frames more than the I-frame
7142    // saves (measured ~+0.1% net) — so the safe end is a true no-op (never regresses).
7143    fe.rdoq_strength = if cfg.gop_size <= 1 { cfg.cabac_rdoq } else { 0.0 };
7144    // Contexts init from SliceQPY (the slice qp), init_idc unused for I, is_i = true.
7145    let mut cab = CabacEncoder::new(qp as i32, 0, true);
7146    let mut cs = CabacState::new(fe.mb_w * fe.mb_h);
7147    let total = fe.mb_w * fe.mb_h;
7148
7149    for mb_y in 0..fe.mb_h {
7150        for mb_x in 0..fe.mb_w {
7151            let mb_idx = mb_y * fe.mb_w + mb_x;
7152            fe.qp = aq_qp[mb_idx];
7153            fe.qpc = chroma_qp(aq_qp[mb_idx]);
7154            let plan = plan_mb(&mut fe, mb_x, mb_y, &sy, &su, &sv);
7155            emit_mb_cabac_i(&mut fe, &mut cab, &mut cs, &plan, mb_x, mb_y);
7156            mb_qpy[mb_idx] = fe.cur_qp;
7157            // end_of_slice_flag (EncodeTerminate): 1 on the last MB, else 0.
7158            {
7159                let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
7160                cab.encode_terminate(mb_idx + 1 == total);
7161                if crate::bitacct::enabled() {
7162                    crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
7163                }
7164            }
7165        }
7166    }
7167
7168    // Append CABAC slice data after cabac_alignment_one_bit (pad header with 1-bits).
7169    while !w.is_byte_aligned() {
7170        w.write_bit(true);
7171    }
7172    for b in cab.into_bytes() {
7173        w.write_bits(b as u32, 8);
7174    }
7175
7176    // Deblock the reconstruction (all-intra: BS derives from intra-ness) -> reference.
7177    let ref_id: Vec<i32> = fe.ref_idx_y.iter().map(|&r| if r >= 0 { r } else { i32::MIN }).collect();
7178    let info = rusty_h264_common::deblock::BlockInfo {
7179        inter: &fe.inter_y,
7180        nnz: &fe.nnz_y,
7181        mv: &fe.mv_y,
7182        ref_id: &ref_id,
7183        mv1: &[],
7184        ref_id1: &[],
7185        w4: fe.mb_w * 4,
7186        t8x8: &[],
7187        bs: &[],
7188        };
7189    rusty_h264_common::deblock::filter_frame(
7190        &mut fe.rec_y, &mut fe.rec_u, &mut fe.rec_v, fe.mb_w, fe.mb_h, &mb_qpy, 0, 0, 0, &info,
7191    );
7192    let w4 = fe.mb_w * 4;
7193    crate::RefFrame {
7194        y: fe.rec_y,
7195        u: fe.rec_u,
7196        v: fe.rec_v,
7197        poc: 0,
7198        frame_num: 0,
7199        mv: fe.mv_y,
7200        ref_idx: fe.ref_idx_y,
7201        w4,
7202        // Filtered lazily on first sub-pel search use (see `RefFrame::hpel`).
7203        hpel: std::sync::OnceLock::new(),
7204    }
7205}
7206
7207// ============================================================================
7208// CABAC P-slice entropy coding — the forward inverse of the decoder's
7209// decode_slice_data_cabac P-slice path. mb_skip_flag / mb_type_p / mvd (UEG3) /
7210// inter residual, plus intra-in-P (the shared intra body under a P mb_type prefix).
7211// Scope: 1 reference (no ref_idx), P_16x16/16x8/8x16 (no P_8x8/sub_mb_type) — the
7212// modes the encoder's decision produces.
7213// ============================================================================
7214
7215// z-order 4x4 block -> 30-entry (6-stride) mvd/ref cache index (openh264 g_kCache30ScanIdx).
7216const CB_CACHE30: [usize; 16] = [7, 8, 13, 14, 9, 10, 15, 16, 19, 20, 25, 26, 21, 22, 27, 28];
7217// z-order 4x4 block -> raster index (openh264 g_kuiScan4): the per-MB mvd/ref grid layout.
7218const CB_G_SCAN4: [usize; 16] = [0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15];
7219
7220/// UEG3 mvd suffix — inverse of `decode_ueg_mv(base)` (TU prefix at base+{0,1,2,3,3..},
7221/// cMax 7, then EG3 bypass). `v` is the value decode_ueg_mv returns.
7222fn cb_ueg_mv(cab: &mut CabacEncoder, base: usize, v: u32) {
7223    const P2C: [usize; 8] = [0, 1, 2, 3, 3, 3, 3, 3];
7224    if v == 0 {
7225        cab.encode_decision(base, 0);
7226        return;
7227    }
7228    cab.encode_decision(base, 1);
7229    if v <= 7 {
7230        // (v-1) ones then a terminating 0, at base+P2C[count] for count = 1..
7231        let mut count = 1;
7232        for _ in 0..v - 1 {
7233            cab.encode_decision(base + P2C[count], 1);
7234            count += 1;
7235        }
7236        cab.encode_decision(base + P2C[count], 0);
7237    } else {
7238        // prefix maxes out: 7 ones (count 1..7) then EG3(v-8).
7239        let mut count = 1;
7240        for _ in 0..7 {
7241            cab.encode_decision(base + P2C[count], 1);
7242            count += 1;
7243        }
7244        let tb = if crate::bitacct::enabled() { cab.pos() } else { 0 };
7245        cb_exp_bypass(cab, 3, v - 8);
7246        if crate::bitacct::enabled() {
7247            crate::bitacct::add(crate::bitacct::B::MvdBypass, cab.pos() - tb);
7248        }
7249    }
7250}
7251
7252/// One `mvd` component — inverse of `parse_mvd_cabac(comp, ctx_inc)` (ctxIdxOffset
7253/// 40 for x, 47 for y).
7254fn cb_mvd(cab: &mut CabacEncoder, comp: usize, ctx_inc: usize, d: i32) {
7255    let th = if crate::bitacct::enabled() { cab.pos() } else { u64::MAX };
7256    let base = 40 + comp * 7;
7257    if d == 0 {
7258        cab.encode_decision(base + ctx_inc, 0);
7259        if th != u64::MAX {
7260            crate::bitacct::add_mvd_sample(0, cab.pos() - th);
7261        }
7262        return;
7263    }
7264    cab.encode_decision(base + ctx_inc, 1);
7265    cb_ueg_mv(cab, base + 3, d.unsigned_abs() - 1); // decode adds 1 back
7266    let ts = if crate::bitacct::enabled() { cab.pos() } else { 0 };
7267    cab.encode_bypass((d < 0) as u32);
7268    if crate::bitacct::enabled() {
7269        crate::bitacct::add(crate::bitacct::B::MvdSign, cab.pos() - ts);
7270    }
7271    if th != u64::MAX {
7272        crate::bitacct::add_mvd_sample(d.unsigned_abs(), cab.pos() - th);
7273    }
7274}
7275
7276/// `mb_skip_flag` — inverse of `parse_mb_skip_cabac` (ctx 11 P + neighbour-not-skip).
7277fn cb_mb_skip(cab: &mut CabacEncoder, ctx_inc: usize, skip: bool) {
7278    cab.encode_decision(ctx_inc, skip as u32);
7279}
7280
7281/// `ref_idx_l0` (P) — inverse of `parse_ref_idx_cabac`. Unary binarization,
7282/// ctxIdxOffset 54: binIdx 0 → `ctx0` (condTermFlagA + 2·condTermFlagB, condTermFlagN =
7283/// neighbour partition's ref_idx > 0), binIdx 1 → 4, binIdx ≥2 → 5 (spec 9.3.3.1.1.6).
7284fn cb_ref_idx(cab: &mut CabacEncoder, ctx0: usize, r: u32) {
7285    const B: usize = 54;
7286    let mut v = r;
7287    let mut bin_idx = 0u32;
7288    loop {
7289        let bin = (v > 0) as u32;
7290        let ctx = match bin_idx {
7291            0 => ctx0,
7292            1 => 4,
7293            _ => 5,
7294        };
7295        cab.encode_decision(B + ctx, bin);
7296        if bin == 0 {
7297            break;
7298        }
7299        v -= 1;
7300        bin_idx += 1;
7301    }
7302}
7303
7304/// P-slice inter `mb_type` (0/1/2 = P_L0_16x16 / P_16x8 / P_8x16) — inverse of the
7305/// inter branch of `parse_mb_type_p_cabac` (ctx base 11).
7306fn cb_mb_type_p_inter(cab: &mut CabacEncoder, mode: u8) {
7307    const S: usize = 11;
7308    cab.encode_decision(S + 3, 0); // inter (prefix bit 0)
7309    match mode {
7310        0 => {
7311            cab.encode_decision(S + 4, 0);
7312            cab.encode_decision(S + 5, 0);
7313        }
7314        3 => {
7315            // P_8x8 (bins "0 0 1")
7316            cab.encode_decision(S + 4, 0);
7317            cab.encode_decision(S + 5, 1);
7318        }
7319        1 => {
7320            cab.encode_decision(S + 4, 1);
7321            cab.encode_decision(S + 6, 1);
7322        }
7323        _ => {
7324            // mode == 2 (P_8x16)
7325            cab.encode_decision(S + 4, 1);
7326            cab.encode_decision(S + 6, 0);
7327        }
7328    }
7329}
7330
7331/// P `sub_mb_type` CABAC — inverse of `parse_sub_mb_type_p_cabac` (ctx base 21).
7332/// Only 0 = P_L0_8x8 (bin "1") is emitted (8×8 sub-partitions only).
7333fn cb_sub_mb_type_p(cab: &mut CabacEncoder, sub_type: u8) {
7334    const S: usize = 21;
7335    match sub_type {
7336        0 => cab.encode_decision(S, 1),
7337        _ => unreachable!("only 8x8 sub_mb_type (0) emitted"),
7338    }
7339}
7340
7341/// P-slice intra `mb_type` prefix — inverse of the intra branch of
7342/// `parse_mb_type_p_cabac` (ctx base 11). Carries the I_16x16 pred-mode/cbp exactly
7343/// like the I-slice mb_type, so the shared intra body re-emits neither.
7344fn cb_mb_type_p_intra(cab: &mut CabacEncoder, plan: &MbPlan) {
7345    const S: usize = 11;
7346    cab.encode_decision(S + 3, 1); // intra (prefix bit 1)
7347    if plan.use_i4 {
7348        cab.encode_decision(S + 6, 0); // I_4x4
7349        return;
7350    }
7351    cab.encode_decision(S + 6, 1); // I_16x16
7352    cab.encode_terminate(false); // not I_PCM
7353    cab.encode_decision(S + 7, plan.i16_cbp15 as u32);
7354    if plan.cbp_chroma != 0 {
7355        cab.encode_decision(S + 8, 1);
7356        cab.encode_decision(S + 8, (plan.cbp_chroma == 2) as u32);
7357    } else {
7358        cab.encode_decision(S + 8, 0);
7359    }
7360    cab.encode_decision(S + 9, (plan.i16_mode as u32 >> 1) & 1);
7361    cab.encode_decision(S + 9, plan.i16_mode as u32 & 1);
7362}
7363
7364/// P-slice partition layout: `(part_idx, z-blocks)` per motion partition (matches
7365/// the decoder's `part!` invocations). part_idx = the partition's top-left z-block
7366/// (its `CACHE30` slot for the mvd ctxInc); z-blocks = every 4x4 it covers.
7367fn p_partition_layout(mode: u8) -> &'static [(usize, &'static [usize])] {
7368    match mode {
7369        1 => &[(0, &[0, 1, 2, 3, 4, 5, 6, 7]), (8, &[8, 9, 10, 11, 12, 13, 14, 15])],
7370        2 => &[(0, &[0, 1, 2, 3, 8, 9, 10, 11]), (4, &[4, 5, 6, 7, 12, 13, 14, 15])],
7371        // P_8x8: four 8×8 quads (z-order 4×4 blocks), part order == inter_partitions(3).
7372        3 => &[(0, &[0, 1, 2, 3]), (4, &[4, 5, 6, 7]), (8, &[8, 9, 10, 11]), (12, &[12, 13, 14, 15])],
7373        _ => &[(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])],
7374    }
7375}
7376
7377/// Emit one motion partition's `mvd` (x,y) and splat it into the 30-entry cache +
7378/// per-MB raster mvd/ref grids — inverse of the decoder's `parse_mvd_partition`.
7379#[allow(clippy::too_many_arguments)]
7380fn cb_emit_mvd_partition(
7381    cab: &mut CabacEncoder,
7382    part_idx: usize,
7383    zblocks: &[usize],
7384    mvdc: &mut [[i16; 2]; 30],
7385    refc: &mut [i8; 30],
7386    mmvd: &mut [[i16; 2]; 16],
7387    mref: &mut [i8; 16],
7388    mvd: (i32, i32),
7389    ref_idx: i8, // the partition's ref_idx_l0 (0 for single-ref) — stored for neighbour context
7390) {
7391    let s = CB_CACHE30[part_idx];
7392    let ctx = |comp: usize| -> usize {
7393        let mut a = 0i32;
7394        if refc[s - 6] >= 0 {
7395            a += mvdc[s - 6][comp].unsigned_abs() as i32;
7396        }
7397        if refc[s - 1] >= 0 {
7398            a += mvdc[s - 1][comp].unsigned_abs() as i32;
7399        }
7400        if a >= 3 {
7401            1 + (a > 32) as usize
7402        } else {
7403            0
7404        }
7405    };
7406    cb_mvd(cab, 0, ctx(0), mvd.0);
7407    cb_mvd(cab, 1, ctx(1), mvd.1);
7408    let (mx, my) = (mvd.0 as i16, mvd.1 as i16);
7409    for &zb in zblocks {
7410        mvdc[CB_CACHE30[zb]] = [mx, my];
7411        refc[CB_CACHE30[zb]] = ref_idx;
7412        mmvd[CB_G_SCAN4[zb]] = [mx, my];
7413        mref[CB_G_SCAN4[zb]] = ref_idx;
7414    }
7415}
7416
7417/// Emit one planned INTER macroblock as CABAC (P-slice, mb_skip_flag already coded
7418/// as 0). `mode`/`parts` + `plan` from `plan_inter_mb`. 1-ref: no ref_idx.
7419fn emit_mb_cabac_p_inter(
7420    fe: &mut FrameEncoder,
7421    cab: &mut CabacEncoder,
7422    cs: &mut CabacState,
7423    mode: u8,
7424    plan: &InterPlan,
7425    mb_x: usize,
7426    mb_y: usize,
7427    num_refs: usize,
7428) {
7429    let mb_w = fe.mb_w;
7430    let addr = mb_y * mb_w + mb_x;
7431    let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
7432    let left = if mb_x > 0 { Some(addr - 1) } else { None };
7433
7434    // Bit accountant (instrument #6): each tap is a `pos()` delta — exact coded
7435    // bits for that element — behind an atomic-bool check when disabled.
7436    let acct = crate::bitacct::enabled();
7437    let mut t0 = if acct { cab.pos() } else { 0 };
7438    cb_mb_type_p_inter(cab, mode);
7439    // P_8x8: four sub_mb_type (all 0 = 8×8), spec order before ref_idx/mvd.
7440    if mode == 3 {
7441        for _ in 0..4 {
7442            cb_sub_mb_type_p(cab, 0);
7443        }
7444    }
7445    if acct {
7446        crate::bitacct::add(crate::bitacct::B::MbType, cab.pos() - t0);
7447        t0 = cab.pos();
7448    }
7449
7450    // ---- mb_pred (spec 7.3.5.1): all ref_idx_l0 FIRST, then all mvd_l0 ----
7451    let mut mvdc = [[0i16; 2]; 30];
7452    let mut refc = [-1i8; 30];
7453    cb_fill_inter_cache(&cs.mb_ref, &cs.mb_mvd, &mut refc, &mut mvdc, top, left, addr, mb_w);
7454    let mut mmvd = [[0i16; 2]; 16];
7455    let mut mref = [0i8; 16];
7456    let layout = p_partition_layout(mode);
7457    // Phase 1: ref_idx_l0 per partition, only when the slice has >1 active reference.
7458    // Update refc after each so a later partition's ref context sees the earlier one.
7459    if num_refs > 1 {
7460        for (part, &(part_idx, zblocks)) in layout.iter().enumerate() {
7461            let r = plan.plan_refs[part];
7462            let s = CB_CACHE30[part_idx];
7463            let ctx0 = (refc[s - 1] > 0) as usize + 2 * (refc[s - 6] > 0) as usize;
7464            cb_ref_idx(cab, ctx0, r as u32);
7465            for &zb in zblocks {
7466                refc[CB_CACHE30[zb]] = r as i8;
7467            }
7468        }
7469    }
7470    if acct {
7471        crate::bitacct::add(crate::bitacct::B::RefIdx, cab.pos() - t0);
7472        t0 = cab.pos();
7473    }
7474    // Phase 2: mvd per partition (carries the ref into refc/mref for neighbour context).
7475    for (part, &(part_idx, zblocks)) in layout.iter().enumerate() {
7476        cb_emit_mvd_partition(
7477            cab, part_idx, zblocks, &mut mvdc, &mut refc, &mut mmvd, &mut mref, plan.mvds[part],
7478            plan.plan_refs[part] as i8,
7479        );
7480    }
7481    if acct {
7482        crate::bitacct::add(crate::bitacct::B::Mvd, cab.pos() - t0);
7483    }
7484    cs.mb_mvd[addr] = mmvd;
7485    cs.mb_ref[addr] = mref;
7486    cs.cat[addr] = 100;
7487    cb_emit_inter_residual(fe, cab, cs, plan, mb_x, mb_y, addr, top, left);
7488}
7489
7490/// Inter cbp + residual (is_intra = false) — shared by P and B inter MBs. Maintains
7491/// cs.mb_cbp/cbf_dc/mb_nzc/last_delta_qp + fe.nnz_y.
7492#[allow(clippy::too_many_arguments)]
7493fn cb_emit_inter_residual(
7494    fe: &mut FrameEncoder,
7495    cab: &mut CabacEncoder,
7496    cs: &mut CabacState,
7497    plan: &InterPlan,
7498    mb_x: usize,
7499    mb_y: usize,
7500    addr: usize,
7501    top: Option<usize>,
7502    left: Option<usize>,
7503) {
7504    let w4 = fe.mb_w * 4;
7505    let cbp = plan.cbp;
7506    let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
7507    let acct = crate::bitacct::enabled();
7508    let mut t0 = if acct { cab.pos() } else { 0 };
7509    cb_cbp(cab, top.map(|a| cs.mb_cbp[a]), left.map(|a| cs.mb_cbp[a]), cbp);
7510    if acct {
7511        crate::bitacct::add(crate::bitacct::B::Cbp, cab.pos() - t0);
7512    }
7513    cs.mb_cbp[addr] = cbp as u8;
7514    let mut nzc = cb_build_nzc(&cs.mb_nzc, top, left);
7515    let mut cbfdc = 0u16;
7516    let ndc = (top.map(|a| cs.cbf_dc[a]), left.map(|a| cs.cbf_dc[a]));
7517
7518    if cbp == 0 {
7519        cs.last_delta_qp = 0;
7520        for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
7521            fe.nnz_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 0;
7522        }
7523    } else {
7524        let delta = fe.qp_delta();
7525        if acct { t0 = cab.pos(); }
7526        cb_mb_qp_delta(cab, &mut cs.last_delta_qp, delta);
7527        if acct {
7528            crate::bitacct::add(crate::bitacct::B::QpDelta, cab.pos() - t0);
7529            t0 = cab.pos();
7530        }
7531        for id8 in 0..4usize {
7532            for id4 in 0..4usize {
7533                let iz = id8 * 4 + id4;
7534                let (lbx, lby) = LUMA_4X4_SCAN_XY[iz];
7535                let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
7536                let total = if cbp_luma & (1 << id8) != 0 {
7537                    let sc = scan_4x4_dcac(&plan.q_blocks[lby * 4 + lbx]);
7538                    cb_residual(cab, &mut nzc, &mut cbfdc, iz, CB_RP_LUMA_4X4, false, ndc, &sc)
7539                } else {
7540                    nzc[CB_NZC_CACHE[iz]] = 0;
7541                    0
7542                };
7543                fe.nnz_y[by * w4 + bx] = total as u8;
7544            }
7545        }
7546        if acct {
7547            crate::bitacct::add(crate::bitacct::B::ResidLuma, cab.pos() - t0);
7548            t0 = cab.pos();
7549        }
7550        cb_emit_chroma_residual(cab, fe, &mut nzc, &mut cbfdc, ndc, false, cbp_chroma, &plan.c_dc_levels, &plan.c_q, mb_x, mb_y);
7551        if acct {
7552            crate::bitacct::add(crate::bitacct::B::ResidChroma, cab.pos() - t0);
7553        }
7554    }
7555    cs.cbf_dc[addr] = cbfdc;
7556    cs.mb_nzc[addr] = cb_export_nzc(&nzc);
7557}
7558
7559/// Emit one planned INTRA macroblock inside a P-slice: the P mb_type prefix (which
7560/// carries the I_16x16 pred-mode/cbp) then the shared intra body.
7561fn emit_mb_cabac_p_intra(
7562    fe: &mut FrameEncoder,
7563    cab: &mut CabacEncoder,
7564    cs: &mut CabacState,
7565    plan: &MbPlan,
7566    mb_x: usize,
7567    mb_y: usize,
7568) {
7569    let mb_w = fe.mb_w;
7570    let addr = mb_y * mb_w + mb_x;
7571    let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
7572    let left = if mb_x > 0 { Some(addr - 1) } else { None };
7573    let acct = crate::bitacct::enabled();
7574    let t0 = if acct { cab.pos() } else { 0 };
7575    cb_mb_type_p_intra(cab, plan);
7576    emit_intra_body_cabac(fe, cab, cs, plan, mb_x, mb_y, addr, top, left);
7577    if acct {
7578        // Whole intra MB (mb_type + modes + its residual) — intra MBs are ~5% of
7579        // P-frame MBs; splitting them further is a separate tap set.
7580        crate::bitacct::add(crate::bitacct::B::IntraBody, cab.pos() - t0);
7581    }
7582}
7583
7584/// Emit a P_Skip macroblock's `mb_skip_flag = 1` and update neighbour state. The
7585/// motion grid was committed by `commit_skip`; the mvd/ref cache is LEFT at its
7586/// init (-1 ref) — matching the decoder, which does not touch mb_mvd/mb_ref for a
7587/// P_Skip (so a skip neighbour contributes nothing to a later mvd ctxInc).
7588fn emit_p_skip_cabac(cab: &mut CabacEncoder, cs: &mut CabacState, addr: usize, top: Option<usize>, left: Option<usize>) {
7589    let sctx = 11
7590        + left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
7591        + top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
7592    let t0 = if crate::bitacct::enabled() { cab.pos() } else { 0 };
7593    cb_mb_skip(cab, sctx, true);
7594    if crate::bitacct::enabled() {
7595        crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - t0);
7596    }
7597    cs.mb_skip[addr] = true;
7598    cs.cat[addr] = 100;
7599    cs.last_delta_qp = 0;
7600}
7601
7602/// CABAC P-slice data coder. Mirrors `encode_slice_data`'s decision (P_Skip check +
7603/// fast/quality inter-vs-intra RD) exactly — only the emit differs (per-MB
7604/// mb_skip_flag + CABAC syntax + per-MB end_of_slice terminate).
7605pub fn encode_slice_data_cabac_p(
7606    w: &mut BitWriter,
7607    cfg: &EncoderConfig,
7608    frame: &YuvFrame,
7609    qp: u8,
7610    refs: &[crate::RefFrame],
7611    qpo: &[i32],
7612) -> crate::RefFrame {
7613    let mut fe = FrameEncoder::new(cfg);
7614    fe.qp = qp;
7615    fe.qpc = chroma_qp(qp);
7616    fe.cur_qp = qp;
7617    if cfg.cabac_dz_div > 0 {
7618        fe.idz = cfg.cabac_dz_div; // CABAC-specific dead-zone override
7619    }
7620    let (sy, su, sv) = coded_source(cfg, frame);
7621    let lambda = 0.85 * fe.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
7622    let num_refs = refs.len();
7623    // me_wide content gate (pure-pan → global-MC residual ≈ 0 → off; see encode_slice_data).
7624    if fe.me_wide && !refs.is_empty() {
7625        let coh = global_mc_residual(&sy, fe.cw, fe.mb_h * 16, &refs[0].y);
7626        if std::env::var("RFF_ME_COH_DBG").is_ok() {
7627            eprintln!("ME_COH qp{qp} residual={coh:.2}");
7628        }
7629        if coh < fe.me_wide_coh {
7630            fe.me_wide = false;
7631        }
7632    }
7633    // me_wide HEAD-ROOM GATE (the dispatcher the truth table asked for). The rescue
7634    // only pays where a wide search actually beats a predictor-local one; measure
7635    // that directly per frame and route the frame. `RFF_ME_HR` sets the threshold
7636    // (percent); 0 disables the gate and restores the always-on behaviour.
7637    // Skip the probe entirely when the gate is disabled: it must not tax the
7638    // default path (`RFF_ME_HR=0`), which stays byte-identical to pre-gate output.
7639    if fe.me_wide && !refs.is_empty() && (me_wide_hr_thresh() > 0.0 || me_wide_hr_dbg()) {
7640        let hr = me_wide_headroom(&sy, fe.cw, fe.mb_h * 16, &refs[0].y);
7641        if me_wide_hr_dbg() {
7642            eprintln!("ME_HR qp{qp} headroom={hr:.2}");
7643        }
7644        if me_wide_hr_thresh() > 0.0 && hr < me_wide_hr_thresh() {
7645            fe.me_wide = false;
7646        }
7647    }
7648    // Track-B B2 DISPATCH — same probe/route as the CAVLC driver above (the two
7649    // drivers must stay in lockstep; the U5-struct bug came from patching one).
7650    if me_sadfp_mode() == 1 && !fe.fast && !refs.is_empty() {
7651        let (mg, dc) = b2_mgain(&sy, fe.cw, fe.mb_h * 16, &refs[0].y);
7652        if me_sadt_dbg() {
7653            eprintln!("B2_MG qp{qp} mgain={mg:.3} dcfrac={dc:.3}");
7654        }
7655        fe.sadfp = mg >= me_sadt() && dc <= me_sad_dcmax();
7656        // H-24: the mv-cost SHAPE rides the same probe (its BD sign-flip tracks
7657        // motion for the same physical reason B2's does).
7658        if mv_smooth_mode() == 1 {
7659            // dcfrac veto mirrors B2's: crew-class FLASH frames satisfy the mgain
7660            // test but SAD/mvd statistics mislead there (H-13/H-26).
7661            fe.mv_smooth = mg >= mv_smooth_t() && dc <= me_sad_dcmax();
7662        }
7663        // H-13: near-static frames skip the split searches entirely.
7664        let smg = split_mg();
7665        if smg > 0.0 {
7666            fe.do_splits = mg >= smg;
7667        }
7668    }
7669    if fe.satd_q > 0.0 {
7670        let mut vars: Vec<i64> = (0..fe.mb_h)
7671            .flat_map(|my| (0..fe.mb_w).map(move |mx| (mx, my)))
7672            .map(|(mx, my)| mb_variance(&sy, fe.cw, mx, my))
7673            .collect();
7674        vars.sort_unstable();
7675        let idx = (((1.0 - fe.satd_q) * vars.len() as f64) as usize).min(vars.len() - 1);
7676        fe.satd_var_thresh = vars[idx];
7677    }
7678    let mut aq_qp = aq_qp_map(&sy, fe.cw, fe.mb_w, fe.mb_h, qp, fe.aq_strength);
7679    apply_mbtree_qpo(&mut aq_qp, qpo); // mb-tree temporal AQ (empty = byte-identical)
7680    fe.cur_qp = qp;
7681    let mut mb_qpy = vec![qp; fe.mb_w * fe.mb_h];
7682
7683    // Same online free-skip dispatch as the CAVLC path gates the greedy P_Skip on
7684    // (see `encode_slice_data`): measured over the frame so far, within-frame so it
7685    // stays deterministic under GOP-parallel encode.
7686    let mut greedy_free = 0usize;
7687    let mut greedy_seen = 0usize;
7688    let mut greedy_on = fe.greedy_min_free == 0;
7689    let greedy_learn = (fe.mb_w * fe.mb_h / 8).max(64);
7690    let mut cab = CabacEncoder::new(qp as i32, cfg.cabac_init_idc, false); // P-slice
7691    let mut cs = CabacState::new(fe.mb_w * fe.mb_h);
7692    let total = fe.mb_w * fe.mb_h;
7693
7694    // ② residue naming: the CABAC driver's MB loop was untapped (the CAVLC twin
7695    // has this scope) — `EncMbLoop − Σ(per-MB stages)` is the per-MB glue.
7696    let _g_loop = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMbLoop);
7697    for mb_y in 0..fe.mb_h {
7698        for mb_x in 0..fe.mb_w {
7699            let mb_idx = mb_y * fe.mb_w + mb_x;
7700            let addr = mb_idx;
7701            let top = if mb_y > 0 { Some(addr - fe.mb_w) } else { None };
7702            let left = if mb_x > 0 { Some(addr - 1) } else { None };
7703            fe.qp = aq_qp[mb_idx];
7704            fe.qpc = chroma_qp(aq_qp[mb_idx]);
7705
7706            // ---- P_Skip check (identical logic to encode_slice_data) ----
7707            let mut inter: Option<InterChoice> = None;
7708            let mut did_skip = false;
7709            if num_refs > 0 {
7710                let mv_skip = fe.skip_mv(mb_x, mb_y);
7711                let skip_y = fe.skip_predict_luma(refs, mb_x, mb_y, mv_skip);
7712                let luma_free = fe.skip_luma_is_free(&sy, mb_x, mb_y, &skip_y);
7713                let skip_c = if luma_free || !fe.fast {
7714                    fe.skip_predict_chroma(refs, mb_x, mb_y, mv_skip)
7715                } else {
7716                    [[0u8; 64]; 2]
7717                };
7718                let is_free = luma_free && fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, &skip_c);
7719                let skip_sad = if fe.fast {
7720                    0
7721                } else {
7722                    let (lx, ly) = (mb_x * 16, mb_y * 16);
7723                    let mut s = 0u32;
7724                    for dy in 0..16 {
7725                        let src = &sy[(ly + dy) * fe.cw + lx..][..16];
7726                        let p = &skip_y[dy * 16..][..16];
7727                        s += src.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
7728                    }
7729                    s
7730                };
7731                greedy_seen += 1;
7732                if greedy_seen >= greedy_learn {
7733                    greedy_on = fe.greedy_min_free == 0
7734                        || greedy_free * 100 >= greedy_seen * fe.greedy_min_free as usize;
7735                }
7736                if is_free {
7737                    fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_c);
7738                    if !fe.fast {
7739                        fe.mb_was_skip[mb_idx] = true;
7740                        fe.mb_skip_sad[mb_idx] = skip_sad;
7741                    }
7742                    greedy_free += 1;
7743                    did_skip = true;
7744                } else {
7745                    let (lx, ly) = (mb_x * 16, mb_y * 16);
7746                    let nb = {
7747                        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMvPred);
7748                        fe.mv_neighbors_block(mb_x as isize * 4, mb_y as isize * 4, 4)
7749                    };
7750                    let lme = lambda.sqrt() * cfg.cabac_lambda_scale;
7751                    if fe.fast {
7752                        fe.mb_use_satd = fe.satd_q > 0.0
7753                            && mb_variance(&sy, fe.cw, mb_x, mb_y) >= fe.satd_var_thresh;
7754                        let (r16, mv16, cost_inter) =
7755                            fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
7756                        let cost_intra = if fe.mb_use_satd {
7757                            fe.best_i16_satd(&sy, mb_x, mb_y)
7758                        } else {
7759                            fe.best_i16_sad(&sy, mb_x, mb_y)
7760                        } + (lme * fe.tune_intra_penalty) as i64;
7761                        inter = if cost_intra < cost_inter {
7762                            None
7763                        } else {
7764                            Some((0, vec![(r16, mv16)]))
7765                        };
7766                    } else {
7767                        // Quality preset: greedy P_Skip, then 16x16 baseline + sub-partitions + intra.
7768                        if fe.greedy_skip && greedy_on && skip_sad < fe.pred_skip_sad(mb_x, mb_y) {
7769                            fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_c);
7770                            fe.mb_was_skip[mb_idx] = true;
7771                            fe.mb_skip_sad[mb_idx] = skip_sad;
7772                            did_skip = true;
7773                        } else {
7774                            let (r16, mv16, c16) =
7775                                fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
7776                            let mut best_c = c16;
7777                            let mut pick: Option<InterChoice> = Some((0, vec![(r16, mv16)]));
7778                            const QSTEP16: [i64; 6] = [10, 11, 13, 14, 16, 18];
7779                            let qstep16 = QSTEP16[(fe.qp % 6) as usize] << (fe.qp / 6);
7780                            let split_gate = ((30 * (qstep16 + 160)) >> 3) * 2;
7781                            let split_t = split_t();
7782                        if fe.do_splits && c16 > split_gate && (split_t <= 0.0 || (c16 as f64) >= split_t * lme) {
7783                                let (rt, mvt, ct) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 8, &[mv16], lme);
7784                                let (rb, mvb, cb) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly + 8, 16, 8, &[mv16], lme);
7785                                let (rl, mvl, cl) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 8, 16, &[mv16], lme);
7786                                let (rr, mvr, cr) = fe.best_part(refs, &sy, &nb, num_refs, lx + 8, ly, 8, 16, &[mv16], lme);
7787                                if ct + cb < best_c {
7788                                    best_c = ct + cb;
7789                                    pick = Some((1u8, vec![(rt, mvt), (rb, mvb)]));
7790                                }
7791                                if cl + cr < best_c {
7792                                    best_c = cl + cr;
7793                                    pick = Some((2u8, vec![(rl, mvl), (rr, mvr)]));
7794                                }
7795                                // P_8x8: four 8×8 sub-partitions (see the CAVLC path).
7796                                if fe.sub8x8 {
7797                                    let mut c8 = (lme * 4.0) as i64;
7798                                    let mut p8 = Vec::with_capacity(4);
7799                                    for &(qx, qy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8)] {
7800                                        let (r, mv, c) = fe.best_part(
7801                                            refs, &sy, &nb, num_refs, lx + qx, ly + qy, 8, 8, &[mv16], lme,
7802                                        );
7803                                        c8 += c;
7804                                        p8.push((r, mv));
7805                                    }
7806                                    if c8 < best_c {
7807                                        best_c = c8;
7808                                        pick = Some((3u8, p8));
7809                                    }
7810                                }
7811                            }
7812                            // U5-struct: refine ONLY the winning shape (see the twin
7813                            // block in the CAVLC driver). This site is the CABAC path —
7814                            // which is now the DEFAULT, so omitting it here left sub-pel
7815                            // deferred but never refined on every default encode.
7816                            if fe.sp_defer.get() {
7817                                if let Some((mode, parts)) = pick.as_mut() {
7818                                    let regions: &[(usize, usize, usize, usize)] = match mode {
7819                                        1 => &[(0, 0, 16, 8), (0, 8, 16, 8)],
7820                                        2 => &[(0, 0, 8, 16), (8, 0, 8, 16)],
7821                                        3 => &[(0, 0, 8, 8), (8, 0, 8, 8), (0, 8, 8, 8), (8, 8, 8, 8)],
7822                                        _ => &[(0, 0, 16, 16)],
7823                                    };
7824                                    let mut tot = if *mode == 3 { (lme * 4.0) as i64 } else { 0 };
7825                                    for (i, &(qx, qy, pw, ph)) in regions.iter().enumerate() {
7826                                        let (r, mv) = parts[i];
7827                                        let (m2, c2) = fe.refine_part(
7828                                            refs, &sy, &nb, num_refs, lx + qx, ly + qy, pw, ph, lme, r, mv,
7829                                        );
7830                                        parts[i] = (r, m2);
7831                                        tot += c2;
7832                                    }
7833                                    best_c = tot;
7834                                }
7835                            }
7836                            let c_intra = fe.best_i16_satd(&sy, mb_x, mb_y)
7837                                + (lme * fe.tune_intra_penalty) as i64;
7838                            inter = if c_intra < best_c { None } else { pick };
7839                            fe.mb_was_skip[mb_idx] = false;
7840                            fe.mb_skip_sad[mb_idx] = skip_sad;
7841                        }
7842                    }
7843                }
7844            }
7845
7846            // ---- emit ----
7847            if did_skip {
7848                emit_p_skip_cabac(&mut cab, &mut cs, addr, top, left);
7849                mb_qpy[mb_idx] = fe.cur_qp;
7850                {
7851                    let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
7852                    {
7853                let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
7854                cab.encode_terminate(mb_idx + 1 == total);
7855                if crate::bitacct::enabled() {
7856                    crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
7857                }
7858            }
7859                    if crate::bitacct::enabled() {
7860                        crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
7861                    }
7862                }
7863                continue;
7864            }
7865            // mb_skip_flag = 0
7866            let sctx = 11
7867                + left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
7868                + top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
7869            let tskip = if crate::bitacct::enabled() { cab.pos() } else { 0 };
7870            cb_mb_skip(&mut cab, sctx, false);
7871            if crate::bitacct::enabled() {
7872                crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - tskip);
7873            }
7874            cs.mb_skip[addr] = false;
7875            match inter {
7876                Some((mode, parts)) => {
7877                    let plan = fe.plan_inter_mb(refs, &sy, &su, &sv, mb_x, mb_y, mode, &parts, None);
7878                    // ② residue naming: the CABAC entropy EMIT was untapped on the
7879                    // (default) CABAC driver — the whole encoder-side arithmetic
7880                    // coder was landing in `mgmt/other`.
7881                    let _ge = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncEmit);
7882                    emit_mb_cabac_p_inter(&mut fe, &mut cab, &mut cs, mode, &plan, mb_x, mb_y, num_refs);
7883                }
7884                None => {
7885                    let plan = plan_mb(&mut fe, mb_x, mb_y, &sy, &su, &sv);
7886                    let _ge = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncEmit);
7887                    emit_mb_cabac_p_intra(&mut fe, &mut cab, &mut cs, &plan, mb_x, mb_y);
7888                }
7889            }
7890            mb_qpy[mb_idx] = fe.cur_qp;
7891            {
7892                let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
7893                cab.encode_terminate(mb_idx + 1 == total);
7894                if crate::bitacct::enabled() {
7895                    crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
7896                }
7897            }
7898        }
7899    }
7900
7901    while !w.is_byte_aligned() {
7902        w.write_bit(true);
7903    }
7904    for b in cab.into_bytes() {
7905        w.write_bits(b as u32, 8);
7906    }
7907
7908    // Deblock -> inter reference (same as encode_slice_data).
7909    let ref_id: Vec<i32> = fe.ref_idx_y.iter().map(|&r| if r >= 0 { r } else { i32::MIN }).collect();
7910    let info = rusty_h264_common::deblock::BlockInfo {
7911        inter: &fe.inter_y,
7912        nnz: &fe.nnz_y,
7913        mv: &fe.mv_y,
7914        ref_id: &ref_id,
7915        mv1: &[],
7916        ref_id1: &[],
7917        w4: fe.mb_w * 4,
7918        t8x8: &[],
7919        bs: &[],
7920        };
7921    rusty_h264_common::deblock::filter_frame(
7922        &mut fe.rec_y, &mut fe.rec_u, &mut fe.rec_v, fe.mb_w, fe.mb_h, &mb_qpy, 0, 0, 0, &info,
7923    );
7924    let w4 = fe.mb_w * 4;
7925    crate::RefFrame {
7926        y: fe.rec_y,
7927        u: fe.rec_u,
7928        v: fe.rec_v,
7929        poc: 0,
7930        frame_num: 0,
7931        mv: fe.mv_y,
7932        ref_idx: fe.ref_idx_y,
7933        w4,
7934        // Filtered lazily on first sub-pel search use (see `RefFrame::hpel`).
7935        hpel: std::sync::OnceLock::new(),
7936    }
7937}
7938
7939// ============================================================================
7940// CABAC B-slice entropy coding — inverse of the decoder's decode_slice_data_cabac
7941// B-slice path. Scope: the modes the encoder's B decision produces — B_Skip,
7942// B_Direct_16x16 (0), B_L0/L1/Bi_16x16 (1/2/3) — no sub_mb_type, no intra-in-B.
7943// The new piece vs P is the dual-list (L0 + L1) mvd/ref neighbour cache.
7944// ============================================================================
7945
7946/// Fill one list's 30-entry mvd/ref neighbour cache from the per-MB export grids
7947/// (openh264 WelsFillCacheInterCabac). Shared by P (List-0) and B (both lists).
7948fn cb_fill_inter_cache(
7949    mb_ref: &[[i8; 16]],
7950    mb_mvd: &[[[i16; 2]; 16]],
7951    refc: &mut [i8; 30],
7952    mvdc: &mut [[i16; 2]; 30],
7953    top: Option<usize>,
7954    left: Option<usize>,
7955    addr: usize,
7956    mb_w: usize,
7957) {
7958    if let Some(l) = left {
7959        for (ci, bi) in [(6usize, 3usize), (12, 7), (18, 11), (24, 15)] {
7960            refc[ci] = mb_ref[l][bi];
7961            mvdc[ci] = mb_mvd[l][bi];
7962        }
7963    }
7964    if let Some(t) = top {
7965        for (ci, bi) in [(1usize, 12usize), (2, 13), (3, 14), (4, 15)] {
7966            refc[ci] = mb_ref[t][bi];
7967            mvdc[ci] = mb_mvd[t][bi];
7968        }
7969    }
7970    let mb_x = addr % mb_w;
7971    let mb_y = addr / mb_w;
7972    if mb_x > 0 && mb_y > 0 {
7973        let a = addr - mb_w - 1;
7974        (refc[0], mvdc[0]) = (mb_ref[a][15], mb_mvd[a][15]);
7975    }
7976    if mb_y > 0 && mb_x + 1 < mb_w {
7977        let a = addr - mb_w + 1;
7978        (refc[5], mvdc[5]) = (mb_ref[a][12], mb_mvd[a][12]);
7979    }
7980}
7981
7982/// B-slice `mb_type` for the encoder's B modes (0 = B_Direct_16x16, 1 = B_L0_16x16,
7983/// 2 = B_L1_16x16, 3 = B_Bi_16x16) — inverse of `parse_mb_type_b_cabac` (ctx 27).
7984fn cb_mb_type_b(cab: &mut CabacEncoder, ctx_inc: usize, dir: u8) {
7985    const B: usize = 27;
7986    match dir {
7987        0 => cab.encode_decision(B + ctx_inc, 0), // B_Direct_16x16
7988        1 => {
7989            cab.encode_decision(B + ctx_inc, 1);
7990            cab.encode_decision(B + 3, 0);
7991            cab.encode_decision(B + 5, 0); // L0
7992        }
7993        2 => {
7994            cab.encode_decision(B + ctx_inc, 1);
7995            cab.encode_decision(B + 3, 0);
7996            cab.encode_decision(B + 5, 1); // L1
7997        }
7998        _ => {
7999            // dir == 3 (B_Bi_16x16): m = 0 → return m+3 = 3
8000            cab.encode_decision(B + ctx_inc, 1);
8001            cab.encode_decision(B + 3, 1);
8002            cab.encode_decision(B + 4, 0);
8003            cab.encode_decision(B + 5, 0);
8004            cab.encode_decision(B + 5, 0);
8005            cab.encode_decision(B + 5, 0);
8006        }
8007    }
8008}
8009
8010const CB_ALL16: [usize; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
8011
8012/// Emit one planned INTER B macroblock (mb_skip_flag already coded 0). `dir` is the
8013/// B direction 0/1/2/3; `plan.mvds` holds mvd_l0 then mvd_l1 (per used list).
8014fn emit_mb_cabac_b(
8015    fe: &mut FrameEncoder,
8016    cab: &mut CabacEncoder,
8017    cs: &mut CabacState,
8018    dir: u8,
8019    plan: &InterPlan,
8020    mb_x: usize,
8021    mb_y: usize,
8022) {
8023    let mb_w = fe.mb_w;
8024    let addr = mb_y * mb_w + mb_x;
8025    let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
8026    let left = if mb_x > 0 { Some(addr - 1) } else { None };
8027
8028    let bci = left.map_or(0, |a| (!cs.mb_direct[a]) as usize)
8029        + top.map_or(0, |a| (!cs.mb_direct[a]) as usize);
8030    cb_mb_type_b(cab, bci, dir);
8031
8032    // Dual-list mvd/ref caches (L0 = mb_ref/mb_mvd, L1 = mb_ref1/mb_mvd1).
8033    let mut mvdc0 = [[0i16; 2]; 30];
8034    let mut refc0 = [-1i8; 30];
8035    let mut mvdc1 = [[0i16; 2]; 30];
8036    let mut refc1 = [-1i8; 30];
8037    cb_fill_inter_cache(&cs.mb_ref, &cs.mb_mvd, &mut refc0, &mut mvdc0, top, left, addr, mb_w);
8038    cb_fill_inter_cache(&cs.mb_ref1, &cs.mb_mvd1, &mut refc1, &mut mvdc1, top, left, addr, mb_w);
8039    let mut mmvd0 = [[0i16; 2]; 16];
8040    let mut mref0 = [-1i8; 16];
8041    let mut mmvd1 = [[0i16; 2]; 16];
8042    let mut mref1 = [-1i8; 16];
8043    let (use0, use1) = (dir == 1 || dir == 3, dir == 2 || dir == 3);
8044    if dir == 0 {
8045        // B_Direct_16x16: no coded motion; ref 0 in both lists (mvd stays 0) so a
8046        // later MB's mvd ctxInc sums |0|.
8047        mref0 = [0i8; 16];
8048        mref1 = [0i8; 16];
8049    } else {
8050        // mvd parse order: list-major (L0 then L1); a single 16x16 partition (idx 0).
8051        let mut k = 0;
8052        if use0 {
8053            cb_emit_mvd_partition(cab, 0, &CB_ALL16, &mut mvdc0, &mut refc0, &mut mmvd0, &mut mref0, plan.mvds[k], 0);
8054            k += 1;
8055        }
8056        if use1 {
8057            cb_emit_mvd_partition(cab, 0, &CB_ALL16, &mut mvdc1, &mut refc1, &mut mmvd1, &mut mref1, plan.mvds[k], 0);
8058        }
8059    }
8060    cs.mb_mvd[addr] = mmvd0;
8061    cs.mb_ref[addr] = mref0;
8062    cs.mb_mvd1[addr] = mmvd1;
8063    cs.mb_ref1[addr] = mref1;
8064    cs.mb_direct[addr] = dir == 0;
8065    cs.cat[addr] = 100;
8066    cb_emit_inter_residual(fe, cab, cs, plan, mb_x, mb_y, addr, top, left);
8067}
8068
8069/// Emit a B_Skip macroblock's mb_skip_flag = 1 (ctx 24 base) + neighbour state. The
8070/// direct motion was committed by `commit_direct_motion`; ref 0 in both lists, mvd 0
8071/// (matching the decoder's decode_b_skip handling).
8072fn emit_b_skip_cabac(cab: &mut CabacEncoder, cs: &mut CabacState, addr: usize, top: Option<usize>, left: Option<usize>) {
8073    let sctx = 24
8074        + left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
8075        + top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
8076    let t0 = if crate::bitacct::enabled() { cab.pos() } else { 0 };
8077    cb_mb_skip(cab, sctx, true);
8078    if crate::bitacct::enabled() {
8079        crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - t0);
8080    }
8081    cs.mb_skip[addr] = true;
8082    cs.cat[addr] = 100;
8083    cs.mb_direct[addr] = true;
8084    cs.mb_ref[addr] = [0i8; 16];
8085    cs.mb_ref1[addr] = [0i8; 16];
8086    cs.last_delta_qp = 0;
8087}
8088
8089/// CABAC B-slice data coder. Mirrors `encode_slice_data_b`'s B_Skip-free check +
8090/// L0/L1/Bi/Direct RD decision verbatim; only the emit differs (per-MB
8091/// mb_skip_flag + CABAC + per-MB terminate). B is non-reference → no deblock/return.
8092#[allow(clippy::too_many_arguments)]
8093pub fn encode_slice_data_cabac_b(
8094    w: &mut BitWriter,
8095    cfg: &EncoderConfig,
8096    frame: &YuvFrame,
8097    qp: u8,
8098    poc: i32,
8099    l0: &crate::RefFrame,
8100    l1: &crate::RefFrame,
8101    qpo: &[i32],
8102) {
8103    let mut fe = FrameEncoder::new(cfg);
8104    fe.qp = qp;
8105    fe.qpc = chroma_qp(qp);
8106    fe.cur_qp = qp;
8107    if cfg.cabac_dz_div > 0 {
8108        fe.idz = cfg.cabac_dz_div; // CABAC-specific dead-zone override
8109    }
8110    fe.bi_w = implicit_bi_weights(poc, l0.poc, l1.poc);
8111    let (sy, su, sv) = coded_source(cfg, frame);
8112    let lambda = 0.85 * fe.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
8113    let lme = lambda.sqrt() * cfg.cabac_lambda_scale;
8114    let refs = std::slice::from_ref(l0);
8115    if fe.satd_q > 0.0 {
8116        let mut vars: Vec<i64> = (0..fe.mb_h)
8117            .flat_map(|my| (0..fe.mb_w).map(move |mx| (mx, my)))
8118            .map(|(mx, my)| mb_variance(&sy, fe.cw, mx, my))
8119            .collect();
8120        vars.sort_unstable();
8121        let idx = (((1.0 - fe.satd_q) * vars.len() as f64) as usize).min(vars.len() - 1);
8122        fe.satd_var_thresh = vars[idx];
8123    }
8124    let mut aq_qp = aq_qp_map(&sy, fe.cw, fe.mb_w, fe.mb_h, qp, fe.aq_strength);
8125    apply_mbtree_qpo(&mut aq_qp, qpo); // mb-tree temporal AQ (empty = byte-identical)
8126    fe.cur_qp = qp;
8127
8128    let mut cab = CabacEncoder::new(qp as i32, cfg.cabac_init_idc, false);
8129    let mut cs = CabacState::new(fe.mb_w * fe.mb_h);
8130    let total = fe.mb_w * fe.mb_h;
8131
8132    for mb_y in 0..fe.mb_h {
8133        for mb_x in 0..fe.mb_w {
8134            let mb_idx = mb_y * fe.mb_w + mb_x;
8135            let addr = mb_idx;
8136            let top = if mb_y > 0 { Some(addr - fe.mb_w) } else { None };
8137            let left = if mb_x > 0 { Some(addr - 1) } else { None };
8138            fe.qp = aq_qp[mb_idx];
8139            fe.qpc = chroma_qp(aq_qp[mb_idx]);
8140            let (lx, ly) = (mb_x * 16, mb_y * 16);
8141            let (pbx, pby) = (mb_x as isize * 4, mb_y as isize * 4);
8142            fe.mb_use_satd =
8143                fe.satd_q > 0.0 && mb_variance(&sy, fe.cw, mb_x, mb_y) >= fe.satd_var_thresh;
8144            let n0 = fe.mv_neighbors_block_list(pbx, pby, 4, 0);
8145            let n1 = fe.mv_neighbors_block_list(pbx, pby, 4, 1);
8146            let pmv0 = predict_partition_mv(0, 0, n0[0], n0[1], n0[2], 0);
8147            let pmv1 = predict_partition_mv(0, 0, n1[0], n1[1], n1[2], 0);
8148            let (dp, dc, dmotion) = fe.b_direct(l0, l1, mb_x, mb_y);
8149            // B_Skip: free direct prediction → mb_skip_flag = 1.
8150            if fe.skip_luma_is_free(&sy, mb_x, mb_y, &dp)
8151                && fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, &dc)
8152            {
8153                fe.commit_direct_motion(mb_x, mb_y, &dmotion);
8154                emit_b_skip_cabac(&mut cab, &mut cs, addr, top, left);
8155                {
8156                    let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
8157                    {
8158                let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
8159                cab.encode_terminate(mb_idx + 1 == total);
8160                if crate::bitacct::enabled() {
8161                    crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
8162                }
8163            }
8164                    if crate::bitacct::enabled() {
8165                        crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
8166                    }
8167                }
8168                continue;
8169            }
8170            let d_direct = fe.pred_dist(&sy, lx, ly, &dp);
8171            let (mv0, j0) = fe.motion_search(l0, &sy, lx, ly, 16, 16, &[pmv0], lme, None);
8172            let (mv1, j1) = fe.motion_search(l1, &sy, lx, ly, 16, 16, &[pmv1], lme, None);
8173            let d_bi = fe.bi_dist(l0, l1, &sy, lx, ly, mv0, mv1);
8174            let r_bi = mvd_bits(mv0.0 - pmv0.0) + mvd_bits(mv0.1 - pmv0.1)
8175                + mvd_bits(mv1.0 - pmv1.0) + mvd_bits(mv1.1 - pmv1.1);
8176            let j_bi = d_bi + (lme * r_bi as f64) as i64;
8177            let (mut dir, mut best) = (0u8, d_direct);
8178            if j0 < best { dir = 1; best = j0; }
8179            if j1 < best { dir = 2; best = j1; }
8180            if j_bi < best { dir = 3; best = j_bi; }
8181            let _ = best;
8182            // mb_skip_flag = 0, then the coded B MB.
8183            let sctx = 24
8184                + left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
8185                + top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
8186            let tskip = if crate::bitacct::enabled() { cab.pos() } else { 0 };
8187            cb_mb_skip(&mut cab, sctx, false);
8188            if crate::bitacct::enabled() {
8189                crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - tskip);
8190            }
8191            cs.mb_skip[addr] = false;
8192            let bspec = BInter { dir, l1, mv0, mv1 };
8193            let plan = fe.plan_inter_mb(refs, &sy, &su, &sv, mb_x, mb_y, 0, &[], Some(bspec));
8194            emit_mb_cabac_b(&mut fe, &mut cab, &mut cs, dir, &plan, mb_x, mb_y);
8195            {
8196                let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
8197                cab.encode_terminate(mb_idx + 1 == total);
8198                if crate::bitacct::enabled() {
8199                    crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
8200                }
8201            }
8202        }
8203    }
8204
8205    while !w.is_byte_aligned() {
8206        w.write_bit(true);
8207    }
8208    for b in cab.into_bytes() {
8209        w.write_bits(b as u32, 8);
8210    }
8211    // B is non-reference: no deblock, no RefFrame (the decoder deblocks for display).
8212}
8213
8214/// Minimal all-B_Skip CABAC B-slice (the rare no-bracketing-anchor fallback in
8215/// `code_picture`): every MB is mb_skip_flag = 1. B is non-reference so the recon
8216/// is irrelevant; this only needs to be a legal CABAC slice.
8217pub fn encode_all_skip_b_cabac(w: &mut BitWriter, cfg: &EncoderConfig, qp: u8, n: usize) {
8218    let mut cab = CabacEncoder::new(qp as i32, cfg.cabac_init_idc, false);
8219    for i in 0..n {
8220        // ctxInc = 24 + (left avail & not-skip) + (top avail & not-skip). Every
8221        // neighbour is either a skip (contributes 0) or unavailable (0) → always 24.
8222        cab.encode_decision(24, 1);
8223        cab.encode_terminate(i + 1 == n);
8224    }
8225    while !w.is_byte_aligned() {
8226        w.write_bit(true);
8227    }
8228    for b in cab.into_bytes() {
8229        w.write_bits(b as u32, 8);
8230    }
8231}