Skip to main content

rusty_h264_decoder/
mb16.rs

1//! I_16x16 macroblock decoding — the mirror of the encoder's `mb16`.
2//!
3//! Parses each macroblock's residuals and reconstructs it with the exact same
4//! prediction + inverse-transform helpers the encoder uses, so decoder output
5//! matches encoder reconstruction bit-for-bit.
6#![allow(clippy::needless_range_loop)]
7
8use rusty_h264_common::bit_reader::OutOfData;
9use rusty_h264_common::cavlc::{
10    decode_residual_block_with, read_cbp_inter, read_cbp_intra, un_scan_4x4_ac_into,
11    un_scan_4x4_dcac, vlc_tables,
12};
13use rusty_h264_common::inter::{
14    inter_partitions, mc_chroma_padded, mc_luma_padded, predict_mv, predict_partition_mv,
15    MvNeighbor,
16};
17use rusty_h264_common::predict::{
18    add_residual_8x8, chroma8x8_pred, chroma_qp, intra4x4_pred, intra8x8_pred, luma16x16_pred,
19    reconstruct_4x4_dc_into, reconstruct_4x4_into, I16Mode,
20    CHROMA_4X4_SCAN_XY, LUMA_4X4_SCAN_XY,
21};
22use rusty_h264_common::transform::{
23    dequant_scatter_4x4, dequantize, dequantize_weighted, inverse_quant_8x8,
24    inverse_quant_chroma_dc,
25    inverse_quant_chroma_dc_weighted, inverse_quant_luma_dc, inverse_quant_luma_dc_weighted,
26};
27use rusty_h264_common::{BitReader, YuvFrame};
28
29/// One frame's motion field, in 4x4-block raster (`mb_w*4` wide).
30///
31/// Captured from any conformant stream this decoder parses — including x264's —
32/// so a harness can compare motion fields between encoders without depending on
33/// external MV-export tooling.
34pub struct MvField {
35    pub mb_w: usize,
36    pub mb_h: usize,
37    pub mv: Vec<(i32, i32)>,
38    pub ref_idx: Vec<i32>,
39    pub inter: Vec<bool>,
40}
41
42/// Frames captured in decode order when `RFF_MV_DUMP=1`. Diagnostic only.
43pub static MV_DUMP: std::sync::Mutex<Vec<MvField>> = std::sync::Mutex::new(Vec::new());
44
45pub fn mv_dump_on() -> bool {
46    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
47    *ON.get_or_init(|| std::env::var("RFF_MV_DUMP").map_or(false, |v| v != "0"))
48}
49
50/// Copy filtered MB rows `[prev..mb_rows)` from coded-size planes into a
51/// Frame-MT progress slot's live padded planes, then bump `ready_rows`.
52/// Coarser publish: only advance the watermark every 2 MB rows (and always
53/// on the last row) to cut lock/notify traffic.
54fn publish_filtered_rows_to_slot(
55    slot: &crate::RefFrame,
56    rec_y: &[u8],
57    rec_u: &[u8],
58    rec_v: &[u8],
59    cw: usize,
60    ccw: usize,
61    ch: usize,
62    mb_rows: usize,
63) {
64    if mb_rows == 0 || slot.frozen.get().is_some() {
65        return;
66    }
67    if !crate::frame_mt::row_publish_on() {
68        return;
69    }
70    let mb_h = (ch + 15) / 16;
71    // Batch: defer watermark until even MB rows (or picture end).
72    if mb_rows < mb_h && (mb_rows & 1) != 0 {
73        return;
74    }
75    let Some(live) = &slot.live else {
76        slot.publish_ready_rows(mb_rows * 16);
77        return;
78    };
79    let cch = ch / 2;
80    let prev = slot.ready_rows.load(std::sync::atomic::Ordering::Acquire) / 16;
81    if mb_rows <= prev {
82        return;
83    }
84    let mut py = live.py.write().unwrap();
85    let mut pu = live.pu.write().unwrap();
86    let mut pv = live.pv.write().unwrap();
87    let ls = cw + 2 * crate::LPAD;
88    let cs = ccw + 2 * crate::CPAD;
89    for mr in prev..mb_rows {
90        for dy in 0..16 {
91            let y = mr * 16 + dy;
92            if y >= ch {
93                break;
94            }
95            let src = &rec_y[y * cw..(y + 1) * cw];
96            let dst_y = y + crate::LPAD;
97            // ONE row segment covering pad+picture+pad, then `split_at_mut` and
98            // two fills — the edge reads and the 2*LPAD writes were separate
99            // whole-plane indexes. Same shape as `expand_plane`.
100            let seg = &mut py[dst_y * ls..][..2 * crate::LPAD + cw];
101            let (lpad, rest) = seg.split_at_mut(crate::LPAD);
102            let (mid, rpad) = rest.split_at_mut(cw);
103            mid.copy_from_slice(src);
104            let (Some(&left), Some(&right)) = (mid.first(), mid.last()) else {
105                continue;
106            };
107            lpad.fill(left);
108            rpad[..crate::LPAD].fill(right);
109        }
110        for dy in 0..8 {
111            let cy = mr * 8 + dy;
112            if cy >= cch {
113                break;
114            }
115            for (rec, plane) in [(rec_u, &mut *pu), (rec_v, &mut *pv)] {
116                let src = &rec[cy * ccw..(cy + 1) * ccw];
117                let dst_y = cy + crate::CPAD;
118                let seg = &mut plane[dst_y * cs..][..2 * crate::CPAD + ccw];
119                let (lpad, rest) = seg.split_at_mut(crate::CPAD);
120                let (mid, rpad) = rest.split_at_mut(ccw);
121                mid.copy_from_slice(src);
122                let (Some(&left), Some(&right)) = (mid.first(), mid.last()) else {
123                    continue;
124                };
125                lpad.fill(left);
126                rpad[..crate::CPAD].fill(right);
127            }
128        }
129    }
130    if prev == 0 {
131        // Copy the whole first picture ROW upward instead of walking columns:
132        // `split_at_mut` at the pad boundary proves both sides at once.
133        let (pad, rest) = py.split_at_mut(crate::LPAD * ls);
134        if let Some(first) = rest.get(..ls) {
135            for row in pad.chunks_mut(ls) {
136                row.copy_from_slice(first);
137            }
138        }
139        for plane in [&mut *pu, &mut *pv] {
140            let (pad, rest) = plane.split_at_mut(crate::CPAD * cs);
141            if let Some(first) = rest.get(..cs) {
142                for row in pad.chunks_mut(cs) {
143                    row.copy_from_slice(first);
144                }
145            }
146        }
147    }
148    slot.publish_ready_rows(mb_rows * 16);
149}
150
151/// Reconstructed coded-size planes plus CAVLC `nnz` context grids.
152pub struct FrameDecoder {
153    mb_w: usize,
154    mb_h: usize,
155    /// Slice QP (`SliceQPy`) — the deblock filter's frame-level QP.
156    qp: u8,
157    /// Running luma QP (`QPy`), carried across macroblocks and stepped by each
158    /// `mb_qp_delta` (spec §7.4.5). Equals `qp` on constant-QP streams.
159    cur_qp: u8,
160    /// `chroma_qp_index_offset` from the active PPS (§8.5.8).
161    chroma_qp_offset: i32,
162    cw: usize,
163    ch: usize,
164    ccw: usize,
165    cch: usize,
166    rec_y: Vec<u8>,
167    rec_u: Vec<u8>,
168    rec_v: Vec<u8>,
169    /// Per-macroblock luma QP (`QPy`), for per-edge deblock strength.
170    mb_qp: Vec<u8>,
171    /// First macroblock address of the slice currently being decoded. Neighbors
172    /// with a lower address belong to an earlier slice and are "not available"
173    /// for prediction (spec §8.3/§8.4). Slices are contiguous raster ranges (we
174    /// reject FMO/slice-groups), so address ≥ this ⇔ same slice.
175    slice_first_mb: usize,
176    nnz_y: Vec<u8>,
177    nnz_c: [Vec<u8>; 2],
178    modes_y: Vec<u8>,
179    coded_y: Vec<bool>,
180    /// Per-4×4-block List-0 motion (mv + ref index, `-1` = no L0). For P slices
181    /// this is the only motion; B slices add the List-1 grids below.
182    mv_y: Vec<(i32, i32)>,
183    inter_y: Vec<bool>,
184    ref_idx_y: Vec<i32>,
185    /// Per-4×4-block List-1 motion for B slices (`ref_idx1 = -1` = no L1).
186    mv1: Vec<(i32, i32)>,
187    ref_idx1: Vec<i32>,
188    /// `RefPicList1` and B-slice flags (unused outside B slices).
189    refs1: Vec<crate::Ref>,
190    num_ref_active1: usize,
191    is_b: bool,
192    /// True if the stream's profile permits B-slices (`profile_idc != 66`). When
193    /// false (Baseline / Constrained Baseline), `as_reference_pooled` skips the per-block
194    /// motion (mv/ref_idx/ref_poc) that only B temporal/spatial direct ever reads.
195    b_possible: bool,
196    direct_spatial: bool,
197    nnz_l_cache: [u8; 25],
198    nnz_c_cache: [[u8; 9]; 2],
199    /// Decoded-picture buffer (most-recent first); empty in I-slices. `ref_idx`
200    /// indexes into this list.
201    refs: Vec<crate::Ref>,
202    /// `num_ref_idx_l0_active` for the current slice — drives whether `ref_idx`
203    /// is coded (active > 1) and its te(v)/ue(v) form, independently of how many
204    /// reference pictures actually exist (spec §7.4.5.1, §9.1).
205    num_ref_active: usize,
206    /// `constrained_intra_pred_flag`: when set, intra prediction may only use
207    /// samples from intra-coded neighbors (inter neighbors are "not available").
208    constrained_intra: bool,
209    /// High-profile 4×4 scaling matrices in **raster** order, indexed by
210    /// `[Y-intra, Cb-intra, Cr-intra, Y-inter, Cb-inter, Cr-inter]`. `None` = flat.
211    scaling: Option<[[i32; 16]; 6]>,
212    /// High-profile 8×8 luma scaling matrices in raster order `[Y-intra, Y-inter]`
213    /// (4:2:0 has only these two). `None` = flat.
214    scaling8: Option<[[i32; 64]; 2]>,
215    /// `transform_8x8_mode_flag` from the PPS: enables `transform_size_8x8_flag`.
216    transform_8x8_mode: bool,
217    /// SPS `qpprime_y_zero_transform_bypass_flag` — refusal gate in `step_qp`.
218    transform_bypass: bool,
219    /// GATE 1 router counters (big-oppy-decoder §2): skip MBs and
220    /// residual-coded MBs this picture. Two integer adds per MB, no atomics.
221    route_skip_mbs: u32,
222    route_coded_mbs: u32,
223    /// Per-slice `(first_mb, idc == 2)` in decode order — drives the
224    /// `disable_deblocking_filter_idc == 2` cross-slice-edge suppression.
225    slice_bounds: Vec<(usize, bool)>,
226    /// The CURRENT slice's `disable_deblocking_filter_idc == 2`, latched by
227    /// `set_deblock_params` and recorded per slice by the decode loops.
228    cur_idc2: bool,
229    /// Per-macroblock `transform_size_8x8_flag` (for deblocking: internal 4×4
230    /// luma edges of 8×8-transform MBs are not filtered).
231    mb_t8x8: Vec<bool>,
232    // ---- Row-interleaved deblocking state (docs/row-interleave-plan.md) ----
233    /// Per-MB boundary strengths, filled row-by-row as decode completes rows.
234    bs_frame: Vec<rusty_h264_common::deblock::MbBs>,
235    /// Rows whose bS is derived (watermark).
236    bs_rows: usize,
237    /// Rows already deblock-FILTERED (watermark; R3).
238    flt_rows: usize,
239    /// Two-row rolling window of packed records (prev = row r-1, cur = row r).
240    pk_prev: Vec<rusty_h264_common::deblock::MbPack>,
241    pk_cur: Vec<rusty_h264_common::deblock::MbPack>,
242    /// Transform-block coded mask (nnz with the 8x8 OR applied), filled per row.
243    nnz_dbr: Vec<u8>,
244    /// Unfiltered bottom rows of the last-filtered MB row (intra reads these).
245    bak_y: Vec<u8>,
246    bak_u: Vec<u8>,
247    bak_v: Vec<u8>,
248    /// Entropy-decouple: deferred pixel jobs + the per-slice activation flag
249    /// (CABAC slices only — the CAVLC loop has no flush hooks).
250    edc_jobs: Vec<EdcJob>,
251    edc_active: bool,
252    // ---- E2: the worker-thread plumbing (all None outside a threaded slice) ----
253    edc_tx: Option<std::sync::mpsc::SyncSender<EdcMsg>>,
254    edc_ctx_rx: Option<std::sync::mpsc::Receiver<PixelCtx>>,
255    edc_back_tx: Option<std::sync::mpsc::Sender<PixelCtx>>,
256    /// While the parse thread holds the pixel context for an intra macroblock
257    /// (planes moved into `self`), the rest of the context parks here.
258    edc_parked: Option<PixelCtx>,
259    /// E3: while parsing a B macroblock in threaded mode, its MC regions
260    /// accumulate here instead of executing (the pixel side is the worker's).
261    edc_regions: Option<Vec<BRegion>>,
262    /// Measurement only: previous full-MB single-rect direct (x, y, refi0,
263    /// refi1, m0, m1) for run-adjacency counting. No decode behavior.
264    bsk_last: Option<(usize, usize, i32, i32, (i32, i32), (i32, i32))>,
265    /// D10: jobs accumulated for the current row, sent as ONE message.
266    edc_batch: Vec<EdcJob>,
267    /// D12: bits/MB carried in from previous slices (0 = not yet known).
268    bits_per_mb: f64,
269    /// Current slice's deblock parameters (set per slice by the caller).
270    db_ena: bool,
271    db_oa: i32,
272    db_ob: i32,
273    /// Per-macroblock deblock derivation CLASS (`MB_KIND_*`), so the loop filter
274    /// can skip the 24-block neighbourhood gather on macroblocks whose strengths
275    /// are determined by syntax alone. Starts UNSET; anything left UNSET simply
276    /// takes the blind path, so a missed producer site costs speed, not
277    /// correctness. Only classes that are uniform BY SYNTAX are written — notably
278    /// NOT `B_Skip`/`B_Direct`, whose direct-derived motion varies per 4×4.
279    mb_kind: Vec<u8>,
280    /// Explicit weighted-prediction tables, when active for this slice.
281    weights: Option<WeightTable>,
282    /// `frame_mt::row_progress_on()` cached at construction (see
283    /// wait_refs_for_mb).
284    row_wait: bool,
285    /// Colocated-picture fast-probe cache (set_b_context): true when refs1[0]
286    /// is frozen (1T), short-term, with motion grids — col_zero then skips
287    /// the Option + live + long_term + w4 branch chain per probe.
288    col_ok: bool,
289    col_w4: usize,
290    /// Measurement knobs cached at construction — these were OnceLock derefs
291    /// on EVERY skip macroblock (no_bskipfast per B skip, no_runmv per P
292    /// skip); one field test now.
293    k_no_bskipfast: bool,
294    k_no_runmv: bool,
295    /// rowdb_on()/rowhook_eager() cached at construction — these were atomic
296    /// loads inside row_hook_at on EVERY macroblock.
297    k_rowdb: bool,
298    k_roweager: bool,
299    /// MEASUREMENT KNOB, cached. `kind_loads()` is a `OnceLock` deref and was
300    /// evaluated as a MATCH GUARD on every Skip/InterUniform macroblock in
301    /// `derive_bs_row` — the dominant class. The census (DBSDERIVE kindarm=0 on
302    /// 6/6 corpus streams) shows the arm it guards never fires by default, so
303    /// every one of those derefs was pure tax. Same treatment `k_rowdb` /
304    /// `k_roweager` already had.
305    k_kindloads: bool,
306    /// True once ANY macroblock in this picture used the 8x8 transform. The
307    /// `nnz_dbr` row copy + the per-row t8 scan in `derive_bs_row` exist ONLY
308    /// to apply the 8x8 coded-status OR; with no t8 macroblock `nnz_dbr` is a
309    /// byte-for-byte copy of `nnz_y`, so both are skipped and the derivation
310    /// reads `nnz_y` directly. Census: t8mb=0 on every cavlc/main stream.
311    any_t8: bool,
312    /// True once any slice in this picture carries
313    /// `disable_deblocking_filter_idc == 2`. Replaces a per-ROW
314    /// `slice_bounds.iter().any(..)` scan (census: idc2row=0 on the corpus).
315    any_idc2: bool,
316    /// Raster address whose P_Skip MV is FORCED (0,0): the previous
317    /// decode_p_skip was at addr-1 and committed (0,0), so this MB's left
318    /// neighbor either fires §8.4.1.1's zero-MV rule (in-slice: ref 0,
319    /// (0,0)) or is unavailable (out-of-slice — same rule). Interior MBs of
320    /// a skip run skip the 3-neighbor gather entirely.
321    skip_zero_next: usize,
322    /// Pending B_Skip grid+recon span: (mb row, x0, n, kind) of CONSECUTIVE
323    /// fast B_Skips with IDENTICAL committed values, deferred and range-
324    /// filled/band-reconned at flush. Flushed before ANY grid or pixel
325    /// reader runs — see span_flush callers.
326    bzspan: Option<(usize, usize, usize, BzKind)>,
327    /// Pending P_Skip (0,0) grid-commit span — the P mirror of `bzspan`
328    /// (values: ref 0 list-0, mv (0,0), inter, coded, DC mode, kind SKIP).
329    /// The bool records whether the RECON is deferred too (1T, identity/no
330    /// weights) — decided at push time, not re-derived at flush (begin_slice
331    /// may have changed the fields in between).
332    pzspan: Option<(usize, usize, usize, bool)>,
333    /// Per-picture bitmap: MB decoded as a ref0/(0,0)-both-lists zero-bi fast
334    /// B_Skip. Never cleared mid-picture — false only ever means "derive
335    /// normally" — so no MB path carries a clearing duty.
336    bzero: Vec<bool>,
337    /// Pooled CABAC slice scratch — refilled (not reallocated) at slice entry;
338    /// carried across pictures via `GridPool`. Taken out of `self` at
339    /// `decode_slice_cabac_inner` entry and put back at its normal exit (an
340    /// error exit simply forfeits the pooled allocation).
341    sc_cat: Vec<u8>,
342    sc_cbp: Vec<u8>,
343    sc_cmode: Vec<i32>,
344    sc_nzc: Vec<[u8; 24]>,
345    sc_cbfdc: Vec<u16>,
346    sc_skip: Vec<bool>,
347    sc_ref: Vec<[i8; 16]>,
348    sc_mvd: Vec<[[i16; 2]; 16]>,
349    sc_ref1: Vec<[i8; 16]>,
350    sc_mvd1: Vec<[[i16; 2]; 16]>,
351    sc_direct: Vec<bool>,
352    /// Lazily cached `implicit_weights(0, 0)` — slice-constant (POCs of
353    /// refs[0]/refs1[0] and cur don't change within a slice). Reset in
354    /// set_b_context.
355    iw00: Option<Option<(i32, i32)>>,
356    /// Cached `weights.list_identity(0)` — all list-0 refs identity; gates the
357    /// whole weight_partition pass for P inter (one choke point, all sites).
358    weights_l0id: bool,
359    /// Cached `weights.ref0_identity()` — identity tables (the x264 weightp=2
360    /// common case) make every ref-0 weighting pass an exact no-op, and the
361    /// skip fast paths check this per MB, so it is computed once per slice.
362    weights_id0: bool,
363    /// Current picture's `PicOrderCnt` (for temporal direct + implicit weighting).
364    cur_poc: i32,
365    /// `weighted_bipred_idc` (0 = none/average, 1 = explicit, 2 = implicit).
366    weighted_bipred_idc: u8,
367    /// `direct_8x8_inference_flag` (B direct co-located sub-block selection).
368    direct_8x8_inference: bool,
369    /// Frame-MT Phase B: shared progress Arc for the picture being decoded.
370    progress: Option<crate::Ref>,
371    /// Slice-stable ref→POC tables for bS (≤16 entries). `derive_bs_row` used
372    /// to `collect()` these every row — same values for the whole slice.
373    ref_poc0: Vec<i32>,
374    ref_poc1: Vec<i32>,
375}
376
377/// Explicit weighted-prediction tables (spec §7.4.3.2 / §8.4.2.3.2). Per
378/// reference list, per ref index: a luma `(weight, offset)` and two chroma
379/// `(weight, offset)` (Cb, Cr). `log2` denominators are shared.
380#[derive(Clone, Default)]
381pub struct WeightTable {
382    pub luma_log2_denom: i32,
383    pub chroma_log2_denom: i32,
384    /// `[list][ref_idx] = (weight, offset)`.
385    pub luma: [Vec<(i32, i32)>; 2],
386    /// `[list][ref_idx][cb=0/cr=1] = (weight, offset)`.
387    pub chroma: [Vec<[(i32, i32); 2]>; 2],
388}
389
390impl WeightTable {
391    /// True when list-0 ref-0 carries identity weights (w == 1<<denom, offset 0)
392    /// for luma and both chroma planes. x264's weightp=2 puts a pred_weight_table
393    /// in EVERY P slice, but outside fades the entries are identity — and the
394    /// identity weight is an EXACT no-op ((s*2^d + 2^(d-1))>>d + 0 == s), so a
395    /// P_Skip recon may bypass the weighting pass byte-identically.
396    fn ref0_identity(&self) -> bool {
397        self.luma[0].first().is_some_and(|&(w, o)| w == 1 << self.luma_log2_denom && o == 0)
398            && self.chroma[0].first().is_some_and(|c| {
399                c.iter().all(|&(w, o)| w == 1 << self.chroma_log2_denom && o == 0)
400            })
401    }
402
403    /// True when EVERY entry of `list` (all refs, luma + both chroma) is the
404    /// identity weight — the x264 weightp=2 shape outside fades. Identity is
405    /// an exact per-pixel no-op (see ref0_identity), so the whole per-ref
406    /// weighting pass may be skipped for this list.
407    fn list_identity(&self, list: usize) -> bool {
408        self.luma[list].iter().all(|&(w, o)| w == 1 << self.luma_log2_denom && o == 0)
409            && self.chroma[list].iter().all(|c| {
410                c.iter().all(|&(w, o)| w == 1 << self.chroma_log2_denom && o == 0)
411            })
412    }
413
414    /// Applies a single-list (uni-prediction) luma weight (spec §8.4.2.3.2).
415    /// Resolves a list/ref slot's (weight, offset) ONCE. `list` is 0..1 and the
416    /// fallback is the IDENTITY weight, which is what an absent entry means.
417    #[inline]
418    fn luma_wo(&self, list: usize, refi: usize) -> (i32, i32) {
419        self.luma[list & 1].get(refi).copied().unwrap_or((1 << self.luma_log2_denom, 0))
420    }
421
422    /// Ditto for chroma component `cc`.
423    #[inline]
424    fn chroma_wo(&self, list: usize, refi: usize, cc: usize) -> (i32, i32) {
425        self.chroma[list & 1]
426            .get(refi)
427            .map(|c| c[cc & 1])
428            .unwrap_or((1 << self.chroma_log2_denom, 0))
429    }
430
431    /// [`Self::apply_luma`] with the weight already resolved — the form the
432    /// per-sample loops use, so the lookup is not repeated 256 times.
433    #[inline]
434    fn apply_luma_wo(&self, sample: u8, w: i32, o: i32) -> u8 {
435        let lwd = self.luma_log2_denom;
436        let v = if lwd >= 1 {
437            ((sample as i32 * w + (1 << (lwd - 1))) >> lwd) + o
438        } else {
439            sample as i32 * w + o
440        };
441        v.clamp(0, 255) as u8
442    }
443
444    /// [`Self::apply_chroma`] with the weight already resolved.
445    #[inline]
446    fn apply_chroma_wo(&self, sample: u8, w: i32, o: i32) -> u8 {
447        let cwd = self.chroma_log2_denom;
448        let v = if cwd >= 1 {
449            ((sample as i32 * w + (1 << (cwd - 1))) >> cwd) + o
450        } else {
451            sample as i32 * w + o
452        };
453        v.clamp(0, 255) as u8
454    }
455
456    fn apply_luma(&self, sample: u8, list: usize, refi: usize) -> u8 {
457        let (w, o) = self.luma_wo(list, refi);
458        let lwd = self.luma_log2_denom;
459        let v = if lwd >= 1 {
460            ((sample as i32 * w + (1 << (lwd - 1))) >> lwd) + o
461        } else {
462            sample as i32 * w + o
463        };
464        v.clamp(0, 255) as u8
465    }
466
467    /// Applies a single-list (uni-prediction) chroma weight for component `cc`.
468    fn apply_chroma(&self, sample: u8, list: usize, refi: usize, cc: usize) -> u8 {
469        let (w, o) = self.chroma_wo(list, refi, cc);
470        let cwd = self.chroma_log2_denom;
471        let v = if cwd >= 1 {
472            ((sample as i32 * w + (1 << (cwd - 1))) >> cwd) + o
473        } else {
474            sample as i32 * w + o
475        };
476        v.clamp(0, 255) as u8
477    }
478}
479
480/// Why a macroblock could not be decoded.
481#[derive(Debug, Clone, PartialEq, Eq)]
482pub enum MbError {
483    Truncated,
484    Unsupported(&'static str),
485}
486
487impl From<OutOfData> for MbError {
488    fn from(_: OutOfData) -> Self {
489        MbError::Truncated
490    }
491}
492
493/// Recycled per-picture scratch grids.
494///
495/// `FrameDecoder::new` used to allocate ~1.65 MB of frame-wide grids for EVERY
496/// coded picture and drop them when the picture finished. The sampled profiler
497/// prices that (stage `dec-setup`) at 6.7% of decode — larger than dequant,
498/// reconstruct and intra prediction combined, and none of it is codec work.
499///
500/// Two costs are being paid, and the allocation is the bigger one. A ~460 KB
501/// `Vec` goes straight to the OS, so every page is a fresh zero page and the
502/// decoder takes a soft page fault on FIRST TOUCH of each 4 KB — a cost charged
503/// to whatever per-macroblock stage happens to touch it first, not to the
504/// allocation. Handing the same buffers back keeps the pages mapped and warm.
505///
506/// The initialising fill is NOT skipped: these grids are read as neighbour
507/// context (`modes_y` must read 2/DC, `ref_idx_y` must read -1) before every
508/// block that writes them, so a stale value from the previous picture is a
509/// correctness bug, not a performance trade. `clear()` + `resize()` keeps the
510/// fill and drops only the allocation.
511///
512/// The reconstruction planes are deliberately NOT pooled: `into_frame` MOVES
513/// them out as the caller's output frame, so there is nothing to hand back.
514#[derive(Default)]
515pub struct GridPool {
516    /// D12: running bits-per-macroblock of decoded slices, the E2 dispatch's
517    /// density signal. Lives here because `GridPool` is the only state that
518    /// survives a picture (`FrameDecoder` is rebuilt per picture).
519    bits_per_mb: f64,
520    mb_qp: Vec<u8>,
521    bs_frame: Vec<rusty_h264_common::deblock::MbBs>,
522    pk_prev: Vec<rusty_h264_common::deblock::MbPack>,
523    pk_cur: Vec<rusty_h264_common::deblock::MbPack>,
524    nnz_dbr: Vec<u8>,
525    bak_y: Vec<u8>,
526    bak_u: Vec<u8>,
527    bak_v: Vec<u8>,
528    nnz_y: Vec<u8>,
529    nnz_c0: Vec<u8>,
530    nnz_c1: Vec<u8>,
531    modes_y: Vec<u8>,
532    coded_y: Vec<bool>,
533    mv_y: Vec<(i32, i32)>,
534    inter_y: Vec<bool>,
535    ref_idx_y: Vec<i32>,
536    mv1: Vec<(i32, i32)>,
537    ref_idx1: Vec<i32>,
538    mb_t8x8: Vec<bool>,
539    mb_kind: Vec<u8>,
540    bzero: Vec<bool>,
541    // CABAC slice scratch (D13 follow-through): these were fresh `vec![..]`
542    // allocations at EVERY slice entry — ~407 KB per P slice, ~700 KB per B
543    // slice at 720p, the same fresh-page class GridPool exists to kill.
544    sc_cat: Vec<u8>,
545    sc_cbp: Vec<u8>,
546    sc_cmode: Vec<i32>,
547    sc_nzc: Vec<[u8; 24]>,
548    sc_cbfdc: Vec<u16>,
549    sc_skip: Vec<bool>,
550    sc_ref: Vec<[i8; 16]>,
551    sc_mvd: Vec<[[i16; 2]; 16]>,
552    sc_ref1: Vec<[i8; 16]>,
553    sc_mvd1: Vec<[[i16; 2]; 16]>,
554    sc_direct: Vec<bool>,
555    // D24 (inline-execution.md 11.10): the ref-POC mirrors were the only
556    // per-picture Vecs NOT recycled - a fresh alloc + collect per picture each.
557    ref_poc0: Vec<i32>,
558    ref_poc1: Vec<i32>,
559}
560
561
562/// Reuse `v`'s allocation for `n` copies of `val`. Identical OBSERVABLE result to
563/// `vec![val; n]`; differs only in that it reuses the existing allocation when the
564/// capacity already suffices.
565#[inline]
566fn refill<T: Clone>(mut v: Vec<T>, n: usize, val: T) -> Vec<T> {
567    v.clear();
568    v.resize(n, val);
569    v
570}
571
572impl FrameDecoder {
573    /// Non-pooled ctor — tests only; production always enters via `with_pool`
574    /// (`GridPool` recycling, see lib.rs).
575    #[cfg(test)]
576    pub fn new(
577        mb_w: usize,
578        mb_h: usize,
579        qp: u8,
580        chroma_qp_offset: i32,
581        refs: Vec<crate::Ref>,
582        num_ref_active: usize,
583        constrained_intra: bool,
584        transform_8x8_mode: bool,
585        b_possible: bool,
586    ) -> Self {
587        Self::with_pool(
588            mb_w,
589            mb_h,
590            qp,
591            chroma_qp_offset,
592            refs,
593            num_ref_active,
594            constrained_intra,
595            transform_8x8_mode,
596            b_possible,
597            GridPool::default(),
598        )
599    }
600
601    /// As `new`, but reusing a previous picture's grid allocations. See `GridPool`.
602    #[allow(clippy::too_many_arguments)]
603    pub fn with_pool(
604        mb_w: usize,
605        mb_h: usize,
606        qp: u8,
607        chroma_qp_offset: i32,
608        refs: Vec<crate::Ref>,
609        num_ref_active: usize,
610        constrained_intra: bool,
611        transform_8x8_mode: bool,
612        b_possible: bool,
613        pool: GridPool,
614    ) -> Self {
615        let (cw, ch) = (mb_w * 16, mb_h * 16);
616        let (ccw, cch) = (cw / 2, ch / 2);
617        let bits_per_mb = pool.bits_per_mb;
618        let ref_poc0 = {
619            let mut v = pool.ref_poc0;
620            v.clear();
621            v.extend(refs.iter().map(|f| f.pic_poc()));
622            v
623        };
624        Self {
625            mb_w,
626            mb_h,
627            qp,
628            cur_qp: qp,
629            chroma_qp_offset,
630            cw,
631            ch,
632            ccw,
633            cch,
634            rec_y: vec![0; cw * ch],
635            rec_u: vec![0; ccw * cch],
636            rec_v: vec![0; ccw * cch],
637            mb_qp: refill(pool.mb_qp, mb_w * mb_h, qp),
638            slice_first_mb: 0,
639            nnz_y: refill(pool.nnz_y, (mb_w * 4) * (mb_h * 4), 0),
640            nnz_c: [
641                refill(pool.nnz_c0, (mb_w * 2) * (mb_h * 2), 0),
642                refill(pool.nnz_c1, (mb_w * 2) * (mb_h * 2), 0),
643            ],
644            modes_y: refill(pool.modes_y, (mb_w * 4) * (mb_h * 4), 2),
645            coded_y: refill(pool.coded_y, (mb_w * 4) * (mb_h * 4), false),
646            mv_y: refill(pool.mv_y, (mb_w * 4) * (mb_h * 4), (0, 0)),
647            inter_y: refill(pool.inter_y, (mb_w * 4) * (mb_h * 4), false),
648            ref_idx_y: refill(pool.ref_idx_y, (mb_w * 4) * (mb_h * 4), -1),
649            mv1: refill(pool.mv1, (mb_w * 4) * (mb_h * 4), (0, 0)),
650            ref_idx1: refill(pool.ref_idx1, (mb_w * 4) * (mb_h * 4), -1),
651            refs1: Vec::new(),
652            num_ref_active1: 0,
653            is_b: false,
654            b_possible,
655            direct_spatial: true,
656            nnz_l_cache: [0x80; 25],
657            nnz_c_cache: [[0x80; 9]; 2],
658            refs,
659            num_ref_active,
660            constrained_intra,
661            scaling: None,
662            scaling8: None,
663            transform_8x8_mode,
664            transform_bypass: false,
665            route_skip_mbs: 0,
666            route_coded_mbs: 0,
667            slice_bounds: Vec::new(),
668            cur_idc2: false,
669            mb_t8x8: refill(pool.mb_t8x8, mb_w * mb_h, false),
670            bs_frame: refill(pool.bs_frame, mb_w * mb_h, Default::default()),
671            bs_rows: 0,
672            flt_rows: 0,
673            pk_prev: {
674                let mut v = pool.pk_prev;
675                v.clear();
676                v
677            },
678            pk_cur: {
679                let mut v = pool.pk_cur;
680                v.clear();
681                v
682            },
683            nnz_dbr: refill(pool.nnz_dbr, (mb_w * 4) * (mb_h * 4), 0),
684            bak_y: refill(pool.bak_y, cw, 0),
685            bak_u: refill(pool.bak_u, ccw, 0),
686            bak_v: refill(pool.bak_v, ccw, 0),
687            edc_jobs: Vec::new(),
688            edc_active: false,
689            edc_tx: None,
690            edc_ctx_rx: None,
691            edc_back_tx: None,
692            edc_parked: None,
693            edc_regions: None,
694            bsk_last: None,
695            edc_batch: Vec::new(),
696            bits_per_mb,
697            db_ena: false,
698            db_oa: 0,
699            db_ob: 0,
700            mb_kind: refill(
701                pool.mb_kind,
702                mb_w * mb_h,
703                rusty_h264_common::deblock::MB_KIND_UNSET,
704            ),
705            weights: None,
706            row_wait: crate::frame_mt::row_progress_on(),
707            col_ok: false,
708            col_w4: 0,
709            k_no_bskipfast: no_bskipfast(),
710            k_no_runmv: no_runmv(),
711            k_rowdb: rowdb_on(),
712            k_roweager: rowhook_eager(),
713            k_kindloads: kind_loads(),
714            any_t8: false,
715            any_idc2: false,
716            skip_zero_next: usize::MAX,
717            bzero: refill(pool.bzero, mb_w * mb_h, false),
718            sc_cat: pool.sc_cat,
719            sc_cbp: pool.sc_cbp,
720            sc_cmode: pool.sc_cmode,
721            sc_nzc: pool.sc_nzc,
722            sc_cbfdc: pool.sc_cbfdc,
723            sc_skip: pool.sc_skip,
724            sc_ref: pool.sc_ref,
725            sc_mvd: pool.sc_mvd,
726            sc_ref1: pool.sc_ref1,
727            sc_mvd1: pool.sc_mvd1,
728            sc_direct: pool.sc_direct,
729            bzspan: None,
730            pzspan: None,
731            iw00: None,
732            weights_id0: false,
733            weights_l0id: false,
734            cur_poc: 0,
735            weighted_bipred_idc: 0,
736            direct_8x8_inference: false,
737            progress: None,
738            ref_poc0,
739            ref_poc1: {
740                let mut v = pool.ref_poc1;
741                v.clear();
742                v
743            },
744        }
745    }
746
747    fn refresh_ref_pocs(&mut self) {
748        self.ref_poc0.clear();
749        self.ref_poc0.extend(self.refs.iter().map(|f| f.pic_poc()));
750        self.ref_poc1.clear();
751        self.ref_poc1.extend(self.refs1.iter().map(|f| f.pic_poc()));
752    }
753
754    /// Frame-MT Phase B: attach the shared progress Arc for row watermarks.
755    pub fn set_progress_slot(&mut self, slot: crate::Ref) {
756        self.progress = Some(slot);
757    }
758
759    /// Wait until every L0/L1 ref has enough ready luma rows for MB row `mb_y`
760    /// (pad conservatively for MV overshoot).
761    #[inline(always)]
762    fn wait_refs_for_mb(&self, mb_y: usize) {
763        // Cached at construction: in 1T (row-progress off, the default) this
764        // is one field test instead of a fn call + OnceLock read per MB.
765        if !self.row_wait {
766            return;
767        }
768        let need = crate::RefFrame::rows_needed_for_mb(mb_y, self.ch);
769        crate::RefFrame::set_mc_row_need(mb_y, self.ch);
770        for r in self.refs.iter().chain(self.refs1.iter()) {
771            // Only pay the wait when the ref may still be in-flight (Phase B).
772            if r.live.is_some() && r.frozen.get().is_none() {
773                r.wait_ready_rows(need);
774            }
775        }
776    }
777
778    fn publish_progress(&mut self) {
779        let Some(slot) = &self.progress else {
780            return;
781        };
782        // Under EDC the worker publishes (PixelCtx::publish_progress_rows).
783        if self.edc_tx.is_some() {
784            return;
785        }
786        // Cached field, not the knob function: this runs per ROW.
787        if !self.k_rowdb || !self.db_ena || self.flt_rows == 0 {
788            return;
789        }
790        publish_filtered_rows_to_slot(
791            slot,
792            &self.rec_y,
793            &self.rec_u,
794            &self.rec_v,
795            self.cw,
796            self.ccw,
797            self.ch,
798            self.flt_rows,
799        );
800    }
801
802    /// Sets the explicit weighted-prediction tables for this slice.
803    pub fn set_weights(&mut self, weights: WeightTable) {
804        self.weights_id0 = weights.ref0_identity();
805        self.weights_l0id = weights.list_identity(0);
806        self.weights = Some(weights);
807    }
808
809    /// Applies explicit uni-prediction weighting to a motion-compensated partition
810    /// (luma `pred_y` region + the two chroma planes), if weighting is active.
811    /// `list` is the reference list and `refi` the partition's reference index.
812    fn weight_partition(
813        &self,
814        pred_y: &mut [u8; 256],
815        c_pred: &mut [[u8; 64]; 2],
816        list: usize,
817        refi: usize,
818        rx: usize,
819        ry: usize,
820        rw: usize,
821        rh: usize,
822    ) {
823        let Some(wt) = &self.weights else { return };
824        // Identity list-0 table (x264 weightp=2 outside fades): the whole pass
825        // is an exact per-pixel no-op — skip it. RS_H264_NO_SKIPFP restores it
826        // for paired A/B (same knob family as the P_Skip weight skip).
827        if list == 0 && self.weights_l0id && !no_skipfp() {
828            edcstat::bump(&edcstat::WP_SKIPPED, 1);
829            return;
830        }
831        // REFUTED, reverted: row-slicing these loops measured +28% instructions
832        // on `weight_partition` (the extents are runtime values, so the slice
833        // bounds cost more than the per-sample checks they replaced). The real
834        // win for this function was hoisting the identity test to its CALLERS,
835        // which stands.
836        // RESOLVE ONCE, APPLY MANY. `apply_luma` re-read `self.luma[list][refi]`
837        // — an array index, a Vec deref and an element index, two of them bounds
838        // checked — on EVERY sample, up to 256 luma plus 128 chroma per call.
839        // The weight is a property of the partition, not of the pixel. (The loop
840        // SHAPE is untouched: row-slicing it is refuted above.)
841        // MASKED, not row-sliced. `pred_y` is a fixed `[u8; 256]` and `c_pred` a
842        // `[[u8; 64]; 2]`, so `& 255` / `& 63` are no-ops that prove the index
843        // outright — where SLICING these loops cost +28% (the extents are runtime
844        // values, so the slice bounds outweigh the checks). Same lesson as
845        // `luma_centre`: the refutation was of one SHAPE, not of the goal.
846        let (lw, lo) = wt.luma_wo(list, refi);
847        for dy in 0..rh {
848            for dx in 0..rw {
849                let i = ((ry + dy) * 16 + (rx + dx)) & 255;
850                pred_y[i] = wt.apply_luma_wo(pred_y[i], lw, lo);
851            }
852        }
853        let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
854        for cc in 0..2 {
855            let (cw, co) = wt.chroma_wo(list, refi, cc);
856            let plane = &mut c_pred[cc & 1];
857            for dy in 0..crh {
858                for dx in 0..crw {
859                    let i = ((cry + dy) * 8 + (crx + dx)) & 63;
860                    plane[i] = wt.apply_chroma_wo(plane[i], cw, co);
861                }
862            }
863        }
864    }
865
866    /// Sets the High-profile scaling matrices (raster order: six 4×4 lists, two
867    /// 8×8 luma lists). The caller un-zig-zags the SPS lists. Flat is the default.
868    /// GATE 1 router counters: (skip MBs, residual-coded MBs) this picture.
869    pub fn route_counters(&self) -> (u32, u32) {
870        (self.route_skip_mbs, self.route_coded_mbs)
871    }
872
873    /// SPS `qpprime_y_zero_transform_bypass_flag` — see [`Self::step_qp`].
874    pub fn set_transform_bypass(&mut self, on: bool) {
875        self.transform_bypass = on;
876    }
877
878    pub fn set_scaling(&mut self, scaling: [[i32; 16]; 6], scaling8: [[i32; 64]; 2]) {
879        self.scaling = Some(scaling);
880        self.scaling8 = Some(scaling8);
881    }
882
883    /// Dequantizes a 4×4 AC block with scaling list `list` (flat if none active).
884    fn dequant(&self, levels: &[i32; 16], qp: u8, list: usize) -> [i32; 16] {
885        match &self.scaling {
886            Some(s) => dequantize_weighted(levels, qp, &s[list]),
887            None => dequantize(levels, qp),
888        }
889    }
890
891    /// Single-coefficient twin of `dequant` for position 0 (DC-only fast path).
892    fn dequant_dc4(&self, level: i32, qp: u8, list: usize) -> i32 {
893        rusty_h264_common::transform::dequantize_dc4(
894            level,
895            qp,
896            self.scaling.as_ref().map(|s| s[list][0]),
897        )
898    }
899
900    /// Inverse-quantizes the I_16x16 luma DC with scaling list `list`'s DC weight.
901    fn dequant_luma_dc(&self, levels: &[i32; 16], qp: u8, list: usize) -> [i32; 16] {
902        match &self.scaling {
903            Some(s) => inverse_quant_luma_dc_weighted(levels, qp, s[list][0]),
904            None => inverse_quant_luma_dc(levels, qp),
905        }
906    }
907
908    /// Inverse-quantizes a chroma DC block with scaling list `list`'s DC weight.
909    fn dequant_chroma_dc(&self, levels: &[i32; 4], qp: u8, list: usize) -> [i32; 4] {
910        match &self.scaling {
911            Some(s) => inverse_quant_chroma_dc_weighted(levels, qp, s[list][0]),
912            None => inverse_quant_chroma_dc(levels, qp),
913        }
914    }
915
916    /// Sets the B-slice context for the slice about to be decoded: `RefPicList1`,
917    /// its active count, and the direct-mode flag.
918    #[allow(clippy::too_many_arguments)]
919    pub fn set_b_context(
920        &mut self,
921        refs1: Vec<crate::Ref>,
922        num_ref_active1: usize,
923        direct_spatial: bool,
924        cur_poc: i32,
925        weighted_bipred_idc: u8,
926        direct_8x8_inference: bool,
927    ) {
928        self.is_b = true;
929        self.refs1 = refs1;
930        self.num_ref_active1 = num_ref_active1;
931        self.direct_spatial = direct_spatial;
932        self.cur_poc = cur_poc;
933        self.weighted_bipred_idc = weighted_bipred_idc;
934        self.direct_8x8_inference = direct_8x8_inference;
935        self.iw00 = None; // slice-scoped cache: lists/POC may have changed
936        // Colocated fast-probe cache (see col_zero / col_zero_fast).
937        self.col_ok = self.refs1.first().is_some_and(|c| {
938            c.live.is_none() && !c.long_term && c.w4 != 0
939        });
940        self.col_w4 = self.refs1.first().map_or(0, |c| c.w4);
941        self.refresh_ref_pocs();
942    }
943
944    /// Cached `implicit_weights(0, 0)` — constant within a slice.
945    fn iw00(&mut self) -> Option<(i32, i32)> {
946        // Read the discriminant ONCE. The `is_none()` + `unwrap()` pair tested it
947        // twice and carried an `unwrap_failed` path for a value this very function
948        // had just written. `get_or_insert_with` cannot be used here: the closure
949        // would need `&mut self` while the `Option` is already borrowed.
950        match self.iw00 {
951            Some(v) => v,
952            None => {
953                let v = if self.refs.is_empty() || self.refs1.is_empty() {
954                    None
955                } else {
956                    self.implicit_weights(0, 0)
957                };
958                self.iw00 = Some(v);
959                v
960            }
961        }
962    }
963
964    /// Steps the running luma QP by a `mb_qp_delta` (spec §7.4.5, 8-bit depth):
965    /// `QPy = (QPy_prev + delta + 52) % 52`.
966    /// Steps QPy by `mb_qp_delta` (spec §7.4.5 modulo). Called exactly for
967    /// residual-coded macroblocks (mb_qp_delta presence ⇔ cbp != 0 or I_16x16),
968    /// which makes it the one chokepoint for the transform-bypass refusal:
969    /// with `qpprime_y_zero_transform_bypass_flag` set and QP'Y == 0 the
970    /// residual is LOSSLESS-bypassed (no transform, no quant — and DPCM intra
971    /// forms), which this decoder does not implement. Refusing here is loud
972    /// and exact: all-PCM lossless streams (no mb_qp_delta) still decode.
973    fn step_qp(&mut self, delta: i32) -> Result<(), MbError> {
974        // Router counter: step_qp fires exactly once per residual-coded MB.
975        self.route_coded_mbs += 1;
976        self.cur_qp = (self.cur_qp as i32 + delta + 52).rem_euclid(52) as u8;
977        if self.transform_bypass && self.cur_qp == 0 {
978            return Err(MbError::Unsupported("transform-bypass (lossless) macroblock"));
979        }
980        Ok(())
981    }
982
983    /// Maps a luma QP to its chroma QP, applying `chroma_qp_index_offset`
984    /// (spec §8.5.8): `QPc = qpc_table(Clip3(0, 51, QPy + offset))`.
985    fn chroma_qp_for(&self, qp_y: u8) -> u8 {
986        let qpi = (qp_y as i32 + self.chroma_qp_offset).clamp(0, 51) as u8;
987        chroma_qp(qpi)
988    }
989
990    /// Resets per-slice state before decoding a continuation slice of the same
991    /// picture: the running QP (each slice carries its own `slice_qp`) and the
992    /// reference list (each slice may reorder it).
993    pub fn begin_slice(&mut self, slice_qp: u8, refs: Vec<crate::Ref>, num_ref_active: usize) {
994        self.cur_qp = slice_qp;
995        self.qp = slice_qp;
996        self.refs = refs;
997        self.num_ref_active = num_ref_active;
998        self.weights = None; // re-set per slice if a pred_weight_table is present
999        self.weights_id0 = false;
1000        self.weights_l0id = false;
1001        self.refresh_ref_pocs();
1002    }
1003
1004    /// Whether the neighbor macroblock at `(nbx, nby)` is in the slice currently
1005    /// being decoded (address ≥ the slice's first MB). For single-slice pictures
1006    /// `slice_first_mb == 0`, so this is always true and prediction is unchanged.
1007    #[inline]
1008    fn nbr_in_slice(&self, nbx: usize, nby: usize) -> bool {
1009        nby * self.mb_w + nbx >= self.slice_first_mb
1010    }
1011
1012    /// Whether the neighbor 4×4 block at `(nbx, nby)` may contribute to intra
1013    /// prediction. With `constrained_intra_pred`, an inter-coded neighbor is
1014    /// treated as unavailable (spec §8.3.1.2.{1,2}); otherwise always usable.
1015    #[inline]
1016    fn intra_nbr_ok(&self, nbx: usize, nby: usize) -> bool {
1017        // Fallible read: this is inlined at eleven sites across the intra paths,
1018        // and `constrained_intra` short-circuits it on nearly every stream, so
1019        // the check was pure tax on a value that is usually never loaded.
1020        // `unwrap_or(true)` is the conservative direction — an out-of-range
1021        // neighbour reads as inter, i.e. UNAVAILABLE, which is what the
1022        // constrained-intra rule means by a neighbour it cannot use.
1023        !self.constrained_intra
1024            || !self.inter_y.get(nby * (self.mb_w * 4) + nbx).copied().unwrap_or(true)
1025    }
1026
1027    fn mv_neighbors(&self, mb_x: usize, mb_y: usize) -> [MvNeighbor; 3] {
1028        let w4 = self.mb_w * 4;
1029        let get = |avail: bool, bx: isize, by: isize| {
1030            if avail {
1031                let idx = by as usize * w4 + bx as usize;
1032                match (self.mv_y.get(idx), self.ref_idx_y.get(idx)) {
1033                    (Some(&m), Some(&r)) => MvNeighbor { available: true, mv: m, ref_idx: r },
1034                    _ => MvNeighbor::NONE,
1035                }
1036            } else {
1037                MvNeighbor::NONE
1038            }
1039        };
1040        let (bx, by) = (mb_x as isize * 4, mb_y as isize * 4);
1041        let a = get(mb_x > 0 && self.nbr_in_slice(mb_x - 1, mb_y), bx - 1, by);
1042        let b = get(mb_y > 0 && self.nbr_in_slice(mb_x, mb_y - 1), bx, by - 1);
1043        let c = if mb_y > 0 && mb_x + 1 < self.mb_w && self.nbr_in_slice(mb_x + 1, mb_y - 1) {
1044            get(true, bx + 4, by - 1)
1045        } else {
1046            get(mb_x > 0 && mb_y > 0 && self.nbr_in_slice(mb_x - 1, mb_y - 1), bx - 1, by - 1)
1047        };
1048        [a, b, c]
1049    }
1050
1051    fn mv_neighbors_block(&self, pbx: isize, pby: isize, pwb: isize) -> [MvNeighbor; 3] {
1052        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Neighbors);
1053        self.mv_neighbors_block_grid(pbx, pby, pwb, 0)
1054    }
1055
1056    fn skip_mv(&self, mb_x: usize, mb_y: usize) -> (i32, i32) {
1057        let [a, b, c] = self.mv_neighbors(mb_x, mb_y);
1058        if !a.available
1059            || !b.available
1060            || (a.ref_idx == 0 && a.mv == (0, 0))
1061            || (b.ref_idx == 0 && b.mv == (0, 0))
1062        {
1063            (0, 0)
1064        } else {
1065            predict_mv(a, b, c, 0)
1066        }
1067    }
1068
1069    fn set_mb_mv(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32), inter: bool, refi: i32) {
1070        // ROW FILLS. This wrote all sixteen cells of three grids one indexed
1071        // store at a time - FORTY-EIGHT separate bounds checks per call - and
1072        // re-evaluated `if inter` inside the inner loop. Each macroblock row is
1073        // four CONTIGUOUS cells, so twelve `fill`s cover it.
1074        let w4 = self.mb_w * 4;
1075        let r = if inter { refi } else { -1 };
1076        for dy in 0..4 {
1077            let a = (mb_y * 4 + dy) * w4 + mb_x * 4;
1078            self.mv_y[a..a + 4].fill(mv);
1079            self.inter_y[a..a + 4].fill(inter);
1080            self.ref_idx_y[a..a + 4].fill(r);
1081        }
1082    }
1083
1084    /// Commit one inter partition's motion into the 4×4 grid (ref 0, 1-ref P).
1085    /// `(rx,ry,rw,rh)` are MB-relative luma pixels; committing before the next
1086    /// partition's prediction is what lets a later partition predict from it.
1087    fn commit_inter_grid(&mut self, mb_x: usize, mb_y: usize, rx: usize, ry: usize, rw: usize, rh: usize, mv: (i32, i32), refi: i8) {
1088        // Row-range fills instead of the per-4x4 scatter: partitions are
1089        // rectangles, so each grid row is one contiguous run (same values).
1090        let w4 = self.mb_w * 4;
1091        let (bx0, bw) = (mb_x * 4 + rx / 4, rw / 4);
1092        for by in ry / 4..ry / 4 + rh / 4 {
1093            let a = (mb_y * 4 + by) * w4 + bx0;
1094            self.mv_y[a..a + bw].fill(mv);
1095            self.inter_y[a..a + bw].fill(true);
1096            self.ref_idx_y[a..a + bw].fill(refi as i32);
1097            self.coded_y[a..a + bw].fill(true);
1098        }
1099    }
1100
1101    /// Per-slice deblock parameters, needed DURING decode by the row-interleave
1102    /// path. `ena` is already resolved against `RFF_ABL_DEBLOCK` by the caller.
1103    pub fn set_deblock_params(&mut self, ena: bool, oa: i32, ob: i32, idc2: bool) {
1104        // Latch: the FIRST disabling slice turns row filtering off for the rest
1105        // of the picture (see `row_hook`); rows already filtered stay counted
1106        // in `flt_rows` and the picture-end tail handles the remainder.
1107        self.db_ena = ena && (self.flt_rows == 0 || self.db_ena);
1108        self.db_oa = oa;
1109        self.db_ob = ob;
1110        self.cur_idc2 = idc2;
1111    }
1112
1113    /// Slice index owning `addr` (slices are raster-contiguous, bounds ascending).
1114    fn slice_of(&self, addr: usize) -> usize {
1115        match self.slice_bounds.binary_search_by(|&(f, _)| f.cmp(&addr)) {
1116            Ok(i) => i,
1117            Err(i) => i - 1,
1118        }
1119    }
1120
1121    /// Derives bS for macroblock row `r` from the just-decoded (hot) grids into
1122    /// `bs_frame`, maintaining the two-row rolling record window (R2 of
1123    /// docs/row-interleave-plan.md).
1124    fn derive_bs_row(&mut self, r: usize) {
1125        // bS derivation reads the motion/nnz grids — flush deferred spans.
1126        self.span_flush();
1127        // Same stage label the in-filter derivation used, so profiles keep
1128        // pricing bS derivation wherever it lives.
1129        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DebDerive);
1130        use rusty_h264_common::deblock::{
1131            derive_mb_kind, derive_mb_records, pack_mb, BlockInfo, MbBs, MbKind,
1132        };
1133        let (mb_w, w4) = (self.mb_w, self.mb_w * 4);
1134        edcstat::bump(&edcstat::DBS_ROWS, 1);
1135        // Transform-block coded mask for this row: raw nnz, then the 8x8 OR for
1136        // t8 macroblocks (spec §8.7: the 8x8 transform's coded status is per 8x8).
1137        //
1138        // T8 GATE. `nnz_dbr` exists ONLY to carry that OR. With no 8x8
1139        // macroblock anywhere in the picture it is a byte-for-byte copy of
1140        // `nnz_y`, so both the 4-row copy and the per-row scan below are pure
1141        // work: the derivation reads `nnz_y` directly instead. Census
1142        // (DBSDERIVE) measured t8mb=0 on EVERY cavlc and main stream while
1143        // nnz_rowcopy ran 10800-16320 sub-rows per decode.
1144        if self.any_t8 {
1145            for br in r * 4..r * 4 + 4 {
1146                let a = br * w4;
1147                self.nnz_dbr[a..a + w4].copy_from_slice(&self.nnz_y[a..a + w4]);
1148            }
1149            edcstat::bump(&edcstat::DBS_NNZ_ROWCOPY, 4);
1150            // WHOLE-MACROBLOCK ROW SLICES. The 2x2-of-2x2 nest did sixteen
1151            // bounds-checked reads and sixteen bounds-checked writes per t8
1152            // macroblock; the same cells are four contiguous runs of four, so
1153            // four slice reads and four slice writes cover them. The OR is over
1154            // bytes, so `a | b | c | d > 0` is exactly the old per-cell `> 0`.
1155            let t8_row_pre = &self.mb_t8x8[r * mb_w..][..mb_w];
1156            for mb_x in 0..mb_w {
1157                if !t8_row_pre[mb_x] {
1158                    continue;
1159                }
1160                edcstat::bump(&edcstat::DBS_T8MB, 1);
1161                let base = r * 4 * w4 + mb_x * 4;
1162                let mut src = [[0u8; 4]; 4];
1163                for (k, row) in src.iter_mut().enumerate() {
1164                    row.copy_from_slice(&self.nnz_y[base + k * w4..][..4]);
1165                }
1166                let mut out = [[0u8; 4]; 4];
1167                for b8 in 0..4usize {
1168                    let (cx, cy) = ((b8 % 2) * 2, (b8 / 2) * 2);
1169                    let any = (src[cy][cx] | src[cy][cx + 1] | src[cy + 1][cx] | src[cy + 1][cx + 1])
1170                        > 0;
1171                    for sy in 0..2 {
1172                        for sx in 0..2 {
1173                            out[cy + sy][cx + sx] = any as u8;
1174                        }
1175                    }
1176                }
1177                for (k, row) in out.iter().enumerate() {
1178                    self.nnz_dbr[base + k * w4..][..4].copy_from_slice(row);
1179                }
1180            }
1181        }
1182        let info = BlockInfo {
1183            inter: &self.inter_y,
1184            nnz: if self.any_t8 { &self.nnz_dbr } else { &self.nnz_y },
1185            mv: &self.mv_y,
1186            ref_id: &self.ref_idx_y,
1187            mv1: &self.mv1,
1188            ref_id1: if self.ref_poc1.is_empty() { &[] } else { &self.ref_idx1 },
1189            w4,
1190            t8x8: &self.mb_t8x8,
1191            bs: &[],
1192            poc0: &self.ref_poc0,
1193            poc1: &self.ref_poc1,
1194            kind: &self.mb_kind,
1195        };
1196        let has1 = !info.ref_id1.is_empty();
1197        std::mem::swap(&mut self.pk_prev, &mut self.pk_cur);
1198        self.pk_cur.clear();
1199        let kl = self.k_kindloads;
1200        // Census gate resolved ONCE per row: `edcstat::on()` is a relaxed atomic
1201        // load and LLVM will not CSE atomic loads, so a per-MB bump costs a load
1202        // each. Same rule as hoisting an A/B arm selector out of the loop under
1203        // test (codec-measurement 15).
1204        let stats = edcstat::on();
1205        // ROW SLICES for the three per-macroblock grids. Each was indexed
1206        // `r * mb_w + mb_x` against a whole-FRAME Vec, which the compiler
1207        // cannot prove in range; sliced to THIS row, `[mb_x]` is provably
1208        // `< mb_w`. These are disjoint FIELDS of `self`, so the `&mut` on
1209        // `bs_frame` coexists with the `&` borrows `info` holds.
1210        let row0 = r * mb_w;
1211        let bs_row = &mut self.bs_frame[row0..][..mb_w];
1212        let t8_row = &self.mb_t8x8[row0..][..mb_w];
1213        let kind_row: &[u8] = if self.mb_kind.is_empty() {
1214            &[]
1215        } else {
1216            &self.mb_kind[row0..][..mb_w]
1217        };
1218        for mb_x in 0..mb_w {
1219            // Always pack: UNSET / Inter neighbours in this row and the next
1220            // read left/top MbPack. Kind stores MbBs directly (no i32 hop).
1221            self.pk_cur.push(pack_mb(&info, has1, mb_x, r));
1222            let slot = r * mb_w + mb_x;
1223            if stats {
1224                edcstat::bump(&edcstat::DBS_MB, 1);
1225            }
1226            // KIND ECONOMICS ON THE PACKED PATH: derive_mb_kind(Skip) does 9
1227            // fresh strided Blk::loads per MB, but pack_mb already ran for this
1228            // MB (the line above) — the packed `_` arm derives from those
1229            // records with no further gathers. A B_Skip kind experiment
1230            // measured kind-arm 1.8% SLOWER (z=-2.69) with +1.07M loads, so
1231            // Skip/InterUniform route to the packed arm; Intra keeps the kind
1232            // arm (pure constants, no loads). `RS_H264_KIND_LOADS=1` restores
1233            // the old routing for paired A/B.
1234            match kind_row.get(mb_x).copied().and_then(MbKind::from_u8) {
1235                Some(MbKind::Intra) => {
1236                    if stats {
1237                        edcstat::bump(&edcstat::DBS_INTRA, 1);
1238                    }
1239                    bs_row[mb_x] = derive_mb_kind(&info, mb_x, r, MbKind::Intra);
1240                }
1241                Some(k @ (MbKind::Skip | MbKind::InterUniform)) if kl => {
1242                    if stats {
1243                        edcstat::bump(&edcstat::DBS_KINDARM, 1);
1244                    }
1245                    bs_row[mb_x] = derive_mb_kind(&info, mb_x, r, k);
1246                }
1247                _ => {
1248                    let Some(cur) = self.pk_cur.get(mb_x) else { continue };
1249                    let left = if mb_x > 0 { self.pk_cur.get(mb_x - 1) } else { None };
1250                    let top = if r > 0 { self.pk_prev.get(mb_x) } else { None };
1251                    let mb_t8 = t8_row[mb_x];
1252                    let (mut bv, mut bh) = ([[0i32; 4]; 4], [[0i32; 4]; 4]);
1253                    let flat = derive_mb_records(cur, left, top, mb_t8, &mut bv, &mut bh);
1254                    if stats {
1255                        edcstat::bump(&edcstat::DBS_PACKED, 1);
1256                    }
1257                    // MEASUREMENT ONLY: sizes the `kind_loads()` match guard that
1258                    // used to be a OnceLock deref here (now `k_kindloads`).
1259                    if stats
1260                        && matches!(
1261                            self.mb_kind.get(slot).copied().and_then(MbKind::from_u8),
1262                            Some(MbKind::Skip | MbKind::InterUniform)
1263                        )
1264                    {
1265                        edcstat::bump(&edcstat::DBS_KINDGUARD, 1);
1266                    }
1267                    // FLAT-AWARE NARROWING. `derive_mb_records` returns early on a
1268                    // flat inter macroblock having written ONLY edge 0 of each
1269                    // orientation; edges 1..4 are still the caller's zero-init, so
1270                    // widening all 32 entries copies 24 known zeros onto 24 known
1271                    // zeros - after a `MbBs::default()` that zeroed them a third
1272                    // time. Census DBSDERIVE flat: 96.8% screen_text, 90.2%
1273                    // FourPeople, 82.1% akiyo - the dominant class.
1274                    let m = if flat {
1275                        if stats {
1276                            edcstat::bump(&edcstat::DBS_FLAT, 1);
1277                        }
1278                        debug_assert!(bv[1..] == [[0i32; 4]; 3] && bh[1..] == [[0i32; 4]; 3]);
1279                        let mut m = MbBs::default();
1280                        m.v[0] = std::array::from_fn(|sg| bv[0][sg] as u8);
1281                        m.h[0] = std::array::from_fn(|sg| bh[0][sg] as u8);
1282                        m
1283                    } else {
1284                        // Written once, not zeroed by `default()` and then written.
1285                        MbBs {
1286                            v: std::array::from_fn(|e| std::array::from_fn(|sg| bv[e][sg] as u8)),
1287                            h: std::array::from_fn(|e| std::array::from_fn(|sg| bh[e][sg] as u8)),
1288                        }
1289                    };
1290                    // MEASUREMENT ONLY: `on()` first, so the 32-byte compare
1291                    // never runs in a shipped decode (it is not otherwise needed
1292                    // here — the consumer in filter_frame_rows makes it).
1293                    if stats && m.v == [[0u8; 4]; 4] && m.h == [[0u8; 4]; 4] {
1294                        edcstat::bump(&edcstat::DBS_ALLZERO, 1);
1295                    }
1296                    bs_row[mb_x] = m;
1297                }
1298            }
1299        }
1300        // disable_deblocking_filter_idc == 2 (spec §7.4.3): a slice may forbid
1301        // filtering ITS macroblocks' edges against OTHER slices. bS = 0 on the
1302        // crossing MB edges kills exactly those filters; interior edges keep
1303        // their derived strengths. Guarded so single-slice / idc 0-1 pictures
1304        // pay one branch per row.
1305        if self.any_idc2 && self.slice_bounds.len() > 1 {
1306            edcstat::bump(&edcstat::DBS_IDC2ROW, 1);
1307            for mb_x in 0..mb_w {
1308                let slot = r * mb_w + mb_x;
1309                let si = self.slice_of(slot);
1310                if !self.slice_bounds.get(si).is_some_and(|b| b.1) {
1311                    continue;
1312                }
1313                if mb_x > 0 && self.slice_of(slot - 1) != si {
1314                    if let Some(b) = self.bs_frame.get_mut(slot) {
1315                        b.v[0] = [0; 4];
1316                    }
1317                }
1318                if r > 0 && self.slice_of(slot - mb_w) != si {
1319                    if let Some(b) = self.bs_frame.get_mut(slot) {
1320                        b.h[0] = [0; 4];
1321                    }
1322                }
1323            }
1324        }
1325    }
1326
1327    /// Decode-loop hook: called at each MB-loop head with the NEXT address to be
1328    /// decoded; derives AND FILTERS (R3) every fully-decoded row. Filtering a
1329    /// row here preserves the spec's raster per-MB filter order exactly (every
1330    /// MB the row's edges touch is already decoded; bottom-adjacent edges
1331    /// belong to the NEXT row's MBs, which filter later).
1332    ///
1333    /// Fast path: mid-row heads (`done <= bs_rows`) do no row work — return
1334    /// before the profiler scope (this hook is entered once per MB; scoping
1335    /// every call was measuring the timer, not the filter).
1336    /// row_hook with the caller's carried row — avoids the per-MB division.
1337    #[inline(always)]
1338    fn row_hook_at(&mut self, addr: usize, mby: usize) {
1339        if self.k_rowdb {
1340            // ~44/45 of calls are mid-row: one compare, no atomic loads.
1341            if !self.k_roweager && mby <= self.bs_rows {
1342                return;
1343            }
1344        }
1345        self.row_hook(addr, mby);
1346    }
1347
1348    /// `mby` is the row `addr` sits in. It is a PARAMETER rather than
1349    /// `addr / self.mb_w` because both callers already carry it: the slice loops
1350    /// track `(mbx, mby)` with a compare-and-wrap precisely so the per-macroblock
1351    /// div+mod never runs, and this hook -- entered PER MACROBLOCK on the
1352    /// non-rowdb arm -- was reconstructing it with an integer divide anyway.
1353    fn row_hook(&mut self, addr: usize, mby: usize) {
1354        debug_assert_eq!(mby, addr / self.mb_w, "row_hook: mby must be addr's row");
1355        // `RS_H264_ROWHOOK_EAGER=1` restores per-MB profiler scoping (A/B oracle).
1356        // Read from the CACHED fields: `row_hook_at` already gates on
1357        // `self.k_roweager` / `self.k_rowdb`, and re-reading the knob functions
1358        // here paid a OnceLock deref plus a relaxed atomic load on every entry -
1359        // and on the non-rowdb arm this function is entered per MACROBLOCK.
1360        let eager = self.k_roweager;
1361        if !self.k_rowdb {
1362            edcstat::bump(&edcstat::MBS, 1);
1363            let _rh = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecRowHook);
1364            self.edc_flush();
1365            let done = mby;
1366            if done > self.bs_rows {
1367                self.bs_rows = done;
1368                self.publish_progress();
1369            }
1370            return;
1371        }
1372        let done = mby;
1373        // ~44/45 of calls are mid-row: no derive/filter/handoff yet.
1374        if !eager && done <= self.bs_rows {
1375            return;
1376        }
1377        edcstat::bump(&edcstat::MBS, 1);
1378        let _rh = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecRowHook);
1379        if done <= self.bs_rows {
1380            return;
1381        }
1382        if self.edc_tx.is_some() {
1383            // E2: derivation stays here (it reads the syntax grids); filtering
1384            // is the worker's, fed the row's bs/qp/t8 snapshot. `flt_rows`
1385            // advances on the worker and comes home with the context.
1386            self.edc_giveback();
1387            while self.bs_rows < done {
1388                let r = self.bs_rows;
1389                self.derive_bs_row(r);
1390                self.bs_rows += 1;
1391                let base = r * self.mb_w;
1392                // ORDER: this row's pixel jobs must reach the worker BEFORE the
1393                // filter message for the same row.
1394                self.edc_flush_batch();
1395                edcstat::bump(&edcstat::ROWS, 1);
1396                edcstat::bump(
1397                    &edcstat::ROWBYTES,
1398                    (self.mb_w
1399                        * (std::mem::size_of::<rusty_h264_common::deblock::MbBs>() + 2))
1400                        as u64,
1401                );
1402                let msg = EdcMsg::Row {
1403                    r,
1404                    bs: self.bs_frame[base..base + self.mb_w].to_vec(),
1405                    qp: self.mb_qp[base..base + self.mb_w].to_vec(),
1406                    t8: self.mb_t8x8[base..base + self.mb_w].to_vec(),
1407                };
1408                self.edc_tx.as_ref().unwrap().send(msg).expect("worker alive");
1409            }
1410            return;
1411        }
1412        self.edc_flush();
1413        while self.bs_rows < done {
1414            let r = self.bs_rows;
1415            self.derive_bs_row(r);
1416            self.bs_rows += 1;
1417            // Row filtering requires deblock enabled on EVERY slice so far
1418            // (`db_ena` latches false once any slice disables it): a mixed
1419            // picture falls back to the picture-end tail so "latest slice
1420            // wins" semantics are preserved.
1421            if self.db_ena {
1422                self.save_bak(r);
1423                self.filter_row(r);
1424                self.flt_rows = r + 1;
1425            }
1426            self.publish_progress();
1427        }
1428    }
1429
1430    /// Saves the UNFILTERED bottom pixel rows of MB row `r` before filtering
1431    /// modifies them: the next row's intra prediction must read pre-deblock
1432    /// samples (spec §8.3), and filtering touches the bottom three rows while
1433    /// intra reads exactly the bottom ONE (+ the corner) — so one backup row
1434    /// per plane suffices, overwritten per row.
1435    fn save_bak(&mut self, r: usize) {
1436        let y0 = (r * 16 + 15) * self.cw;
1437        self.bak_y.copy_from_slice(&self.rec_y[y0..y0 + self.cw]);
1438        let c0 = (r * 8 + 7) * self.ccw;
1439        self.bak_u.copy_from_slice(&self.rec_u[c0..c0 + self.ccw]);
1440        self.bak_v.copy_from_slice(&self.rec_v[c0..c0 + self.ccw]);
1441    }
1442
1443    /// Filters one MB row against the stored strengths, using the CURRENT
1444    /// slice's alpha/beta offsets (single-offset streams — the whole corpus —
1445    /// are bit-identical to the picture-end call; the plan's risk register
1446    /// documents the multi-offset divergence).
1447    fn filter_row(&mut self, r: usize) {
1448        let info = rusty_h264_common::deblock::BlockInfo {
1449            inter: &self.inter_y,
1450            // Same source `derive_bs_row` used (see the T8 GATE there). This
1451            // path passes a non-empty `bs`, so it takes the PRECOMPUTED arm and
1452            // never reads `nnz` at all; kept in step so the two cannot diverge.
1453            nnz: if self.any_t8 { &self.nnz_dbr } else { &self.nnz_y },
1454            mv: &self.mv_y,
1455            ref_id: &self.ref_idx_y,
1456            mv1: &self.mv1,
1457            ref_id1: &self.ref_idx1,
1458            w4: self.mb_w * 4,
1459            t8x8: &self.mb_t8x8,
1460            bs: &self.bs_frame,
1461            poc0: &[],
1462            poc1: &[],
1463            kind: &self.mb_kind,
1464        };
1465        rusty_h264_common::deblock::filter_frame_rows(
1466            &mut self.rec_y,
1467            &mut self.rec_u,
1468            &mut self.rec_v,
1469            self.mb_w,
1470            self.mb_h,
1471            r..r + 1,
1472            &self.mb_qp,
1473            self.chroma_qp_offset,
1474            self.db_oa,
1475            self.db_ob,
1476            &info,
1477        );
1478    }
1479
1480    /// Top-neighbour LUMA pixel for intra prediction: reads the unfiltered
1481    /// backup row when the row above has already been deblock-filtered by the
1482    /// row-interleave (flt_rows gates it; 0 when the interleave is off, so
1483    /// this compiles to the plain read on the fallback path).
1484    #[inline]
1485    fn top_y_px(&self, py: usize, x: usize) -> u8 {
1486        // 128 is the spec's value for an unavailable sample (1 << (BitDepth - 1),
1487        // §8.3.1.2), so the fallback is the one the prediction rules already use
1488        // — and unreachable anyway, since callers gate on availability first.
1489        if py % 16 == 0 && self.flt_rows * 16 >= py {
1490            self.bak_y.get(x).copied().unwrap_or(128)
1491        } else {
1492            self.rec_y.get((py - 1) * self.cw + x).copied().unwrap_or(128)
1493        }
1494    }
1495
1496    /// Slice form of [`Self::top_y_px`] for the contiguous 16-wide I16 gather.
1497    #[inline]
1498    fn top_y_row(&self, py: usize, x: usize, n: usize) -> &[u8] {
1499        if py % 16 == 0 && self.flt_rows * 16 >= py {
1500            &self.bak_y[x..x + n]
1501        } else {
1502            &self.rec_y[(py - 1) * self.cw + x..][..n]
1503        }
1504    }
1505
1506    /// Top-neighbour CHROMA pixel (plane `c`: 0 = U, 1 = V).
1507    #[inline]
1508    fn top_c_px(&self, c: usize, cy: usize, x: usize) -> u8 {
1509        // 128 = the spec's unavailable sample, as in `top_y_px`.
1510        if cy % 8 == 0 && self.flt_rows * 8 >= cy {
1511            let bak = if c == 0 { &self.bak_u } else { &self.bak_v };
1512            bak.get(x).copied().unwrap_or(128)
1513        } else {
1514            let rec = if c == 0 { &self.rec_u } else { &self.rec_v };
1515            rec.get((cy - 1) * self.ccw + x).copied().unwrap_or(128)
1516        }
1517    }
1518
1519    /// Slice form of [`Self::top_c_px`] for the 8-wide chroma gather.
1520    #[inline]
1521    fn top_c_row(&self, c: usize, cy: usize, x: usize, n: usize) -> &[u8] {
1522        if cy % 8 == 0 && self.flt_rows * 8 >= cy {
1523            if c == 0 { &self.bak_u[x..x + n] } else { &self.bak_v[x..x + n] }
1524        } else {
1525            let rec = if c == 0 { &self.rec_u } else { &self.rec_v };
1526            &rec[(cy - 1) * self.ccw + x..][..n]
1527        }
1528    }
1529
1530    /// Snapshots the (deblocked) reconstruction as a reference picture, drawing
1531    /// its padded-plane allocations from `pool` (recycled
1532    /// planes of evicted DPB frames — see `Decoder::reclaim_retired`). ~1.9 MB of
1533    /// fresh allocation per reference picture otherwise (`dpb-clone` stage, 3-4%
1534    /// of decode, mostly first-touch page faults).
1535    pub fn as_reference_pooled(&self, pool: &mut Vec<Vec<u8>>) -> crate::RefFrame {
1536        // MV CAPTURE (`RFF_MV_DUMP=1`) — lets a harness read the motion field any
1537        // conformant H.264 stream carries, including x264's, using this decoder as
1538        // the parser. Diagnostic only; inert unless the env var is set.
1539        if mv_dump_on() {
1540            MV_DUMP.lock().unwrap().push(MvField {
1541                mb_w: self.mb_w,
1542                mb_h: self.mb_h,
1543                mv: self.mv_y.clone(),
1544                ref_idx: self.ref_idx_y.clone(),
1545                inter: self.inter_y.clone(),
1546            });
1547        }
1548
1549        // The per-block motion (mv/ref_idx/ref_poc) is read ONLY by B temporal/spatial
1550        // direct (`col.mv/ref_idx/ref_poc`, guarded on `w4 != 0` + `idx < len`). On
1551        // Baseline/Constrained-Baseline streams (no B) it's pure waste — skip the two
1552        // grid clones + the per-block ref_poc resolve/alloc. `w4 = 0` makes the B
1553        // readers no-op even on malformed input.
1554        let (mv, ref_idx, mv1, ref_idx1, ref_poc, w4) = if self.b_possible {
1555            (
1556                self.mv_y.clone(),
1557                self.ref_idx_y.clone(),
1558                self.mv1.clone(),
1559                self.ref_idx1.clone(),
1560                // Resolve each block's List-0 ref index to the referenced picture's
1561                // POC, so temporal direct can map it into the current list.
1562                // Via a tiny per-ref LUT: the per-block bounds + Option chain +
1563                // Ref pointer chase (57k blocks at 720p, once per reference
1564                // frame) becomes one table index. Identical output: LUT slots
1565                // past refs.len() hold MIN, exactly what .get() returned.
1566                {
1567                    let mut poc_lut = [i32::MIN; 32];
1568                    for (i, f) in self.refs.iter().take(32).enumerate() {
1569                poc_lut[i & 31] = f.pic_poc();
1570                    }
1571                    self.ref_idx_y
1572                        .iter()
1573                        .map(|&r| if (0..32).contains(&r) { poc_lut[r as usize] } else { i32::MIN })
1574                        .collect()
1575                },
1576                self.mb_w * 4,
1577            )
1578        } else {
1579            (Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new(), 0)
1580        };
1581        // Pop an exact-size recycled buffer per plane; a miss falls back to a
1582        // fresh allocation inside `pad_plane_into`.
1583        let mut take = |len: usize| -> Vec<u8> {
1584            match pool.iter().position(|v| v.len() == len) {
1585                Some(i) => pool.swap_remove(i),
1586                None => Vec::new(),
1587            }
1588        };
1589        let (lpw, lph) = (self.cw + 2 * crate::LPAD, self.ch + 2 * crate::LPAD);
1590        let (cpw, cph) = (self.ccw + 2 * crate::CPAD, self.ch / 2 + 2 * crate::CPAD);
1591        crate::RefFrame {
1592            // Pad once here (ExpandPicture) instead of extracting a clamped tile
1593            // on every MC call — same copy class as the old plane clone.
1594            py: rusty_h264_common::inter::pad_plane_into(take(lpw * lph), &self.rec_y, self.cw, self.ch, crate::LPAD),
1595            pu: rusty_h264_common::inter::pad_plane_into(take(cpw * cph), &self.rec_u, self.ccw, self.ch / 2, crate::CPAD),
1596            pv: rusty_h264_common::inter::pad_plane_into(take(cpw * cph), &self.rec_v, self.ccw, self.ch / 2, crate::CPAD),
1597            cw: self.cw,
1598            ch: self.ch,
1599            ready_rows: std::sync::atomic::AtomicUsize::new(0),
1600            live: None,
1601            frozen: std::sync::OnceLock::new(),
1602            frame_num: 0, // set by the caller (decode_slice knows frame_num)
1603            poc: 0,       // set by the caller
1604            mv,
1605            ref_idx,
1606            mv1,
1607            ref_idx1,
1608            ref_poc,
1609            w4,
1610            long_term: false,
1611            long_term_idx: 0,
1612        }
1613    }
1614
1615    fn nnz_cache_load(&mut self, mb_x: usize, mb_y: usize) {
1616        let w4 = self.mb_w * 4;
1617        let top_unavail = mb_y == 0 || !self.nbr_in_slice(mb_x, mb_y - 1);
1618        let left_unavail = mb_x == 0 || !self.nbr_in_slice(mb_x - 1, mb_y);
1619        // The four TOP neighbours are one contiguous run of the nnz grid; only
1620        // the left column is strided. Hoisting the uniform `top_unavail` test out
1621        // of the loop turns four checked grid loads into one slice copy.
1622        if top_unavail {
1623            self.nnz_l_cache[1..5].fill(0x80);
1624        } else {
1625            let src = &self.nnz_y[(mb_y * 4 - 1) * w4 + mb_x * 4..][..4];
1626            self.nnz_l_cache[1..5].copy_from_slice(src);
1627        }
1628        for lby in 0..4 {
1629            self.nnz_l_cache[(lby + 1) * 5] =
1630                if left_unavail {
1631                0x80
1632            } else {
1633                self.nnz_y.get((mb_y * 4 + lby) * w4 + (mb_x * 4 - 1)).copied().unwrap_or(0)
1634            };
1635        }
1636    }
1637    #[inline]
1638    fn nc_pred(&self, lbx: usize, lby: usize) -> i32 {
1639        // MASKED, and it pays twice over: this helper and `nnz_cache_set` are
1640        // inlined into the intra path, the inter path AND both slice loops, so
1641        // the two unprovable indexes were replicated at every call site. The
1642        // cache is a 5x5 grid in a `[u8; 25]` and every caller passes a 4x4 block
1643        // coordinate (`LUMA_4X4_SCAN_XY`, `b8*2 + s`, or a literal), so `& 3` is
1644        // a no-op that puts the worst case at 4 * 5 + 4 = 24.
1645        let (lbx, lby) = (lbx & 3, lby & 3);
1646        let left = self.nnz_l_cache[(lby + 1) * 5 + lbx] as i32;
1647        let top = self.nnz_l_cache[lby * 5 + (lbx + 1)] as i32;
1648        let r = left + top;
1649        if r < 0x80 { (r + 1) >> 1 } else { r & 0x7f }
1650    }
1651    #[inline]
1652    fn nnz_cache_set(&mut self, lbx: usize, lby: usize, total: u8) {
1653        self.nnz_l_cache[((lby & 3) + 1) * 5 + ((lbx & 3) + 1)] = total;
1654    }
1655    fn chroma_cache_load(&mut self, mb_x: usize, mb_y: usize) {
1656        let w2 = self.mb_w * 2;
1657        let top_unavail = mb_y == 0 || !self.nbr_in_slice(mb_x, mb_y - 1);
1658        let left_unavail = mb_x == 0 || !self.nbr_in_slice(mb_x - 1, mb_y);
1659        for c in 0..2 {
1660            if top_unavail {
1661                self.nnz_c_cache[c][1..3].fill(0x80);
1662            } else {
1663                let src = &self.nnz_c[c][(mb_y * 2 - 1) * w2 + mb_x * 2..][..2];
1664                self.nnz_c_cache[c][1..3].copy_from_slice(src);
1665            }
1666            for by in 0..2 {
1667                self.nnz_c_cache[c][(by + 1) * 3] =
1668                    if left_unavail {
1669                0x80
1670            } else {
1671                self.nnz_c[c & 1].get((mb_y * 2 + by) * w2 + (mb_x * 2 - 1)).copied().unwrap_or(0)
1672            };
1673            }
1674        }
1675    }
1676    #[inline]
1677    fn chroma_nc_pred(&self, c: usize, bx: usize, by: usize) -> i32 {
1678        // Same shape as `nc_pred`, one size down: a 3x3 grid in `[u8; 9]`, two
1679        // planes, and every caller passes 0..1 for all three coordinates.
1680        let (c, bx, by) = (c & 1, bx & 1, by & 1);
1681        let left = self.nnz_c_cache[c][(by + 1) * 3 + bx] as i32;
1682        let top = self.nnz_c_cache[c][by * 3 + (bx + 1)] as i32;
1683        let r = left + top;
1684        if r < 0x80 { (r + 1) >> 1 } else { r & 0x7f }
1685    }
1686    #[inline]
1687    fn chroma_nnz_cache_set(&mut self, c: usize, bx: usize, by: usize, total: u8) {
1688        self.nnz_c_cache[c & 1][((by & 1) + 1) * 3 + ((bx & 1) + 1)] = total;
1689    }
1690
1691    /// Decodes one slice's macroblocks (raster order) starting at `first_mb`,
1692    /// until `more_rbsp_data()` is exhausted or the picture is full. Returns the
1693    /// next macroblock address (= total when the picture is complete). In a
1694    /// P-slice each macroblock is preceded by `mb_skip_run`.
1695    /// CABAC slice-data decode (docs/cabac-decode-plan.md), brought up brick by brick
1696    /// against the instrumented openh264 oracle. Phase 1: verify engine init; the
1697    /// syntax layer (Phase 2+) is WIP.
1698    #[allow(clippy::too_many_arguments)]
1699    pub fn decode_slice_data_cabac(
1700        &mut self,
1701        rbsp: &[u8],
1702        start_byte: usize,
1703        slice_qp: u8,
1704        cabac_init_idc: u32,
1705        is_i: bool,
1706        is_p: bool,
1707        first_mb: usize,
1708    ) -> Result<usize, MbError> {
1709        // E2: overlap parse (this thread) with pixel reconstruction (a scoped
1710        // worker owning the planes) for P slices. I slices and B slices keep
1711        // the inline path (their pixel coupling is per-MB); the ownership
1712        // ping-pong around intra-in-P macroblocks is `edc_intra_sync`.
1713        let eligible = edc_on() && rowdb_on() && !is_i && (is_p || self.is_b);
1714        let threaded = eligible && edc_spawn_worker(self.mb_w, self.mb_h, self.bits_per_mb, true);
1715        edcstat::bump(&edcstat::DISPATCH_ON, threaded as u64);
1716        edcstat::bump(&edcstat::DISPATCH_SEEN, eligible as u64);
1717        if !threaded {
1718            let r = self.decode_slice_cabac_inner(rbsp, start_byte, slice_qp, cabac_init_idc, is_i, is_p, first_mb);
1719            self.note_slice_density(rbsp.len().saturating_sub(start_byte), first_mb, &r);
1720            return r;
1721        }
1722        let ctx = self.edc_take_ctx();
1723        // D7 PROBE: is the CPU overhead PAYLOAD (alloc/copy per job) or
1724        // SYNCHRONISATION (blocking on a full queue, park/unpark)? The bound
1725        // separates them: raising it removes send-blocking without changing a
1726        // single byte copied. `RS_H264_EDC_BOUND` sweeps it.
1727        let (tx, rx) = std::sync::mpsc::sync_channel::<EdcMsg>(edc_bound());
1728        let (ctx_tx, ctx_rx) = std::sync::mpsc::channel::<PixelCtx>();
1729        let (back_tx, back_rx) = std::sync::mpsc::channel::<PixelCtx>();
1730        let (res, ctx, panicked) = std::thread::scope(|sc| {
1731            let h = sc.spawn(move || edc_worker(ctx, rx, ctx_tx, back_rx));
1732            self.edc_tx = Some(tx);
1733            self.edc_ctx_rx = Some(ctx_rx);
1734            self.edc_back_tx = Some(back_tx);
1735            // UNWIND SAFETY (found by the fuzzer as a HANG, not a failure): a
1736            // panic inside the parse loop would skip the cleanup below — but
1737            // the sender lives in `self`, which outlives the unwind, so the
1738            // channel would never close, the worker would never exit, and the
1739            // scope's join would block forever, converting a diagnosable panic
1740            // into a silent deadlock under `catch_unwind` harnesses. Catch,
1741            // clean up, join, restore the planes, THEN resume the panic.
1742            let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1743                self.decode_slice_cabac_inner(rbsp, start_byte, slice_qp, cabac_init_idc, is_i, is_p, first_mb)
1744            }));
1745            self.edc_flush_batch(); // ORDER: no job may outlive the channel
1746            self.edc_giveback(); // if an intra macroblock left us holding
1747            self.edc_tx = None; // closes the channel -> worker drains + returns
1748            self.edc_ctx_rx = None;
1749            self.edc_back_tx = None;
1750            match (r, h.join()) {
1751                (Ok(res), Ok(ctx)) => (res, Some(ctx), None),
1752                (Err(p), Ok(ctx)) => (Err(MbError::Truncated), Some(ctx), Some(p)),
1753                (Ok(_), Err(p)) | (Err(_), Err(p)) => (Err(MbError::Truncated), None, Some(p)),
1754            }
1755        });
1756        if let Some(ctx) = ctx {
1757            self.edc_restore_ctx(ctx);
1758        }
1759        if let Some(p) = panicked {
1760            std::panic::resume_unwind(p);
1761        }
1762        self.note_slice_density(rbsp.len().saturating_sub(start_byte), first_mb, &res);
1763        res
1764    }
1765
1766    /// Feed the D12 dispatch its density signal from a slice just decoded.
1767    /// Exponentially smoothed so one atypical slice cannot flip the arm, and
1768    /// only ever read on the NEXT slice — the current one is already committed.
1769    fn note_slice_density(&mut self, bytes: usize, first_mb: usize, r: &Result<usize, MbError>) {
1770        let Ok(end) = r else { return };
1771        let mbs = end.saturating_sub(first_mb);
1772        if mbs == 0 {
1773            return;
1774        }
1775        let bpm = (bytes * 8) as f64 / mbs as f64;
1776        self.bits_per_mb = if self.bits_per_mb == 0.0 {
1777            bpm
1778        } else {
1779            0.75 * self.bits_per_mb + 0.25 * bpm
1780        };
1781    }
1782
1783    fn decode_slice_cabac_inner(
1784        &mut self,
1785        rbsp: &[u8],
1786        start_byte: usize,
1787        slice_qp: u8,
1788        cabac_init_idc: u32,
1789        is_i: bool,
1790        is_p: bool,
1791        first_mb: usize,
1792    ) -> Result<usize, MbError> {
1793        self.edc_active = edc_on();
1794        let mut cab = crate::cabac::Cabac::new(rbsp, start_byte, slice_qp as i32, cabac_init_idc, is_i);
1795        let (range, _offset) = cab.dbg_state();
1796        let trace = std::env::var_os("RH_CABAC_TRACE").is_some();
1797        debug_assert_eq!(range, 510, "CABAC init range must be 510");
1798
1799        let mbw = self.mb_w;
1800        let total = self.mb_w * self.mb_h;
1801        // Per-MB neighbour state (single-slice assumption: avail == in-bounds).
1802        // SCOPED: zero-initialised allocations sized by MB count, once per slice.
1803        // D13: B-only grids (List-1 ref/mvd + direct flags) are ~292 KB at 720p and
1804        // are ONLY written on the B branch below — allocating them on every P/I
1805        // slice was the same fresh-page class GridPool fixed for the frame grids.
1806        // `RS_H264_FAT_SLICE=1` restores the always-alloc path for A/B.
1807        let _alloc_g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecSliceAlloc);
1808        // POOLED (was fresh `vec![..]` per slice): refill reuses the pooled
1809        // allocation with contents identical to a fresh vec, so the body below
1810        // is untouched. Taken out of `self` here; put back at the normal exit.
1811        let mut cat = refill(std::mem::take(&mut self.sc_cat), total, 255u8); // 0=I4x4, 2=I16, 255=unavailable
1812        let mut mb_cbp = refill(std::mem::take(&mut self.sc_cbp), total, 0u8);
1813        let mut cmode = refill(std::mem::take(&mut self.sc_cmode), total, -1i32); // chroma pred mode
1814        let mut mb_nzc = refill(std::mem::take(&mut self.sc_nzc), total, [0u8; 24]); // 16 luma raster + 8 chroma
1815        let mut cbf_dc = refill(std::mem::take(&mut self.sc_cbfdc), total, 0u16);
1816        let mut mb_skip = refill(std::mem::take(&mut self.sc_skip), total, false);
1817        let mut mb_ref = refill(std::mem::take(&mut self.sc_ref), total, [-1i8; 16]); // per-4×4-block List-0 ref (-1 = intra)
1818        let mut mb_mvd = refill(std::mem::take(&mut self.sc_mvd), total, [[0i16; 2]; 16]); // per-block mvd (for mvd ctxInc)
1819        // D13: B-only grids (~292 KB @720p) only on B slices (or FAT_SLICE A/B).
1820        let want_b_grids = self.is_b || fat_slice_on();
1821        let mut mb_ref1 = if want_b_grids {
1822            refill(std::mem::take(&mut self.sc_ref1), total, [-1i8; 16])
1823        } else {
1824            Vec::new()
1825        };
1826        let mut mb_mvd1 = if want_b_grids {
1827            refill(std::mem::take(&mut self.sc_mvd1), total, [[0i16; 2]; 16])
1828        } else {
1829            Vec::new()
1830        };
1831        let mut mb_direct = if want_b_grids {
1832            refill(std::mem::take(&mut self.sc_direct), total, false)
1833        } else {
1834            Vec::new()
1835        };
1836        drop(_alloc_g);
1837        // Multi-slice availability (spec §6.4.x): a macroblock before this
1838        // slice's first_mb is NOT available — for pixel-domain prediction
1839        // (`nbr_in_slice`, like the CAVLC twin sets) AND for every CABAC ctx
1840        // neighbour below. The per-slice ctx arrays' defaults are not the
1841        // "unavailable" value (cat 255 reads as available-I16, mb_cbp 0 as
1842        // all-zero-cbp, mb_t8x8 is a frame grid…), so the `left`/`top`
1843        // options themselves are gated on slice membership — one gate that
1844        // every downstream ctxIdxInc read inherits. Confirmed against ffmpeg
1845        // on an x264 `--slices 4` stream, which desynced without this.
1846        self.slice_first_mb = first_mb;
1847        self.slice_bounds.push((first_mb, self.cur_idc2));
1848        self.any_idc2 |= self.cur_idc2;
1849        let mut last_delta_qp = 0i32;
1850        let mut addr = first_mb;
1851
1852        let _mbloop_g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbLoop);
1853        // A new slice's first MBs may read grids a previous slice's deferred
1854        // spans still owe (P-after-B in one picture included).
1855        self.span_flush();
1856        // `mbw` is `pic_width_in_mbs_minus1 + 1` from the SPS. It cannot be 0 in
1857        // a conformant stream, but it is a runtime value, so this div+rem pair
1858        // carried a divide-by-zero panic reachable from a MALFORMED header --
1859        // on the untrusted-input path, at slice entry. `.max(1)` retires both
1860        // checks and cannot change a conformant decode.
1861        let mbw_nz = mbw.max(1);
1862        let (mut mbx, mut mby) = (addr % mbw_nz, addr / mbw_nz);
1863        // Slice-invariant B properties, formerly re-derived per macroblock.
1864        if self.is_b {
1865            self.edc_flush(); // drain anything a preceding P slice queued
1866        }
1867        let b_records = self.edc_tx.is_some();
1868        let b_refs_ok = !self.refs.is_empty() && !self.refs1.is_empty();
1869        // Loop-invariant: the lowest `addr` whose top neighbour is in this slice.
1870        let top_lim = first_mb + mbw;
1871        // Reused across macroblocks: MC overwrites every byte it reads back (all
1872        // B layouts cover the full macroblock), so the per-MB 384-byte zero-init
1873        // these used to pay was dead. Byte-identity is the guard.
1874        let mut pred_y = [0u8; 256];
1875        let mut c_pred = [[0u8; 64]; 2];
1876        // The loop wraps mbx at entry; bias so the first iteration is exact.
1877        // BOUND the entropy-coded loop (fuzzer-surfaced): a mutated stream can
1878        // never produce decode_terminate and the engine zero-fills forever. The
1879        // bound needs checking ONCE at entry — every in-loop path that advances
1880        // `addr` already breaks on `addr >= total` before continuing.
1881        if addr >= total {
1882            return Err(MbError::Truncated);
1883        }
1884        // BIND THE LENGTH FOR THE LOOP. Every grid below is `refill(.., total, ..)`
1885        // so its length is EXACTLY `total`, and the loop is entered only with
1886        // `addr < total` (checked above, re-asserted each turn) — but nothing
1887        // related the two, so every `[addr]` carried a check. Twenty-eight of
1888        // those became `get_mut` in the previous pass and cost ~2% on CABAC
1889        // content (crowd_run-main 1.021x, z=+2.11): a branch per write on the
1890        // decoder's hottest loop. Reborrowing at the literal `total` makes
1891        // `addr < total` and `addr < len` the SAME fact — no check, no branch.
1892        // The borrows end with this block, before the grids go back to the pool.
1893        // `mb_direct`/`mb_ref1`/`mb_mvd1` are deliberately NOT bound here: they
1894        // are `Vec::new()` on non-B slices, so slicing them to `total` would be
1895        // the very panic this campaign is removing.
1896        {
1897            let cat = &mut cat[..total];
1898            let mb_cbp = &mut mb_cbp[..total];
1899            let cmode = &mut cmode[..total];
1900            let mb_nzc = &mut mb_nzc[..total];
1901            let cbf_dc = &mut cbf_dc[..total];
1902            let mb_skip = &mut mb_skip[..total];
1903            let mb_ref = &mut mb_ref[..total];
1904            let mb_mvd = &mut mb_mvd[..total];
1905        loop {
1906            // A REAL check, not `debug_assert` — which compiles OUT in release,
1907            // so nothing carried `addr < total` across the loop's back-edge and
1908            // the reborrows above folded nothing on their own. This cannot fire
1909            // (every path that advances `addr` already breaks on `addr >= total`,
1910            // and entry is guarded above), but stating it once per macroblock
1911            // replaces FIFTEEN per-write branches with one that never taken.
1912            if addr >= total {
1913                break;
1914            }
1915            // Carried coordinates: one compare-and-wrap replaces the per-MB
1916            // div+mod pair (and row_hook's own division).
1917            if mbx == mbw {
1918                mbx = 0;
1919                mby += 1;
1920            }
1921            self.row_hook_at(addr, mby);
1922            self.wait_refs_for_mb(mby);
1923            let left = (mbx > 0 && addr > first_mb).then(|| addr - 1);
1924            let top = (mby > 0 && addr >= top_lim).then(|| addr - mbw);
1925
1926            // Brick 3.1/3.2: P-slice mb_skip_flag, then mb_type (P mb_type is neighbour-
1927            // independent; intra sub-types map to the I dispatch below).
1928            let mb_type;
1929            if is_p {
1930                // Direct bool arithmetic — no Option chain on the hot path.
1931                let sctx = 11
1932                    + (left.is_some() && !mb_skip.get(addr - 1).copied().unwrap_or(true)) as usize
1933                    + (top.is_some()
1934                        && !mb_skip.get(addr.wrapping_sub(mbw)).copied().unwrap_or(true))
1935                        as usize;
1936                if parse_mb_skip_cabac(&mut cab, sctx) {
1937                    if let Some(p) = mb_skip.get_mut(addr) {
1938                        *p = true;
1939                    }
1940                    last_delta_qp = 0; // skip codes no mb_qp_delta → delta ctxInc resets
1941                    // P_Skip recon reuses the entropy-free CAVLC primitive verbatim: it
1942                    // takes no bit-reader (skip has no coded syntax past the flag), just
1943                    // predicts the skip MV, motion-compensates, and commits the grid.
1944                    self.decode_p_skip(mbx, mby)?;
1945                    if let Some(p) = self.mb_qp.get_mut(addr) {
1946                        *p = self.cur_qp;
1947                    } // skip inherits QPy
1948                    let eos = cab.decode_terminate();
1949                    addr += 1;
1950                    mbx += 1;
1951                    if eos || addr >= total {
1952                        break;
1953                    }
1954                    continue;
1955                }
1956                let mbt = parse_mb_type_p_cabac(&mut cab);
1957                // NON-SKIP P MB: the inter arms below predict MVs from the
1958                // grids (mv_neighbors_block) and the intra arm gathers — flush
1959                // the deferred P span (B span cannot be pending in a P slice,
1960                // but span_flush is one Option check each).
1961                self.span_flush();
1962                if mbt <= 3 {
1963                    let _gb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbP);
1964                    // noSubMbPartSizeLessThan8x8Flag (spec 7.3.5): P_8x8 permits the
1965                    // 8x8 transform only when every sub-partition is itself 8x8.
1966                    let mut allow8 = true;
1967                    // Inter MB (Bricks 3.3/3.4/3.5). 1-ref stream → ref_idx not coded (ref=0).
1968                    // Build the 30-entry mvd/ref neighbour cache (openh264 WelsFillCacheInterCabac).
1969                    let mut mvdc = [[0i16; 2]; 30];
1970                    let mut refc = [-1i8; 30];
1971                    // Index the neighbour's RECORD once, then read within it.
1972                    // `mb_ref[l][bi]` is two bounds checks - one on the Vec, one
1973                    // on the array - repeated for each of the four entries and
1974                    // again for `mb_mvd`, i.e. sixteen checks per macroblock for
1975                    // four neighbours. Binding the record hoists the Vec check
1976                    // out; the inner index is a literal and folds.
1977                    if let Some(l) = left {
1978                        let (Some(lr), Some(lm)) = (mb_ref.get(l), mb_mvd.get(l)) else {
1979                            return Err(MbError::Truncated);
1980                        };
1981                        for (ci, bi) in [(6usize, 3usize), (12, 7), (18, 11), (24, 15)] {
1982                            refc[ci] = lr[bi];
1983                            mvdc[ci] = lm[bi];
1984                        }
1985                    }
1986                    if let Some(t) = top {
1987                        let (Some(tr), Some(tm)) = (mb_ref.get(t), mb_mvd.get(t)) else {
1988                            return Err(MbError::Truncated);
1989                        };
1990                        for (ci, bi) in [(1usize, 12usize), (2, 13), (3, 14), (4, 15)] {
1991                            refc[ci] = tr[bi];
1992                            mvdc[ci] = tm[bi];
1993                        }
1994                    }
1995                    if mbx > 0 && mby > 0 {
1996                        let a = addr - mbw - 1;
1997                        if let (Some(r), Some(m)) = (mb_ref.get(a), mb_mvd.get(a)) {
1998                            (refc[0], mvdc[0]) = (r[15], m[15]);
1999                        }
2000                    }
2001                    if mby > 0 && mbx + 1 < mbw {
2002                        let a = addr - mbw + 1;
2003                        if let (Some(r), Some(m)) = (mb_ref.get(a), mb_mvd.get(a)) {
2004                            (refc[5], mvdc[5]) = (r[12], m[12]);
2005                        }
2006                    }
2007                    let mut mmvd = [[0i16; 2]; 16];
2008                    let mut mref = [0i8; 16];
2009                    // mb_pred (spec 7.3.5.1): all ref_idx_l0 FIRST (only when >1 active
2010                    // ref), then all mvd + ref-aware predict + commit. `refidx!` parses one
2011                    // partition's ref_idx (ctxIdxOffset 54, ctx from neighbour refc) and
2012                    // seeds refc so a later partition's ref/mvd context sees it — mirror
2013                    // of the encoder's two-phase emit_mb_cabac_p_inter.
2014                    macro_rules! refidx {
2015                        ($pi:expr, $zb:expr) => {{
2016                            if self.num_ref_active > 1 {
2017                                let s = CACHE30[$pi & 15].clamp(6, 29);
2018                                let c0 = (refc[s - 1] > 0) as usize + 2 * (refc[s - 6] > 0) as usize;
2019                                let r = parse_ref_idx_cabac(&mut cab, c0);
2020                                for &zb in $zb.iter() {
2021                                    refc[CACHE30[zb & 15]] = r;
2022                                }
2023                                r
2024                            } else {
2025                                0i8
2026                            }
2027                        }};
2028                    }
2029                    macro_rules! part {
2030                        ($pi:expr, $zb:expr, $pred:expr, $rx:expr, $ry:expr, $rw:expr, $rh:expr, $refi:expr) => {{
2031                            let (mvx, mvy) = parse_mvd_partition(&mut cab, $pi, $zb, &mut mvdc, &mut refc, &mut mmvd, &mut mref, $refi);
2032                            let [na, nb, nc] = self.mv_neighbors_block(
2033                                (mbx * 4 + $rx / 4) as isize,
2034                                (mby * 4 + $ry / 4) as isize,
2035                                ($rw / 4) as isize,
2036                            );
2037                            let pmv = $pred(na, nb, nc);
2038                            self.commit_inter_grid(mbx, mby, $rx, $ry, $rw, $rh, (pmv.0 + mvx, pmv.1 + mvy), $refi);
2039                        }};
2040                    }
2041                    match mbt {
2042                        0 => {
2043                            // Sibling of CAVLC P_16x16: one (ref, mv) for all 16
2044                            // blocks → every internal edge is strength 0 (§8.7.2.1).
2045                            // Without this, CABAC Main/High P_16x16 stayed UNSET and
2046                            // paid the blind 24-block bS gather.
2047                            if let Some(k) = self.mb_kind.get_mut(mby * self.mb_w + mbx) {
2048            *k =
2049                                rusty_h264_common::deblock::MB_KIND_INTER_UNIFORM;
2050        }
2051                            let r0 = refidx!(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
2052                            part!(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], |a, b, c| predict_partition_mv(0, 0, a, b, c, r0 as i32), 0, 0, 16, 16, r0);
2053                        }
2054                        1 => {
2055                            let r0 = refidx!(0, &[0, 1, 2, 3, 4, 5, 6, 7]);
2056                            let r1 = refidx!(8, &[8, 9, 10, 11, 12, 13, 14, 15]);
2057                            part!(0, &[0, 1, 2, 3, 4, 5, 6, 7], |a, b, c| predict_partition_mv(1, 0, a, b, c, r0 as i32), 0, 0, 16, 8, r0);
2058                            part!(8, &[8, 9, 10, 11, 12, 13, 14, 15], |a, b, c| predict_partition_mv(1, 1, a, b, c, r1 as i32), 0, 8, 16, 8, r1);
2059                        }
2060                        2 => {
2061                            let r0 = refidx!(0, &[0, 1, 2, 3, 8, 9, 10, 11]);
2062                            let r1 = refidx!(4, &[4, 5, 6, 7, 12, 13, 14, 15]);
2063                            part!(0, &[0, 1, 2, 3, 8, 9, 10, 11], |a, b, c| predict_partition_mv(2, 0, a, b, c, r0 as i32), 0, 0, 8, 16, r0);
2064                            part!(4, &[4, 5, 6, 7, 12, 13, 14, 15], |a, b, c| predict_partition_mv(2, 1, a, b, c, r1 as i32), 8, 0, 8, 16, r1);
2065                        }
2066                        _ => {
2067                            // P_8x8: 4 sub_mb_types, then 4 ref_idx (one per 8×8), then mvd.
2068                            let mut subt = [0u32; 4];
2069                            for st in &mut subt {
2070                                *st = parse_sub_mb_type_p_cabac(&mut cab);
2071                            }
2072                            allow8 = subt.iter().all(|&t| t == 0);
2073                            let mut pr = [0i8; 4];
2074                            for (i, r) in pr.iter_mut().enumerate() {
2075                                let b = i * 4;
2076                                *r = refidx!(b, &[b, b + 1, b + 2, b + 3]);
2077                            }
2078                            for i in 0..4usize {
2079                                let b = i * 4;
2080                                let (ox, oy) = ((i % 2) * 8, (i / 2) * 8); // 8×8 pixel origin in MB
2081                                let ri = pr[i];
2082                                match subt[i] {
2083                                    0 => part!(b, &[b, b + 1, b + 2, b + 3], |a, b, c| predict_mv(a, b, c, ri as i32), ox, oy, 8, 8, ri),
2084                                    1 => {
2085                                        part!(b, &[b, b + 1], |a, b, c| predict_mv(a, b, c, ri as i32), ox, oy, 8, 4, ri);
2086                                        part!(b + 2, &[b + 2, b + 3], |a, b, c| predict_mv(a, b, c, ri as i32), ox, oy + 4, 8, 4, ri);
2087                                    }
2088                                    2 => {
2089                                        part!(b, &[b, b + 2], |a, b, c| predict_mv(a, b, c, ri as i32), ox, oy, 4, 8, ri);
2090                                        part!(b + 1, &[b + 1, b + 3], |a, b, c| predict_mv(a, b, c, ri as i32), ox + 4, oy, 4, 8, ri);
2091                                    }
2092                                    _ => {
2093                                        for j in 0..4usize {
2094                                            let (sx, sy) = ((j % 2) * 4, (j / 2) * 4);
2095                                            part!(b + j, &[b + j], |a, b, c| predict_mv(a, b, c, ri as i32), ox + sx, oy + sy, 4, 4, ri);
2096                                        }
2097                                    }
2098                                }
2099                            }
2100                        }
2101                    }
2102                    if let Some(p) = mb_ref.get_mut(addr) {
2103                        *p = mref;
2104                    }
2105                    if let Some(p) = mb_mvd.get_mut(addr) {
2106                        *p = mmvd;
2107                    }
2108
2109                    // Inter cbp + residual (is_intra = false → cbf default nA=nB=0).
2110                    let cbp = parse_cbp_cabac(&mut cab, top.and_then(|a| mb_cbp.get(a).copied()), left.and_then(|a| mb_cbp.get(a).copied()));
2111                    if let Some(p) = mb_cbp.get_mut(addr) {
2112                        *p = cbp as u8;
2113                    }
2114                    // H-49: an INTER macroblock carries transform_size_8x8_flag AFTER cbp
2115                    // (spec 7.3.5), present only when CodedBlockPatternLuma > 0 and
2116                    // noSubMbPartSizeLessThan8x8Flag. Same context as the intra read.
2117                    let t8 = self.transform_8x8_mode && (cbp & 15) != 0 && allow8 && {
2118                        let a = left.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
2119                        let b = top.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
2120                        cab.decode_decision(399 + a + b) != 0
2121                    };
2122                    if let Some(p) = self.mb_t8x8.get_mut(addr) {
2123                        *p = t8;
2124                    }
2125                    self.any_t8 |= t8;
2126                    // D9c: cbp==0 never parses residuals — skip the 2.5 KB coeff
2127                    // zero-init + PInterJob entirely when NORES is on (default).
2128                    // Current-MB nzc slots stay unset under cbp==0 and export as 0
2129                    // (same as the 0xff→0 scrub below), so mb_nzc = [0;24] is exact.
2130                    if cbp == 0 && nores_on() {
2131                        last_delta_qp = 0;
2132                        if let Some(p) = self.mb_qp.get_mut(addr) {
2133                            *p = self.cur_qp;
2134                        }
2135                        if let Some(p) = cbf_dc.get_mut(addr) {
2136                            *p = 0;
2137                        }
2138                        if let Some(p) = mb_nzc.get_mut(addr) {
2139                    *p = [0u8; 24];
2140                }
2141                        if self.refs.is_empty() {
2142                            return Err(MbError::Unsupported("inter without reference"));
2143                        }
2144                        let (mut jgmv, mut jgref) = ([(0i32, 0i32); 16], [0u8; 16]);
2145                        {
2146                            let w4r = self.mb_w * 4;
2147                            for by in 0..4usize {
2148                                // Row-contiguous — see the coded-inter gather.
2149                                let row = (mby * 4 + by) * w4r + mbx * 4;
2150                                jgmv[by * 4..by * 4 + 4]
2151                                    .copy_from_slice(&self.mv_y[row..row + 4]);
2152                                // Row-slice the ref grid too - it was the only
2153                                // one still indexed per block.
2154                                let ridx = &self.ref_idx_y[row..row + 4];
2155                                for bx in 0..4usize {
2156                                    jgref[by * 4 + bx] = ridx[bx].clamp(0, 15) as u8;
2157                                }
2158                            }
2159                        }
2160                        let pj = PInterNoResJob {
2161                            mbx,
2162                            mby,
2163                            t8,
2164                            gmv: jgmv,
2165                            gref: jgref,
2166                        };
2167                        if self.edc_tx.is_some() {
2168                            self.edc_giveback();
2169                            self.edc_commit_nnz(mbx, mby, t8, &[0u8; 24], 0);
2170                            if edcstat::on() {
2171                                edcstat::bump(&edcstat::J_INTER, 1);
2172                                edcstat::bump(&edcstat::J_INTER_NORES, 1);
2173                            }
2174                            edcstat::bump(&edcstat::J_NORES_SENT, 1);
2175                            self.edc_send_job(EdcJob::InterNoRes(Box::new(pj)));
2176                        } else if self.edc_active {
2177                            edcstat::bump(&edcstat::J_NORES_SENT, 1);
2178                            self.edc_jobs.push(EdcJob::InterNoRes(Box::new(pj)));
2179                        } else {
2180                            self.recon_p_inter_nores(&pj);
2181                            if double_recon() {
2182                                self.recon_p_inter_nores(&pj);
2183                            }
2184                        }
2185                        let eos = cab.decode_terminate();
2186                        addr += 1;
2187                    mbx += 1;
2188                        if eos || addr >= total {
2189                            break;
2190                        }
2191                        continue;
2192                    }
2193                    // Zeroed ONLY when the 8x8 transform is in play — this was a
2194                    // 1KB memset per coded inter MB on non-t8 tiers.
2195                    let mut luma8: Option<[[i32; 64]; 4]> = None;
2196                    let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
2197                    let mut nzc = [0xffu8; 48];
2198                    if let Some(t) = top {
2199                        let tnz = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
2200                        nzc[1..5].copy_from_slice(&tnz[12..16]);
2201                        (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
2202                        (nzc[6], nzc[7], nzc[30], nzc[31]) = (tnz[20], tnz[21], tnz[22], tnz[23]);
2203                    }
2204                    if let Some(l) = left {
2205                        let lnz = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
2206                        (nzc[8], nzc[16], nzc[24], nzc[32]) = (lnz[3], lnz[7], lnz[11], lnz[15]);
2207                        (nzc[13], nzc[21], nzc[37], nzc[45]) = (lnz[17], lnz[21], lnz[19], lnz[23]);
2208                    }
2209                    let mut cbfdc = 0u16;
2210                    let mut nnzs = [0u8; 24]; // parsed totalCoeff per block (see add_inter_residual)
2211                    // Materialised ONLY when the parse actually writes them: a
2212                    // t8 macroblock never touches luma_scan (1 KB), and
2213                    // cbp_chroma < 2 never touches cac (512 B).
2214                    let mut luma_scan: Option<[[i32; 16]; 16]> = None; // per z-order 4×4 block
2215                    let mut cdc = [[0i32; 4]; 2]; // chroma DC per plane (scan order)
2216                    let mut cac: Option<[[[i32; 16]; 4]; 2]> = None; // chroma AC per plane
2217                    // A cbp==0 MB codes no mb_qp_delta → the next MB's delta ctxInc sees 0.
2218                    if cbp == 0 {
2219                        last_delta_qp = 0;
2220                    }
2221                    if cbp != 0 {
2222                        let ndc = (top.and_then(|a| cbf_dc.get(a).copied()), left.and_then(|a| cbf_dc.get(a).copied()));
2223                        let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
2224                        self.step_qp(qpd)?;
2225                        for id8 in 0..4usize {
2226                            if cbp_luma & (1 << id8) != 0 {
2227                                if t8 {
2228                                    // All four slots carry the 8x8 total: cat 5 has no per-4x4
2229                                    // counts, and the recon helper now reads one slot
2230                                    // per 4x4 cell.
2231                                    let n8 = parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, id8 * 4, RP_LUMA_8X8, false, ndc, &mut luma8.get_or_insert_with(|| [[0i32; 64]; 4])[id8]) as u8;
2232                                    for k in 0..4 {
2233                                        nnzs[id8 * 4 + k] = n8;
2234                                    }
2235                                } else {
2236                                    let ls = luma_scan.get_or_insert_with(|| [[0i32; 16]; 16]);
2237                                    for id4 in 0..4usize {
2238                                        let iz = id8 * 4 + id4;
2239                                        nnzs[iz] = parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, iz, RP_LUMA_4X4, false, ndc, &mut ls[iz]) as u8;
2240                                    }
2241                                }
2242                            } else {
2243                                for k in 0..4 {
2244                                    nzc[NZC_CACHE[(id8 * 4 + k).min(23)].min(47)] = 0;
2245                                }
2246                            }
2247                        }
2248                        if cbp_chroma >= 1 {
2249                            for i in 0..2usize {
2250                                parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, 16 + i * 4, RP_CHROMA_DC + i, false, ndc, &mut cdc[i]);
2251                            }
2252                        }
2253                        if cbp_chroma == 2 {
2254                            let cacm = cac.get_or_insert_with(|| [[[0i32; 16]; 4]; 2]);
2255                            for i in 0..2usize {
2256                                for id4 in 0..4usize {
2257                                    nnzs[16 + i * 4 + id4] = parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, 16 + i * 4 + id4, RP_CHROMA_AC + i, false, ndc, &mut cacm[i][id4]) as u8;
2258                                }
2259                            }
2260                        }
2261                    }
2262                    if let Some(p) = self.mb_qp.get_mut(addr) {
2263                        *p = self.cur_qp;
2264                    }
2265                    if let Some(p) = cbf_dc.get_mut(addr) {
2266                        *p = cbfdc;
2267                    }
2268                    let _sc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecStateCache);
2269                    let mut mn = [0u8; 24];
2270                    for k in 0..4 {
2271                        mn[k] = nzc[9 + k];
2272                        mn[4 + k] = nzc[17 + k];
2273                        mn[8 + k] = nzc[25 + k];
2274                        mn[12 + k] = nzc[33 + k];
2275                    }
2276                    (mn[16], mn[17], mn[20], mn[21]) = (nzc[14], nzc[15], nzc[22], nzc[23]);
2277                    (mn[18], mn[19], mn[22], mn[23]) = (nzc[38], nzc[39], nzc[46], nzc[47]);
2278                    // A block whose residual was skipped (cbp bit clear / no chroma AC)
2279                    // has 0 coeffs, not "unavailable" — export 0 so an intra neighbour's
2280                    // CBF ctxInc reads 0 (not the 0xff sentinel → is_intra default).
2281                    for v in mn.iter_mut() {
2282                        if *v == 0xff {
2283                            *v = 0;
2284                        }
2285                    }
2286                    if let Some(p) = mb_nzc.get_mut(addr) {
2287                        *p = mn;
2288                    }
2289                    drop(_sc);
2290
2291                    if self.refs.is_empty() {
2292                        return Err(MbError::Unsupported("inter without reference"));
2293                    }
2294                    // NOTE (measured, do not "optimise" this away): EDC is
2295                    // DEFAULT ON (`edc_on()`, opt out with RS_H264_EDC=0), so
2296                    // even single-threaded this job is built and deferred —
2297                    // that batching is the E1 loop-fission win, not overhead.
2298                    // A "skip the job in 1T" bypass added here could never fire
2299                    // and was removed.
2300                    let (mut jgmv, mut jgref) = ([(0i32, 0i32); 16], [0u8; 16]);
2301                    {
2302                        let w4r = self.mb_w * 4;
2303                        for by in 0..4usize {
2304                            // Row-contiguous: one slice copy for the MVs
2305                            // instead of four bounds-checked strided loads.
2306                            let row = (mby * 4 + by) * w4r + mbx * 4;
2307                            jgmv[by * 4..by * 4 + 4].copy_from_slice(&self.mv_y[row..row + 4]);
2308                            // Row-slice the ref grid too - it was the only one
2309                            // still indexed per block.
2310                            let ridx = &self.ref_idx_y[row..row + 4];
2311                            for bx in 0..4usize {
2312                                jgref[by * 4 + bx] = ridx[bx].clamp(0, 15) as u8;
2313                            }
2314                        }
2315                    }
2316                    // D9c: when `cbp == 0`, never materialise the 2.5 KB coeff
2317                    // arrays into a `PInterJob` — ship `InterNoRes` (or call
2318                    // `recon_p_inter_nores` inline). `RS_H264_NORES=0` keeps the
2319                    // old full-job path for A/B.
2320                    let nores = cbp == 0 && nores_on();
2321                    if nores {
2322                        let pj = PInterNoResJob {
2323                            mbx,
2324                            mby,
2325                            t8,
2326                            gmv: jgmv,
2327                            gref: jgref,
2328                        };
2329                        if self.edc_tx.is_some() {
2330                            self.edc_giveback();
2331                            self.edc_commit_nnz(mbx, mby, t8, &[0u8; 24], 0);
2332                            if edcstat::on() {
2333                                edcstat::bump(&edcstat::J_INTER, 1);
2334                                edcstat::bump(&edcstat::J_INTER_NORES, 1);
2335                            }
2336                            edcstat::bump(&edcstat::J_NORES_SENT, 1);
2337                            self.edc_send_job(EdcJob::InterNoRes(Box::new(pj)));
2338                        } else if self.edc_active {
2339                            edcstat::bump(&edcstat::J_NORES_SENT, 1);
2340                            self.edc_jobs.push(EdcJob::InterNoRes(Box::new(pj)));
2341                        } else {
2342                            self.recon_p_inter_nores(&pj);
2343                            if double_recon() {
2344                                self.recon_p_inter_nores(&pj);
2345                            }
2346                        }
2347                        let eos = cab.decode_terminate();
2348                        addr += 1;
2349                    mbx += 1;
2350                        if eos || addr >= total {
2351                            break;
2352                        }
2353                        continue;
2354                    }
2355                    let job = PInterJob {
2356                        mbx,
2357                        mby,
2358                        qp: self.cur_qp,
2359                        cbp_chroma,
2360                        gmv: jgmv,
2361                        gref: jgref,
2362                        luma_scan,
2363                        luma8,
2364                        cdc,
2365                        cac,
2366                        nnzs,
2367                    };
2368                    if self.edc_tx.is_some() {
2369                        self.edc_giveback();
2370                        self.edc_commit_nnz(mbx, mby, t8, &nnzs, cbp_chroma);
2371                        if edcstat::on() {
2372                            edcstat::bump(&edcstat::J_INTER, 1);
2373                        }
2374                        self.edc_send_job(EdcJob::Inter(Box::new(job)));
2375                    } else if self.edc_active {
2376                        self.edc_jobs.push(EdcJob::Inter(Box::new(job)));
2377                    } else {
2378                        self.recon_p_inter(&job);
2379                        if double_recon() {
2380                            self.recon_p_inter(&job);
2381                        }
2382                    }
2383
2384                    let eos = cab.decode_terminate();
2385                    addr += 1;
2386                    mbx += 1;
2387                    if eos || addr >= total {
2388                        break;
2389                    }
2390                    continue;
2391                }
2392                mb_type = mbt - 5; // 5→0 (I_4x4), 6..29→1..24 (I_16x16)
2393            } else if self.is_b {
2394                // NOTE: the per-macroblock edc_flush() that used to sit here is
2395                // hoisted to slice entry (see below) — nothing inside a B slice
2396                // pushes to edc_jobs.
2397                if b_records {
2398                    // E3: this B macroblock's MC regions record instead of executing.
2399                    self.edc_regions = Some(Vec::with_capacity(8));
2400                }
2401                let _gb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbB);
2402                // noSubMbPartSizeLessThan8x8Flag for B: direct MBs qualify only under
2403                // direct_8x8_inference_flag; B_8x8 needs every sub-partition 8x8.
2404                let mut allow8 = true;
2405                // B-slice: mb_skip_flag (ctx 24 + neighbour-not-skip), then B mb_type.
2406                let (hl, ht) = (left.is_some(), top.is_some());
2407                let sctx = 24
2408                    + (hl && !mb_skip.get(addr - 1).copied().unwrap_or(true)) as usize
2409                    + (ht && !mb_skip.get(addr.wrapping_sub(mbw)).copied().unwrap_or(true)) as usize;
2410                if parse_mb_skip_cabac(&mut cab, sctx) {
2411                    if let (Some(sk), Some(di)) = (mb_skip.get_mut(addr), mb_direct.get_mut(addr)) {
2412                        (*sk, *di) = (true, true);
2413                    }
2414                    last_delta_qp = 0; // skip codes no mb_qp_delta → delta ctxInc resets
2415                    // B_Skip recon reuses the entropy-free CAVLC primitive (spatial/temporal
2416                    // direct with no residual), which also commits the motion grid.
2417                    // Hot prefix inline: forced run continuations skip the call.
2418                    if !self.b_skip_hot(mbx, mby) {
2419                        self.decode_b_skip(mbx, mby)?;
2420                    }
2421                    if let Some(p) = self.mb_qp.get_mut(addr) {
2422                        *p = self.cur_qp;
2423                    }
2424                    // Skip/direct blocks contribute mvd 0 to a later MB's mvd ctxInc; the
2425                    // ref stays in-list so |mvd|=0 is summed (same result either way).
2426                    // mb_ref/mb_ref1 stay at their -1 init: every reader is
2427                    // either a `> 0` context test (-1 and 0 both false) or the
2428                    // mvd-sum's `>= 0` gate — and a skip's mvd is (0,0), so
2429                    // exclusion (-1) and inclusion-of-zero (0) give the same
2430                    // sum. The 64-byte per-skip zero-fill was pure waste.
2431                    let eos = cab.decode_terminate();
2432                    addr += 1;
2433                    mbx += 1;
2434                    if eos || addr >= total {
2435                        break;
2436                    }
2437                    continue;
2438                }
2439                // NON-SKIP B MB: every arm below reads the grids INLINE
2440                // (H-48: CABAC never routes through decode_b_mb) — flush
2441                // the deferred spans. Caught by the tempete ARM-DIFF.
2442                self.span_flush();
2443                let bci = (hl && !mb_direct.get(addr - 1).copied().unwrap_or(true)) as usize
2444                    + (ht && !mb_direct.get(addr.wrapping_sub(mbw)).copied().unwrap_or(true)) as usize;
2445                let bmt = { let _s = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BTypeParse); parse_mb_type_b_cabac(&mut cab, bci) };
2446                if bmt < 23 {
2447                    // ---- B inter: parse motion (mvd L0/L1; ref not coded on this 1-ref
2448                    // stream) + residual. Recon (b_mc/direct) deferred to B.3. ----
2449                    let mut mvdc0 = [[0i16; 2]; 30];
2450                    let mut refc0 = [-1i8; 30];
2451                    let mut mvdc1 = [[0i16; 2]; 30];
2452                    let mut refc1 = [-1i8; 30];
2453                    // WelsFillCacheInterCabac, per list (L0 = mb_ref/mb_mvd, L1 = mb_ref1/mb_mvd1).
2454                    // The corner addresses and their guards are macroblock
2455                    // properties, resolved ONCE here rather than rebuilt inside
2456                    // each list expansion; each neighbour grid row is borrowed
2457                    // once instead of re-indexed per entry.
2458                    let tl = (mbx > 0 && mby > 0).then(|| addr - mbw - 1);
2459                    let tr = (mby > 0 && mbx + 1 < mbw).then(|| addr - mbw + 1);
2460                    macro_rules! fill {
2461                        ($mrf:expr, $mmv:expr, $rc:expr, $mc:expr) => {{
2462                            // `.get` PER PARALLEL GRID. The ref and mvd grids are
2463                            // separate Vecs indexed by the same macroblock address,
2464                            // so `$mrf[l]` proved nothing about `$mmv[l]` and each
2465                            // of the four neighbour slots carried two panic paths —
2466                            // doubled again because this macro expands once per
2467                            // list. A `None` here degrades to exactly what the
2468                            // cache already means by "neighbour unavailable" (the
2469                            // `-1` ref it is initialised to), so the fallible form
2470                            // is the honest one as well as the cheap one.
2471                            if let Some(l) = left {
2472                                if let (Some(rr), Some(mm)) = ($mrf.get(l), $mmv.get(l)) {
2473                                    for (ci, bi) in [(6usize, 3usize), (12, 7), (18, 11), (24, 15)] {
2474                                        $rc[ci] = rr[bi];
2475                                        $mc[ci] = mm[bi];
2476                                    }
2477                                }
2478                            }
2479                            if let Some(t) = top {
2480                                if let (Some(rr), Some(mm)) = ($mrf.get(t), $mmv.get(t)) {
2481                                    for (ci, bi) in [(1usize, 12usize), (2, 13), (3, 14), (4, 15)] {
2482                                        $rc[ci] = rr[bi];
2483                                        $mc[ci] = mm[bi];
2484                                    }
2485                                }
2486                            }
2487                            if let Some(a) = tl {
2488                                if let (Some(rr), Some(mm)) = ($mrf.get(a), $mmv.get(a)) {
2489                                    ($rc[0], $mc[0]) = (rr[15], mm[15]);
2490                                }
2491                            }
2492                            if let Some(a) = tr {
2493                                if let (Some(rr), Some(mm)) = ($mrf.get(a), $mmv.get(a)) {
2494                                    ($rc[5], $mc[5]) = (rr[12], mm[12]);
2495                                }
2496                            }
2497                        }};
2498                    }
2499                    {
2500                        let _s = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BFillCache);
2501                        fill!(mb_ref, mb_mvd, refc0, mvdc0);
2502                        fill!(mb_ref1, mb_mvd1, refc1, mvdc1);
2503                    }
2504                    let mut _smv = Some(rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BMvdParse));
2505                    let mut mmvd0 = [[0i16; 2]; 16];
2506                    let mut mref0 = [-1i8; 16];
2507                    let mut mmvd1 = [[0i16; 2]; 16];
2508                    let mut mref1 = [-1i8; 16];
2509                    if !b_refs_ok {
2510                        return Err(MbError::Unsupported("B without references"));
2511                    }
2512                    // Recon (mirrors CAVLC decode_b_mb / decode_b_8x8): predict each list's
2513                    // MV off the committed grid + the CABAC-parsed mvd, commit, MC (bi-pred
2514                    // blend), then add the residual. Prediction reads mmvd0/mmvd1 (the mvd
2515                    // per raster block, splatted during the parse above).
2516                    // pred_y / c_pred are slice-scope scratch (see the loop head).
2517                    // Recording is a per-macroblock property — ask once, not per
2518                    // partition, so the 1T path calls b_mc straight through.
2519                    let rec_mode = self.edc_regions.is_some();
2520
2521                    if bmt == 0 {
2522                        // B_Direct_16x16: no coded motion. A direct block contributes mvd 0
2523                        // to a later MB's mvd ctxInc with its ref in-list (|0| summed).
2524                        if let Some(p) = mb_direct.get_mut(addr) {
2525                            *p = true;
2526                        }
2527                        allow8 = self.direct_8x8_inference;
2528                        (mref0, mref1) = ([0i8; 16], [0i8; 16]);
2529                        // FORCED derivation via the zero-bi bitmap (same triple
2530                        // as b_skip_hot): a direct-16 whose left/top/topright are
2531                        // recorded ref0/(0,0) committers derives (0,0)/(0,0) bi —
2532                        // skip the gather + derivation, run the region half
2533                        // directly. Its own commit is zero-bi too, so it EXTENDS
2534                        // forcing chains through coded direct MBs.
2535                        // `mbw` is already carried by the loop, and the row-above
2536                        // address is one subtraction, not three.
2537                        let up = addr.wrapping_sub(mbw);
2538                        let forced = !b_records
2539                            && mbx > 0
2540                            && mby > 0
2541                            && mbx + 1 < mbw
2542                            && up >= self.slice_first_mb
2543                            && match self.bzero.get(..=addr) {
2544                                // A slice ENDING at `addr` proves all three
2545                                // neighbour reads at once — the guards above put
2546                                // every one of them below `addr`. Same shape as
2547                                // `b_skip_hot`.
2548                                Some(bz) => {
2549                                    bz.get(addr - 1).copied().unwrap_or(false)
2550                                        && bz.get(up).copied().unwrap_or(false)
2551                                        && bz.get(up + 1).copied().unwrap_or(false)
2552                                }
2553                                None => false,
2554                            };
2555                        if forced {
2556                            self.b_direct_region(mbx, mby, 0, 0, 16, 16, &mut pred_y, &mut c_pred, (0, 0, (0, 0), (0, 0), false));
2557                            if let Some(b) = self.bzero.get_mut(addr) {
2558            *b = true;
2559        }
2560                        } else {
2561                            self.decode_b_direct(mbx, mby, 0, 0, 16, 16, &mut pred_y, &mut c_pred);
2562                        }
2563                    } else if bmt == 22 {
2564                        // B_8x8: 4 sub_mb_types, (ref not coded on 1-ref), then mvd
2565                        // list-major → sub-MB → sub-partition (openh264 order).
2566                        let mut subt = [0u32; 4];
2567                        for s in &mut subt {
2568                            *s = parse_sub_mb_type_b_cabac(&mut cab);
2569                        }
2570                        let d8i = self.direct_8x8_inference;
2571                        allow8 = subt.iter().all(|&t| if t == 0 { d8i } else { (1..=3).contains(&t) });
2572                        // uses(list) depends only on the sub_mb_type — resolve the
2573                        // whole 4x2 table once here; the ref_idx, mvd and recon
2574                        // loops below all read it instead of re-asking.
2575                        let su = [
2576                            [b_sub_uses(subt[0], 0), b_sub_uses(subt[0], 1)],
2577                            [b_sub_uses(subt[1], 0), b_sub_uses(subt[1], 1)],
2578                            [b_sub_uses(subt[2], 0), b_sub_uses(subt[2], 1)],
2579                            [b_sub_uses(subt[3], 0), b_sub_uses(subt[3], 1)],
2580                        ];
2581                        // A direct sub-partition contributes mvd 0 / ref in-list to the
2582                        // ctxInc — both the per-MB export and the within-MB 30-cache that a
2583                        // later (non-direct) sub in this MB reads.
2584                        for i in 0..4usize {
2585                            if subt[i] == 0 {
2586                                let b = i * 4;
2587                                for zb in b..b + 4 {
2588                                    // each table read once, not twice
2589                                    let (g, c) = (G_SCAN4[zb & 15], CACHE30[zb & 15]);
2590                                    (mref0[g], mref1[g]) = (0, 0);
2591                                    (refc0[c], refc1[c]) = (0, 0);
2592                                }
2593                            }
2594                        }
2595                        // ref_idx_l0 for all four 8x8s, then ref_idx_l1, then the mvds
2596                        // (spec 7.3.5.2 sub_mb_pred). ONE ref per 8x8 -- never per
2597                        // sub-partition -- and B_Direct_8x8 codes none.
2598                        let mut sref = [[0i8; 2]; 4]; // [sub-MB][list]
2599                        for list in 0..2usize {
2600                            // Single-reference lists code no ref_idx at all.
2601                            if (if list == 0 { self.num_ref_active } else { self.num_ref_active1 }) <= 1 {
2602                                continue;
2603                            }
2604                            let rc = if list == 0 { &mut refc0 } else { &mut refc1 };
2605                            for i in 0..4usize {
2606                                let st = subt[i];
2607                                if st == 0 || !su[i][list] {
2608                                    continue;
2609                                }
2610                                let b = i * 4;
2611                                let s = CACHE30[b & 15].clamp(6, 29);
2612                                let c0 = (rc[s - 1] > 0) as usize + 2 * (rc[s - 6] > 0) as usize;
2613                                let r = parse_ref_idx_cabac(&mut cab, c0);
2614                                for &zb in &[b, b + 1, b + 2, b + 3] {
2615                                    rc[CACHE30[zb]] = r;
2616                                }
2617                                sref[i][list] = r;
2618                            }
2619                        }
2620                        for list in 0..2usize {
2621                            let (mmv, mrf, mc, rc) = if list == 0 {
2622                                (&mut mmvd0, &mut mref0, &mut mvdc0, &mut refc0)
2623                            } else {
2624                                (&mut mmvd1, &mut mref1, &mut mvdc1, &mut refc1)
2625                            };
2626                            for i in 0..4usize {
2627                                let st = subt[i];
2628                                if st == 0 || !su[i][list] {
2629                                    continue;
2630                                }
2631                                let b = i * 4;
2632                                for &(sx, sy, sw, sh) in b_sub_parts(st) {
2633                                    // Shapes are 8x8 / 8x4 / 4x8 / 4x4, so the block
2634                                    // list is closed-form: `step` is 1 when the part
2635                                    // spans both columns, 2 when it spans both rows.
2636                                    let (w4b, h4b) = (sw / 4, sh / 4);
2637                                    let base = b + (sy / 4) * 2 + sx / 4;
2638                                    let step = if w4b == 2 { 1 } else { 2 };
2639                                    let zb = [base, base + step, base + 2, base + 3];
2640                                    let n = w4b * h4b;
2641                                    parse_mvd_partition(&mut cab, zb[0], &zb[..n], mc, rc, mmv, mrf, sref[i][list]);
2642                                }
2643                            }
2644                        }
2645                        // Recon each 8×8: direct sub → decode_b_direct; else per sub-part
2646                        // predict (median) + commit + MC.
2647                        // Spatial-direct A/B/C are MB-level — walk once if any sub is
2648                        // direct. `dmemo=0` rewalks every direct 8×8 (A/B oracle).
2649                        // Derivation hoist: A/B/C AND the rid/median result are
2650                        // MB-level — derive once, run only the per-8x8 region half
2651                        // (czg differs per sub) for every direct sub.
2652                        let hoisted = if self.direct_spatial
2653                            && direct_memo_on()
2654                            && subt.iter().any(|&t| t == 0)
2655                        {
2656                            let (n0, n1) = self.b_direct_nbrs(mbx, mby);
2657                            Some(Self::b_direct_refs_mvs(&n0, &n1))
2658                        } else {
2659                            None
2660                        };
2661                        for (p, &st) in subt.iter().enumerate() {
2662                            let (b8x, b8y) = ((p % 2) * 8, (p / 2) * 8);
2663                            if st == 0 {
2664                                match hoisted {
2665                                    Some(derived) => self.b_direct_region(
2666                                        mbx, mby, b8x, b8y, 8, 8, &mut pred_y, &mut c_pred, derived,
2667                                    ),
2668                                    None => self.decode_b_direct(
2669                                        mbx, mby, b8x, b8y, 8, 8, &mut pred_y, &mut c_pred,
2670                                    ),
2671                                }
2672                                continue;
2673                            }
2674                            // From the table built at the top of this arm.
2675                            let (u0, u1) = (su[p & 3][0], su[p & 3][1]);
2676                            for &(sx, sy, sw, sh) in b_sub_parts(st) {
2677                                let (px, py) = (b8x + sx, b8y + sy);
2678                                let mut mv = [(0i32, 0i32); 2];
2679                                let (bx4, by4) = ((mbx * 4 + px / 4) as isize, (mby * 4 + py / 4) as isize);
2680                                let didx = (py / 4) * 4 + px / 4;
2681                                // A BI sub-part gathers both lists at ONE position, so the
2682                                // availability work (bounds + coded + slice) is shared —
2683                                // the fusion the 16x16 layout path already had.
2684                                if u0 && u1 {
2685                                    let (n0, n1) = self.mv_neighbors_both(bx4, by4, (sw / 4) as isize);
2686                                    for (list, nb) in [(0usize, &n0), (1, &n1)] {
2687                                        let d = (if list == 0 { &mmvd0 } else { &mmvd1 })[didx & 15];
2688                                        let pmv = predict_mv(nb[0], nb[1], nb[2], sref[p & 3][list & 1] as i32);
2689                                        mv[list & 1] = (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
2690                                    }
2691                                } else {
2692                                    for list in 0..2usize {
2693                                        if if list == 0 { u0 } else { u1 } {
2694                                            let d = (if list == 0 { &mmvd0 } else { &mmvd1 })[didx & 15];
2695                                            let nb = self.mv_neighbors_list(bx4, by4, (sw / 4) as isize, list);
2696                                            let pmv = predict_mv(nb[0], nb[1], nb[2], sref[p & 3][list & 1] as i32);
2697                                            mv[list & 1] = (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
2698                                        }
2699                                    }
2700                                }
2701                                let refi0 = if u0 { sref[p & 3][0] as i32 } else { -1 };
2702                                let refi1 = if u1 { sref[p & 3][1] as i32 } else { -1 };
2703                                self.b_set_motion(mbx, mby, px, py, sw, sh, refi0, mv[0], refi1, mv[1]);
2704                                if rec_mode {
2705                                    self.b_mc_or_record(mbx, mby, px, py, sw, sh, refi0, mv[0], refi1, mv[1], &mut pred_y, &mut c_pred);
2706                                } else {
2707                                    self.b_mc(mbx, mby, px, py, sw, sh, refi0, mv[0], refi1, mv[1], &mut pred_y, &mut c_pred);
2708                                }
2709                            }
2710                        }
2711                    } else {
2712                        let (layout, mvmode, preds) = b_inter_layout(bmt);
2713                        let parts: &[(usize, &[usize])] = match mvmode {
2714                            0 => &[(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])],
2715                            1 => &[(0, &[0, 1, 2, 3, 4, 5, 6, 7]), (8, &[8, 9, 10, 11, 12, 13, 14, 15])],
2716                            _ => &[(0, &[0, 1, 2, 3, 8, 9, 10, 11]), (4, &[4, 5, 6, 7, 12, 13, 14, 15])],
2717                        };
2718                        // ref_idx_l0 for EVERY partition, then ref_idx_l1, then the mvds
2719                        // (spec 7.3.5.1 macroblock_prediction). This was missing entirely
2720                        // -- the B path assumed a single reference -- so any B slice with
2721                        // more than one active reference in either list desynced the
2722                        // arithmetic decoder at the first partition that codes a ref_idx,
2723                        // and the slice ended early at a phantom end_of_slice_flag.
2724                        let mut pref = [[0i8; 2]; 2]; // [partition][list]
2725                        for list in 0..2usize {
2726                            let active = if list == 0 { self.num_ref_active } else { self.num_ref_active1 };
2727                            if active <= 1 {
2728                                continue;
2729                            }
2730                            let rc = if list == 0 { &mut refc0 } else { &mut refc1 };
2731                            for (p, &(pidx, zb)) in parts.iter().enumerate() {
2732                                if !preds[p & 1].uses(list) {
2733                                    continue;
2734                                }
2735                                let s = CACHE30[pidx & 15].clamp(6, 29);
2736                                let c0 = (rc[s - 1] > 0) as usize + 2 * (rc[s - 6] > 0) as usize;
2737                                let r = parse_ref_idx_cabac(&mut cab, c0);
2738                                // Seed the cache so a later partition's ref/mvd ctxInc sees it.
2739                                for &zbi in zb.iter() {
2740                                    rc[CACHE30[zbi & 15]] = r;
2741                                }
2742                                pref[p & 1][list & 1] = r;
2743                            }
2744                        }
2745                        // mvd parse order: list-major, partition-minor (openh264
2746                        // ParseInterBMotionInfoCabac); the ctxInc reads the same-list cache.
2747                        for list in 0..2usize {
2748                            let (mmv, mrf, mc, rc) = if list == 0 {
2749                                (&mut mmvd0, &mut mref0, &mut mvdc0, &mut refc0)
2750                            } else {
2751                                (&mut mmvd1, &mut mref1, &mut mvdc1, &mut refc1)
2752                            };
2753                            for (p, &(pidx, zb)) in parts.iter().enumerate() {
2754                                if preds[p & 1].uses(list) {
2755                                    parse_mvd_partition(&mut cab, pidx, zb, mc, rc, mmv, mrf, pref[p & 1][list & 1]);
2756                                }
2757                            }
2758                        }
2759                        // Per-partition recon: predict each list's MV, commit, MC.
2760                        let (mbb_x, mbb_y) = (mbx * 4, mby * 4);
2761                        for (p, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
2762                            let mut mv = [(0i32, 0i32); 2];
2763                            // uses(list) is a property of the layout — resolve both
2764                            // once instead of five times per partition, and build
2765                            // the neighbour coordinates once instead of per list.
2766                            let (u0, u1) = (preds[p & 1].uses(0), preds[p & 1].uses(1));
2767                            let (nx, ny) = ((mbb_x + rx / 4) as isize, (mbb_y + ry / 4) as isize);
2768                            let (nw, didx) = ((rw / 4) as isize, (ry / 4) * 4 + rx / 4);
2769                            // Bi partitions gather both lists at ONE position —
2770                            // fuse the availability work (same trick as
2771                            // b_direct_nbrs); uni partitions keep the single
2772                            // gather.
2773                            if u0 && u1 {
2774                                let (n0, n1) = self.mv_neighbors_both(nx, ny, nw);
2775                                for (list, n) in [(0usize, &n0), (1, &n1)] {
2776                                    let d = (if list == 0 { &mmvd0 } else { &mmvd1 })[didx & 15];
2777                                    let pmv = predict_partition_mv(mvmode, p, n[0], n[1], n[2], pref[p & 1][list & 1] as i32);
2778                                    mv[list & 1] = (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
2779                                }
2780                            } else {
2781                                for list in 0..2usize {
2782                                    if if list == 0 { u0 } else { u1 } {
2783                                        let d = (if list == 0 { &mmvd0 } else { &mmvd1 })[didx & 15];
2784                                        let n = self.mv_neighbors_list(nx, ny, nw, list);
2785                                        let pmv = predict_partition_mv(mvmode, p, n[0], n[1], n[2], pref[p & 1][list & 1] as i32);
2786                                        mv[list & 1] = (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
2787                                    }
2788                                }
2789                            }
2790                            let refi0 = if u0 { pref[p & 1][0] as i32 } else { -1 };
2791                            let refi1 = if u1 { pref[p & 1][1] as i32 } else { -1 };
2792                            self.b_set_motion(mbx, mby, rx, ry, rw, rh, refi0, mv[0], refi1, mv[1]);
2793                            // Proper spec bi-prediction (average of L0+L1). NOTE: the CAVLC
2794                            // decode_b_mb replicates an openh264 bug here for a Bi 16×8/8×16
2795                            // partition; our pixel gate is ffmpeg (spec-correct), so we do NOT.
2796                            if rec_mode {
2797                                self.b_mc_or_record(mbx, mby, rx, ry, rw, rh, refi0, mv[0], refi1, mv[1], &mut pred_y, &mut c_pred);
2798                            } else {
2799                                self.b_mc(mbx, mby, rx, ry, rw, rh, refi0, mv[0], refi1, mv[1], &mut pred_y, &mut c_pred);
2800                            }
2801                        }
2802                    }
2803                    // Paired stores: one index expression per grid pair.
2804                    // Four per-macroblock grids at one address; each is its own
2805                    // Vec, so each assignment carried its own check.
2806                    if let (Some(r0), Some(d0)) = (mb_ref.get_mut(addr), mb_mvd.get_mut(addr)) {
2807                        (*r0, *d0) = (mref0, mmvd0);
2808                    }
2809                    if let (Some(r1), Some(d1)) = (mb_ref1.get_mut(addr), mb_mvd1.get_mut(addr)) {
2810                        (*r1, *d1) = (mref1, mmvd1);
2811                    }
2812                    _smv = None; // close b:mvd-parse; the residual half follows
2813                    let _sres = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BResid);
2814
2815                    // Inter cbp + residual (identical to the P path).
2816                    let cbp = parse_cbp_cabac(&mut cab, top.and_then(|a| mb_cbp.get(a).copied()), left.and_then(|a| mb_cbp.get(a).copied()));
2817                    if let Some(p) = mb_cbp.get_mut(addr) {
2818                        *p = cbp as u8;
2819                    }
2820                    // H-49: an INTER macroblock carries transform_size_8x8_flag AFTER cbp
2821                    // (spec 7.3.5), present only when CodedBlockPatternLuma > 0 and
2822                    // noSubMbPartSizeLessThan8x8Flag. Same context as the intra read.
2823                    let t8 = self.transform_8x8_mode && (cbp & 15) != 0 && allow8 && {
2824                        let a = left.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
2825                        let b = top.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
2826                        cab.decode_decision(399 + a + b) != 0
2827                    };
2828                    if let Some(p) = self.mb_t8x8.get_mut(addr) {
2829                        *p = t8;
2830                    }
2831                    self.any_t8 |= t8;
2832                    // D9c-B: coded B with cbp==0 never parses residuals — skip the
2833                    // ~2.5 KB coeff zero-init + fat BJob when NORES is on (default).
2834                    // MC already filled pred_y / edc_regions; recon == pred (same as
2835                    // B_Skip / P InterNoRes). t8 is always false here ((cbp&15)==0).
2836                    if cbp == 0 && nores_on() {
2837                        last_delta_qp = 0;
2838                        if let Some(p) = self.mb_qp.get_mut(addr) {
2839                            *p = self.cur_qp;
2840                        }
2841                        if let Some(p) = cbf_dc.get_mut(addr) {
2842                            *p = 0;
2843                        }
2844                        if let Some(p) = mb_nzc.get_mut(addr) {
2845                    *p = [0u8; 24];
2846                }
2847                        if let Some(regions) = self.edc_regions.take() {
2848                            self.edc_giveback();
2849                            self.edc_commit_nnz(mbx, mby, false, &[0u8; 24], 0);
2850                            edcstat::bump(&edcstat::J_NORES_SENT, 1);
2851                            self.edc_send_job(EdcJob::BSkip { mbx, mby, regions });
2852                        } else {
2853                            // Inline twin of decode_b_skip's plane copy (MC already done).
2854                            for dy in 0..16 {
2855                                let d = (mby * 16 + dy) * self.cw + mbx * 16;
2856                                self.rec_y[d..d + 16]
2857                                    .copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
2858                            }
2859                            for c in 0..2 {
2860                                let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
2861                                for dy in 0..8 {
2862                                    let d = (mby * 8 + dy) * self.ccw + mbx * 8;
2863                                    plane[d..d + 8]
2864                                        .copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
2865                                }
2866                            }
2867                            let w4 = self.mb_w * 4;
2868                            for dy in 0..4 {
2869                                self.nnz_y[(mby * 4 + dy) * w4 + mbx * 4..][..4].fill(0);
2870                            }
2871                        }
2872                        let eos = cab.decode_terminate();
2873                        addr += 1;
2874                    mbx += 1;
2875                        if eos || addr >= total {
2876                            break;
2877                        }
2878                        continue;
2879                    }
2880                    // Zeroed ONLY when the 8x8 transform is in play — this was a
2881                    // 1KB memset per coded inter MB on non-t8 tiers.
2882                    let mut luma8: Option<[[i32; 64]; 4]> = None;
2883                    let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
2884                    let mut nzc = [0xffu8; 48];
2885                    if let Some(t) = top {
2886                        let tnz = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
2887                        nzc[1..5].copy_from_slice(&tnz[12..16]);
2888                        (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
2889                        (nzc[6], nzc[7], nzc[30], nzc[31]) = (tnz[20], tnz[21], tnz[22], tnz[23]);
2890                    }
2891                    if let Some(l) = left {
2892                        let lnz = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
2893                        (nzc[8], nzc[16], nzc[24], nzc[32]) = (lnz[3], lnz[7], lnz[11], lnz[15]);
2894                        (nzc[13], nzc[21], nzc[37], nzc[45]) = (lnz[17], lnz[21], lnz[19], lnz[23]);
2895                    }
2896                    let mut cbfdc = 0u16;
2897                    let mut nnzs = [0u8; 24]; // parsed totalCoeff per block
2898                    // See the P arm: materialised only when actually written.
2899                    let mut luma_scan: Option<[[i32; 16]; 16]> = None;
2900                    let mut cdc = [[0i32; 4]; 2];
2901                    let mut cac: Option<[[[i32; 16]; 4]; 2]> = None;
2902                    if cbp == 0 {
2903                        last_delta_qp = 0;
2904                    }
2905                    if cbp != 0 {
2906                        let ndc = (top.and_then(|a| cbf_dc.get(a).copied()), left.and_then(|a| cbf_dc.get(a).copied()));
2907                        let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
2908                        self.step_qp(qpd)?;
2909                        for id8 in 0..4usize {
2910                            if cbp_luma & (1 << id8) != 0 {
2911                                if t8 {
2912                                    // All four slots carry the 8x8 total: cat 5 has no per-4x4
2913                                    // counts, and the recon helper now reads one slot
2914                                    // per 4x4 cell.
2915                                    let n8 = parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, id8 * 4, RP_LUMA_8X8, false, ndc, &mut luma8.get_or_insert_with(|| [[0i32; 64]; 4])[id8]) as u8;
2916                                    for k in 0..4 {
2917                                        nnzs[id8 * 4 + k] = n8;
2918                                    }
2919                                } else {
2920                                    let ls = luma_scan.get_or_insert_with(|| [[0i32; 16]; 16]);
2921                                    for id4 in 0..4usize {
2922                                        let iz = id8 * 4 + id4;
2923                                        nnzs[iz] = parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, iz, RP_LUMA_4X4, false, ndc, &mut ls[iz]) as u8;
2924                                    }
2925                                }
2926                            } else {
2927                                for k in 0..4 {
2928                                    nzc[NZC_CACHE[(id8 * 4 + k).min(23)].min(47)] = 0;
2929                                }
2930                            }
2931                        }
2932                        if cbp_chroma >= 1 {
2933                            for i in 0..2usize {
2934                                parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, 16 + i * 4, RP_CHROMA_DC + i, false, ndc, &mut cdc[i]);
2935                            }
2936                        }
2937                        if cbp_chroma == 2 {
2938                            let cacm = cac.get_or_insert_with(|| [[[0i32; 16]; 4]; 2]);
2939                            for i in 0..2usize {
2940                                for id4 in 0..4usize {
2941                                    nnzs[16 + i * 4 + id4] = parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, 16 + i * 4 + id4, RP_CHROMA_AC + i, false, ndc, &mut cacm[i][id4]) as u8;
2942                                }
2943                            }
2944                        }
2945                    }
2946                    if let Some(p) = self.mb_qp.get_mut(addr) {
2947                        *p = self.cur_qp;
2948                    }
2949                    if let Some(p) = cbf_dc.get_mut(addr) {
2950                        *p = cbfdc;
2951                    }
2952                    let _sc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecStateCache);
2953                    let mut mn = [0u8; 24];
2954                    for k in 0..4 {
2955                        mn[k] = nzc[9 + k];
2956                        mn[4 + k] = nzc[17 + k];
2957                        mn[8 + k] = nzc[25 + k];
2958                        mn[12 + k] = nzc[33 + k];
2959                    }
2960                    (mn[16], mn[17], mn[20], mn[21]) = (nzc[14], nzc[15], nzc[22], nzc[23]);
2961                    (mn[18], mn[19], mn[22], mn[23]) = (nzc[38], nzc[39], nzc[46], nzc[47]);
2962                    // A block whose residual was skipped (cbp bit clear / no chroma AC)
2963                    // has 0 coeffs, not "unavailable" — export 0 so an intra neighbour's
2964                    // CBF ctxInc reads 0 (not the 0xff sentinel → is_intra default).
2965                    for v in mn.iter_mut() {
2966                        if *v == 0xff {
2967                            *v = 0;
2968                        }
2969                    }
2970                    if let Some(p) = mb_nzc.get_mut(addr) {
2971                        *p = mn;
2972                    }
2973                    drop(_sc);
2974                    if let Some(regions) = self.edc_regions.take() {
2975                        self.edc_giveback();
2976                        self.edc_commit_nnz(mbx, mby, t8, &nnzs, cbp_chroma);
2977                        let job = BJob {
2978                            mbx,
2979                            mby,
2980                            qp: self.cur_qp,
2981                            cbp_chroma,
2982                            skip: false,
2983                            regions,
2984                            luma_scan,
2985                            luma8,
2986                            cdc,
2987                            cac,
2988                            nnzs,
2989                        };
2990                        self.edc_send_job(EdcJob::B(Box::new(job)));
2991                    } else {
2992                        self.add_inter_residual(mbx, mby, &pred_y, &c_pred, luma_scan.as_ref(), luma8.as_ref(), &cdc, cac.as_ref(), cbp_chroma, &nnzs);
2993                    }
2994
2995                    let eos = cab.decode_terminate();
2996                    addr += 1;
2997                    mbx += 1;
2998                    if eos || addr >= total {
2999                        break;
3000                    }
3001                    continue;
3002                }
3003                mb_type = bmt - 23; // 23→0 (I_4x4), 24..=47→1..24 (I_16x16), 48→25 (PCM)
3004            } else {
3005                let li = left.map_or(0, |a| cat.get(a).is_some_and(|&c| c >= 2) as usize);
3006                let ti = top.map_or(0, |a| cat.get(a).is_some_and(|&c| c >= 2) as usize);
3007                mb_type = parse_mb_type_i_cabac(&mut cab, li + ti);
3008            }
3009            // H-48: the CABAC intra path is INLINED in this loop, not routed through
3010            // `decode_intra_mb` (which only the CAVLC readers call) — wiring the scope
3011            // there reported ZERO calls against 480,510 intra-pred calls. All three
3012            // intra entries (I-slice, P-slice mb_type>3, B-slice bmt>=23) converge
3013            // here, so this is the one point that sees every intra MB.
3014            self.edc_intra_sync(); // intra reconstruction reads neighbour PIXELS
3015            // Intra prediction ALSO reads the coded_y/modes_y/inter_y grids a
3016            // deferred span still owes (this arm is inline — it never passes
3017            // decode_b_mb's flush). Caught by the tempete ARM-DIFF.
3018            self.span_flush();
3019            let _gi = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbI);
3020            // Intra bS is a constant pattern (4 on MB edges, 3 internal) — written
3021            // here because CABAC inlines I recon and never calls decode_intra_mb.
3022            if let Some(p) = self.mb_kind.get_mut(mby * self.mb_w + mbx) {
3023                *p = rusty_h264_common::deblock::MB_KIND_INTRA;
3024            }
3025            if mb_type == 25 {
3026                // ---- I_PCM (spec §7.3.5): 384 raw byte-aligned sample bytes inside
3027                // the CABAC stream. The PCM marker was a terminate bin, so the engine
3028                // has stopped; DecodeFlush + pcm_alignment_zero_bit put the samples
3029                // at `pcm_start_byte()`, and the engine re-initialises after them
3030                // with its CONTEXTS KEPT (§9.3.1). All three slice types (I, P via
3031                // mbt 30, B via bmt 48) reach here as mb_type 25.
3032                let pcm = cab.pcm_start_byte();
3033                let end = pcm + 384;
3034                if end > rbsp.len() {
3035                    return Err(MbError::Truncated);
3036                }
3037                let mut pr = BitReader::new(&rbsp[pcm..end]);
3038                self.decode_ipcm(&mut pr, mbx, mby)?;
3039                cab.reinit_at(end);
3040                // Neighbour context (§7.4.5 + §9.3.3.1.1.x inferences): intra, QPy
3041                // unchanged (no mb_qp_delta — its ctxInc resets), CodedBlockPattern
3042                // luma/chroma inferred 15/2, every coded_block_flag (incl. DC) 1,
3043                // nnz 16, chroma pred mode 0.
3044                if let Some(p) = self.mb_qp.get_mut(addr) {
3045                    *p = self.cur_qp;
3046                }
3047                if let Some(p) = cat.get_mut(addr) {
3048                    *p = 25;
3049                }
3050                if let Some(p) = cmode.get_mut(addr) {
3051                    *p = 0;
3052                }
3053                if let Some(p) = mb_cbp.get_mut(addr) {
3054                    *p = 0x2f;
3055                }
3056                if let Some(p) = cbf_dc.get_mut(addr) {
3057                    *p = (1 << RP_I16_DC) | (1 << RP_CHROMA_DC) | (1 << (RP_CHROMA_DC + 1));
3058                }
3059                if let Some(p) = mb_nzc.get_mut(addr) {
3060                    *p = [16u8; 24];
3061                }
3062                last_delta_qp = 0;
3063                let eos = cab.decode_terminate();
3064                addr += 1;
3065                    mbx += 1;
3066                if eos || addr >= total {
3067                    break;
3068                }
3069                continue;
3070            }
3071            // chroma-pred-mode ctxInc from neighbour chroma modes (1..=3).
3072            let cci = left.map_or(0, |a| cmode.get(a).is_some_and(|c| (1..=3).contains(c)) as usize)
3073                + top.map_or(0, |a| cmode.get(a).is_some_and(|c| (1..=3).contains(c)) as usize);
3074
3075            if mb_type != 0 {
3076                // ---- I_16x16 (mb_type 1..=24): pred mode & cbp DERIVED from mb_type;
3077                // luma DC always coded. Syntax order: intra_chroma_pred_mode, mb_qp_delta,
3078                // luma DC (Hadamard), luma AC (if cbp_luma), chroma DC/AC. Mirrors the CAVLC
3079                // decode_i16, driven by the CABAC residual. ----
3080                let mt = mb_type - 1;
3081                let pred_mode = I16Mode::from_id(mt % 4);
3082                let cbp_chroma = (mt % 12) / 4;
3083                let cbp_luma_15 = mt / 12 == 1;
3084                let chroma_mode = parse_intra_chroma_pred_mode_cabac(&mut cab, cci) as u8;
3085                if let Some(p) = cmode.get_mut(addr) {
3086                    *p = chroma_mode as i32;
3087                }
3088                if let Some(p) = cat.get_mut(addr) {
3089                    *p = 2;
3090                }
3091                if let Some(p) = mb_cbp.get_mut(addr) {
3092                    *p = ((cbp_chroma as u8) << 4) | if cbp_luma_15 { 15 } else { 0 };
3093                }
3094                let w4 = self.mb_w * 4;
3095
3096                let mut nzc = [0xffu8; 48];
3097                if let Some(t) = top {
3098                    let tn = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
3099                    nzc[1..5].copy_from_slice(&tn[12..16]);
3100                    (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
3101                    (nzc[6], nzc[7]) = (tn[20], tn[21]);
3102                    (nzc[30], nzc[31]) = (tn[22], tn[23]);
3103                }
3104                if let Some(l) = left {
3105                    let ln = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
3106                    (nzc[8], nzc[16], nzc[24], nzc[32]) = (ln[3], ln[7], ln[11], ln[15]);
3107                    (nzc[13], nzc[21], nzc[37], nzc[45]) = (ln[17], ln[21], ln[19], ln[23]);
3108                }
3109
3110                let ndc = (top.and_then(|a| cbf_dc.get(a).copied()), left.and_then(|a| cbf_dc.get(a).copied()));
3111                let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
3112                self.step_qp(qpd)?;
3113                let qp = self.cur_qp;
3114                let mut cbfdc = 0u16;
3115
3116                // Luma DC (iz=0, category I16_LUMA_DC, 16 coeffs) → Hadamard dequant.
3117                let mut dc_scan = [0i32; 16];
3118                parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, 0, RP_I16_DC, true, ndc, &mut dc_scan);
3119                let recon_dc = self.dequant_luma_dc(&un_scan_4x4_dcac(&dc_scan), qp, 0);
3120
3121                // Luma AC (iz 0..15, category I16_LUMA_AC, 15 coeffs) when cbp_luma set.
3122                // Materialised only when AC is actually coded — a DC-only
3123                // I_16x16 zeroed 1 KB of stack for nothing.
3124                let mut q_blocks: Option<[[i32; 16]; 16]> = None;
3125                for (iz, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
3126                    let total = if cbp_luma_15 {
3127                        let mut ac = [0i32; 16];
3128                        let t = parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, iz, RP_I16_AC, true, ndc, &mut ac);
3129                        un_scan_4x4_ac_into(&ac, &mut q_blocks.get_or_insert_with(|| [[0i32; 16]; 16])[(lby & 3) * 4 + (lbx & 3)]);
3130                        t as u8
3131                    } else {
3132                        nzc[NZC_CACHE[iz.min(23)].min(47)] = 0;
3133                        0
3134                    };
3135                    if let Some(p) = self.nnz_y.get_mut((mby * 4 + lby) * w4 + (mbx * 4 + lbx)) {
3136                        *p = total;
3137                    }
3138                }
3139
3140                let mut cdc = [[0i32; 4]; 2];
3141                let mut cac: Option<[[[i32; 16]; 4]; 2]> = None;
3142                if cbp_chroma >= 1 {
3143                    for i in 0..2usize {
3144                        parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, 16 + i * 4, RP_CHROMA_DC + i, true, ndc, &mut cdc[i]);
3145                    }
3146                }
3147                if cbp_chroma == 2 {
3148                    let cacm = cac.get_or_insert_with(|| [[[0i32; 16]; 4]; 2]);
3149                    for i in 0..2usize {
3150                        for id4 in 0..4usize {
3151                            parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, 16 + i * 4 + id4, RP_CHROMA_AC + i, true, ndc, &mut cacm[i][id4]);
3152                        }
3153                    }
3154                }
3155
3156                // Luma recon: 16×16 intra prediction, then per-4×4 (dequant AC + injected DC).
3157                let top_ok = mby > 0 && self.nbr_in_slice(mbx, mby - 1) && self.intra_nbr_ok(mbx * 4, mby * 4 - 1);
3158                let left_ok = mbx > 0 && self.nbr_in_slice(mbx - 1, mby) && self.intra_nbr_ok(mbx * 4 - 1, mby * 4);
3159                self.recon_i16_luma(mbx, mby, pred_mode, top_ok, left_ok, q_blocks.as_ref(), &recon_dc, qp);
3160                self.recon_chroma_cabac(mbx, mby, chroma_mode, &cdc, cac.as_ref(), cbp_chroma, top_ok, left_ok);
3161
3162                if let Some(p) = self.mb_qp.get_mut(addr) {
3163                    *p = self.cur_qp;
3164                }
3165                if let Some(p) = cbf_dc.get_mut(addr) {
3166                    *p = cbfdc;
3167                }
3168                let mut mn = [0u8; 24];
3169                for k in 0..4 {
3170                    mn[k] = nzc[9 + k];
3171                    mn[4 + k] = nzc[17 + k];
3172                    mn[8 + k] = nzc[25 + k];
3173                    mn[12 + k] = nzc[33 + k];
3174                }
3175                (mn[16], mn[17], mn[20], mn[21]) = (nzc[14], nzc[15], nzc[22], nzc[23]);
3176                (mn[18], mn[19], mn[22], mn[23]) = (nzc[38], nzc[39], nzc[46], nzc[47]);
3177                for v in mn.iter_mut() {
3178                    if *v == 0xff {
3179                        *v = 0;
3180                    }
3181                }
3182                if let Some(p) = mb_nzc.get_mut(addr) {
3183                    *p = mn;
3184                }
3185
3186                let eos = cab.decode_terminate();
3187                addr += 1;
3188                    mbx += 1;
3189                if eos || addr >= total {
3190                    break;
3191                }
3192                continue;
3193            }
3194            if let Some(p) = cat.get_mut(addr) {
3195                *p = 0;
3196            }
3197            let w4 = self.mb_w * 4;
3198            // H-49: transform_size_8x8_flag. For I_NxN it precedes the intra pred
3199            // modes (spec §7.3.5); ctxIdx = 399 + condTermFlagA + condTermFlagB,
3200            // each 1 when that neighbour MB carries the flag. Omitting this read is
3201            // what desynced every High-profile stream.
3202            let t8 = self.transform_8x8_mode && {
3203                let a = left.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
3204                let b = top.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
3205                cab.decode_decision(399 + a + b) != 0
3206            };
3207            if let Some(p) = self.mb_t8x8.get_mut(addr) {
3208                *p = t8;
3209            }
3210            self.any_t8 |= t8;
3211            // Hoisted ABOVE the mode loop (it was computed after it) so the
3212            // per-block mode prediction can use it - see `predict_i4_mode_fast`.
3213            let top_ok = mby > 0
3214                && self.nbr_in_slice(mbx, mby - 1)
3215                && self.intra_nbr_ok(mbx * 4, mby * 4 - 1);
3216            let left_ok = mbx > 0
3217                && self.nbr_in_slice(mbx - 1, mby)
3218                && self.intra_nbr_ok(mbx * 4 - 1, mby * 4);
3219            // Brick 2.4 + recon: derive & store each intra mode (prev-flag → the
3220            // neighbour-predicted mode, else rem), exactly as the CAVLC path.
3221            let mut modes = [2u8; 16]; // raster [lby*4+lbx]
3222            let mut modes8 = [2u8; 4]; // one per 8×8 when t8
3223            if t8 {
3224                // One mode per 8×8, broadcast to its four 4×4 cells so neighbour
3225                // mode prediction keeps working unchanged.
3226                for b8 in 0..4usize {
3227                    let (b8x, b8y) = (b8 % 2, b8 / 2);
3228                    let (bx, by) = (mbx * 4 + b8x * 2, mby * 4 + b8y * 2);
3229                    let predicted =
3230                        self.predict_i4_mode_fast(bx, by, b8x * 2, b8y * 2, top_ok, left_ok);
3231                    let rr = parse_intra4x4_pred_mode_cabac(&mut cab);
3232                    let actual = if rr < 0 {
3233                        predicted
3234                    } else {
3235                        let rem = rr as u8;
3236                        if rem < predicted { rem } else { rem + 1 }
3237                    };
3238                    modes8[b8] = actual;
3239                    for dy in 0..2 {
3240                        for dx in 0..2 {
3241                            if let Some(p) = self.modes_y.get_mut((by + dy) * w4 + (bx + dx)) {
3242                                *p = actual;
3243                            }
3244                            modes[(b8y * 2 + dy) * 4 + (b8x * 2 + dx)] = actual;
3245                        }
3246                    }
3247                }
3248            } else {
3249                for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
3250                    let (bx, by) = (mbx * 4 + lbx, mby * 4 + lby);
3251                    let predicted = self.predict_i4_mode_fast(bx, by, lbx, lby, top_ok, left_ok);
3252                    let rr = parse_intra4x4_pred_mode_cabac(&mut cab);
3253                    let actual = if rr < 0 {
3254                        predicted
3255                    } else {
3256                        let rem = rr as u8;
3257                        if rem < predicted { rem } else { rem + 1 }
3258                    };
3259                    if let Some(m) = self.modes_y.get_mut(by * w4 + bx) {
3260                        *m = actual;
3261                    }
3262                    modes[(lby & 3) * 4 + (lbx & 3)] = actual;
3263                }
3264            }
3265            let chroma_mode = parse_intra_chroma_pred_mode_cabac(&mut cab, cci) as u8;
3266            if let Some(p) = cmode.get_mut(addr) {
3267                *p = chroma_mode as i32;
3268            }
3269            let cbp = parse_cbp_cabac(&mut cab, top.and_then(|a| mb_cbp.get(a).copied()), left.and_then(|a| mb_cbp.get(a).copied()));
3270            if let Some(p) = mb_cbp.get_mut(addr) {
3271                *p = cbp as u8;
3272            }
3273            let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
3274
3275            // Build the padded nzc cache from neighbours (openh264 WelsFillCacheNonZeroCount).
3276            let mut nzc = [0xffu8; 48];
3277            if let Some(t) = top {
3278                let tn = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
3279                nzc[1..5].copy_from_slice(&tn[12..16]);
3280                (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
3281                (nzc[6], nzc[7]) = (tn[20], tn[21]);
3282                (nzc[30], nzc[31]) = (tn[22], tn[23]);
3283            }
3284            if let Some(l) = left {
3285                let ln = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
3286                (nzc[8], nzc[16], nzc[24], nzc[32]) = (ln[3], ln[7], ln[11], ln[15]);
3287                (nzc[13], nzc[21], nzc[37], nzc[45]) = (ln[17], ln[21], ln[19], ln[23]);
3288            }
3289
3290            // Bricks 2.6 + 2.7: mb_qp_delta + residual (I_4x4 luma 4×4 + chroma DC/AC),
3291            // storing scan-order coefficients for recon.
3292            let mut cbfdc = 0u16;
3293            // Both materialised only when the parse writes them: an I_8x8
3294            // macroblock never touches luma_scan (it carries luma8), and
3295            // cbp_chroma < 2 never touches cac.
3296            let mut luma_scan: Option<[[i32; 16]; 16]> = None; // per z-order 4×4 block
3297            let mut luma8: Option<[[i32; 64]; 4]> = None; // allocated only under t8
3298            let mut cdc = [[0i32; 4]; 2]; // chroma DC per plane
3299            let mut cac: Option<[[[i32; 16]; 4]; 2]> = None; // chroma AC per plane, per 4×4 block
3300            let mut i4n = [0u8; 16]; // parse-side per-block coeff counts (I_4x4)
3301            if cbp == 0 {
3302                last_delta_qp = 0;
3303            }
3304            if cbp != 0 {
3305                let ndc = (top.and_then(|a| cbf_dc.get(a).copied()), left.and_then(|a| cbf_dc.get(a).copied()));
3306                let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
3307                self.step_qp(qpd)?;
3308                for id8 in 0..4usize {
3309                    if cbp_luma & (1 << id8) != 0 {
3310                        if t8 {
3311                            // ctxBlockCat 5: ONE 64-coefficient block per 8×8, and no
3312                            // coded_block_flag — presence comes from cbp_luma alone.
3313                            let n = parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, id8 * 4, RP_LUMA_8X8, true, ndc, &mut luma8.get_or_insert_with(|| [[0i32; 64]; 4])[id8]);
3314                            let (b8x, b8y) = (id8 % 2, id8 / 2);
3315                            for sy in 0..2 {
3316                                for sx in 0..2 {
3317                                    if let Some(p) = self.nnz_y.get_mut((mby * 4 + b8y * 2 + sy) * w4 + (mbx * 4 + b8x * 2 + sx)) {
3318                                        *p = n as u8;
3319                                    }
3320                                }
3321                            }
3322                        } else {
3323                            for id4 in 0..4usize {
3324                                let iz = id8 * 4 + id4;
3325                                // Capture the parse's own count — the recon loop
3326                                // used to re-scan all 16 coefficients per block.
3327                                i4n[iz] = parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, iz, RP_LUMA_4X4, true, ndc, &mut luma_scan.get_or_insert_with(|| [[0i32; 16]; 16])[iz]) as u8;
3328                            }
3329                        }
3330                    } else {
3331                        for k in 0..4 {
3332                            nzc[NZC_CACHE[(id8 * 4 + k).min(23)].min(47)] = 0;
3333                        }
3334                        if t8 {
3335                            let (b8x, b8y) = (id8 % 2, id8 / 2);
3336                            for sy in 0..2 {
3337                                for sx in 0..2 {
3338                                    if let Some(p) = self.nnz_y.get_mut((mby * 4 + b8y * 2 + sy) * w4 + (mbx * 4 + b8x * 2 + sx)) {
3339                                        *p = 0;
3340                                    }
3341                                }
3342                            }
3343                        }
3344                    }
3345                }
3346                if cbp_chroma >= 1 {
3347                    for i in 0..2usize {
3348                        parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, 16 + i * 4, RP_CHROMA_DC + i, true, ndc, &mut cdc[i]);
3349                    }
3350                }
3351                if cbp_chroma == 2 {
3352                    for i in 0..2usize {
3353                        for id4 in 0..4usize {
3354                            parse_residual_cabac(&mut cab, &mut nzc, &mut cbfdc, 16 + i * 4 + id4, RP_CHROMA_AC + i, true, ndc, &mut cac.get_or_insert_with(|| [[[0i32; 16]; 4]; 2])[i][id4]);
3355                        }
3356                    }
3357                }
3358            }
3359            if let Some(p) = self.mb_qp.get_mut(addr) {
3360                *p = self.cur_qp;
3361            }
3362            if let Some(p) = cbf_dc.get_mut(addr) {
3363                *p = cbfdc;
3364            }
3365            // Extract the MB's nzc (raster luma + chroma) for future neighbours.
3366            let mut mn = [0u8; 24];
3367            for k in 0..4 {
3368                mn[k] = nzc[9 + k];
3369                mn[4 + k] = nzc[17 + k];
3370                mn[8 + k] = nzc[25 + k];
3371                mn[12 + k] = nzc[33 + k];
3372            }
3373            (mn[16], mn[17], mn[20], mn[21]) = (nzc[14], nzc[15], nzc[22], nzc[23]);
3374            (mn[18], mn[19], mn[22], mn[23]) = (nzc[38], nzc[39], nzc[46], nzc[47]);
3375            for v in mn.iter_mut() {
3376                if *v == 0xff {
3377                    *v = 0;
3378                }
3379            }
3380            if let Some(p) = mb_nzc.get_mut(addr) {
3381                *p = mn;
3382            }
3383
3384            // ---- Brick 4.3a: recon (I_4x4 luma + chroma) via the CAVLC-proven primitives.
3385            let qp = self.cur_qp;
3386
3387            if t8 {
3388                // I_8x8 recon, reusing the CAVLC-proven primitives verbatim
3389                // (un_scan_8x8 / inv_quant8 / gather_i8 / intra8x8_pred /
3390                // add_residual_8x8). Only the ENTROPY half differed.
3391                for b8 in 0..4usize {
3392                    let (b8x, b8y) = (b8 % 2, b8 / 2);
3393                    let (bx, by) = (mbx * 4 + b8x * 2, mby * 4 + b8y * 2);
3394                    let coded = cbp_luma & (1 << b8) != 0;
3395                    let avail_top = b8y > 0 || top_ok;
3396                    let avail_left = b8x > 0 || left_ok;
3397                    self.recon_i8_block(bx, by, modes8[b8], avail_top, avail_left, luma8.as_ref().and_then(|l| coded.then(|| &l[b8])), qp);
3398                    for sy in 0..2 {
3399                        // Row fill: the 2x2 cell block is two contiguous PAIRS.
3400                        self.coded_y[(by + sy) * w4 + bx..][..2].fill(true);
3401                    }
3402                }
3403            }
3404            // The `t8` guard was INSIDE the loop (breaking on the first
3405            // iteration) and the `luma_scan` Option was re-resolved on EVERY one
3406            // of the sixteen blocks. Both are macroblock-invariant.
3407            if !t8 {
3408                let scans = luma_scan.as_ref().unwrap_or(&ZERO_LUMA_SCAN);
3409                for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
3410                    let (bx, by) = (mbx * 4 + lbx, mby * 4 + lby);
3411                    let at = lby > 0 || top_ok;
3412                    let al = lbx > 0 || left_ok;
3413                    let nnz = i4n[blk & 15];
3414                    if let Some(p) = self.nnz_y.get_mut(by * w4 + bx) {
3415                        *p = nnz;
3416                    }
3417                    self.recon_i4_block(
3418                        bx, by, modes[(lby & 3) * 4 + (lbx & 3)], at, al, &scans[blk & 15], nnz, qp,
3419                    );
3420                }
3421            }
3422            self.recon_chroma_cabac(mbx, mby, chroma_mode, &cdc, cac.as_ref(), cbp_chroma, top_ok, left_ok);
3423
3424            // Brick 2.1: end_of_slice_flag.
3425            let eos = cab.decode_terminate();
3426            addr += 1;
3427                    mbx += 1;
3428            if eos || addr >= total {
3429                break;
3430            }
3431        }
3432        }
3433        if trace {
3434            eprintln!("# CABAC decoded {} MBs (of {total})", addr - first_mb);
3435        }
3436        self.edc_flush(); // slice end: no job crosses a slice boundary
3437        // Return the pooled slice scratch (an error exit forfeits it — the
3438        // next slice then refills fresh allocations, correctness unchanged).
3439        self.sc_cat = cat;
3440        self.sc_cbp = mb_cbp;
3441        self.sc_cmode = cmode;
3442        self.sc_nzc = mb_nzc;
3443        self.sc_cbfdc = cbf_dc;
3444        self.sc_skip = mb_skip;
3445        self.sc_ref = mb_ref;
3446        self.sc_mvd = mb_mvd;
3447        if want_b_grids {
3448            self.sc_ref1 = mb_ref1;
3449            self.sc_mvd1 = mb_mvd1;
3450            self.sc_direct = mb_direct;
3451        }
3452        Ok(addr)
3453    }
3454
3455    /// CABAC chroma recon (mirrors `decode_chroma`'s reconstruction, driven by the
3456    /// CABAC-parsed DC/AC coefficients). `cdc[c]` = 2×2 DC (scan order); `cac[c][blk]`
3457    /// = 15 AC per 4×4 block (scan order).
3458    #[allow(clippy::too_many_arguments)]
3459    /// Add a CABAC-parsed inter residual to an already-built motion-comp prediction
3460    /// (`pred_y`/`c_pred`), writing the reconstruction. Shared by the P and B inter
3461    /// paths — same `reconstruct_4x4` as intra, MC output as the prediction, inter
3462    /// scaling lists (luma 3 / chroma 4+c). `luma_scan[z]`/`cdc`/`cac` are the
3463    /// scan-order coefficients; uncoded blocks are zero so recon == prediction.
3464    #[allow(clippy::too_many_arguments)]
3465    fn add_inter_residual(
3466        &mut self,
3467        mb_x: usize,
3468        mb_y: usize,
3469        pred_y: &[u8; 256],
3470        c_pred: &[[u8; 64]; 2],
3471        luma_scan: Option<&[[i32; 16]; 16]>,
3472        // `Some` when the macroblock carries transform_size_8x8_flag: four 8x8
3473        // blocks in 8x8 scan order, replacing the sixteen 4x4 luma blocks.
3474        luma8: Option<&[[i32; 64]; 4]>,
3475        cdc: &[[i32; 4]; 2],
3476        cac: Option<&[[[i32; 16]; 4]; 2]>,
3477        cbp_chroma: u32,
3478        // Parsed totalCoeff per block, indexed exactly as the parse's `iz`:
3479        // [0..16] luma 4x4 z-order (for t8, the 8x8 count sits at `id8*4`),
3480        // [16..24] chroma AC as `16 + c*4 + id4`. The parser already counted
3481        // every significant coefficient; re-deriving the counts here scanned
3482        // 16-64 array elements per block (~400 loads/MB) for information the
3483        // caller was holding — the diagnosis's stage-boundary re-derivation tax.
3484        nnzs: &[u8; 24],
3485    ) {
3486        // A `None` field means the parse wrote nothing there; every read below
3487        // is guarded by a zero-count test, so the shared zero plane is
3488        // read-equivalent to the per-macroblock zeroed stack array it replaces.
3489        let luma_scan = luma_scan.unwrap_or(&ZERO_LUMA_SCAN);
3490        let cac = cac.unwrap_or(&ZERO_CAC);
3491        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecResidAdd);
3492        let qp = self.cur_qp;
3493        let qpc = self.chroma_qp_for(qp);
3494        let (w4r, w2r) = (self.mb_w * 4, self.mb_w * 2);
3495        if let Some(l8) = luma8 {
3496            // INTER 8x8 luma: same primitives the I_8x8 and CAVLC paths use.
3497            for b8 in 0..4usize {
3498                let (b8x, b8y) = (b8 % 2, b8 / 2);
3499                // PER-CELL, not one aggregate broadcast over all four cells. CAVLC
3500                // codes an 8x8 block as four 4x4 sub-blocks and its nC predictor
3501                // reads these per-4x4 counts from `nnz_y`, so the broadcast
3502                // corrupted the NEXT macroblock's nC and desynced the parse -- which
3503                // is why CAVLC 8x8 streams ffmpeg accepts would not decode here. The
3504                // worker copy of this function never wrote `nnz_y` at all, so the
3505                // threaded path was unaffected and hid the defect. CABAC has no
3506                // per-4x4 counts, so its callers put the 8x8 total in all four slots.
3507                let nnz: u32 = (0..4).map(|k| nnzs[b8 * 4 + k] as u32).sum();
3508                // Two row slices, not four whole-grid indexed stores. The pair
3509                // written per row is contiguous and `nnzs` is a fixed 24-entry
3510                // array, so both sides become provable.
3511                for sy in 0..2 {
3512                    let base = (mb_y * 4 + b8y * 2 + sy) * w4r + mb_x * 4 + b8x * 2;
3513                    self.nnz_y[base..][..2].copy_from_slice(&nnzs[b8 * 4 + sy * 2..][..2]);
3514                }
3515                // The 4x4 inter path marks coded_y per block; the 8x8 branch must too,
3516                // or a later intra macroblock's neighbour availability is wrong.
3517                for sy in 0..2 {
3518                    let base = (mb_y * 4 + b8y * 2 + sy) * w4r + mb_x * 4 + b8x * 2;
3519                    self.coded_y[base..][..2].fill(true);
3520                }
3521                let (px, py) = (mb_x * 16 + b8x * 8, mb_y * 16 + b8y * 8);
3522                if nnz == 0 {
3523                    // Zero residual: recon == pred — the 4x4 arm zero shortcut,
3524                    // ported to the 8x8 branch.
3525                    edcstat::bump(&edcstat::T8_ZERO, 1);
3526                    for dy in 0..8 {
3527                        let d = (py + dy) * self.cw + px;
3528                        let po = (b8y * 8 + dy) * 16 + b8x * 8;
3529                        self.rec_y[d..d + 8].copy_from_slice(&pred_y[po..po + 8]);
3530                    }
3531                } else {
3532                    let raster = un_scan_8x8(&l8[b8]);
3533                    // list 1 = INTER 8x8 luma scaling list (0 is the intra one).
3534                    let res8 = self.inv_quant8(&raster, qp, 1);
3535                    let predb: [i32; 64] =
3536                        std::array::from_fn(|i| pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32);
3537                    let recon = add_residual_8x8(&res8, &predb);
3538                    // Eight row copies. This wrote SIXTY-FOUR individually
3539                    // bounds-checked samples into the luma plane per coded 8x8
3540                    // block — the same shape whose fix carried `recon_i8_block`.
3541                    // Present in BOTH the main path and the EDC worker twin.
3542                    for dy in 0..8 {
3543                        let d = (py + dy) * self.cw + px;
3544                        self.rec_y[d..][..8].copy_from_slice(&recon[dy * 8..][..8]);
3545                    }
3546                }
3547            }
3548        }
3549        for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
3550            if luma8.is_some() {
3551                break;
3552            }
3553            let nnz = nnzs[blk];
3554            if let Some(p) = self.nnz_y.get_mut((mb_y * 4 + lby) * w4r + (mb_x * 4 + lbx)) {
3555                *p = nnz;
3556            }
3557            let cw = self.cw;
3558            let p_off = (lby * 4) * 16 + lbx * 4;
3559            let r_off = (mb_y * 4 + lby) * 4 * cw + (mb_x * 4 + lbx) * 4;
3560            if nnz == 0 {
3561                // Zero residual → recon == prediction EXACTLY (the integer IDCT is
3562                // linear so zeros map to zeros, and pred is already 0..=255) — copy
3563                // the pred rows straight into the plane. On real (sparse-cbp)
3564                // streams this is MOST of the 4×4 blocks.
3565                for r in 0..4 {
3566                    self.rec_y[r_off + r * cw..r_off + r * cw + 4]
3567                        .copy_from_slice(&pred_y[p_off + r * 16..p_off + r * 16 + 4]);
3568                }
3569                continue;
3570            }
3571            // DC-ONLY: the sole significant coefficient is scan position 0 (the
3572            // zig-zag starts at DC, and un_scan keeps it at raster 0), so the
3573            // whole dequant + IDCT collapses to one multiply and a flat add.
3574            if nnz == 1 && luma_scan[blk][0] != 0 {
3575                let f = self.dequant_dc4(luma_scan[blk][0], qp, 3);
3576                reconstruct_4x4_dc_into((f + 32) >> 6, pred_y, p_off, 16, &mut self.rec_y, r_off, cw);
3577            } else {
3578                // Fused un-scan + dequant over ONLY the significant coefficients,
3579                // then IDCT + add + clip straight into the plane — no `qb`, no
3580                // `deq`-from-dense, no `predb` gather, no `s`, no `store` call.
3581                //
3582                // HYBRID: the scatter walks scan positions with a data-dependent
3583                // branch per slot, which beats the branchless dense 16-multiply
3584                // loop only while the block is SPARSE. The DC/zero fast paths
3585                // already removed the sparsest blocks, so the population here
3586                // skews denser — above ~6 coefficients the dense loop wins.
3587                let deq = if nnz <= 6 {
3588                    dequant_scatter_4x4(&luma_scan[blk], nnz, 0, qp, self.scaling.as_ref().map(|sc| &sc[3]))
3589                } else {
3590                    self.dequant(&un_scan_4x4_dcac(&luma_scan[blk]), qp, 3)
3591                };
3592                reconstruct_4x4_into(&deq, pred_y, p_off, 16, &mut self.rec_y, r_off, cw);
3593            }
3594        }
3595        let mut c_dc = [[0i32; 4]; 2];
3596        if cbp_chroma != 0 {
3597            for c in 0..2 {
3598                c_dc[c] = self.dequant_chroma_dc(&cdc[c], qpc, 4 + c);
3599            }
3600        }
3601        let ccw = self.ccw;
3602        for c in 0..2 {
3603            for &(bx, by) in &CHROMA_4X4_SCAN_XY {
3604                let mut ac_nz = false;
3605                if cbp_chroma == 2 {
3606                    let n = nnzs[(16 + c * 4 + by * 2 + bx).min(23)];
3607                    if let Some(p) = self.nnz_c[c & 1].get_mut((mb_y * 2 + by) * w2r + (mb_x * 2 + bx)) {
3608                        *p = n;
3609                    }
3610                    ac_nz = n != 0;
3611                }
3612                let dc = c_dc[c & 1][(by * 2 + bx) & 3];
3613                let p_off = (by * 4) * 8 + bx * 4;
3614                let r_off = (mb_y * 2 + by) * 4 * ccw + (mb_x * 2 + bx) * 4;
3615                let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
3616                if dc == 0 && !ac_nz {
3617                    // Zero residual (no AC, zero DC) → recon == prediction exactly.
3618                    for r in 0..4 {
3619                        plane[r_off + r * ccw..r_off + r * ccw + 4]
3620                            .copy_from_slice(&c_pred[c][p_off + r * 8..p_off + r * 8 + 4]);
3621                    }
3622                    continue;
3623                }
3624                // DC-ONLY (no coded AC — covers every cbp_chroma==1 block and the
3625                // AC-empty blocks of cbp_chroma==2): the chroma DC arrives ALREADY
3626                // dequantized, so the residual is `(dc + 32) >> 6` flat.
3627                if !ac_nz {
3628                    reconstruct_4x4_dc_into((dc + 32) >> 6, &c_pred[c], p_off, 8, plane, r_off, ccw);
3629                    continue;
3630                }
3631                // AC-only scan: index i is overall scan position i+1 (ac_shift=1).
3632                // Same sparse/dense hybrid as luma.
3633                let n = nnzs[(16 + c * 4 + by * 2 + bx).min(23)];
3634                let mut deq = if n <= 6 {
3635                    dequant_scatter_4x4(&cac[c & 1][(by * 2 + bx) & 3], n, 1, qpc, self.scaling.as_ref().map(|sc| &sc[4 + c]))
3636                } else {
3637                    let mut ac = [0i32; 16];
3638                    un_scan_4x4_ac_into(&cac[c & 1][(by * 2 + bx) & 3], &mut ac);
3639                    // Free-fn dequant: `self.dequant` borrows all of `self`, which
3640                    // conflicts with the live `plane` (&mut self.rec_u/v) borrow.
3641                    match &self.scaling {
3642                        Some(sc) => dequantize_weighted(&ac, qpc, &sc[4 + c]),
3643                        None => dequantize(&ac, qpc),
3644                    }
3645                };
3646                deq[0] = dc;
3647                reconstruct_4x4_into(&deq, &c_pred[c], p_off, 8, plane, r_off, ccw);
3648            }
3649        }
3650    }
3651
3652    // ── SHARED INTRA RECON PRIMITIVES ──────────────────────────────────
3653    // ONE implementation per block family, called by BOTH entropy arms.
3654    // The per-coder recon twins are where routing rot lived: the residual
3655    // ladder and the DC-only collapse each had to be found and ported
3656    // twin-by-twin. The parse halves stay per-coder (that is the real
3657    // difference between the coders); the pixel halves live here — the
3658    // same convergence D14 gave the inter path via add_inter_residual.
3659
3660    /// Intra 4x4 luma block: gather + predict + the residual ladder
3661    /// (zero / DC-only / sparse-scatter / dense), fused into the plane.
3662    /// `scan` = the block's coefficients in SCAN order; `nnz` its count
3663    /// (the caller has already committed nnz to its entropy-side state).
3664    /// Marks the block coded.
3665    #[allow(clippy::too_many_arguments)]
3666    fn recon_i4_block(&mut self, bx: usize, by: usize, mode: u8, at: bool, al: bool, scan: &[i32; 16], nnz: u8, qp: u8) {
3667        let (px, py) = (bx * 4, by * 4);
3668        let (t, l, corner) = self.gather_i4(px, py, at, al, bx, by);
3669        let pred = intra4x4_pred(mode, at, al, &t, &l, corner);
3670        let r_off = py * self.cw + px;
3671        let cw = self.cw;
3672        if nnz == 0 {
3673            edcstat::bump(&edcstat::I4_ZERO, 1);
3674            // ONE span for the 4x4 write window (rows `cw` apart), so the four
3675            // row copies share a single bounds check. This is the DOMINANT arm:
3676            // 63.4% of I_4x4 blocks are all-zero on all-intra content.
3677            let win = &mut self.rec_y[r_off..r_off + 3 * cw + 4];
3678            for r in 0..4 {
3679                win[r * cw..r * cw + 4].copy_from_slice(&pred[r * 4..r * 4 + 4]);
3680            }
3681        } else if nnz == 1 && scan[0] != 0 {
3682            edcstat::bump(&edcstat::I4_DC, 1);
3683            let f = self.dequant_dc4(scan[0], qp, 0);
3684            reconstruct_4x4_dc_into((f + 32) >> 6, &pred, 0, 4, &mut self.rec_y, r_off, cw);
3685        } else {
3686            let deq = if nnz <= 6 {
3687                edcstat::bump(&edcstat::I4_SPARSE, 1);
3688                dequant_scatter_4x4(scan, nnz, 0, qp, self.scaling.as_ref().map(|sc| &sc[0]))
3689            } else {
3690                edcstat::bump(&edcstat::I4_DENSE, 1);
3691                self.dequant(&un_scan_4x4_dcac(scan), qp, 0)
3692            };
3693            reconstruct_4x4_into(&deq, &pred, 0, 4, &mut self.rec_y, r_off, cw);
3694        }
3695        if let Some(c) = self.coded_y.get_mut(by * (self.mb_w * 4) + bx) {
3696            *c = true;
3697        }
3698    }
3699
3700    /// Intra 8x8 luma block: predict + zero-arm or un-scan/quant/add.
3701    /// `coeffs` = SCAN-order 8x8 coefficients, `None` when the cbp bit is
3702    /// unset. `coded_y` marking stays with the callers (their orders differ).
3703    fn recon_i8_block(&mut self, bx: usize, by: usize, mode: u8, avail_top: bool, avail_left: bool, coeffs: Option<&[i32; 64]>, qp: u8) {
3704        let (px, py) = (bx * 4, by * 4);
3705        let (t, l, corner, avail_corner) = self.gather_i8(px, py, avail_top, avail_left, bx, by);
3706        let pred = intra8x8_pred(mode, avail_top, avail_left, avail_corner, &t, &l, corner);
3707        match coeffs {
3708            None => {
3709                // Zero residual: recon == pred exactly.
3710                edcstat::bump(&edcstat::T8_ZERO, 1);
3711                let (cw, base) = (self.cw, py * self.cw + px);
3712                let win = &mut self.rec_y[base..base + 7 * cw + 8];
3713                for dy in 0..8 {
3714                    win[dy * cw..dy * cw + 8].copy_from_slice(&pred[dy * 8..dy * 8 + 8]);
3715                }
3716            }
3717            Some(scan8) => {
3718                let raster = un_scan_8x8(scan8);
3719                let res8 = self.inv_quant8(&raster, qp, 0);
3720                // Written once rather than zeroed-then-filled.
3721                let predb: [i32; 64] = core::array::from_fn(|i| pred[i] as i32);
3722                let recon = add_residual_8x8(&res8, &predb);
3723                // ROW COPIES, NOT PER-PIXEL STORES. This wrote all 64 samples
3724                // one at a time, each separately bounds-checked, where the
3725                // destination is eight contiguous 8-byte runs `cw` apart.
3726                // REFUTED, do not retry: replacing this WINDOW + sub-slice with
3727                // eight direct `rec_y[d..][..8]` slices left the panic count
3728                // EXACTLY unchanged (12) and cost +3.0% instructions. Neither
3729                // form folds the check, but the window amortises the base
3730                // address computation across the eight rows. (Contrast
3731                // `gather_tile`, where a window cost +57.6% — a window pays only
3732                // when the rows it spans are written in one tight loop.)
3733                let (cw, base) = (self.cw, py * self.cw + px);
3734                let win = &mut self.rec_y[base..base + 7 * cw + 8];
3735                for dy in 0..8 {
3736                    win[dy * cw..dy * cw + 8].copy_from_slice(&recon[dy * 8..dy * 8 + 8]);
3737                }
3738            }
3739        }
3740    }
3741
3742    /// I_16x16 luma: neighbor gather + prediction + the 16 4x4 AC blocks
3743    /// with the DC-only collapse. `q_blocks` = RASTER AC (slot 0 unused),
3744    /// `recon_dc` = the Hadamard-dequantized DC per block. Marks modes_y
3745    /// (I_16x16 predicts as DC for neighbors) + coded_y.
3746    #[allow(clippy::too_many_arguments)]
3747    fn recon_i16_luma(&mut self, mbx: usize, mby: usize, pred_mode: rusty_h264_common::predict::I16Mode, top_ok: bool, left_ok: bool, q_blocks: Option<&[[i32; 16]; 16]>, recon_dc: &[i32; 16], qp: u8) {
3748        // `None` on a DC-only I_16x16 macroblock (CodedBlockPatternLuma == 0):
3749        // no AC was parsed, so the shared zero plane is read-equivalent to the
3750        // 1 KB of stack this used to zero per macroblock.
3751        let q_blocks = q_blocks.unwrap_or(&ZERO_LUMA_SCAN);
3752        let w4 = self.mb_w * 4;
3753        let (lx, ly) = (mbx * 16, mby * 16);
3754        let mut t16 = [0u8; 16];
3755        let mut l16 = [0u8; 16];
3756        if top_ok {
3757            t16.copy_from_slice(self.top_y_row(ly, lx, 16));
3758        }
3759        if left_ok {
3760            // ONE check for the 16-sample column (see `gather_i4`).
3761            // `step_by` walk rather than a span + `col[i * cw]` (see `gather_i8`):
3762            // the span form's inner index stayed checked on all sixteen samples.
3763            let (cw, base) = (self.cw, ly * self.cw + lx - 1);
3764            for (s, &v) in l16.iter_mut().zip(self.rec_y[base..].iter().step_by(cw)) {
3765                *s = v;
3766            }
3767        }
3768        let corner = if top_ok && left_ok { self.top_y_px(ly, lx - 1) } else { 0 };
3769        let pred_l = luma16x16_pred(pred_mode, top_ok, left_ok, &t16, &l16, corner);
3770        for by in 0..4 {
3771            for bx in 0..4 {
3772                let p_off = (by * 4) * 16 + bx * 4;
3773                let r_off = (ly + by * 4) * self.cw + lx + bx * 4;
3774                if q_blocks[(by & 3) * 4 + (bx & 3)] == [0i32; 16] {
3775                    // Zero AC: the residual is the Hadamard DC alone.
3776                    edcstat::bump(&edcstat::I16_DCONLY, 1);
3777                    reconstruct_4x4_dc_into((recon_dc[by * 4 + bx] + 32) >> 6, &pred_l, p_off, 16, &mut self.rec_y, r_off, self.cw);
3778                } else {
3779                    let mut deq = self.dequant(&q_blocks[(by & 3) * 4 + (bx & 3)], qp, 0);
3780                    deq[0] = recon_dc[by * 4 + bx];
3781                    reconstruct_4x4_into(&deq, &pred_l, p_off, 16, &mut self.rec_y, r_off, self.cw);
3782                }
3783            }
3784        }
3785        // ROW FILLS. The mode/coded grids were written one 4x4 cell at a time
3786        // inside the recon loop; each macroblock row is four CONTIGUOUS cells,
3787        // so four fills replace sixteen indexed stores.
3788        for by in 0..4 {
3789            let r = (mby * 4 + by) * w4 + mbx * 4;
3790            self.modes_y[r..r + 4].fill(2);
3791            self.coded_y[r..r + 4].fill(true);
3792        }
3793    }
3794
3795    /// Chroma 8x8 recon, both planes: prediction + four 4x4 blocks per
3796    /// plane with the DC-only collapse. `qac` = RASTER AC per plane/block
3797    /// (all-zero when uncoded), `dc` = the 2x2-Hadamard-dequantized DC.
3798    #[allow(clippy::too_many_arguments)]
3799    fn recon_chroma_blocks(&mut self, mb_x: usize, mb_y: usize, chroma_mode: u8, avail_top: bool, avail_left: bool, qac: &[[[i32; 16]; 4]; 2], dc: &[[i32; 4]; 2], qpc: u8) {
3800        let (cx, cy) = (mb_x * 8, mb_y * 8);
3801        for c in 0..2 {
3802            let mut ctop = [0u8; 8];
3803            let mut cleft = [0u8; 8];
3804            let mut ccorner = 0u8;
3805            {
3806                let rec_c = if c == 0 { &self.rec_u } else { &self.rec_v };
3807                if avail_top {
3808                    ctop.copy_from_slice(self.top_c_row(c, cy, cx, 8));
3809                }
3810                if avail_left {
3811                    // Strided WALK, as in `gather_i4`/`gather_i8`.
3812                    let cbase = cy * self.ccw + cx - 1;
3813                    for (o, &v) in cleft.iter_mut().zip(rec_c[cbase..].iter().step_by(self.ccw)) {
3814                        *o = v;
3815                    }
3816                }
3817                if avail_top && avail_left {
3818                    ccorner = self.top_c_px(c, cy, cx - 1);
3819                }
3820            }
3821            let pred8 = chroma8x8_pred(chroma_mode, avail_top, avail_left, &ctop, &cleft, ccorner);
3822            for &(bx, by) in &CHROMA_4X4_SCAN_XY {
3823                let p_off = (by * 4) * 8 + bx * 4;
3824                let ccw = self.ccw;
3825                let r_off = (cy + by * 4) * ccw + cx + bx * 4;
3826                if qac[c & 1][(by * 2 + bx) & 3] == [0i32; 16] {
3827                    // Zero AC (cbp_chroma <= 1, the common case): DC-alone flat add.
3828                    edcstat::bump(&edcstat::I16_DCONLY, 1);
3829                    let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
3830                    reconstruct_4x4_dc_into((dc[c & 1][(by * 2 + bx) & 3] + 32) >> 6, &pred8, p_off, 8, plane, r_off, ccw);
3831                } else {
3832                    let mut deq = self.dequant(&qac[c & 1][(by * 2 + bx) & 3], qpc, 1 + c);
3833                    deq[0] = dc[c & 1][(by * 2 + bx) & 3];
3834                    let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
3835                    reconstruct_4x4_into(&deq, &pred8, p_off, 8, plane, r_off, ccw);
3836                }
3837            }
3838        }
3839    }
3840
3841    fn recon_chroma_cabac(
3842        &mut self,
3843        mb_x: usize,
3844        mb_y: usize,
3845        chroma_mode: u8,
3846        cdc: &[[i32; 4]; 2],
3847        cac: Option<&[[[i32; 16]; 4]; 2]>,
3848        cbp_chroma: u32,
3849        avail_top: bool,
3850        avail_left: bool,
3851    ) {
3852        // `None` = no chroma AC was parsed; every read below is already gated
3853        // on cbp_chroma == 2, so the shared zero plane is read-equivalent.
3854        let cac = cac.unwrap_or(&ZERO_CAC);
3855        let qpc = self.chroma_qp_for(self.cur_qp);
3856        let mut c_dc = [[0i32; 4]; 2];
3857        if cbp_chroma != 0 {
3858            for c in 0..2 {
3859                c_dc[c] = self.dequant_chroma_dc(&cdc[c], qpc, 1 + c);
3860            }
3861        }
3862        // ENTROPY-SIDE half: un-scan the AC into raster + commit nnz_c; the
3863        // pixel half is the SHARED recon_chroma_blocks.
3864        let w2 = self.mb_w * 2;
3865        let mut qac = [[[0i32; 16]; 4]; 2];
3866        if cbp_chroma == 2 {
3867            for c in 0..2 {
3868                for &(bx, by) in &CHROMA_4X4_SCAN_XY {
3869                    let cnt = cac[c & 1][(by * 2 + bx) & 3].iter().filter(|&&v| v != 0).count() as u8;
3870                    if let Some(p) = self.nnz_c[c & 1].get_mut((mb_y * 2 + by) * w2 + (mb_x * 2 + bx)) {
3871                        *p = cnt;
3872                    }
3873                    // Zero-skip: empty AC leaves the fresh-zero raster block.
3874                    if cnt != 0 {
3875                        un_scan_4x4_ac_into(&cac[c & 1][(by * 2 + bx) & 3], &mut qac[c & 1][(by * 2 + bx) & 3]);
3876                    }
3877                }
3878            }
3879        }
3880        self.recon_chroma_blocks(mb_x, mb_y, chroma_mode, avail_top, avail_left, &qac, &c_dc, qpc);
3881    }
3882
3883    /// D14 — the CAVLC E-seam (P3 item 5). Mirrors `decode_slice_data_cabac`:
3884    /// overlap parse (this thread) with pixel reconstruction (a scoped worker
3885    /// owning the planes). Now possible because the CAVLC inter recon was
3886    /// converged onto `add_inter_residual`, so both entropy coders emit the SAME
3887    /// `PInterJob` and share one worker recon.
3888    pub fn decode_slice_data(
3889        &mut self,
3890        r: &mut BitReader,
3891        is_p: bool,
3892        first_mb: usize,
3893    ) -> Result<usize, MbError> {
3894        // Same cross-slice guard as the CABAC loop head.
3895        self.span_flush();
3896        let eligible = edc_on() && rowdb_on() && (is_p || self.is_b);
3897        let threaded = eligible && edc_spawn_worker(self.mb_w, self.mb_h, self.bits_per_mb, false);
3898        edcstat::bump(&edcstat::DISPATCH_ON, threaded as u64);
3899        edcstat::bump(&edcstat::DISPATCH_SEEN, eligible as u64);
3900        if !threaded {
3901            return self.decode_slice_cavlc_inner(r, is_p, first_mb);
3902        }
3903        let ctx = self.edc_take_ctx();
3904        let (tx, rx) = std::sync::mpsc::sync_channel::<EdcMsg>(edc_bound());
3905        let (ctx_tx, ctx_rx) = std::sync::mpsc::channel::<PixelCtx>();
3906        let (back_tx, back_rx) = std::sync::mpsc::channel::<PixelCtx>();
3907        let (res, ctx, panicked) = std::thread::scope(|sc| {
3908            let h = sc.spawn(move || edc_worker(ctx, rx, ctx_tx, back_rx));
3909            self.edc_tx = Some(tx);
3910            self.edc_ctx_rx = Some(ctx_rx);
3911            self.edc_back_tx = Some(back_tx);
3912            // UNWIND SAFETY (same trap the CABAC wrapper documents): the sender
3913            // lives in `self`, which outlives an unwind, so a panic in the parse
3914            // loop would leave the channel open, the worker alive and the scope
3915            // join blocking forever — turning a diagnosable panic into a silent
3916            // deadlock. Catch, clean up, join, restore, THEN resume.
3917            let r2 = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3918                self.decode_slice_cavlc_inner(r, is_p, first_mb)
3919            }));
3920            self.edc_flush_batch();
3921            self.edc_giveback();
3922            self.edc_tx = None;
3923            self.edc_ctx_rx = None;
3924            self.edc_back_tx = None;
3925            match (r2, h.join()) {
3926                (Ok(res), Ok(ctx)) => (res, Some(ctx), None),
3927                (Err(pn), Ok(ctx)) => (Err(MbError::Truncated), Some(ctx), Some(pn)),
3928                (Ok(_), Err(pn)) | (Err(_), Err(pn)) => (Err(MbError::Truncated), None, Some(pn)),
3929            }
3930        });
3931        if let Some(ctx) = ctx {
3932            self.edc_restore_ctx(ctx);
3933        }
3934        if let Some(pn) = panicked {
3935            std::panic::resume_unwind(pn);
3936        }
3937        res
3938    }
3939
3940    fn decode_slice_cavlc_inner(
3941        &mut self,
3942        r: &mut BitReader,
3943        is_p: bool,
3944        first_mb: usize,
3945    ) -> Result<usize, MbError> {
3946        let total = self.mb_w * self.mb_h;
3947        self.slice_first_mb = first_mb;
3948        self.slice_bounds.push((first_mb, self.cur_idc2));
3949        self.any_idc2 |= self.cur_idc2;
3950        self.edc_active = edc_on();
3951        let mut addr = first_mb;
3952        // Same malformed-header divide-by-zero as the sibling slice loop above.
3953        let mbw_nz = self.mb_w.max(1);
3954        let (mut mbx, mut mby) = (addr % mbw_nz, addr / mbw_nz);
3955        let mbw_c = self.mb_w;
3956        while addr < total {
3957            if mbx == mbw_c {
3958                mbx = 0;
3959                mby += 1;
3960            }
3961            self.row_hook_at(addr, mby);
3962            if is_p || self.is_b {
3963                let skip_run = {
3964                    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
3965                    r.read_ue()?
3966                } as usize;
3967                // A run past the picture end is a corrupt stream (ffmpeg errors
3968                // here too); a run TO the end is legal. Silently clamping used
3969                // to fill the remainder with skip MBs.
3970                if skip_run > total - addr {
3971                    return Err(MbError::Truncated);
3972                }
3973                // The run length is KNOWN here (CAVLC codes it as one syntax
3974                // element). P runs: after the first skip commits (0,0), every
3975                // later run MB is FORCED (0,0) by the zero-MV rule — process
3976                // the remainder SEGMENT-WISE: one span extension + one mb_qp
3977                // fill per row segment instead of a per-MB call + span match +
3978                // store. Non-(0,0) runs and B runs keep the per-MB loop. The
3979                // per-MB `addr >= total` check is gone: the run was validated
3980                // against `total - addr` above.
3981                let mut remaining = skip_run;
3982                while remaining > 0 {
3983                    debug_assert!(addr < total);
3984                    if mbx == mbw_c {
3985                        mbx = 0;
3986                        mby += 1;
3987                    }
3988                    if self.is_b {
3989                        if !self.b_skip_hot(mbx, mby) {
3990                            self.decode_b_skip(mbx, mby)?;
3991                        }
3992                        if let Some(p) = self.mb_qp.get_mut(addr) {
3993                            *p = self.cur_qp;
3994                        } // skip inherits QPy
3995                        addr += 1;
3996                        mbx += 1;
3997                        remaining -= 1;
3998                        continue;
3999                    }
4000                    self.decode_p_skip(mbx, mby)?;
4001                    if let Some(p) = self.mb_qp.get_mut(addr) {
4002                        *p = self.cur_qp;
4003                    }
4004                    addr += 1;
4005                    mbx += 1;
4006                    remaining -= 1;
4007                    // Segment-wise remainder: forced (0,0) continuation holds
4008                    // exactly while skip_zero_next tracks addr (decode_p_skip
4009                    // set it iff this skip committed (0,0)).
4010                    // Worker mode (edc_tx) must route per-MB jobs through the
4011                    // channel — the parse thread no longer owns the planes.
4012                    while remaining > 0
4013                        && self.skip_zero_next == addr
4014                        && !self.k_no_runmv
4015                        && self.edc_tx.is_none()
4016                    {
4017                        if mbx == mbw_c {
4018                            mbx = 0;
4019                            mby += 1;
4020                        }
4021                        // Row segment: run to row end or run end.
4022                        let seg = remaining.min(mbw_c - mbx);
4023                        let recon = self.edc_tx.is_none()
4024                            && (self.weights.is_none() || self.weights_id0);
4025                        for k in 0..seg {
4026                            self.pz_push(mbx + k, mby, recon);
4027                        }
4028                        edcstat::bump(&edcstat::SKIPMV_FORCED, seg as u64);
4029                        self.route_skip_mbs += seg as u32;
4030                        self.mb_qp[addr..addr + seg].fill(self.cur_qp);
4031                        if !recon {
4032                            for k in 0..seg {
4033                                self.edc_jobs.push(EdcJob::Skip { mbx: mbx + k, mby, mv: (0, 0) });
4034                            }
4035                        }
4036                        addr += seg;
4037                        mbx += seg;
4038                        remaining -= seg;
4039                        self.skip_zero_next = addr;
4040                    }
4041                }
4042                if addr >= total {
4043                    break;
4044                }
4045                // A trailing skip run with no following macroblock ends the slice.
4046                if skip_run > 0 && !r.more_rbsp_data() {
4047                    break;
4048                }
4049            }
4050            // The skip run above may have crossed a row boundary within this
4051            // iteration — wrap before the non-skip MB decodes.
4052            if mbx == mbw_c {
4053                mbx = 0;
4054                mby += 1;
4055            }
4056            if self.is_b {
4057                // ORDER: B reconstructs inline (not seam-ready).
4058                self.edc_intra_sync();
4059                self.decode_b_mb(r, mbx, mby)?;
4060            } else {
4061                // Non-skip CAVLC MB: inter MV prediction + intra gathers
4062                // read the grids — flush deferred spans.
4063                self.span_flush();
4064                self.decode_mb(r, mbx, mby, is_p)?;
4065            }
4066            if let Some(p) = self.mb_qp.get_mut(addr) {
4067                *p = self.cur_qp;
4068            }
4069            addr += 1;
4070            mbx += 1;
4071            // CAVLC slice end: no more data after this macroblock.
4072            if !r.more_rbsp_data() {
4073                break;
4074            }
4075        }
4076        self.edc_flush(); // slice end: no job crosses a slice boundary
4077        Ok(addr)
4078    }
4079
4080    fn decode_mb(
4081        &mut self,
4082        r: &mut BitReader,
4083        mb_x: usize,
4084        mb_y: usize,
4085        is_p: bool,
4086    ) -> Result<(), MbError> {
4087        self.wait_refs_for_mb(mb_y);
4088        let mut mb_type = {
4089            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
4090            r.read_ue()?
4091        };
4092        if is_p {
4093            // In P-slices, mb_type 0/1/2 are inter (16×16, 16×8, 8×16),
4094            // 3 = P_8x8, 4 = P_8x8ref0 (ref_idx forced 0), 5+ intra.
4095            if mb_type <= 2 {
4096                return self.decode_inter(r, mb_x, mb_y, mb_type as u8);
4097            }
4098            if mb_type == 3 || mb_type == 4 {
4099                return self.decode_p8x8(r, mb_x, mb_y, mb_type == 4);
4100            }
4101            mb_type -= 5;
4102        }
4103        // ORDER: intra reconstruction reads neighbour PIXELS, so the worker
4104        // must have applied every deferred job before this point.
4105        self.edc_intra_sync();
4106        self.decode_intra_mb(r, mb_x, mb_y, mb_type)
4107    }
4108
4109    /// Decodes an intra macroblock given its intra `mb_type` (0 = I_4x4,
4110    /// 1..=24 = I_16x16, 25 = I_PCM) — shared by I-, P- and B-slice paths.
4111    fn decode_intra_mb(
4112        &mut self,
4113        r: &mut BitReader,
4114        mb_x: usize,
4115        mb_y: usize,
4116        mb_type: u32,
4117    ) -> Result<(), MbError> {
4118        // H-48: this scope was DECLARED and never wired, which is precisely why the
4119        // stage table left 19.8% unaccounted — 66,120 of 475,200 macroblocks on the
4120        // reference stream are I-type and had no scope at all.
4121        let _gi = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbI);
4122        if let Some(p) = self.mb_kind.get_mut(mb_y * self.mb_w + mb_x) {
4123            *p = rusty_h264_common::deblock::MB_KIND_INTRA;
4124        }
4125        if mb_type == 0 {
4126            // I_NxN: transform_size_8x8_flag (when enabled) selects I_8x8 vs I_4x4.
4127            if self.transform_8x8_mode && r.read_bit()? {
4128                self.decode_i8x8(r, mb_x, mb_y)?;
4129            } else {
4130                self.decode_i4x4(r, mb_x, mb_y)?;
4131            }
4132        } else if (1..=24).contains(&mb_type) {
4133            self.decode_i16(r, mb_x, mb_y, mb_type - 1)?;
4134        } else if mb_type == 25 {
4135                        // ORDER: I_PCM writes pixels directly.
4136            self.edc_intra_sync();
4137            self.decode_ipcm(r, mb_x, mb_y)?;
4138        } else {
4139            return Err(MbError::Unsupported("only I_4x4 / I_16x16 / I_PCM macroblocks"));
4140        }
4141        // Mark all luma blocks coded for the next macroblock's top-right.
4142        // Four contiguous cells per macroblock row - `fill`, not sixteen
4143        // separately bounds-checked indexed stores.
4144        let w4 = self.mb_w * 4;
4145        for lby in 0..4usize {
4146            let a = (mb_y * 4 + lby) * w4 + mb_x * 4;
4147            self.coded_y[a..a + 4].fill(true);
4148        }
4149        Ok(())
4150    }
4151
4152    /// Reconstructs an inter macroblock (`mode` 0 = P_L0_16x16, 1 = P_16x8,
4153    /// 2 = P_8x16): parse the per-partition motion vectors and residual,
4154    /// motion-compensate each partition, and add the residual.
4155    fn decode_inter(
4156        &mut self,
4157        r: &mut BitReader,
4158        mb_x: usize,
4159        mb_y: usize,
4160        mode: u8,
4161    ) -> Result<(), MbError> {
4162        if self.refs.is_empty() {
4163            return Err(MbError::Unsupported("inter without reference"));
4164        }
4165        // DEBLOCK CLASS: mode 0 is P_L0_16x16 — ONE partition, so all 16 blocks
4166        // share a reference and motion vector and no internal edge can reach
4167        // strength 1. Internal strengths then follow from coefficients alone, i.e.
4168        // 16 nnz bytes instead of a 24-block gather across 5-7 grids. Modes 1/2
4169        // (P_16x8 / P_8x16) have two partitions with independent motion and stay
4170        // UNSET (blind path).
4171        if mode == 0 {
4172            if let Some(k) = self.mb_kind.get_mut(mb_y * self.mb_w + mb_x) {
4173                *k = rusty_h264_common::deblock::MB_KIND_INTER_UNIFORM;
4174            }
4175        }
4176        // QP (qp/qpc) is bound after mb_qp_delta is read below.
4177        let w4 = self.mb_w * 4;
4178        let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
4179        let num_refs = self.refs.len();
4180        let layout = inter_partitions(mode);
4181
4182        // mb_pred order (spec 7.3.5.1): all ref_idx_l0 first (only when more than
4183        // one reference is active), then all mvd_l0.
4184        let nparts = layout.len();
4185        let mut ref_idxs = [0i32; 4];
4186        if self.num_ref_active > 1 {
4187            for ri in ref_idxs[..nparts].iter_mut() {
4188                *ri = read_ref_idx(r, self.num_ref_active)?;
4189                if *ri as usize >= num_refs {
4190                    return Err(MbError::Truncated); // references a non-existent picture
4191                }
4192            }
4193        }
4194
4195        // Phase 1: per partition, ref-aware MV prediction + mvd, committing the
4196        // motion grid so a later partition predicts from an earlier one.
4197        let mut part_mv = [(0i32, (0i32, 0i32)); 4];
4198        {
4199            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MvGrid);
4200            for (part, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
4201                let refi = ref_idxs[part];
4202                let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
4203                let [a, b, c] = self.mv_neighbors_block(pbx, pby, (rw / 4) as isize);
4204                let pmv = predict_partition_mv(mode, part, a, b, c, refi);
4205                let mvd_x = read_mvd(r)?;
4206                let mvd_y = read_mvd(r)?;
4207                let mv = (pmv.0 + mvd_x, pmv.1 + mvd_y);
4208                part_mv[part] = (refi, mv);
4209                for by in ry / 4..ry / 4 + rh / 4 {
4210                    for bx in rx / 4..rx / 4 + rw / 4 {
4211                        let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
4212                        if let (Some(m), Some(it), Some(rf), Some(cd)) = (
4213                            self.mv_y.get_mut(idx),
4214                            self.inter_y.get_mut(idx),
4215                            self.ref_idx_y.get_mut(idx),
4216                            self.coded_y.get_mut(idx),
4217                        ) {
4218                            (*m, *it, *rf, *cd) = (mv, true, refi, true);
4219                        }
4220                    }
4221                }
4222            }
4223        }
4224
4225        // Phase 2: motion-compensate each partition from its reference.
4226        let mut pred_y = [0u8; 256];
4227        let mut c_pred = [[0u8; 64]; 2];
4228        // D8-CAVLC: the double-stage ablation, extended to the CAVLC loop. The
4229        // CABAC measurement could not run here at all (`edc_active` is false on
4230        // this path, so nothing replays through `edc_flush` and `doubled` read
4231        // 0) — yet CAVLC is exactly the population P3 item 5 targets, and its
4232        // cheaper parse should make the PIXEL share larger. Doubling the whole
4233        // partition loop is idempotent: pass 2 overwrites `pred_y` with fresh MC
4234        // BEFORE `weight_partition` runs, so weighting cannot apply twice.
4235        // This doubles MC only (not the residual add inside `inter_finish`), so
4236        // it is a LOWER BOUND on the CAVLC pixel share.
4237        // D14 (CAVLC E-seam): when the seam is live the WORKER motion-compensates
4238        // from the committed MV grids, so skip MC here rather than computing a
4239        // prediction that would be discarded.
4240        let defer = self.edc_tx.is_some() || self.edc_active;
4241        let mc_passes = if defer { 0 } else if double_recon() { 2 } else { 1 };
4242        for _pass in 0..mc_passes {
4243        if _pass > 0 {
4244            edcstat::bump(&edcstat::DOUBLED, 1);
4245        }
4246        for (part, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
4247            let (refi, mv) = part_mv[part & 3];
4248            let Some(reference) = self.refs.get(refi as usize) else { continue };
4249            let mut tmp = [0u8; 256];
4250            mc_luma_padded(&*reference.luma_guard(reference.ch), reference.lstride(), crate::LPAD, self.cw, ch, mb_x * 16 + rx, mb_y * 16 + ry, rw, rh, mv.0, mv.1, &mut tmp);
4251            {
4252                let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
4253                restride(&mut pred_y, 16, rx, ry, &tmp, rw, rh);
4254            }
4255            let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
4256            for cc in 0..2 {
4257                let rc = if cc == 0 { &*reference.chroma_guard(0, reference.ch) } else { &*reference.chroma_guard(1, reference.ch) };
4258                let mut tc = [0u8; 64];
4259                mc_chroma_padded(rc, reference.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv.0, mv.1, &mut tc);
4260                {
4261                    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
4262                    restride(&mut c_pred[cc], 8, crx, cry, &tc, crw, crh);
4263                }
4264            }
4265            self.weight_partition(&mut pred_y, &mut c_pred, 0, refi as usize, rx, ry, rw, rh);
4266        }
4267        }
4268
4269        // 16×16/16×8/8×16 partitions are all ≥ 8×8, so the 8×8 transform is allowed.
4270        self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, true, defer)
4271    }
4272
4273    /// Shared inter tail: parse `coded_block_pattern` + `mb_qp_delta`, decode the
4274    /// luma/chroma residual, and add it to the already-built motion-compensated
4275    /// prediction. Used by both the 16×16/16×8/8×16 path and `P_8x8`.
4276    fn inter_finish(
4277        &mut self,
4278        r: &mut BitReader,
4279        mb_x: usize,
4280        mb_y: usize,
4281        pred_y: &[u8; 256],
4282        c_pred: &[[u8; 64]; 2],
4283        allow_8x8: bool,
4284        // D14: emit a worker job instead of reconstructing. Only the CAVLC
4285        // 16x16/16x8/8x16 path sets this; B and P_8x8 stay inline.
4286        defer: bool,
4287    ) -> Result<(), MbError> {
4288        let vt = vlc_tables();
4289        let w4 = self.mb_w * 4;
4290        let cbp = {
4291            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
4292            read_cbp_inter(r)?
4293        };
4294        let cbp_luma = cbp & 15;
4295        let cbp_chroma = cbp >> 4;
4296        // transform_size_8x8_flag follows cbp (before mb_qp_delta) when luma has
4297        // coefficients, the 8×8 transform is enabled, and every partition ≥ 8×8.
4298        let t8x8 = cbp_luma > 0 && self.transform_8x8_mode && allow_8x8 && r.read_bit()?;
4299        if t8x8 {
4300            if let Some(f) = self.mb_t8x8.get_mut(mb_y * self.mb_w + mb_x) {
4301                *f = true;
4302            }
4303            self.any_t8 = true;
4304        }
4305        if cbp != 0 {
4306            self.step_qp(r.read_se()?)?;
4307        }
4308        let (qp, _qpc) = (self.cur_qp, self.chroma_qp_for(self.cur_qp));
4309
4310        // ---- luma residual ----
4311        self.nnz_cache_load(mb_x, mb_y);
4312        let mut luma_scan = [[0i32; 16]; 16];
4313        let mut nnzs = [0u8; 24];
4314        let mut luma8: Option<[[i32; 64]; 4]> = None; // allocated only under t8x8
4315        // BUILD THE MACROBLOCK'S nnz RASTER ON THE STACK, COPY IT ROW-WISE ONCE.
4316        // Both arms below scattered sixteen individually bounds-checked stores
4317        // into the frame grid while the entropy parse ran. Nothing reads
4318        // `nnz_y` for THIS macroblock before the function returns - `nc_pred`
4319        // predicts from the separate `nnz_cache` - so the writes can be
4320        // deferred. The zero arm then costs NOTHING: the raster already holds
4321        // zeros.
4322        let mut nnz_raster = [0u8; 16];
4323        if t8x8 {
4324            for b8 in 0..4 {
4325                let (b8x, b8y) = (b8 % 2, b8 / 2);
4326                let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2);
4327                let _ = (bx, by);
4328                if cbp_luma & (1 << b8) != 0 {
4329                    let mut scan8 = [0i32; 64];
4330                    for sub in 0..4 {
4331                        let (sx, sy) = (sub % 2, sub / 2);
4332                        let (cx, cy) = (b8x * 2 + sx, b8y * 2 + sy);
4333                        let nc = self.nc_pred(cx, cy);
4334                        let (blk, total) = decode_residual_block_with(vt, r, 16, nc)?;
4335                        self.nnz_cache_set(cx, cy, total);
4336                        nnz_raster[cy * 4 + cx] = total;
4337                        // The PER-SUB-BLOCK count the next macroblock's nC prediction
4338                        // depends on -- summing these into one slot and letting the
4339                        // recon helper broadcast it back is what broke CAVLC 8x8.
4340                        nnzs[b8 * 4 + sub] = total;
4341                        for k in 0..16 {
4342                            scan8[4 * k + sub] = blk[k];
4343                        }
4344                    }
4345                    // RAW: `add_inter_residual` applies un_scan_8x8 + inv_quant8
4346                    // itself, exactly as it does for the CABAC path.
4347                    luma8.get_or_insert_with(|| [[0i32; 64]; 4])[b8] = scan8;
4348                } else {
4349                    for sub in 0..4 {
4350                        let (sx, sy) = (sub % 2, sub / 2);
4351                        self.nnz_cache_set(b8x * 2 + sx, b8y * 2 + sy, 0);
4352                        // `nnz_raster` is already zero here - no store needed.
4353                    }
4354                }
4355            }
4356        } else {
4357            for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
4358                let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
4359                let total = if cbp_luma & (1 << (blk / 4)) != 0 {
4360                    let nc = self.nc_pred(lbx, lby);
4361                    let (scan16, total) = decode_residual_block_with(vt, r, 16, nc)?;
4362                    luma_scan[blk] = scan16; // RAW scan order, like CABAC
4363                    total
4364                } else {
4365                    0
4366                };
4367                self.nnz_cache_set(lbx, lby, total);
4368                let _ = (bx, by);
4369                nnz_raster[(lby & 3) * 4 + (lbx & 3)] = total;
4370                nnzs[blk] = total;
4371            }
4372        }
4373        // ONE contiguous copy per macroblock row.
4374        for by in 0..4usize {
4375            let a = (mb_y * 4 + by) * w4 + mb_x * 4;
4376            self.nnz_y[a..a + 4].copy_from_slice(&nnz_raster[by * 4..by * 4 + 4]);
4377        }
4378
4379        // ---- chroma residual ----
4380        let mut c_recon_dc = [[0i32; 4]; 2];
4381        if cbp_chroma != 0 {
4382            for slot in c_recon_dc.iter_mut() {
4383                let (dc, _) = decode_residual_block_with(vt, r, 4, -1)?;
4384                *slot = [dc[0], dc[1], dc[2], dc[3]]; // RAW; dequantised in the helper
4385            }
4386        }
4387        let mut c_q = [[[0i32; 16]; 4]; 2];
4388        if cbp_chroma == 2 {
4389            self.chroma_cache_load(mb_x, mb_y);
4390            let w2 = self.mb_w * 2;
4391            let mut cnnz = [[0u8; 4]; 2];
4392            for c in 0..2 {
4393                for &(bx, by) in &CHROMA_4X4_SCAN_XY {
4394                    let nc = self.chroma_nc_pred(c, bx, by);
4395                    let (ac, total) = decode_residual_block_with(vt, r, 15, nc)?;
4396                    self.chroma_nnz_cache_set(c, bx, by, total);
4397                    cnnz[c][by * 2 + bx] = total;
4398                    c_q[c][by * 2 + bx] = ac; // RAW scan order
4399                    nnzs[(16 + c * 4 + by * 2 + bx).min(23)] = total;
4400                }
4401                for by in 0..2usize {
4402                    let a = (mb_y * 2 + by) * w2 + mb_x * 2;
4403                    self.nnz_c[c][a..a + 2].copy_from_slice(&cnnz[c][by * 2..by * 2 + 2]);
4404                }
4405            }
4406        }
4407
4408        // ---- reconstruction ----
4409        //
4410        // D13: this used to be a 109-line hand-rolled copy of the residual add.
4411        // It now calls the SAME `add_inter_residual` the CABAC path uses, which
4412        // is what makes the CAVLC E-seam possible at all: the two paths had
4413        // different residual representations (CAVLC pre-applied un_scan and
4414        // inv_quant at PARSE time; CABAC carries raw scan-order coefficients and
4415        // dequantises inside the helper), so no job could be shared. Carrying the
4416        // raw forms — which CAVLC already had in hand — converges them, deletes
4417        // a duplicate implementation, and lets a deferred job reuse the existing
4418        // worker recon instead of needing a second copy that could drift.
4419        if defer {
4420            // The residual representation now matches CABAC exactly (the
4421            // convergence commit), so the SAME `PInterJob` and the SAME worker
4422            // `recon_p_inter` serve both entropy coders — no second recon
4423            // implementation exists that could drift.
4424            let (mut gmv, mut gref) = ([(0i32, 0i32); 16], [0u8; 16]);
4425            let w4r = self.mb_w * 4;
4426            // ROW SLICES over BOTH grids. This was the single densest panic site
4427            // left in the decoder crate: sixteen iterations reading two PARALLEL
4428            // frame grids at the same index, and `mv_y`'s bound proved nothing
4429            // about `ref_idx_y`, so all thirty-two loads carried their own check.
4430            // Each macroblock row is four contiguous cells in both.
4431            for by in 0..4usize {
4432                let base = (mb_y * 4 + by) * w4r + mb_x * 4;
4433                let mvr = &self.mv_y[base..][..4];
4434                let rfr = &self.ref_idx_y[base..][..4];
4435                gmv[by * 4..][..4].copy_from_slice(mvr);
4436                for bx in 0..4usize {
4437                    gref[by * 4 + bx] = rfr[bx].clamp(0, 15) as u8;
4438                }
4439            }
4440            // D9 applies here too: `cbp == 0` means all 2,592 coefficient bytes
4441            // of the 2,784-byte job are ZERO, so ship the 176-byte motion-only
4442            // form. Discovered on the CABAC path; it transfers for free because
4443            // the CAVLC arm now emits the SAME job type.
4444            let ej = if cbp == 0 && nores_on() {
4445                edcstat::bump(&edcstat::J_NORES_SENT, 1);
4446                EdcJob::InterNoRes(Box::new(PInterNoResJob {
4447                    mbx: mb_x, mby: mb_y, t8: t8x8, gmv, gref,
4448                }))
4449            } else {
4450                EdcJob::Inter(Box::new(PInterJob {
4451                    mbx: mb_x, mby: mb_y, qp,
4452                    cbp_chroma, gmv, gref,
4453                    luma_scan: Some(luma_scan), luma8, cdc: c_recon_dc, cac: Some(c_q), nnzs,
4454                }))
4455            };
4456            if self.edc_tx.is_some() {
4457                self.edc_giveback();
4458                self.edc_send_job(ej);
4459            } else {
4460                self.edc_jobs.push(ej);
4461            }
4462        } else {
4463            self.add_inter_residual(
4464                mb_x, mb_y, pred_y, c_pred, Some(&luma_scan),
4465                luma8.as_ref(),
4466                &c_recon_dc, Some(&c_q), cbp_chroma, &nnzs,
4467            );
4468        }
4469
4470        // MV grid + coded flags were set per partition; mark modes as DC.
4471        // Four contiguous cells per row - `fill`, not sixteen indexed stores.
4472        for lby in 0..4usize {
4473            let a = (mb_y * 4 + lby) * w4 + mb_x * 4;
4474            self.modes_y[a..a + 4].fill(2);
4475        }
4476        Ok(())
4477    }
4478
4479    // ---------------------------------------------------------------------
4480    // B-slice macroblock decoding
4481    // ---------------------------------------------------------------------
4482
4483    /// Per-list (`list` 0 or 1) MV-prediction neighbors for the block region at
4484    /// `(pbx, pby)` of width `pwb` blocks — the L0/L1 analogue of
4485    /// `mv_neighbors_block`.
4486    fn mv_neighbors_list(&self, pbx: isize, pby: isize, pwb: isize, list: usize) -> [MvNeighbor; 3] {
4487        self.mv_neighbors_block_grid(pbx, pby, pwb, list)
4488    }
4489
4490    /// Spatial-direct A/B/C at the MB origin — same for every 8×8 in the MB
4491    /// (spec §8.4.1.2.2). B_8x8 callers hoist this once; 16×16 skip/direct walk
4492    /// once inside `decode_b_direct`.
4493    #[inline]
4494    fn b_direct_nbrs(&self, mb_x: usize, mb_y: usize) -> ([MvNeighbor; 3], [MvNeighbor; 3]) {
4495        self.mv_neighbors_both((mb_x * 4) as isize, (mb_y * 4) as isize, 4)
4496    }
4497
4498    /// FUSED dual-list gather at arbitrary partition geometry: A/B/C positions
4499    /// and their availability (bounds + coded + slice) are list-independent —
4500    /// compute once, load both lists' grids from the same resolved index.
4501    fn mv_neighbors_both(&self, pbx: isize, pby: isize, pwb: isize) -> ([MvNeighbor; 3], [MvNeighbor; 3]) {
4502        let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
4503        let get2 = |bx: isize, by: isize| -> (MvNeighbor, MvNeighbor) {
4504            if bx < 0
4505                || by < 0
4506                || bx >= w4
4507                || by >= h4
4508                || !self.coded_y.get((by * w4 + bx) as usize).copied().unwrap_or(false)
4509                || !self.nbr_in_slice(bx as usize / 4, by as usize / 4)
4510            {
4511                (MvNeighbor::NONE, MvNeighbor::NONE)
4512            } else {
4513                // FOUR PARALLEL GRIDS at one index. The `coded_y` guard above
4514                // bounds none of them — they are separate Vecs — so each carried
4515                // its own panic path. `MvNeighbor::NONE` is the established
4516                // "no such neighbour" value, so the fallible form degrades into
4517                // the branch directly above it.
4518                let idx = (by * w4 + bx) as usize;
4519                match (self.mv_y.get(idx), self.ref_idx_y.get(idx), self.mv1.get(idx), self.ref_idx1.get(idx)) {
4520                    (Some(&m0), Some(&r0), Some(&m1), Some(&r1)) => (
4521                        MvNeighbor { available: true, mv: m0, ref_idx: r0 },
4522                        MvNeighbor { available: true, mv: m1, ref_idx: r1 },
4523                    ),
4524                    _ => (MvNeighbor::NONE, MvNeighbor::NONE),
4525                }
4526            }
4527        };
4528        let (a0, a1) = get2(pbx - 1, pby);
4529        let (b0, b1) = get2(pbx, pby - 1);
4530        let (mut c0, mut c1) = get2(pbx + pwb, pby - 1);
4531        if !c0.available {
4532            // The C fallback is position-driven (topright unavailable ⇒
4533            // topleft), so both lists fall back together — same decision the
4534            // two per-list gathers made independently.
4535            let t = get2(pbx - 1, pby - 1);
4536            c0 = t.0;
4537            c1 = t.1;
4538        }
4539        ([a0, b0, c0], [a1, b1, c1])
4540    }
4541
4542    fn mv_neighbors_block_grid(&self, pbx: isize, pby: isize, pwb: isize, list: usize) -> [MvNeighbor; 3] {
4543        let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
4544        let (mvg, refg) = if list == 0 {
4545            (&self.mv_y, &self.ref_idx_y)
4546        } else {
4547            (&self.mv1, &self.ref_idx1)
4548        };
4549        let get = |bx: isize, by: isize| -> MvNeighbor {
4550            if bx < 0
4551                || by < 0
4552                || bx >= w4
4553                || by >= h4
4554                || !self.coded_y.get((by * w4 + bx) as usize).copied().unwrap_or(false)
4555                || !self.nbr_in_slice(bx as usize / 4, by as usize / 4)
4556            {
4557                MvNeighbor::NONE
4558            } else {
4559                let idx = (by * w4 + bx) as usize;
4560                match (mvg.get(idx), refg.get(idx)) {
4561                    (Some(&m), Some(&r)) => MvNeighbor { available: true, mv: m, ref_idx: r },
4562                    _ => MvNeighbor::NONE,
4563                }
4564            }
4565        };
4566        let a = get(pbx - 1, pby);
4567        let b = get(pbx, pby - 1);
4568        let mut c = get(pbx + pwb, pby - 1);
4569        if !c.available {
4570            c = get(pbx - 1, pby - 1);
4571        }
4572        [a, b, c]
4573    }
4574
4575    /// `colZeroFlag` for the 4×4 block at absolute block coords `(bx, by)`: true
4576    /// when `RefPicList1[0]` is a short-term picture whose co-located block uses
4577    /// reference 0 with a near-zero motion vector (spec §8.4.1.2.2).
4578    /// Co-located 4x4 block coords for the current block's `(bx4, by4)` within the
4579    /// macroblock, per spec 8.4.1.2.1. Under `direct_8x8_inference_flag` every 4x4
4580    /// in an 8x8 takes that 8x8's OUTER CORNER (`luma4x4BlkIdx = 5 * mbPartIdx`,
4581    /// i.e. (0,0) (3,0) (0,3) (3,3)); otherwise motion is genuinely per-4x4.
4582    ///
4583    /// 8.4.1.2.1 is SHARED by both direct modes, so spatial and temporal must map
4584    /// identically. They did not: temporal mapped the corner and spatial read the
4585    /// block's own coords, which is invisible while every 4x4 in the co-located 8x8
4586    /// carries the same motion -- true of every stream until sub-8x8 P partitions
4587    /// (x264 `--partitions p4x4`) make them differ. Hence one function.
4588    #[inline]
4589    fn col_block(&self, bx4: usize, by4: usize) -> (usize, usize) {
4590        if self.direct_8x8_inference {
4591            ((bx4 / 2) * 3, (by4 / 2) * 3)
4592        } else {
4593            (bx4, by4)
4594        }
4595    }
4596
4597    fn col_zero(&self, bx: usize, by: usize) -> bool {
4598        // Fast path (1T frozen colocated, the default): the invariant checks
4599        // were hoisted to set_b_context; two grid loads + the threshold test.
4600        if self.col_ok {
4601            let Some(col) = self.refs1.first() else { return false };
4602            let idx = by * self.col_w4 + bx;
4603            // `.get` on EVERY parallel array, not a length test on one of them.
4604            // The `idx >= ref_idx.len()` guard proved nothing about `mv`,
4605            // `ref_idx1` or `mv1` — separate Vecs with independent lengths — so
4606            // each of those reads carried its own panic path. They are built
4607            // together and always agree; the fallible form states that instead of
4608            // asserting it, and keeps the "no panic on malformed input" contract.
4609            let (cref, cmv) = match col.ref_idx.get(idx) {
4610                Some(&r0) if r0 >= 0 => match col.mv.get(idx) {
4611                    Some(&m) => (r0, m),
4612                    None => return false,
4613                },
4614                Some(_) => match (col.ref_idx1.get(idx), col.mv1.get(idx)) {
4615                    (Some(&r1), Some(&m)) if r1 >= 0 => (r1, m),
4616                    _ => return false,
4617                },
4618                None => return false,
4619            };
4620            return cref == 0 && cmv.0.abs() <= 1 && cmv.1.abs() <= 1;
4621        }
4622        let Some(col) = self.refs1.first() else { return false };
4623        if let Some(live) = col.live.as_ref() {
4624            col.wait_motion_ready();
4625            let meta = live.meta.read().unwrap();
4626            if meta.long_term || meta.w4 == 0 {
4627                return false;
4628            }
4629            let idx = by * meta.w4 + bx;
4630            // Same `.get`-per-array shape as the frozen-colocated path above.
4631            let (cref, cmv) = match meta.ref_idx.get(idx) {
4632                Some(&r0) if r0 >= 0 => match meta.mv.get(idx) {
4633                    Some(&m) => (r0, m),
4634                    None => return false,
4635                },
4636                Some(_) => match (meta.ref_idx1.get(idx), meta.mv1.get(idx)) {
4637                    (Some(&r1), Some(&m)) if r1 >= 0 => (r1, m),
4638                    _ => return false,
4639                },
4640                None => return false,
4641            };
4642            return cref == 0 && cmv.0.abs() <= 1 && cmv.1.abs() <= 1;
4643        }
4644        if col.long_term || col.w4 == 0 {
4645            return false;
4646        }
4647        let idx = by * col.w4 + bx;
4648        if idx >= col.ref_idx.len() {
4649            return false;
4650        }
4651        let (cref, cmv) = match col.ref_idx.get(idx) {
4652            Some(&r0) if r0 >= 0 => match col.mv.get(idx) {
4653                Some(&m) => (r0, m),
4654                None => return false,
4655            },
4656            Some(_) => match (col.ref_idx1.get(idx), col.mv1.get(idx)) {
4657                (Some(&r1), Some(&m)) if r1 >= 0 => (r1, m),
4658                _ => return false,
4659            },
4660            None => return false,
4661        };
4662        cref == 0 && cmv.0.abs() <= 1 && cmv.1.abs() <= 1
4663    }
4664
4665    /// Implicit bi-prediction weights `(w0, w1)` from POC distances (spec
4666    /// §8.4.2.3.2), or `None` for the plain average (idc≠2, uni-pred, or the
4667    /// equidistant / out-of-range fall-back to 32:32 which equals the average).
4668    fn implicit_weights(&self, refi0: i32, refi1: i32) -> Option<(i32, i32)> {
4669        if self.weighted_bipred_idc != 2 || refi0 < 0 || refi1 < 0 {
4670            return None;
4671        }
4672        let (Some(r0), Some(r1)) = (self.refs.get(refi0 as usize), self.refs1.get(refi1 as usize))
4673        else {
4674            return None;
4675        };
4676        let td = (r1.pic_poc() - r0.pic_poc()).clamp(-128, 127);
4677        let tb = (self.cur_poc - r0.pic_poc()).clamp(-128, 127);
4678        if td == 0 || r0.long_term || r1.long_term {
4679            return None; // 32:32 → identical to the average
4680        }
4681        let tx = tx_for_td(td);
4682        let dsf = ((tb * tx + 32) >> 6).clamp(-1024, 1023);
4683        let w1 = dsf >> 2;
4684        if !(-64..=128).contains(&w1) {
4685            return None; // out of range → 32:32 average
4686        }
4687        Some((64 - w1, w1))
4688    }
4689
4690    /// Motion-compensates a region with the given per-list refs/MVs. Bi-prediction
4691    /// is the simple `(a+b+1)>>1` average, or POC-weighted when implicit weighting
4692    /// (idc 2) is active. Writes into `pred_y`/`c_pred`.
4693    #[allow(clippy::too_many_arguments)]
4694    fn b_mc(
4695        &self,
4696        mb_x: usize,
4697        mb_y: usize,
4698        px: usize,
4699        py: usize,
4700        rw: usize,
4701        rh: usize,
4702        refi0: i32,
4703        mv0: (i32, i32),
4704        refi1: i32,
4705        mv1: (i32, i32),
4706        pred_y: &mut [u8; 256],
4707        c_pred: &mut [[u8; 64]; 2],
4708    ) {
4709        let _gb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBMc);
4710        // Malformed-stream armor, mirroring the P path: now that B slices actually
4711        // PARSE ref_idx (they used to be hardcoded to 0), a mutated stream can hand
4712        // us an index past the end of either list. Clamp rather than panic — the
4713        // crate is `forbid(unsafe_code)` and fuzz-gated to never panic, and a
4714        // wrong picture on garbage input carries no conformance duty.
4715        let refi0 = if refi0 >= 0 { (refi0 as usize).min(self.refs.len().saturating_sub(1)) as i32 } else { -1 };
4716        let refi1 = if refi1 >= 0 { (refi1 as usize).min(self.refs1.len().saturating_sub(1)) as i32 } else { -1 };
4717        if (refi0 >= 0 && self.refs.is_empty()) || (refi1 >= 0 && self.refs1.is_empty()) {
4718            return;
4719        }
4720        let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
4721        let weights = {
4722            let _gw = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBWeights);
4723            self.implicit_weights(refi0, refi1)
4724        };
4725        // Bi-prediction blend: the weights decision is LOOP-INVARIANT, so every
4726        // blend site below matches on `weights` ONCE and runs a branch-free
4727        // pixel loop — the unweighted `(p+q+1)>>1` average then autovectorizes
4728        // (the per-pixel closure this replaces hid the invariant behind a
4729        // capture, and its chroma form was a &dyn call PER PIXEL).
4730        // FULL-WIDTH regions (px == 0, rw == 16 — every 16×16/16×8 partition and
4731        // most direct regions) occupy contiguous rows of `pred_y`, so MC writes
4732        // the destination DIRECTLY: uni-pred needs no staging at all, bi-pred
4733        // stages only the second list and blends in place. The staging arrays
4734        // (512 B zeroed per call before this) now exist only on the branches
4735        // that read them. Same fusion as the P path's mc_rect (WHYS Part 8).
4736        let full = px == 0 && rw == 16;
4737        if refi0 >= 0 && refi1 >= 0 && mv0.0 % 4 == 0 && mv0.1 % 4 == 0 && mv1.0 % 4 == 0 && mv1.1 % 4 == 0 {
4738            edcstat::bump(&edcstat::BMC_BI_FP, 1);
4739        }
4740        let _gl = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBLuma);
4741        // One scratch borrow for the whole region — both bi-pred passes included.
4742        // The closure yields whether the arm already ran the chroma half (the
4743        // bi-pred full-width arm does, to keep its staging alive) — a plain
4744        // `return` inside would exit the CLOSURE only and chroma would run twice.
4745        let chroma_done = rusty_h264_common::inter::with_mc_scratch(|scr| match (refi0 >= 0, refi1 >= 0, full) {
4746            (true, false, true) => {
4747                let Some(rf) = self.refs.get(refi0 as usize) else { return false };
4748                rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16, mb_y * 16 + py, rw, rh, mv0.0, mv0.1, &mut pred_y[py * 16..py * 16 + rw * rh]);
4749                false
4750            }
4751            (false, true, true) => {
4752                let Some(rf) = self.refs1.get(refi1 as usize) else { return false };
4753                rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16, mb_y * 16 + py, rw, rh, mv1.0, mv1.1, &mut pred_y[py * 16..py * 16 + rw * rh]);
4754                false
4755            }
4756            (true, true, true) => {
4757                let Some(rf) = self.refs.get(refi0 as usize) else { return false };
4758                rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16, mb_y * 16 + py, rw, rh, mv0.0, mv0.1, &mut pred_y[py * 16..py * 16 + rw * rh]);
4759                let mut b = [0u8; 256];
4760                let Some(rf) = self.refs1.get(refi1 as usize) else { return false };
4761                rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16, mb_y * 16 + py, rw, rh, mv1.0, mv1.1, &mut b[..rw * rh]);
4762                drop(_gl);
4763                let _gbl = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
4764                // SLICE-then-zip: proving the bounds ONCE lets rustc emit the whole
4765                // 256-byte average as 8 straight-line vpavgb ops (verified in
4766                // isolation, x86-64-v3); the indexed form kept a per-iteration
4767                // bounds check and a loop. A hand AVX2 kernel is refuted — the
4768                // compiler already emits the ideal instruction.
4769                let dst = &mut pred_y[py * 16..py * 16 + rw * rh];
4770                match weights {
4771                    None => {
4772                        for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
4773                            *d = ((*d as u16 + *s as u16 + 1) >> 1) as u8;
4774                        }
4775                    }
4776                    Some((w0, w1)) => {
4777                        for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
4778                            *d = ((*d as i32 * w0 + *s as i32 * w1 + 32) >> 6).clamp(0, 255) as u8;
4779                        }
4780                    }
4781                }
4782                let _gc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
4783                self.b_mc_chroma(mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch);
4784                true
4785            }
4786            _ => {
4787                // Narrow region — rows are strided in `pred_y`; stage and copy.
4788                let (mut a, mut b) = ([0u8; 256], [0u8; 256]);
4789                if refi0 >= 0 {
4790                    let Some(rf) = self.refs.get(refi0 as usize) else { return false };
4791                    rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16 + px, mb_y * 16 + py, rw, rh, mv0.0, mv0.1, &mut a[..rw * rh]);
4792                }
4793                if refi1 >= 0 {
4794                    let Some(rf) = self.refs1.get(refi1 as usize) else { return false };
4795                    rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16 + px, mb_y * 16 + py, rw, rh, mv1.0, mv1.1, &mut b[..rw * rh]);
4796                }
4797                drop(_gl);
4798                let _gbl = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
4799                match (refi0 >= 0, refi1 >= 0) {
4800                    (true, true) => {
4801                        for dy in 0..rh {
4802                            let (ar, br) = (&a[dy * rw..dy * rw + rw], &b[dy * rw..dy * rw + rw]);
4803                            let base = (py + dy) * 16 + px;
4804                            let dst = &mut pred_y[base..base + rw];
4805                            match weights {
4806                                None => {
4807                                    for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
4808                                        *d = ((*p as u16 + *q as u16 + 1) >> 1) as u8;
4809                                    }
4810                                }
4811                                Some((w0, w1)) => {
4812                                    for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
4813                                        *d = ((*p as i32 * w0 + *q as i32 * w1 + 32) >> 6).clamp(0, 255) as u8;
4814                                    }
4815                                }
4816                            }
4817                        }
4818                    }
4819                    (true, false) => {
4820                        for dy in 0..rh {
4821                            let d = (py + dy) * 16 + px;
4822                            pred_y[d..d + rw].copy_from_slice(&a[dy * rw..dy * rw + rw]);
4823                        }
4824                    }
4825                    _ => {
4826                        for dy in 0..rh {
4827                            let d = (py + dy) * 16 + px;
4828                            pred_y[d..d + rw].copy_from_slice(&b[dy * rw..dy * rw + rw]);
4829                        }
4830                    }
4831                }
4832                false
4833            }
4834        });
4835        if chroma_done {
4836            return;
4837        }
4838        let _gc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
4839        self.b_mc_chroma(mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch);
4840    }
4841
4842    /// Chroma half of `b_mc`, with the same full-width direct-write fusion
4843    /// (crw == 8 rows are contiguous in the 8-wide `c_pred` planes).
4844    #[allow(clippy::too_many_arguments)]
4845    /// Chroma half of `b_mc`. U and V share every piece of MC geometry, so
4846    /// each list is ONE `mc_chroma_padded_pair` call (setup + range check paid
4847    /// once, kernels unchanged) instead of two per-plane calls — the per-plane
4848    /// pixel math is byte-identical to the old per-plane flow.
4849    #[allow(clippy::too_many_arguments)]
4850    fn b_mc_chroma(
4851        &self,
4852        mb_x: usize,
4853        mb_y: usize,
4854        px: usize,
4855        py: usize,
4856        rw: usize,
4857        rh: usize,
4858        refi0: i32,
4859        mv0: (i32, i32),
4860        refi1: i32,
4861        mv1: (i32, i32),
4862        c_pred: &mut [[u8; 64]; 2],
4863        weights: Option<(i32, i32)>,
4864        cch: usize,
4865    ) {
4866        use rusty_h264_common::inter::mc_chroma_padded_pair;
4867        let (crx, cry, crw, crh) = (px / 2, py / 2, rw / 2, rh / 2);
4868        let full = crx == 0 && crw == 8;
4869        let n = crw * crh;
4870        // RESOLVE BOTH SLOTS ONCE, then let the match patterns bind them. Every
4871        // arm below re-indexed `refs`/`refs1` by a raw `refi` — six checked Vec
4872        // indexes across the four arms — although `a0`/`a1` already meant
4873        // exactly "this slot is present". Carrying `Option<&RefPic>` instead of
4874        // a bool makes "present" and "in range" the same fact.
4875        let rf0 = (refi0 >= 0).then(|| self.refs.get(refi0 as usize)).flatten();
4876        let rf1 = (refi1 >= 0).then(|| self.refs1.get(refi1 as usize)).flatten();
4877        let (a0, a1) = (rf0.is_some(), rf1.is_some());
4878        let [cu, cv] = c_pred;
4879        match (a0, a1, full) {
4880            (true, false, true) | (false, true, true) => {
4881                let (Some(rf), mv) = (if a0 { rf0 } else { rf1 }, if a0 { mv0 } else { mv1 }) else {
4882                    return;
4883                };
4884                let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
4885                mc_chroma_padded_pair(&gu, &gv, rf.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8, mb_y * 8 + cry, crw, crh, mv.0, mv.1, &mut cu[cry * 8..cry * 8 + n], &mut cv[cry * 8..cry * 8 + n]);
4886            }
4887            (true, true, true) => {
4888                let (Some(rfa), Some(rfb)) = (rf0, rf1) else { return };
4889                {
4890                    let rf = rfa;
4891                    let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
4892                    mc_chroma_padded_pair(&gu, &gv, rf.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8, mb_y * 8 + cry, crw, crh, mv0.0, mv0.1, &mut cu[cry * 8..cry * 8 + n], &mut cv[cry * 8..cry * 8 + n]);
4893                }
4894                let (mut bu, mut bv) = ([0u8; 64], [0u8; 64]);
4895                {
4896                    let rf = rfb;
4897                    let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
4898                    mc_chroma_padded_pair(&gu, &gv, rf.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8, mb_y * 8 + cry, crw, crh, mv1.0, mv1.1, &mut bu[..n], &mut bv[..n]);
4899                }
4900                for (dst, stage) in [(&mut *cu, &bu), (&mut *cv, &bv)] {
4901                    let d = &mut dst[cry * 8..cry * 8 + n];
4902                    match weights {
4903                        None => {
4904                            for (o, q) in d.iter_mut().zip(&stage[..n]) {
4905                                *o = ((*o as u16 + *q as u16 + 1) >> 1) as u8;
4906                            }
4907                        }
4908                        Some((w0, w1)) => {
4909                            for (o, q) in d.iter_mut().zip(&stage[..n]) {
4910                                *o = ((*o as i32 * w0 + *q as i32 * w1 + 32) >> 6).clamp(0, 255) as u8;
4911                            }
4912                        }
4913                    }
4914                }
4915            }
4916            _ => {
4917                // Narrow region — rows are strided in the 8-wide pred planes;
4918                // stage per list (paired), then copy/blend strided per plane.
4919                let (mut au, mut av, mut bu, mut bv) = ([0u8; 64], [0u8; 64], [0u8; 64], [0u8; 64]);
4920                if let Some(rf) = rf0 {
4921                    let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
4922                    mc_chroma_padded_pair(&gu, &gv, rf.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv0.0, mv0.1, &mut au[..n], &mut av[..n]);
4923                }
4924                if let Some(rf) = rf1 {
4925                    let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
4926                    mc_chroma_padded_pair(&gu, &gv, rf.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv1.0, mv1.1, &mut bu[..n], &mut bv[..n]);
4927                }
4928                for (dst, sa, sb) in [(&mut *cu, &au, &bu), (&mut *cv, &av, &bv)] {
4929                    for dy in 0..crh {
4930                        let base = (cry + dy) * 8 + crx;
4931                        let d = &mut dst[base..base + crw];
4932                        match (a0, a1) {
4933                            (true, true) => {
4934                                let (pr, qr) = (&sa[dy * crw..dy * crw + crw], &sb[dy * crw..dy * crw + crw]);
4935                                match weights {
4936                                    None => {
4937                                        for ((o, pp), q) in d.iter_mut().zip(pr).zip(qr) {
4938                                            *o = ((*pp as u16 + *q as u16 + 1) >> 1) as u8;
4939                                        }
4940                                    }
4941                                    Some((w0, w1)) => {
4942                                        for ((o, pp), q) in d.iter_mut().zip(pr).zip(qr) {
4943                                            *o = ((*pp as i32 * w0 + *q as i32 * w1 + 32) >> 6).clamp(0, 255) as u8;
4944                                        }
4945                                    }
4946                                }
4947                            }
4948                            (true, false) => d.copy_from_slice(&sa[dy * crw..dy * crw + crw]),
4949                            _ => d.copy_from_slice(&sb[dy * crw..dy * crw + crw]),
4950                        }
4951                    }
4952                }
4953            }
4954        }
4955    }
4956
4957    /// Commits a region's per-list motion to the 4×4 grids (and marks coded).
4958    #[allow(clippy::too_many_arguments)]
4959    fn b_set_motion(&mut self, mb_x: usize, mb_y: usize, px: usize, py: usize, rw: usize, rh: usize, refi0: i32, mv0: (i32, i32), refi1: i32, mv1: (i32, i32)) {
4960        let _gs = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBSet);
4961        let w4 = self.mb_w * 4;
4962        let mv0w = if refi0 >= 0 { mv0 } else { (0, 0) };
4963        let mv1w = if refi1 >= 0 { mv1 } else { (0, 0) };
4964        let by0 = mb_y * 4 + py / 4;
4965        let bx0 = mb_x * 4 + px / 4;
4966        let (bw, bh) = (rw / 4, rh / 4);
4967        // Contiguous per-row slices: fill instead of per-4x4 stores (same values).
4968        // SEVEN parallel grids, seven separate range checks — they are disjoint
4969        // fields of `self`, so one `get_mut` apiece resolves them all together
4970        // and a short row simply writes nothing rather than panicking.
4971        for by in by0..by0 + bh {
4972            let row = by * w4 + bx0;
4973            let end = row + bw;
4974            let (Some(r0), Some(m0), Some(r1), Some(m1)) = (
4975                self.ref_idx_y.get_mut(row..end),
4976                self.mv_y.get_mut(row..end),
4977                self.ref_idx1.get_mut(row..end),
4978                self.mv1.get_mut(row..end),
4979            ) else {
4980                continue;
4981            };
4982            r0.fill(refi0);
4983            m0.fill(mv0w);
4984            r1.fill(refi1);
4985            m1.fill(mv1w);
4986            let (Some(it), Some(cd), Some(md)) = (
4987                self.inter_y.get_mut(row..end),
4988                self.coded_y.get_mut(row..end),
4989                self.modes_y.get_mut(row..end),
4990            ) else {
4991                continue;
4992            };
4993            it.fill(true);
4994            cd.fill(true);
4995            md.fill(2);
4996        }
4997    }
4998
4999    /// Spatial direct prediction for a region (whole MB or an 8×8): derives the
5000    /// per-list reference indices and base MVs, then motion-compensates each 4×4
5001    /// sub-block (applying `colZeroFlag`) and commits the motion (spec §8.4.1.2.2).
5002    #[allow(clippy::too_many_arguments)]
5003    /// Splits a `w`×`h` block region (4×4-block units) into the fewest rectangles
5004    /// whose contents are `uniform`, preferring partition-shaped cuts (whole →
5005    /// horizontal halves → vertical halves → quadrants). Emits at most w·h rects
5006    /// (the all-different worst case degenerates to per-block, i.e. the old loop).
5007    fn coalesce_region<U: Fn(usize, usize, usize, usize) -> bool, E: FnMut(usize, usize, usize, usize)>(
5008        x: usize,
5009        y: usize,
5010        w: usize,
5011        h: usize,
5012        uniform: &U,
5013        emit: &mut E,
5014    ) {
5015        if uniform(x, y, w, h) {
5016            emit(x, y, w, h);
5017            return;
5018        }
5019        if h > 1 && uniform(x, y, w, h / 2) && uniform(x, y + h / 2, w, h / 2) {
5020            emit(x, y, w, h / 2);
5021            emit(x, y + h / 2, w, h / 2);
5022            return;
5023        }
5024        if w > 1 && uniform(x, y, w / 2, h) && uniform(x + w / 2, y, w / 2, h) {
5025            emit(x, y, w / 2, h);
5026            emit(x + w / 2, y, w / 2, h);
5027            return;
5028        }
5029        match (w > 1, h > 1) {
5030            (true, true) => {
5031                for q in 0..4usize {
5032                    Self::coalesce_region(x + (q % 2) * (w / 2), y + (q / 2) * (h / 2), w / 2, h / 2, uniform, emit);
5033                }
5034            }
5035            (true, false) => {
5036                Self::coalesce_region(x, y, w / 2, h, uniform, emit);
5037                Self::coalesce_region(x + w / 2, y, w / 2, h, uniform, emit);
5038            }
5039            (false, true) => {
5040                Self::coalesce_region(x, y, w, h / 2, uniform, emit);
5041                Self::coalesce_region(x, y + h / 2, w, h / 2, uniform, emit);
5042            }
5043            (false, false) => emit(x, y, 1, 1),
5044        }
5045    }
5046
5047    fn decode_b_direct(&mut self, mb_x: usize, mb_y: usize, px: usize, py: usize, rw: usize, rh: usize, pred_y: &mut [u8; 256], c_pred: &mut [[u8; 64]; 2]) {
5048        if !self.direct_spatial {
5049            return self.decode_b_direct_temporal(mb_x, mb_y, px, py, rw, rh, pred_y, c_pred);
5050        }
5051        let (n0, n1) = self.b_direct_nbrs(mb_x, mb_y);
5052        self.decode_b_direct_n(mb_x, mb_y, px, py, rw, rh, pred_y, c_pred, n0, n1);
5053    }
5054
5055    /// Spec §8.4.1.2.2: the spatial-direct reference indices (min positive over
5056    /// the three neighbors, per list) and the predicted MVs. `direct_zero` is
5057    /// the both-lists-unavailable case, which forces ref 0 / mv (0,0). Shared
5058    /// by `decode_b_direct_n` and the B_Skip zero-bi fast path — ONE derivation,
5059    /// no drift.
5060    #[inline]
5061    fn b_direct_refs_mvs(n0: &[MvNeighbor; 3], n1: &[MvNeighbor; 3]) -> (i32, i32, (i32, i32), (i32, i32), bool) {
5062        let min_pos = |a: i32, b: i32| if a < 0 { b } else if b < 0 { a } else { a.min(b) };
5063        let rid = |n: &[MvNeighbor; 3]| min_pos(min_pos(n[0].ref_idx, n[1].ref_idx), n[2].ref_idx);
5064        let (mut refi0, mut refi1) = (rid(n0), rid(n1));
5065        let direct_zero = refi0 < 0 && refi1 < 0;
5066        if direct_zero {
5067            refi0 = 0;
5068            refi1 = 0;
5069        }
5070        let mv0 = if refi0 >= 0 && !direct_zero { predict_mv(n0[0], n0[1], n0[2], refi0) } else { (0, 0) };
5071        let mv1 = if refi1 >= 0 && !direct_zero { predict_mv(n1[0], n1[1], n1[2], refi1) } else { (0, 0) };
5072        (refi0, refi1, mv0, mv1, direct_zero)
5073    }
5074
5075    fn decode_b_direct_n(
5076        &mut self,
5077        mb_x: usize,
5078        mb_y: usize,
5079        px: usize,
5080        py: usize,
5081        rw: usize,
5082        rh: usize,
5083        pred_y: &mut [u8; 256],
5084        c_pred: &mut [[u8; 64]; 2],
5085        n0: [MvNeighbor; 3],
5086        n1: [MvNeighbor; 3],
5087    ) {
5088        let _gb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBDirect);
5089        // H-48: DERIVATION-ONLY scope, dropped before the MC loop below. DecBDirect
5090        // wraps this function whole and therefore INCLUDES the `b_mc` calls it makes,
5091        // so its 1460 ns/call was never "MV derivation is slow" — that read was wrong.
5092        // This guard is what separates the two.
5093        let gd = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBDeriv);
5094        let derived = Self::b_direct_refs_mvs(&n0, &n1);
5095        drop(gd);
5096        self.b_direct_region(mb_x, mb_y, px, py, rw, rh, pred_y, c_pred, derived);
5097    }
5098
5099    /// The post-derivation half of spatial direct: czg probing (gated), region
5100    /// coalescing, MC + motion commit. Split out so B_8x8 direct subs derive
5101    /// ONCE per MB (the A/B/C neighbours and the rid/median result are
5102    /// MB-level; only czg varies per 8x8).
5103    #[allow(clippy::too_many_arguments)]
5104    fn b_direct_region(
5105        &mut self,
5106        mb_x: usize,
5107        mb_y: usize,
5108        px: usize,
5109        py: usize,
5110        rw: usize,
5111        rh: usize,
5112        pred_y: &mut [u8; 256],
5113        c_pred: &mut [[u8; 64]; 2],
5114        derived: (i32, i32, (i32, i32), (i32, i32), bool),
5115    ) {
5116        let (refi0, refi1, mv0, mv1, direct_zero) = derived;
5117        // Per 4×4 sub-block: colZeroFlag zeroes the ref-0 motion vector. cz is the
5118        // ONLY per-block variable (two possible (m0,m1) values for the region), and
5119        // the MC filters + bi-blend are per-output-pixel — so sub-blocks with equal
5120        // cz coalesce into one wider `b_mc`, BIT-IDENTICAL. A 16×16 direct MB paid
5121        // 16 bi-pred b_mc calls (~96 MC kernel entries) before this; typically 1 now.
5122        let (bx0, by0, bw, bh) = (px / 4, py / 4, rw / 4, rh / 4);
5123        // MASKED at every use below. The loop bounds are the region's `bw`/`bh` in
5124    // 4x4 units, which are always <= 4 for a macroblock but are RUNTIME values,
5125    // so none of the eight indexes into this fixed 4x4 grid was provable.
5126    let mut czg = [[false; 4]; 4]; // region-local, [dy][dx]
5127        // colZeroFlag can only change a list whose ref is 0 AND whose predicted
5128        // MV is nonzero (it zeroes MVs; zeroing (0,0) is a no-op). When neither
5129        // list qualifies, skip the probing entirely: czg stays false, the
5130        // region is uniform, and the m values are identical — the only change
5131        // is FEWER b_mc calls where the old czg would have split rects with
5132        // equal values (tiles of the same math, byte-identical).
5133        let cz_matters = (refi0 == 0 && mv0 != (0, 0)) || (refi1 == 0 && mv1 != (0, 0));
5134        // Uniformity is KNOWN after the probe fill — when every probe agreed,
5135        // the 16-bool coalesce scan and its recursion are skipped outright.
5136        let mut cz_mixed = false;
5137        if cz_matters && !direct_zero && self.direct_8x8_inference {
5138            // Under direct_8x8_inference every 4×4 in an 8×8 shares one colZeroFlag
5139            // (col_block collapses to the MB-corner). Probe once per 8×8 — same
5140            // czg, fewer wait_motion_ready / meta locks on the live-ref path.
5141            let mut oy = 0usize;
5142            while oy < bh {
5143                let h = (bh - oy).min(2);
5144                let mut ox = 0usize;
5145                while ox < bw {
5146                    let w = (bw - ox).min(2);
5147                    let (colx, coly) = self.col_block(bx0 + ox, by0 + oy);
5148                    let cz = self.col_zero(mb_x * 4 + colx, mb_y * 4 + coly);
5149                    cz_mixed |= cz != czg[0][0] && !(ox == 0 && oy == 0);
5150                    for dy in oy..oy + h {
5151                        for dx in ox..ox + w {
5152                            czg[dy & 3][dx & 3] = cz;
5153                        }
5154                    }
5155                    ox += w;
5156                }
5157                oy += h;
5158            }
5159        } else if cz_matters && !direct_zero {
5160            for dy in 0..bh {
5161                for dx in 0..bw {
5162                    let (colx, coly) = self.col_block(bx0 + dx, by0 + dy);
5163                    let cz = self.col_zero(mb_x * 4 + colx, mb_y * 4 + coly);
5164                    cz_mixed |= cz != czg[0][0] && !(dx == 0 && dy == 0);
5165                    czg[dy & 3][dx & 3] = cz;
5166                }
5167            }
5168        }
5169        let mut rects: [(usize, usize, usize, usize); 16] = [(0, 0, 0, 0); 16];
5170        let mut n = 0usize;
5171        if !cz_mixed {
5172            rects[0] = (0, 0, bw, bh);
5173            n = 1;
5174        } else {
5175            let uniform = |x: usize, y: usize, w: usize, h: usize| -> bool {
5176                let t = czg[y & 3][x & 3];
5177                (y..y + h).all(|dy| (x..x + w).all(|dx| czg[dy & 3][dx & 3] == t))
5178            };
5179            Self::coalesce_region(0, 0, bw, bh, &uniform, &mut |x, y, w, h| {
5180                rects[n & 15] = (x, y, w, h);
5181                n += 1;
5182            });
5183        }
5184        let recording = self.edc_regions.is_some();
5185        if rw == 16 && rh == 16 {
5186            edcstat::bump(&edcstat::BSK_FULLMB, 1);
5187            if n == 1 {
5188                edcstat::bump(&edcstat::BSK_1RECT, 1);
5189                let cz = czg[0][0];
5190                let m0 = if refi0 == 0 && cz { (0, 0) } else { mv0 };
5191                let m1 = if refi1 == 0 && cz { (0, 0) } else { mv1 };
5192                let (a0, a1) = (refi0 >= 0, refi1 >= 0);
5193                if a0 && a1 && m0 == (0, 0) && m1 == (0, 0) {
5194                    edcstat::bump(&edcstat::BSK_ZBI, 1);
5195                } else if (a0 != a1) && (if a0 { m0 } else { m1 }) == (0, 0) {
5196                    edcstat::bump(&edcstat::BSK_ZUNI, 1);
5197                } else if (!a0 || (m0.0 % 4 == 0 && m0.1 % 4 == 0))
5198                    && (!a1 || (m1.0 % 4 == 0 && m1.1 % 4 == 0))
5199                {
5200                    edcstat::bump(&edcstat::BSK_FP, 1);
5201                }
5202                if self.bsk_last == Some((mb_x.wrapping_sub(1), mb_y, refi0, refi1, m0, m1)) {
5203                    edcstat::bump(&edcstat::BSK_RUNCONT, 1);
5204                }
5205                self.bsk_last = Some((mb_x, mb_y, refi0, refi1, m0, m1));
5206            } else {
5207                self.bsk_last = None;
5208            }
5209        }
5210        for &(x, y, w, h) in &rects[..n] {
5211            let cz = czg[y & 3][x & 3];
5212            let m0 = if refi0 == 0 && cz { (0, 0) } else { mv0 };
5213            let m1 = if refi1 == 0 && cz { (0, 0) } else { mv1 };
5214            let (lx, ly, lw, lh) = ((bx0 + x) * 4, (by0 + y) * 4, w * 4, h * 4);
5215            if recording {
5216                self.b_mc_or_record(mb_x, mb_y, lx, ly, lw, lh, refi0, m0, refi1, m1, pred_y, c_pred);
5217            } else {
5218                self.b_mc(mb_x, mb_y, lx, ly, lw, lh, refi0, m0, refi1, m1, pred_y, c_pred);
5219            }
5220            self.b_set_motion(mb_x, mb_y, lx, ly, lw, lh, refi0, m0, refi1, m1);
5221        }
5222    }
5223
5224    /// Temporal direct prediction for a region (spec §8.4.1.2.3): for each 4×4
5225    /// (or per-8×8 corner under `direct_8x8_inference`), take the co-located
5226    /// List-0 motion from `RefPicList1[0]`, map its reference into the current
5227    /// List-0 by POC, and scale the motion vector by the POC distances.
5228    #[allow(clippy::too_many_arguments)]
5229    fn decode_b_direct_temporal(&mut self, mb_x: usize, mb_y: usize, px: usize, py: usize, rw: usize, rh: usize, pred_y: &mut [u8; 256], c_pred: &mut [[u8; 64]; 2]) {
5230        let poc1 = self.refs1.first().map_or(0, |f| f.pic_poc());
5231        let infer = self.direct_8x8_inference;
5232        // Under direct_8x8_inference every 4×4 in an 8×8 takes the same MB-corner
5233        // co-located motion, so motion-compensate the whole 8×8 in one call — this
5234        // hits the width-8 MC asm and pays the per-call tile/blend setup 4× less.
5235        // Without inference, motion is genuinely per-4×4. Bit-identical either way
5236        // (MC of an 8×8 with one MV == four 4×4 MCs with that same MV).
5237        let step = if infer { 8 } else { 4 };
5238        let mut sy = py;
5239        while sy < py + rh {
5240            let mut sx = px;
5241            while sx < px + rw {
5242                // Co-located 4×4 (the 8×8's MB-corner under inference) — shared with
5243                // the spatial path's colZeroFlag, which must map identically.
5244                let (colx, coly) = self.col_block(sx / 4, sy / 4);
5245                let (mvcol, refpoc) = {
5246                    let Some(col) = self.refs1.first() else { return };
5247                    if let Some(live) = col.live.as_ref() {
5248                        col.wait_motion_ready();
5249                        let meta = live.meta.read().unwrap();
5250                        let idx = (mb_y * 4 + coly) * meta.w4 + (mb_x * 4 + colx);
5251                        // `mv` and `ref_poc` are PARALLEL Vecs: the `mv.len()`
5252                        // guard said nothing about `ref_poc`.
5253                        match (meta.mv.get(idx), meta.ref_poc.get(idx)) {
5254                            (Some(&m), Some(&pc)) if meta.w4 != 0 && pc != i32::MIN => (m, pc),
5255                            _ => ((0, 0), i32::MIN),
5256                        }
5257                    } else {
5258                        let idx = (mb_y * 4 + coly) * col.w4 + (mb_x * 4 + colx);
5259                        // intra co-located → zero motion, refIdxL0 = 0
5260                        match (col.mv.get(idx), col.ref_poc.get(idx)) {
5261                            (Some(&m), Some(&pc)) if col.w4 != 0 && pc != i32::MIN => (m, pc),
5262                            _ => ((0, 0), i32::MIN),
5263                        }
5264                    }
5265                };
5266                // MapColToList0: the current-list index of the co-located reference.
5267                let (refi0, mvc) = if refpoc == i32::MIN {
5268                    (0, (0, 0))
5269                } else {
5270                    let r = self
5271                        .refs
5272                        .iter()
5273                        .position(|f| f.pic_poc() == refpoc)
5274                        .unwrap_or(0) as i32;
5275                    (r, mvcol)
5276                };
5277                let Some(r0) = self.refs.get(refi0 as usize) else { return };
5278        let poc0 = r0.pic_poc();
5279                let td = (poc1 - poc0).clamp(-128, 127);
5280                let tb = (self.cur_poc - poc0).clamp(-128, 127);
5281                let (mv0, mv1) = if td == 0 || r0.long_term {
5282                    (mvc, (0, 0))
5283                } else {
5284                    let tx = tx_for_td(td);
5285                    let dsf = ((tb * tx + 32) >> 6).clamp(-1024, 1023);
5286                    let m0 = ((dsf * mvc.0 + 128) >> 8, (dsf * mvc.1 + 128) >> 8);
5287                    (m0, (m0.0 - mvc.0, m0.1 - mvc.1))
5288                };
5289                self.b_mc_or_record(mb_x, mb_y, sx, sy, step, step, refi0, mv0, 0, mv1, pred_y, c_pred);
5290                self.b_set_motion(mb_x, mb_y, sx, sy, step, step, refi0, mv0, 0, mv1);
5291                sx += step;
5292            }
5293            sy += step;
5294        }
5295    }
5296
5297    /// Reads `ref_idx_lX` for a B partition (te(v)/ue(v) by the list's active
5298    /// count), bounds-checked against the available reference count.
5299    fn read_b_ref(&self, r: &mut BitReader, list: usize) -> Result<i32, MbError> {
5300        let (active, avail) = if list == 0 {
5301            (self.num_ref_active, self.refs.len())
5302        } else {
5303            (self.num_ref_active1, self.refs1.len())
5304        };
5305        let v = if active > 1 { read_ref_idx(r, active)? } else { 0 };
5306        if v as usize >= avail {
5307            return Err(MbError::Truncated);
5308        }
5309        Ok(v)
5310    }
5311
5312    /// Reconstructs a `B_Skip` macroblock: spatial-direct prediction, no residual.
5313    /// Defer one fast B_Skip's grid commit + recon into the pending row span.
5314    /// Continuation requires position adjacency AND kind equality.
5315    #[inline]
5316    fn bz_push(&mut self, mb_x: usize, mb_y: usize, kind: BzKind) {
5317        match self.bzspan {
5318            Some((row, x0, ref mut n, k)) if row == mb_y && x0 + *n == mb_x && k == kind => {
5319                *n += 1
5320            }
5321            _ => {
5322                self.bz_flush();
5323                self.bzspan = Some((mb_y, mb_x, 1, kind));
5324            }
5325        }
5326    }
5327
5328    /// Range-fill the pending span's grids. MUST run before anything reads
5329    /// the motion/coded/mode/nnz grids of a deferred MB: decode_b_mb entry,
5330    /// decode_b_skip's b_direct_nbrs, bS derivation (derive_bs_row callers
5331    /// via row_hook + deblock), and slice end (edc_flush covers it).
5332    #[inline(always)]
5333    fn bz_flush(&mut self) {
5334        if self.bzspan.is_some() {
5335            self.bz_flush_slow();
5336        }
5337    }
5338
5339    fn bz_flush_slow(&mut self) {
5340        let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSpanRecon);
5341        let Some((row, x0, n, kind)) = self.bzspan.take() else { return };
5342        edcstat::bump(&edcstat::BZ_SPANS, 1);
5343        edcstat::bump(&edcstat::BZ_SPAN_MBS, n as u64);
5344        // Grid values per kind (mirrors b_set_motion's inactive-list zeroing).
5345        let (g_r0, g_r1, g_m0, g_m1): (i32, i32, (i32, i32), (i32, i32)) = match kind {
5346            BzKind::ZeroBi => (0, 0, (0, 0), (0, 0)),
5347            BzKind::ZeroUni(0, ri) => (ri as i32, -1, (0, 0), (0, 0)),
5348            BzKind::ZeroUni(_, ri) => (-1, ri as i32, (0, 0), (0, 0)),
5349            BzKind::Fp { r0, r1, m0, m1 } => {
5350                let mm0 = if r0 >= 0 { (m0.0 as i32, m0.1 as i32) } else { (0, 0) };
5351                let mm1 = if r1 >= 0 { (m1.0 as i32, m1.1 as i32) } else { (0, 0) };
5352                (r0 as i32, r1 as i32, mm0, mm1)
5353            }
5354        };
5355        let w4 = self.mb_w * 4;
5356        let (b0, len) = (x0 * 4, n * 4);
5357        for dy in 0..4 {
5358            let a = (row * 4 + dy) * w4 + b0;
5359            self.ref_idx_y[a..a + len].fill(g_r0);
5360            self.mv_y[a..a + len].fill(g_m0);
5361            self.ref_idx1[a..a + len].fill(g_r1);
5362            self.mv1[a..a + len].fill(g_m1);
5363            self.inter_y[a..a + len].fill(true);
5364            self.coded_y[a..a + len].fill(true);
5365            self.modes_y[a..a + len].fill(2);
5366            self.nnz_y[a..a + len].fill(0);
5367        }
5368        match kind {
5369            BzKind::ZeroBi => self.bz_recon_band_bi(row, x0, n),
5370            BzKind::ZeroUni(list, ri) => self.bz_recon_band_copy(row, x0, n, list as usize, ri as usize, (0, 0)),
5371            BzKind::Fp { r0, r1, m0, m1 } => {
5372                let mv0 = (m0.0 as i32, m0.1 as i32);
5373                let mv1 = (m1.0 as i32, m1.1 as i32);
5374                match (r0 >= 0, r1 >= 0) {
5375                    (true, true) => self.bz_recon_band_fp_bi(row, x0, n, r0 as usize, r1 as usize, mv0, mv1),
5376                    (true, false) => self.bz_recon_band_copy(row, x0, n, 0, r0 as usize, mv0),
5377                    _ => self.bz_recon_band_copy(row, x0, n, 1, r1 as usize, mv1),
5378                }
5379            }
5380        }
5381    }
5382
5383    /// ZeroBi band: rows of 16n averaged from both padded refs (index 0).
5384    fn bz_recon_band_bi(&mut self, row: usize, x0: usize, n: usize) {
5385        // Byte-identical to n recon_b_skip_zero_bi(_, _, 0, 0) calls: each
5386        // output byte is the same (a + b + 1) >> 1 of the same source bytes.
5387        let w = n * 16;
5388        let Some(rf0) = self.refs.first() else { return };
5389        let Some(rf1) = self.refs1.first() else { return };
5390        let (l0st, l1st) = (rf0.lstride(), rf1.lstride());
5391        {
5392            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
5393            let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
5394            for dy in 0..16 {
5395                let y = row * 16 + dy;
5396                let s0 = (y + crate::LPAD) * l0st + crate::LPAD + x0 * 16;
5397                let s1 = (y + crate::LPAD) * l1st + crate::LPAD + x0 * 16;
5398                let d = y * self.cw + x0 * 16;
5399                for ((dst, a), b) in self.rec_y[d..d + w].iter_mut().zip(&ly0[s0..s0 + w]).zip(&ly1[s1..s1 + w]) {
5400                    *dst = ((*a as u16 + *b as u16 + 1) >> 1) as u8;
5401                }
5402            }
5403        }
5404        let (c0st, c1st) = (rf0.cstride(), rf1.cstride());
5405        let wc = n * 8;
5406        for c in 0..2 {
5407            let rc0 = rf0.chroma_guard(c, rf0.ch);
5408            let rc1 = rf1.chroma_guard(c, rf1.ch);
5409            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
5410            for dy in 0..8 {
5411                let y = row * 8 + dy;
5412                let s0 = (y + crate::CPAD) * c0st + crate::CPAD + x0 * 8;
5413                let s1 = (y + crate::CPAD) * c1st + crate::CPAD + x0 * 8;
5414                let d = y * self.ccw + x0 * 8;
5415                for ((dst, a), b) in plane[d..d + wc].iter_mut().zip(&rc0[s0..s0 + wc]).zip(&rc1[s1..s1 + wc]) {
5416                    *dst = ((*a as u16 + *b as u16 + 1) >> 1) as u8;
5417                }
5418            }
5419        }
5420    }
5421
5422    /// Full-pel offset copy band from ONE list's padded planes. The caller
5423    /// prevalidated (per MB, at push): mv%8 == 0 both components and luma +
5424    /// chroma windows inside the padded planes — contiguous MB windows tile,
5425    /// so the span's union window is valid by induction.
5426    #[allow(clippy::too_many_arguments)]
5427    fn bz_recon_band_copy(&mut self, row: usize, x0: usize, n: usize, list: usize, ri: usize, mv: (i32, i32)) {
5428        let Some(rf) = (if list == 0 { self.refs.get(ri) } else { self.refs1.get(ri) }) else {
5429            return;
5430        };
5431        let lst = rf.lstride();
5432        let (dx, dy) = ((mv.0 / 4) as isize, (mv.1 / 4) as isize);
5433        let w = n * 16;
5434        {
5435            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
5436            let ly = rf.luma_guard(rf.ch);
5437            for r in 0..16 {
5438                let y = (row * 16 + r) as isize + dy;
5439                let src = ((y + crate::LPAD as isize) * lst as isize
5440                    + crate::LPAD as isize
5441                    + x0 as isize * 16
5442                    + dx) as usize;
5443                let d = (row * 16 + r) * self.cw + x0 * 16;
5444                self.rec_y[d..d + w].copy_from_slice(&ly[src..src + w]);
5445            }
5446        }
5447        let cst = rf.cstride();
5448        let (cdx, cdy) = ((mv.0 / 8) as isize, (mv.1 / 8) as isize);
5449        let wc = n * 8;
5450        for c in 0..2 {
5451            let rc = rf.chroma_guard(c, rf.ch);
5452            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
5453            for r in 0..8 {
5454                let y = (row * 8 + r) as isize + cdy;
5455                let src = ((y + crate::CPAD as isize) * cst as isize
5456                    + crate::CPAD as isize
5457                    + x0 as isize * 8
5458                    + cdx) as usize;
5459                let d = (row * 8 + r) * self.ccw + x0 * 8;
5460                plane[d..d + wc].copy_from_slice(&rc[src..src + wc]);
5461            }
5462        }
5463    }
5464
5465    /// Full-pel bi band: rows of 16n averaged from two offset windows
5466    /// (prevalidated as above; implicit weights None/(32,32) guaranteed by
5467    /// the pushing arm).
5468    #[allow(clippy::too_many_arguments)]
5469    fn bz_recon_band_fp_bi(&mut self, row: usize, x0: usize, n: usize, r0: usize, r1: usize, mv0: (i32, i32), mv1: (i32, i32)) {
5470        let (Some(rf0), Some(rf1)) = (self.refs.get(r0), self.refs1.get(r1)) else {
5471            return;
5472        };
5473        let (l0, l1) = (rf0.lstride(), rf1.lstride());
5474        let off = |st: usize, pad: isize, yy: isize, xx: isize| (((yy + pad) * st as isize) + pad + xx) as usize;
5475        let w = n * 16;
5476        {
5477            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
5478            let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
5479            for r in 0..16 {
5480                let y = (row * 16 + r) as isize;
5481                let s0 = off(l0, crate::LPAD as isize, y + (mv0.1 / 4) as isize, x0 as isize * 16 + (mv0.0 / 4) as isize);
5482                let s1 = off(l1, crate::LPAD as isize, y + (mv1.1 / 4) as isize, x0 as isize * 16 + (mv1.0 / 4) as isize);
5483                let d = (row * 16 + r) * self.cw + x0 * 16;
5484                for ((dst, a), b) in self.rec_y[d..d + w].iter_mut().zip(&ly0[s0..s0 + w]).zip(&ly1[s1..s1 + w]) {
5485                    *dst = ((*a as u16 + *b as u16 + 1) >> 1) as u8;
5486                }
5487            }
5488        }
5489        let (c0, c1) = (rf0.cstride(), rf1.cstride());
5490        let wc = n * 8;
5491        for c in 0..2 {
5492            let rc0 = rf0.chroma_guard(c, rf0.ch);
5493            let rc1 = rf1.chroma_guard(c, rf1.ch);
5494            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
5495            for r in 0..8 {
5496                let y = (row * 8 + r) as isize;
5497                let s0 = off(c0, crate::CPAD as isize, y + (mv0.1 / 8) as isize, x0 as isize * 8 + (mv0.0 / 8) as isize);
5498                let s1 = off(c1, crate::CPAD as isize, y + (mv1.1 / 8) as isize, x0 as isize * 8 + (mv1.0 / 8) as isize);
5499                let d = (row * 8 + r) * self.ccw + x0 * 8;
5500                for ((dst, a), b) in plane[d..d + wc].iter_mut().zip(&rc0[s0..s0 + wc]).zip(&rc1[s1..s1 + wc]) {
5501                    *dst = ((*a as u16 + *b as u16 + 1) >> 1) as u8;
5502                }
5503            }
5504        }
5505    }
5506
5507    /// Per-MB prevalidation for an Fp span push: mv%8 both components (chroma
5508    /// copies at mv/8) and luma + chroma windows inside the padded planes for
5509    /// every active list. False ⇒ the arm recons immediately instead.
5510    fn bz_fp_valid(&self, mbx: usize, mby: usize, r0: Option<usize>, r1: Option<usize>, mv0: (i32, i32), mv1: (i32, i32)) -> bool {
5511        let chk = |rf: &crate::Ref, mv: (i32, i32)| -> bool {
5512            if mv.0 % 8 != 0 || mv.1 % 8 != 0 {
5513                return false;
5514            }
5515            let lst = rf.lstride();
5516            let lpad = crate::LPAD as isize;
5517            let cx = mbx as isize * 16 + (mv.0 / 4) as isize + lpad;
5518            let cy = mby as isize * 16 + (mv.1 / 4) as isize + lpad;
5519            let lrows = rf.lrows() as isize;
5520            if cx < 0 || cx + 16 > lst as isize || cy < 0 || cy + 16 > lrows {
5521                return false;
5522            }
5523            let cst = rf.cstride();
5524            let cpad = crate::CPAD as isize;
5525            let ccx = mbx as isize * 8 + (mv.0 / 8) as isize + cpad;
5526            let ccy = mby as isize * 8 + (mv.1 / 8) as isize + cpad;
5527            let crows = rf.crows() as isize;
5528            ccx >= 0 && ccx + 8 <= cst as isize && ccy >= 0 && ccy + 8 <= crows
5529        };
5530        r0.is_none_or(|i| self.refs.get(i).is_some_and(|r| chk(r, mv0)))
5531            && r1.is_none_or(|i| self.refs1.get(i).is_some_and(|r| chk(r, mv1)))
5532    }
5533
5534    /// Defer one (0,0) P_Skip's grid commit into the pending P span.
5535    #[inline]
5536    fn pz_push(&mut self, mb_x: usize, mb_y: usize, recon: bool) {
5537        match self.pzspan {
5538            Some((row, x0, ref mut n, r)) if row == mb_y && x0 + *n == mb_x && r == recon => {
5539                *n += 1
5540            }
5541            _ => {
5542                self.pz_flush();
5543                self.pzspan = Some((mb_y, mb_x, 1, recon));
5544            }
5545        }
5546    }
5547
5548    /// Range-fill the pending P span's grids (the deferral constants of
5549    /// decode_p_skip's commit: list-0 ref 0, mv (0,0), inter, coded, DC mode,
5550    /// deblock kind SKIP). Same flush points as the B span (span_flush).
5551    #[inline(always)]
5552    fn pz_flush(&mut self) {
5553        if self.pzspan.is_some() {
5554            self.pz_flush_slow();
5555        }
5556    }
5557
5558    fn pz_flush_slow(&mut self) {
5559        let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSpanRecon);
5560        let Some((row, x0, n, recon)) = self.pzspan.take() else { return };
5561        edcstat::bump(&edcstat::PZ_SPANS, 1);
5562        edcstat::bump(&edcstat::PZ_SPAN_MBS, n as u64);
5563        let w4 = self.mb_w * 4;
5564        let (b0, len) = (x0 * 4, n * 4);
5565        for dy in 0..4 {
5566            let a = (row * 4 + dy) * w4 + b0;
5567            self.mv_y[a..a + len].fill((0, 0));
5568            self.inter_y[a..a + len].fill(true);
5569            self.ref_idx_y[a..a + len].fill(0);
5570            self.coded_y[a..a + len].fill(true);
5571            self.modes_y[a..a + len].fill(2);
5572        }
5573        self.mb_kind[row * self.mb_w + x0..row * self.mb_w + x0 + n]
5574            .fill(rusty_h264_common::deblock::MB_KIND_SKIP);
5575        if recon {
5576            // The recon deferred with the commit: one band copy from ref[0]
5577            // replaces n queued EdcJob::Skip pushes AND the flush-time
5578            // run-coalescing scan that used to rediscover this very run.
5579            self.recon_p_skip_band(x0, row, n);
5580        }
5581    }
5582
5583    /// Flush BOTH pending grid spans — the one call every grid reader makes.
5584    #[inline]
5585    fn span_flush(&mut self) {
5586        self.bz_flush();
5587        self.pz_flush();
5588    }
5589
5590    /// Hot prefix of decode_b_skip: the FORCED zero-bi run continuation,
5591    /// small enough to inline into the slice loops. Returns true when the MB
5592    /// was fully handled (deferred into the span); false falls to the cold
5593    /// body. Exactly the forced arm's conditions — no behavior change.
5594    #[inline(always)]
5595    fn b_skip_hot(&mut self, mb_x: usize, mb_y: usize) -> bool {
5596        if !self.direct_spatial || self.edc_tx.is_some() || self.k_no_bskipfast {
5597            return false;
5598        }
5599        let mbw = self.mb_w;
5600        let addr = mb_y * mbw + mb_x;
5601        // ONE slice that ENDS at `addr` proves all three neighbour reads. Each
5602        // was a separate check against the whole `bzero` grid even though every
5603        // index is strictly below `addr` (the guards above establish that), and
5604        // the trailing `bzero[addr] = true` a fourth. `get(..=addr)` states the
5605        // bound once, in the form the neighbour indexes are already inside.
5606        // The `get(..=addr)` slice did NOT fold these: it bounds the reads from
5607        // above, but LLVM still has to chain `mb_x > 0 => addr >= 1` and
5608        // `mb_y > 0 => addr >= mbw` through the `&&` sequence, and it will not.
5609        // Fallible reads state each one where it is made; a missing neighbour is
5610        // "not zero", which is the conservative answer this guard wants.
5611        let up = addr.wrapping_sub(mbw);
5612        if !(mb_x > 0
5613            && mb_y > 0
5614            && mb_x + 1 < mbw
5615            && up >= self.slice_first_mb
5616            && self.bzero.get(addr - 1).copied().unwrap_or(false)
5617            && self.bzero.get(up).copied().unwrap_or(false)
5618            && self.bzero.get(up + 1).copied().unwrap_or(false))
5619        {
5620            return false;
5621        }
5622        let wgt = self.iw00();
5623        if !(wgt.is_none() || wgt == Some((32, 32))) {
5624            return false;
5625        }
5626        self.route_skip_mbs += 1;
5627        edcstat::bump(&edcstat::BSKB_FORCED, 1);
5628        let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSkipHot);
5629        edcstat::bump(&edcstat::BSKB_FAST, 1);
5630        self.bz_push(mb_x, mb_y, BzKind::ZeroBi);
5631        if let Some(b) = self.bzero.get_mut(addr) {
5632            *b = true;
5633        }
5634        true
5635    }
5636
5637    /// Zero this macroblock's 16 luma nnz entries, walking rows by adding `w4`
5638    /// instead of recomputing `(mb_y * 4 + dy) * w4 + mb_x * 4` four times.
5639    /// Shared by the three sites in decode_b_skip that had it open-coded.
5640    #[inline]
5641    fn clear_mb_nnz(&mut self, mb_x: usize, mb_y: usize, w4: usize) {
5642        let mut a = (mb_y * 4) * w4 + mb_x * 4;
5643        for _ in 0..4 {
5644            self.nnz_y[a..a + 4].fill(0);
5645            a += w4;
5646        }
5647    }
5648
5649    /// The B_Skip SLOW half: full spatial-direct recon plus the zero-residual
5650    /// plane copy. Split out so `pred_y`/`c_pred` (384 B) are built ONLY when a
5651    /// macroblock actually reaches it — the zero-bi / zero-uni / full-pel fast
5652    /// paths in decode_b_skip all return before this, and on LIGHT streams they
5653    /// take 90%+ of the calls.
5654    fn b_skip_slow(
5655        &mut self,
5656        mb_x: usize,
5657        mb_y: usize,
5658        nbrs: Option<([MvNeighbor; 3], [MvNeighbor; 3])>,
5659        w4: usize,
5660    ) -> Result<(), MbError> {
5661        let mut pred_y = [0u8; 256];
5662        let mut c_pred = [[0u8; 64]; 2];
5663        match nbrs {
5664            // Reuses the already-probed neighbours — the grid walk is the
5665            // expensive half of the derivation and paying it twice leaned on
5666            // fall-through-heavy streams (stockholm).
5667            Some((n0, n1)) => {
5668                self.decode_b_direct_n(mb_x, mb_y, 0, 0, 16, 16, &mut pred_y, &mut c_pred, n0, n1)
5669            }
5670            None => self.decode_b_direct(mb_x, mb_y, 0, 0, 16, 16, &mut pred_y, &mut c_pred),
5671        }
5672        if let Some(regions) = self.edc_regions.take() {
5673            // nnz clears are PARSE state; the pixel copy is the worker's.
5674            self.clear_mb_nnz(mb_x, mb_y, w4);
5675            self.edc_giveback();
5676            self.edc_send_job(EdcJob::BSkip { mbx: mb_x, mby: mb_y, regions });
5677            return Ok(());
5678        }
5679        // Zero residual: the prediction IS the reconstruction — copy it row-wise,
5680        // stepping the destination by the stride rather than multiplying per row.
5681        let mut d = (mb_y * 16) * self.cw + mb_x * 16;
5682        for dy in 0..16 {
5683            self.rec_y[d..d + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
5684            d += self.cw;
5685        }
5686        let (ccw, cx0) = (self.ccw, mb_x * 8);
5687        for c in 0..2 {
5688            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
5689            let mut d = (mb_y * 8) * ccw + cx0;
5690            for dy in 0..8 {
5691                plane[d..d + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
5692                d += ccw;
5693            }
5694        }
5695        // nnz stays 0 (no residual) — clear the grids for neighbour context.
5696        self.clear_mb_nnz(mb_x, mb_y, w4);
5697        Ok(())
5698    }
5699
5700    fn decode_b_skip(&mut self, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
5701        let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSkipCold);
5702        self.route_skip_mbs += 1;
5703        self.wait_refs_for_mb(mb_y);
5704        // Each list length read ONCE: the emptiness test and every later
5705        // `len() - 1` clamp (six reads in all) come from these two.
5706        let (nref0, nref1) = (self.refs.len(), self.refs1.len());
5707        if nref0 == 0 || nref1 == 0 {
5708            return Err(MbError::Unsupported("B without references"));
5709        }
5710        let (lref0, lref1) = (nref0 - 1, nref1 - 1);
5711        let mbw = self.mb_w;
5712        let addr = mb_y * mbw + mb_x;
5713        let w4 = mbw * 4;
5714        // ZERO-BI FAST PATH: when spatial direct derives (0,0)/(0,0) bi motion,
5715        // colZeroFlag is IRRELEVANT (it only zeroes MVs that are already zero),
5716        // so the colocated probing, region split, b_mc staging and the pred
5717        // round-trip all collapse into one fused row-average from the two
5718        // padded refs straight into the recon planes. b_mc's bi blend applies
5719        // only IMPLICIT weights, so `None` or (32,32) (both exactly
5720        // (a+b+1)>>1) is the whole identity condition. FourPeople-class LIGHT
5721        // streams put 90%+ of their B_Skips here (BSK_ZBI counter).
5722        // pred_y / c_pred now live in `b_skip_slow` — see there.
5723        if self.direct_spatial && self.edc_tx.is_none() && !self.k_no_bskipfast {
5724            // (The FORCED zero-bi run continuation lives in b_skip_hot, inlined
5725            // at the loop call sites — this cold body only sees non-forced MBs.)
5726            // A pending span can only occupy the LEFT gather position, and the
5727            // left member's committed grid values are fully determined by the
5728            // span KIND — synthesize them instead of flushing, for EVERY kind
5729            // (the same mapping bz_flush writes).
5730            let patch = match self.bzspan {
5731                Some((r, x0, n, k)) if r == mb_y && x0 + n == mb_x => Some(k),
5732                _ => None,
5733            };
5734            if patch.is_none() {
5735                self.bz_flush();
5736            }
5737            let (mut n0, mut n1) = self.b_direct_nbrs(mb_x, mb_y);
5738            if let Some(k) = patch {
5739                let (r0, r1, m0, m1): (i32, i32, (i32, i32), (i32, i32)) = match k {
5740                    BzKind::ZeroBi => (0, 0, (0, 0), (0, 0)),
5741                    BzKind::ZeroUni(0, ri) => (ri as i32, -1, (0, 0), (0, 0)),
5742                    BzKind::ZeroUni(_, ri) => (-1, ri as i32, (0, 0), (0, 0)),
5743                    BzKind::Fp { r0, r1, m0, m1 } => {
5744                        let mm0 = if r0 >= 0 { (m0.0 as i32, m0.1 as i32) } else { (0, 0) };
5745                        let mm1 = if r1 >= 0 { (m1.0 as i32, m1.1 as i32) } else { (0, 0) };
5746                        (r0 as i32, r1 as i32, mm0, mm1)
5747                    }
5748                };
5749                n0[0] = MvNeighbor { available: true, mv: m0, ref_idx: r0 };
5750                n1[0] = MvNeighbor { available: true, mv: m1, ref_idx: r1 };
5751            }
5752            let (refi0, refi1, mv0, mv1, _dz) = Self::b_direct_refs_mvs(&n0, &n1);
5753            let (a0, a1) = (refi0 >= 0, refi1 >= 0);
5754            let fast = if a0 && a1 && mv0 == (0, 0) && mv1 == (0, 0) {
5755                // Same malformed-stream ref clamp as b_mc.
5756                let r0 = (refi0 as usize).min(lref0);
5757                let r1 = (refi1 as usize).min(lref1);
5758                // (0,0) dominates here and is already cached slice-wide by iw00.
5759                let wgt = if r0 == 0 && r1 == 0 {
5760                    self.iw00()
5761                } else {
5762                    self.implicit_weights(r0 as i32, r1 as i32)
5763                };
5764                if wgt.is_none() || wgt == Some((32, 32)) {
5765                    if refi0 == 0 && refi1 == 0 {
5766                        if let Some(b) = self.bzero.get_mut(addr) {
5767            *b = true;
5768        }
5769                        // Same constants as the forced arm: recon AND commit
5770                        // both defer into the span.
5771                        self.bz_push(mb_x, mb_y, BzKind::ZeroBi);
5772                        edcstat::bump(&edcstat::BSKB_FAST, 1);
5773                        return Ok(());
5774                    }
5775                    self.recon_b_skip_zero_bi(mb_x, mb_y, r0, r1);
5776                    true
5777                } else {
5778                    false
5779                }
5780            } else if a0 != a1 && (if a0 { mv0 } else { mv1 }) == (0, 0) {
5781                // ZERO-UNI: one active list at (0,0) — b_mc's uni arms are plain
5782                // unweighted copies. Defer as a memcpy-band span.
5783                let (list, ri) = if a0 {
5784                    (0u8, (refi0 as usize).min(lref0) as u8)
5785                } else {
5786                    (1u8, (refi1 as usize).min(lref1) as u8)
5787                };
5788                self.bz_push(mb_x, mb_y, BzKind::ZeroUni(list, ri));
5789                edcstat::bump(&edcstat::BSKB_FAST, 1);
5790                return Ok(());
5791            } else if (!a0 || (mv0.0 % 4 == 0 && mv0.1 % 4 == 0))
5792                && (!a1 || (mv1.0 % 4 == 0 && mv1.1 % 4 == 0))
5793                && self.direct_8x8_inference
5794            {
5795                // FULL-PEL arm (the pan case — shields): nonzero MVs make
5796                // colZeroFlag matter, so probe the four 8x8 corners; a UNIFORM
5797                // czg keeps the MB one region and the MC is an offset read.
5798                // Non-uniform czg or an out-of-pad window falls through.
5799                let mut cz_ok = true;
5800                let mut czv = false;
5801                // Same cz-relevance gate as decode_b_direct_n: only a ref-0
5802                // list with a nonzero MV can be changed by colZeroFlag (fp-arm
5803                // MVs are nonzero by construction, so refs alone decide).
5804                let cz_need = refi0 == 0 || refi1 == 0;
5805                // LOOP-INVARIANT: this gated a `break` INSIDE the probe loop, so
5806                // a fact known before the loop was re-tested on every probe.
5807                if cz_need {
5808                    // Absolute block base, hoisted out of the four probes.
5809                    let (bx4, by4) = (mb_x * 4, mb_y * 4);
5810                    for (k, &(ox, oy)) in [(0usize, 0usize), (2, 0), (0, 2), (2, 2)].iter().enumerate() {
5811                        // col_block takes MB-LOCAL block coords (the whole MB is
5812                        // the region here); col_zero takes absolute — same
5813                        // convention as decode_b_direct_n's probe loop.
5814                        let (colx, coly) = self.col_block(ox, oy);
5815                        let cz = self.col_zero(bx4 + colx, by4 + coly);
5816                        if k == 0 {
5817                            czv = cz;
5818                        } else if cz != czv {
5819                            cz_ok = false;
5820                            break;
5821                        }
5822                    }
5823                }
5824                if cz_ok {
5825                    let m0 = if refi0 == 0 && czv { (0, 0) } else { mv0 };
5826                    let m1 = if refi1 == 0 && czv { (0, 0) } else { mv1 };
5827                    let r0c = (refi0 as usize).min(lref0);
5828                    let r1c = (refi1 as usize).min(lref1);
5829                    let (or0, or1) = (a0.then_some(r0c), a1.then_some(r1c));
5830                    let wgt_ok = !(a0 && a1) || {
5831                        let wgt = self.implicit_weights(r0c as i32, r1c as i32);
5832                        wgt.is_none() || wgt == Some((32, 32))
5833                    };
5834                    // SPAN path: prevalidated windows + %8 chroma ⇒ defer as
5835                    // an offset copy/avg band; otherwise the immediate per-MB
5836                    // recon below (which handles interpolating chroma).
5837                    if wgt_ok && self.bz_fp_valid(mb_x, mb_y, or0, or1, m0, m1) {
5838                        let kind = BzKind::Fp {
5839                            r0: if a0 { r0c as i8 } else { -1 },
5840                            r1: if a1 { r1c as i8 } else { -1 },
5841                            m0: (m0.0 as i16, m0.1 as i16),
5842                            m1: (m1.0 as i16, m1.1 as i16),
5843                        };
5844                        self.bz_push(mb_x, mb_y, kind);
5845                        edcstat::bump(&edcstat::BSKB_FP, 1);
5846                        return Ok(());
5847                    }
5848                    let ok = wgt_ok && self.recon_b_skip_fp(mb_x, mb_y, or0, or1, m0, m1);
5849                    if ok {
5850                        edcstat::bump(&edcstat::BSKB_FP, 1);
5851                        self.b_set_motion(mb_x, mb_y, 0, 0, 16, 16, refi0, m0, refi1, m1);
5852                        self.clear_mb_nnz(mb_x, mb_y, w4);
5853                        return Ok(());
5854                    }
5855                }
5856                false
5857            } else {
5858                false
5859            };
5860            if fast {
5861                edcstat::bump(&edcstat::BSKB_FAST, 1);
5862                self.b_set_motion(mb_x, mb_y, 0, 0, 16, 16, refi0, (0, 0), refi1, (0, 0));
5863                self.clear_mb_nnz(mb_x, mb_y, w4);
5864                return Ok(());
5865            }
5866            // Fall-through hands the ALREADY-probed neighbours to the slow
5867            // half so the grid walk is not paid twice.
5868            return self.b_skip_slow(mb_x, mb_y, Some((n0, n1)), w4);
5869        } else {
5870            if self.edc_tx.is_some() {
5871                self.edc_regions = Some(Vec::with_capacity(4));
5872            }
5873            return self.b_skip_slow(mb_x, mb_y, None, w4);
5874        }
5875    }
5876
5877    /// Reconstructs a B macroblock (spec Table 7-14): direct, L0/L1/Bi partitions,
5878    /// `B_8x8`, or intra.
5879    fn decode_b_mb(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
5880        // Non-skip B MBs gather neighbor motion/modes — flush any deferred
5881        // spans before those grids are read.
5882        self.span_flush();
5883        self.wait_refs_for_mb(mb_y);
5884        let mb_type = r.read_ue()?;
5885        if mb_type >= 23 {
5886            return self.decode_intra_mb(r, mb_x, mb_y, mb_type - 23);
5887        }
5888        if self.refs.is_empty() || self.refs1.is_empty() {
5889            return Err(MbError::Unsupported("B without references"));
5890        }
5891        let mut pred_y = [0u8; 256];
5892        let mut c_pred = [[0u8; 64]; 2];
5893
5894        if mb_type == 0 {
5895            // B_Direct_16x16 — 8×8 transform allowed only with direct_8x8_inference.
5896            // FORCED derivation via the zero-bi bitmap (CAVLC parity with the
5897            // CABAC direct arm): gather + rid/median skipped, chains extended.
5898            let addr_f = mb_y * self.mb_w + mb_x;
5899            let forced = self.direct_spatial
5900                && self.edc_tx.is_none()
5901                && mb_x > 0
5902                && mb_y > 0
5903                && mb_x + 1 < self.mb_w
5904                && addr_f - self.mb_w >= self.slice_first_mb
5905                && self.bzero.get(addr_f - 1).copied().unwrap_or(false)
5906                && self.bzero.get(addr_f.wrapping_sub(self.mb_w)).copied().unwrap_or(false)
5907                && self
5908                    .bzero
5909                    .get(addr_f.wrapping_sub(self.mb_w) + 1)
5910                    .copied()
5911                    .unwrap_or(false);
5912            if forced {
5913                self.b_direct_region(mb_x, mb_y, 0, 0, 16, 16, &mut pred_y, &mut c_pred, (0, 0, (0, 0), (0, 0), false));
5914                if let Some(b) = self.bzero.get_mut(addr_f) {
5915                *b = true;
5916            }
5917            } else {
5918                self.decode_b_direct(mb_x, mb_y, 0, 0, 16, 16, &mut pred_y, &mut c_pred);
5919            }
5920            return self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, self.direct_8x8_inference, false);
5921        }
5922        if mb_type == 22 {
5923            return self.decode_b_8x8(r, mb_x, mb_y);
5924        }
5925
5926        // 16x16 / 16x8 / 8x16 partitions with per-partition L0/L1/Bi.
5927        let (layout, mvmode, preds) = b_inter_layout(mb_type);
5928        // mb_pred order: ref_idx_l0 (all L0 parts), ref_idx_l1, mvd_l0, mvd_l1.
5929        let mut refi = [[-1i32; 2]; 2]; // [part][list]
5930        for (p, &(_, _, _, _)) in layout.iter().enumerate() {
5931            if preds[p & 1].uses(0) {
5932                refi[p][0] = self.read_b_ref(r, 0)?;
5933            }
5934        }
5935        for (p, _) in layout.iter().enumerate() {
5936            if preds[p & 1].uses(1) {
5937                refi[p][1] = self.read_b_ref(r, 1)?;
5938            }
5939        }
5940        let mut mvd = [[(0i32, 0i32); 2]; 2];
5941        for (p, _) in layout.iter().enumerate() {
5942            if preds[p & 1].uses(0) {
5943                mvd[p][0] = (read_mvd(r)?, read_mvd(r)?);
5944            }
5945        }
5946        for (p, _) in layout.iter().enumerate() {
5947            if preds[p & 1].uses(1) {
5948                mvd[p][1] = (read_mvd(r)?, read_mvd(r)?);
5949            }
5950        }
5951        // Per partition: predict + commit each list's MV, then motion-compensate.
5952        for (p, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
5953            let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
5954            let pwb = (rw / 4) as isize;
5955            let mut mv = [(0i32, 0i32); 2];
5956            for list in 0..2 {
5957                if refi[p][list] >= 0 {
5958                    let n = self.mv_neighbors_list(pbx, pby, pwb, list);
5959                    let pmv = predict_partition_mv(mvmode, p, n[0], n[1], n[2], refi[p][list]);
5960                    mv[list] = (pmv.0 + mvd[p][list].0, pmv.1 + mvd[p][list].1);
5961                }
5962            }
5963            self.b_set_motion(mb_x, mb_y, rx, ry, rw, rh, refi[p & 3][0], mv[0], refi[p & 3][1], mv[1]);
5964            // Spec-correct bi-prediction (average of L0 and L1), matching the CABAC
5965            // path. This used to replicate an openh264 bug for a Bi 16x8/8x16
5966            // partition -- openh264 mis-handles the destination buffer there, so
5967            // partition 0 came out List-1-only and partition 1 List-0-only. That was
5968            // deliberate when openh264's h264dec WAS the conformance oracle, but the
5969            // gate is ffmpeg now and the CABAC path already went spec-correct; the
5970            // CAVLC path was simply left behind. Measured: mb_type 12..21 (every B
5971            // 16x8/8x16 with at least one Bi partition) were 100% wrong vs ffmpeg,
5972            // while 1..11 (no Bi partition) were only collaterally damaged.
5973            self.b_mc_or_record(mb_x, mb_y, rx, ry, rw, rh, refi[p & 3][0], mv[0], refi[p & 3][1], mv[1], &mut pred_y, &mut c_pred);
5974        }
5975        self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, true, false)
5976    }
5977
5978    /// Reconstructs a `B_8x8` macroblock: four 8×8 sub-macroblock partitions, each
5979    /// direct or L0/L1/Bi with its own sub-partitioning (spec Table 7-18).
5980    fn decode_b_8x8(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
5981        let mut sub = [0u32; 4];
5982        for s in sub.iter_mut() {
5983            let v = r.read_ue()?;
5984            if v > 12 {
5985                return Err(MbError::Unsupported("invalid B sub_mb_type"));
5986            }
5987            *s = v;
5988        }
5989        let mut pred_y = [0u8; 256];
5990        let mut c_pred = [[0u8; 64]; 2];
5991        // ref_idx for all 8×8 partitions (L0 batch, then L1 batch), for the
5992        // non-direct sub-partitions.
5993        let mut refi = [[-1i32; 2]; 4];
5994        for (p, &st) in sub.iter().enumerate() {
5995            if st != 0 && b_sub_uses(st, 0) {
5996                refi[p][0] = self.read_b_ref(r, 0)?;
5997            }
5998        }
5999        for (p, &st) in sub.iter().enumerate() {
6000            if st != 0 && b_sub_uses(st, 1) {
6001                refi[p][1] = self.read_b_ref(r, 1)?;
6002            }
6003        }
6004        // mvd: all mvd_l0 (partition-major, sub-partition order), then all mvd_l1.
6005        //
6006        // FIXED ARRAYS, NOT `Vec::new()` + push. These are per-MACROBLOCK on every
6007        // B_8x8, and a growing Vec allocated (and reallocated) twice per MB — on a
6008        // B-heavy stream that is thousands of allocations per frame for data whose
6009        // maximum size is a compile-time constant: 4 partitions x at most 4
6010        // sub-partitions = 16 entries. Indexing a fixed array cannot exceed that, and
6011        // an out-of-range index would panic rather than misbehave, so the bound is
6012        // enforced either way and this crate stays forbid(unsafe).
6013        const MAX_MVD: usize = 16;
6014        let mut mvd0 = [(0i32, 0i32); MAX_MVD];
6015        let mut mvd1 = [(0i32, 0i32); MAX_MVD];
6016        let (mut n0, mut n1) = (0usize, 0usize);
6017        for &st in &sub {
6018            if st != 0 && b_sub_uses(st, 0) {
6019                for _ in b_sub_parts(st) {
6020                    mvd0[n0] = (read_mvd(r)?, read_mvd(r)?);
6021                    n0 += 1;
6022                }
6023            }
6024        }
6025        for &st in &sub {
6026            if st != 0 && b_sub_uses(st, 1) {
6027                for _ in b_sub_parts(st) {
6028                    mvd1[n1] = (read_mvd(r)?, read_mvd(r)?);
6029                    n1 += 1;
6030                }
6031            }
6032        }
6033        // Decode each 8×8 partition.
6034        // Spatial-direct A/B/C are MB-level — walk once if any sub is direct.
6035        // `dmemo=0` rewalks every direct 8×8 (A/B oracle).
6036        let hoisted = if self.direct_spatial
6037            && direct_memo_on()
6038            && sub.iter().any(|&t| t == 0)
6039        {
6040            Some(self.b_direct_nbrs(mb_x, mb_y))
6041        } else {
6042            None
6043        };
6044        let (mut i0, mut i1) = (0usize, 0usize);
6045        for (p, &st) in sub.iter().enumerate() {
6046            let (b8x, b8y) = ((p % 2) * 8, (p / 2) * 8);
6047            if st == 0 {
6048                match hoisted {
6049                    Some((n0, n1)) => self.decode_b_direct_n(
6050                        mb_x, mb_y, b8x, b8y, 8, 8, &mut pred_y, &mut c_pred, n0, n1,
6051                    ),
6052                    None => self.decode_b_direct(
6053                        mb_x, mb_y, b8x, b8y, 8, 8, &mut pred_y, &mut c_pred,
6054                    ),
6055                }
6056                continue;
6057            }
6058            for &(sx, sy, sw, sh) in b_sub_parts(st) {
6059                let (px, py) = (b8x + sx, b8y + sy);
6060                let (pbx, pby) = ((mb_x * 4 + px / 4) as isize, (mb_y * 4 + py / 4) as isize);
6061                let pwb = (sw / 4) as isize;
6062                let mut mv = [(0i32, 0i32); 2];
6063                if b_sub_uses(st, 0) {
6064                    let n = self.mv_neighbors_list(pbx, pby, pwb, 0);
6065                    let pmv = predict_mv(n[0], n[1], n[2], refi[p & 3][0]);
6066                    let d = mvd0[i0 & 15];
6067                    i0 += 1;
6068                    mv[0] = (pmv.0 + d.0, pmv.1 + d.1);
6069                }
6070                if b_sub_uses(st, 1) {
6071                    let n = self.mv_neighbors_list(pbx, pby, pwb, 1);
6072                    let pmv = predict_mv(n[0], n[1], n[2], refi[p & 3][1]);
6073                    let d = mvd1[i1 & 15];
6074                    i1 += 1;
6075                    mv[1] = (pmv.0 + d.0, pmv.1 + d.1);
6076                }
6077                self.b_set_motion(mb_x, mb_y, px, py, sw, sh, refi[p & 3][0], mv[0], refi[p & 3][1], mv[1]);
6078                self.b_mc_or_record(mb_x, mb_y, px, py, sw, sh, refi[p & 3][0], mv[0], refi[p & 3][1], mv[1], &mut pred_y, &mut c_pred);
6079            }
6080        }
6081        // noSubMbPartSizeLessThan8x8: each sub-partition must be ≥ 8×8 (direct
6082        // counts only with the 8×8 inference flag).
6083        let allow_8x8 = sub
6084            .iter()
6085            .all(|&st| if st == 0 { self.direct_8x8_inference } else { st <= 3 });
6086        self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, allow_8x8, false)
6087    }
6088
6089    /// Reconstructs a `P_8x8` macroblock: four 8×8 sub-macroblock partitions,
6090    /// each independently split (8×8 / 8×4 / 4×8 / 4×4) with its own motion
6091    /// vector(s). `ref0` is `P_8x8ref0` (every `ref_idx` forced to 0, not coded).
6092    fn decode_p8x8(
6093        &mut self,
6094        r: &mut BitReader,
6095        mb_x: usize,
6096        mb_y: usize,
6097        ref0: bool,
6098    ) -> Result<(), MbError> {
6099        if self.refs.is_empty() {
6100            return Err(MbError::Unsupported("inter without reference"));
6101        }
6102        let w4 = self.mb_w * 4;
6103        let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
6104        let num_refs = self.refs.len();
6105
6106        // mb_pred order (spec §7.3.5.2): all sub_mb_type, then all ref_idx_l0,
6107        // then all mvd_l0 (partition-major, sub-partition order within each).
6108        let mut sub_types = [0u32; 4];
6109        for st in sub_types.iter_mut() {
6110            let v = r.read_ue()?;
6111            if v > 3 {
6112                return Err(MbError::Unsupported("B-slice / invalid sub_mb_type"));
6113            }
6114            *st = v;
6115        }
6116        let mut ref_idxs = [0i32; 4];
6117        if self.num_ref_active > 1 && !ref0 {
6118            for ri in ref_idxs.iter_mut() {
6119                *ri = read_ref_idx(r, self.num_ref_active)?;
6120                if *ri as usize >= num_refs {
6121                    return Err(MbError::Truncated); // references a non-existent picture
6122                }
6123            }
6124        }
6125
6126        // Per sub-partition (in decoding order): median MV prediction from the
6127        // committed neighbor grid, mvd, commit, then motion-compensate. Committing
6128        // before the next prediction is what lets sub-partitions chain correctly.
6129        // D14b: P_8x8 defers too. Syncing before it instead cost 9,772 pipeline
6130        // drains per stream (45 per 1000 MBs, vs CABAC's 3.3) because P_8x8 is
6131        // common in CAVLC P slices — and the seam measured 1.65-1.97x SLOWER
6132        // for it. Deferring is byte-identical for sub-partitions: the worker
6133        // motion-compensates per 4x4 from the committed grids, which is exactly
6134        // how the CABAC path already handles P_8x8, and a 6-tap filter applied
6135        // per-4x4 with the same MV gives the same pixels as one 8x8 call.
6136        let defer = self.edc_tx.is_some() || self.edc_active;
6137
6138        // ── PHASE 1: PARSE + COMMIT. Must always run to completion. ──────────
6139        //
6140        // These two are interleaved BY NECESSITY: each sub-partition's MV
6141        // prediction reads the grids the previous one committed, so they cannot
6142        // be separated from each other. But they consume BITSTREAM, so nothing
6143        // here may ever be skipped conditionally.
6144        //
6145        // This phase split is a STRUCTURAL guard, not a tidy-up. When MC lived
6146        // inside this loop, deferring it was written as a `break` — which also
6147        // skipped the `read_se` mvd reads, desynced the bitstream, and
6148        // mis-parsed as a B-slice sub_mb_type on a Baseline stream. It happened
6149        // to crash; a desync that stayed IN RANGE would have produced plausible
6150        // garbage instead. With MC in its own pass below, skipping pixel work
6151        // cannot reach the bitstream at all — the mistake is unavailable.
6152        let mut regions: [(usize, usize, usize, usize, i32, (i32, i32)); 16] =
6153            [(0, 0, 0, 0, 0, (0, 0)); 16];
6154        let mut nreg = 0usize;
6155        for part in 0..4usize {
6156            let refi = ref_idxs[part];
6157            let (b8x, b8y) = ((part % 2) * 8, (part / 2) * 8);
6158            for &(srx, sry, srw, srh) in sub_mb_partitions(sub_types[part]) {
6159                let (px, py) = (b8x + srx, b8y + sry);
6160                let (pbx, pby) = ((mb_x * 4 + px / 4) as isize, (mb_y * 4 + py / 4) as isize);
6161                let [a, b, c] = self.mv_neighbors_block(pbx, pby, (srw / 4) as isize);
6162                let pmv = predict_mv(a, b, c, refi);
6163                let mvd_x = read_mvd(r)?;
6164                let mvd_y = read_mvd(r)?;
6165                let mv = (pmv.0 + mvd_x, pmv.1 + mvd_y);
6166                for by in py / 4..py / 4 + srh / 4 {
6167                    for bx in px / 4..px / 4 + srw / 4 {
6168                        let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
6169                        if let (Some(m), Some(it), Some(rf), Some(cd)) = (
6170                            self.mv_y.get_mut(idx),
6171                            self.inter_y.get_mut(idx),
6172                            self.ref_idx_y.get_mut(idx),
6173                            self.coded_y.get_mut(idx),
6174                        ) {
6175                            (*m, *it, *rf, *cd) = (mv, true, refi, true);
6176                        }
6177                    }
6178                }
6179                regions[nreg & 15] = (px, py, srw, srh, refi, mv);
6180                nreg += 1;
6181            }
6182        }
6183
6184        // ── PHASE 2: PIXEL WORK ONLY. Reads no bitstream; safe to skip. ──────
6185        let mut pred_y = [0u8; 256];
6186        let mut c_pred = [[0u8; 64]; 2];
6187        if !defer {
6188            for &(px, py, srw, srh, refi, mv) in &regions[..nreg] {
6189                let Some(reference) = self.refs.get(refi as usize) else { continue };
6190                let mut tmp = [0u8; 256];
6191                mc_luma_padded(&*reference.luma_guard(reference.ch), reference.lstride(), crate::LPAD, self.cw, ch, mb_x * 16 + px, mb_y * 16 + py, srw, srh, mv.0, mv.1, &mut tmp);
6192                restride(&mut pred_y, 16, px, py, &tmp, srw, srh);
6193                let (crx, cry, crw, crh) = (px / 2, py / 2, srw / 2, srh / 2);
6194                for cc in 0..2 {
6195                    let rc = if cc == 0 { &*reference.chroma_guard(0, reference.ch) } else { &*reference.chroma_guard(1, reference.ch) };
6196                    let mut tc = [0u8; 64];
6197                    mc_chroma_padded(rc, reference.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv.0, mv.1, &mut tc);
6198                    restride(&mut c_pred[cc], 8, crx, cry, &tc, crw, crh);
6199                }
6200                self.weight_partition(
6201                    &mut pred_y, &mut c_pred, 0, refi as usize, px, py, srw, srh,
6202                );
6203            }
6204        }
6205
6206        // P_8x8 allows the 8×8 transform only when every sub-partition is 8×8.
6207        let allow_8x8 = sub_types.iter().all(|&t| t == 0);
6208        self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, allow_8x8, defer)
6209    }
6210
6211    /// Reconstructs a `P_Skip` macroblock: motion-compensate from the reference
6212    /// at the skip MV, with no residual.
6213    /// Records a B MC region (threaded mode) or executes it inline — the SAME
6214    /// arguments as `b_mc`; the recorded arm resolves the implicit weights at
6215    /// parse time (identical function, parse-side data).
6216    #[allow(clippy::too_many_arguments)]
6217    fn b_mc_or_record(&mut self, mb_x: usize, mb_y: usize, px: usize, py: usize, rw: usize, rh: usize, refi0: i32, mv0: (i32, i32), refi1: i32, mv1: (i32, i32), pred_y: &mut [u8; 256], c_pred: &mut [[u8; 64]; 2]) {
6218        if self.edc_regions.is_some() {
6219            // Mirror `b_mc`'s malformed-stream armor EXACTLY before touching the
6220            // ref lists: the inline path clamps the indices and bails on empty
6221            // lists BEFORE computing weights; calling `implicit_weights` with the
6222            // raw indices re-introduced the panic the armor exists to prevent
6223            // (found by the fuzzer, via the unwind-guard that turned the
6224            // resulting worker deadlock back into a diagnosable failure).
6225            let cr0 = if refi0 >= 0 { (refi0 as usize).min(self.refs.len().saturating_sub(1)) as i32 } else { -1 };
6226            let cr1 = if refi1 >= 0 { (refi1 as usize).min(self.refs1.len().saturating_sub(1)) as i32 } else { -1 };
6227            let w = if (cr0 >= 0 && self.refs.is_empty()) || (cr1 >= 0 && self.refs1.is_empty()) {
6228                None // the worker's port returns before reading the weights
6229            } else {
6230                self.implicit_weights(cr0, cr1)
6231            };
6232            // Cannot bind above: `implicit_weights` needs `&mut self` between the
6233            // guard and here. The guard already proved this is `Some`, so the
6234            // `else` is unreachable -- but an unreachable no-op is the correct
6235            // shape for armor code, not an `unwrap` that can abort a decode.
6236            if let Some(regions) = self.edc_regions.as_mut() {
6237                regions.push(BRegion { px, py, rw, rh, refi0, refi1, mv0, mv1, w });
6238            }
6239            return;
6240        }
6241        self.b_mc(mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, pred_y, c_pred);
6242    }
6243
6244    /// Builds the worker's owned pixel context from `self` (planes MOVED out,
6245    /// shared read-only state cloned, filter inputs snapshotted).
6246    fn edc_take_ctx(&mut self) -> PixelCtx {
6247        PixelCtx {
6248            rec_y: std::mem::take(&mut self.rec_y),
6249            rec_u: std::mem::take(&mut self.rec_u),
6250            rec_v: std::mem::take(&mut self.rec_v),
6251            bak_y: std::mem::take(&mut self.bak_y),
6252            bak_u: std::mem::take(&mut self.bak_u),
6253            bak_v: std::mem::take(&mut self.bak_v),
6254            refs: self.refs.clone(),
6255            refs1: self.refs1.clone(),
6256            weights: self.weights.clone(),
6257            weights_l0id: self.weights_l0id,
6258            scaling: self.scaling,
6259            scaling8: self.scaling8,
6260            cw: self.cw,
6261            ccw: self.ccw,
6262            mb_w: self.mb_w,
6263            mb_h: self.mb_h,
6264            chroma_qp_offset: self.chroma_qp_offset,
6265            flt_rows: self.flt_rows,
6266            db_ena: self.db_ena,
6267            db_oa: self.db_oa,
6268            db_ob: self.db_ob,
6269            cur_qp: self.cur_qp,
6270            qp_grid: self.mb_qp.clone(),
6271            t8_grid: self.mb_t8x8.clone(),
6272            bs_store: self.bs_frame.clone(),
6273            progress: self.progress.clone(),
6274        }
6275    }
6276
6277    /// Restores the planes (and the filter watermark) from a returned context.
6278    fn edc_restore_ctx(&mut self, ctx: PixelCtx) {
6279        self.rec_y = ctx.rec_y;
6280        self.rec_u = ctx.rec_u;
6281        self.rec_v = ctx.rec_v;
6282        self.bak_y = ctx.bak_y;
6283        self.bak_u = ctx.bak_u;
6284        self.bak_v = ctx.bak_v;
6285        self.flt_rows = ctx.flt_rows;
6286    }
6287
6288    /// Intra macroblocks read neighbour PIXELS: fetch the context from the
6289    /// worker (which drains all prior jobs first — the channel is FIFO) and
6290    /// install the planes so the inline intra path runs unchanged. The context
6291    /// is given back lazily at the next job/row/slice-end (`edc_giveback`), so
6292    /// consecutive intra macroblocks pay ONE round-trip.
6293    /// Queue a pixel job for the worker (D10). Batched per row; see `EdcMsg::Batch`.
6294    #[inline]
6295    fn edc_send_job(&mut self, job: EdcJob) {
6296        edcstat::bump(&edcstat::JOBS, 1);
6297        if !batch_on() {
6298            self.edc_tx
6299                .as_ref()
6300                .unwrap()
6301                .send(EdcMsg::Job(job))
6302                .expect("worker alive");
6303            return;
6304        }
6305        self.edc_batch.push(job);
6306    }
6307
6308    /// Ship the accumulated row batch. MUST be called before anything that
6309    /// depends on those jobs having been applied: the row's `Row` filter
6310    /// message, a `NeedCtx` handover, and slice end.
6311    fn edc_flush_batch(&mut self) {
6312        if self.edc_batch.is_empty() {
6313            return;
6314        }
6315        // Replace with a PRE-RESERVED buffer rather than the empty Vec
6316        // `mem::take` would leave: otherwise each row reallocs and regrows from
6317        // zero, trading 208k channel sends for ~7 reallocs x 3k rows.
6318        let cap = self.edc_batch.capacity().max(self.mb_w);
6319        let jobs = std::mem::replace(&mut self.edc_batch, Vec::with_capacity(cap));
6320        edcstat::bump(&edcstat::BATCHES, 1);
6321        self.edc_tx
6322            .as_ref()
6323            .unwrap()
6324            .send(EdcMsg::Batch(jobs))
6325            .expect("worker alive");
6326    }
6327
6328    fn edc_intra_sync(&mut self) {
6329        if self.edc_tx.is_none() {
6330            self.edc_flush();
6331            return;
6332        }
6333        if self.edc_parked.is_some() {
6334            return; // already holding
6335        }
6336        // ORDER: drain the batch before taking the planes, or those jobs would
6337        // be applied to a context the parse thread is concurrently holding.
6338        self.edc_flush_batch();
6339        edcstat::bump(&edcstat::NEEDCTX, 1);
6340        self.edc_tx.as_ref().unwrap().send(EdcMsg::NeedCtx).expect("worker alive");
6341        let mut ctx = self.edc_ctx_rx.as_ref().unwrap().recv().expect("worker ctx");
6342        self.rec_y = std::mem::take(&mut ctx.rec_y);
6343        self.rec_u = std::mem::take(&mut ctx.rec_u);
6344        self.rec_v = std::mem::take(&mut ctx.rec_v);
6345        self.bak_y = std::mem::take(&mut ctx.bak_y);
6346        self.bak_u = std::mem::take(&mut ctx.bak_u);
6347        self.bak_v = std::mem::take(&mut ctx.bak_v);
6348        self.flt_rows = ctx.flt_rows;
6349        self.edc_parked = Some(ctx);
6350    }
6351
6352    /// Returns a held context to the worker (inverse of `edc_intra_sync`).
6353    /// NOTE (D10): this is called per-macroblock on the job paths, so it must
6354    /// NOT flush the batch — that would undo the batching. It is safe: the
6355    /// parked state is only ever entered through `edc_intra_sync`, which
6356    /// flushes before taking the planes, so nothing can be queued-but-unsent
6357    /// while the parse thread holds them.
6358    fn edc_giveback(&mut self) {
6359        if let Some(mut parked) = self.edc_parked.take() {
6360            parked.rec_y = std::mem::take(&mut self.rec_y);
6361            parked.rec_u = std::mem::take(&mut self.rec_u);
6362            parked.rec_v = std::mem::take(&mut self.rec_v);
6363            parked.bak_y = std::mem::take(&mut self.bak_y);
6364            parked.bak_u = std::mem::take(&mut self.bak_u);
6365            parked.bak_v = std::mem::take(&mut self.bak_v);
6366            parked.flt_rows = self.flt_rows;
6367            // Fail-soft: on the unwind path the worker may already be gone;
6368            // dropping the parked context is acceptable there (the planes are
6369            // lost, but the panic is being propagated anyway).
6370            let _ = self.edc_back_tx.as_ref().unwrap().send(parked);
6371        }
6372    }
6373
6374    /// Parse-side twin of the nnz/coded grid writes `add_inter_residual` does
6375    /// inline — the worker's port omits them (they are PARSE state: deblock
6376    /// derivation and the CAVLC nC contexts read them), so the threaded path
6377    /// commits them here, from the same parsed counts (their equality with the
6378    /// recon-side recount is the Part 8 nnz-threading brick's own invariant).
6379    fn edc_commit_nnz(&mut self, mbx: usize, mby: usize, t8: bool, nnzs: &[u8; 24], cbp_chroma: u32) {
6380        // BUILD THE RASTER LOCALLY, THEN COPY ROW-WISE. Both luma arms scattered
6381        // sixteen individually bounds-checked stores into the frame grid (the t8
6382        // arm through a 2x2-of-2x2 nest, the other through the z-order scan
6383        // table); chroma scattered eight more. Assembling the macroblock's own
6384        // 4x4 raster on the stack first turns that into four contiguous row
6385        // copies per plane.
6386        let (w4r, w2r) = (self.mb_w * 4, self.mb_w * 2);
6387        let mut raster = [0u8; 16];
6388        if t8 {
6389            for b8 in 0..4usize {
6390                let (b8x, b8y) = (b8 % 2, b8 / 2);
6391                let n = nnzs[b8 * 4];
6392                for sy in 0..2 {
6393                    for sx in 0..2 {
6394                        raster[(b8y * 2 + sy) * 4 + b8x * 2 + sx] = n;
6395                    }
6396                }
6397            }
6398        } else {
6399            for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
6400                raster[lby * 4 + lbx] = nnzs[blk];
6401            }
6402        }
6403        for by in 0..4usize {
6404            let a = (mby * 4 + by) * w4r + mbx * 4;
6405            self.nnz_y[a..a + 4].copy_from_slice(&raster[by * 4..by * 4 + 4]);
6406        }
6407        if cbp_chroma == 2 {
6408            for c in 0..2usize {
6409                let mut cr = [0u8; 4];
6410                for &(bx, by) in &CHROMA_4X4_SCAN_XY {
6411                    cr[by * 2 + bx] = nnzs[(16 + c * 4 + by * 2 + bx).min(23)];
6412                }
6413                for by in 0..2usize {
6414                    let a = (mby * 2 + by) * w2r + mbx * 2;
6415                    self.nnz_c[c][a..a + 2].copy_from_slice(&cr[by * 2..by * 2 + 2]);
6416                }
6417            }
6418        }
6419    }
6420
6421    /// Flush the entropy-decouple job queue: replay every deferred pixel job
6422    /// in parse order. Called before any intra macroblock (its reconstruction
6423    /// reads neighbour PIXELS), before row filtering, at B-branch entry, at
6424    /// slice end, and at `deblock()` as a backstop.
6425    /// Per-macroblock guard: the B arm calls this once per B MB — the empty
6426    /// test inlines, the drain body stays outlined.
6427    #[inline(always)]
6428    fn edc_flush(&mut self) {
6429        if self.edc_jobs.is_empty() {
6430            return;
6431        }
6432        self.edc_flush_slow();
6433    }
6434
6435    fn edc_flush_slow(&mut self) {
6436        let jobs = std::mem::take(&mut self.edc_jobs);
6437        // Band identity holds unweighted OR under identity weights (the x264
6438        // weightp=2 common case: a table in every P slice, identity outside fades).
6439        let band_ok = self.weights.is_none() || self.weights_id0;
6440        // Measurement: nonzero-MV skips sitting in same-row runs of >=2 EQUAL
6441        // MVs (the next band candidate). counted_until stops double-counting.
6442        let mut counted_until = 0usize;
6443        let mut i = 0usize;
6444        while i < jobs.len() {
6445            let j = &jobs[i];
6446            match j {
6447                EdcJob::Skip { mbx, mby, mv } => {
6448                    // mb_skip_run band coalescing: gather the maximal run of
6449                    // consecutive same-row (0,0)-skips and recon it as ONE
6450                    // band copy (weighted-P falls back — the copy identity
6451                    // only holds unweighted).
6452                    if *mv == (0, 0) && band_ok && !no_skipband() {
6453                        let (x0, y0) = (*mbx, *mby);
6454                        let mut n = 1usize;
6455                        while let Some(EdcJob::Skip { mbx: nx, mby: ny, mv: nmv }) = jobs.get(i + n) {
6456                            if *ny == y0 && *nx == x0 + n && *nmv == (0, 0) {
6457                                n += 1;
6458                            } else {
6459                                break;
6460                            }
6461                        }
6462                        self.recon_p_skip_band(x0, y0, n);
6463                        if double_recon() {
6464                            edcstat::bump(&edcstat::DOUBLED, n as u64);
6465                            self.recon_p_skip_band(x0, y0, n);
6466                        }
6467                        i += n;
6468                        continue;
6469                    }
6470                    edcstat::bump(&edcstat::SKIP_SINGLES, 1);
6471                    if *mv != (0, 0) && i >= counted_until && edcstat::on() {
6472                        let (x0, y0) = (*mbx, *mby);
6473                        let mut n2 = 1usize;
6474                        while let Some(EdcJob::Skip { mbx: nx, mby: ny, mv: nmv }) = jobs.get(i + n2) {
6475                            if *ny == y0 && *nx == x0 + n2 && nmv == mv { n2 += 1; } else { break; }
6476                        }
6477                        if n2 >= 2 {
6478                            let fp = mv.0 % 4 == 0 && mv.1 % 4 == 0;
6479                            edcstat::bump(if fp { &edcstat::PEQ_FP } else { &edcstat::PEQ_FRAC }, n2 as u64);
6480                        }
6481                        counted_until = i + n2;
6482                    }
6483                    self.recon_p_skip(*mbx, *mby, *mv);
6484                    if double_recon() {
6485                        edcstat::bump(&edcstat::DOUBLED, 1);
6486                        self.recon_p_skip(*mbx, *mby, *mv);
6487                    }
6488                }
6489                EdcJob::Inter(job) => {
6490                    self.recon_p_inter(job);
6491                    if double_recon() {
6492                        edcstat::bump(&edcstat::DOUBLED, 1);
6493                        self.recon_p_inter(job);
6494                    }
6495                }
6496                EdcJob::InterNoRes(job) => {
6497                    // D9b: do NOT to_full() — that memset ~2.5 KB of zeros per MB
6498                    // then walked add_inter_residual's all-zero path. MC + plane copy.
6499                    self.recon_p_inter_nores(job);
6500                    if double_recon() {
6501                        edcstat::bump(&edcstat::DOUBLED, 1);
6502                        self.recon_p_inter_nores(job);
6503                    }
6504                }
6505                // B jobs exist only in worker (MT) mode and are never queued
6506                // here — the single-thread seam keeps B inline. Not reachable
6507                // from any input (the push sites are gated on `edc_tx`).
6508                EdcJob::B(_) | EdcJob::BSkip { .. } => unreachable!("B jobs are worker-only"),
6509            }
6510            i += 1;
6511        }
6512        // Hand the (now empty) Vec back so its allocation is reused.
6513        self.edc_jobs = jobs;
6514        self.edc_jobs.clear();
6515    }
6516
6517    /// Reconstructs one CABAC P inter macroblock from its parse job — the
6518    /// pixel half of the entropy-decouple seam (docs/entropy-decouple-plan.md
6519    /// E1). Reads NOTHING from parse state except the frame grids this MB's
6520    /// parse already committed (its own block MVs/refs, re-gathered below —
6521    /// stable after commit) and the immutable DPB; called either inline
6522    /// (seam off / flush disabled) or in-order at a flush point. Byte-
6523    /// identical to the former inline block by construction: replay order
6524    /// equals inline order at every pixel-observable point (intra reads, row
6525    /// filtering) because flushes precede both.
6526    /// The P-inter reconstruction body, taking its inputs directly rather than
6527    /// through a `PInterJob`. Split out so the residual planes can be passed as
6528    /// `Option`s (absent == nothing was parsed) instead of forcing every caller
6529    /// to own a zeroed copy. NOTE: it re-gathers `gmv`/`gref` from the frame
6530    /// grids rather than reading the job's copies — those are for the worker,
6531    /// which must not touch the parse thread's grids.
6532    #[allow(clippy::too_many_arguments)]
6533    fn recon_p_inter_parts(
6534        &mut self,
6535        mbx: usize,
6536        mby: usize,
6537        qp: u8,
6538        cbp_chroma: u32,
6539        luma_scan: Option<&[[i32; 16]; 16]>,
6540        luma8: Option<&[[i32; 64]; 4]>,
6541        cdc: &[[i32; 4]; 2],
6542        cac: Option<&[[[i32; 16]; 4]; 2]>,
6543        nnzs: &[u8; 24],
6544    ) {
6545        let mbw = self.mb_w;
6546        // `add_inter_residual` (and anything under it) reads `self.cur_qp`,
6547        // which at FLUSH time belongs to a later macroblock — replay must
6548        // restore this MB's qp. The x264 corpus (near-constant QP) could not
6549        // see this; the encoder's delta-QP roundtrip stream caught it.
6550        let saved_qp = self.cur_qp;
6551        self.cur_qp = qp;
6552                    // ---- Recon: motion-comp (per 4×4 luma / co-located 2×2 chroma using the
6553                    // committed grid MV — the 6-tap/bilinear filter is per-output-pixel, so
6554                    // per-block MC is bit-identical to per-partition MC) + residual add via the
6555                    // SAME reconstruct_4x4 as intra, with the MC output as the prediction.
6556                    let w4r = mbw * 4;
6557                    let mut pred_y = [0u8; 256];
6558                    let mut c_pred = [[0u8; 64]; 2];
6559                    {
6560                        // MC-CALL COALESCING (side-by-side descent, dec target #2): the old
6561                        // loop paid 16 mc_luma(4×4) + 32 mc_chroma(2×2) per MB regardless of
6562                        // partitioning — 48 calls even for a single-MV 16×16 MB, and the
6563                        // per-call glue around 2.4M calls was ~40% of decoding real-world
6564                        // (x264) streams. The 6-tap/bilinear filters are per-output-pixel,
6565                        // so merging blocks with equal (mv, ref) into one wider MC call is
6566                        // BIT-IDENTICAL; the rect ladder mirrors the partition shapes.
6567                        let _ms = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMcStage);
6568                        let (rh16, cch) = (self.mb_h * 16, self.mb_h * 8);
6569                        let mut gmv = [(0i32, 0i32); 16];
6570                        let mut gref = [0usize; 16];
6571                        let _gg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MvGrid);
6572                        let nrefs = self.refs.len() - 1;
6573                        for by in 0..4usize {
6574                            // Row-contiguous: one slice copy for the MVs.
6575                            let row = (mby * 4 + by) * w4r + mbx * 4;
6576                            gmv[by * 4..by * 4 + 4].copy_from_slice(&self.mv_y[row..row + 4]);
6577                            let ridx = &self.ref_idx_y[row..row + 4];
6578                            for bx in 0..4usize {
6579                                // Per-block reference (multi-ref P): ref_idx_l0 committed to the
6580                                // grid. Clamp — a corrupt stream can over-range it (never panic).
6581                                gref[by * 4 + bx] = (ridx[bx].max(0) as usize).min(nrefs);
6582                            }
6583                        }
6584                        drop(_gg);
6585                        // All blocks of the rect (in 4×4-block units) match its top-left?
6586                        let rect_eq = |x4: usize, y4: usize, w4: usize, h4: usize| -> bool {
6587                            let t = y4 * 4 + x4;
6588                            (0..h4).all(|dy| {
6589                                (0..w4).all(|dx| {
6590                                    let b = ((y4 + dy) * 4 + (x4 + dx)) & 15;
6591                                    gmv[b] == gmv[t] && gref[b] == gref[t]
6592                                })
6593                            })
6594                        };
6595                        let refs = &self.refs;
6596                        let (cw, ccw) = (self.cw, self.ccw);
6597                        let mc_rect = |x4: usize,
6598                                           y4: usize,
6599                                           w4: usize,
6600                                           h4: usize,
6601                                           pred_y: &mut [u8; 256],
6602                                           c_pred: &mut [[u8; 64]; 2]| {
6603                            let b = y4 * 4 + x4;
6604                            let (mv, gr) = (gmv[b & 15], gref[b & 15]);
6605                            // `gref` holds a reference-list slot; a slot the list
6606                            // does not have means there is nothing to predict from,
6607                            // so the rect is left as it stands.
6608                            let Some(reference) = refs.get(gr) else { return };
6609                            let (w, h) = (w4 * 4, h4 * 4);
6610                            // A FULL-WIDTH rect (w == 16, so x4 == 0) occupies contiguous
6611                            // whole rows of `pred_y` — the MC output layout and the
6612                            // destination layout coincide, so MC writes the prediction
6613                            // buffer DIRECTLY. The staging copy exists only for narrow
6614                            // rects, whose rows really are strided in `pred_y`. This is
6615                            // the diagnosis's "stage-boundary materialization" tax paid
6616                            // by the dominant 16×16/16×8 shapes: 256 B of `t` zeroing
6617                            // plus a 256 B copy per rect, for nothing.
6618                            if w == 16 {
6619                                rusty_h264_common::inter::with_mc_scratch(|scr| rusty_h264_common::inter::mc_luma_padded_pre(scr, &*reference.luma_guard(reference.ch), reference.lstride(), crate::LPAD, cw, rh16, mbx * 16, mby * 16 + y4 * 4, w, h, mv.0, mv.1, &mut pred_y[y4 * 64..y4 * 64 + w * h]));
6620                            } else {
6621                                let mut t = [0u8; 256];
6622                                rusty_h264_common::inter::with_mc_scratch(|scr| rusty_h264_common::inter::mc_luma_padded_pre(scr, &*reference.luma_guard(reference.ch), reference.lstride(), crate::LPAD, cw, rh16, mbx * 16 + x4 * 4, mby * 16 + y4 * 4, w, h, mv.0, mv.1, &mut t[..w * h]));
6623                                let _pb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
6624                                for dy in 0..h {
6625                                    pred_y[(y4 * 4 + dy) * 16 + x4 * 4..][..w]
6626                                        .copy_from_slice(&t[dy * w..dy * w + w]);
6627                                }
6628                            }
6629                            let (cw4, ch4) = (w4 * 2, h4 * 2);
6630                            let nc = cw4 * ch4;
6631                            let (gu, gv) = (reference.chroma_guard(0, reference.ch), reference.chroma_guard(1, reference.ch));
6632                            // U+V paired: one setup serves both planes (see
6633                            // mc_chroma_padded_pair). Full-width coincidence:
6634                            // cw4 == 8 rows are contiguous in the 8-wide plane.
6635                            if cw4 == 8 {
6636                                let [cu, cv] = &mut *c_pred;
6637                                rusty_h264_common::inter::mc_chroma_padded_pair(&gu, &gv, reference.cstride(), crate::CPAD, ccw, cch, mbx * 8, mby * 8 + y4 * 2, cw4, ch4, mv.0, mv.1, &mut cu[y4 * 16..y4 * 16 + nc], &mut cv[y4 * 16..y4 * 16 + nc]);
6638                            } else {
6639                                let (mut tu, mut tv) = ([0u8; 64], [0u8; 64]);
6640                                rusty_h264_common::inter::mc_chroma_padded_pair(&gu, &gv, reference.cstride(), crate::CPAD, ccw, cch, mbx * 8 + x4 * 2, mby * 8 + y4 * 2, cw4, ch4, mv.0, mv.1, &mut tu[..nc], &mut tv[..nc]);
6641                                let _pb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
6642                                for (cc, tc) in [(0usize, &tu), (1, &tv)] {
6643                                    for dy in 0..ch4 {
6644                                        c_pred[cc][(y4 * 2 + dy) * 8 + x4 * 2..][..cw4]
6645                                            .copy_from_slice(&tc[dy * cw4..dy * cw4 + cw4]);
6646                                    }
6647                                }
6648                            }
6649                        };
6650                        if rect_eq(0, 0, 4, 4) {
6651                            mc_rect(0, 0, 4, 4, &mut pred_y, &mut c_pred);
6652                        } else if rect_eq(0, 0, 4, 2) && rect_eq(0, 2, 4, 2) {
6653                            mc_rect(0, 0, 4, 2, &mut pred_y, &mut c_pred);
6654                            mc_rect(0, 2, 4, 2, &mut pred_y, &mut c_pred);
6655                        } else if rect_eq(0, 0, 2, 4) && rect_eq(2, 0, 2, 4) {
6656                            mc_rect(0, 0, 2, 4, &mut pred_y, &mut c_pred);
6657                            mc_rect(2, 0, 2, 4, &mut pred_y, &mut c_pred);
6658                        } else {
6659                            for q in 0..4usize {
6660                                let (qx, qy) = ((q % 2) * 2, (q / 2) * 2);
6661                                if rect_eq(qx, qy, 2, 2) {
6662                                    mc_rect(qx, qy, 2, 2, &mut pred_y, &mut c_pred);
6663                                } else if rect_eq(qx, qy, 2, 1) && rect_eq(qx, qy + 1, 2, 1) {
6664                                    mc_rect(qx, qy, 2, 1, &mut pred_y, &mut c_pred);
6665                                    mc_rect(qx, qy + 1, 2, 1, &mut pred_y, &mut c_pred);
6666                                } else if rect_eq(qx, qy, 1, 2) && rect_eq(qx + 1, qy, 1, 2) {
6667                                    mc_rect(qx, qy, 1, 2, &mut pred_y, &mut c_pred);
6668                                    mc_rect(qx + 1, qy, 1, 2, &mut pred_y, &mut c_pred);
6669                                } else {
6670                                    for j in 0..4usize {
6671                                        mc_rect(qx + (j % 2), qy + (j / 2), 1, 1, &mut pred_y, &mut c_pred);
6672                                    }
6673                                }
6674                            }
6675                        }
6676                        // EXPLICIT WEIGHTED PREDICTION (spec 8.4.2.3). The CAVLC inter
6677                        // path weights each partition after MC; the MC-call-coalescing
6678                        // rewrite of this CABAC path lost it, and nothing caught that
6679                        // because the effect is invisible unless a stream actually
6680                        // carries non-default weights. x264's `weightp` DUPLICATES a
6681                        // reference and distinguishes the copy ONLY by its weights, so
6682                        // every macroblock picking the weighted index decoded unweighted
6683                        // -- a silent, accumulating luma drift.
6684                        //
6685                        // Applied per 4x4 block rather than per partition: the weight
6686                        // depends solely on the block's reference index, so the two are
6687                        // equivalent, and `gref` already holds it for every block
6688                        // regardless of which rect ladder rung ran.
6689                        if self.weights.is_some() {
6690                            for by in 0..4usize {
6691                                for bx in 0..4usize {
6692                                    let refi = gref[by * 4 + bx];
6693                                    self.weight_partition(
6694                                        &mut pred_y, &mut c_pred, 0, refi, bx * 4, by * 4, 4, 4,
6695                                    );
6696                                }
6697                            }
6698                        }
6699                    }
6700                    // Residual add — the SAME helper the B path uses (this inline
6701                    // copy was a duplicate; deduped when the zero-block fast path
6702                    // landed so both paths share it).
6703                    self.add_inter_residual(mbx, mby, &pred_y, &c_pred, luma_scan, luma8, cdc, cac, cbp_chroma, nnzs);
6704        self.cur_qp = saved_qp;
6705    }
6706
6707    /// Job-shaped entry point (EDC replay + the `double_recon` A/B knob).
6708    fn recon_p_inter(&mut self, j: &PInterJob) {
6709        self.recon_p_inter_parts(
6710            j.mbx,
6711            j.mby,
6712            j.qp,
6713            j.cbp_chroma,
6714            j.luma_scan.as_ref(),
6715            j.luma8.as_ref(),
6716            &j.cdc,
6717            j.cac.as_ref(),
6718            &j.nnzs,
6719        );
6720    }
6721
6722    /// D9b: P inter with `cbp == 0` — MC + plane copy, no coeff memset / residual walk.
6723    /// Byte-identical to `recon_p_inter` on a zero-residual job: prediction is the recon.
6724    fn recon_p_inter_nores(&mut self, j: &PInterNoResJob) {
6725        let w4r = self.mb_w * 4;
6726        // Parse-side nnz for single-thread EDC flush (MT commits earlier via edc_commit_nnz).
6727        // ROW FILLS. Both arms touch the same sixteen cells - four contiguous
6728        // per macroblock row - and wrote them one indexed store at a time (the
6729        // t8 arm through a 2x2-of-2x2 nest, the other through the z-order scan).
6730        for by in 0..4usize {
6731            let r = (j.mby * 4 + by) * w4r + j.mbx * 4;
6732            self.nnz_y[r..r + 4].fill(0);
6733            if j.t8 {
6734                self.coded_y[r..r + 4].fill(true);
6735            }
6736        }
6737        let mut pred_y = [0u8; 256];
6738        let mut c_pred = [[0u8; 64]; 2];
6739        // `self.refs.len() - 1` was re-loaded on every one of the sixteen
6740        // iterations; it is a per-macroblock invariant. Written once, not
6741        // zeroed-then-filled.
6742        let nrefs = self.refs.len() - 1;
6743        let gref: [usize; 16] = core::array::from_fn(|k| (j.gref[k] as usize).min(nrefs));
6744        coalesce_p_inter_mc(
6745            &self.refs,
6746            self.cw,
6747            self.ccw,
6748            self.mb_h,
6749            j.mbx,
6750            j.mby,
6751            &j.gmv,
6752            &gref,
6753            &mut pred_y,
6754            &mut c_pred,
6755        );
6756        // The identity early-out lives INSIDE `weight_partition`, so an x264
6757        // stream - which carries a pred_weight_table in EVERY P slice, identity
6758        // outside fades - still paid sixteen calls per macroblock to be told
6759        // there was nothing to do. Hoisted to one test.
6760        if self.weights.is_some() && !self.weights_l0id {
6761            for by in 0..4usize {
6762                for bx in 0..4usize {
6763                    let refi = gref[by * 4 + bx];
6764                    self.weight_partition(&mut pred_y, &mut c_pred, 0, refi, bx * 4, by * 4, 4, 4);
6765                }
6766            }
6767        }
6768        // ONE span per plane: the destination is a stack of contiguous runs a
6769        // fixed stride apart, so the rows share a single bounds check instead of
6770        // one each (16 + 8 + 8 of them).
6771        let (cw, ccw) = (self.cw, self.ccw);
6772        let ybase = (j.mby * 16) * cw + j.mbx * 16;
6773        let ywin = &mut self.rec_y[ybase..ybase + 15 * cw + 16];
6774        for dy in 0..16 {
6775            ywin[dy * cw..dy * cw + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
6776        }
6777        let cbase = (j.mby * 8) * ccw + j.mbx * 8;
6778        for c in 0..2 {
6779            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
6780            let cwin = &mut plane[cbase..cbase + 7 * ccw + 8];
6781            for dy in 0..8 {
6782                cwin[dy * ccw..dy * ccw + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
6783            }
6784        }
6785    }
6786
6787    fn decode_p_skip(&mut self, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
6788        self.route_skip_mbs += 1;
6789        self.wait_refs_for_mb(mb_y);
6790        // DEBLOCK CLASS: a P_Skip macroblock carries no coefficients and one
6791        // (ref, mv) for all 16 blocks, so every internal boundary strength is 0 by
6792        // §8.7.2.1 and the loop filter needs 9 block loads instead of 24. This is
6793        // the single highest-value classification: the MB-kind census measures Skip
6794        // at 36.4% (CAVLC) / 65.0% (main) / 57.8% (high) of real x264 corpora.
6795        // Written HERE because both the CAVLC and the CABAC slice loops funnel
6796        // through this one function.
6797        //
6798        // Deliberately NOT done for `B_Skip` — its motion is direct-derived and can
6799        // differ per 4×4 sub-block, so its internal edges can legally reach
6800        // strength 1. B_Skip stays UNSET and takes the blind path.
6801        // (kind commit moved into the span/immediate arms below.)
6802        // P_Skip always references index 0 (the most recent picture). Borrow it —
6803        // a full-frame `.cloned()` here was ~86% of total decode time (one ~3 MB
6804        // plane copy per skip MB, thousands per frame).
6805        if self.refs.is_empty() {
6806            return Err(MbError::Unsupported("P_Skip without reference"));
6807        }
6808        let addr = mb_y * self.mb_w + mb_x;
6809        // mb_x == 0: the left neighbor is off-frame, so §8.4.1.1's
6810        // unavailability rule forces (0,0) with no gather at all.
6811        let mv = if (mb_x == 0 || self.skip_zero_next == addr) && !self.k_no_runmv {
6812            edcstat::bump(&edcstat::SKIPMV_FORCED, 1);
6813            (0, 0)
6814        } else {
6815            // The gather reads the grids; the pending spans cannot contain
6816            // this MB's left (a non-forced skip means the previous MB was not
6817            // a (0,0) committer) — flush both before deriving.
6818            self.span_flush();
6819            edcstat::bump(&edcstat::SKIPMV_DERIVED, 1);
6820            self.skip_mv(mb_x, mb_y)
6821        };
6822        self.skip_zero_next = if mv == (0, 0) { addr + 1 } else { usize::MAX };
6823        // Grid commits are PARSE state — (0,0) skips defer them into the P
6824        // span (the deferral constants); nonzero-MV skips commit immediately.
6825        if mv == (0, 0) {
6826            // Recon defers too when the band identity holds (1T, no/identity
6827            // weights) — the span flush band-copies instead of queueing jobs.
6828            let recon = self.edc_tx.is_none() && (self.weights.is_none() || self.weights_id0);
6829            self.pz_push(mb_x, mb_y, recon);
6830            if recon {
6831                return Ok(());
6832            }
6833        } else {
6834            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
6835            if let Some(p) = self.mb_kind.get_mut(mb_y * self.mb_w + mb_x) {
6836                *p = rusty_h264_common::deblock::MB_KIND_SKIP;
6837            }
6838            self.set_mb_mv(mb_x, mb_y, mv, true, 0);
6839            let w4 = self.mb_w * 4;
6840            for dy in 0..4 {
6841                let a = (mb_y * 4 + dy) * w4 + mb_x * 4;
6842                self.coded_y[a..a + 4].fill(true);
6843                self.modes_y[a..a + 4].fill(2);
6844            }
6845        }
6846        if self.edc_tx.is_some() {
6847            self.edc_giveback();
6848            self.edc_send_job(EdcJob::Skip { mbx: mb_x, mby: mb_y, mv });
6849            return Ok(());
6850        }
6851        if self.edc_active {
6852            self.edc_jobs.push(EdcJob::Skip { mbx: mb_x, mby: mb_y, mv });
6853            return Ok(());
6854        }
6855        self.recon_p_skip(mb_x, mb_y, mv);
6856        if double_recon() {
6857            self.recon_p_skip(mb_x, mb_y, mv);
6858        }
6859        Ok(())
6860    }
6861
6862    /// Pixel half of P_Skip (see the E1 seam note on `recon_p_inter`).
6863    /// Run-coalesced P_Skip reconstruction: `n` consecutive skip MBs on one
6864    /// row, all with `mv == (0,0)` and NO weighted prediction. At full-pel
6865    /// zero motion, MC is the identity read of `refs[0]` — so the recon is a
6866    /// straight band copy from the padded reference plane, no staging, no MC
6867    /// calls. Byte-identical to `n` calls of `recon_p_skip` by construction
6868    /// (mc_luma_padded/mc_chroma_padded at mv 0 return exactly these bytes).
6869    ///
6870    /// The (0,0) run theorem that makes ONE derivation cover the run: once a
6871    /// skip MB commits (ref 0, mv (0,0)), every later MB of the run derives
6872    /// (0,0) too — its left neighbour (or a row-start's missing neighbour)
6873    /// triggers §8.4.1.1's zero-MV rule in `skip_mv`.
6874    fn recon_p_skip_band(&mut self, mbx0: usize, mby: usize, n: usize) {
6875        let w = n * 16;
6876        let Some(rf0) = self.refs.first() else { return };
6877        let lst = rf0.lstride();
6878        {
6879            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
6880            let ly = rf0.luma_guard(rf0.ch);
6881            for dy in 0..16 {
6882                let y = mby * 16 + dy;
6883                let src = (y + crate::LPAD) * lst + crate::LPAD + mbx0 * 16;
6884                let dst = y * self.cw + mbx0 * 16;
6885                // ARMOR (fuzz find, inline-execution.md 11.11): on a MALFORMED
6886                // stream a reference can carry geometry that does not match the
6887                // open picture (mutated parameter sets / truncated ref planes),
6888                // so the in-frame band's ref window can leave the guard slice.
6889                // The band REFUSES rather than panics; a conformant stream
6890                // cannot take this arm (repro: scratchpad repro.264, plane 6400
6891                // vs index 6416). Output on malformed input is unspecified —
6892                // absence of panic is the contract the fuzz gate enforces.
6893                let (Some(d), Some(sr)) = (
6894                    self.rec_y.get_mut(dst..dst + w),
6895                    ly.get(src..src + w),
6896                ) else {
6897                    return;
6898                };
6899                d.copy_from_slice(sr);
6900            }
6901        }
6902        let cst = rf0.cstride();
6903        let wc = n * 8;
6904        for c in 0..2 {
6905            let rc = rf0.chroma_guard(c, rf0.ch);
6906            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
6907            for dy in 0..8 {
6908                let y = mby * 8 + dy;
6909                let src = (y + crate::CPAD) * cst + crate::CPAD + mbx0 * 8;
6910                let dst = y * self.ccw + mbx0 * 8;
6911                // Same armor as the luma band above.
6912                let (Some(d), Some(sr)) = (plane.get_mut(dst..dst + wc), rc.get(src..src + wc))
6913                else {
6914                    return;
6915                };
6916                d.copy_from_slice(sr);
6917            }
6918        }
6919        edcstat::bump(&edcstat::SKIPBAND_MBS, n as u64);
6920        edcstat::bump(&edcstat::SKIPBAND_RUNS, 1);
6921    }
6922
6923    /// B_Skip zero-bi recon: rec = avg(ref0, ref1') row-wise, straight from the
6924    /// padded planes. Byte-identical to b_mc at mv (0,0)/(0,0): full-pel MC is
6925    /// an identity read and the unweighted bi blend is exactly (a+b+1)>>1 —
6926    /// which the compiler emits as pavgb over the sliced zips, same as b_mc's
6927    /// blend site.
6928    fn recon_b_skip_zero_bi(&mut self, mbx: usize, mby: usize, r0i: usize, r1i: usize) {
6929        let (Some(rf0), Some(rf1)) = (self.refs.get(r0i), self.refs1.get(r1i)) else {
6930            return;
6931        };
6932        let (l0st, l1st) = (rf0.lstride(), rf1.lstride());
6933        {
6934            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
6935            let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
6936            for dy in 0..16 {
6937                let y = mby * 16 + dy;
6938                let s0 = (y + crate::LPAD) * l0st + crate::LPAD + mbx * 16;
6939                let s1 = (y + crate::LPAD) * l1st + crate::LPAD + mbx * 16;
6940                let d = y * self.cw + mbx * 16;
6941                for ((dst, a), b) in self.rec_y[d..d + 16].iter_mut().zip(&ly0[s0..s0 + 16]).zip(&ly1[s1..s1 + 16]) {
6942                    *dst = ((*a as u16 + *b as u16 + 1) >> 1) as u8;
6943                }
6944            }
6945        }
6946        let (c0st, c1st) = (rf0.cstride(), rf1.cstride());
6947        for c in 0..2 {
6948            let rc0 = rf0.chroma_guard(c, rf0.ch);
6949            let rc1 = rf1.chroma_guard(c, rf1.ch);
6950            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
6951            for dy in 0..8 {
6952                let y = mby * 8 + dy;
6953                let s0 = (y + crate::CPAD) * c0st + crate::CPAD + mbx * 8;
6954                let s1 = (y + crate::CPAD) * c1st + crate::CPAD + mbx * 8;
6955                let d = y * self.ccw + mbx * 8;
6956                for ((dst, a), b) in plane[d..d + 8].iter_mut().zip(&rc0[s0..s0 + 8]).zip(&rc1[s1..s1 + 8]) {
6957                    *dst = ((*a as u16 + *b as u16 + 1) >> 1) as u8;
6958                }
6959            }
6960        }
6961    }
6962
6963    /// Full-pel P_Skip single: rec window = ref\[0\] window at an integer offset.
6964    /// Returns false (touching nothing) when any window leaves the padded
6965    /// plane — mc's edge clamping takes over there. Luma needs mv%4==0;
6966    /// chroma needs mv%8==0 (its frac is mv&7), so odd full-pel MVs copy luma
6967    /// and interpolate chroma.
6968    fn recon_p_skip_fullpel(&mut self, mbx: usize, mby: usize, mv: (i32, i32)) -> bool {
6969        let (dx, dy) = ((mv.0 / 4) as isize, (mv.1 / 4) as isize);
6970        let Some(rf0) = self.refs.first() else { return false };
6971        let lst = rf0.lstride();
6972        let lpad = crate::LPAD as isize;
6973        let cx = mbx as isize * 16 + dx + lpad;
6974        let cy = mby as isize * 16 + dy + lpad;
6975        {
6976            let ly = rf0.luma_guard(rf0.ch);
6977            let lrows = rf0.lrows() as isize;
6978            if cx < 0 || cx + 16 > lst as isize || cy < 0 || cy + 16 > lrows {
6979                return false;
6980            }
6981            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
6982            // ONE span each side: sixteen rows a fixed stride apart, so the
6983            // source and destination checks are paid once instead of per row.
6984            let (cw, sb) = (self.cw, cy as usize * lst + cx as usize);
6985            let db = (mby * 16) * cw + mbx * 16;
6986            let sw = &ly[sb..sb + 15 * lst + 16];
6987            let dw = &mut self.rec_y[db..db + 15 * cw + 16];
6988            for r in 0..16 {
6989                dw[r * cw..r * cw + 16].copy_from_slice(&sw[r * lst..r * lst + 16]);
6990            }
6991        }
6992        let cst = rf0.cstride();
6993        let cpad = crate::CPAD as isize;
6994        if mv.0 % 8 == 0 && mv.1 % 8 == 0 {
6995            let ccx = mbx as isize * 8 + (mv.0 / 8) as isize + cpad;
6996            let ccy = mby as isize * 8 + (mv.1 / 8) as isize + cpad;
6997            let crows = rf0.crows() as isize;
6998            if ccx >= 0 && ccx + 8 <= cst as isize && ccy >= 0 && ccy + 8 <= crows {
6999                for c in 0..2 {
7000                    let rc = rf0.chroma_guard(c, rf0.ch);
7001                    let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
7002                    let (ccw, sb) = (self.ccw, ccy as usize * cst + ccx as usize);
7003                    let db = (mby * 8) * ccw + mbx * 8;
7004                    let sw = &rc[sb..sb + 7 * cst + 8];
7005                    let dw = &mut plane[db..db + 7 * ccw + 8];
7006                    for r in 0..8 {
7007                        dw[r * ccw..r * ccw + 8].copy_from_slice(&sw[r * cst..r * cst + 8]);
7008                    }
7009                }
7010                edcstat::bump(&edcstat::SKIP_FP_FULL, 1);
7011                return true;
7012            }
7013        }
7014        // Odd full-pel (or chroma window out of pad): luma is already copied,
7015        // chroma still interpolates — identical to the mc path's chroma half.
7016        let cch = self.mb_h * 8;
7017        for c in 0..2 {
7018            let mut pc = [0u8; 64];
7019            let rc = if c == 0 { &*rf0.chroma_guard(0, rf0.ch) } else { &*rf0.chroma_guard(1, rf0.ch) };
7020            mc_chroma_padded(rc, cst, crate::CPAD, self.ccw, cch, mbx * 8, mby * 8, 8, 8, mv.0, mv.1, &mut pc);
7021            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
7022            for r in 0..8 {
7023                let d = (mby * 8 + r) * self.ccw + mbx * 8;
7024                plane[d..d + 8].copy_from_slice(&pc[r * 8..r * 8 + 8]);
7025            }
7026        }
7027        edcstat::bump(&edcstat::SKIP_FP_LUMA, 1);
7028        true
7029    }
7030
7031    /// B_Skip full-pel recon with per-list integer offsets: uni = offset row
7032    /// copy, bi = offset row average ((a+b+1)>>1). Luma always copies here
7033    /// (caller guarantees mv%4==0); chroma copies at mv%8==0 and otherwise
7034    /// interpolates via b_mc_chroma — identical to the b_mc path's chroma
7035    /// half with no implicit weights. Returns false untouched when a luma
7036    /// window leaves the padded plane.
7037    fn recon_b_skip_fp(&mut self, mbx: usize, mby: usize, r0: Option<usize>, r1: Option<usize>, mv0: (i32, i32), mv1: (i32, i32)) -> bool {
7038        let lpad = crate::LPAD as isize;
7039        // (start-row, start-col, stride, rows) of each active list's luma window.
7040        let win = |rf: &crate::RefFrame, mv: (i32, i32)| -> Option<(usize, usize)> {
7041            let lst = rf.lstride();
7042            let cx = mbx as isize * 16 + (mv.0 / 4) as isize + lpad;
7043            let cy = mby as isize * 16 + (mv.1 / 4) as isize + lpad;
7044            let lrows = rf.lrows() as isize;
7045            if cx < 0 || cx + 16 > lst as isize || cy < 0 || cy + 16 > lrows {
7046                None
7047            } else {
7048                Some((cy as usize * lst + cx as usize, lst))
7049            }
7050        };
7051        let w0 = match r0 {
7052            Some(i) => match self.refs.get(i).and_then(|rr| win(rr, mv0)) {
7053                Some(w) => Some(w),
7054                None => return false,
7055            },
7056            None => None,
7057        };
7058        let w1 = match r1 {
7059            Some(i) => match self.refs1.get(i).and_then(|rr| win(rr, mv1)) {
7060                Some(w) => Some(w),
7061                None => return false,
7062            },
7063            None => None,
7064        };
7065        {
7066            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
7067            match (w0, w1) {
7068                (Some((s0, l0)), Some((s1, l1))) => {
7069                    let Some(rf0) = r0.and_then(|i| self.refs.get(i)) else { return false };
7070                    // Same shape as the `rf0` line above: `w1` is `Some` only when
7071                    // `r1` is `Some(i)` AND `refs1.get(i)` returned a frame, so the
7072                    // `else` is unreachable -- but it is a REFUSAL, not a panic, and
7073                    // `recon_b_skip_fp` already answers `false` for every case it
7074                    // cannot fast-path. Indexing a `Vec` with an unwrapped `Option`
7075                    // spent a discriminant test, an `unwrap_failed` call and a bounds
7076                    // check to restate an invariant the caller had already proven.
7077                    let Some(rf1) = r1.and_then(|i| self.refs1.get(i)) else { return false };
7078                    let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
7079                    for r in 0..16 {
7080                        let d = (mby * 16 + r) * self.cw + mbx * 16;
7081                        let (a, b) = (&ly0[s0 + r * l0..s0 + r * l0 + 16], &ly1[s1 + r * l1..s1 + r * l1 + 16]);
7082                        for ((dst, p), q) in self.rec_y[d..d + 16].iter_mut().zip(a).zip(b) {
7083                            *dst = ((*p as u16 + *q as u16 + 1) >> 1) as u8;
7084                        }
7085                    }
7086                }
7087                (Some((s0, l0)), None) => {
7088                    let Some(rf0) = r0.and_then(|i| self.refs.get(i)) else { return false };
7089                    let ly = rf0.luma_guard(rf0.ch);
7090                    for r in 0..16 {
7091                        let d = (mby * 16 + r) * self.cw + mbx * 16;
7092                        self.rec_y[d..d + 16].copy_from_slice(&ly[s0 + r * l0..s0 + r * l0 + 16]);
7093                    }
7094                }
7095                (None, Some((s1, l1))) => {
7096                    // Same shape as the `rf0` line above: `w1` is `Some` only when
7097                    // `r1` is `Some(i)` AND `refs1.get(i)` returned a frame, so the
7098                    // `else` is unreachable -- but it is a REFUSAL, not a panic, and
7099                    // `recon_b_skip_fp` already answers `false` for every case it
7100                    // cannot fast-path. Indexing a `Vec` with an unwrapped `Option`
7101                    // spent a discriminant test, an `unwrap_failed` call and a bounds
7102                    // check to restate an invariant the caller had already proven.
7103                    let Some(rf1) = r1.and_then(|i| self.refs1.get(i)) else { return false };
7104                    let ly = rf1.luma_guard(rf1.ch);
7105                    for r in 0..16 {
7106                        let d = (mby * 16 + r) * self.cw + mbx * 16;
7107                        self.rec_y[d..d + 16].copy_from_slice(&ly[s1 + r * l1..s1 + r * l1 + 16]);
7108                    }
7109                }
7110                (None, None) => unreachable!("caller guarantees an active list"),
7111            }
7112        }
7113        // Chroma: interpolating half — route through b_mc_chroma into a stage
7114        // (weights None: the caller only enters at implicit None/(32,32), and
7115        // (32,32) IS the plain average b_mc_chroma computes for None).
7116        let cch = self.mb_h * 8;
7117        let mut c_pred = [[0u8; 64]; 2];
7118        let (ri0, ri1) = (r0.map_or(-1, |i| i as i32), r1.map_or(-1, |i| i as i32));
7119        self.b_mc_chroma(mbx, mby, 0, 0, 16, 16, ri0, mv0, ri1, mv1, &mut c_pred, None, cch);
7120        for c in 0..2 {
7121            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
7122            for r in 0..8 {
7123                let d = (mby * 8 + r) * self.ccw + mbx * 8;
7124                plane[d..d + 8].copy_from_slice(&c_pred[c][r * 8..r * 8 + 8]);
7125            }
7126        }
7127        true
7128    }
7129
7130    fn recon_p_skip(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32)) {
7131        // Full-pel + identity/no weights: MC is a pure offset read, so copy the
7132        // ref window straight into rec — no pred staging, no weight pass. The
7133        // window must sit inside the PADDED plane (outside it, mc's clamping
7134        // differs from a raw offset read, so fall through).
7135        if (self.weights.is_none() || self.weights_id0)
7136            && mv.0 % 4 == 0
7137            && mv.1 % 4 == 0
7138            && !no_skipfp()
7139            && self.recon_p_skip_fullpel(mb_x, mb_y, mv)
7140        {
7141            return;
7142        }
7143        let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
7144
7145        let mut pred = [0u8; 256];
7146        let Some(rf0) = self.refs.first() else { return };
7147        mc_luma_padded(&*rf0.luma_guard(rf0.ch), rf0.lstride(), crate::LPAD, self.cw, ch, mb_x * 16, mb_y * 16, 16, 16, mv.0, mv.1, &mut pred);
7148        if let Some(wt) = &self.weights {
7149            if !self.weights_id0 || no_skipfp() {
7150                for p in pred.iter_mut() {
7151                    *p = wt.apply_luma(*p, 0, 0);
7152                }
7153            }
7154        }
7155        {
7156            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
7157            // ONE span for the sixteen rows (see `recon_p_inter_nores`).
7158            let (cw, base) = (self.cw, (mb_y * 16) * self.cw + mb_x * 16);
7159            let win = &mut self.rec_y[base..base + 15 * cw + 16];
7160            for dy in 0..16 {
7161                win[dy * cw..dy * cw + 16].copy_from_slice(&pred[dy * 16..dy * 16 + 16]);
7162            }
7163        }
7164        let (mut pu, mut pv) = ([0u8; 64], [0u8; 64]);
7165        {
7166            let Some(rf0) = self.refs.first() else { return };
7167            let (gu, gv) = (rf0.chroma_guard(0, rf0.ch), rf0.chroma_guard(1, rf0.ch));
7168            rusty_h264_common::inter::mc_chroma_padded_pair(&gu, &gv, rf0.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8, mb_y * 8, 8, 8, mv.0, mv.1, &mut pu, &mut pv);
7169        }
7170        if let Some(wt) = &self.weights {
7171            if !self.weights_id0 || no_skipfp() {
7172                for p in pu.iter_mut() {
7173                    *p = wt.apply_chroma(*p, 0, 0, 0);
7174                }
7175                for p in pv.iter_mut() {
7176                    *p = wt.apply_chroma(*p, 0, 0, 1);
7177                }
7178            }
7179        }
7180        for (pc, plane) in [(&pu, &mut self.rec_u), (&pv, &mut self.rec_v)] {
7181            for dy in 0..8 {
7182                let d = (mb_y * 8 + dy) * self.ccw + mb_x * 8;
7183                plane[d..d + 8].copy_from_slice(&pc[dy * 8..dy * 8 + 8]);
7184            }
7185        }
7186    }
7187
7188    /// Predicted `Intra_4x4` mode for the block at absolute coords `(bx, by)`.
7189    /// If either the left or top neighbor is outside the frame or in another
7190    /// slice, the prediction is DC (mode 2) (spec §8.3.1.1).
7191    /// `predict_i4_mode` with the MACROBLOCK-level availability already
7192    /// resolved by the caller (`top_ok` / `left_ok`).
7193    ///
7194    /// Twelve of a macroblock's sixteen blocks have BOTH neighbours inside the
7195    /// same macroblock, so their slice test is a foregone conclusion - yet the
7196    /// general form recomputed two `nbr_in_slice` products and two
7197    /// `intra_nbr_ok` tests for every one of them, ~1M times on all-intra
7198    /// content.
7199    ///
7200    /// `constrained_intra` is excluded deliberately: there the general form
7201    /// tests THIS macroblock's own `inter_y` cells for interior blocks, which
7202    /// the macroblock-level flags do not model, so that case defers unchanged.
7203    #[inline]
7204    fn predict_i4_mode_fast(
7205        &self,
7206        bx: usize,
7207        by: usize,
7208        lbx: usize,
7209        lby: usize,
7210        top_ok: bool,
7211        left_ok: bool,
7212    ) -> u8 {
7213        if self.constrained_intra {
7214            return self.predict_i4_mode(bx, by);
7215        }
7216        if !((lbx > 0 || left_ok) && (lby > 0 || top_ok)) {
7217            return 2;
7218        }
7219        let w4 = self.mb_w * 4;
7220        // Fallible reads: two checks on the same grid at two indexes. `2` is DC,
7221        // which is exactly what every unavailable-neighbour path above already
7222        // returns, so the fallback is the function's own existing semantics.
7223        let l = self.modes_y.get(by * w4 + (bx - 1)).copied().unwrap_or(2);
7224        let t = self.modes_y.get((by - 1) * w4 + bx).copied().unwrap_or(2);
7225        l.min(t)
7226    }
7227
7228    fn predict_i4_mode(&self, bx: usize, by: usize) -> u8 {
7229        if bx == 0 || by == 0 {
7230            return 2;
7231        }
7232        // Left neighbor block (bx-1,by); top neighbor block (bx,by-1). A neighbor
7233        // in another slice — or, under constrained_intra, an inter neighbor — is
7234        // unavailable, forcing the predicted mode to DC.
7235        if !self.nbr_in_slice((bx - 1) / 4, by / 4)
7236            || !self.nbr_in_slice(bx / 4, (by - 1) / 4)
7237            || !self.intra_nbr_ok(bx - 1, by)
7238            || !self.intra_nbr_ok(bx, by - 1)
7239        {
7240            return 2;
7241        }
7242        let w4 = self.mb_w * 4;
7243        // Fallible reads: two checks on the same grid at two indexes. `2` is DC,
7244        // which is exactly what every unavailable-neighbour path above already
7245        // returns, so the fallback is the function's own existing semantics.
7246        let l = self.modes_y.get(by * w4 + (bx - 1)).copied().unwrap_or(2);
7247        let t = self.modes_y.get((by - 1) * w4 + bx).copied().unwrap_or(2);
7248        l.min(t)
7249    }
7250
7251    /// Gathers 4×4 luma intra neighbors at pixel `(px, py)` from `rec_y`.
7252    fn gather_i4(
7253        &self,
7254        px: usize,
7255        py: usize,
7256        avail_top: bool,
7257        avail_left: bool,
7258        bx: usize,
7259        by: usize,
7260    ) -> ([u8; 8], [u8; 4], u8) {
7261        let (cw, w4) = (self.cw, self.mb_w * 4);
7262        let mut top = [0u8; 8];
7263        let mut left = [0u8; 4];
7264        let mut corner = 0;
7265        if avail_top {
7266            // Row-slice loads: the bak-vs-rec source branch runs once per row
7267            // segment instead of once per PIXEL (top_y_px paid it 8 times).
7268            top[..4].copy_from_slice(self.top_y_row(py, px, 4));
7269            let tr_avail = bx + 1 < w4
7270                && self.coded_y.get((by - 1) * w4 + (bx + 1)).copied().unwrap_or(false)
7271                && self.nbr_in_slice((bx + 1) / 4, (by - 1) / 4)
7272                && self.intra_nbr_ok(bx + 1, by - 1);
7273            if tr_avail {
7274                top[4..8].copy_from_slice(self.top_y_row(py, px + 4, 4));
7275            } else {
7276                let t3 = top[3];
7277                top[4..8].fill(t3);
7278            }
7279        }
7280        if avail_left {
7281            // STRIDED WALK. The span form this replaces claimed `i * cw <= 3 * cw`
7282            // was provable against the span's length; it is not — the same shape
7283            // left five live checks on `recon_i16_luma`'s sixteen-sample column.
7284            // `step_by` does the stride with no index, and `zip` bounds the count.
7285            let base = py * cw + px - 1;
7286            for (s, &v) in left.iter_mut().zip(self.rec_y[base..].iter().step_by(cw)) {
7287                *s = v;
7288            }
7289        }
7290        // The above-left corner has its own availability (block D); under
7291        // constrained_intra it is gone if that block is inter.
7292        if avail_top && avail_left && self.intra_nbr_ok(bx - 1, by - 1) {
7293            corner = self.top_y_px(py, px - 1);
7294        }
7295        (top, left, corner)
7296    }
7297
7298    /// Reconstructs an `I_PCM` macroblock: byte-aligned raw 8-bit samples, no
7299    /// prediction/transform/quant (spec §7.3.5, §8.3.5).
7300    fn decode_ipcm(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
7301        r.align_to_byte()?;
7302        // ROW SLICES throughout. A PCM macroblock wrote 384 samples one at a
7303        // time, each a separately bounds-checked index into a whole plane, and
7304        // then 104 more scattered context stores below. Every extent here is a
7305        // literal (16, 8, 4, 2), so one slice per row makes the inner index
7306        // provable and the stores become a straight walk.
7307        let (lx, ly) = (mb_x * 16, mb_y * 16);
7308        for dy in 0..16 {
7309            let row = &mut self.rec_y[(ly + dy) * self.cw + lx..][..16];
7310            for px in row.iter_mut() {
7311                *px = r.read_bits(8)? as u8;
7312            }
7313        }
7314        let (cx, cy) = (mb_x * 8, mb_y * 8);
7315        let ccw = self.ccw;
7316        for plane in [&mut self.rec_u, &mut self.rec_v] {
7317            for dy in 0..8 {
7318                let row = &mut plane[(cy + dy) * ccw + cx..][..8];
7319                for px in row.iter_mut() {
7320                    *px = r.read_bits(8)? as u8;
7321                }
7322            }
7323        }
7324        // Neighbor context: an I_PCM block contributes TotalCoeff = 16, counts as
7325        // intra with DC mode for prediction, and has no motion (§9.2.1, §8.3.1.2.2).
7326        let (w4, w2) = (self.mb_w * 4, self.mb_w * 2);
7327        // The scan order is irrelevant when every written value is a constant:
7328        // the sixteen `LUMA_4X4_SCAN_XY` positions are exactly the macroblock's
7329        // 4x4 rectangle, so four row fills per array cover them.
7330        for ry in 0..4 {
7331            let base = (mb_y * 4 + ry) * w4 + mb_x * 4;
7332            self.nnz_y[base..][..4].fill(16);
7333            self.modes_y[base..][..4].fill(2);
7334            self.coded_y[base..][..4].fill(true);
7335            self.inter_y[base..][..4].fill(false);
7336            self.ref_idx_y[base..][..4].fill(-1);
7337            self.mv_y[base..][..4].fill((0, 0));
7338        }
7339        for c in 0..2 {
7340            for by in 0..2 {
7341                self.nnz_c[c][(mb_y * 2 + by) * w2 + mb_x * 2..][..2].fill(16);
7342            }
7343        }
7344        Ok(())
7345    }
7346
7347    fn decode_i4x4(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
7348        let vt = vlc_tables();
7349        let w4 = self.mb_w * 4;
7350
7351        // intra4x4 mode signalling
7352        let mut modes = [2u8; 16]; // raster [lby*4+lbx]
7353        for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
7354            let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
7355            let predicted = self.predict_i4_mode(bx, by);
7356            let actual = if r.read_bit()? {
7357                predicted
7358            } else {
7359                let rem = r.read_bits(3)? as u8;
7360                if rem < predicted {
7361                    rem
7362                } else {
7363                    rem + 1
7364                }
7365            };
7366            if let Some(p) = self.modes_y.get_mut(by * w4 + bx) {
7367                *p = actual;
7368            }
7369            modes[(lby & 3) * 4 + (lbx & 3)] = actual;
7370        }
7371
7372        let chroma_mode = r.read_ue()? as u8;
7373        let cbp = read_cbp_intra(r)?;
7374        let cbp_luma = cbp & 15;
7375        let cbp_chroma = cbp >> 4;
7376        if cbp != 0 {
7377            self.step_qp(r.read_se()?)?;
7378        }
7379        let qp = self.cur_qp;
7380
7381        // luma residuals + serial reconstruction. Cross-MB neighbors are only
7382        // available when the adjacent macroblock is in this slice (and, under
7383        // constrained_intra_pred, is itself intra-coded).
7384        let top_mb_avail = mb_y > 0
7385            && self.nbr_in_slice(mb_x, mb_y - 1)
7386            && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
7387        let left_mb_avail = mb_x > 0
7388            && self.nbr_in_slice(mb_x - 1, mb_y)
7389            && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
7390        self.nnz_cache_load(mb_x, mb_y);
7391        let mut nnz_raster = [0u8; 16];
7392        for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
7393            let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
7394            let avail_top = lby > 0 || top_mb_avail;
7395            let avail_left = lbx > 0 || left_mb_avail;
7396            let mut scan16 = [0i32; 16];
7397            let total = if cbp_luma & (1 << (blk / 4)) != 0 {
7398                let nc = self.nc_pred(lbx, lby);
7399                let t;
7400                (scan16, t) = decode_residual_block_with(vt, r, 16, nc)?;
7401                t
7402            } else {
7403                0
7404            };
7405            self.nnz_cache_set(lbx, lby, total);
7406            nnz_raster[(lby & 3) * 4 + (lbx & 3)] = total;
7407            self.recon_i4_block(bx, by, modes[(lby & 3) * 4 + (lbx & 3)], avail_top, avail_left, &scan16, total, qp);
7408        }
7409        // Deferred: `recon_i4_block` gathers from `coded_y` / `rec_y`, never
7410        // from `nnz_y`, so one contiguous copy per row replaces sixteen stores.
7411        for by in 0..4usize {
7412            let a = (mb_y * 4 + by) * w4 + mb_x * 4;
7413            self.nnz_y[a..a + 4].copy_from_slice(&nnz_raster[by * 4..by * 4 + 4]);
7414        }
7415
7416        self.decode_chroma(r, mb_x, mb_y, cbp_chroma, chroma_mode)
7417    }
7418
7419    /// Decodes an `I_8x8` macroblock (High profile): four 8×8 luma blocks, each
7420    /// with its own intra mode, 8×8 transform residual (CAVLC = four interleaved
7421    /// 4×4 blocks), and 8×8 intra prediction.
7422    fn decode_i8x8(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
7423        let vt = vlc_tables();
7424        let w4 = self.mb_w * 4;
7425        if let Some(p) = self.mb_t8x8.get_mut(mb_y * self.mb_w + mb_x) {
7426            *p = true;
7427        }
7428        self.any_t8 = true;
7429
7430        // intra8x8 mode signalling — one mode per 8×8 block (raster 0..3),
7431        // stored into all four of its 4×4 cells so neighbors can read it.
7432        let mut modes8 = [2u8; 4];
7433        for (b8, mode) in modes8.iter_mut().enumerate() {
7434            let (b8x, b8y) = (b8 % 2, b8 / 2);
7435            let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2);
7436            let predicted = self.predict_i4_mode(bx, by);
7437            let actual = if r.read_bit()? {
7438                predicted
7439            } else {
7440                let rem = r.read_bits(3)? as u8;
7441                if rem < predicted { rem } else { rem + 1 }
7442            };
7443            *mode = actual;
7444            for sy in 0..2 {
7445                // Row fill: the 2x2 cell block is two contiguous PAIRS.
7446                self.modes_y[(by + sy) * w4 + bx..][..2].fill(actual);
7447            }
7448        }
7449
7450        let chroma_mode = r.read_ue()? as u8;
7451        let cbp = read_cbp_intra(r)?;
7452        let cbp_luma = cbp & 15;
7453        let cbp_chroma = cbp >> 4;
7454        if cbp != 0 {
7455            self.step_qp(r.read_se()?)?;
7456        }
7457        let qp = self.cur_qp;
7458
7459        let top_mb_avail = mb_y > 0
7460            && self.nbr_in_slice(mb_x, mb_y - 1)
7461            && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
7462        let left_mb_avail = mb_x > 0
7463            && self.nbr_in_slice(mb_x - 1, mb_y)
7464            && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
7465        self.nnz_cache_load(mb_x, mb_y);
7466
7467        for b8 in 0..4 {
7468            let (b8x, b8y) = (b8 % 2, b8 / 2);
7469            let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2);
7470
7471            // residual: 8×8 CAVLC = four 4×4 sub-blocks, coeff k of sub-block s
7472            // mapping to 8×8 scan position 4·k + s (spec §7.3.5.3.2).
7473            let mut scan8 = [0i32; 64];
7474            let coded8 = cbp_luma & (1 << b8) != 0;
7475            if coded8 {
7476                for sub in 0..4 {
7477                    let (sx, sy) = (sub % 2, sub / 2);
7478                    let (cx, cy) = (b8x * 2 + sx, b8y * 2 + sy);
7479                    let nc = self.nc_pred(cx, cy);
7480                    let (blk, total) = decode_residual_block_with(vt, r, 16, nc)?;
7481                    self.nnz_cache_set(cx, cy, total);
7482                    if let Some(c) = self.nnz_y.get_mut((by + sy) * w4 + (bx + sx)) {
7483                        *c = total;
7484                    }
7485                    for k in 0..16 {
7486                        scan8[4 * k + sub] = blk[k];
7487                    }
7488                }
7489            } else {
7490                for sub in 0..4 {
7491                    let (sx, sy) = (sub % 2, sub / 2);
7492                    self.nnz_cache_set(b8x * 2 + sx, b8y * 2 + sy, 0);
7493                    if let Some(c) = self.nnz_y.get_mut((by + sy) * w4 + (bx + sx)) {
7494                        *c = 0;
7495                    }
7496                }
7497            }
7498
7499            let avail_top = b8y > 0 || top_mb_avail;
7500            let avail_left = b8x > 0 || left_mb_avail;
7501            self.recon_i8_block(bx, by, modes8[b8], avail_top, avail_left, coded8.then_some(&scan8), qp);
7502            for sy in 0..2 {
7503                // Row fill: the 2x2 cell block is two contiguous PAIRS.
7504                self.coded_y[(by + sy) * w4 + bx..][..2].fill(true);
7505            }
7506        }
7507
7508        self.decode_chroma(r, mb_x, mb_y, cbp_chroma, chroma_mode)
7509    }
7510
7511    /// Dequantizes + inverse-transforms an 8×8 luma block, applying the scaling
7512    /// matrix `list` (0 = intra, 1 = inter) or flat weights.
7513    fn inv_quant8(&self, raster: &[i32; 64], qp: u8, list: usize) -> [i32; 64] {
7514        match &self.scaling8 {
7515            Some(s) => inverse_quant_8x8(raster, qp, &s[list]),
7516            None => inverse_quant_8x8(raster, qp, &[16i32; 64]),
7517        }
7518    }
7519
7520    /// Gathers the 8×8 luma intra reference samples at pixel `(px, py)`: the 16
7521    /// top samples (8..15 substituted from the last when no top-right), 8 left
7522    /// samples, the above-left corner, and whether the corner is available.
7523    #[allow(clippy::too_many_arguments)]
7524    fn gather_i8(
7525        &self,
7526        px: usize,
7527        py: usize,
7528        avail_top: bool,
7529        avail_left: bool,
7530        bx: usize,
7531        by: usize,
7532    ) -> ([u8; 16], [u8; 8], u8, bool) {
7533        let (cw, w4) = (self.cw, self.mb_w * 4);
7534        let mut top = [0u8; 16];
7535        let mut left = [0u8; 8];
7536        let mut corner = 0;
7537        if avail_top {
7538            // Row-slice loads: one source branch per segment, not per pixel.
7539            top[..8].copy_from_slice(self.top_y_row(py, px, 8));
7540            let tr_avail = bx + 2 < w4
7541                && self.coded_y.get((by - 1) * w4 + (bx + 2)).copied().unwrap_or(false)
7542                && self.nbr_in_slice((bx + 2) / 4, (by - 1) / 4)
7543                && self.intra_nbr_ok(bx + 2, by - 1);
7544            if tr_avail {
7545                top[8..16].copy_from_slice(self.top_y_row(py, px + 8, 8));
7546            } else {
7547                let t7 = top[7];
7548                top[8..16].fill(t7);
7549            }
7550        }
7551        if avail_left {
7552            // STRIDED WALK, NOT STRIDED INDEXING. A span plus `col[i * cw]` still
7553            // checks, because `i * cw <= 7 * cw` is not something LLVM derives
7554            // against the span's own length; `step_by` on the iterator does the
7555            // stride with no index at all, and `zip` bounds the count.
7556            let base = py * cw + px - 1;
7557            for (s, &v) in left.iter_mut().zip(self.rec_y[base..].iter().step_by(cw)) {
7558                *s = v;
7559            }
7560        }
7561        let avail_corner = avail_top && avail_left && self.intra_nbr_ok(bx - 1, by - 1);
7562        if avail_corner {
7563            corner = self.top_y_px(py, px - 1);
7564        }
7565        (top, left, corner, avail_corner)
7566    }
7567
7568    fn decode_i16(
7569        &mut self,
7570        r: &mut BitReader,
7571        mb_x: usize,
7572        mb_y: usize,
7573        mt: u32,
7574    ) -> Result<(), MbError> {
7575        let vt = vlc_tables();
7576        let pred_mode = I16Mode::from_id(mt % 4);
7577        let cbp_chroma = (mt % 12) / 4;
7578        let cbp_luma_15 = mt / 12 == 1;
7579        let chroma_mode = r.read_ue()? as u8;
7580        self.step_qp(r.read_se()?)?;
7581        let qp = self.cur_qp;
7582        let w4 = self.mb_w * 4;
7583
7584        // luma DC
7585        self.nnz_cache_load(mb_x, mb_y);
7586        let nc_dc = self.nc_pred(0, 0);
7587        let (dc_scan, _) = decode_residual_block_with(vt, r, 16, nc_dc)?;
7588        let dc_levels = un_scan_4x4_dcac(&dc_scan);
7589        let recon_dc = self.dequant_luma_dc(&dc_levels, qp, 0);
7590
7591        // luma AC (nnz set for all 16 blocks: 0 when DC-only, matching the encoder)
7592        let mut nnz_raster = [0u8; 16];
7593        let mut q_blocks = [[0i32; 16]; 16];
7594        for &(bx, by) in &LUMA_4X4_SCAN_XY {
7595            let total = if cbp_luma_15 {
7596                let nc = self.nc_pred(bx, by);
7597                let (ac, t) = decode_residual_block_with(vt, r, 15, nc)?;
7598                // Zero-skip: an empty AC block leaves the fresh-zero raster
7599                // block untouched (un-scanning 16 zeros wrote zeros on zeros).
7600                if t != 0 {
7601                    un_scan_4x4_ac_into(&ac, &mut q_blocks[(by & 3) * 4 + (bx & 3)]);
7602                }
7603                t
7604            } else {
7605                0
7606            };
7607            self.nnz_cache_set(bx, by, total);
7608            nnz_raster[(by & 3) * 4 + (bx & 3)] = total;
7609        }
7610        // ONE contiguous copy per row (nothing reads `nnz_y` for this
7611        // macroblock in between - `nc_pred` predicts from `nnz_cache`).
7612        for by in 0..4usize {
7613            let a = (mb_y * 4 + by) * w4 + mb_x * 4;
7614            self.nnz_y[a..a + 4].copy_from_slice(&nnz_raster[by * 4..by * 4 + 4]);
7615        }
7616
7617        // prediction + reconstruction
7618        let avail_top = mb_y > 0
7619            && self.nbr_in_slice(mb_x, mb_y - 1)
7620            && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
7621        let avail_left = mb_x > 0
7622            && self.nbr_in_slice(mb_x - 1, mb_y)
7623            && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
7624        self.recon_i16_luma(mb_x, mb_y, pred_mode, avail_top, avail_left, Some(&q_blocks), &recon_dc, qp);
7625        // I_16x16 blocks are treated as DC for neighbor mode prediction.
7626        for lby in 0..4usize {
7627            let a = (mb_y * 4 + lby) * w4 + mb_x * 4;
7628            self.modes_y[a..a + 4].fill(2);
7629        }
7630
7631        self.decode_chroma(r, mb_x, mb_y, cbp_chroma, chroma_mode)
7632    }
7633
7634    /// Reads and reconstructs the chroma residual (shared by both luma types).
7635    fn decode_chroma(
7636        &mut self,
7637        r: &mut BitReader,
7638        mb_x: usize,
7639        mb_y: usize,
7640        cbp_chroma: u32,
7641        chroma_mode: u8,
7642    ) -> Result<(), MbError> {
7643        let vt = vlc_tables();
7644        let qpc = self.chroma_qp_for(self.cur_qp);
7645        let avail_top = mb_y > 0
7646            && self.nbr_in_slice(mb_x, mb_y - 1)
7647            && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
7648        let avail_left = mb_x > 0
7649            && self.nbr_in_slice(mb_x - 1, mb_y)
7650            && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
7651
7652        let mut c_recon_dc = [[0i32; 4]; 2];
7653        if cbp_chroma != 0 {
7654            for (c, slot) in c_recon_dc.iter_mut().enumerate() {
7655                let (dc, _) = decode_residual_block_with(vt, r, 4, -1)?;
7656                *slot = self.dequant_chroma_dc(&[dc[0], dc[1], dc[2], dc[3]], qpc, 1 + c);
7657            }
7658        }
7659        let mut c_q_blocks = [[[0i32; 16]; 4]; 2];
7660        if cbp_chroma == 2 {
7661            self.chroma_cache_load(mb_x, mb_y);
7662            let w2 = self.mb_w * 2;
7663            for c in 0..2 {
7664                for &(bx, by) in &CHROMA_4X4_SCAN_XY {
7665                    let nc = self.chroma_nc_pred(c, bx, by);
7666                    let (ac, total) = decode_residual_block_with(vt, r, 15, nc)?;
7667                    self.chroma_nnz_cache_set(c, bx, by, total);
7668                    if let Some(n) = self.nnz_c[c & 1].get_mut((mb_y * 2 + by) * w2 + (mb_x * 2 + bx)) {
7669                        *n = total;
7670                    }
7671                    // Zero-skip: empty AC leaves the fresh-zero raster block.
7672                    if total != 0 {
7673                        un_scan_4x4_ac_into(&ac, &mut c_q_blocks[c][by * 2 + bx]);
7674                    }
7675                }
7676            }
7677        }
7678        self.recon_chroma_blocks(mb_x, mb_y, chroma_mode, avail_top, avail_left, &c_q_blocks, &c_recon_dc, qpc);
7679        Ok(())
7680    }
7681
7682    /// Applies the in-loop deblocking filter to the reconstructed frame, with
7683    /// the slice's `FilterOffsetA`/`FilterOffsetB` (each = the coded `*_div2`
7684    /// value × 2).
7685    /// Per-frame per-MB dump for conformance bisection, keyed on `RH264_DUMP_MB`.
7686    /// Prints one char per macroblock: `i` = intra, otherwise the List-0 reference
7687    /// index of the MB's top-left 4x4 block. Directly comparable with ffmpeg's
7688    /// `-debug mb_type` map, which is the only per-MB ground truth we can get out
7689    /// of the reference decoder.
7690    fn dump_mb_map(&self) {
7691        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7692        if !*ON.get_or_init(|| std::env::var_os("RH264_DUMP_MB").is_some()) {
7693            return;
7694        }
7695        let w4 = self.mb_w * 4;
7696        let mut hist = [0usize; 4];
7697        eprintln!("--- frame poc {} ---", self.cur_poc);
7698        for mb_y in 0..self.mb_h {
7699            let mut row = String::new();
7700            for mb_x in 0..self.mb_w {
7701                let b = (mb_y * 4) * w4 + mb_x * 4;
7702                let Some(&r) = self.ref_idx_y.get(b) else { continue };
7703                if r < 0 {
7704                    row.push('i');
7705                } else {
7706                    if (r as usize) < 4 {
7707                        hist[r as usize] += 1;
7708                    }
7709                    row.push((b'0' + (r as u8).min(9)) as char);
7710                }
7711            }
7712            eprintln!("{row}");
7713        }
7714        eprintln!(
7715            "ref histogram: {hist:?}   num_ref_active={} refs.len()={}   OUT-OF-RANGE={}",
7716            self.num_ref_active,
7717            self.refs.len(),
7718            hist.iter().skip(self.refs.len()).sum::<usize>()
7719        );
7720        let list: Vec<String> = self
7721            .refs
7722            .iter()
7723            .enumerate()
7724            .map(|(i, f)| {
7725                // A synthesized frame_num-gap frame is uniform grey with w4 == 0;
7726                // flag it, because it silently displaces real pictures in the list.
7727                let synth = if f.w4 == 0 { " SYNTH-GREY" } else { "" };
7728                format!("[{i}] poc={} fn={}{synth}", f.poc, f.frame_num)
7729            })
7730            .collect();
7731        eprintln!("  RefPicList0: {}", list.join("  "));
7732    }
7733
7734    pub fn deblock(&mut self, offset_a: i32, offset_b: i32) {
7735        self.span_flush(); // deferred grid spans feed bS derivation below
7736        self.edc_flush(); // backstop: no pixel job may survive to filtering
7737        self.dump_mb_map();
7738        // ROW MODE: finish any rows not derived during decode (mid-row slice
7739        // ends, error paths) FIRST, while `self` is still mutably borrowable.
7740        if rowdb_on() {
7741            while self.bs_rows < self.mb_h {
7742                let r = self.bs_rows;
7743                self.derive_bs_row(r);
7744                self.bs_rows += 1;
7745            }
7746        }
7747        // Deblock boundary strength uses the *transform block's* coded status. For
7748        // an 8×8-transform macroblock the unit is the whole 8×8, so every 4×4 cell
7749        // shares the 8×8's coefficient presence (OR of its four sub-block counts)
7750        // — distinct from the per-sub-block `nnz_y` used for the CAVLC nC context.
7751        // Only differs from `nnz_y` when some MB uses the 8×8 transform (High
7752        // profile). On Baseline (no 8×8) it's identical — skip the clone + rewrite.
7753        // Row-interleave already derived bS into `bs_frame`; the filter then
7754        // reads only `bs`+`t8x8`+qp — do not clone nnz or rebuild POC maps.
7755        let rowdb = rowdb_on();
7756        let nnz_db_storage;
7757        let nnz_db: &[u8] = if rowdb {
7758            &[]
7759        } else if self.mb_t8x8.iter().any(|&t| t) {
7760            let mut n = self.nnz_y.clone();
7761            let w4 = self.mb_w * 4;
7762            for mb_y in 0..self.mb_h {
7763                for mb_x in 0..self.mb_w {
7764                    if !self.mb_t8x8.get(mb_y * self.mb_w + mb_x).copied().unwrap_or(false) {
7765                        continue;
7766                    }
7767                    for b8 in 0..4 {
7768                        let (bx, by) = (mb_x * 4 + (b8 % 2) * 2, mb_y * 4 + (b8 / 2) * 2);
7769                        let any = (0..2)
7770                            .any(|sy| self.nnz_y[(by + sy) * w4 + bx..][..2].iter().any(|&v| v > 0));
7771                        for sy in 0..2 {
7772                            // Row fill: the 2x2 cell block is two contiguous PAIRS.
7773                            n[(by + sy) * w4 + bx..][..2].fill(u8::from(any));
7774                        }
7775                    }
7776                }
7777            }
7778            nnz_db_storage = n;
7779            &nnz_db_storage
7780        } else {
7781            &self.nnz_y
7782        };
7783        let mut info = rusty_h264_common::deblock::BlockInfo {
7784            inter: if rowdb { &[] } else { &self.inter_y },
7785            nnz: nnz_db,
7786            mv: if rowdb { &[] } else { &self.mv_y },
7787            ref_id: if rowdb { &[] } else { &self.ref_idx_y },
7788            mv1: if rowdb { &[] } else { &self.mv1 },
7789            ref_id1: if rowdb || self.ref_poc1.is_empty() {
7790                &[]
7791            } else {
7792                &self.ref_idx1
7793            },
7794            w4: self.mb_w * 4,
7795            t8x8: &self.mb_t8x8,
7796            bs: &[],
7797            poc0: if rowdb { &[] } else { &self.ref_poc0 },
7798            poc1: if rowdb { &[] } else { &self.ref_poc1 },
7799            kind: &self.mb_kind,
7800        };
7801        // ROW MODE (R2): rows were derived during decode; the remainder was
7802        // finished above (before `info` borrowed the grids). Fallback: the
7803        // Part 16/17 picture-end precompute; `RS_H264_BS_PRE=0` further falls
7804        // back to the pack-then-derive-in-loop pipeline.
7805        let bs_store;
7806        if rowdb {
7807            bs_store = std::mem::take(&mut self.bs_frame);
7808            info.bs = &bs_store;
7809        } else if bs_pre_on() {
7810            let mut buf = Vec::new();
7811            rusty_h264_common::deblock::precompute_bs_frame(&info, self.mb_w, self.mb_h, &mut buf);
7812            bs_store = buf;
7813            info.bs = &bs_store;
7814        } else {
7815            bs_store = Vec::new();
7816        }
7817        let first_row = if rowdb { self.flt_rows } else { 0 };
7818        rusty_h264_common::deblock::filter_frame_rows(
7819            &mut self.rec_y,
7820            &mut self.rec_u,
7821            &mut self.rec_v,
7822            self.mb_w,
7823            self.mb_h,
7824            first_row..self.mb_h,
7825            &self.mb_qp,
7826            self.chroma_qp_offset,
7827            offset_a,
7828            offset_b,
7829            &info,
7830        );
7831        drop(info);
7832        if rowdb {
7833            self.bs_frame = bs_store;
7834        }
7835    }
7836
7837    /// Crops the reconstructed coded-size planes to the display window.
7838    /// `into_frame`, additionally handing the per-picture grids back for reuse by
7839    /// the next picture. See `GridPool` for why this is worth doing.
7840    pub fn into_frame_recycle(mut self, crop_r: usize, crop_b: usize) -> (YuvFrame, GridPool) {
7841        let [c0, c1] = std::mem::take(&mut self.nnz_c);
7842        let pool = GridPool {
7843            bits_per_mb: self.bits_per_mb,
7844            mb_qp: std::mem::take(&mut self.mb_qp),
7845            bs_frame: std::mem::take(&mut self.bs_frame),
7846            pk_prev: std::mem::take(&mut self.pk_prev),
7847            pk_cur: std::mem::take(&mut self.pk_cur),
7848            nnz_dbr: std::mem::take(&mut self.nnz_dbr),
7849            bak_y: std::mem::take(&mut self.bak_y),
7850            bak_u: std::mem::take(&mut self.bak_u),
7851            bak_v: std::mem::take(&mut self.bak_v),
7852            nnz_y: std::mem::take(&mut self.nnz_y),
7853            nnz_c0: c0,
7854            nnz_c1: c1,
7855            modes_y: std::mem::take(&mut self.modes_y),
7856            coded_y: std::mem::take(&mut self.coded_y),
7857            mv_y: std::mem::take(&mut self.mv_y),
7858            inter_y: std::mem::take(&mut self.inter_y),
7859            ref_idx_y: std::mem::take(&mut self.ref_idx_y),
7860            mv1: std::mem::take(&mut self.mv1),
7861            ref_idx1: std::mem::take(&mut self.ref_idx1),
7862            mb_t8x8: std::mem::take(&mut self.mb_t8x8),
7863            mb_kind: std::mem::take(&mut self.mb_kind),
7864            bzero: std::mem::take(&mut self.bzero),
7865            sc_cat: std::mem::take(&mut self.sc_cat),
7866            sc_cbp: std::mem::take(&mut self.sc_cbp),
7867            sc_cmode: std::mem::take(&mut self.sc_cmode),
7868            sc_nzc: std::mem::take(&mut self.sc_nzc),
7869            sc_cbfdc: std::mem::take(&mut self.sc_cbfdc),
7870            sc_skip: std::mem::take(&mut self.sc_skip),
7871            sc_ref: std::mem::take(&mut self.sc_ref),
7872            sc_mvd: std::mem::take(&mut self.sc_mvd),
7873            sc_ref1: std::mem::take(&mut self.sc_ref1),
7874            sc_mvd1: std::mem::take(&mut self.sc_mvd1),
7875            sc_direct: std::mem::take(&mut self.sc_direct),
7876            ref_poc0: std::mem::take(&mut self.ref_poc0),
7877            ref_poc1: std::mem::take(&mut self.ref_poc1),
7878        };
7879        (self.into_frame(crop_r, crop_b), pool)
7880    }
7881
7882    pub fn into_frame(self, crop_r: usize, crop_b: usize) -> YuvFrame {
7883        // No cropping (the common case): the reconstruction planes ARE the output —
7884        // move them out instead of allocating + copying three full planes per frame.
7885        if crop_r == 0 && crop_b == 0 {
7886            return YuvFrame {
7887                width: self.cw,
7888                height: self.ch,
7889                y: self.rec_y,
7890                u: self.rec_u,
7891                v: self.rec_v,
7892            };
7893        }
7894        let dw = self.cw - 2 * crop_r;
7895        let dh = self.ch - 2 * crop_b;
7896        let mut y = vec![0u8; dw * dh];
7897        for row in 0..dh {
7898            y[row * dw..row * dw + dw].copy_from_slice(&self.rec_y[row * self.cw..row * self.cw + dw]);
7899        }
7900        let (cdw, cdh) = (dw / 2, dh / 2);
7901        let mut u = vec![0u8; cdw * cdh];
7902        let mut v = vec![0u8; cdw * cdh];
7903        for row in 0..cdh {
7904            u[row * cdw..row * cdw + cdw]
7905                .copy_from_slice(&self.rec_u[row * self.ccw..row * self.ccw + cdw]);
7906            v[row * cdw..row * cdw + cdw]
7907                .copy_from_slice(&self.rec_v[row * self.ccw..row * self.ccw + cdw]);
7908        }
7909        let _ = self.cch;
7910        YuvFrame {
7911            width: dw,
7912            height: dh,
7913            y,
7914            u,
7915            v,
7916        }
7917    }
7918}
7919
7920/// Reads `ref_idx_l0` as `te(v)` with range `num_ref_active - 1`: a single flag
7921/// when exactly two references are active (cMax == 1), else `ue(v)`.
7922// ---- CABAC binarization engine helpers (openh264 cabac_decoder.cpp) ----
7923
7924/// Unary bin (`DecodeUnaryBinCabac`): bin0 at `ctx`; if 1, count bins at `ctx+off`
7925/// (including the terminating 0) until a 0.
7926/// CAVLC `mvd_lX` with a sanity bound. `se(v)` can legally code ±2^31-1, but
7927/// every profile/level caps |MV| far below ±2^17 quarter-pel units; beyond
7928/// that is a corrupt stream, and the unchecked `pmv + mvd` addition would
7929/// overflow i32 (debug panic / release wrap into an absurd vector).
7930fn read_mvd(r: &mut BitReader) -> Result<i32, MbError> {
7931    let v = r.read_se()?;
7932    if v.unsigned_abs() > (1 << 17) {
7933        return Err(MbError::Truncated);
7934    }
7935    Ok(v)
7936}
7937
7938fn cabac_unary(cab: &mut crate::cabac::Cabac, ctx: usize, off: usize) -> u32 {
7939    if cab.decode_decision(ctx) == 0 {
7940        return 0;
7941    }
7942    let mut sym = 0;
7943    loop {
7944        let bin = cab.decode_decision(ctx + off);
7945        sym += 1;
7946        // Cap the unary run: no valid H.264 element coded through this helper
7947        // (mb_qp_delta) exceeds a few dozen bins, but on malformed / buffer-exhausted
7948        // input the arithmetic engine keeps yielding 1s (it zero-fills past the end),
7949        // which would loop forever. 512 is far beyond any legal value.
7950        if bin == 0 || sym >= 512 {
7951            break;
7952        }
7953    }
7954    sym
7955}
7956
7957/// k-th order Exp-Golomb in bypass (`DecodeExpBypassCabac`).
7958fn cabac_exp_bypass(cab: &mut crate::cabac::Cabac, mut count: i32) -> u32 {
7959    let mut sym = 0u32;
7960    loop {
7961        let c = cab.decode_bypass();
7962        if c == 1 {
7963            sym += 1 << count;
7964            count += 1;
7965        }
7966        if c == 0 || count == 16 {
7967            break;
7968        }
7969    }
7970    let mut sym2 = 0u32;
7971    while count > 0 {
7972        count -= 1;
7973        if cab.decode_bypass() != 0 {
7974            sym2 |= 1 << count;
7975        }
7976    }
7977    sym + sym2
7978}
7979
7980/// UEG0 coeff-level suffix (`DecodeUEGLevelCabac`): TU prefix at `ctx` (≤13) then an
7981/// EG0 bypass suffix.
7982fn cabac_ueg_level(cab: &mut crate::cabac::Cabac, ctx: usize) -> u32 {
7983    if cab.decode_decision(ctx) == 0 {
7984        return 0;
7985    }
7986    let mut code = 0u32;
7987    let mut tmp;
7988    loop {
7989        tmp = cab.decode_decision(ctx);
7990        code += 1;
7991        if tmp == 0 || code == 12 {
7992            break;
7993        }
7994    }
7995    if tmp != 0 {
7996        code += cabac_exp_bypass(cab, 0) + 1;
7997    }
7998    code
7999}
8000
8001/// `mb_qp_delta` CABAC (`ParseDeltaQpCabac`): ctxIdxOffset 60, ctxInc = (prev delta ≠ 0).
8002pub fn parse_mb_qp_delta_cabac(cab: &mut crate::cabac::Cabac, last_delta_qp: &mut i32) -> i32 {
8003    const O: usize = 60;
8004    let ctx_inc = (*last_delta_qp != 0) as usize;
8005    let mut qp_delta = 0;
8006    if cab.decode_decision(O + ctx_inc) != 0 {
8007        let code = cabac_unary(cab, O + 2, 1) + 1;
8008        qp_delta = ((code + 1) >> 1) as i32;
8009        if code & 1 == 0 {
8010            qp_delta = -qp_delta;
8011        }
8012    }
8013    *last_delta_qp = qp_delta;
8014    qp_delta
8015}
8016
8017// Shared CABAC residual glue tables (NZC_CACHE, RES_*) now live in
8018// `cabac_tables` — both coders read the ONE copy.
8019use rusty_h264_common::cabac_tables::{NZC_CACHE, RES_CBF, RES_MAP, RES_MAXC2, RES_MAXPOS, RES_ONE};
8020// res-property values (post GetMbResProperty, CABAC): the ctx-table index.
8021const RP_I16_DC: usize = 1;
8022const RP_I16_AC: usize = 2;
8023const RP_LUMA_4X4: usize = 3;
8024const RP_CHROMA_DC: usize = 7; // U (V=8, same offsets)
8025const RP_CHROMA_AC: usize = 9; // U (V=10, same offsets)
8026/// Luma 8×8 (ctxBlockCat 5). Its RES_MAP/RES_CBF entries stay 0: cat 5 does NOT
8027/// share the `105 + off` / `166 + off` context bases the 4×4 categories use — it
8028/// has its own absolute bases (402 sig, 417 last) and its own per-position
8029/// ctxIdxInc maps below. RES_ONE[6] = 199 IS used, because 227 + 199 = 426 and
8030/// 232 + 199 = 431 reproduce the spec's coeff_abs_level_minus1 base exactly, so
8031/// the level loop needs no special case at all.
8032const RP_LUMA_8X8: usize = 6;
8033
8034// SIG8X8 / LAST8X8 moved to `rusty_h264_common::cabac_tables` (R6-1) so the encoder's
8035// ctxBlockCat 5 writer shares the exact spec data this reader is validated against.
8036use rusty_h264_common::cabac_tables::{LAST8X8, SIG8X8};
8037
8038/// One residual block (openh264 `ParseResidualBlockCabac`), generic over the 5 CABAC
8039/// block categories. `rp` selects the context offsets. DC categories (I16 luma DC,
8040/// chroma DC) take the cbf context from the per-MB `cbf_dc` bitmask + neighbour MB DC
8041/// cbf; AC categories from the padded nzc cache. Returns totalCoeffNum.
8042#[allow(clippy::too_many_arguments)]
8043fn parse_residual_cabac(
8044    cab: &mut crate::cabac::Cabac,
8045    nzc: &mut [u8; 48],
8046    cbf_dc: &mut u16,
8047    iz: usize,
8048    rp: usize,
8049    is_intra: bool,
8050    ndc: (Option<u16>, Option<u16>), // (top MB cbf_dc, left MB cbf_dc); None = unavailable
8051    out: &mut [i32],                 // scan-order coefficients written here (len ≥ maxPos+1)
8052) -> u32 {
8053    // The CABAC residual parse IS the decoder's entropy stage on Main-profile
8054    // streams — it was invisible (a ~47% residue) until this scope named it.
8055    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Entropy);
8056    // ---- coded_block_flag ----
8057    // ctxBlockCat 5 is the ONLY category with no coded_block_flag: its presence is
8058    // inferred from CodedBlockPatternLuma, so parsing one here would desync.
8059    let is8 = rp == RP_LUMA_8X8;
8060    let is_dc = rp == RP_I16_DC || rp == RP_CHROMA_DC || rp == RP_CHROMA_DC + 1;
8061    let (mut na, mut nb) = (is_intra as u8, is_intra as u8);
8062    // `nzc` is [u8; 48] and every `NZC_CACHE` entry lies in [9, 47], so this
8063    // clamp is a semantic no-op that hands LLVM BOTH bounds - which is what
8064    // makes `nzc[scan]`, `nzc[scan - 8]` and `nzc[scan - 1]` below provable
8065    // instead of three bounds checks. Same trick as `parse_mvd_partition`.
8066    let scan = NZC_CACHE[iz.min(23)].clamp(8, 47);
8067    if is_dc {
8068        if let Some(t) = ndc.0 {
8069            nb = ((t >> rp) & 1) as u8;
8070        }
8071        if let Some(l) = ndc.1 {
8072            na = ((l >> rp) & 1) as u8;
8073        }
8074    } else {
8075        let (nbc, nac) = (nzc[scan - 8], nzc[scan - 1]);
8076        if nbc != 0xff {
8077            nb = (nbc != 0) as u8;
8078        }
8079        if nac != 0xff {
8080            na = (nac != 0) as u8;
8081        }
8082    }
8083    if !is8 {
8084        let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EntCbf);
8085        let cbf = cab.decode_decision(85 + RES_CBF[rp] + (na + (nb << 1)) as usize);
8086        if cbf == 0 {
8087            if !is_dc {
8088                nzc[scan] = 0;
8089            }
8090            return 0;
8091        }
8092        if is_dc {
8093            *cbf_dc |= 1 << rp;
8094        }
8095    }
8096    // ---- significance map ----
8097    let maxpos = RES_MAXPOS[rp] as usize;
8098    // cat 5 uses its own absolute bases; the 4×4 categories share 105/166 + offset.
8099    let (map, last) = if is8 {
8100        (402, 417)
8101    } else {
8102        let m = RES_MAP[rp];
8103        (105 + m, 166 + m)
8104    };
8105    // SPARSE significance map: record each significant POSITION in `pos[..n]`
8106    // instead of marking a dense 64-entry array. Three costs disappear — the
8107    // 256-byte `sig` zeroing per call, the level loop's data-dependent
8108    // `sig[i] != 0` re-scan of every position (a branch mispredict per
8109    // transition on typical 2-4-coeff blocks), and the final dense copy into
8110    // `out`. Bin ORDER is unchanged: levels were decoded at descending
8111    // significant positions, which is exactly `pos[..n]` reversed.
8112    //
8113    // CONTRACT with the callers (all 10 sites): `out` is freshly zeroed, so
8114    // writing only the significant entries leaves the same contents the dense
8115    // copy produced. A reused non-zero `out` would be a correctness bug.
8116    let mut pos = [0u8; 64];
8117    let mut n = 0usize;
8118    let mut last_hit = false;
8119    let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EntSig);
8120    // 4×4: ctxIdxInc IS the scan position. 8×8: it comes from the folded maps.
8121    // NOTE (4:2:2 landmine): `(i, i)` is correct for every 4:2:0 category
8122    // only because chroma-DC (cat 3) has NumC8x8 == 1 here; spec §9.3.3.1.3
8123    // wants `Min(i / NumC8x8, 2)` for its sig/last ctxIdxInc, which
8124    // diverges the day 4:2:2 (NumC8x8 == 2) is admitted.
8125    //
8126    let (mi_is8, li_is8) = (is8, is8);
8127    let _ = (mi_is8, li_is8);
8128    for i in 0..maxpos {
8129        // Both tables are [u8; 64] and `i < maxpos <= 63`, so `& 63` changes no
8130        // value and folds the two bounds checks.
8131        let (mi, li) = if is8 {
8132            (SIG8X8[i & 63] as usize, LAST8X8[i & 63] as usize)
8133        } else {
8134            (i, i)
8135        };
8136        if cab.decode_decision(map + mi) != 0 {
8137            pos[n & 63] = i as u8;
8138            n += 1;
8139            if cab.decode_decision(last + li) != 0 {
8140                last_hit = true;
8141                break;
8142            }
8143        }
8144    }
8145    if !last_hit {
8146        pos[n & 63] = maxpos as u8;
8147        n += 1;
8148    }
8149    let coeff_num = n as u32;
8150    // ---- levels ----
8151    let one = 227 + RES_ONE[rp];
8152    let abs = one + 5;
8153    let maxc2 = RES_MAXC2[rp];
8154    let (mut c1, mut c2) = (1i32, 0i32);
8155    drop(_sg);
8156    let _lg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EntLvl);
8157    for k in (0..n).rev() {
8158        let mut level = 1 + cab.decode_decision(one + c1 as usize) as i32;
8159        if level == 2 {
8160            level += cabac_ueg_level(cab, abs + c2 as usize) as i32;
8161            c2 = (c2 + 1).min(maxc2);
8162            c1 = 0;
8163        } else if c1 != 0 {
8164            c1 = (c1 + 1).min(4);
8165        }
8166        if cab.decode_bypass() != 0 {
8167            level = -level;
8168        }
8169        // NOT A MASK — `.get_mut`. `out` is `&mut [i32]`, a RUNTIME-length slice
8170        // of maxPos+1, so no constant bound can be correct: an `& 15` here wrote
8171        // 8x8 coefficients (maxPos = 63) to the WRONG positions and failed the
8172        // corpus on tempete high/default. The distinction that matters is that a
8173        // mask silently RELOCATES an out-of-range write while `.get_mut` can only
8174        // drop it, and dropping is unreachable here (`pos` holds values <= maxPos).
8175        if let Some(o) = out.get_mut(pos[k & 63] as usize) {
8176            *o = level;
8177        }
8178    }
8179    if is8 {
8180        // One 8×8 covers four consecutive z-order 4×4 cells. Every later
8181        // coded_block_flag ctxIdxInc reads this cache, so all four must carry the
8182        // count — writing only `scan` would corrupt the NEXT macroblock's contexts.
8183        let cn = coeff_num as u8;
8184        for k in 0..4 {
8185            nzc[NZC_CACHE[(iz + k).min(23)].min(47)] = cn;
8186        }
8187    } else if !is_dc {
8188        nzc[scan] = coeff_num as u8;
8189    }
8190    coeff_num
8191}
8192
8193
8194// ============================================================================
8195// Entropy-decouple E2: the OWNED pixel context that crosses the thread
8196// boundary (docs/entropy-decouple-plan.md). The worker owns the planes, the
8197// backup rows, the DPB Arcs and its own qp/t8/bs grids (fed by Row messages);
8198// the parse thread keeps every syntax grid. The methods below are ports of
8199// the FrameDecoder pixel halves — grid writes removed (parse commits its own
8200// grids), motion carried in the job instead of re-gathered.
8201// ============================================================================
8202
8203/// The committed-value class of a deferred B_Skip span. Continuation
8204/// requires kind EQUALITY: the flush range-fills grids and recons the band
8205/// from these values alone.
8206#[derive(Clone, Copy, PartialEq, Eq)]
8207enum BzKind {
8208    /// ref 0 both lists, (0,0)/(0,0) — band = avg of both padded refs.
8209    ZeroBi,
8210    /// One active list at (0,0): (list, clamped ref index) — band = memcpy.
8211    ZeroUni(u8, u8),
8212    /// Uniform full-pel direct: clamped refs (-1 = inactive) + adjusted MVs,
8213    /// windows PREVALIDATED per MB at push (contiguous tiles ⇒ the union
8214    /// window is valid) — band = offset copy/avg. Chroma requires mv%8==0
8215    /// (also prevalidated).
8216    Fp { r0: i8, r1: i8, m0: (i16, i16), m1: (i16, i16) },
8217}
8218
8219/// Messages from the parse thread to the pixel worker.
8220enum EdcMsg {
8221    Job(EdcJob),
8222    /// A ROW's worth of pixel jobs in one message (D10).
8223    ///
8224    /// The seam sent ONE message per macroblock: 208k sends per 60-frame pass
8225    /// against 2,596 rows. The overhead is per-JOB (channel lock, park/unpark)
8226    /// while the prize is proportional to pixel WORK, so the per-job send was
8227    /// dividing the payoff by ~80 for nothing. Batching per row cuts the
8228    /// synchronisation events by that factor and moves not one byte of pixel
8229    /// work off the worker.
8230    ///
8231    /// ORDER IS THE CORRECTNESS CONDITION: the worker must see a row's jobs
8232    /// before that row's `Row` filter message, so the batch is flushed at every
8233    /// row boundary, before `NeedCtx`, and at slice end.
8234    Batch(Vec<EdcJob>),
8235    /// A macroblock row finished parsing: install its qp/t8/bs and filter it.
8236    Row {
8237        r: usize,
8238        bs: Vec<rusty_h264_common::deblock::MbBs>,
8239        qp: Vec<u8>,
8240        t8: Vec<bool>,
8241    },
8242    /// An intra macroblock needs the planes on the parse thread: send the
8243    /// context over and wait for it to come back.
8244    NeedCtx,
8245}
8246
8247pub(crate) struct PixelCtx {
8248    rec_y: Vec<u8>,
8249    rec_u: Vec<u8>,
8250    rec_v: Vec<u8>,
8251    bak_y: Vec<u8>,
8252    bak_u: Vec<u8>,
8253    bak_v: Vec<u8>,
8254    refs: Vec<crate::Ref>,
8255    refs1: Vec<crate::Ref>,
8256    weights: Option<WeightTable>,
8257    /// `weights.list_identity(0)`, cached - mirrors `FrameDecoder::weights_l0id`
8258    /// so the worker twin does not have to walk the table per macroblock.
8259    weights_l0id: bool,
8260    scaling: Option<[[i32; 16]; 6]>,
8261    scaling8: Option<[[i32; 64]; 2]>,
8262    cw: usize,
8263    ccw: usize,
8264    mb_w: usize,
8265    mb_h: usize,
8266    chroma_qp_offset: i32,
8267    flt_rows: usize,
8268    db_ena: bool,
8269    db_oa: i32,
8270    db_ob: i32,
8271    cur_qp: u8,
8272    qp_grid: Vec<u8>,
8273    t8_grid: Vec<bool>,
8274    bs_store: Vec<rusty_h264_common::deblock::MbBs>,
8275    /// Frame-MT Phase B progress Arc (row publish from the EDC worker).
8276    progress: Option<crate::Ref>,
8277}
8278
8279impl PixelCtx {
8280    /// Frame-MT Phase B: copy filtered MB rows into the shared progress Arc.
8281    fn publish_progress_rows(&self) {
8282        let Some(slot) = &self.progress else {
8283            return;
8284        };
8285        if !self.db_ena || self.flt_rows == 0 {
8286            return;
8287        }
8288        publish_filtered_rows_to_slot(
8289            slot,
8290            &self.rec_y,
8291            &self.rec_u,
8292            &self.rec_v,
8293            self.cw,
8294            self.ccw,
8295            self.mb_h * 16,
8296            self.flt_rows,
8297        );
8298    }
8299
8300    fn chroma_qp_for(&self, qp: u8) -> u8 {
8301        rusty_h264_common::predict::chroma_qp(
8302            ((qp as i32 + self.chroma_qp_offset).clamp(0, 51)) as u8,
8303        )
8304    }
8305
8306    fn dequant(&self, levels: &[i32; 16], qp: u8, list: usize) -> [i32; 16] {
8307        match &self.scaling {
8308            Some(sc) => dequantize_weighted(levels, qp, &sc[list]),
8309            None => dequantize(levels, qp),
8310        }
8311    }
8312
8313    fn dequant_dc4(&self, level: i32, qp: u8, list: usize) -> i32 {
8314        rusty_h264_common::transform::dequantize_dc4(
8315            level,
8316            qp,
8317            self.scaling.as_ref().map(|sc| sc[list][0]),
8318        )
8319    }
8320
8321    fn inv_quant8(&self, raster: &[i32; 64], qp: u8, list: usize) -> [i32; 64] {
8322        match &self.scaling8 {
8323            Some(sc) => inverse_quant_8x8(raster, qp, &sc[list]),
8324            None => inverse_quant_8x8(raster, qp, &[16i32; 64]),
8325        }
8326    }
8327
8328    fn dequant_chroma_dc(&self, levels: &[i32; 4], qp: u8, list: usize) -> [i32; 4] {
8329        match &self.scaling {
8330            Some(sc) => inverse_quant_chroma_dc_weighted(levels, qp, sc[list][0]),
8331            None => inverse_quant_chroma_dc(levels, qp),
8332        }
8333    }
8334
8335    fn weight_partition(
8336        &self,
8337        pred_y: &mut [u8; 256],
8338        c_pred: &mut [[u8; 64]; 2],
8339        list: usize,
8340        refi: usize,
8341        rx: usize,
8342        ry: usize,
8343        rw: usize,
8344        rh: usize,
8345    ) {
8346        let Some(wt) = &self.weights else { return };
8347        // REFUTED, reverted: row-slicing these loops measured +28% instructions
8348        // on `weight_partition` (the extents are runtime values, so the slice
8349        // bounds cost more than the per-sample checks they replaced). The real
8350        // win for this function was hoisting the identity test to its CALLERS,
8351        // which stands.
8352        // RESOLVE ONCE, APPLY MANY. `apply_luma` re-read `self.luma[list][refi]`
8353        // — an array index, a Vec deref and an element index, two of them bounds
8354        // checked — on EVERY sample, up to 256 luma plus 128 chroma per call.
8355        // The weight is a property of the partition, not of the pixel. (The loop
8356        // SHAPE is untouched: row-slicing it is refuted above.)
8357        // MASKED, not row-sliced. `pred_y` is a fixed `[u8; 256]` and `c_pred` a
8358        // `[[u8; 64]; 2]`, so `& 255` / `& 63` are no-ops that prove the index
8359        // outright — where SLICING these loops cost +28% (the extents are runtime
8360        // values, so the slice bounds outweigh the checks). Same lesson as
8361        // `luma_centre`: the refutation was of one SHAPE, not of the goal.
8362        let (lw, lo) = wt.luma_wo(list, refi);
8363        for dy in 0..rh {
8364            for dx in 0..rw {
8365                let i = ((ry + dy) * 16 + (rx + dx)) & 255;
8366                pred_y[i] = wt.apply_luma_wo(pred_y[i], lw, lo);
8367            }
8368        }
8369        let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
8370        for cc in 0..2 {
8371            let (cw, co) = wt.chroma_wo(list, refi, cc);
8372            let plane = &mut c_pred[cc & 1];
8373            for dy in 0..crh {
8374                for dx in 0..crw {
8375                    let i = ((cry + dy) * 8 + (crx + dx)) & 63;
8376                    plane[i] = wt.apply_chroma_wo(plane[i], cw, co);
8377                }
8378            }
8379        }
8380    }
8381
8382        fn recon_p_inter(&mut self, j: &PInterJob) {
8383        crate::RefFrame::set_mc_row_need(j.mby, self.mb_h * 16);
8384        // `add_inter_residual` reads `self.cur_qp`; unlike the FrameDecoder copy
8385        // (which interleaves with parsing and must save/restore), every PixelCtx
8386        // job sets it from the job before any reader, so no restore is needed.
8387        self.cur_qp = j.qp;
8388                    // ---- Recon: motion-comp (per 4×4 luma / co-located 2×2 chroma using the
8389                    // committed grid MV — the 6-tap/bilinear filter is per-output-pixel, so
8390                    // per-block MC is bit-identical to per-partition MC) + residual add via the
8391                    // SAME reconstruct_4x4 as intra, with the MC output as the prediction.
8392                    let mut pred_y = [0u8; 256];
8393                    let mut c_pred = [[0u8; 64]; 2];
8394                    {
8395                        // MC-CALL COALESCING (side-by-side descent, dec target #2): the old
8396                        // loop paid 16 mc_luma(4×4) + 32 mc_chroma(2×2) per MB regardless of
8397                        // partitioning — 48 calls even for a single-MV 16×16 MB, and the
8398                        // per-call glue around 2.4M calls was ~40% of decoding real-world
8399                        // (x264) streams. The 6-tap/bilinear filters are per-output-pixel,
8400                        // so merging blocks with equal (mv, ref) into one wider MC call is
8401                        // BIT-IDENTICAL; the rect ladder mirrors the partition shapes.
8402                        let _ms = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMcStage);
8403                        let (rh16, cch) = (self.mb_h * 16, self.mb_h * 8);
8404                        // E2: the worker owns no syntax grids — the job carries the
8405                        // committed per-block motion (filled at parse time).
8406                        let gmv = j.gmv;
8407                        let mut gref = [0usize; 16];
8408                        for k in 0..16 {
8409                            gref[k] = (j.gref[k] as usize).min(self.refs.len() - 1);
8410                        }
8411                        // All blocks of the rect (in 4×4-block units) match its top-left?
8412                        let rect_eq = |x4: usize, y4: usize, w4: usize, h4: usize| -> bool {
8413                            let t = y4 * 4 + x4;
8414                            (0..h4).all(|dy| {
8415                                (0..w4).all(|dx| {
8416                                    let b = ((y4 + dy) * 4 + (x4 + dx)) & 15;
8417                                    gmv[b] == gmv[t] && gref[b] == gref[t]
8418                                })
8419                            })
8420                        };
8421                        let refs = &self.refs;
8422                        let (cw, ccw) = (self.cw, self.ccw);
8423                        let mc_rect = |x4: usize,
8424                                           y4: usize,
8425                                           w4: usize,
8426                                           h4: usize,
8427                                           pred_y: &mut [u8; 256],
8428                                           c_pred: &mut [[u8; 64]; 2]| {
8429                            let b = y4 * 4 + x4;
8430                            let (mv, gr) = (gmv[b & 15], gref[b & 15]);
8431                            // `gref` holds a reference-list slot; a slot the list
8432                            // does not have means there is nothing to predict from,
8433                            // so the rect is left as it stands.
8434                            let Some(reference) = refs.get(gr) else { return };
8435                            let (w, h) = (w4 * 4, h4 * 4);
8436                            // A FULL-WIDTH rect (w == 16, so x4 == 0) occupies contiguous
8437                            // whole rows of `pred_y` — the MC output layout and the
8438                            // destination layout coincide, so MC writes the prediction
8439                            // buffer DIRECTLY. The staging copy exists only for narrow
8440                            // rects, whose rows really are strided in `pred_y`. This is
8441                            // the diagnosis's "stage-boundary materialization" tax paid
8442                            // by the dominant 16×16/16×8 shapes: 256 B of `t` zeroing
8443                            // plus a 256 B copy per rect, for nothing.
8444                            if w == 16 {
8445                                rusty_h264_common::inter::with_mc_scratch(|scr| rusty_h264_common::inter::mc_luma_padded_pre(scr, &*reference.luma_guard(reference.ch), reference.lstride(), crate::LPAD, cw, rh16, j.mbx * 16, j.mby * 16 + y4 * 4, w, h, mv.0, mv.1, &mut pred_y[y4 * 64..y4 * 64 + w * h]));
8446                            } else {
8447                                let mut t = [0u8; 256];
8448                                rusty_h264_common::inter::with_mc_scratch(|scr| rusty_h264_common::inter::mc_luma_padded_pre(scr, &*reference.luma_guard(reference.ch), reference.lstride(), crate::LPAD, cw, rh16, j.mbx * 16 + x4 * 4, j.mby * 16 + y4 * 4, w, h, mv.0, mv.1, &mut t[..w * h]));
8449                                let _pb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
8450                                for dy in 0..h {
8451                                    pred_y[(y4 * 4 + dy) * 16 + x4 * 4..][..w]
8452                                        .copy_from_slice(&t[dy * w..dy * w + w]);
8453                                }
8454                            }
8455                            let (cw4, ch4) = (w4 * 2, h4 * 2);
8456                            let nc = cw4 * ch4;
8457                            let (gu, gv) = (reference.chroma_guard(0, reference.ch), reference.chroma_guard(1, reference.ch));
8458                            // U+V paired: one setup serves both planes (see
8459                            // mc_chroma_padded_pair). Full-width coincidence:
8460                            // cw4 == 8 rows are contiguous in the 8-wide plane.
8461                            if cw4 == 8 {
8462                                let [cu, cv] = &mut *c_pred;
8463                                rusty_h264_common::inter::mc_chroma_padded_pair(&gu, &gv, reference.cstride(), crate::CPAD, ccw, cch, j.mbx * 8, j.mby * 8 + y4 * 2, cw4, ch4, mv.0, mv.1, &mut cu[y4 * 16..y4 * 16 + nc], &mut cv[y4 * 16..y4 * 16 + nc]);
8464                            } else {
8465                                let (mut tu, mut tv) = ([0u8; 64], [0u8; 64]);
8466                                rusty_h264_common::inter::mc_chroma_padded_pair(&gu, &gv, reference.cstride(), crate::CPAD, ccw, cch, j.mbx * 8 + x4 * 2, j.mby * 8 + y4 * 2, cw4, ch4, mv.0, mv.1, &mut tu[..nc], &mut tv[..nc]);
8467                                let _pb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
8468                                for (cc, tc) in [(0usize, &tu), (1, &tv)] {
8469                                    for dy in 0..ch4 {
8470                                        c_pred[cc][(y4 * 2 + dy) * 8 + x4 * 2..][..cw4]
8471                                            .copy_from_slice(&tc[dy * cw4..dy * cw4 + cw4]);
8472                                    }
8473                                }
8474                            }
8475                        };
8476                        if rect_eq(0, 0, 4, 4) {
8477                            mc_rect(0, 0, 4, 4, &mut pred_y, &mut c_pred);
8478                        } else if rect_eq(0, 0, 4, 2) && rect_eq(0, 2, 4, 2) {
8479                            mc_rect(0, 0, 4, 2, &mut pred_y, &mut c_pred);
8480                            mc_rect(0, 2, 4, 2, &mut pred_y, &mut c_pred);
8481                        } else if rect_eq(0, 0, 2, 4) && rect_eq(2, 0, 2, 4) {
8482                            mc_rect(0, 0, 2, 4, &mut pred_y, &mut c_pred);
8483                            mc_rect(2, 0, 2, 4, &mut pred_y, &mut c_pred);
8484                        } else {
8485                            for q in 0..4usize {
8486                                let (qx, qy) = ((q % 2) * 2, (q / 2) * 2);
8487                                if rect_eq(qx, qy, 2, 2) {
8488                                    mc_rect(qx, qy, 2, 2, &mut pred_y, &mut c_pred);
8489                                } else if rect_eq(qx, qy, 2, 1) && rect_eq(qx, qy + 1, 2, 1) {
8490                                    mc_rect(qx, qy, 2, 1, &mut pred_y, &mut c_pred);
8491                                    mc_rect(qx, qy + 1, 2, 1, &mut pred_y, &mut c_pred);
8492                                } else if rect_eq(qx, qy, 1, 2) && rect_eq(qx + 1, qy, 1, 2) {
8493                                    mc_rect(qx, qy, 1, 2, &mut pred_y, &mut c_pred);
8494                                    mc_rect(qx + 1, qy, 1, 2, &mut pred_y, &mut c_pred);
8495                                } else {
8496                                    for j in 0..4usize {
8497                                        mc_rect(qx + (j % 2), qy + (j / 2), 1, 1, &mut pred_y, &mut c_pred);
8498                                    }
8499                                }
8500                            }
8501                        }
8502                        // EXPLICIT WEIGHTED PREDICTION (spec 8.4.2.3). The CAVLC inter
8503                        // path weights each partition after MC; the MC-call-coalescing
8504                        // rewrite of this CABAC path lost it, and nothing caught that
8505                        // because the effect is invisible unless a stream actually
8506                        // carries non-default weights. x264's `weightp` DUPLICATES a
8507                        // reference and distinguishes the copy ONLY by its weights, so
8508                        // every macroblock picking the weighted index decoded unweighted
8509                        // -- a silent, accumulating luma drift.
8510                        //
8511                        // Applied per 4x4 block rather than per partition: the weight
8512                        // depends solely on the block's reference index, so the two are
8513                        // equivalent, and `gref` already holds it for every block
8514                        // regardless of which rect ladder rung ran.
8515                        if self.weights.is_some() {
8516                            for by in 0..4usize {
8517                                for bx in 0..4usize {
8518                                    let refi = gref[by * 4 + bx];
8519                                    self.weight_partition(
8520                                        &mut pred_y, &mut c_pred, 0, refi, bx * 4, by * 4, 4, 4,
8521                                    );
8522                                }
8523                            }
8524                        }
8525                    }
8526                    // Residual add — the SAME helper the B path uses (this inline
8527                    // copy was a duplicate; deduped when the zero-block fast path
8528                    // landed so both paths share it).
8529                    self.add_inter_residual(j.mbx, j.mby, &pred_y, &c_pred, j.luma_scan.as_ref(), j.luma8.as_ref(), &j.cdc, j.cac.as_ref(), j.cbp_chroma, &j.nnzs);
8530    }
8531
8532    /// D9b: P inter with `cbp == 0` — MC + plane copy (worker twin of FrameDecoder).
8533    fn recon_p_inter_nores(&mut self, j: &PInterNoResJob) {
8534        let mut pred_y = [0u8; 256];
8535        let mut c_pred = [[0u8; 64]; 2];
8536        // `self.refs.len() - 1` was re-loaded on every one of the sixteen
8537        // iterations; it is a per-macroblock invariant. Written once, not
8538        // zeroed-then-filled.
8539        let nrefs = self.refs.len() - 1;
8540        let gref: [usize; 16] = core::array::from_fn(|k| (j.gref[k] as usize).min(nrefs));
8541        coalesce_p_inter_mc(
8542            &self.refs,
8543            self.cw,
8544            self.ccw,
8545            self.mb_h,
8546            j.mbx,
8547            j.mby,
8548            &j.gmv,
8549            &gref,
8550            &mut pred_y,
8551            &mut c_pred,
8552        );
8553        // The identity early-out lives INSIDE `weight_partition`, so an x264
8554        // stream - which carries a pred_weight_table in EVERY P slice, identity
8555        // outside fades - still paid sixteen calls per macroblock to be told
8556        // there was nothing to do. Hoisted to one test.
8557        if self.weights.is_some() && !self.weights_l0id {
8558            for by in 0..4usize {
8559                for bx in 0..4usize {
8560                    let refi = gref[by * 4 + bx];
8561                    self.weight_partition(&mut pred_y, &mut c_pred, 0, refi, bx * 4, by * 4, 4, 4);
8562                }
8563            }
8564        }
8565        // ONE span per plane: the destination is a stack of contiguous runs a
8566        // fixed stride apart, so the rows share a single bounds check instead of
8567        // one each (16 + 8 + 8 of them).
8568        let (cw, ccw) = (self.cw, self.ccw);
8569        let ybase = (j.mby * 16) * cw + j.mbx * 16;
8570        let ywin = &mut self.rec_y[ybase..ybase + 15 * cw + 16];
8571        for dy in 0..16 {
8572            ywin[dy * cw..dy * cw + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
8573        }
8574        let cbase = (j.mby * 8) * ccw + j.mbx * 8;
8575        for c in 0..2 {
8576            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
8577            let cwin = &mut plane[cbase..cbase + 7 * ccw + 8];
8578            for dy in 0..8 {
8579                cwin[dy * ccw..dy * ccw + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
8580            }
8581        }
8582    }
8583
8584    fn recon_p_skip(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32)) {
8585        crate::RefFrame::set_mc_row_need(mb_y, self.mb_h * 16);
8586        let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
8587
8588        let mut pred = [0u8; 256];
8589        let Some(rf0) = self.refs.first() else { return };
8590        mc_luma_padded(&*rf0.luma_guard(rf0.ch), rf0.lstride(), crate::LPAD, self.cw, ch, mb_x * 16, mb_y * 16, 16, 16, mv.0, mv.1, &mut pred);
8591        if let Some(wt) = &self.weights {
8592            for p in pred.iter_mut() {
8593                *p = wt.apply_luma(*p, 0, 0);
8594            }
8595        }
8596        {
8597            let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
8598            // ONE span for the sixteen rows (see `recon_p_inter_nores`).
8599            let (cw, base) = (self.cw, (mb_y * 16) * self.cw + mb_x * 16);
8600            let win = &mut self.rec_y[base..base + 15 * cw + 16];
8601            for dy in 0..16 {
8602                win[dy * cw..dy * cw + 16].copy_from_slice(&pred[dy * 16..dy * 16 + 16]);
8603            }
8604        }
8605        for c in 0..2 {
8606            let mut pc = [0u8; 64];
8607            let Some(rf0) = self.refs.first() else { return };
8608            let rc = if c == 0 { &*rf0.chroma_guard(0, rf0.ch) } else { &*rf0.chroma_guard(1, rf0.ch) };
8609            mc_chroma_padded(rc, rf0.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8, mb_y * 8, 8, 8, mv.0, mv.1, &mut pc);
8610            if let Some(wt) = &self.weights {
8611                for p in pc.iter_mut() {
8612                    *p = wt.apply_chroma(*p, 0, 0, c);
8613                }
8614            }
8615            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
8616            for dy in 0..8 {
8617                let d = (mb_y * 8 + dy) * self.ccw + mb_x * 8;
8618                plane[d..d + 8].copy_from_slice(&pc[dy * 8..dy * 8 + 8]);
8619            }
8620        }
8621    }
8622
8623    fn add_inter_residual(
8624        &mut self,
8625        mb_x: usize,
8626        mb_y: usize,
8627        pred_y: &[u8; 256],
8628        c_pred: &[[u8; 64]; 2],
8629        luma_scan: Option<&[[i32; 16]; 16]>,
8630        // `Some` when the macroblock carries transform_size_8x8_flag: four 8x8
8631        // blocks in 8x8 scan order, replacing the sixteen 4x4 luma blocks.
8632        luma8: Option<&[[i32; 64]; 4]>,
8633        cdc: &[[i32; 4]; 2],
8634        cac: Option<&[[[i32; 16]; 4]; 2]>,
8635        cbp_chroma: u32,
8636        // Parsed totalCoeff per block, indexed exactly as the parse's `iz`:
8637        // [0..16] luma 4x4 z-order (for t8, the 8x8 count sits at `id8*4`),
8638        // [16..24] chroma AC as `16 + c*4 + id4`. The parser already counted
8639        // every significant coefficient; re-deriving the counts here scanned
8640        // 16-64 array elements per block (~400 loads/MB) for information the
8641        // caller was holding — the diagnosis's stage-boundary re-derivation tax.
8642        nnzs: &[u8; 24],
8643    ) {
8644        // A `None` field means the parse wrote nothing there; every read below
8645        // is guarded by a zero-count test, so the shared zero plane is
8646        // read-equivalent to the per-macroblock zeroed stack array it replaces.
8647        let luma_scan = luma_scan.unwrap_or(&ZERO_LUMA_SCAN);
8648        let cac = cac.unwrap_or(&ZERO_CAC);
8649        let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecResidAdd);
8650        let qp = self.cur_qp;
8651        let qpc = self.chroma_qp_for(qp);
8652        if let Some(l8) = luma8 {
8653            // INTER 8x8 luma: same primitives the I_8x8 and CAVLC paths use.
8654            for b8 in 0..4usize {
8655                let (b8x, b8y) = (b8 % 2, b8 / 2);
8656                // Summed, not slot 0: with per-4x4 counts in the CAVLC case, slot 0
8657                // is only the first sub-block and can be 0 while the block is coded.
8658                let nnz: u32 = (0..4).map(|k| nnzs[b8 * 4 + k] as u32).sum();
8659                let (px, py) = (mb_x * 16 + b8x * 8, mb_y * 16 + b8y * 8);
8660                if nnz == 0 {
8661                    // Zero residual: recon == pred — same shortcut as the
8662                    // FrameDecoder twin.
8663                    edcstat::bump(&edcstat::T8_ZERO, 1);
8664                    for dy in 0..8 {
8665                        let d = (py + dy) * self.cw + px;
8666                        let po = (b8y * 8 + dy) * 16 + b8x * 8;
8667                        self.rec_y[d..d + 8].copy_from_slice(&pred_y[po..po + 8]);
8668                    }
8669                } else {
8670                    let raster = un_scan_8x8(&l8[b8]);
8671                    // list 1 = INTER 8x8 luma scaling list (0 is the intra one).
8672                    let res8 = self.inv_quant8(&raster, qp, 1);
8673                    let predb: [i32; 64] =
8674                        std::array::from_fn(|i| pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32);
8675                    let recon = add_residual_8x8(&res8, &predb);
8676                    // Eight row copies. This wrote SIXTY-FOUR individually
8677                    // bounds-checked samples into the luma plane per coded 8x8
8678                    // block — the same shape whose fix carried `recon_i8_block`.
8679                    // Present in BOTH the main path and the EDC worker twin.
8680                    for dy in 0..8 {
8681                        let d = (py + dy) * self.cw + px;
8682                        self.rec_y[d..][..8].copy_from_slice(&recon[dy * 8..][..8]);
8683                    }
8684                }
8685            }
8686        }
8687        for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
8688            if luma8.is_some() {
8689                break;
8690            }
8691            let nnz = nnzs[blk];
8692            let cw = self.cw;
8693            let p_off = (lby * 4) * 16 + lbx * 4;
8694            let r_off = (mb_y * 4 + lby) * 4 * cw + (mb_x * 4 + lbx) * 4;
8695            if nnz == 0 {
8696                // Zero residual → recon == prediction EXACTLY (the integer IDCT is
8697                // linear so zeros map to zeros, and pred is already 0..=255) — copy
8698                // the pred rows straight into the plane. On real (sparse-cbp)
8699                // streams this is MOST of the 4×4 blocks.
8700                for r in 0..4 {
8701                    self.rec_y[r_off + r * cw..r_off + r * cw + 4]
8702                        .copy_from_slice(&pred_y[p_off + r * 16..p_off + r * 16 + 4]);
8703                }
8704                continue;
8705            }
8706            // DC-ONLY: the sole significant coefficient is scan position 0 (the
8707            // zig-zag starts at DC, and un_scan keeps it at raster 0), so the
8708            // whole dequant + IDCT collapses to one multiply and a flat add.
8709            if nnz == 1 && luma_scan[blk][0] != 0 {
8710                let f = self.dequant_dc4(luma_scan[blk][0], qp, 3);
8711                reconstruct_4x4_dc_into((f + 32) >> 6, pred_y, p_off, 16, &mut self.rec_y, r_off, cw);
8712            } else {
8713                // Fused un-scan + dequant over ONLY the significant coefficients,
8714                // then IDCT + add + clip straight into the plane — no `qb`, no
8715                // `deq`-from-dense, no `predb` gather, no `s`, no `store` call.
8716                //
8717                // HYBRID: the scatter walks scan positions with a data-dependent
8718                // branch per slot, which beats the branchless dense 16-multiply
8719                // loop only while the block is SPARSE. The DC/zero fast paths
8720                // already removed the sparsest blocks, so the population here
8721                // skews denser — above ~6 coefficients the dense loop wins.
8722                let deq = if nnz <= 6 {
8723                    dequant_scatter_4x4(&luma_scan[blk], nnz, 0, qp, self.scaling.as_ref().map(|sc| &sc[3]))
8724                } else {
8725                    self.dequant(&un_scan_4x4_dcac(&luma_scan[blk]), qp, 3)
8726                };
8727                reconstruct_4x4_into(&deq, pred_y, p_off, 16, &mut self.rec_y, r_off, cw);
8728            }
8729        }
8730        let mut c_dc = [[0i32; 4]; 2];
8731        if cbp_chroma != 0 {
8732            for c in 0..2 {
8733                c_dc[c] = self.dequant_chroma_dc(&cdc[c], qpc, 4 + c);
8734            }
8735        }
8736        let ccw = self.ccw;
8737        for c in 0..2 {
8738            for &(bx, by) in &CHROMA_4X4_SCAN_XY {
8739                let mut ac_nz = false;
8740                if cbp_chroma == 2 {
8741                    let n = nnzs[(16 + c * 4 + by * 2 + bx).min(23)];
8742                    ac_nz = n != 0;
8743                }
8744                let dc = c_dc[c & 1][(by * 2 + bx) & 3];
8745                let p_off = (by * 4) * 8 + bx * 4;
8746                let r_off = (mb_y * 2 + by) * 4 * ccw + (mb_x * 2 + bx) * 4;
8747                let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
8748                if dc == 0 && !ac_nz {
8749                    // Zero residual (no AC, zero DC) → recon == prediction exactly.
8750                    for r in 0..4 {
8751                        plane[r_off + r * ccw..r_off + r * ccw + 4]
8752                            .copy_from_slice(&c_pred[c][p_off + r * 8..p_off + r * 8 + 4]);
8753                    }
8754                    continue;
8755                }
8756                // DC-ONLY (no coded AC — covers every cbp_chroma==1 block and the
8757                // AC-empty blocks of cbp_chroma==2): the chroma DC arrives ALREADY
8758                // dequantized, so the residual is `(dc + 32) >> 6` flat.
8759                if !ac_nz {
8760                    reconstruct_4x4_dc_into((dc + 32) >> 6, &c_pred[c], p_off, 8, plane, r_off, ccw);
8761                    continue;
8762                }
8763                // AC-only scan: index i is overall scan position i+1 (ac_shift=1).
8764                // Same sparse/dense hybrid as luma.
8765                let n = nnzs[(16 + c * 4 + by * 2 + bx).min(23)];
8766                let mut deq = if n <= 6 {
8767                    dequant_scatter_4x4(&cac[c & 1][(by * 2 + bx) & 3], n, 1, qpc, self.scaling.as_ref().map(|sc| &sc[4 + c]))
8768                } else {
8769                    let mut ac = [0i32; 16];
8770                    un_scan_4x4_ac_into(&cac[c & 1][(by * 2 + bx) & 3], &mut ac);
8771                    // Free-fn dequant: `self.dequant` borrows all of `self`, which
8772                    // conflicts with the live `plane` (&mut self.rec_u/v) borrow.
8773                    match &self.scaling {
8774                        Some(sc) => dequantize_weighted(&ac, qpc, &sc[4 + c]),
8775                        None => dequantize(&ac, qpc),
8776                    }
8777                };
8778                deq[0] = dc;
8779                reconstruct_4x4_into(&deq, &c_pred[c], p_off, 8, plane, r_off, ccw);
8780            }
8781        }
8782    }
8783
8784    fn filter_row(&mut self, r: usize) {
8785        // The precomputed consumer path reads ONLY `bs` + `t8x8` (+ the qp grid
8786        // passed as a parameter) — verified when the path landed (WHYS Part 15).
8787        let info = rusty_h264_common::deblock::BlockInfo {
8788            inter: &[],
8789            nnz: &[],
8790            mv: &[],
8791            ref_id: &[],
8792            mv1: &[],
8793            ref_id1: &[],
8794            w4: self.mb_w * 4,
8795            t8x8: &self.t8_grid,
8796            bs: &self.bs_store,
8797            poc0: &[],
8798            poc1: &[],
8799            kind: &[],
8800        };
8801        rusty_h264_common::deblock::filter_frame_rows(
8802            &mut self.rec_y,
8803            &mut self.rec_u,
8804            &mut self.rec_v,
8805            self.mb_w,
8806            self.mb_h,
8807            r..r + 1,
8808            &self.qp_grid,
8809            self.chroma_qp_offset,
8810            self.db_oa,
8811            self.db_ob,
8812            &info,
8813        );
8814    }
8815
8816    fn save_bak(&mut self, r: usize) {
8817        let y0 = (r * 16 + 15) * self.cw;
8818        self.bak_y.copy_from_slice(&self.rec_y[y0..y0 + self.cw]);
8819        let c0 = (r * 8 + 7) * self.ccw;
8820        self.bak_u.copy_from_slice(&self.rec_u[c0..c0 + self.ccw]);
8821        self.bak_v.copy_from_slice(&self.rec_v[c0..c0 + self.ccw]);
8822    }
8823}
8824
8825/// The pixel worker: replays jobs in parse order, filters rows as their
8826/// messages arrive, and hands the whole context to the parse thread (and
8827/// back) around intra macroblocks. Returns the context at slice end.
8828/// E2 SEAM COUNTERS (D7). Deterministic — one run is the verdict, no pinning.
8829/// `RS_H264_EDC_STATS=1` prints at decode end. Counts, not clocks: the question
8830/// "does one intra macroblock drain the pipeline" is a COUNT question.
8831pub(crate) mod edcstat {
8832    use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
8833    pub static NEEDCTX: AtomicU64 = AtomicU64::new(0);
8834    pub static JOBS: AtomicU64 = AtomicU64::new(0);
8835    pub static ROWS: AtomicU64 = AtomicU64::new(0);
8836    pub static ROWBYTES: AtomicU64 = AtomicU64::new(0);
8837    pub static MBS: AtomicU64 = AtomicU64::new(0);
8838    pub static J_INTER: AtomicU64 = AtomicU64::new(0);
8839    pub static DOUBLED: AtomicU64 = AtomicU64::new(0);
8840    pub static J_NORES_SENT: AtomicU64 = AtomicU64::new(0);
8841    pub static BATCHES: AtomicU64 = AtomicU64::new(0);
8842    pub static DISPATCH_ON: AtomicU64 = AtomicU64::new(0);
8843    pub static DISPATCH_SEEN: AtomicU64 = AtomicU64::new(0);
8844    /// mb_skip_run band path (big-oppy-decoder §6 KEY glue): P_Skip MBs
8845    /// reconstructed by run-coalesced band copies, and the runs themselves.
8846    /// DETERMINISTIC WIN counters — each banded MB removed 1 luma MC call,
8847    /// 2 chroma MC calls and the 256+128-byte pred staging round-trip.
8848    pub static SKIPBAND_MBS: AtomicU64 = AtomicU64::new(0);
8849    pub static SKIPBAND_RUNS: AtomicU64 = AtomicU64::new(0);
8850    pub static SKIP_SINGLES: AtomicU64 = AtomicU64::new(0);
8851    // MEASUREMENT counters sizing the next skip-band extensions (no behavior).
8852    // P side: singles that sit in a same-row run of >=2 EQUAL nonzero MVs,
8853    // split by full-pel (band-copy-with-offset eligible) vs fractional.
8854    pub static PEQ_FP: AtomicU64 = AtomicU64::new(0);
8855    pub static PEQ_FRAC: AtomicU64 = AtomicU64::new(0);
8856    // B side: full-16x16 spatial-direct calls (B_Skip + direct-16), how many
8857    // collapse to ONE uniform rect, and of those the zero-motion bi/uni and
8858    // full-pel populations + run adjacency (previous MB same params, same row).
8859    pub static BSK_FULLMB: AtomicU64 = AtomicU64::new(0);
8860    pub static BSK_1RECT: AtomicU64 = AtomicU64::new(0);
8861    pub static BSK_ZBI: AtomicU64 = AtomicU64::new(0);
8862    pub static BSK_ZUNI: AtomicU64 = AtomicU64::new(0);
8863    pub static BSK_FP: AtomicU64 = AtomicU64::new(0);
8864    pub static BSK_RUNCONT: AtomicU64 = AtomicU64::new(0);
8865    /// DETERMINISTIC WIN counter — B_Skip MBs taken by the zero-bi fast path.
8866    pub static BSKB_FAST: AtomicU64 = AtomicU64::new(0);
8867    /// P_Skip singles taken by the full-pel direct-copy path (both planes /
8868    /// luma only, chroma still interpolating at mv%8!=0).
8869    pub static SKIP_FP_FULL: AtomicU64 = AtomicU64::new(0);
8870    pub static SKIP_FP_LUMA: AtomicU64 = AtomicU64::new(0);
8871    /// B_Skip full-pel nonzero-MV MBs taken by the offset copy/avg path.
8872    pub static BSKB_FP: AtomicU64 = AtomicU64::new(0);
8873    /// P_Skip parse side: MBs whose skip MV was FORCED (0,0) by the run
8874    /// theorem (left = just-committed (0,0) skip, or out-of-slice) vs MBs
8875    /// that ran the 3-neighbor gather + rule.
8876    pub static SKIPMV_FORCED: AtomicU64 = AtomicU64::new(0);
8877    pub static SKIPMV_DERIVED: AtomicU64 = AtomicU64::new(0);
8878    /// B_Skips whose zero-bi derivation was FORCED by the known-zero bitmap
8879    /// (left+top+topright all recorded ref0/(0,0)-both-lists) — the 6-gather
8880    /// b_direct_nbrs walk skipped entirely.
8881    pub static BSKB_FORCED: AtomicU64 = AtomicU64::new(0);
8882    /// Zero-bi fast B_Skip grid commits batched into row spans: MBs covered
8883    /// and spans flushed (fills of 4N replace N MBs x 28 per-MB fills).
8884    pub static BZ_SPAN_MBS: AtomicU64 = AtomicU64::new(0);
8885    pub static BZ_SPANS: AtomicU64 = AtomicU64::new(0);
8886    /// P_Skip (0,0) grid-commit spans (parse-side mirror of BZ_*).
8887    pub static PZ_SPAN_MBS: AtomicU64 = AtomicU64::new(0);
8888    pub static PZ_SPANS: AtomicU64 = AtomicU64::new(0);
8889    /// Census: b_mc bi-pred regions where BOTH MVs are full-pel (fusable to a
8890    /// direct offset average — sizing only, no behavior).
8891    pub static BMC_BI_FP: AtomicU64 = AtomicU64::new(0);
8892    /// weight_partition passes skipped because the whole list-0 table is
8893    /// identity (384 apply ops avoided per full-MB pass).
8894    pub static WP_SKIPPED: AtomicU64 = AtomicU64::new(0);
8895    /// Intra I_4x4 luma residual dispatch (the inter ladder, ported): how many
8896    /// blocks took the zero / DC-only / sparse-scatter / dense arms.
8897    pub static I4_ZERO: AtomicU64 = AtomicU64::new(0);
8898    pub static I4_DC: AtomicU64 = AtomicU64::new(0);
8899    pub static I4_SPARSE: AtomicU64 = AtomicU64::new(0);
8900    pub static I4_DENSE: AtomicU64 = AtomicU64::new(0);
8901    /// 8x8 residual blocks (intra + inter t8) whose residual is entirely zero:
8902    /// recon == pred, the 64-px widen + add + clip skipped.
8903    pub static T8_ZERO: AtomicU64 = AtomicU64::new(0);
8904    /// I_16x16 4x4 sub-blocks with zero AC: the residual is the (already
8905    /// Hadamard-transformed) DC alone, so the dense dequant + IDCT collapse
8906    /// to one flat add.
8907    pub static I16_DCONLY: AtomicU64 = AtomicU64::new(0);
8908    pub static J_INTER_NORES: AtomicU64 = AtomicU64::new(0);
8909    // ---- deb:derive census (derive_bs_row) — sizing the bS derivation arms.
8910    /// Rows derived, and macroblocks derived across them.
8911    pub static DBS_ROWS: AtomicU64 = AtomicU64::new(0);
8912    pub static DBS_MB: AtomicU64 = AtomicU64::new(0);
8913    /// Arm split: Intra kind-arm / Skip|InterUniform kind-arm (only under
8914    /// RS_H264_KIND_LOADS=1) / the packed `derive_mb_records` default.
8915    pub static DBS_INTRA: AtomicU64 = AtomicU64::new(0);
8916    pub static DBS_KINDARM: AtomicU64 = AtomicU64::new(0);
8917    /// Macroblocks whose kind is Skip|InterUniform: the population that used
8918    /// to evaluate the `kind_loads()` OnceLock guard once EACH.
8919    pub static DBS_KINDGUARD: AtomicU64 = AtomicU64::new(0);
8920    pub static DBS_PACKED: AtomicU64 = AtomicU64::new(0);
8921    /// Of the packed arm: macroblocks whose derivation returned `flat_inter`
8922    /// (internal strengths 0 by construction — the early-return class).
8923    pub static DBS_FLAT: AtomicU64 = AtomicU64::new(0);
8924    /// Macroblocks whose FINAL stored MbBs is all-zero: the population the
8925    /// consumer's two-16-byte-compare early-out serves, and the denominator
8926    /// for the per-MB 128-byte bs_v/bs_h zero-init that precedes it.
8927    pub static DBS_ALLZERO: AtomicU64 = AtomicU64::new(0);
8928    /// nnz_dbr maintenance: rows copied wholesale vs rows that actually
8929    /// carried a transform-8x8 macroblock needing the 8x8 OR fixup.
8930    pub static DBS_NNZ_ROWCOPY: AtomicU64 = AtomicU64::new(0);
8931    pub static DBS_T8ROW: AtomicU64 = AtomicU64::new(0);
8932    pub static DBS_T8MB: AtomicU64 = AtomicU64::new(0);
8933    /// Rows that ran the disable_deblocking_filter_idc==2 crossing-edge pass.
8934    pub static DBS_IDC2ROW: AtomicU64 = AtomicU64::new(0);
8935    #[inline]
8936    pub fn bump(c: &AtomicU64, n: u64) {
8937        if on() {
8938            c.fetch_add(n, Relaxed);
8939        }
8940    }
8941    /// Off by default, yet read on EVERY bump — up to four times per B_Skip.
8942    /// `OnceLock::get_or_init` is an acquire load plus an initialised-state
8943    /// branch behind a non-inlined call; this is the relaxed AtomicU8 tri-state
8944    /// the other knobs in this file already use, and it inlines to one load.
8945    #[inline]
8946    pub fn on() -> bool {
8947        use std::sync::atomic::AtomicU8;
8948        static V: AtomicU8 = AtomicU8::new(0);
8949        match V.load(Relaxed) {
8950            1 => true,
8951            2 => false,
8952            _ => {
8953                let b = std::env::var_os("RS_H264_EDC_STATS").is_some();
8954                V.store(if b { 1 } else { 2 }, Relaxed);
8955                b
8956            }
8957        }
8958    }
8959    pub fn report() {
8960        if !on() {
8961            return;
8962        }
8963        eprintln!(
8964            "EDCDISPATCH threaded_slices={} eligible_slices={}",
8965            DISPATCH_ON.load(Relaxed), DISPATCH_SEEN.load(Relaxed)
8966        );
8967        eprintln!(
8968            "EDCSIZE EdcMsg={} EdcJob={} PInterJob={} BJob={}",
8969            std::mem::size_of::<super::EdcMsg>(),
8970            std::mem::size_of::<super::EdcJob>(),
8971            std::mem::size_of::<super::PInterJob>(),
8972            std::mem::size_of::<super::BJob>(),
8973        );
8974        let (n, j, r, b, m) = (
8975            NEEDCTX.load(Relaxed), JOBS.load(Relaxed), ROWS.load(Relaxed),
8976            ROWBYTES.load(Relaxed), MBS.load(Relaxed),
8977        );
8978        eprintln!(
8979            "EDCSTAT needctx={n} jobs={j} rows={r} rowbytes={b} mbs={m} batches={} jobs_per_batch={:.1} needctx_per_1k_mb={:.1} jobs_per_needctx={:.1}",
8980            BATCHES.load(Relaxed),
8981            j as f64 / BATCHES.load(Relaxed).max(1) as f64,
8982            1000.0 * n as f64 / m.max(1) as f64,
8983            j as f64 / n.max(1) as f64
8984        );
8985        eprintln!(
8986            "SKIPRUN band_mbs={} band_runs={} mbs_per_run={:.1} singles={} banded_pct={:.1}",
8987            SKIPBAND_MBS.load(Relaxed),
8988            SKIPBAND_RUNS.load(Relaxed),
8989            SKIPBAND_MBS.load(Relaxed) as f64 / SKIPBAND_RUNS.load(Relaxed).max(1) as f64,
8990            SKIP_SINGLES.load(Relaxed),
8991            100.0 * SKIPBAND_MBS.load(Relaxed) as f64
8992                / (SKIPBAND_MBS.load(Relaxed) + SKIP_SINGLES.load(Relaxed)).max(1) as f64,
8993        );
8994        eprintln!(
8995            "SKIPNEXT peq_fp={} peq_frac={} bsk_fullmb={} bsk_1rect={} bsk_zbi={} bsk_zuni={} bsk_fp={} bsk_runcont={} bskb_fast={} skip_fp_full={} skip_fp_luma={} bskb_fp={} skipmv_forced={} skipmv_derived={} bskb_forced={} bz_spans={} bz_span_mbs={} pz_spans={} pz_span_mbs={} bmc_bi_fp={} wp_skipped={} i4_zero={} i4_dc={} i4_sparse={} i4_dense={} t8_zero={} i16_dconly={}",
8996            PEQ_FP.load(Relaxed), PEQ_FRAC.load(Relaxed),
8997            BSK_FULLMB.load(Relaxed), BSK_1RECT.load(Relaxed),
8998            BSK_ZBI.load(Relaxed), BSK_ZUNI.load(Relaxed),
8999            BSK_FP.load(Relaxed), BSK_RUNCONT.load(Relaxed),
9000            BSKB_FAST.load(Relaxed),
9001            SKIP_FP_FULL.load(Relaxed), SKIP_FP_LUMA.load(Relaxed),
9002            BSKB_FP.load(Relaxed),
9003            SKIPMV_FORCED.load(Relaxed), SKIPMV_DERIVED.load(Relaxed),
9004            BSKB_FORCED.load(Relaxed),
9005            BZ_SPANS.load(Relaxed), BZ_SPAN_MBS.load(Relaxed),
9006            PZ_SPANS.load(Relaxed), PZ_SPAN_MBS.load(Relaxed),
9007            BMC_BI_FP.load(Relaxed),
9008            WP_SKIPPED.load(Relaxed),
9009            I4_ZERO.load(Relaxed), I4_DC.load(Relaxed),
9010            I4_SPARSE.load(Relaxed), I4_DENSE.load(Relaxed),
9011            T8_ZERO.load(Relaxed), I16_DCONLY.load(Relaxed),
9012        );
9013        let dmb = DBS_MB.load(Relaxed).max(1);
9014        eprintln!(
9015            "DBSDERIVE rows={} mb={} intra={} kindarm={} kindguard={} packed={} flat={} ({:.1}%) allzero={} ({:.1}%) nnz_rowcopy={} t8row={} t8mb={} idc2row={}",
9016            DBS_ROWS.load(Relaxed), DBS_MB.load(Relaxed),
9017            DBS_INTRA.load(Relaxed), DBS_KINDARM.load(Relaxed),
9018            DBS_KINDGUARD.load(Relaxed), DBS_PACKED.load(Relaxed),
9019            DBS_FLAT.load(Relaxed), 100.0 * DBS_FLAT.load(Relaxed) as f64 / dmb as f64,
9020            DBS_ALLZERO.load(Relaxed), 100.0 * DBS_ALLZERO.load(Relaxed) as f64 / dmb as f64,
9021            DBS_NNZ_ROWCOPY.load(Relaxed), DBS_T8ROW.load(Relaxed),
9022            DBS_T8MB.load(Relaxed), DBS_IDC2ROW.load(Relaxed),
9023        );
9024        let (ji, jn) = (J_INTER.load(Relaxed), J_INTER_NORES.load(Relaxed));
9025        eprintln!(
9026            "EDCMIX doubled={} nores_sent={} inter={ji} inter_no_residual={jn} ({:.1}% of inter) wasted_bytes={:.1} MB of {:.1} MB total inter payload",
9027            DOUBLED.load(Relaxed),
9028            J_NORES_SENT.load(Relaxed),
9029            100.0 * jn as f64 / ji.max(1) as f64,
9030            (jn * 2784) as f64 / 1.048576e6,
9031            (ji * 2784) as f64 / 1.048576e6,
9032        );
9033    }
9034}
9035
9036fn edc_worker(
9037    mut ctx: PixelCtx,
9038    rx: std::sync::mpsc::Receiver<EdcMsg>,
9039    ctx_tx: std::sync::mpsc::Sender<PixelCtx>,
9040    back_rx: std::sync::mpsc::Receiver<PixelCtx>,
9041) -> PixelCtx {
9042    while let Ok(msg) = rx.recv() {
9043        match msg {
9044            EdcMsg::Batch(jobs) => {
9045                for j in jobs {
9046                    match j {
9047                        EdcJob::Skip { mbx, mby, mv } => ctx.recon_p_skip(mbx, mby, mv),
9048                        EdcJob::Inter(j) => ctx.recon_p_inter(&j),
9049                        EdcJob::InterNoRes(j) => ctx.recon_p_inter_nores(&j),
9050                        EdcJob::B(j) => ctx.recon_b(&j),
9051                        EdcJob::BSkip { mbx, mby, regions } => ctx.recon_b_skip(mbx, mby, &regions),
9052                    }
9053                }
9054            }
9055            EdcMsg::Job(EdcJob::Skip { mbx, mby, mv }) => ctx.recon_p_skip(mbx, mby, mv),
9056            EdcMsg::Job(EdcJob::Inter(j)) => ctx.recon_p_inter(&j),
9057            EdcMsg::Job(EdcJob::InterNoRes(j)) => ctx.recon_p_inter_nores(&j),
9058            EdcMsg::Job(EdcJob::B(j)) => ctx.recon_b(&j),
9059            EdcMsg::Job(EdcJob::BSkip { mbx, mby, regions }) => ctx.recon_b_skip(mbx, mby, &regions),
9060            EdcMsg::Row { r, bs, qp, t8 } => {
9061                let (w, base) = (ctx.mb_w, r * ctx.mb_w);
9062                ctx.bs_store[base..base + w].copy_from_slice(&bs);
9063                ctx.qp_grid[base..base + w].copy_from_slice(&qp);
9064                ctx.t8_grid[base..base + w].copy_from_slice(&t8);
9065                if ctx.db_ena {
9066                    ctx.save_bak(r);
9067                    ctx.filter_row(r);
9068                    ctx.flt_rows = r + 1;
9069                    ctx.publish_progress_rows();
9070                }
9071            }
9072            EdcMsg::NeedCtx => {
9073                ctx_tx.send(ctx).expect("parse thread alive");
9074                ctx = back_rx.recv().expect("ctx returned after intra");
9075            }
9076        }
9077    }
9078    ctx
9079}
9080
9081
9082/// One motion-compensation region of a B macroblock, recorded at parse time
9083/// (E3). Weights are the RESOLVED implicit pair — computing them needs the
9084/// ref lists' POCs, which are parse-side state.
9085pub(crate) struct BRegion {
9086    px: usize,
9087    py: usize,
9088    rw: usize,
9089    rh: usize,
9090    refi0: i32,
9091    refi1: i32,
9092    mv0: (i32, i32),
9093    mv1: (i32, i32),
9094    w: Option<(i32, i32)>,
9095}
9096
9097/// A B macroblock's deferred pixel work: replay the regions into fresh
9098/// prediction buffers, then either copy them out (skip/direct, no residual)
9099/// or run the residual add.
9100pub(crate) struct BJob {
9101    mbx: usize,
9102    mby: usize,
9103    qp: u8,
9104    cbp_chroma: u32,
9105    skip: bool,
9106    regions: Vec<BRegion>,
9107    /// `None` when no 4x4 luma block was coded — t8 macroblocks included,
9108    /// since those carry `luma8` instead.
9109    luma_scan: Option<[[i32; 16]; 16]>,
9110    /// `Some` only under transform_size_8x8_flag.
9111    luma8: Option<[[i32; 64]; 4]>,
9112    cdc: [[i32; 4]; 2],
9113    /// `None` unless cbp_chroma == 2 (chroma AC coded).
9114    cac: Option<[[[i32; 16]; 4]; 2]>,
9115    nnzs: [u8; 24],
9116}
9117
9118impl PixelCtx {
9119    fn b_mc(
9120        &self,
9121        mb_x: usize,
9122        mb_y: usize,
9123        px: usize,
9124        py: usize,
9125        rw: usize,
9126        rh: usize,
9127        refi0: i32,
9128        mv0: (i32, i32),
9129        refi1: i32,
9130        mv1: (i32, i32),
9131        pred_y: &mut [u8; 256],
9132        c_pred: &mut [[u8; 64]; 2],
9133        wparam: Option<(i32, i32)>,
9134    ) {
9135        let _gb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBMc);
9136        // Malformed-stream armor, mirroring the P path: now that B slices actually
9137        // PARSE ref_idx (they used to be hardcoded to 0), a mutated stream can hand
9138        // us an index past the end of either list. Clamp rather than panic — the
9139        // crate is `forbid(unsafe_code)` and fuzz-gated to never panic, and a
9140        // wrong picture on garbage input carries no conformance duty.
9141        let refi0 = if refi0 >= 0 { (refi0 as usize).min(self.refs.len().saturating_sub(1)) as i32 } else { -1 };
9142        let refi1 = if refi1 >= 0 { (refi1 as usize).min(self.refs1.len().saturating_sub(1)) as i32 } else { -1 };
9143        if (refi0 >= 0 && self.refs.is_empty()) || (refi1 >= 0 && self.refs1.is_empty()) {
9144            return;
9145        }
9146        let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
9147        // E3: implicit weights are PARSE-side (they read the ref lists' POCs);
9148        // the region carries the resolved pair.
9149        let weights = wparam;
9150        // Bi-prediction blend: the weights decision is LOOP-INVARIANT, so every
9151        // blend site below matches on `weights` ONCE and runs a branch-free
9152        // pixel loop — the unweighted `(p+q+1)>>1` average then autovectorizes
9153        // (the per-pixel closure this replaces hid the invariant behind a
9154        // capture, and its chroma form was a &dyn call PER PIXEL).
9155        // FULL-WIDTH regions (px == 0, rw == 16 — every 16×16/16×8 partition and
9156        // most direct regions) occupy contiguous rows of `pred_y`, so MC writes
9157        // the destination DIRECTLY: uni-pred needs no staging at all, bi-pred
9158        // stages only the second list and blends in place. The staging arrays
9159        // (512 B zeroed per call before this) now exist only on the branches
9160        // that read them. Same fusion as the P path's mc_rect (WHYS Part 8).
9161        let full = px == 0 && rw == 16;
9162        let _gl = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBLuma);
9163        // One scratch borrow for the whole region — both bi-pred passes included.
9164        // The closure yields whether the arm already ran the chroma half (the
9165        // bi-pred full-width arm does, to keep its staging alive) — a plain
9166        // `return` inside would exit the CLOSURE only and chroma would run twice.
9167        let chroma_done = rusty_h264_common::inter::with_mc_scratch(|scr| match (refi0 >= 0, refi1 >= 0, full) {
9168            (true, false, true) => {
9169                let Some(rf) = self.refs.get(refi0 as usize) else { return false };
9170                rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16, mb_y * 16 + py, rw, rh, mv0.0, mv0.1, &mut pred_y[py * 16..py * 16 + rw * rh]);
9171                false
9172            }
9173            (false, true, true) => {
9174                let Some(rf) = self.refs1.get(refi1 as usize) else { return false };
9175                rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16, mb_y * 16 + py, rw, rh, mv1.0, mv1.1, &mut pred_y[py * 16..py * 16 + rw * rh]);
9176                false
9177            }
9178            (true, true, true) => {
9179                let Some(rf) = self.refs.get(refi0 as usize) else { return false };
9180                rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16, mb_y * 16 + py, rw, rh, mv0.0, mv0.1, &mut pred_y[py * 16..py * 16 + rw * rh]);
9181                let mut b = [0u8; 256];
9182                let Some(rf) = self.refs1.get(refi1 as usize) else { return false };
9183                rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16, mb_y * 16 + py, rw, rh, mv1.0, mv1.1, &mut b[..rw * rh]);
9184                drop(_gl);
9185                let _gbl = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
9186                // SLICE-then-zip: proving the bounds ONCE lets rustc emit the whole
9187                // 256-byte average as 8 straight-line vpavgb ops (verified in
9188                // isolation, x86-64-v3); the indexed form kept a per-iteration
9189                // bounds check and a loop. A hand AVX2 kernel is refuted — the
9190                // compiler already emits the ideal instruction.
9191                let dst = &mut pred_y[py * 16..py * 16 + rw * rh];
9192                match weights {
9193                    None => {
9194                        for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
9195                            *d = ((*d as u16 + *s as u16 + 1) >> 1) as u8;
9196                        }
9197                    }
9198                    Some((w0, w1)) => {
9199                        for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
9200                            *d = ((*d as i32 * w0 + *s as i32 * w1 + 32) >> 6).clamp(0, 255) as u8;
9201                        }
9202                    }
9203                }
9204                let _gc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
9205                self.b_mc_chroma(mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch);
9206                true
9207            }
9208            _ => {
9209                // Narrow region — rows are strided in `pred_y`; stage and copy.
9210                let (mut a, mut b) = ([0u8; 256], [0u8; 256]);
9211                if refi0 >= 0 {
9212                    let Some(rf) = self.refs.get(refi0 as usize) else { return false };
9213                    rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16 + px, mb_y * 16 + py, rw, rh, mv0.0, mv0.1, &mut a[..rw * rh]);
9214                }
9215                if refi1 >= 0 {
9216                    let Some(rf) = self.refs1.get(refi1 as usize) else { return false };
9217                    rusty_h264_common::inter::mc_luma_padded_pre(scr, &*rf.luma_guard(rf.ch), rf.lstride(), crate::LPAD, self.cw, ch, mb_x * 16 + px, mb_y * 16 + py, rw, rh, mv1.0, mv1.1, &mut b[..rw * rh]);
9218                }
9219                drop(_gl);
9220                let _gbl = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
9221                match (refi0 >= 0, refi1 >= 0) {
9222                    (true, true) => {
9223                        for dy in 0..rh {
9224                            let (ar, br) = (&a[dy * rw..dy * rw + rw], &b[dy * rw..dy * rw + rw]);
9225                            let base = (py + dy) * 16 + px;
9226                            let dst = &mut pred_y[base..base + rw];
9227                            match weights {
9228                                None => {
9229                                    for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
9230                                        *d = ((*p as u16 + *q as u16 + 1) >> 1) as u8;
9231                                    }
9232                                }
9233                                Some((w0, w1)) => {
9234                                    for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
9235                                        *d = ((*p as i32 * w0 + *q as i32 * w1 + 32) >> 6).clamp(0, 255) as u8;
9236                                    }
9237                                }
9238                            }
9239                        }
9240                    }
9241                    (true, false) => {
9242                        for dy in 0..rh {
9243                            let d = (py + dy) * 16 + px;
9244                            pred_y[d..d + rw].copy_from_slice(&a[dy * rw..dy * rw + rw]);
9245                        }
9246                    }
9247                    _ => {
9248                        for dy in 0..rh {
9249                            let d = (py + dy) * 16 + px;
9250                            pred_y[d..d + rw].copy_from_slice(&b[dy * rw..dy * rw + rw]);
9251                        }
9252                    }
9253                }
9254                false
9255            }
9256        });
9257        if chroma_done {
9258            return;
9259        }
9260        let _gc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
9261        self.b_mc_chroma(mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch);
9262    }
9263
9264    fn b_mc_chroma(
9265        &self,
9266        mb_x: usize,
9267        mb_y: usize,
9268        px: usize,
9269        py: usize,
9270        rw: usize,
9271        rh: usize,
9272        refi0: i32,
9273        mv0: (i32, i32),
9274        refi1: i32,
9275        mv1: (i32, i32),
9276        c_pred: &mut [[u8; 64]; 2],
9277        weights: Option<(i32, i32)>,
9278        cch: usize,
9279    ) {
9280        let (crx, cry, crw, crh) = (px / 2, py / 2, rw / 2, rh / 2);
9281        let full = crx == 0 && crw == 8;
9282        for c in 0..2 {
9283            match (refi0 >= 0, refi1 >= 0, full) {
9284                (true, false, true) => {
9285                    let Some(rf) = self.refs.get(refi0 as usize) else { return };
9286                    let pl = if c == 0 { &*rf.chroma_guard(0, rf.ch) } else { &*rf.chroma_guard(1, rf.ch) };
9287                    mc_chroma_padded(pl, rf.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8, mb_y * 8 + cry, crw, crh, mv0.0, mv0.1, &mut c_pred[c][cry * 8..cry * 8 + crw * crh]);
9288                }
9289                (false, true, true) => {
9290                    let Some(rf) = self.refs1.get(refi1 as usize) else { return };
9291                    let pl = if c == 0 { &*rf.chroma_guard(0, rf.ch) } else { &*rf.chroma_guard(1, rf.ch) };
9292                    mc_chroma_padded(pl, rf.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8, mb_y * 8 + cry, crw, crh, mv1.0, mv1.1, &mut c_pred[c][cry * 8..cry * 8 + crw * crh]);
9293                }
9294                (true, true, true) => {
9295                    let Some(rf) = self.refs.get(refi0 as usize) else { return };
9296                    let pl = if c == 0 { &*rf.chroma_guard(0, rf.ch) } else { &*rf.chroma_guard(1, rf.ch) };
9297                    mc_chroma_padded(pl, rf.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8, mb_y * 8 + cry, crw, crh, mv0.0, mv0.1, &mut c_pred[c][cry * 8..cry * 8 + crw * crh]);
9298                    let mut cb = [0u8; 64];
9299                    let Some(rf) = self.refs1.get(refi1 as usize) else { return };
9300                    let pl = if c == 0 { &*rf.chroma_guard(0, rf.ch) } else { &*rf.chroma_guard(1, rf.ch) };
9301                    mc_chroma_padded(pl, rf.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8, mb_y * 8 + cry, crw, crh, mv1.0, mv1.1, &mut cb[..crw * crh]);
9302                    let dst = &mut c_pred[c][cry * 8..cry * 8 + crw * crh];
9303                    match weights {
9304                        None => {
9305                            for (d, s) in dst.iter_mut().zip(&cb[..crw * crh]) {
9306                                *d = ((*d as u16 + *s as u16 + 1) >> 1) as u8;
9307                            }
9308                        }
9309                        Some((w0, w1)) => {
9310                            for (d, s) in dst.iter_mut().zip(&cb[..crw * crh]) {
9311                                *d = ((*d as i32 * w0 + *s as i32 * w1 + 32) >> 6).clamp(0, 255) as u8;
9312                            }
9313                        }
9314                    }
9315                }
9316                _ => {
9317                    let (mut ca, mut cb) = ([0u8; 64], [0u8; 64]);
9318                    if refi0 >= 0 {
9319                        let Some(rf) = self.refs.get(refi0 as usize) else { return };
9320                        let pl = if c == 0 { &*rf.chroma_guard(0, rf.ch) } else { &*rf.chroma_guard(1, rf.ch) };
9321                        mc_chroma_padded(pl, rf.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv0.0, mv0.1, &mut ca[..crw * crh]);
9322                    }
9323                    if refi1 >= 0 {
9324                        let Some(rf) = self.refs1.get(refi1 as usize) else { return };
9325                        let pl = if c == 0 { &*rf.chroma_guard(0, rf.ch) } else { &*rf.chroma_guard(1, rf.ch) };
9326                        mc_chroma_padded(pl, rf.cstride(), crate::CPAD, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv1.0, mv1.1, &mut cb[..crw * crh]);
9327                    }
9328                    match (refi0 >= 0, refi1 >= 0) {
9329                        (true, true) => {
9330                            for dy in 0..crh {
9331                                let (pr, qr) = (&ca[dy * crw..dy * crw + crw], &cb[dy * crw..dy * crw + crw]);
9332                                let base = (cry + dy) * 8 + crx;
9333                                let dst = &mut c_pred[c][base..base + crw];
9334                                match weights {
9335                                    None => {
9336                                        for ((d, p), q) in dst.iter_mut().zip(pr).zip(qr) {
9337                                            *d = ((*p as u16 + *q as u16 + 1) >> 1) as u8;
9338                                        }
9339                                    }
9340                                    Some((w0, w1)) => {
9341                                        for ((d, p), q) in dst.iter_mut().zip(pr).zip(qr) {
9342                                            *d = ((*p as i32 * w0 + *q as i32 * w1 + 32) >> 6).clamp(0, 255) as u8;
9343                                        }
9344                                    }
9345                                }
9346                            }
9347                        }
9348                        (true, false) => {
9349                            for dy in 0..crh {
9350                                let d = (cry + dy) * 8 + crx;
9351                                c_pred[c][d..d + crw].copy_from_slice(&ca[dy * crw..dy * crw + crw]);
9352                            }
9353                        }
9354                        _ => {
9355                            for dy in 0..crh {
9356                                let d = (cry + dy) * 8 + crx;
9357                                c_pred[c][d..d + crw].copy_from_slice(&cb[dy * crw..dy * crw + crw]);
9358                            }
9359                        }
9360                    }
9361                }
9362            }
9363        }
9364    }
9365
9366    /// B_Skip replay: regions into fresh prediction buffers, then the plane
9367    /// copy — no residual, no coefficient arrays.
9368    fn recon_b_skip(&mut self, mbx: usize, mby: usize, regions: &[BRegion]) {
9369        crate::RefFrame::set_mc_row_need(mby, self.mb_h * 16);
9370        let mut pred_y = [0u8; 256];
9371        let mut c_pred = [[0u8; 64]; 2];
9372        for r in regions {
9373            self.b_mc(mbx, mby, r.px, r.py, r.rw, r.rh, r.refi0, r.mv0, r.refi1, r.mv1, &mut pred_y, &mut c_pred, r.w);
9374        }
9375        for dy in 0..16 {
9376            let d = (mby * 16 + dy) * self.cw + mbx * 16;
9377            self.rec_y[d..d + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
9378        }
9379        for c in 0..2 {
9380            let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
9381            for dy in 0..8 {
9382                let d = (mby * 8 + dy) * self.ccw + mbx * 8;
9383                plane[d..d + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
9384            }
9385        }
9386    }
9387
9388    /// Replays one B macroblock's regions + residual (the worker half of the
9389    /// E3 seam). Mirrors the inline order exactly: MC regions in parse order
9390    /// into the prediction buffers, then the residual add (or the skip copy).
9391    fn recon_b(&mut self, j: &BJob) {
9392        crate::RefFrame::set_mc_row_need(j.mby, self.mb_h * 16);
9393        let mut pred_y = [0u8; 256];
9394        let mut c_pred = [[0u8; 64]; 2];
9395        for r in &j.regions {
9396            self.b_mc(j.mbx, j.mby, r.px, r.py, r.rw, r.rh, r.refi0, r.mv0, r.refi1, r.mv1, &mut pred_y, &mut c_pred, r.w);
9397        }
9398        if j.skip {
9399            for dy in 0..16 {
9400                let d = (j.mby * 16 + dy) * self.cw + j.mbx * 16;
9401                self.rec_y[d..d + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
9402            }
9403            for c in 0..2 {
9404                let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
9405                for dy in 0..8 {
9406                    let d = (j.mby * 8 + dy) * self.ccw + j.mbx * 8;
9407                    plane[d..d + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
9408                }
9409            }
9410        } else {
9411            self.cur_qp = j.qp;
9412            self.add_inter_residual(j.mbx, j.mby, &pred_y, &c_pred, j.luma_scan.as_ref(), j.luma8.as_ref(), &j.cdc, j.cac.as_ref(), j.cbp_chroma, &j.nnzs);
9413        }
9414    }
9415}
9416
9417
9418/// D9b: MC-call coalescing for a P inter MB from committed per-block (mv, ref).
9419/// Shared by residual and no-residual recon — filters are per-output-pixel so
9420/// wider rects are bit-identical to sixteen 4×4 calls.
9421fn coalesce_p_inter_mc(
9422    refs: &[crate::Ref],
9423    cw: usize,
9424    ccw: usize,
9425    mb_h: usize,
9426    mbx: usize,
9427    mby: usize,
9428    gmv: &[(i32, i32); 16],
9429    gref: &[usize; 16],
9430    pred_y: &mut [u8; 256],
9431    c_pred: &mut [[u8; 64]; 2],
9432) {
9433    let _ms = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMcStage);
9434    let (rh16, cch) = (mb_h * 16, mb_h * 8);
9435    let rect_eq = |x4: usize, y4: usize, w4: usize, h4: usize| -> bool {
9436        let t = y4 * 4 + x4;
9437        (0..h4).all(|dy| {
9438            (0..w4).all(|dx| {
9439                // 4x4 block grid: the index is always < 16, but LLVM cannot
9440                // infer it from the nested ranges. `& 15` states it.
9441                let b = ((y4 + dy) * 4 + (x4 + dx)) & 15;
9442                gmv[b] == gmv[t] && gref[b] == gref[t]
9443            })
9444        })
9445    };
9446    let mut mc_rect = |x4: usize, y4: usize, w4: usize, h4: usize| {
9447        let b = y4 * 4 + x4;
9448        let (mv, gr) = (gmv[b & 15], gref[b & 15]);
9449        let Some(reference) = refs.get(gr) else { return };
9450        let (w, h) = (w4 * 4, h4 * 4);
9451        if w == 16 {
9452            rusty_h264_common::inter::with_mc_scratch(|scr| {
9453                rusty_h264_common::inter::mc_luma_padded_pre(
9454                    scr,
9455                    &*reference.luma_guard(reference.ch),
9456                    reference.lstride(),
9457                    crate::LPAD,
9458                    cw,
9459                    rh16,
9460                    mbx * 16,
9461                    mby * 16 + y4 * 4,
9462                    w,
9463                    h,
9464                    mv.0,
9465                    mv.1,
9466                    &mut pred_y[y4 * 64..y4 * 64 + w * h],
9467                )
9468            });
9469        } else {
9470            let mut t = [0u8; 256];
9471            rusty_h264_common::inter::with_mc_scratch(|scr| {
9472                rusty_h264_common::inter::mc_luma_padded_pre(
9473                    scr,
9474                    &*reference.luma_guard(reference.ch),
9475                    reference.lstride(),
9476                    crate::LPAD,
9477                    cw,
9478                    rh16,
9479                    mbx * 16 + x4 * 4,
9480                    mby * 16 + y4 * 4,
9481                    w,
9482                    h,
9483                    mv.0,
9484                    mv.1,
9485                    &mut t[..w * h],
9486                )
9487            });
9488            let _pb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
9489            for dy in 0..h {
9490                pred_y[(y4 * 4 + dy) * 16 + x4 * 4..][..w]
9491                    .copy_from_slice(&t[dy * w..dy * w + w]);
9492            }
9493        }
9494        let (cw4, ch4) = (w4 * 2, h4 * 2);
9495        let nc = cw4 * ch4;
9496        let (gu, gv) = (reference.chroma_guard(0, reference.ch), reference.chroma_guard(1, reference.ch));
9497        // U+V paired: one setup serves both planes (see mc_chroma_padded_pair).
9498        if cw4 == 8 {
9499            let [cu, cv] = &mut *c_pred;
9500            rusty_h264_common::inter::mc_chroma_padded_pair(&gu, &gv, reference.cstride(), crate::CPAD, ccw, cch, mbx * 8, mby * 8 + y4 * 2, cw4, ch4, mv.0, mv.1, &mut cu[y4 * 16..y4 * 16 + nc], &mut cv[y4 * 16..y4 * 16 + nc]);
9501        } else {
9502            let (mut tu, mut tv) = ([0u8; 64], [0u8; 64]);
9503            rusty_h264_common::inter::mc_chroma_padded_pair(&gu, &gv, reference.cstride(), crate::CPAD, ccw, cch, mbx * 8 + x4 * 2, mby * 8 + y4 * 2, cw4, ch4, mv.0, mv.1, &mut tu[..nc], &mut tv[..nc]);
9504            let _pb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
9505            for (cc, tc) in [(0usize, &tu), (1, &tv)] {
9506                for dy in 0..ch4 {
9507                    c_pred[cc][(y4 * 2 + dy) * 8 + x4 * 2..][..cw4]
9508                        .copy_from_slice(&tc[dy * cw4..dy * cw4 + cw4]);
9509                }
9510            }
9511        }
9512    };
9513    if rect_eq(0, 0, 4, 4) {
9514        mc_rect(0, 0, 4, 4);
9515    } else if rect_eq(0, 0, 4, 2) && rect_eq(0, 2, 4, 2) {
9516        mc_rect(0, 0, 4, 2);
9517        mc_rect(0, 2, 4, 2);
9518    } else if rect_eq(0, 0, 2, 4) && rect_eq(2, 0, 2, 4) {
9519        mc_rect(0, 0, 2, 4);
9520        mc_rect(2, 0, 2, 4);
9521    } else {
9522        for q in 0..4usize {
9523            let (qx, qy) = ((q % 2) * 2, (q / 2) * 2);
9524            if rect_eq(qx, qy, 2, 2) {
9525                mc_rect(qx, qy, 2, 2);
9526            } else if rect_eq(qx, qy, 2, 1) && rect_eq(qx, qy + 1, 2, 1) {
9527                mc_rect(qx, qy, 2, 1);
9528                mc_rect(qx, qy + 1, 2, 1);
9529            } else if rect_eq(qx, qy, 1, 2) && rect_eq(qx + 1, qy, 1, 2) {
9530                mc_rect(qx, qy, 1, 2);
9531                mc_rect(qx + 1, qy, 1, 2);
9532            } else {
9533                for j in 0..4usize {
9534                    mc_rect(qx + (j % 2), qy + (j / 2), 1, 1);
9535                }
9536            }
9537        }
9538    }
9539}
9540
9541/// One deferred pixel-reconstruction job (entropy-decouple E1 seam).
9542enum EdcJob {
9543    Skip { mbx: usize, mby: usize, mv: (i32, i32) },
9544    Inter(Box<PInterJob>),
9545    B(Box<BJob>),
9546    /// B_Skip / no-residual direct: regions only. The full `BJob` carried
9547    /// 2.6 KB of ZEROED coefficient arrays for ~60% of B macroblocks — the
9548    /// wall-time regression's main CPU tax (B-heavy MT arm measured +45% CPU).
9549    BSkip { mbx: usize, mby: usize, regions: Vec<BRegion> },
9550    /// P inter with `cbp == 0` — the P-side twin of `BSkip` (D9).
9551    InterNoRes(Box<PInterNoResJob>),
9552}
9553
9554/// A P inter macroblock with NO residual (`cbp == 0`) — motion only.
9555///
9556/// D9. `PInterJob` is 2,784 bytes and **93% of that is coefficient arrays**
9557/// (`luma_scan` 1024 + `luma8` 1024 + `cac` 512 + `cdc` 32 = 2,592). When
9558/// `cbp == 0` every one of them is ZERO, and the seam was heap-allocating,
9559/// filling, channel-passing and freeing all 2,592 bytes of nothing —
9560/// 12.8-37.5% of inter jobs on the x264 corpus, 15.8-44.1 MB per 60-frame pass.
9561///
9562/// This is the same pathology `EdcJob::BSkip` was introduced to fix on the B
9563/// side ("2.6 KB of ZEROED coefficient arrays for ~60% of B macroblocks — the
9564/// wall-time regression's main CPU tax"). It was never ported to P.
9565///
9566/// Consumer uses `recon_p_inter_nores` (MC + plane copy) — bit-identical to
9567/// `recon_p_inter` on zero residuals, without memset of 2.5 KB coeff arrays.
9568/// (`RS_H264_NORES=0` keeps the old full-job path for A/B — routed at the
9569/// CONSTRUCTOR, so this job carries only what nores recon reads.)
9570struct PInterNoResJob {
9571    mbx: usize,
9572    mby: usize,
9573    t8: bool,
9574    gmv: [(i32, i32); 16],
9575    gref: [u8; 16],
9576}
9577
9578
9579/// The compact inputs of one CABAC P inter macroblock's reconstruction.
9580/// Shared all-zero residual planes. An Option-shaped residual field is `None`
9581/// exactly when the parse wrote nothing into it, and every consumer read is
9582/// already guarded by a zero-count test — so a `None` reader would have seen
9583/// zeros anyway. Pointing at one static beats zero-initialising 1 KB (luma) or
9584/// 512 B (chroma AC) of stack per macroblock.
9585static ZERO_LUMA_SCAN: [[i32; 16]; 16] = [[0; 16]; 16];
9586/// `(16384 + |td|/2) / td` for every `td` the spec can present. `td` is
9587/// `.clamp(-128, 127)` at both call sites (spec 8.4.1.2.3), so the divide has
9588/// exactly 256 possible inputs and a table replaces it EXACTLY -- this is not an
9589/// approximation, it is the same arithmetic evaluated ahead of time. `td == 0`
9590/// is guarded by the caller and maps to 0 here so the table has no hole.
9591static TX_FOR_TD: [i32; 256] = {
9592    let mut t = [0i32; 256];
9593    let mut i = 0usize;
9594    while i < 256 {
9595        let td = i as i32 - 128; // index 0 => -128 .. index 255 => 127
9596        t[i] = if td == 0 { 0 } else { (16384 + td.abs() / 2) / td };
9597        i += 1;
9598    }
9599    t
9600};
9601
9602#[inline(always)]
9603fn tx_for_td(td: i32) -> i32 {
9604    debug_assert!((-128..=127).contains(&td), "td must be clamped before this");
9605    TX_FOR_TD[(td.clamp(-128, 127) + 128) as usize]
9606}
9607
9608/// The "no coefficients" neighbour record. `mb_nzc` reads are reached only
9609/// through `if let Some(..) = top/left`, so an out-of-range index is
9610/// unreachable; this is what the unavailable branches already imply.
9611static ZERO_NZC: [u8; 24] = [0u8; 24];
9612static ZERO_CAC: [[[i32; 16]; 4]; 2] = [[[0; 16]; 4]; 2];
9613
9614struct PInterJob {
9615    mbx: usize,
9616    mby: usize,
9617    qp: u8,
9618    cbp_chroma: u32,
9619    /// The committed per-block motion, copied at parse time so the worker
9620    /// never reads the parse thread's grids (E2). Ref indices clamped by the
9621    /// consumer, kept u8 (spec max 15).
9622    gmv: [(i32, i32); 16],
9623    gref: [u8; 16],
9624    /// `None` when no 4x4 luma block was coded — t8 macroblocks included,
9625    /// since those carry `luma8` instead.
9626    luma_scan: Option<[[i32; 16]; 16]>,
9627    /// `Some` only under transform_size_8x8_flag.
9628    luma8: Option<[[i32; 64]; 4]>,
9629    cdc: [[i32; 4]; 2],
9630    /// `None` unless cbp_chroma == 2 (chroma AC coded).
9631    cac: Option<[[[i32; 16]; 4]; 2]>,
9632    nnzs: [u8; 24],
9633}
9634
9635/// Entropy-decouple master knob — DEFAULT ON since 2026-08-05 (`RS_H264_EDC=0`
9636/// opts out). E1 was expected to be cost-neutral scaffolding for the E2
9637/// thread; it BANKED on its own: 13/15 pairs, z=2.84, median +4.0% (pooled
9638/// 19/24, z=2.86). Mechanism: LOOP FISSION — batching a row's parsing and
9639/// then a row's reconstruction keeps each large code path's I-cache and
9640/// branch state hot, instead of alternating two giant bodies per macroblock.
9641fn edc_on() -> bool {
9642    use std::sync::atomic::{AtomicU8, Ordering};
9643    static ON: AtomicU8 = AtomicU8::new(0);
9644    match ON.load(Ordering::Relaxed) {
9645        0 => {
9646            let v = !std::env::var_os("RS_H264_EDC").is_some_and(|v| v == "0");
9647            ON.store(if v { 1 } else { 2 }, Ordering::Relaxed);
9648            v
9649        }
9650        n => n == 1,
9651    }
9652}
9653
9654/// E2/E3 worker-thread knob.
9655///
9656/// **Default OFF (2026-08-11).** ffmpeg's parallel unit is the PICTURE
9657/// (`decode_slice` + `hl_decode_mb` on the same thread). `edc_worker` is a
9658/// nested recon thread — the wrong function:
9659///   * 1T (`ffmpeg -threads 1`, pinvs pin): two threads thrash one core
9660///   * frame-MT (`ffmpeg -threads N`): each picture worker would spawn another
9661/// Frame-MT (`RS_H264_FRAME_THREADS=N`) is the ffmpeg-shaped pool. This
9662/// pipeline stays as an explicit oracle (`RS_H264_EDC_MT=1`) / old auto-gate
9663/// (`=auto`). `=0` forces inline.
9664/// D8: run every pixel reconstruction TWICE (the second pass is discarded work,
9665/// not discarded output -- recon is idempotent, so the bytes are unchanged).
9666/// `t(double) - t(single)` IS the pixel half's cost, which is the parallel
9667/// fraction the E2 seam can address. Byte-identity is the proof the ablation
9668/// did not change the program (unlike removing the stage, which cascades).
9669/// D9 compact no-residual P inter jobs. `RS_H264_NORES=0` restores the old
9670/// always-full-payload path for A/B (the arm must PIN the value, never inherit
9671/// a default -- an "off" arm that only omits an override measures
9672/// default-vs-default and prints all zeros).
9673/// D10 row batching — **DEFAULT ON. A DELIBERATE THROUGHPUT-OVER-LATENCY TRADE.**
9674///
9675/// Ships a row's pixel jobs in one message instead of one per macroblock:
9676/// 207,949 sends -> 3,086 (**67-70x fewer**), and **~3% less CPU**.
9677///
9678/// ⚠ IT COSTS 2-4% WALL on a single stream — 0.963x / 0.980x, **11/11 pairs on
9679/// two clips**, A/B'd against itself at a fixed queue bound. That is measured,
9680/// reproducible, and ACCEPTED, not an oversight. Do not "fix" it by flipping
9681/// the default; read this first.
9682///
9683/// WHY IT COSTS WALL: batching trades pipelining for synchronisation.
9684/// Per-macroblock sends let the worker start on job 1 immediately; a row batch
9685/// makes it idle until ~70 macroblocks are parsed, then hands it a burst.
9686///
9687/// WHY IT IS STILL THE RIGHT DEFAULT: wall time here is SINGLE-STREAM LATENCY;
9688/// CPU is THROUGHPUT. A host decoding many streams concurrently is CPU-bound,
9689/// not latency-bound, so 3% less CPU is ~3% more capacity while the 2-4% wall
9690/// cost falls on a dimension that is not the constraint. The 67x drop in
9691/// channel operations also removes a park/unpark storm that scales with the
9692/// number of runnable threads — it gets better, not worse, as the box fills.
9693///
9694/// `RS_H264_BATCH=0` restores per-macroblock sends for the latency-sensitive
9695/// single-stream case (playback, seek preview, anything where first-frame time
9696/// dominates).
9697fn batch_on() -> bool {
9698    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9699    *V.get_or_init(|| !std::env::var_os("RS_H264_BATCH").is_some_and(|v| v == "0"))
9700}
9701
9702fn nores_on() -> bool {
9703    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9704    *V.get_or_init(|| !std::env::var_os("RS_H264_NORES").is_some_and(|v| v == "0"))
9705}
9706
9707/// D13 A/B: always allocate B-only CABAC neighbour grids even on P/I slices.
9708fn fat_slice_on() -> bool {
9709    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9710    *V.get_or_init(|| std::env::var_os("RS_H264_FAT_SLICE").is_some_and(|v| v == "1"))
9711}
9712
9713/// MEASUREMENT KNOB — `RS_H264_NO_SKIPBAND=1` forces the per-MB skip path so the
9714/// mb_skip_run band coalescer can be A/B'd paired on ONE binary. Inert when unset.
9715fn no_skipband() -> bool {
9716    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9717    *V.get_or_init(|| std::env::var_os("RS_H264_NO_SKIPBAND").is_some_and(|v| v == "1"))
9718}
9719
9720/// MEASUREMENT KNOB — `RS_H264_NO_RUNMV=1` forces the full skip_mv derivation
9721/// on every P_Skip (disables the run-theorem forced-(0,0) branch) for paired A/B.
9722fn no_runmv() -> bool {
9723    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9724    *V.get_or_init(|| std::env::var_os("RS_H264_NO_RUNMV").is_some_and(|v| v == "1"))
9725}
9726
9727/// MEASUREMENT KNOB — `RS_H264_NO_SKIPFP=1` disables the P_Skip single fast
9728/// paths (full-pel direct copy + identity-weight-pass skip) for paired A/B.
9729fn no_skipfp() -> bool {
9730    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9731    *V.get_or_init(|| std::env::var_os("RS_H264_NO_SKIPFP").is_some_and(|v| v == "1"))
9732}
9733
9734/// MEASUREMENT KNOB — `RS_H264_NO_BSKIPFAST=1` forces the full decode_b_direct
9735/// path for B_Skip so the zero-bi fast path can be A/B'd paired on ONE binary.
9736fn no_bskipfast() -> bool {
9737    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9738    *V.get_or_init(|| std::env::var_os("RS_H264_NO_BSKIPFAST").is_some_and(|v| v == "1"))
9739}
9740
9741fn double_recon() -> bool {
9742    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9743    // `== "1"`, not `is_some()`: the old presence test meant even
9744    // `RS_H264_DOUBLE_RECON=0` DOUBLED the recon work — the one knob in the
9745    // inventory whose "off" spelling turned it on (2026-08-27 audit, site 8).
9746    *V.get_or_init(|| std::env::var_os("RS_H264_DOUBLE_RECON").is_some_and(|v| v == "1"))
9747}
9748
9749fn edc_bound() -> usize {
9750    static V: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
9751    *V.get_or_init(|| {
9752        std::env::var("RS_H264_EDC_BOUND")
9753            .ok()
9754            .and_then(|v| v.parse().ok())
9755            .unwrap_or(256)
9756    })
9757}
9758
9759/// D12 — E2 THREADING DISPATCH. Fires on `720p-or-smaller AND bits/MB > 38.4`.
9760///
9761/// The seam threads unconditionally before this, and that shipped a REGRESSION:
9762/// 8-10% slower wall on main profile for 38-56% more CPU. It cannot pay in
9763/// general — the pixel half is only ~15.6% of decode, so Amdahl caps two
9764/// threads at 1.085x — but it DOES win on some streams, so the answer is a
9765/// dispatch, not abandonment.
9766///
9767/// Fitted with `bench/examples/gate_optimizer.rs` over **28 interleaved
9768/// configurations** (12 clips x core counts 4-8, `bench/pinmtx.ps1`):
9769/// **net +29.40 of +30.70 perfect**, train +25.10 AND holdout +4.30, worst
9770/// fired class **+2.94**, precision 0.80. It forgoes +0.40 of wins to avoid
9771/// **169.90** of losses. Calibration: depth-2 **2/300** rules passed (0.67%),
9772/// so the separation carries information.
9773///
9774/// Per clause, both load-bearing (dropping either: -20.50 / -50.60, 6-7 big
9775/// losers):
9776/// * `bits/MB > 38.4` — coefficient density is the runtime proxy for PIXEL
9777///   SHARE: more coefficients means more residual work on the far side of the
9778///   seam. Threshold sits in an open gap (highest excluded 35.4, lowest firing
9779///   41.4). Note this is the OPPOSITE direction to an earlier hand-fitted
9780///   `bits < 65`, which was fitted to one clip and falsified.
9781/// * frame <= 720p — every 1080p configuration measured loses, including a
9782///   low-density one, so density alone is not sufficient.
9783/// * `cabac` — ADDED 2026-08-07 after the CAVLC arm made those units
9784///   measurable. bits/MB DOES NOT TRANSFER ACROSS ENTROPY CODERS: CAVLC needs
9785///   more bits for the SAME coefficients, so its density (62-65) reads deep
9786///   inside the firing region while its pixel work is unchanged. Without this
9787///   clause the rule routed CAVLC into threading, where it measured 1.29-1.49x
9788///   SLOWER — net -52.30, worst class -40.85. With it, +29.40 and worst class
9789///   +2.94. `gate_optimizer` could not find this: the rule needs THREE clauses
9790///   and the search is depth-2 (both depth-2 pairs fail, -20.50 / -50.60).
9791///
9792/// The estimate comes from ALREADY-DECODED slices, so the first slice of a
9793/// stream runs INLINE (the safe arm) until a measurement exists. Both arms are
9794/// byte-identical, so the choice can never affect output.
9795/// Invoked only by `RS_H264_EDC_MT=auto` (the pre-2026-08-11 default).
9796fn edc_dispatch(mb_w: usize, mb_h: usize, bits_per_mb: f64, cabac: bool) -> bool {
9797    const BITS_MIN: f64 = 38.4;
9798    const MAX_MBS: usize = 5000; // 720p = 3600, 1080p = 8160
9799    // The `cabac` clause is NOT cosmetic — see the header note. Without it this
9800    // rule scores net -52.30 with worst class -40.85 once CAVLC units are in the
9801    // corpus, because CAVLC's bits/MB is inflated by a less efficient entropy
9802    // coder rather than by more pixel work.
9803    cabac && bits_per_mb > BITS_MIN && mb_w * mb_h <= MAX_MBS
9804}
9805
9806fn edc_mt() -> Option<bool> {
9807    static V: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
9808    *V.get_or_init(|| match std::env::var("RS_H264_EDC_MT").ok().as_deref() {
9809        Some("0") => Some(false),
9810        Some("1") => Some(true),
9811        Some("auto") => None,
9812        _ => Some(false),
9813    })
9814}
9815
9816/// Spawn `edc_worker`? ffmpeg never does this: the picture thread owns recon.
9817/// Frame-MT workers (`FRAME_THREADS>1`) always inline. Otherwise honor the knob
9818/// (`1` / `0` / `auto`→[`edc_dispatch`]; unset = inline).
9819fn edc_spawn_worker(mb_w: usize, mb_h: usize, bits_per_mb: f64, cabac: bool) -> bool {
9820    if crate::frame_mt::frame_threads() > 1 {
9821        return false;
9822    }
9823    edc_mt().unwrap_or_else(|| edc_dispatch(mb_w, mb_h, bits_per_mb, cabac))
9824}
9825
9826
9827
9828/// Picture-end bS precompute (rowdb-off fallback). `RS_H264_BS_PRE=0` opts out.
9829fn bs_pre_on() -> bool {
9830    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9831    *V.get_or_init(|| !std::env::var_os("RS_H264_BS_PRE").is_some_and(|v| v == "0"))
9832}
9833
9834/// Row-interleaved deblocking master knob: `RS_H264_ROWDB=0` opts out,
9835/// restoring the picture-end pipeline (WHYS Part 17) as the A/B comparator.
9836/// MEASUREMENT KNOB — `RS_H264_KIND_LOADS=1` restores the Blk::load-based
9837/// kind arms in derive_bs_row (see the routing comment there) for paired A/B.
9838fn kind_loads() -> bool {
9839    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9840    *V.get_or_init(|| std::env::var_os("RS_H264_KIND_LOADS").is_some_and(|v| v == "1"))
9841}
9842
9843fn rowdb_on() -> bool {
9844    use std::sync::atomic::{AtomicU8, Ordering};
9845    static ON: AtomicU8 = AtomicU8::new(0);
9846    match ON.load(Ordering::Relaxed) {
9847        0 => {
9848            let v = !std::env::var_os("RS_H264_ROWDB").is_some_and(|v| v == "0");
9849            ON.store(if v { 1 } else { 2 }, Ordering::Relaxed);
9850            v
9851        }
9852        n => n == 1,
9853    }
9854}
9855
9856/// A/B: per-MB `row_hook` body even when no row has completed (old behaviour).
9857fn rowhook_eager() -> bool {
9858    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9859    *V.get_or_init(|| std::env::var_os("RS_H264_ROWHOOK_EAGER").is_some_and(|v| v == "1"))
9860}
9861
9862/// A/B: `RS_H264_DIRECT_MEMO=0` rewalks spatial-direct neighbours every 8×8.
9863#[inline]
9864fn direct_memo_on() -> bool {
9865    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9866    *V.get_or_init(|| !std::env::var_os("RS_H264_DIRECT_MEMO").is_some_and(|v| v == "0"))
9867}
9868
9869/// 4×4-block (z-order) → 30-entry (6-stride) mv/ref/mvd cache index (openh264
9870/// g_kCache30ScanIdx). Top neighbour = cache[idx-6], left = cache[idx-1].
9871use rusty_h264_common::cabac_tables::CACHE30;
9872
9873/// z-order 4×4-block → raster index (openh264 g_kuiScan4). Per-MB mvd/ref state is
9874/// stored raster-indexed (matching how neighbour blocks 3/7/11/15 and 12..15 are read).
9875use rusty_h264_common::cabac_tables::G_SCAN4;
9876
9877/// P `sub_mb_type` CABAC (openh264 `ParseSubMBTypeCabac`, ctx 21). 0=8×8, 1=8×4, 2=4×8, 3=4×4.
9878fn parse_sub_mb_type_p_cabac(cab: &mut crate::cabac::Cabac) -> u32 {
9879    const S: usize = 21;
9880    if cab.decode_decision(S) != 0 {
9881        return 0;
9882    }
9883    if cab.decode_decision(S + 1) != 0 {
9884        3 - cab.decode_decision(S + 2)
9885    } else {
9886        1
9887    }
9888}
9889
9890/// Intra `mb_type` sub-parse for P/B slices (openh264 `DecodeCabacIntraMbType`, `base`=32
9891/// for B). Returns 0 = I_4x4, 1..=24 = I_16x16, 25 = I_PCM (in the intra numbering).
9892fn parse_intra_mb_type_cabac(cab: &mut crate::cabac::Cabac, base: usize) -> u32 {
9893    if cab.decode_decision(base) == 0 {
9894        return 0; // I_4x4
9895    }
9896    if cab.decode_terminate() {
9897        return 25; // I_PCM
9898    }
9899    let mut t = 1 + 12 * cab.decode_decision(base + 1) as u32; // cbp_luma != 0
9900    if cab.decode_decision(base + 2) != 0 {
9901        t += 4 + 4 * cab.decode_decision(base + 2) as u32;
9902    }
9903    t += 2 * cab.decode_decision(base + 3) as u32;
9904    t += cab.decode_decision(base + 3) as u32;
9905    t
9906}
9907
9908/// B `mb_type` CABAC (openh264 `ParseMBTypeBSliceCabac`, ctx base 27). `ctx_inc` = (left
9909/// avail & !direct) + (top avail & !direct). Returns 0 = B_Direct_16x16, 1..=21 = the
9910/// L0/L1/Bi 16×16/16×8/8×16 shapes, 22 = B_8x8, 23.. = intra (mb_type − 23).
9911/// Test-only alias so the ENCODER crate can gate `cb_mb_type_b` against this
9912/// parser directly — they are exact inverses, so a round-trip is a complete gate.
9913#[doc(hidden)]
9914pub fn parse_mb_type_b(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
9915    parse_mb_type_b_cabac(cab, ctx_inc)
9916}
9917
9918#[inline]
9919fn parse_mb_type_b_cabac(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
9920    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
9921    const B: usize = 27;
9922    if cab.decode_decision(B + ctx_inc) == 0 {
9923        return 0; // B_Direct_16x16
9924    }
9925    if cab.decode_decision(B + 3) == 0 {
9926        return 1 + cab.decode_decision(B + 5) as u32; // 16×16 L0 / L1
9927    }
9928    let mut m = (cab.decode_decision(B + 4) as u32) << 3;
9929    m |= (cab.decode_decision(B + 5) as u32) << 2;
9930    m |= (cab.decode_decision(B + 5) as u32) << 1;
9931    m |= cab.decode_decision(B + 5) as u32;
9932    if m < 8 {
9933        return m + 3;
9934    }
9935    if m == 13 {
9936        return parse_intra_mb_type_cabac(cab, 32) + 23;
9937    }
9938    if m == 14 {
9939        return 11; // B_Bi_8x16
9940    }
9941    if m == 15 {
9942        return 22; // B_8x8
9943    }
9944    m = (m << 1) | cab.decode_decision(B + 5) as u32;
9945    m - 4
9946}
9947
9948/// B `sub_mb_type` CABAC (openh264 `ParseBSubMBTypeCabac`, ctx base 36). Returns 0..=12
9949/// per spec Table 7-18 (0 = B_Direct_8x8, 1 = B_L0_8x8, …, 12 = B_Bi_4x4).
9950fn parse_sub_mb_type_b_cabac(cab: &mut crate::cabac::Cabac) -> u32 {
9951    const B: usize = 36;
9952    if cab.decode_decision(B) == 0 {
9953        return 0; // B_Direct_8x8
9954    }
9955    if cab.decode_decision(B + 1) == 0 {
9956        return 1 + cab.decode_decision(B + 3) as u32; // B_L0_8x8 / B_L1_8x8
9957    }
9958    let mut st = 3u32;
9959    if cab.decode_decision(B + 2) != 0 {
9960        if cab.decode_decision(B + 3) != 0 {
9961            return 11 + cab.decode_decision(B + 3) as u32; // B_L1_4x4 / B_Bi_4x4
9962        }
9963        st += 4;
9964    }
9965    st += 2 * cab.decode_decision(B + 3) as u32;
9966    st += cab.decode_decision(B + 3) as u32;
9967    st
9968}
9969
9970/// Parse one motion partition's `mvd` (x,y) and splat it into the 30-entry cache + the
9971/// per-MB raster mvd/ref state. `part_idx` = the partition's top-left z-order block (for
9972/// the ctxInc neighbour lookup); `zblocks` = every z-order 4×4 block the partition covers.
9973fn parse_mvd_partition(
9974    cab: &mut crate::cabac::Cabac,
9975    part_idx: usize,
9976    zblocks: &[usize],
9977    mvdc: &mut [[i16; 2]; 30],
9978    refc: &mut [i8; 30],
9979    mmvd: &mut [[i16; 2]; 16],
9980    mref: &mut [i8; 16],
9981    ref_idx: i8,
9982) -> (i32, i32) {
9983    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
9984    // `CACHE30` is [usize; 16] and every entry lies in [7, 28]; the mask and
9985    // clamp are semantic no-ops that hand LLVM the ranges it cannot infer, so
9986    // the `refc[s - 6]` / `mvdc[s - 1]` indexes into the 30-entry caches become
9987    // provable instead of bounds-checked.
9988    let s = CACHE30[part_idx & 15].clamp(6, 29);
9989    // Neighbour AVAILABILITY is per-neighbour, not per-component: the old closure
9990    // re-tested both refc slots for each of x and y, four loads to answer two
9991    // questions. Resolve once, then sum each component.
9992    let (av_b, av_a) = (refc[s - 6] >= 0, refc[s - 1] >= 0);
9993    let (nb, na) = (mvdc[s - 6], mvdc[s - 1]);
9994    let ctx = |comp: usize| -> usize {
9995        let mut a = 0i32;
9996        if av_b {
9997            a += nb[comp].unsigned_abs() as i32;
9998        }
9999        if av_a {
10000            a += na[comp].unsigned_abs() as i32;
10001        }
10002        if a >= 3 {
10003            1 + (a > 32) as usize
10004        } else {
10005            0
10006        }
10007    };
10008    let (cx, cy) = (ctx(0), ctx(1));
10009    let mvx = parse_mvd_cabac(cab, 0, cx);
10010    let mvy = parse_mvd_cabac(cab, 1, cy);
10011    let mvd = [mvx, mvy];
10012    if zblocks.len() == 16 {
10013        // Whole-macroblock partition (the dominant B shape): every raster slot
10014        // takes the same value — one fill each instead of 16 indexed stores.
10015        mmvd.fill(mvd);
10016        mref.fill(ref_idx);
10017        for &zb in zblocks {
10018            let c = CACHE30[zb & 15];
10019            mvdc[c] = mvd;
10020            refc[c] = ref_idx;
10021        }
10022    } else {
10023        for &zb in zblocks {
10024            // Each table was being read twice per block.
10025            let (c, g) = (CACHE30[zb & 15], G_SCAN4[zb & 15]);
10026            mvdc[c] = mvd;
10027            refc[c] = ref_idx;
10028            mmvd[g] = mvd;
10029            mref[g] = ref_idx;
10030        }
10031    }
10032    (mvx as i32, mvy as i32)
10033}
10034
10035/// `ref_idx_l0` (P) CABAC — mirror of the encoder `cb_ref_idx`. Unary, ctxIdxOffset
10036/// 54: binIdx 0 → `ctx0` (condTermFlagA + 2·condTermFlagB), binIdx 1 → 4, binIdx ≥2 → 5.
10037pub fn parse_ref_idx_cabac(cab: &mut crate::cabac::Cabac, ctx0: usize) -> i8 {
10038    const B: usize = 54;
10039    let mut r = 0i8;
10040    let mut bin_idx = 0u32;
10041    // Cap the unary length: valid ref_idx ≤ 15 (16 refs max); the cap keeps a corrupt
10042    // stream from looping unboundedly. The MC clamps the index, so an over-range value
10043    // is decoded as garbage (never a panic) — the robustness contract, not correctness.
10044    while bin_idx < 32 {
10045        let ctx = match bin_idx {
10046            0 => ctx0,
10047            1 => 4,
10048            _ => 5,
10049        };
10050        if cab.decode_decision(B + ctx) == 0 {
10051            break;
10052        }
10053        r += 1;
10054        bin_idx += 1;
10055    }
10056    r
10057}
10058
10059/// UEG3 mvd suffix (openh264 `DecodeUEGMvCabac`): TU prefix at `base + {0,1,2,3,3,..}`
10060/// (≤7), then EG3 bypass.
10061fn decode_ueg_mv(cab: &mut crate::cabac::Cabac, base: usize) -> u32 {
10062    const P2C: [usize; 8] = [0, 1, 2, 3, 3, 3, 3, 3];
10063    if cab.decode_decision(base) == 0 {
10064        return 0;
10065    }
10066    let mut code = 0u32;
10067    let mut count = 1usize;
10068    let mut tmp;
10069    loop {
10070        tmp = cab.decode_decision(base + P2C[count]);
10071        code += 1;
10072        count += 1;
10073        if tmp == 0 || count == 8 {
10074            break;
10075        }
10076    }
10077    if tmp != 0 {
10078        code += cabac_exp_bypass(cab, 3) + 1;
10079    }
10080    code
10081}
10082
10083/// One `mvd` component (openh264 `ParseMvdInfoCabac`). `ctx_inc` (0/1/2) from the
10084/// neighbour |mvd| sum. ctxIdxOffset 40 (x) / 47 (y).
10085fn parse_mvd_cabac(cab: &mut crate::cabac::Cabac, comp: usize, ctx_inc: usize) -> i16 {
10086    let base = 40 + comp * 7; // NEW_CTX_OFFSET_MVD + comp*CTX_NUM_MVD
10087    if cab.decode_decision(base + ctx_inc) == 0 {
10088        return 0;
10089    }
10090    let mag = (decode_ueg_mv(cab, base + 3) + 1) as i16;
10091    if cab.decode_bypass() != 0 {
10092        -mag
10093    } else {
10094        mag
10095    }
10096}
10097
10098/// `mb_skip_flag` CABAC (openh264 `ParseSkipFlagCabac`). `ctx_inc` = base 11 (P) or 24
10099/// (B) + (left avail & not-skip) + (top avail & not-skip). Returns true if skipped.
10100#[inline]
10101fn parse_mb_skip_cabac(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> bool {
10102    cab.decode_decision(ctx_inc) != 0
10103}
10104
10105/// P-slice `mb_type` CABAC (openh264 `ParseMBTypePSliceCabac`). Returns 0..3 = inter
10106/// (P_L0_16x16 / P_16x8 / P_8x16 / P_8x8), 5 = I_4x4, 6..29 = I_16x16, 30 = I_PCM.
10107#[inline]
10108fn parse_mb_type_p_cabac(cab: &mut crate::cabac::Cabac) -> u32 {
10109    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
10110    const S: usize = 11; // NEW_CTX_OFFSET_SKIP; P mb_type contexts hang off it
10111    if cab.decode_decision(S + 3) == 0 {
10112        // inter
10113        return if cab.decode_decision(S + 4) != 0 {
10114            if cab.decode_decision(S + 6) != 0 { 1 } else { 2 }
10115        } else if cab.decode_decision(S + 5) != 0 {
10116            3
10117        } else {
10118            0
10119        };
10120    }
10121    // intra (prefix bit was 1)
10122    if cab.decode_decision(S + 6) == 0 {
10123        return 5; // I_4x4
10124    }
10125    if cab.decode_terminate() {
10126        return 30; // I_PCM
10127    }
10128    let mut t = 6 + cab.decode_decision(S + 7) * 12;
10129    if cab.decode_decision(S + 8) != 0 {
10130        t += 4;
10131        if cab.decode_decision(S + 8) != 0 {
10132            t += 4;
10133        }
10134    }
10135    t += cab.decode_decision(S + 9) << 1;
10136    t += cab.decode_decision(S + 9);
10137    t
10138}
10139
10140/// I-slice `mb_type` CABAC parse (spec §9.3.2.5 / openh264 `ParseMBTypeISliceCabac`).
10141/// `ctx_inc` = (left MB is I_16x16/non-intra) + (top MB is …), i.e. 0..2; the corner
10142/// MB has no neighbours so `ctx_inc = 0`. Returns the raw mb_type: 0 = I_NxN (I_4x4/
10143/// I_8x8), 1..24 = I_16x16 (pred-mode/cbp packed), 25 = I_PCM.
10144fn parse_mb_type_i_cabac(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
10145    const O: usize = 3; // ctxIdxOffset for I-slice mb_type
10146    if cab.decode_decision(O + ctx_inc) == 0 {
10147        return 0; // I_NxN
10148    }
10149    if cab.decode_terminate() {
10150        return 25; // I_PCM
10151    }
10152    let mut t = 1 + cab.decode_decision(O + 3) * 12; // CBP luma: 0 or 12
10153    if cab.decode_decision(O + 4) != 0 {
10154        t += 4; // CBP chroma 1 or 2
10155        if cab.decode_decision(O + 5) != 0 {
10156            t += 4;
10157        }
10158    }
10159    t += cab.decode_decision(O + 6) << 1; // I_16x16 pred mode (2 bins)
10160    t += cab.decode_decision(O + 7);
10161    t
10162}
10163
10164/// One `Intra_4x4` (or `8x8`) pred-mode CABAC parse (openh264 `ParseIntraPredModeLuma
10165/// Cabac`): `prev_intra4x4_pred_mode_flag` (ctx 68) then, if 0, `rem_intra4x4_pred_mode`
10166/// (3 bins at ctx 69). Returns `-1` for "use predicted mode", else the 0..7 remainder.
10167fn parse_intra4x4_pred_mode_cabac(cab: &mut crate::cabac::Cabac) -> i32 {
10168    const IPR: usize = 68;
10169    if cab.decode_decision(IPR) == 1 {
10170        return -1; // prev_intra4x4_pred_mode_flag = 1
10171    }
10172    let mut m = cab.decode_decision(IPR + 1) as i32;
10173    m |= (cab.decode_decision(IPR + 1) as i32) << 1;
10174    m |= (cab.decode_decision(IPR + 1) as i32) << 2;
10175    m
10176}
10177
10178/// `intra_chroma_pred_mode` CABAC parse (openh264 `ParseIntraPredModeChromaCabac`):
10179/// TU(cMax=3) — bin0 at ctx `64 + ctx_inc` (ctx_inc from neighbour chroma modes, 0 for
10180/// the corner MB), the rest at ctx 67. Returns the mode 0..3.
10181fn parse_intra_chroma_pred_mode_cabac(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
10182    const CIPR: usize = 64;
10183    if cab.decode_decision(CIPR + ctx_inc) == 0 {
10184        return 0;
10185    }
10186    if cab.decode_decision(CIPR + 3) == 0 {
10187        return 1;
10188    }
10189    if cab.decode_decision(CIPR + 3) == 0 {
10190        return 2;
10191    }
10192    3
10193}
10194
10195/// `coded_block_pattern` CABAC parse (openh264 `ParseCbpInfoCabac`), corner-MB variant
10196/// (top/left neighbours unavailable → their terms are 0). ctxIdxOffset 73 (luma) with 4
10197/// z-order 8×8 bins whose ctxInc uses the EARLIER-decoded bits within this MB, then
10198/// chroma bits at 77/81. Returns cbp: bits 0-3 = luma 8×8, bits 4-5 = chroma pattern.
10199pub fn parse_cbp_cabac(cab: &mut crate::cabac::Cabac, top: Option<u8>, left: Option<u8>) -> u32 {
10200    let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
10201    const CBP: usize = 73;
10202    let t = |m: u32| top.map_or(0u32, |c| ((c as u32 & m) == 0) as u32);
10203    let l = |m: u32| left.map_or(0u32, |c| ((c as u32 & m) == 0) as u32);
10204    let nb = |x: u32| (x == 0) as u32; // earlier 8×8 bin within this MB was NOT coded
10205    // Luma, 4 8×8 blocks in z-order. Top uses cbp bits 2/3, left uses 1/3.
10206    let b0 = cab.decode_decision(CBP + (l(1 << 1) + (t(1 << 2) << 1)) as usize);
10207    let b1 = cab.decode_decision(CBP + (nb(b0) + (t(1 << 3) << 1)) as usize);
10208    let b2 = cab.decode_decision(CBP + (l(1 << 3) + (nb(b0) << 1)) as usize);
10209    let b3 = cab.decode_decision(CBP + (nb(b2) + (nb(b1) << 1)) as usize);
10210    let mut cbp = b0 | (b1 << 1) | (b2 << 2) | (b3 << 3);
10211    // Chroma (4:2:0). ctxInc from neighbour chroma cbp (>>4).
10212    let ct = top.map_or(0u32, |c| ((c >> 4) != 0) as u32);
10213    let cl = left.map_or(0u32, |c| ((c >> 4) != 0) as u32);
10214    if cab.decode_decision(CBP + 4 + (cl + (ct << 1)) as usize) != 0 {
10215        let ct2 = top.map_or(0u32, |c| ((c >> 4) == 2) as u32);
10216        let cl2 = left.map_or(0u32, |c| ((c >> 4) == 2) as u32);
10217        let c1 = cab.decode_decision(CBP + 8 + (cl2 + (ct2 << 1)) as usize);
10218        cbp |= 1 << (4 + c1);
10219    }
10220    cbp
10221}
10222
10223fn read_ref_idx(r: &mut BitReader, num_ref_active: usize) -> Result<i32, OutOfData> {
10224    if num_ref_active == 2 {
10225        Ok(if r.read_bit()? { 0 } else { 1 }) // te(v): value = !bit
10226    } else {
10227        Ok(r.read_ue()? as i32)
10228    }
10229}
10230
10231/// B-partition prediction direction.
10232#[derive(Clone, Copy, PartialEq)]
10233enum BPred {
10234    L0,
10235    L1,
10236    Bi,
10237}
10238impl BPred {
10239    /// Whether this direction uses reference list `list` (0 or 1).
10240    fn uses(self, list: usize) -> bool {
10241        matches!(
10242            (self, list),
10243            (BPred::L0, 0) | (BPred::L1, 1) | (BPred::Bi, 0) | (BPred::Bi, 1)
10244        )
10245    }
10246}
10247
10248const B16X16: &[(usize, usize, usize, usize)] = &[(0, 0, 16, 16)];
10249const B16X8: &[(usize, usize, usize, usize)] = &[(0, 0, 16, 8), (0, 8, 16, 8)];
10250const B8X16: &[(usize, usize, usize, usize)] = &[(0, 0, 8, 16), (8, 0, 8, 16)];
10251
10252/// A partition region `(x, y, w, h)` in samples.
10253type Region = (usize, usize, usize, usize);
10254
10255/// B `mb_type` 1..=21 → (partition layout, MV-prediction mode 0/1/2 for 16×16/
10256/// 16×8/8×16, per-partition prediction direction) (spec Table 7-14).
10257/// Test-only view of [`b_inter_layout`] for the ENCODER crate: `(mvmode, p0, p1)`
10258/// with pred coded 1 = L0, 2 = L1, 3 = Bi — the encoder's `b_part_mb_type` is the
10259/// exact inverse, so a round-trip over 4..=21 gates the two tables against drift.
10260pub fn b_inter_shape(mb_type: u32) -> (u8, u8, u8) {
10261    let (_, mvmode, preds) = b_inter_layout(mb_type);
10262    let code = |p: BPred| match (p.uses(0), p.uses(1)) {
10263        (true, true) => 3,
10264        (true, false) => 1,
10265        _ => 2,
10266    };
10267    (mvmode, code(preds[0]), code(preds[1]))
10268}
10269
10270fn b_inter_layout(mb_type: u32) -> (&'static [Region], u8, [BPred; 2]) {
10271    use BPred::*;
10272    match mb_type {
10273        1 => (B16X16, 0, [L0, L0]),
10274        2 => (B16X16, 0, [L1, L1]),
10275        3 => (B16X16, 0, [Bi, Bi]),
10276        4 => (B16X8, 1, [L0, L0]),
10277        5 => (B8X16, 2, [L0, L0]),
10278        6 => (B16X8, 1, [L1, L1]),
10279        7 => (B8X16, 2, [L1, L1]),
10280        8 => (B16X8, 1, [L0, L1]),
10281        9 => (B8X16, 2, [L0, L1]),
10282        10 => (B16X8, 1, [L1, L0]),
10283        11 => (B8X16, 2, [L1, L0]),
10284        12 => (B16X8, 1, [L0, Bi]),
10285        13 => (B8X16, 2, [L0, Bi]),
10286        14 => (B16X8, 1, [L1, Bi]),
10287        15 => (B8X16, 2, [L1, Bi]),
10288        16 => (B16X8, 1, [Bi, L0]),
10289        17 => (B8X16, 2, [Bi, L0]),
10290        18 => (B16X8, 1, [Bi, L1]),
10291        19 => (B8X16, 2, [Bi, L1]),
10292        20 => (B16X8, 1, [Bi, Bi]),
10293        _ => (B8X16, 2, [Bi, Bi]), // 21
10294    }
10295}
10296
10297/// Whether a B `sub_mb_type` (1..=12) uses reference list `list`.
10298fn b_sub_uses(st: u32, list: usize) -> bool {
10299    let pred = match st {
10300        1 | 4 | 5 | 10 => 0,  // L0
10301        2 | 6 | 7 | 11 => 1,  // L1
10302        _ => 2,               // Bi (3, 8, 9, 12)
10303    };
10304    (list == 0 && pred != 1) || (list == 1 && pred != 0)
10305}
10306
10307/// Sub-partition shapes within an 8×8 for a B `sub_mb_type` (1..=12).
10308fn b_sub_parts(st: u32) -> &'static [(usize, usize, usize, usize)] {
10309    match st {
10310        1..=3 => &[(0, 0, 8, 8)],
10311        4 | 6 | 8 => &[(0, 0, 8, 4), (0, 4, 8, 4)],
10312        5 | 7 | 9 => &[(0, 0, 4, 8), (4, 0, 4, 8)],
10313        _ => &[(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)], // 10/11/12
10314    }
10315}
10316
10317/// Sub-macroblock partition layout `(x, y, w, h)` in samples within an 8×8, for
10318/// a P-slice `sub_mb_type` (0 = 8×8, 1 = 8×4, 2 = 4×8, 3 = 4×4).
10319fn sub_mb_partitions(sub_type: u32) -> &'static [(usize, usize, usize, usize)] {
10320    match sub_type {
10321        0 => &[(0, 0, 8, 8)],
10322        1 => &[(0, 0, 8, 4), (0, 4, 8, 4)],
10323        2 => &[(0, 0, 4, 8), (4, 0, 4, 8)],
10324        _ => &[(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)],
10325    }
10326}
10327
10328/// Copy a contiguous `w`x`h` block into a strided destination at `(x0, y0)`.
10329///
10330/// The width is SPECIALISED. Written as a per-pixel loop bounded by a runtime `w`,
10331/// this lowers to a bounds-checked store per pixel — and where it is a row copy of
10332/// runtime length, to a variable-length `memcpy` CALL per row. Both are the same
10333/// codegen trap the ENCODER fixed long ago ("H-17"); the decoder's copy of it was
10334/// never fixed, and it costs the most on exactly the streams a real encoder emits,
10335/// because x264's sub-16x16 partitions call it far more often than our own
10336/// 16x16-dominated bitstreams ever did. Byte-identical to the scalar form.
10337#[inline]
10338fn restride(dst: &mut [u8], dst_stride: usize, x0: usize, y0: usize, src: &[u8], w: usize, h: usize) {
10339    macro_rules! rows {
10340        ($n:expr) => {{
10341            for dy in 0..h {
10342                dst[(y0 + dy) * dst_stride + x0..][..$n].copy_from_slice(&src[dy * $n..][..$n]);
10343            }
10344        }};
10345    }
10346    match w {
10347        16 => rows!(16),
10348        8 => rows!(8),
10349        4 => rows!(4),
10350        2 => rows!(2),
10351        _ => {
10352            for dy in 0..h {
10353                dst[(y0 + dy) * dst_stride + x0..][..w].copy_from_slice(&src[dy * w..][..w]);
10354            }
10355        }
10356    }
10357}
10358
10359/// Un-scans an 8×8 block from frame zig-zag scan order to raster (spec Table 8-12).
10360fn un_scan_8x8(scan: &[i32; 64]) -> [i32; 64] {
10361    const ZZ8: [usize; 64] = [
10362        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,
10363        20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
10364        58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
10365    ];
10366    let mut out = [0i32; 64];
10367    for k in 0..64 {
10368        out[ZZ8[k]] = scan[k];
10369    }
10370    out
10371}
10372
10373#[cfg(test)]
10374mod tests {
10375    use super::*;
10376
10377    fn fd(qp: u8, offset: i32) -> FrameDecoder {
10378        FrameDecoder::new(1, 1, qp, offset, Vec::new(), 1, false, false, true)
10379    }
10380
10381    #[test]
10382    fn mb_qp_delta_accumulates_mod_52() {
10383        let mut d = fd(26, 0);
10384        assert_eq!(d.cur_qp, 26, "QPy starts at the slice QP");
10385        d.step_qp(4).unwrap();
10386        assert_eq!(d.cur_qp, 30); // 26 + 4
10387        d.step_qp(-10).unwrap();
10388        assert_eq!(d.cur_qp, 20); // carries from the previous MB, not the slice
10389        // Wrap-around: (20 + 40 + 52) % 52 = 112 % 52 = 8.
10390        d.step_qp(40).unwrap();
10391        assert_eq!(d.cur_qp, 8);
10392        // Negative wrap: (8 - 20 + 52) % 52 = 40.
10393        d.step_qp(-20).unwrap();
10394        assert_eq!(d.cur_qp, 40);
10395    }
10396
10397    #[test]
10398    fn chroma_qp_index_offset_applied_and_clamped() {
10399        // Offset 0 reproduces the bare luma->chroma table (QP30 -> 29).
10400        assert_eq!(fd(0, 0).chroma_qp_for(30), 29);
10401        // Positive offset shifts the table lookup (QP30 + 2 -> table[2] = 31).
10402        assert_eq!(fd(0, 2).chroma_qp_for(30), 31);
10403        // The qPi index is clamped into 0..=51 before the lookup.
10404        assert_eq!(fd(0, -12).chroma_qp_for(5), chroma_qp(0));
10405        assert_eq!(fd(0, 99).chroma_qp_for(40), chroma_qp(51));
10406    }
10407}