Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-core/src/golden.rs

//! **golden** — the shared GOLDEN-IMAGE GUARD for facett's screenshot proofs.
//!
//! A screenshot test that renders a PNG, **overwrites the committed golden** and
//! then asserts only "it is bigger than 5 KB" is not a test. It is a write-only
//! file: it can never go red, and it silently absorbs every visual regression that
//! lands on top of it. facett shipped exactly that shape until 2026-07-23 — and it
//! hid a deleted `facett-grid` toolbar (45 % of the flagship golden's pixels) for
//! three weeks.
//!
//! This module is the guard those tests were missing. [`assert_golden`] **compares**
//! the freshly rendered image against the committed PNG and does not write it:
//!
//! | situation                    | result                                                    |
//! |------------------------------|-----------------------------------------------------------|
//! | identical render             | **pass**, golden untouched (a clean `git status`)          |
//! | render differs               | **FAIL** — pixel count + %, `actual`/`diff` PNGs in `target/` |
//! | different size               | **FAIL** — expected vs actual dimensions                   |
//! | **golden missing**           | **FAIL** — never a silent pass on nothing                  |
//! | `UPDATE_SNAPSHOTS=1`         | golden (re-)written, test passes, "BLESSED" on stderr      |
//!
//! The missing-golden row is the important one. The classic way to build a guard
//! that guards nothing is to compare snapshots but treat "no snapshot yet" as
//! success (or to assert on the *error text* of the missing-file case): the suite is
//! then structurally green over an empty directory. A missing golden here is a
//! failure, full stop — the only way to create one is the explicit opt-in below.
//!
//! ## Exact match, no tolerance
//!
//! The comparison is **byte-exact per channel**. facett's `egui_kittest` + wgpu
//! renders are deterministic on a fixed host: two consecutive runs of the same
//! source produce byte-identical PNGs (verified before this guard was trusted).
//! No anti-aliasing/font-jitter tolerance is configured, because none is needed and
//! a tolerance is exactly the knob that lets a real regression through. If a future
//! host (a different GPU / driver) does jitter, add the smallest tolerance that
//! makes *that* host stable and say so at the call site — do not widen it globally.
//!
//! ## Blessing
//!
//! Opt-in, never the default:
//!
//! ```text
//! UPDATE_SNAPSHOTS=1 cargo test -p facett-grid --test snapshot
//! ```
//!
//! Accepted values: `1` / `true` / `yes` / `on` (anything else is off; an
//! unrecognised value panics rather than silently testing). Blessing a golden is a
//! reviewable act: the PNG is git-lfs, so the diff shows up as a new LFS object and
//! the commit message has to say *why* the pixels moved.

use std::path::{Path, PathBuf};

use image::RgbaImage;

/// Where the failure artifacts (`*.actual.png`, `*.diff.png`) are written, so a
/// human can look at them without dirtying the git tree. Under `target/` on
/// purpose: the golden directories are git-lfs and have been a recurring source of
/// accidental staging noise.
fn artifact_dir() -> PathBuf {
    if let Ok(dir) = std::env::var("CARGO_TARGET_DIR") {
        return PathBuf::from(dir).join("snapshot-failures");
    }
    // `current_exe` is `<target>/<profile>/deps/<test-bin>`; walk up to `target`.
    if let Ok(exe) = std::env::current_exe() {
        for a in exe.ancestors() {
            if a.file_name().is_some_and(|n| n == "target") {
                return a.join("snapshot-failures");
            }
        }
    }
    // Fallback: this crate sits one level under the workspace root.
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap_or_else(|| Path::new("."))
        .join("target")
        .join("snapshot-failures")
}

/// `<target>/snapshot-failures/<name>.<kind>.png`, with any `<name>` subdirectory
/// (the palette gallery names its shots `palette/facett_demo_*`) created.
fn artifact_path(name: &str, kind: &str) -> PathBuf {
    let p = artifact_dir().join(format!("{name}.{kind}.png"));
    if let Some(parent) = p.parent() {
        std::fs::create_dir_all(parent).ok();
    }
    p
}

