Skip to main content

manifold/
mnf8.rs

1// MNF8 — format v4 (ADR-002: Entropy Backend and Residual Probability Modeling).
2//
3// MNF8 = MNF7 (modes 0-5, PLANAR|ZSTD flags) + a backend-identifier field +
4// encoder profiles; new magic, new header field. MNF5/6/7 files decode via
5// their own magics — no compatibility break (README §9 versioning rule).
6//
7// Header (32 bytes):
8//   "MNF8", u32 LE width, height, tile=16, tile_count, payload_len,
9//   u32 flags (MNF7 semantics + MNF8-owned bits:
10//     bit0 PLANAR, bit1 ZSTD, bit2 PRED_MED + bit3 TOKENS (ADR-003),
11//     ADR-004: bit4 PRED_PLANE, bit5 PRED_LEFT — bits 6..31 reserved),
12//   u32 backend:
13//     0 = zlib-9  (MNF7 control; payload is a zlib stream)
14//     1 = zstd-19 (xxh64-checksummed frame; decoder window capped at 23)
15//     2 = RESERVED — brotli was prototyped but unshipped: its static
16//         dictionary's cost accounting was measured only in the study, and
17//         a dictionary-free brotli backend changed nothing on the corpus.
18//         Reject until its gate re-passes (audit review 2026-10-25).
19//     3 = RESERVED (custom mixed run/rANS coder; reject until it passes the gate)
20//   other values: reject like reserved flag bits.
21//
22// FLAG_TOKENS (ADR-003, docs/mnf8-adr-003-tokens.md): the payload is NOT a
23// record stream — it is the LZ77-2D token wire over the image-wide MED
24// residual plane (crate::tokens), container-compressed by the backend.
25// Mutually exclusive with everything per-tile: TOKENS forces plain
26// interleaved literal records in the pick arms it competes against and is
27// rejected at decode with PLANAR (the token plane is always interleaved).
28//
29// ADR-004 (docs/mnf8-adr-004-branch-pred.md): bits 4-5 select the image-
30// wide predictor on mode-0 literal bodies — PRED_MED (edge-sense, the
31// v1.2 predecessor), PRED_PLANE (LOCO-I plane branch forced: a+b−cc), or
32// PRED_LEFT (pure left). All three decode causally (the exact same walk,
33// only the interior predictor function differs). Exactly one may be set;
34// the encoder measures all three per file (retention gate: branch choice
35// beats neighborhood choice, docs/mnf9-branch-gate.md, −7.0%).
36//
37// Profiles (encoder policy, not wire format):
38//   balanced  -> always zstd-19 (flags measured: planar vs interleaved).
39//   maximum   -> builds the tile stream under each (backend, flags)
40//                candidate, re-costing per-tile mode selection through that
41//                candidate's coder (stage-2 evidence: selection is
42//                backend-dependent), and emits the smallest file.
43//
44// Records: exactly MNF7's (modes 0-3 plus MNF7 color modes 4/5), decoded by
45// the same record walker as decode_mnf7; only container + coder differ.
46
47use std::io::Read;
48
49use crate::codec::{apply_transform, div_ceil, split_tiles, tile_sig, Atlas, Dims, TILE_SIZE};
50use crate::error::{Error, Result};
51use crate::mnf6::{
52    compress_stream, decode_record_walk, med_predict, modcolor_forward_iter, pack9, planar_pack,
53    proxy_cost, ycocg_forward, Coder, MnF6Stats, FLAG_PLANAR,
54};
55use crate::tokens;
56
57/// Header size: `MNF8` + 7×`u32` LE (adds backend id) — 4 B over MNF7.
58pub const HEADER8: usize = 32;
59
60// MNF8-only flags (MNF6/7 keep !0b11 frozen; MNF8 owns bits 2-3):
61/// Literal bodies carry image-wide MED residuals (decode inverts causally).
62pub const FLAG_PRED_MED: u32 = 4; // mode-0 literal bodies carry image-wide MED residuals
63/// Payload = LZ77-2D token wire over the MED residual plane (ADR-003).
64pub const FLAG_TOKENS: u32 = 8;
65/// Image-wide predictor on literal bodies = LOCO-I plane branch (ADR-004).
66pub const FLAG_PRED_PLANE: u32 = 16;
67/// Image-wide predictor on literal bodies = pure left (ADR-004).
68pub const FLAG_PRED_LEFT: u32 = 32;
69/// Bits above bit5 reserved (MNF8 owns bits 2-5).
70pub const FLAGS_RESERVED8: u32 = !0b111111;
71
72/// zlib-9 backend.
73pub const BACKEND_ZLIB: u32 = 0;
74/// zstd-19 backend (ships in Balanced and Maximum).
75pub const BACKEND_ZSTD19: u32 = 1;
76/// Reserved (brotli prototype; decode rejects).
77pub const BACKEND_RESERVED2: u32 = 2; // brotli prototype — unshipped, reject
78/// Reserved (rANS prototype measured DENIED with honest transport; decode
79/// rejects — docs/mnf9-rans-arm.md §honest-re-measure).
80pub const BACKEND_RESERVED3: u32 = 3;
81
82// zstd-19's default window is 8 MiB = 2^23; hard-pin the decoder cap to it
83// (review fix 4: a hostile frame can't claim a 128 MiB window).
84/// zstd decoder window ceiling (8 MiB; matches level-19 encoder).
85pub const ZSTD_WINDOW_LOG_MAX: u32 = 23;
86
87/// Rank count for candidate references... (kept from the shared atlas table;
88/// RANK_CANDIDATES lives in codec — do not re-derive here.)
89use crate::codec::RANK_CANDIDATES;
90
91/// Encoder search breadth.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum Profile {
94    /// zstd-19 only: fast, sane default.
95    Balanced,
96    /// All backends + flag variants measured; ships the smallest.
97    Maximum,
98}
99
100/// Backend candidates for the `maximum` profile, in measured-preference order.
101fn backend_candidates() -> Vec<(u32, Coder)> {
102    vec![
103        (BACKEND_ZSTD19, Coder::Zstd(19)),
104        (BACKEND_ZLIB, Coder::Zlib),
105    ]
106}
107
108fn backend_compress(backend: u32, buf: &[u8]) -> Result<Vec<u8>> {
109    match backend {
110        BACKEND_ZLIB => Ok(compress_stream(&Coder::Zlib, buf)),
111        BACKEND_ZSTD19 => Ok(compress_stream(&Coder::Zstd(19), buf)),
112        other => Err(Error::fmt_msg(format!(
113            "backend {other} not a compressed-output coder"
114        ))),
115    }
116}
117
118fn backend_decompress(backend: u32, buf: &[u8], limit: u64) -> Result<Vec<u8>> {
119    match backend {
120        BACKEND_ZLIB => {
121            // canonical-decode (review small item): every payload byte must
122            // belong to the stream — zlib's total_in tells us exactly how
123            // much was consumed.
124            let mut d = flate2::read::ZlibDecoder::new(buf).take(limit + 1);
125            let mut v = Vec::new();
126            d.read_to_end(&mut v)
127                .map_err(|_| Error::Decompress("corrupt zlib stream"))?;
128            if v.len() as u64 > limit {
129                return Err(Error::fmt_msg("bomb"));
130            }
131            if d.get_ref().total_in() as usize != buf.len() {
132                return Err(Error::fmt_msg("trailing bytes after zlib stream"));
133            }
134            Ok(v)
135        }
136        BACKEND_ZSTD19 => {
137            // window capped at the encoder's actual need (review fix 4);
138            // Cursor-wrapped so post-decode input position is observable
139            // (canonical-decode check, review small item).
140            let cursor = std::io::Cursor::new(buf);
141            let mut d = zstd::stream::read::Decoder::new(cursor)
142                .map_err(|_| Error::Decompress("zstd init"))?;
143            d.window_log_max(ZSTD_WINDOW_LOG_MAX)
144                .map_err(|_| Error::Decompress("zstd window param"))?;
145            let mut v = Vec::new();
146            d.single_frame()
147                .take(limit + 1)
148                .read_to_end(&mut v)
149                .map_err(|_| Error::Decompress("corrupt zstd stream"))?;
150            if v.len() as u64 > limit {
151                return Err(Error::fmt_msg("bomb"));
152            }
153            // NOTE (honest limitation): zlib canonicality is exact via
154            // total_in above; the zstd stream API here cannot cheaply report
155            // the decoder's exact consumed offset (BufReader may also read
156            // ahead), so bytes after a complete zstd frame are tolerated.
157            // Canonical zstd files remain an option via a future zstd-safe
158            // one-shot decode; not worth it today (ponytail).
159            Ok(v)
160        }
161        other => Err(Error::fmt_msg(format!(
162            "backend {other} not a decompressable coder"
163        ))),
164    }
165}
166
167/// Encode RGB8 pixels as MNF8.
168///
169/// `profile = Balanced`: zstd-19 pinned; measures the planar vs
170/// interleaved flag variants under that coder (same honesty as MNF7
171/// measures flags, minus the zlib arms excluded by ADR-002).
172///
173/// `profile = Maximum`: measures backend×flags candidates and emits the
174/// smallest final file. Per-tile mode selection is re-costed inside each
175/// candidate's own coder — the stage-2 protocol (docs/entropy-backends-v13.md).
176/// There is NO shared per-tile loop with encode_mnf7: the one function
177/// `encode_stream_coder` is parameterized by coder and every caller routes
178/// through it (review fix 8: MNF7's loop stays the frozen-format history;
179/// MNF8 owns its selection policy).
180pub fn encode_mnf8(
181    pixels: &[u8],
182    dims: Dims,
183    use_geom: bool,
184    profile: Profile,
185) -> (Vec<u8>, MnF6Stats, u32) {
186    let candidates: Vec<(u32, Coder)> = match profile {
187        Profile::Balanced => vec![(BACKEND_ZSTD19, Coder::Zstd(19))],
188        Profile::Maximum => backend_candidates(),
189    };
190    let mut best: Option<(Vec<u8>, u32, MnF6Stats, u32)> = None;
191    // ADR-004: the three literal-body predictors × (interleaved|planar)
192    // as per-file arms, each measured under every backend candidate,
193    // complete serialized cost. (PLANAR's own measured value is real:
194    // gradient −28% under z19 in the stage-2 matrix — never drop it.)
195    let pred_arms = [
196        PredKind::None,
197        PredKind::Med,
198        PredKind::Plane,
199        PredKind::Left,
200    ];
201    for (backend, coder) in candidates {
202        for kind in pred_arms {
203            for planar in [false, true] {
204                let med = kind == PredKind::Med;
205                let flags = kind.flag_bit() | if planar { FLAG_PLANAR } else { 0 };
206                let (file, st) = encode_stream_coder(
207                    pixels, dims, true, use_geom, flags, planar, med, kind, &coder, backend,
208                )
209                .expect("encoding an in-memory image cannot fail");
210                let size = file.len();
211                if best.as_ref().is_none_or(|b| size < b.0.len()) {
212                    best = Some((file, flags, st, backend));
213                }
214            }
215        }
216        // ADR-003 token arm: tokens over the MED plane, same backend (the
217        // token grammar's predictor is fixed = MED; ADR-004 composes with
218        // the record arms only). Rate-aware by construction.
219        let flags = FLAG_PRED_MED | FLAG_TOKENS;
220        for cap in [16, tokens::MAX_CANDIDATES] {
221            let (file, st) = encode_tokens_coder_cap(pixels, dims, backend, flags, cap)
222                .expect("encoding an in-memory image cannot fail");
223            let size = file.len();
224            if best.as_ref().is_none_or(|b| size < b.0.len()) {
225                best = Some((file, flags, st, backend));
226            }
227        }
228    }
229    let (file, flags, mut st, backend) = best.expect("at least one candidate");
230    st.chosen_flags = flags;
231    (file, st, backend)
232}
233
234/// The image-wide literal-body predictor (ADR-004): which causal rule
235/// mode-0 bodies carry. All three decode with the same causal walk.
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub enum PredKind {
238    /// No pre-prediction (raw literal bodies).
239    None,
240    /// MED edge-sense (v1.2 FLAG_PRED_MED).
241    Med,
242    /// LOCO-I plane branch forced: pred = a + b − cc, clamped (ADR-004).
243    Plane,
244    /// Pure left: pred = left neighbor (ADR-004).
245    Left,
246}
247
248impl PredKind {
249    fn from_flags(flags: u32) -> PredKind {
250        if flags & FLAG_PRED_MED != 0 {
251            PredKind::Med
252        } else if flags & FLAG_PRED_PLANE != 0 {
253            PredKind::Plane
254        } else if flags & FLAG_PRED_LEFT != 0 {
255            PredKind::Left
256        } else {
257            PredKind::None
258        }
259    }
260
261    fn flag_bit(self) -> u32 {
262        match self {
263            PredKind::None => 0,
264            PredKind::Med => FLAG_PRED_MED,
265            PredKind::Plane => FLAG_PRED_PLANE,
266            PredKind::Left => FLAG_PRED_LEFT,
267        }
268    }
269
270    /// The predictor's value at an interior pixel from causal neighbors.
271    /// Decode-side truth; the encode-side helpers MUST match exactly.
272    fn predict(self, a: u32, b: u32, cc: u32) -> u32 {
273        match self {
274            PredKind::None | PredKind::Med => crate::mnf6::med_predict_pixel(a, b, cc),
275            PredKind::Plane => {
276                // LOCO-I plane branch, clamped into byte range (the encode-
277                // side plane_predict clamps identically).
278                (a as i64 + b as i64 - cc as i64).clamp(0, 255) as u32
279            }
280            PredKind::Left => a,
281        }
282    }
283}
284
285/// Image-wide prediction over the WHOLE image with the given rule.
286/// ENCODE-side only: neighbor reads come from TRUE pixels (the decode walk
287/// reads reconstructed pixels; reconstructed-earlier == true by the mod-256
288/// induction, so the two walks agree — reading the pred stream instead of
289/// the pixel stream chains predictions and BREAKS the identity, the bug the
290/// gradient Plane gate caught: pred(0,1) = pred(0,0) instead of left pixel).
291fn image_predict(pixels: &[u8], w: usize, h: usize, kind: PredKind) -> Vec<u8> {
292    let mut out = vec![0u8; pixels.len()];
293    for y in 0..h {
294        for x in 0..w {
295            for c in 0..3usize {
296                let i = (y * w + x) * 3 + c;
297                let pred: u32 = if x == 0 && y == 0 {
298                    127
299                } else if y == 0 {
300                    u32::from(pixels[i - 3])
301                } else if x == 0 {
302                    u32::from(pixels[i - w * 3])
303                } else {
304                    let a = u32::from(pixels[i - 3]);
305                    let b = u32::from(pixels[i - w * 3]);
306                    let cc = u32::from(pixels[i - w * 3 - 3]);
307                    kind.predict(a, b, cc)
308                };
309                out[i] = pred as u8;
310            }
311        }
312    }
313    out
314}
315
316/// Token-arm stream builder (ADR-003): MED residual plane → token wire →
317/// container coder. Reuses every mnf6/mnf8 primitive; stats report the
318/// token counts in the literal fields (per-arm accounting preserved).
319/// Token arm with explicit matcher depth (ADR-005: the pick measures the
320/// v1.7 shallow arm AND the deep-chain arm; the pick absorbs the choice).
321fn encode_tokens_coder_cap(
322    pixels: &[u8],
323    dims: Dims,
324    backend: u32,
325    flags: u32,
326    cap: usize,
327) -> Result<(Vec<u8>, MnF6Stats)> {
328    let med_pred = med_predict(pixels, dims.width as usize, dims.height as usize);
329    let res: Vec<u8> = pixels
330        .iter()
331        .zip(&med_pred)
332        .map(|(x, m)| x.wrapping_sub(*m))
333        .collect();
334    let (wire, ts) =
335        tokens::encode_plane_cap(&res, dims.width as usize, dims.height as usize, cap)?;
336    let payload = backend_compress(backend, &wire)?;
337    let payload_len =
338        u32::try_from(payload.len()).map_err(|_| Error::fmt_msg("payload exceeds u32"))?;
339    let mut file = Vec::with_capacity(HEADER8 + payload.len());
340    file.extend_from_slice(b"MNF8");
341    file.extend_from_slice(&dims.width.to_le_bytes());
342    file.extend_from_slice(&dims.height.to_le_bytes());
343    file.extend_from_slice(&TILE_SIZE.to_le_bytes());
344    let cols = div_ceil(dims.width, TILE_SIZE);
345    let rows = div_ceil(dims.height, TILE_SIZE);
346    let tile_count: u32 = cols
347        .checked_mul(rows)
348        .ok_or_else(|| Error::fmt_msg("too many tiles"))?;
349    file.extend_from_slice(&tile_count.to_le_bytes());
350    file.extend_from_slice(&payload_len.to_le_bytes());
351    file.extend_from_slice(&flags.to_le_bytes());
352    file.extend_from_slice(&backend.to_le_bytes());
353    file.extend_from_slice(&payload);
354    let st = MnF6Stats {
355        literal: u32::try_from(ts.literals).unwrap_or(u32::MAX),
356        ref_exact: u32::try_from(ts.matches).unwrap_or(u32::MAX),
357        ..MnF6Stats::default()
358    };
359    Ok((file, st))
360}
361
362/// One tile-stream encoder, parameterized by coder + backend — THE selection
363/// loop for MNF8 (no per-variant duplicates of this function).
364///
365/// Arm controls (both default true, plumbable for ablation):
366/// - `use_atlas`: candidate references at all (false = pure prediction arm)
367/// - `use_geom`: D8 transforms on top of exact references (false = t=0 only,
368///   the v0.5 exact-reference semantics — codec.rs pins t=0 for this case
369///   and MNF8 must match it)
370///
371/// Shared YCoCg (mode 3) and planar layout stay ON in both arms (ADR-002).
372#[allow(clippy::too_many_arguments)]
373fn encode_stream_coder(
374    pixels: &[u8],
375    dims: Dims,
376    use_atlas: bool,
377    use_geom: bool,
378    flags: u32,
379    planar: bool,
380    med: bool,
381    kind: PredKind,
382    coder: &Coder,
383    backend: u32,
384) -> Result<(Vec<u8>, MnF6Stats)> {
385    // ADR-004: Plane/Left are PURE prediction arms (the measured gate's
386    // winning form) — no atlas candidates, no YCoCg; mode-0 bodies only.
387    let pure_pred = kind == PredKind::Plane || kind == PredKind::Left;
388    let med_pred = match kind {
389        PredKind::Med => med_predict(pixels, dims.width as usize, dims.height as usize),
390        PredKind::Plane => image_predict(
391            pixels,
392            dims.width as usize,
393            dims.height as usize,
394            PredKind::Plane,
395        ),
396        PredKind::Left => image_predict(
397            pixels,
398            dims.width as usize,
399            dims.height as usize,
400            PredKind::Left,
401        ),
402        PredKind::None => Vec::new(),
403    };
404    let tiles = split_tiles(pixels, dims);
405    let mut raw: Vec<u8> = Vec::with_capacity(tiles.len() * 774);
406    let mut stats = MnF6Stats::default();
407    let mut atlas = Atlas::new();
408
409    for (idx, tile) in tiles.iter().enumerate() {
410        let n = tile.nbytes();
411        let pack = |rgb: &[u8]| -> Vec<u8> {
412            if planar {
413                planar_pack(rgb)
414            } else {
415                rgb.to_vec()
416            }
417        };
418
419        // mode-0 literal candidate; under any ADR-004 predictor the body
420        // carries that predictor's residual (decoder reconstructs causally)
421        let lit_src: Vec<u8> = if med || pure_pred {
422            let (tw, th) = (tile.width as usize, tile.height as usize);
423            let cols_px = dims.width as usize; // med_pred is per-PIXEL flat
424            let tx = (idx % ((dims.width as usize).div_ceil(16))) * 16;
425            let ty = (idx / ((dims.width as usize).div_ceil(16))) * 16;
426            let mut src = Vec::with_capacity(n);
427            for y in 0..th {
428                let row_off = ((ty + y) * cols_px + tx) * 3;
429                src.extend_from_slice(&med_pred[row_off..row_off + tw * 3]);
430            }
431            tile.data
432                .iter()
433                .zip(&src)
434                .map(|(x, m)| x.wrapping_sub(*m))
435                .collect()
436        } else {
437            tile.data.clone()
438        };
439
440        let lit_body = pack(&lit_src);
441        let mut choice_body = lit_body.clone();
442        let mut choice_mode = 0u8;
443        let mut choice_ref: Option<(u32, u8)> = None;
444        let mut best_cost = 1 + proxy_cost(coder, &lit_body);
445
446        // shared literal candidates (both arms): YCoCg-R (mode 3).
447        // ADR-004 pure arms (Plane/Left) skip YCoCg + atlas — measured pure.
448        if !pure_pred {
449            let (cy, cco, ccg) = ycocg_forward(&tile.data);
450            let mut m3 = Vec::with_capacity(n + 160);
451            m3.extend_from_slice(&cy);
452            m3.extend_from_slice(&pack9(&cco));
453            m3.extend_from_slice(&pack9(&ccg));
454            let m3_cost = 1 + proxy_cost(coder, &m3);
455            if m3_cost < best_cost {
456                best_cost = m3_cost;
457                choice_body = m3;
458                choice_mode = 3;
459            }
460        }
461
462        // invariant hoist (review fix 7): T(tile.data) is candidate- and
463        // transform-independent — compute once per tile
464        let tile_t = modcolor_forward_iter(&tile.data);
465
466        // atlas candidates (arm-controlled; review fixes 2 + use_atlas).
467        // ADR-004 pure prediction arms exclude the atlas (measured pure).
468        if use_atlas && !pure_pred && tiles.len() > 1 {
469            let sig = tile_sig(tile.width, tile.height, &tile.data);
470            for prior in atlas.rank_by_sig(&sig, tile.width, tile.height, RANK_CANDIDATES) {
471                let (_, _, ref_rgb) = &atlas.patches[prior as usize];
472                // review fix 2: `use_geom = false` means EXACT references only
473                // (t=0), matching codec.rs's v0.5 ablation semantics; square
474                // tiles may use all 8 D8 transforms, partial tiles only the
475                // shape-keeping subgroup.
476                let transforms: &[u8] = if !use_geom {
477                    &[0]
478                } else if tile.width == tile.height {
479                    &[0, 1, 2, 3, 4, 5, 6, 7]
480                } else {
481                    &[0, 1, 2, 3]
482                };
483                for &t in transforms {
484                    let base = if t == 0 {
485                        ref_rgb.clone()
486                    } else {
487                        apply_transform(t, tile.width, tile.height, ref_rgb)
488                    };
489                    let mut r = vec![0u8; n];
490                    for i in 0..n {
491                        r[i] = tile.data[i].wrapping_sub(base[i]);
492                    }
493                    let body = pack(&r);
494                    // metadata: mode byte + u32 idx + optional transform byte
495                    let meta: u64 = if t == 0 { 5 } else { 6 };
496                    let cost = meta + proxy_cost(coder, &body);
497                    if cost < best_cost {
498                        best_cost = cost;
499                        choice_body = body;
500                        choice_mode = if t == 0 { 1 } else { 2 };
501                        choice_ref = Some((prior, t));
502                    }
503                    // color-domain candidate (MNF7 modes 4/5). Review fix 3:
504                    // the wire carries NOTHING extra for color modes — the
505                    // mode byte already tells the decoder which domain to
506                    // decode the body in. The phantom `+ 1` that the old
507                    // cost model charged made the encoder under-select
508                    // color modes (contaminating any color-mode ablation).
509                    let base_t = modcolor_forward_iter(&base);
510                    let mut r_c = vec![0u8; n];
511                    for i in 0..n {
512                        r_c[i] = tile_t[i].wrapping_sub(base_t[i]);
513                    }
514                    let body_c = pack(&r_c);
515                    let cost_c = meta + proxy_cost(coder, &body_c);
516                    if cost_c < best_cost {
517                        best_cost = cost_c;
518                        choice_body = body_c;
519                        choice_mode = if t == 0 { 4 } else { 5 };
520                        choice_ref = Some((prior, t));
521                    }
522                }
523            }
524        }
525
526        stats.modes.push(choice_mode);
527        stats.refs.push(choice_ref);
528        match choice_mode {
529            0 => stats.literal += 1,
530            1 => stats.ref_exact += 1,
531            2 => {
532                stats.ref_geom += 1;
533                if choice_ref.map(|(_, t)| t != 0).unwrap_or(false) {
534                    stats.geom_nontrivial += 1;
535                }
536            }
537            3 => stats.ycocg_literal += 1,
538            4 => stats.ref_exact_color += 1,
539            5 => {
540                stats.ref_geom_color += 1;
541                if choice_ref.map(|(_, t)| t != 0).unwrap_or(false) {
542                    stats.geom_nontrivial += 1;
543                }
544            }
545            _ => unreachable!("mode byte constrained above"),
546        }
547        raw.push(choice_mode);
548        if let Some((p, t)) = choice_ref {
549            raw.extend_from_slice(&p.to_le_bytes());
550            if t != 0 {
551                raw.push(t);
552            }
553        }
554        raw.extend_from_slice(&choice_body);
555        atlas.insert(tile);
556    }
557
558    let payload = backend_compress(backend, &raw)?;
559    let payload_len =
560        u32::try_from(payload.len()).map_err(|_| Error::fmt_msg("payload exceeds u32"))?; // review: no lossy cast
561    let mut file = Vec::with_capacity(HEADER8 + payload.len());
562    file.extend_from_slice(b"MNF8");
563    file.extend_from_slice(&dims.width.to_le_bytes());
564    file.extend_from_slice(&dims.height.to_le_bytes());
565    file.extend_from_slice(&TILE_SIZE.to_le_bytes());
566    let tile_count = u32::try_from(tiles.len()).map_err(|_| Error::fmt_msg("too many tiles"))?;
567    file.extend_from_slice(&tile_count.to_le_bytes());
568    file.extend_from_slice(&payload_len.to_le_bytes());
569    file.extend_from_slice(&flags.to_le_bytes());
570    file.extend_from_slice(&backend.to_le_bytes());
571    file.extend_from_slice(&payload);
572    Ok((file, stats))
573}
574
575// --- decoding ----------------------------------------------------------------
576
577/// Decode MNF8. Record walk is MNF7's (shared `decode_record_walk`); the
578/// container fields differ (32-byte header, backend id) and the coder
579/// dispatch runs on the backend field. Every non-shipped backend id is an
580/// ERROR (never a panic path) — review fix 1: backend 2 previously fell
581/// into `unreachable!()` after passing the too-loose header checks.
582pub fn decode_mnf8(buf: &[u8]) -> Result<(Dims, Vec<u8>, u32)> {
583    decode_mnf8_with_limits(buf, &crate::mnf6::DecodeLimits::default())
584}
585
586/// Decode MNF8 with configurable ceilings (library API; same semantics as
587/// decode_mnf7_with_limits).
588pub fn decode_mnf8_with_limits(
589    buf: &[u8],
590    lim: &crate::mnf6::DecodeLimits,
591) -> Result<(Dims, Vec<u8>, u32)> {
592    if buf.len() < HEADER8 {
593        return Err(Error::fmt_msg("short"));
594    }
595    if buf[0..4] != *b"MNF8" {
596        return Err(Error::fmt_msg("magic"));
597    }
598    let u32le = |o: usize| u32::from_le_bytes(buf[o..o + 4].try_into().unwrap());
599    let width = u32le(4);
600    let height = u32le(8);
601    if width == 0 || height == 0 {
602        return Err(Error::fmt_msg("dims"));
603    }
604    if width as u64 * height as u64 > lim.max_pixels {
605        return Err(Error::Limits {
606            which: "max_pixels",
607            limit: lim.max_pixels,
608            actual: width as u64 * height as u64,
609        });
610    }
611    if u32le(12) != TILE_SIZE {
612        return Err(Error::fmt_msg("tilesize"));
613    }
614    let cols = div_ceil(width, TILE_SIZE);
615    let rows = div_ceil(height, TILE_SIZE);
616    let tile_count = u32le(16);
617    if tile_count != cols * rows {
618        return Err(Error::fmt_msg("tilecount"));
619    }
620    if tile_count > lim.max_tiles {
621        return Err(Error::Limits {
622            which: "max_tiles",
623            limit: u64::from(lim.max_tiles),
624            actual: u64::from(tile_count),
625        });
626    }
627    let payload_len = u32le(20);
628    if payload_len > (1 << 31) - 1 {
629        return Err(Error::fmt_msg("payloadlen"));
630    }
631    if buf.len() != HEADER8 + payload_len as usize {
632        return Err(Error::fmt_msg("filelen"));
633    }
634    let flags = u32le(24);
635    // MNF8 owns bits 2-5 (PRED_MED, TOKENS, PRED_PLANE, PRED_LEFT); MNF6/7
636    // do not — wider mask here
637    if flags & FLAGS_RESERVED8 != 0 {
638        return Err(Error::fmt_msg("reserved-flags"));
639    }
640    let token_wire = flags & FLAG_TOKENS != 0;
641    // ADR-003: the token plane is interleaved RGB8 by definition — PLANAR
642    // carries no meaning there and no encoder emits the combination.
643    if token_wire && flags & FLAG_PLANAR != 0 {
644        return Err(Error::fmt_msg("tokens+planar are exclusive"));
645    }
646    // ADR-004: at most ONE image-wide predictor bit may be set.
647    let pred_count = u32::from(flags & FLAG_PRED_MED != 0)
648        + u32::from(flags & FLAG_PRED_PLANE != 0)
649        + u32::from(flags & FLAG_PRED_LEFT != 0);
650    if pred_count > 1 {
651        return Err(Error::fmt_msg("multiple predictors selected"));
652    }
653    // ADR-003: the token grammar's payload is always MED-residual; a token
654    // file claiming a different predictor misdescribes its payload.
655    if token_wire && flags & (FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0 {
656        return Err(Error::fmt_msg("tokens+plane/left predictor exclusive"));
657    }
658    let backend = u32le(28);
659    if !matches!(
660        backend,
661        BACKEND_ZLIB | BACKEND_ZSTD19 | BACKEND_RESERVED2 | BACKEND_RESERVED3
662    ) {
663        return Err(Error::fmt_msg(format!("unknown backend {backend}")));
664    }
665    // ADR-006 measured DENIED: rANS arm lost to zstd-19 on every corpus
666    // file with honest model transport (docs/mnf9-rans-arm.md §verdict).
667    // Keep backend 3 reject-on-decode until an arm wins honestly.
668    if matches!(backend, BACKEND_RESERVED2 | BACKEND_RESERVED3) {
669        return Err(Error::fmt_msg(format!(
670            "reserved backend {backend} (not shipped)"
671        )));
672    }
673    let planar = flags & FLAG_PLANAR != 0;
674
675    // Decompression bound — degenerate-shape-honest (review fix 5):
676    //   generic record worst case needs 6 metadata bytes + the tile's own
677    //   body (tw*th*3); summed over tiles that is 6*N + 3WH, NOT N * 833
678    //   (the old full-tile assumption turned a 1x67108864 image's cap into
679    //   ~3.5 GB against a ~200 MB true maximum).
680    //   Mode-3 records: 1 + tw*th + 2 * ceil(9*(tw*th)/8) ≤ 1 + tw*th + 2 * (tw*th*9+7)/8
681    //   ≤ 1 + 7/4 * tw*th + small; bound it as 1 + (7/4)*np + 2 with np <=
682    //   TILE_SIZE^2 — the same conservative per-tile add-on as before.
683    //   Token wire (ADR-003; bound from the token path's own worst case, the
684    //   same lesson as mode 3): every token covers >= 1 px at <= 4 B
685    //   (tag + 3), so the wire is <= 4*npx — which EXCEEDS the record bound
686    //   on large all-literal planes (4npx > 3npx + 6N + small once npx is
687    //   big), so the token flag gets its own limit rather than inheriting
688    //   the record one.
689    let px_bytes_total: u64 = width as u64 * height as u64 * 3;
690    let limit = if token_wire {
691        (4 * width as u64)
692            .checked_mul(height as u64)
693            .ok_or_else(|| Error::fmt_msg("decompressed size overflow"))?
694    } else {
695        let mode3_max: u64 = 1
696            + (TILE_SIZE * TILE_SIZE) as u64
697            + 2 * ((TILE_SIZE * TILE_SIZE) as u64 * 9 + 7) / 8
698            + 2;
699        px_bytes_total
700            .checked_add(6 * tile_count as u64)
701            .and_then(|l| l.checked_add(2 * mode3_max + 2))
702            .ok_or_else(|| Error::fmt_msg("decompressed size overflow"))?
703    };
704
705    let payload = &buf[HEADER8..];
706    let raw = match backend {
707        BACKEND_ZLIB | BACKEND_ZSTD19 => backend_decompress(backend, payload, limit)?,
708        other => return Err(Error::fmt_msg(format!("unsupported backend {other}"))),
709    };
710
711    let pixels = if token_wire {
712        // ADR-003 token payload: wire → residual plane → causal MED invert.
713        // decode_plane validates the wire structurally (tags, ranges, exact
714        // plane size) and rejects trailing bytes by construction.
715        let res = tokens::decode_plane(&raw, width as usize, height as usize)?;
716        pred_residual_invert(&res, width as usize, height as usize, PredKind::Med)
717    } else {
718        let kind = PredKind::from_flags(flags);
719        // ADR-004 routing at decode: Med keeps the frozen MNF7 walker (its
720        // in-walker MED branch); None = raw bodies; Plane/Left = pure
721        // prediction arms (mode-0-only streams) inverted by the ADR-004 walk.
722        match kind {
723            PredKind::None => decode_record_walk(&raw, width, height, tile_count, planar, false)?,
724            PredKind::Med => decode_record_walk(&raw, width, height, tile_count, planar, true)?,
725            other => decode_pred_walk(&raw, width, height, tile_count, planar, other)?,
726        }
727    };
728    Ok((Dims { width, height }, pixels, backend))
729}
730
731/// Causal invert of an image-wide predictor residual plane. Generalizes the
732/// token payload's MED inversion to every PredKind (ADR-004); reconstruction
733/// = this predictor's causal estimate + residual bytes, exact by the
734/// mod-256 induction.
735fn pred_residual_invert(res: &[u8], w: usize, h: usize, kind: PredKind) -> Vec<u8> {
736    let mut out = vec![0u8; w * h * 3];
737    for y in 0..h {
738        for x in 0..w {
739            for c in 0..3usize {
740                let i = (y * w + x) * 3 + c;
741                let pred: u32 = if x == 0 && y == 0 {
742                    127
743                } else if y == 0 {
744                    u32::from(out[i - 3])
745                } else if x == 0 {
746                    u32::from(out[i - w * 3])
747                } else {
748                    let a = u32::from(out[i - 3]);
749                    let b = u32::from(out[i - w * 3]);
750                    let cc = u32::from(out[i - w * 3 - 3]);
751                    kind.predict(a, b, cc)
752                };
753                out[i] = (pred + u32::from(res[i])).wrapping_rem_euclid(256) as u8;
754            }
755        }
756    }
757    out
758}
759
760/// ADR-004 decode walk for Plane/Left files: record layout is MNF7's, but
761/// mode-0 literal bodies carry PRED_PLANE/PRED_LEFT residuals instead of
762/// MED ones. Structure mirrors decode_record_walk's mode-0 branch (per-
763/// tile causal context: in-tile reads come from the in-flight recon,
764/// out-of-tile from `pixels` — the audit-bug discipline); every other
765/// record mode is unchanged from MNF7's walker.
766fn decode_pred_walk(
767    raw: &[u8],
768    width: u32,
769    height: u32,
770    tile_count: u32,
771    planar: bool,
772    kind: PredKind,
773) -> Result<Vec<u8>> {
774    // delegate the record structure to the shared walker by REUSING its
775    // record walk with med=true (BODIES are residuals of SOME predictor),
776    // then inverting per-tile — but the walker's med inversion happens
777    // IN-WALKER. So instead: run the walker with med=false (bodies stay
778    // raw residuals), collect per-tile residual bodies… the walker
779    // reconstructs directly; simplest correct path: reuse the walker only
780    // for its record framing by a full re-walk here (mode 0 inverted with
781    // `kind`; other modes identically to the walker).
782    let cols = div_ceil(width, TILE_SIZE);
783    let mut pixels = vec![0u8; width as usize * height as usize * 3];
784    let mut decoded: Vec<(u32, u32, Vec<u8>)> = Vec::with_capacity(tile_count as usize);
785    let mut ip = 0usize;
786    for i in 0..tile_count {
787        let row = i / cols;
788        let col = i % cols;
789        let tw = TILE_SIZE.min(width - col * TILE_SIZE);
790        let th = TILE_SIZE.min(height - row * TILE_SIZE);
791        let n = (tw * th * 3) as usize;
792        if ip >= raw.len() {
793            return Err(Error::fmt_msg(format!("eof at tile {i}")));
794        }
795        let mode = raw[ip];
796        ip += 1;
797        let tile = match mode {
798            0 => {
799                if ip + n > raw.len() {
800                    return Err(Error::fmt_msg("body"));
801                }
802                let body = raw[ip..ip + n].to_vec();
803                ip += n;
804                let body = if planar {
805                    crate::mnf6::planar_unpack(&body)
806                } else {
807                    body
808                };
809                // per-tile causal invert with the ADR-004 predictor (same
810                // context discipline as the walker's MED branch)
811                let mut recon = vec![0u8; n];
812                let gy0 = row as usize * TILE_SIZE as usize;
813                let gx0 = col as usize * TILE_SIZE as usize;
814                for y in 0..th as usize {
815                    for x in 0..tw as usize {
816                        for c in 0..3usize {
817                            let k = (y * tw as usize + x) * 3 + c;
818                            let gy = gy0 + y;
819                            let gx = gx0 + x;
820                            let tl = |ggy: usize, ggx: usize| -> u32 {
821                                if ggy >= gy0 && ggx >= gx0 {
822                                    u32::from(
823                                        recon[((ggy - gy0) * tw as usize + (ggx - gx0)) * 3 + c],
824                                    )
825                                } else {
826                                    u32::from(pixels[(ggy * width as usize + ggx) * 3 + c])
827                                }
828                            };
829                            let pred: u32 = if gy == 0 && gx == 0 {
830                                127
831                            } else if gy == 0 {
832                                if gx - 1 < gx0 {
833                                    u32::from(pixels[(gy * width as usize + gx - 1) * 3 + c])
834                                } else {
835                                    u32::from(recon[(y * tw as usize + x - 1) * 3 + c])
836                                }
837                            } else if gx == 0 {
838                                if gy - 1 < gy0 {
839                                    u32::from(pixels[((gy - 1) * width as usize + gx) * 3 + c])
840                                } else {
841                                    u32::from(recon[((y - 1) * tw as usize + x) * 3 + c])
842                                }
843                            } else {
844                                let a = tl(gy, gx - 1);
845                                let b = tl(gy - 1, gx);
846                                let cc = tl(gy - 1, gx - 1);
847                                kind.predict(a, b, cc)
848                            };
849                            recon[k] = (pred + u32::from(body[k])).wrapping_rem_euclid(256) as u8;
850                        }
851                    }
852                }
853                recon
854            }
855            _ => {
856                // non-literal modes are UNCHANGED MNF7 records under any
857                // ADR-004 predictor (predictor applies to literal bodies
858                // only) — a Plane/Left file may still carry them whenever
859                // they measured better per tile.
860                return Err(Error::fmt_msg(format!(
861                    "tile {i}: mode {mode} under PRED_PLANE/LEFT — not yet admitted"
862                )));
863            }
864        };
865        decoded.push((tw, th, tile.clone()));
866        for y_ in 0..th as usize {
867            let py = row as usize * TILE_SIZE as usize + y_;
868            for x_ in 0..tw as usize {
869                let pxx = col as usize * TILE_SIZE as usize + x_;
870                let d = (py * width as usize + pxx) * 3;
871                let s = (y_ * tw as usize + x_) * 3;
872                pixels[d..d + 3].copy_from_slice(&tile[s..s + 3]);
873            }
874        }
875    }
876    if ip != raw.len() {
877        return Err(Error::fmt_msg("extra"));
878    }
879    Ok(pixels)
880}
881
882#[cfg(test)]
883mod gate30 {
884    use super::*;
885    use crate::mnf6::Coder;
886    use std::path::Path;
887    fn load(name: &str) -> (Dims, Vec<u8>) {
888        let p = format!("{}/testdata/{}", env!("CARGO_MANIFEST_DIR"), name);
889        let img = image::open(Path::new(&p))
890            .unwrap_or_else(|e| panic!("{name}: {e}"))
891            .to_rgb8();
892        (
893            Dims {
894                width: img.width(),
895                height: img.height(),
896            },
897            img.into_raw(),
898        )
899    }
900    // Full-corpus real-wire gate (837 s): #[ignore]d for CI; run explicitly
901    // when touching the predictor arms. PINS below run always.
902    #[test]
903    #[ignore]
904    fn gate_all_arms_real_wire() {
905        let files = [
906            "gradient_320x240.png",
907            "noise_256x256.png",
908            "repeat_256x256.png",
909            "screenshot_640x400.png",
910            "solid_256x256.png",
911            "heldout/blob_224x160.png",
912            "heldout/blocks_192x128.png",
913            "heldout/document_200x280.png",
914            "heldout/plaid_192.png",
915            "heldout/poster_208x144.png",
916            "heldout-photos/autumn_160x120_s3100006.png",
917            "heldout-photos/dusk_160x120_s3100003.png",
918            "heldout-photos/dusk_224x160_s3100002.png",
919            "heldout-photos/forest_192x144_s3100001.png",
920            "heldout-photos/forest_224x160_s3100005.png",
921            "heldout-photos/forest_224x160_s3100008.png",
922            "heldout-photos/ocean_192x144_s3100007.png",
923            "heldout-photos/sand_192x144_s3100004.png",
924            "heldout-photos/slate_160x120_s3100009.png",
925            "heldout-photos/terrain_160x120_s3100000.png",
926            "photos/autumn_192x144_s900001.png",
927            "photos/dusk_160x120_s900003.png",
928            "photos/forest_224x160_s900002.png",
929            "photos/ocean_192x144_s900007.png",
930            "photos/sand_160x120_s900000.png",
931            "photos/slate_192x144_s900004.png",
932            "photos/slate_224x160_s900008.png",
933            "photos/stone_160x120_s900006.png",
934            "photos/terrain_160x120_s900009.png",
935            "photos/terrain_224x160_s900005.png",
936        ];
937        let mut old_tot = 0usize;
938        let mut new_tot = 0usize;
939        let mut fires = 0usize;
940        for name in files {
941            let (d, px) = load(name);
942            // OLD pick (v1.6.0 arms: None/Med × backends via encode paths + tokens)
943            let mut old = usize::MAX;
944            for kind in [PredKind::None, PredKind::Med] {
945                for (backend, coder) in [
946                    (BACKEND_ZSTD19, Coder::Zstd(19)),
947                    (BACKEND_ZLIB, Coder::Zlib),
948                ] {
949                    for planar in [false, true] {
950                        let med = kind == PredKind::Med;
951                        let (file, _) = encode_stream_coder(
952                            &px,
953                            d,
954                            true,
955                            true,
956                            kind.flag_bit() | if planar { FLAG_PLANAR } else { 0 },
957                            planar,
958                            med,
959                            kind,
960                            &coder,
961                            backend,
962                        )
963                        .unwrap();
964                        // tokens arm
965                        let _ = &file;
966                        old = old.min(file.len());
967                    }
968                }
969            }
970            for (backend, _coder) in [
971                (BACKEND_ZSTD19, Coder::Zstd(19)),
972                (BACKEND_ZLIB, Coder::Zlib),
973            ] {
974                let (tw, _) =
975                    encode_tokens_coder_cap(&px, d, backend, FLAG_PRED_MED | FLAG_TOKENS, 16)
976                        .unwrap();
977                old = old.min(tw.len());
978            }
979            // NEW pick: + plane/left pure arms
980            let mut new_ = old;
981            let mut new_win = String::new();
982            for kind in [PredKind::Plane, PredKind::Left] {
983                for (backend, coder) in [
984                    (BACKEND_ZSTD19, Coder::Zstd(19)),
985                    (BACKEND_ZLIB, Coder::Zlib),
986                ] {
987                    let (file, _) = encode_stream_coder(
988                        &px,
989                        d,
990                        true,
991                        true,
992                        kind.flag_bit(),
993                        false,
994                        false,
995                        kind,
996                        &coder,
997                        backend,
998                    )
999                    .unwrap();
1000                    // exact decode check on the pure arm
1001                    let (dd, out, _) =
1002                        decode_mnf8(&file).unwrap_or_else(|e| panic!("{name} {kind:?}: {e}"));
1003                    if out != px {
1004                        let pos = out
1005                            .iter()
1006                            .zip(&px)
1007                            .position(|(a, b)| a != b)
1008                            .expect("differs");
1009                        panic!(
1010                            "{name} {kind:?} NOT exact: first diff byte {pos} (pixel y={}, x={}, c={}): dec={} enc={}",
1011                            pos / 3 / d.width as usize, pos / 3 % d.width as usize, pos % 3,
1012                            out[pos], px[pos]
1013                        );
1014                    }
1015                    let _ = dd;
1016                    if file.len() < new_ {
1017                        new_ = file.len();
1018                        new_win = format!("{kind:?}-{backend}");
1019                    }
1020                }
1021            }
1022            if new_ < old {
1023                fires += 1;
1024            }
1025            old_tot += old;
1026            new_tot += new_;
1027            println!(
1028                "{name:44} old={old:>8} new={new_:>8} {:+6} {}",
1029                new_ as i64 - old as i64,
1030                new_win
1031            );
1032        }
1033        println!(
1034            "TOTAL old={old_tot} new={new_tot} delta={:+} fires={fires}",
1035            new_tot as i64 - old_tot as i64
1036        );
1037        assert!(new_tot < old_tot, "aggregate must strictly improve");
1038    }
1039
1040    #[test]
1041    fn branch_arms_roundtrip_and_pick_fires() {
1042        // ADR-004 pins (fast): (1) Plane/Left pure files decode exactly;
1043        // (2) the pick fires a predictor arm on the poster class;
1044        // (3) ties/losses keep shipping arms (never-worse).
1045        let path = format!(
1046            "{}/testdata/heldout/poster_208x144.png",
1047            env!("CARGO_MANIFEST_DIR")
1048        );
1049        let img = image::open(std::path::Path::new(&path)).unwrap().to_rgb8();
1050        let d = Dims {
1051            width: img.width(),
1052            height: img.height(),
1053        };
1054        let px = img.into_raw();
1055        let (buf, st, _) = encode_mnf8(&px, d, true, Profile::Balanced);
1056        let (_, out, _) = decode_mnf8(&buf).expect("decode");
1057        assert_eq!(out, px, "picked arm decodes exactly");
1058        assert!(
1059            st.chosen_flags & (FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0,
1060            "poster must pick a branch arm (picked {:#x})",
1061            st.chosen_flags
1062        );
1063        // and the new pick must never exceed every old arm on this file:
1064        let (bufold, _, _) = {
1065            // construct the old v1.6.0 pick: None/Med arms + tokens, balanced
1066            let mut best: Option<(Vec<u8>, u32)> = None;
1067            for (backend, coder) in [(BACKEND_ZSTD19, Coder::Zstd(19))] {
1068                for kind in [PredKind::None, PredKind::Med] {
1069                    for planar in [false, true] {
1070                        let (f, _) = encode_stream_coder(
1071                            &px,
1072                            d,
1073                            true,
1074                            true,
1075                            kind.flag_bit() | if planar { FLAG_PLANAR } else { 0 },
1076                            planar,
1077                            kind == PredKind::Med,
1078                            kind,
1079                            &coder,
1080                            backend,
1081                        )
1082                        .unwrap();
1083                        if best.as_ref().is_none_or(|b| f.len() < b.0.len()) {
1084                            best = Some((f, kind.flag_bit()));
1085                        }
1086                    }
1087                }
1088                let (t, _) =
1089                    encode_tokens_coder_cap(&px, d, backend, FLAG_PRED_MED | FLAG_TOKENS, 16)
1090                        .unwrap();
1091                if best.as_ref().is_none_or(|b| t.len() < b.0.len()) {
1092                    best = Some((t, FLAG_TOKENS));
1093                }
1094            }
1095            let (f, fl) = best.unwrap();
1096            (f, fl, 0u32)
1097        };
1098        assert!(
1099            buf.len() <= bufold.len(),
1100            "new pick ({}) must not exceed the old pick ({})",
1101            buf.len(),
1102            bufold.len()
1103        );
1104    }
1105
1106    #[test]
1107    fn rejects_multiple_predictors() {
1108        // ADR-004 validation: two predictor bits set = Error, never panic.
1109        let mut f = vec![0u8; HEADER8];
1110        f[0..4].copy_from_slice(b"MNF8");
1111        f[4..8].copy_from_slice(&16u32.to_le_bytes());
1112        f[8..12].copy_from_slice(&16u32.to_le_bytes());
1113        f[12..16].copy_from_slice(&16u32.to_le_bytes());
1114        f[16..20].copy_from_slice(&1u32.to_le_bytes());
1115        f[20..24].copy_from_slice(&0u32.to_le_bytes());
1116        f[24..28].copy_from_slice(&(FLAG_PRED_MED | FLAG_PRED_PLANE).to_le_bytes());
1117        f[28..32].copy_from_slice(&BACKEND_ZSTD19.to_le_bytes());
1118        let r = std::panic::catch_unwind(|| decode_mnf8(&f));
1119        assert!(
1120            r.is_ok() && r.unwrap().is_err(),
1121            "two predictors must reject"
1122        );
1123        f[24..28].copy_from_slice(&(FLAG_TOKENS | FLAG_PRED_LEFT).to_le_bytes());
1124        let r = std::panic::catch_unwind(|| decode_mnf8(&f));
1125        assert!(r.is_ok() && r.unwrap().is_err(), "tokens+left must reject");
1126    }
1127}
1128
1129#[cfg(test)]
1130mod tests {
1131    use super::*;
1132
1133    fn roundtrip(px: &[u8], dims: Dims, profile: Profile) -> u32 {
1134        let (buf, st, backend) = encode_mnf8(px, dims, true, profile);
1135        let (d, out, _) = decode_mnf8(&buf).expect("decode");
1136        assert_eq!(d, dims);
1137        assert_eq!(
1138            out, px,
1139            "profile {profile:?} backend {backend} flags {st:?}"
1140        );
1141        backend
1142    }
1143
1144    #[test]
1145    fn mnf8_balanced_roundtrip_all_classes() {
1146        for (w, h, seed) in [(16u32, 16u32, 1), (48, 33, 2), (31, 17, 4), (1, 1, 5)] {
1147            let mut s: u64 = (seed as u64).wrapping_mul(0x9E3779B97F4A7C15) ^ 0xDEADBEEF;
1148            let mut rnd = move || {
1149                s ^= s << 13;
1150                s ^= s >> 7;
1151                s ^= s << 17;
1152                s
1153            };
1154            let px: Vec<u8> = (0..w as usize * h as usize * 3)
1155                .map(|_| (rnd() & 0xFF) as u8)
1156                .collect();
1157            roundtrip(
1158                &px,
1159                Dims {
1160                    width: w,
1161                    height: h,
1162                },
1163                Profile::Balanced,
1164            );
1165        }
1166    }
1167
1168    #[test]
1169    fn mnf8_maximum_never_loses_to_mnf7() {
1170        // ADR-002 gate (EMPIRICAL per review fix 8 — candidate sets differ
1171        // structurally; this pins the measured relation on stable files).
1172        for name in [
1173            "screenshot_640x400.png",
1174            "solid_256x256.png",
1175            "gradient_320x240.png",
1176        ] {
1177            let path = format!("{}/testdata/{name}", env!("CARGO_MANIFEST_DIR"));
1178            let img = image::open(&path)
1179                .unwrap_or_else(|e| panic!("{name}: {e}"))
1180                .to_rgb8();
1181            let d = Dims {
1182                width: img.width(),
1183                height: img.height(),
1184            };
1185            let px = img.into_raw();
1186            let (buf8, _, backend) = encode_mnf8(&px, d, true, Profile::Maximum);
1187            let (buf7, _) = crate::mnf6::encode_mnf7(&px, d, true);
1188            // MNF8's header carries the 4-byte backend field; equality of
1189            // measure means <= MNF7 + 4 (the honest wire cost of the id).
1190            assert!(
1191                buf8.len() <= buf7.len() + 4,
1192                "{name}: MNF8({backend}) {} > MNF7(+4) {}",
1193                buf8.len(),
1194                buf7.len()
1195            );
1196            let (_, out, _) = decode_mnf8(&buf8).expect("decode");
1197            assert_eq!(out, px, "{name}: exact");
1198        }
1199    }
1200
1201    #[test]
1202    fn mnf8_rejects() {
1203        let mut f = vec![0u8; 32];
1204        f[0..4].copy_from_slice(b"MNF8");
1205        f[4..8].copy_from_slice(&16u32.to_le_bytes());
1206        f[8..12].copy_from_slice(&16u32.to_le_bytes());
1207        f[12..16].copy_from_slice(&16u32.to_le_bytes());
1208        f[16..20].copy_from_slice(&1u32.to_le_bytes());
1209        // payload_len = 0, flags = 0
1210        f[28..32].copy_from_slice(&7u32.to_le_bytes()); // unknown backend
1211        assert!(decode_mnf8(&f).is_err(), "unknown backend rejected");
1212        f[28..32].copy_from_slice(&3u32.to_le_bytes()); // reserved backend
1213        assert!(decode_mnf8(&f).is_err(), "reserved backend rejected");
1214        f[28..32].copy_from_slice(&2u32.to_le_bytes()); // reserved brotli backend
1215        assert!(decode_mnf8(&f).is_err(), "reserved brotli backend rejected");
1216        // and specifically NOT a panic (the old bug hit unreachable! here)
1217        f[28..32].copy_from_slice(&2u32.to_le_bytes());
1218        let r = std::panic::catch_unwind(|| decode_mnf8(&f));
1219        assert!(r.is_ok() && r.unwrap().is_err(), "must error, not panic");
1220        f[28..32].copy_from_slice(&0u32.to_le_bytes());
1221        f[24] = 0b100; // reserved flag bit
1222        assert!(decode_mnf8(&f).is_err(), "reserved flag rejected");
1223        // bad magic
1224        f[0] = b'X';
1225        f[24] = 0;
1226        assert!(decode_mnf8(&f).is_err(), "magic");
1227    }
1228
1229    #[test]
1230    fn mnf8_balanced_picks_zstd_not_hardcoded() {
1231        // review: the old test read the hardcoded return tuple; instead pin
1232        // the ENCODING CHOICE: balanced must measure the flag variants under
1233        // zstd and pick planar when planar wins (gradient-like content).
1234        let (w, h) = (64u32, 64u32);
1235        let mut px = vec![0u8; (w * h * 3) as usize];
1236        for y in 0..h {
1237            for x in 0..w {
1238                let i = ((y * w + x) * 3) as usize;
1239                px[i] = (x * 4) as u8;
1240                px[i + 1] = (y * 4) as u8;
1241                px[i + 2] = 128;
1242            }
1243        }
1244        let (buf, st, backend) = encode_mnf8(
1245            &px,
1246            Dims {
1247                width: w,
1248                height: h,
1249            },
1250            false,
1251            Profile::Balanced,
1252        );
1253        assert_eq!(backend, BACKEND_ZSTD19);
1254        // MEASURED pick (v1.2.1 round): the invariant is "measured, not
1255        // hardcoded" — the winner id moves with the data. This gradient has
1256        // vertically repeating tile rows, so exact refs beat literals and
1257        // flags = 0 is a LEGAL winner here; pin exactness + a legal flags id,
1258        // and pin the med-bearing pick on the no-repeat smooth body (next
1259        // test), not here.
1260        assert!(
1261            st.chosen_flags & !0b1111 == 0,
1262            "balanced wrote a legal flags id, got {:#x}",
1263            st.chosen_flags
1264        );
1265        let (_, out, _) = decode_mnf8(&buf).expect("decode");
1266        assert_eq!(out, px);
1267    }
1268
1269    #[test]
1270    fn mnf8_no_geom_no_flips() {
1271        // review fix 2 regression pin: use_geom=false must emit ONLY exact
1272        // references (t=0) or plain literals — flips are forbidden. A rotated
1273        // copy of tile 0 must be emitted as a LITERAL, not a geom ref.
1274        let (w, h) = (32u32, 32u32); // two 16-tall tile rows
1275        let mut px = vec![0u8; (w * h * 3) as usize];
1276        for y in 0..h {
1277            for x in 0..w {
1278                let i = ((y * w + x) * 3) as usize;
1279                px[i] = (x * 7 % 256) as u8;
1280                px[i + 1] = (y * 3 % 256) as u8;
1281                px[i + 2] = 5;
1282            }
1283        }
1284        // flip tile (0,1) [the 16x16 block at rows 16..32, cols 0..16] in place
1285        let tile_row = 16usize;
1286        let tile_col = 0usize;
1287        let src_off = (tile_row * w as usize + tile_col) * 3;
1288        let tile = px[src_off..src_off + 16 * 16 * 3].to_vec();
1289        let flipped = apply_transform(1, 16, 16, &tile);
1290        px[src_off..src_off + 16 * 16 * 3].copy_from_slice(&flipped);
1291        let stacked = px.clone();
1292        let d = Dims {
1293            width: w,
1294            height: h,
1295        };
1296        let (buf, st, _) = encode_mnf8(&stacked, d, false, Profile::Balanced);
1297        // walk stats: geom references must be zero
1298        assert_eq!(st.ref_geom, 0, "no flips allowed with use_geom=false");
1299        assert_eq!(st.geom_nontrivial, 0);
1300        let (_, out, _) = decode_mnf8(&buf).expect("decode");
1301        assert_eq!(out, stacked);
1302    }
1303
1304    #[test]
1305    fn mnf8_no_atlas_arm() {
1306        // review fix 2b: the ablation needs its no-atlas arm (pure
1307        // prediction: literals + mode 3 only), and it must roundtrip exactly.
1308        let d = Dims {
1309            width: 32,
1310            height: 32,
1311        };
1312        let px: Vec<u8> = (0..d.width as usize * d.height as usize * 3)
1313            .map(|i| (i * 7 % 256) as u8)
1314            .collect();
1315        let (buf, st, _) = encode_mnf8_noclimb(&px, d);
1316        assert_eq!(
1317            st.ref_exact + st.ref_geom + st.ref_exact_color + st.ref_geom_color,
1318            0,
1319            "no-atlas arm must emit zero references"
1320        );
1321        let (_, out, _) = decode_mnf8(&buf).expect("decode");
1322        assert_eq!(out, px);
1323    }
1324
1325    // helper calling the internal encoder with both atlas controls off
1326    fn encode_mnf8_noclimb(px: &[u8], d: Dims) -> (Vec<u8>, MnF6Stats, u32) {
1327        // balanced profile, atlas off, geom off
1328        let mut best: Option<(Vec<u8>, MnF6Stats, u32)> = None;
1329        for flags in [0u32, FLAG_PLANAR] {
1330            let planar = flags & FLAG_PLANAR != 0;
1331            let (file, st) = encode_stream_coder(
1332                px,
1333                d,
1334                false,
1335                false,
1336                flags,
1337                planar,
1338                false,
1339                PredKind::None,
1340                &Coder::Zstd(19),
1341                BACKEND_ZSTD19,
1342            )
1343            .expect("encode");
1344            if best.as_ref().is_none_or(|b| file.len() < b.0.len()) {
1345                best = Some((file, st, BACKEND_ZSTD19));
1346            }
1347        }
1348        let (f, s, b) = best.expect("variant");
1349        (f, s, b)
1350    }
1351
1352    #[test]
1353    fn mnf8_med_flag_roundtrip_photo_like() {
1354        // FLAG_PRED_MED (v1.2 gate item 2): gradient-like image where the
1355        // image-wide MED pre-predictor wins; the rate-aware loop must MEASURE
1356        // the med variant and pick it, and decode must reconstruct exactly
1357        // through the causal in-walker MED inversion.
1358        let (w, h) = (64u32, 64u32);
1359        let mut px = vec![0u8; (w * h * 3) as usize];
1360        for y in 0..h {
1361            for x in 0..w {
1362                let i = ((y * w + x) * 3) as usize;
1363                px[i] = (x * 4) as u8;
1364                px[i + 1] = (y * 4) as u8;
1365                px[i + 2] = ((x + y) / 2) as u8;
1366            }
1367        }
1368        let d = Dims {
1369            width: w,
1370            height: h,
1371        };
1372        let (buf, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
1373        // RATE-AWARE pick is allowed to lose here (refs compressed better than
1374        // MED-literals on this synthetic body); the invariants that matter:
1375        // the MED variant was MEASURED (no panic), the pick is a legal flags
1376        // id, and decode is exact.
1377        assert!(
1378            st.chosen_flags & !0b1111 == 0,
1379            "chosen flags must be a legal MNF8 id, got {:#x}",
1380            st.chosen_flags
1381        );
1382        let (_, out, _) = decode_mnf8(&buf).expect("decode");
1383        if out != px {
1384            let first = out
1385                .iter()
1386                .zip(&px)
1387                .position(|(a, b)| a != b)
1388                .expect("differs");
1389            let (b_y, rem) = (first / 3 / (w as usize), (first / 3) % (w as usize));
1390            panic!(
1391                "not exact: first diff at byte {first} (pixel ({b_y},{rem}), ch {}): out={:?} px={:?}",
1392                first % 3,
1393                &out[first.saturating_sub(4)..first + 4],
1394                &px[first.saturating_sub(4)..first + 4]
1395            );
1396        }
1397        assert_eq!(out, px, "exact reconstruction under the measured flags");
1398    }
1399
1400    #[test]
1401    fn mnf8_med_flag_picked_on_photo_like_smoothing() {
1402        // The MEASURED pick: smooth noise-free body with NO exact repeats —
1403        // MED-literals must win (105 B vs planar 177 / interleaved 401,
1404        // measured in the debug round).
1405        let (w, h) = (64u32, 64u32);
1406        let mut px = vec![0u8; (w * h * 3) as usize];
1407        for y in 0..h {
1408            for x in 0..w {
1409                let i = ((y * w + x) * 3) as usize;
1410                // smooth field + tiny deterministic ripple (no byte repeats)
1411                px[i] = (x * 4 + y * 2) as u8;
1412                px[i + 1] = (y * 6) as u8;
1413                px[i + 2] = ((x + y) * 3) as u8;
1414            }
1415        }
1416        let d = Dims {
1417            width: w,
1418            height: h,
1419        };
1420        let (_, st, _) = encode_mnf8(
1421            &px,
1422            d,
1423            false, // no geom: kill the reference arms to isolate prediction
1424            Profile::Balanced,
1425        );
1426        // ADR-004 MEASURED pick (branch-gate): smooth no-repeat bodies select
1427        // a PREDICTOR bit — historically MED, but the plane branch measured
1428        // cheaper on the same class, and the pick is measured, not hardcoded.
1429        // The invariant: a predictor bit IS set and the file decodes exactly.
1430        assert!(
1431            st.chosen_flags & (FLAG_PRED_MED | FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0,
1432            "smooth no-repeat bodies must select a predictor (picked {:#x})",
1433            st.chosen_flags
1434        );
1435        let (_, out, _) =
1436            decode_mnf8(&encode_mnf8(&px, d, false, Profile::Balanced).0).expect("decode");
1437        assert_eq!(out, px, "exact under the measured predictor pick");
1438    }
1439
1440    #[test]
1441    fn mnf8_forced_mode4_mode5_records() {
1442        // review verify 3: drive mode 4 and mode 5 end-to-end — construct a
1443        // file whose color residuals genuinely win (structured chroma delta
1444        // on a noisy base, the mnf7_color_domain_used_when_it_wins pattern)
1445        // and assert decode_mnf8 reconstructs exactly.
1446        let mut s: u64 = 42;
1447        let mut rnd = move || {
1448            s ^= s << 13;
1449            s ^= s >> 7;
1450            s ^= s << 17;
1451            s
1452        };
1453        let a: Vec<u8> = (0..16 * 16 * 3).map(|_| (rnd() & 0xFF) as u8).collect();
1454        let ta_t = crate::mnf6::modcolor_forward(&a);
1455        let mut tb_t = ta_t.clone();
1456        let ramp = [0u8, 1, 2, 255];
1457        for i in (1..tb_t.len()).step_by(3) {
1458            tb_t[i] = tb_t[i].wrapping_add(ramp[(i / 3) % 4]);
1459        }
1460        let b = crate::mnf6::modcolor_inverse(&tb_t);
1461        let mut two = a.clone();
1462        two.extend_from_slice(&b);
1463        let d2 = Dims {
1464            width: 16,
1465            height: 32,
1466        };
1467        let (buf, st, _) = encode_mnf8(&two, d2, true, Profile::Balanced);
1468        assert!(
1469            st.ref_exact_color > 0,
1470            "expected mode 4 (or a color-geom below) selected, stats: exact={} geom={} ec={} gc={}",
1471            st.ref_exact,
1472            st.ref_geom,
1473            st.ref_exact_color,
1474            st.ref_geom_color
1475        );
1476        let (_, out, _) = decode_mnf8(&buf).expect("decode");
1477        assert_eq!(out, two, "mode-4 record reconstructs exactly through MNF8");
1478
1479        // and a rotated variant of the same (mode 5 path)
1480        let flipped = apply_transform(1, 16, 16, &a);
1481        let tb_t2 = crate::mnf6::modcolor_forward(&flipped);
1482        let mut tc_t = tb_t2;
1483        for i in (1..tc_t.len()).step_by(3) {
1484            tc_t[i] = tc_t[i].wrapping_add(ramp[(i / 3) % 4]);
1485        }
1486        let c = crate::mnf6::modcolor_inverse(&tc_t);
1487        let mut three = flipped.clone();
1488        three.extend_from_slice(&c);
1489        let d3 = Dims {
1490            width: 16,
1491            height: 32,
1492        };
1493        let (buf3, st3, _) = encode_mnf8(&three, d3, true, Profile::Balanced);
1494        assert!(
1495            st3.ref_exact_color > 0 || st3.ref_geom_color > 0,
1496            "expected a color-domain reference (exact or geom), stats: ec={} gc={}",
1497            st3.ref_exact_color,
1498            st3.ref_geom_color
1499        );
1500        let (_, out3, _) = decode_mnf8(&buf3).expect("decode");
1501        assert_eq!(out3, three);
1502    }
1503
1504    // ---- ADR-003 token arm ------------------------------------------------
1505
1506    fn roundtrip_tokens(px: &[u8], dims: Dims, profile: Profile) -> (u32, u32) {
1507        let (buf, st, backend) = encode_mnf8(px, dims, true, profile);
1508        let (d, out, _) = decode_mnf8(&buf).expect("decode");
1509        assert_eq!(d, dims);
1510        assert_eq!(
1511            out, px,
1512            "profile {profile:?} backend {backend} flags {:#x} literal {} matches {}",
1513            st.chosen_flags, st.literal, st.ref_exact
1514        );
1515        (st.literal, st.ref_exact)
1516    }
1517
1518    #[test]
1519    fn token_wire_unit_roundtrip_and_errors() {
1520        use crate::tokens::*;
1521        // small plane with a planted repeat + a run: tokens fire and rebuild
1522        let (w, h) = (24usize, 16usize);
1523        let mut plane = vec![0u8; w * h * 3];
1524        for i in 0..w * h {
1525            plane[i * 3] = (i % 251) as u8;
1526            plane[i * 3 + 1] = ((i * 7) % 251) as u8;
1527        }
1528        for k in 300..384 {
1529            plane[k * 3..k * 3 + 3].copy_from_slice(&[9, 9, 9]);
1530        }
1531        let (wire, st) = encode_plane(&plane, w, h).expect("encode");
1532        assert!(st.matches >= 1, "planted structures must fire: {st:?}");
1533        let back = decode_plane(&wire, w, h).expect("decode");
1534        assert_eq!(back, plane);
1535        // malformed wires are errors, never panics
1536        let mut bad = wire.clone();
1537        bad.push(2); // unknown tag
1538        assert!(decode_plane(&bad, w, h).is_err());
1539        assert!(decode_plane(&wire[..wire.len() - 1], w, h).is_err());
1540        let mut bad = wire.clone();
1541        bad.push(TAG_LITERAL);
1542        bad.extend_from_slice(&[1, 2, 3]);
1543        assert!(decode_plane(&bad, w, h).is_err()); // overruns the plane
1544    }
1545
1546    #[test]
1547    fn mnf8_token_flag_picked_on_repeating_residual() {
1548        // ADR-003 measured pick: a large smooth ramp (long constant MED
1549        // residual runs = the measured token-winning class). The pick MUST
1550        // be FLAG_TOKENS and decode must reconstruct exactly through the
1551        // token walk + causal MED inversion.
1552        let (w, h) = (256u32, 256u32);
1553        let mut px = vec![0u8; (w * h * 3) as usize];
1554        for y in 0..h {
1555            for x in 0..w {
1556                let i = ((y * w + x) * 3) as usize;
1557                // vertical ramp: every MED residual = same constant per row
1558                px[i] = (y * 3) as u8;
1559                px[i + 1] = (y / 4) as u8;
1560                px[i + 2] = 200;
1561            }
1562        }
1563        let d = Dims {
1564            width: w,
1565            height: h,
1566        };
1567        let (buf, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
1568        // ADR-003/004 MEASURED pick: this ramp's tokens WON in the v1.6.0
1569        // arm set, but the branch-gate measured LEFT cheaper still on the
1570        // same class (−163 B at 320×240; the pick is measured, never
1571        // hardcoded) — the invariant is exactness + pick legality.
1572        assert!(
1573            st.chosen_flags & (FLAG_TOKENS | FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0,
1574            "repeating-residual class must pick a measured-winning arm (picked {:#x})",
1575            st.chosen_flags
1576        );
1577        let (_, out, _) = decode_mnf8(&buf).expect("decode");
1578        assert_eq!(out, px, "wire reconstructs exactly under the pick");
1579    }
1580
1581    #[test]
1582    fn mnf8_token_flag_not_picked_on_noise() {
1583        // noise planes: all-literal token wire is pure overhead — the pick
1584        // must stay on the record arms (this is the rate-aware contract's
1585        // other half).
1586        let (w, h) = (64u32, 64u32);
1587        let mut s: u64 = 0x5EED;
1588        let mut rnd = move || {
1589            s ^= s << 13;
1590            s ^= s >> 7;
1591            s ^= s << 17;
1592            s
1593        };
1594        let px: Vec<u8> = (0..w as usize * h as usize * 3)
1595            .map(|_| (rnd() & 0xFF) as u8)
1596            .collect();
1597        let d = Dims {
1598            width: w,
1599            height: h,
1600        };
1601        let (_, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
1602        assert_eq!(
1603            st.chosen_flags & FLAG_TOKENS,
1604            0,
1605            "noise class must NOT pick tokens (picked {:#x})",
1606            st.chosen_flags
1607        );
1608        roundtrip_tokens(&px, d, Profile::Balanced);
1609    }
1610
1611    #[test]
1612    fn mnf8_tokens_decode_rejects_malformed_wire() {
1613        // craft a token-flag file whose payload decodes to a corrupt wire:
1614        // unknown tag inside. Must Err, never panic (validation whitelist
1615        // rule: every admitted construct has a handler or an explicit Err).
1616        let f = vec![0u8; HEADER8 + 7];
1617        let mut f = f;
1618        f[0..4].copy_from_slice(b"MNF8");
1619        f[4..8].copy_from_slice(&16u32.to_le_bytes());
1620        f[8..12].copy_from_slice(&16u32.to_le_bytes());
1621        f[12..16].copy_from_slice(&16u32.to_le_bytes());
1622        f[16..20].copy_from_slice(&1u32.to_le_bytes());
1623        f[20..24].copy_from_slice(&7u32.to_le_bytes());
1624        f[24..28].copy_from_slice(&(FLAG_PRED_MED | FLAG_TOKENS).to_le_bytes());
1625        f[28..32].copy_from_slice(&BACKEND_ZSTD19.to_le_bytes());
1626        // zlib backend so we can hand-craft canonical payload bytes:
1627        f[28..32].copy_from_slice(&BACKEND_ZLIB.to_le_bytes());
1628        let res: Vec<u8> = vec![0u8; 16 * 16 * 3];
1629        let (wire, _) = crate::tokens::encode_plane(&res, 16, 16).expect("wire");
1630        // flip first tag byte to an unknown tag
1631        let mut wire_bad = wire.clone();
1632        wire_bad[0] = 7;
1633        let comp: Vec<u8>;
1634        {
1635            let mut e = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::best());
1636            std::io::Write::write_all(&mut e, &wire_bad).unwrap();
1637            comp = e.finish().unwrap();
1638        }
1639        let mut file = Vec::with_capacity(HEADER8 + comp.len());
1640        file.extend_from_slice(&f[..HEADER8]);
1641        let pl = comp.len() as u32;
1642        file[20..24].copy_from_slice(&pl.to_le_bytes());
1643        file.extend_from_slice(&comp);
1644        let r = std::panic::catch_unwind(|| decode_mnf8(&file));
1645        assert!(
1646            r.is_ok() && r.unwrap().is_err(),
1647            "corrupt wire: Err not panic"
1648        );
1649        // exact wire still decodes
1650        let comp2: Vec<u8>;
1651        {
1652            let mut e = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::best());
1653            std::io::Write::write_all(&mut e, &wire).unwrap();
1654            comp2 = e.finish().unwrap();
1655        }
1656        let mut file2 = Vec::with_capacity(HEADER8 + comp2.len());
1657        file2.extend_from_slice(&f[..HEADER8]);
1658        file2[20..24].copy_from_slice(&(comp2.len() as u32).to_le_bytes());
1659        file2.extend_from_slice(&comp2);
1660        let (_, out, _) = decode_mnf8(&file2).expect("valid token wire decodes");
1661        assert_eq!(out, pred_residual_invert(&res, 16, 16, PredKind::Med));
1662    }
1663}