crates/nornir-warehouse-nav/tests/robot_world_shot.rs
//! **robot_world_shot** — the image-backed proof for the owner's report ("typ 1000 noder
//! är gråa och läggs över varandra"). A screenshot-capturing egui_kittest robot test that
//! renders the SHIPPED `WarehouseNav` world graph at ~1000 nodes on the GPU
//! (`Harness::render()` → RgbaImage), saves it, and asserts — QUANTITATIVELY, not by
//! eyeballing — that the two failure modes are gone:
//!
//! 1. **Stacked nodes** — the fraction of nodes whose nearest neighbour is closer than a
//! dot diameter (in projected px, using the panel's real fit camera) is small. The
//! "before" look (184-px chips at zoom 1) trips the same guard.
//! 2. **All grey** — the rendered node fills have real variance (many distinct colours
//! keyed by repo/crate). A single-grey model trips the guard.
//!
//! Saves `world-1000.png` (before: chips, zoom-1, greyed) and `world-1000-fixed.png`
//! (after: the real dot-cloud panel) under `workspace_nordisk/scratch/` for visual review.
//!
//! Needs a GPU adapter (egui_kittest `wgpu` feature); on a box without one the render step
//! is skipped honestly and the data-level guards still run.
#![cfg(not(target_arch = "wasm32"))]
use std::cell::RefCell;
use std::collections::BTreeSet;
use std::rc::Rc;
use egui::{vec2, Color32};
use egui_kittest::Harness;
use facett_core::Facet;
use facett_graphpan::{draw_graph, Decorations, GraphModel, GraphView, BOX_H, BOX_W};
use nornir_warehouse_nav::WarehouseNav;
fn shot_dir() -> String {
format!(
"{}/git/workspace_nordisk/scratch",
std::env::var("HOME").unwrap_or_default()
)
}
const N_TARGET: u64 = 1000;
/// The panel's own dot radius formula (kept in sync) — for the projected-overlap guard.
fn dot_radius(n: usize) -> f32 {
(9000.0 / n.max(1) as f32).sqrt().clamp(1.8, 6.0)
}
/// Distinct node fill colours in a model (the COLOUR proxy — grey blobs ⇒ ~1).
fn distinct_fills(m: &GraphModel) -> usize {
m.nodes
.iter()
.map(|n| (n.fill.r(), n.fill.g(), n.fill.b()))
.collect::<BTreeSet<_>>()
.len()
}
/// Fraction of nodes whose nearest neighbour is closer than `min_px` in PROJECTED pixels
/// (world distance × zoom) — the "stacked on top of each other" proxy.
fn stacked_fraction(m: &GraphModel, zoom: f32, min_px: f32) -> f32 {
let n = m.nodes.len();
if n < 2 {
return 0.0;
}
let min2 = (min_px / zoom.max(1e-6)).powi(2); // compare in world units
let mut stacked = 0usize;
for (i, a) in m.nodes.iter().enumerate() {
let mut near = false;
for (j, b) in m.nodes.iter().enumerate() {
if i == j {
continue;
}
let dx = a.pos.x - b.pos.x;
let dy = a.pos.y - b.pos.y;
if dx * dx + dy * dy < min2 {
near = true;
break;
}
}
if near {
stacked += 1;
}
}
stacked as f32 / n as f32
}
/// Fraction of nodes whose CHIP (184×34 world units at zoom 1 — the pre-fix render)
/// overlaps another node's chip. This is the stacking the old chip renderer produced.
fn chip_overlap_fraction(m: &GraphModel) -> f32 {
let n = m.nodes.len();
if n < 2 {
return 0.0;
}
let mut over = 0usize;
for (i, a) in m.nodes.iter().enumerate() {
let mut hit = false;
for (j, b) in m.nodes.iter().enumerate() {
if i == j {
continue;
}
if (a.pos.x - b.pos.x).abs() < BOX_W && (a.pos.y - b.pos.y).abs() < BOX_H {
hit = true;
break;
}
}
if hit {
over += 1;
}
}
over as f32 / n as f32
}
fn nav_1000() -> WarehouseNav {
let mut nav = WarehouseNav::from_fixture_with_functions(N_TARGET as usize);
nav.update_json(&format!(r#"{{"world_target":{N_TARGET}}}"#));
nav.update_json(r#"{"world":true}"#);
nav
}
#[test]
fn robot_world_1000_is_spread_and_coloured_with_screenshot() {
let _ = std::fs::create_dir_all(shot_dir());
// ── the SHIPPED panel at ~1000 nodes, rendered on the GPU ──────────────────────
let nav = Rc::new(RefCell::new(nav_1000()));
let n = nav.clone();
let mut h = Harness::builder()
.with_size(vec2(1400.0, 1000.0))
.wgpu()
.build_ui(move |ui| {
n.borrow_mut().ui(ui);
});
h.run();
h.run(); // second frame: the fit has been applied, dots are placed
let s = Facet::state_json(&*nav.borrow());
let nodes = s["preview_nodes"].as_u64().unwrap();
assert!(
nodes >= 700,
"world previews ~1000 nodes (got {nodes}): {s}"
);
// ── AFTER guards (data-level, deterministic) ───────────────────────────────────
let model = nav.borrow().world_preview_model();
let view = nav.borrow().world_view_snapshot();
let dot_r = dot_radius(model.nodes.len());
// COLOUR: many distinct fills (repo/crate/function hues), not one grey.
let fills = distinct_fills(&model);
assert!(
fills > 12,
"world is multi-coloured ({fills} distinct fills), not grey blobs"
);
// SPREAD: with the real fit camera + dot size, few nodes stack (centres within a dot
// diameter). The pre-fix collapse put >50% here.
let stacked = stacked_fraction(&model, view.zoom, 2.0 * dot_r);
assert!(
stacked < 0.20,
"nodes are spread — only {:.1}% stack within a dot diameter (fit zoom {:.4}, dot {:.1}px)",
stacked * 100.0,
view.zoom,
dot_r
);
// Positions are distinct (no exact pile-up).
let distinct_pos = model
.nodes
.iter()
.map(|n| (n.pos.x.to_bits(), n.pos.y.to_bits()))
.collect::<BTreeSet<_>>()
.len();
assert_eq!(
distinct_pos,
model.nodes.len(),
"every node has a distinct position"
);
// ── save the AFTER screenshot (the real rendered panel) ────────────────────────
match h.render() {
Ok(img) => {
let p = format!("{}/world-1000-fixed.png", shot_dir());
img.save(&p).expect("save fixed screenshot");
eprintln!(
"[shot] AFTER → {p} ({} nodes, {fills} fills, {:.1}% stacked)",
model.nodes.len(),
stacked * 100.0
);
}
Err(e) => eprintln!("[shot] AFTER render skipped (no GPU?): {e}"),
}
// ── BEFORE: the pre-fix look (184-px chips, zoom 1, greyed) — the red-when-broken
// contrast. Same node positions, rendered the old way, trips BOTH guards. ────────
let mut grey = model.clone();
for nd in &mut grey.nodes {
nd.fill = Color32::from_gray(140);
nd.stroke = Color32::from_gray(90);
}
// colour guard FAILS on the greyed model:
assert_eq!(
distinct_fills(&grey),
1,
"the greyed before-model trips the colour guard"
);
// stacking guard FAILS on the chip render (184-px chips overlap heavily):
let chip_over = chip_overlap_fraction(&grey);
assert!(
chip_over > 0.40,
"the before (chip) render is stacked — {:.1}% of chips overlap (guard is red-when-broken)",
chip_over * 100.0
);
let gm = grey.clone();
let gv = Rc::new(RefCell::new(GraphView::default())); // zoom 1, dot_px None → chips
let gvc = gv.clone();
let mut h2 = Harness::builder()
.with_size(vec2(1400.0, 1000.0))
.wgpu()
.build_ui(move |ui| {
let bg = Color32::from_gray(14);
let text = Color32::from_gray(235);
let ring = Color32::from_rgb(0x39, 0x87, 0xe5);
let dim = Color32::from_gray(120);
let _ = draw_graph(
ui,
&gm,
&Decorations::default(),
&mut gvc.borrow_mut(),
bg,
text,
ring,
dim,
);
});
h2.run();
match h2.render() {
Ok(img) => {
let p = format!("{}/world-1000.png", shot_dir());
img.save(&p).expect("save before screenshot");
eprintln!(
"[shot] BEFORE → {p} (greyed chips, {:.1}% overlap)",
chip_over * 100.0
);
}
Err(e) => eprintln!("[shot] BEFORE render skipped (no GPU?): {e}"),
}
// testmatrix cell — the world graph is spread + multi-coloured (not grey
// stacked blobs). Reaching here means every guard above passed; this row
// surfaces the visual proof in the dwarves testmatrix (`--features testmatrix`).
#[cfg(feature = "testmatrix")]
nornir_testmatrix::functional_status(
"dwarves/world-graph",
"world_1000_is_spread_and_coloured",
true,
&format!(
"world previews {nodes} nodes with {fills} distinct fills and only \
{:.1}% stacked within a dot diameter (was grey + >50% stacked)",
stacked * 100.0
),
);
}