/// Is the blessing opt-in switched on? Reads `UPDATE_SNAPSHOTS`.
///
/// # Panics
/// On an unrecognised value — a typo (`UPDATE_SNAPSHOTS=yess`) must not quietly
/// leave the guard armed *or* quietly disarm it.
pub fn blessing() -> bool {
    match std::env::var("UPDATE_SNAPSHOTS") {
        Err(_) => false,
        Ok(v) => match v.trim().to_ascii_lowercase().as_str() {
            "" | "0" | "false" | "no" | "off" => false,
            "1" | "true" | "yes" | "on" => true,
            other => panic!(
                "UPDATE_SNAPSHOTS={other:?} is not understood — use 1/true/yes/on to bless, or unset it to test"
            ),
        },
    }
}

/// The outcome of one golden comparison.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Verdict {
    /// Every pixel matched.
    Match,
    /// The golden was (re-)written because `UPDATE_SNAPSHOTS` is set.
    Blessed,
}

/// A structured difference between two same-sized images.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Diff {
    /// Number of pixels differing in any channel.
    pub pixels: u64,
    /// Total pixels compared.
    pub total: u64,
    /// The first differing pixel in raster order, `(x, y)`.
    pub first: Option<(u32, u32)>,
    /// Bounding box of all differing pixels, `(x0, y0, x1, y1)` inclusive.
    pub bbox: Option<(u32, u32, u32, u32)>,
    /// The largest absolute per-channel delta seen.
    pub max_channel_delta: u8,
}

impl Diff {
    /// Differing pixels as a percentage of the frame.
    pub fn percent(&self) -> f64 {
        if self.total == 0 { 0.0 } else { self.pixels as f64 * 100.0 / self.total as f64 }
    }
}

/// Compare two same-sized RGBA images pixel-exactly. `None` = identical.
///
/// Pure (no I/O), so it is unit-testable and reusable by any facett render proof
/// that already holds both buffers.
pub fn compare(golden: &RgbaImage, actual: &RgbaImage) -> Option<Diff> {
    debug_assert_eq!(golden.dimensions(), actual.dimensions());
    let (w, h) = golden.dimensions();
    let (g, a) = (golden.as_raw(), actual.as_raw());
    let mut d = Diff {
        pixels: 0,
        total: u64::from(w) * u64::from(h),
        first: None,
        bbox: None,
        max_channel_delta: 0,
    };
    for i in 0..(g.len() / 4) {
        let (gp, ap) = (&g[i * 4..i * 4 + 4], &a[i * 4..i * 4 + 4]);
        if gp == ap {
            continue;
        }
        let (x, y) = ((i as u32) % w, (i as u32) / w);
        d.pixels += 1;
        if d.first.is_none() {
            d.first = Some((x, y));
        }
        d.bbox = Some(match d.bbox {
            None => (x, y, x, y),
            Some((x0, y0, x1, y1)) => (x0.min(x), y0.min(y), x1.max(x), y1.max(y)),
        });
        for c in 0..4 {
            d.max_channel_delta = d.max_channel_delta.max(gp[c].abs_diff(ap[c]));
        }
    }
    (d.pixels > 0).then_some(d)
}

/// The floor a **"the picture changed"** proof has to clear.
///
/// [`compare`] answers *did anything differ*. That is the right question for a golden,
/// and the WRONG one for a before/after interaction proof — which is why every robot
/// suite that copied the reference harness wrote `assert_ne!(png_bytes_a, png_bytes_b)`
/// and got a test that cannot fail for the reason it claims.
///
/// Three separate suites were measured passing that assertion on things the click never
/// caused: **a window title**, **an ephemeral port number rendered as text**, and **a
/// text legend**. Each moves a handful of pixels in a thin strip, and "the bytes differ"
/// cannot tell that from a pane redrawing.
///
/// So a real proof needs two floors, and `percent` alone is not enough:
///
/// * **`percent`** — the share of the frame that must differ. Rules out a port number,
///   a frame counter, a few characters of text.
/// * **`extent`** — the changed region's bounding box must span at least this share of
///   the frame in **both** axes. Rules out a title bar or a status line: wide, but a
///   thin strip. A pane that actually redrew has extent in both directions.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MinChange {
    /// Minimum share of differing pixels, in percent of the whole frame.
    pub percent: f64,
    /// Minimum bbox width AND height, each as a share (0.0–1.0) of the frame.
    pub extent: f64,
}

impl MinChange {
    /// A pane or panel visibly redrew — the usual "did my click reach the pixels" case.
    ///
    /// 0.5 % of a 1100×760 frame is ~4 200 pixels, and the box must span 10 % of the
    /// frame each way. A one-line text change cannot reach either number.
    pub const PANE: Self = Self { percent: 0.5, extent: 0.10 };

    /// A smaller widget changed (a toggle, a single row). Still far above text noise.
    pub const WIDGET: Self = Self { percent: 0.05, extent: 0.02 };
}

/// Assert that `after` differs from `before` **substantially enough to be the change
/// you meant**, returning the [`Diff`] on success and a named reason on failure.
///
/// This is the ONE writer for before/after interaction proofs. Use it instead of
/// comparing encoded PNG bytes: those carry metadata and encoder state, so
/// `assert_ne!` on them can pass while the picture is identical, and passes trivially on
/// any incidental text.
///
/// # Errors
/// Names which floor failed and by how much, so a failure is actionable without
/// re-running under a debugger:
/// * the two frames are identical;
/// * differing pixels below `min.percent`;
/// * the changed region is a thin strip (below `min.extent` in either axis);
/// * the frames are different sizes (a resize is not a proof that a click landed).
pub fn moved(before: &RgbaImage, after: &RgbaImage, min: MinChange) -> Result<Diff, String> {
    if before.dimensions() != after.dimensions() {
        return Err(format!(
            "frames differ in SIZE ({:?} vs {:?}) — a resize is not proof that the \
             interaction reached the pixels",
            before.dimensions(),
            after.dimensions()
        ));
    }
    let (w, h) = before.dimensions();
    let Some(d) = compare(before, after) else {
        return Err(
            "the two frames are byte-identical — the interaction never reached the pixels"
                .to_owned(),
        );
    };
    if d.percent() < min.percent {
        return Err(format!(
            "only {:.4} % of the frame changed ({} of {} px), below the {:.4} % floor. \
             That is the size of a text label or a port number, not a pane redrawing",
            d.percent(),
            d.pixels,
            d.total,
            min.percent
        ));
    }
    // `compare` guarantees a bbox whenever pixels > 0.
    let (x0, y0, x1, y1) = d.bbox.expect("compare sets bbox when pixels differ");
    let (bw, bh) = (x1 - x0 + 1, y1 - y0 + 1);
    let (fw, fh) = (f64::from(bw) / f64::from(w), f64::from(bh) / f64::from(h));
    if fw < min.extent || fh < min.extent {
        return Err(format!(
            "the change is a THIN STRIP: bbox {bw}×{bh} px at ({x0},{y0}) is {:.3}×{:.3} \
             of the frame, below the {:.3} floor in {}. A window title, a status line or \
             a legend looks exactly like this",
            fw,
            fh,
            min.extent,
            if fw < min.extent { "width" } else { "height" }
        ));
    }
    Ok(d)
}

/// **The control every before/after pixel proof has to run FIRST**: two renders of
/// the *same* state must be byte-identical.
///
/// [`moved`] answers "did the picture change enough". On its own that is still half a
/// proof, because it cannot distinguish *the interaction* from *the host*. If a render
/// is not reproducible — driver dithering, an unpinned clock, a frame counter, an
/// animation still easing — then a `moved` pass may be measuring jitter, and no floor
/// chosen without measuring that jitter is a considered number.
///
/// So the shape is: render, render again with **no input in between**, assert `stable`
/// (the measured NOISE FLOOR, which must be 0 px), *then* interact and assert
/// [`moved`]. A `moved` figure is only meaningful next to the noise floor it clears.
///
/// STINKY-19 R0 named this control as one of four legs of the fix pattern; the other
/// three (raw RGBA, a percent floor, an extent floor) live in [`compare`] and [`moved`].
/// It is here rather than in each suite so the fourth leg is not re-derived — badly —
/// a fifth time.
///
/// # Errors
/// Names the finding, the amount of jitter and where it is, so the failure says
/// "this host is not deterministic" instead of "images differ".
pub fn stable(first: &RgbaImage, second: &RgbaImage) -> Result<(), String> {
    if first.dimensions() != second.dimensions() {
        return Err(format!(
            "two renders of the SAME state came out at DIFFERENT SIZES ({:?} vs {:?}) — \
             the harness is not reproducible, so no before/after pixel proof on it means \
             anything",
            first.dimensions(),
            second.dimensions()
        ));
    }
    match compare(first, second) {
        None => Ok(()),
        Some(d) => {
            let (x0, y0, x1, y1) = d.bbox.expect("compare sets bbox when pixels differ");
            Err(format!(
                "NOT DETERMINISTIC: two renders of the SAME state differ by {} of {} px \
                 ({:.4} % of the frame), max channel delta {}, region x {x0}..={x1} \
                 y {y0}..={y1}. The noise floor is not zero, so a before/after `moved` \
                 pass on this host may be measuring jitter rather than the interaction. \
                 Pin the clock / the animation / the incidental text before trusting any \
                 pixel proof here",
                d.pixels,
                d.total,
                d.percent(),
                d.max_channel_delta
            ))
        }
    }
}

/// Paint a human-readable diff image: the golden dimmed to grayscale, with every
/// differing pixel stamped magenta. Reads at a glance as "here is what moved".
pub fn diff_image(golden: &RgbaImage, actual: &RgbaImage) -> RgbaImage {
    let (w, h) = golden.dimensions();
    let mut out = RgbaImage::new(w, h);
    let (g, a) = (golden.as_raw(), actual.as_raw());
    for (i, px) in out.pixels_mut().enumerate() {
        let (gp, ap) = (&g[i * 4..i * 4 + 4], &a[i * 4..i * 4 + 4]);
        *px = if gp == ap {
            let lum = (u16::from(gp[0]) * 30 + u16::from(gp[1]) * 59 + u16::from(gp[2]) * 11) / 100;
            let l = (lum / 3) as u8; // dimmed, so the stamps pop
            image::Rgba([l, l, l, 255])
        } else {
            image::Rgba([255, 0, 190, 255])
        };
    }
    out
}

/// **The guard.** Compare `actual` against the committed golden PNG at `golden_path`.
///
/// `name` is only used to label the failure artifacts and the panic message.
///
/// See the [module docs](self) for the full behaviour table. Returns the
/// [`Verdict`] on success; **panics** on any mismatch, size change, or missing
/// golden.
///
/// # Panics
/// - the golden file does not exist, or cannot be decoded (a bare git-lfs pointer
///   is the usual cause — run `git lfs pull`)
/// - the rendered size differs from the golden's
/// - any pixel differs
#[track_caller]
pub fn assert_golden(actual: &RgbaImage, golden_path: &Path, name: &str) -> Verdict {
    assert_golden_within(actual, golden_path, name, 0)
}

/// [`assert_golden`] for a render that measurably **jitters by driver rounding** on a
/// host it was not blessed on: a diff passes when NO channel of ANY pixel moved by more
/// than `max_channel_delta`. `0` is [`assert_golden`] exactly.
///
/// This is the module rule "add the smallest tolerance that makes *that* host stable and
/// say so at the call site", not a global widening: every call site passes its own
/// number with the measurement that justifies it. A tolerance of a couple of levels
/// cannot absorb a structural change — a removed toolbar, a missing label, a wrong fill
/// moves channels by tens to hundreds — and the diff count and region are still printed
/// when it passes, so an accumulating drift stays visible.
///
/// # Panics
/// As [`assert_golden`], except that a diff within `max_channel_delta` passes.
#[track_caller]
pub fn assert_golden_within(actual: &RgbaImage, golden_path: &Path, name: &str, max_channel_delta: u8) -> Verdict {
    let (aw, ah) = actual.dimensions();

    if blessing() {
        if let Some(parent) = golden_path.parent() {
            std::fs::create_dir_all(parent).ok();
        }
        actual.save(golden_path).unwrap_or_else(|e| panic!("[{name}] writing golden {}: {e}", golden_path.display()));
        eprintln!("BLESSED {name} → {} ({aw}×{ah})", golden_path.display());
        return Verdict::Blessed;
    }

    // Dump the render next to the failure evidence and return the panic message.
    let dump = |why: String| -> ! {
        let actual_path = artifact_path(name, "actual");
        let saved = actual.save(&actual_path).is_ok();
        let where_ = if saved {
            format!("\n  actual render → {}", actual_path.display())
        } else {
            String::new()
        };
        panic!(
            "\nSNAPSHOT GUARD FAILED [{name}]\n  {why}{where_}\n  \
             bless with: UPDATE_SNAPSHOTS=1 cargo test …  (only when the change is intended)\n"
        );
    };

    if !golden_path.exists() {
        dump(format!(
            "MISSING golden {} — a snapshot test with no committed golden is not a passing test.",
            golden_path.display()
        ));
    }

    let golden = match image::open(golden_path) {
        Ok(img) => img.to_rgba8(),
        Err(e) => dump(format!(
            "could not decode golden {}: {e}\n  (a git-lfs pointer instead of a PNG? try `git lfs pull`)",
            golden_path.display()
        )),
    };

    let (gw, gh) = golden.dimensions();
    if (gw, gh) != (aw, ah) {
        dump(format!("size changed: golden is {gw}×{gh}, render is {aw}×{ah}"));
    }

    if let Some(d) = compare(&golden, actual).filter(|d| {
        if max_channel_delta > 0 && d.max_channel_delta <= max_channel_delta {
            eprintln!(
                "golden OK {name} WITHIN TOLERANCE: {} of {} pixels ({:.2}%) differ by at most {} (allowed {max_channel_delta}) ← {}",
                d.pixels,
                d.total,
                d.percent(),
                d.max_channel_delta,
                golden_path.display()
            );
            false
        } else {
            true
        }
    }) {
        // Write the diff image beside the actual render before panicking.
        let diff_path = artifact_path(name, "diff");
        let diff_note = if diff_image(&golden, actual).save(&diff_path).is_ok() {
            format!("\n  diff image    → {} (magenta = changed)", diff_path.display())
        } else {
            String::new()
        };
        let (x0, y0, x1, y1) = d.bbox.unwrap_or((0, 0, 0, 0));
        dump(format!(
            "{} of {} pixels differ ({:.2}%), max channel delta {}\n  \
             first differing pixel at ({}, {}); changed region x {x0}..={x1}, y {y0}..={y1}\n  \
             golden        → {}{diff_note}",
            d.pixels,
            d.total,
            d.percent(),
            d.max_channel_delta,
            d.first.map_or(0, |p| p.0),
            d.first.map_or(0, |p| p.1),
            golden_path.display(),
        ));
    }

    let how = if max_channel_delta == 0 { "exact".to_string() } else { format!("within {max_channel_delta}") };
    eprintln!("golden OK {name} ({aw}×{ah}, {how}) ← {}", golden_path.display());
    Verdict::Match
}

#[cfg(test)]
mod tests {
    use super::*;

    fn img(w: u32, h: u32, f: impl Fn(u32, u32) -> [u8; 4]) -> RgbaImage {
        RgbaImage::from_fn(w, h, |x, y| image::Rgba(f(x, y)))
    }

    /// The tolerance passes rounding jitter up to its bound and nothing past it: a
    /// delta-2 speckle over a quarter of the frame passes at 2, fails at 1 and at 0
    /// (plain `assert_golden`), and a single pixel off by 3 fails at 2.
    #[test]
    fn a_channel_tolerance_forgives_rounding_and_nothing_bigger() {
        if blessing() {
            return; // a blessing run rewrites goldens; this test writes its own
        }
        let dir = std::env::temp_dir().join(format!("facett-golden-tol-{}", std::process::id()));
        std::fs::create_dir_all(&dir).unwrap();
        let path = dir.join("g.png");
        let base = img(16, 16, |x, y| [(x * 9) as u8, (y * 9) as u8, 100, 255]);
        base.save(&path).unwrap();
        let jitter = img(16, 16, |x, y| {
            let d = u8::from(x % 2 == 0 && y % 2 == 0) * 2;
            [(x * 9) as u8 + d, (y * 9) as u8, 100, 255]
        });
        assert_eq!(assert_golden_within(&jitter, &path, "tol-2", 2), Verdict::Match);
        let one = std::panic::catch_unwind(|| assert_golden_within(&jitter, &path, "tol-1", 1));
        assert!(one.is_err(), "a delta-2 diff must fail a tolerance of 1");
        let exact = std::panic::catch_unwind(|| assert_golden(&jitter, &path, "tol-0"));
        assert!(exact.is_err(), "and plain assert_golden stays exact");
        let mut spike = base.clone();
        spike.get_pixel_mut(7, 7).0[2] = 103;
        let three = std::panic::catch_unwind(|| assert_golden_within(&spike, &path, "tol-2-spike", 2));
        assert!(three.is_err(), "one pixel off by 3 must fail a tolerance of 2");
        std::fs::remove_dir_all(&dir).ok();
    }

    /// **`moved` must REJECT the three shapes that were measured passing
    /// `assert_ne!(png_a, png_b)` in three independent robot suites.**
    ///
    /// This is the point of the whole predicate, so it is asserted directly rather than
    /// left to a comment. Each case below is a real observed false pass.
    #[test]
    fn moved_rejects_the_incidental_changes_that_fooled_assert_ne() {
        let base = img(1100, 760, |_, _| [30, 30, 40, 255]);

        // 1. A WINDOW TITLE / status line: wide, but ~18 px tall.
        let title = img(1100, 760, |_, y| {
            if y < 18 { [200, 200, 210, 255] } else { [30, 30, 40, 255] }
        });
        let e = moved(&base, &title, MinChange::PANE).expect_err("a title strip is not a redraw");
        assert!(e.contains("THIN STRIP"), "must name the strip shape, got: {e}");

        // 2. An EPHEMERAL PORT rendered as text: a few dozen pixels somewhere.
        let port = img(1100, 760, |x, y| {
            if (40..96).contains(&x) && (700..712).contains(&y) {
                [255, 255, 255, 255]
            } else {
                [30, 30, 40, 255]
            }
        });
        let e = moved(&base, &port, MinChange::PANE).expect_err("a port number is not a redraw");
        assert!(e.contains("below the"), "must name the floor it missed, got: {e}");

        // 3. A LEGEND: a small block, still nothing like a pane.
        let legend = img(1100, 760, |x, y| {
            if (900..1000).contains(&x) && (20..44).contains(&y) {
                [180, 180, 90, 255]
            } else {
                [30, 30, 40, 255]
            }
        });
        assert!(moved(&base, &legend, MinChange::PANE).is_err(), "a legend is not a redraw");

        // And it must ACCEPT a pane actually redrawing — otherwise the guard is simply
        // "always red", which is no more useful than "always green".
        let pane = img(1100, 760, |x, y| {
            if (100..700).contains(&x) && (100..600).contains(&y) {
                [90, 140, 200, 255]
            } else {
                [30, 30, 40, 255]
            }
        });
        let d = moved(&base, &pane, MinChange::PANE).expect("a redrawn pane must pass");
        assert!(d.percent() > 30.0, "the accepted case really is large: {:.2} %", d.percent());
    }

    /// Identical frames are the original failure the `assert_ne!` was reaching for, and
    /// the message has to say so rather than reporting a floor.
    #[test]
    fn moved_rejects_identical_frames_by_name() {
        let a = img(64, 64, |_, _| [1, 2, 3, 255]);
        let e = moved(&a, &a.clone(), MinChange::WIDGET).expect_err("identical must fail");
        assert!(e.contains("byte-identical"), "got: {e}");
    }

    /// A resize is not proof that an interaction landed, and must not be silently
    /// compared (the old code would have compared different-length byte vectors and
    /// happily reported "they differ").
    #[test]
    fn moved_rejects_a_size_change_instead_of_comparing_it() {
        let a = img(32, 32, |_, _| [0, 0, 0, 255]);
        let b = img(64, 32, |_, _| [0, 0, 0, 255]);
        let e = moved(&a, &b, MinChange::WIDGET).expect_err("a resize must not be a pass");
        assert!(e.contains("SIZE"), "got: {e}");
    }

    /// **The determinism control must itself be able to go red.**
    ///
    /// A control that only ever returns `Ok` is the exact disease it exists to catch:
    /// the suite would print "noise floor 0 px" whatever the host did. So both
    /// directions are asserted — a reproducible pair passes, and a single jittering
    /// pixel is REFUSED and named.
    #[test]
    fn stable_accepts_a_reproducible_pair_and_refuses_one_jittering_pixel() {
        let a = img(1100, 760, |x, y| [(x % 251) as u8, (y % 253) as u8, 40, 255]);
        stable(&a, &a.clone()).expect("two identical renders are a zero noise floor");

        // One channel, one bit, one pixel — 0.0001 % of the frame. `moved` would call
        // this "below the floor"; the control has to call it NOT DETERMINISTIC.
        let mut jitter = a.clone();
        let p = *jitter.get_pixel(500, 400);
        jitter.put_pixel(500, 400, image::Rgba([p.0[0] ^ 1, p.0[1], p.0[2], p.0[3]]));
        let e = stable(&a, &jitter).expect_err("a jittering pixel must fail the control");
        assert!(e.contains("NOT DETERMINISTIC"), "must name the finding, got: {e}");
        assert!(e.contains("1 of 836000 px"), "must report the measured jitter, got: {e}");
    }

    /// A same-state pair that comes back at a different SIZE is a broken harness, not a
    /// comparison to attempt — and it must not be reported as a mere difference.
    #[test]
    fn stable_refuses_a_size_change_as_a_broken_harness() {
        let a = img(32, 32, |_, _| [0, 0, 0, 255]);
        let b = img(32, 64, |_, _| [0, 0, 0, 255]);
        let e = stable(&a, &b).expect_err("a resize between same-state renders must fail");
        assert!(e.contains("DIFFERENT SIZES"), "got: {e}");
    }

    #[test]
    fn identical_images_compare_equal() {
        let a = img(8, 4, |x, y| [x as u8, y as u8, 7, 255]);
        let b = a.clone();
        assert_eq!(compare(&a, &b), None);
    }

    #[test]
    fn one_changed_pixel_is_located_and_counted() {
        let a = img(8, 4, |_, _| [10, 10, 10, 255]);
        let mut b = a.clone();
        b.put_pixel(5, 2, image::Rgba([10, 10, 13, 255]));
        let d = compare(&a, &b).expect("a one-pixel change is a difference");
        assert_eq!(d.pixels, 1);
        assert_eq!(d.total, 32);
        assert_eq!(d.first, Some((5, 2)));
        assert_eq!(d.bbox, Some((5, 2, 5, 2)));
        assert_eq!(d.max_channel_delta, 3);
        assert!((d.percent() - 3.125).abs() < 1e-9);
    }

    /// The guard is EXACT: a single least-significant-bit change must not be
    /// forgiven. This is the assertion a tolerance would break.
    #[test]
    fn a_one_bit_change_is_not_forgiven() {
        let a = img(4, 4, |_, _| [128, 128, 128, 255]);
        let mut b = a.clone();
        b.put_pixel(0, 0, image::Rgba([129, 128, 128, 255]));
        assert!(compare(&a, &b).is_some());
    }

    #[test]
    fn diff_image_stamps_only_the_changed_pixels() {
        let a = img(4, 2, |_, _| [200, 200, 200, 255]);
        let mut b = a.clone();
        b.put_pixel(3, 1, image::Rgba([0, 0, 0, 255]));
        let d = diff_image(&a, &b);
        assert_eq!(*d.get_pixel(3, 1), image::Rgba([255, 0, 190, 255]));
        assert_ne!(*d.get_pixel(0, 0), image::Rgba([255, 0, 190, 255]));
    }

    #[test]
    fn a_missing_golden_fails() {
        if blessing() {
            return; // under an explicit bless run, "missing" is legitimately created
        }
        let dir = std::env::temp_dir().join("facett-golden-missing-test");
        std::fs::create_dir_all(&dir).ok();
        let path = dir.join("definitely-not-here.png");
        std::fs::remove_file(&path).ok();
        let a = img(4, 4, |_, _| [1, 2, 3, 255]);
        let err = std::panic::catch_unwind(|| assert_golden(&a, &path, "missing_probe"))
            .expect_err("a missing golden must FAIL, not pass");
        let msg = err.downcast_ref::<String>().map(String::as_str).unwrap_or_default();
        assert!(msg.contains("MISSING golden"), "unexpected panic: {msg}");
        // …and it must not have created the golden as a side effect.
        assert!(!path.exists(), "the guard must never write the golden outside a blessing");
    }
}