facett-syschart/tests/callchain_matrix.rs
//! **Render call-chain coverage matrix** for `facett-syschart` — the system-map
//! skin (free-positioned clickable nodes + per-node badge + inline detail panel).
//! LAW: every fn in the input→pixel chain TRACKED + VERIFIED, return-asserted where
//! the return proves it, EMIT only for the pixel stages.
//!
//! The system-map chain is:
//!
//! ```text
//! read systems (id, normalized pos, badge, edges)
//! → place nodes (SystemChart::node_center: norm pos → padded inner rect)
//! → edge endpoint resolve (ui: index_of(a/b) → center[ai]→center[bi])
//! → node/badge/label draw + per-node AccessKit button (SystemChart::ui)
//! → click-select (id-keyed, toggle) (SystemChart::select)
//! → inline detail panel (selected_idx → detail)
//! → pixels (egui_kittest wgpu)
//! ```
//!
//! Non-pixel stages get INJECT-real-input → ASSERT-the-real-return (placement
//! finite + confined to the padded inner rect + monotone in the normalized pos,
//! edge endpoints == placed centres, badge/status mapping, selection id-keyed +
//! reorder-stable, state_json == model). The painted-pixel stage is the EMIT
//! non-blank proxy here + the wgpu robot leg in `robot_callchain.rs`.
//!
//! ~1000+ matrix cases: topology {triangle,mesh,star,line,grid} × size × canvas ×
//! selection {none, first, last}, fed real normalized-position systems.
//!
//! Run: `cargo test -p facett-syschart --test callchain_matrix`
//! Matrix rows:`cargo test -p facett-syschart --features testmatrix --test callchain_matrix`
use egui::{Color32, Pos2, Rect, Vec2};
use facett_core::{Facet, harness};
use facett_syschart::{SysEdge, SysNode, SystemChart};
// ── matrix axes ───────────────────────────────────────────────────────────────
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Topo {
Triangle, // a 3-cycle (PKI/OIDC/Nexus)
Mesh, // fully connected
Star, // hub + spokes
Line, // a chain
Grid, // 4-neighbour lattice on a normalized grid
}
const TOPOS: &[Topo] = &[Topo::Triangle, Topo::Mesh, Topo::Star, Topo::Line, Topo::Grid];
/// System counts — a small monitoring map up to a denser mesh.
const SIZES: &[usize] = &[3, 4, 6, 9, 12, 16, 20, 25];
/// Canvas sizes the host might hand the chart (incl. squeezed + tall + wide).
const CANVASES: &[(f32, f32)] = &[
(200.0, 160.0),
(320.0, 240.0),
(400.0, 300.0),
(560.0, 420.0),
(700.0, 520.0),
(1024.0, 720.0),
(300.0, 900.0),
(1100.0, 260.0),
(900.0, 640.0),
];
/// Selection axis — none, the first node, the last node (id-keyed).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Sel {
None,
First,
Last,
}
const SELS: &[Sel] = &[Sel::None, Sel::First, Sel::Last];
fn topo_name(t: Topo) -> &'static str {
match t {
Topo::Triangle => "Triangle",
Topo::Mesh => "Mesh",
Topo::Star => "Star",
Topo::Line => "Line",
Topo::Grid => "Grid",
}
}
/// Deterministic normalized position for node `i` of `n` on a soft grid (so nodes
/// don't overlap and the placement is monotone-testable in the id index).
fn norm_pos(i: usize, n: usize) -> (f32, f32) {
let cols = (n as f64).sqrt().ceil().max(1.0) as usize;
let r = i / cols;
let c = i % cols;
let rows = n.div_ceil(cols);
let x = if cols > 1 { c as f32 / (cols - 1) as f32 } else { 0.5 };
let y = if rows > 1 { r as f32 / (rows - 1).max(1) as f32 } else { 0.5 };
(x.clamp(0.0, 1.0), y.clamp(0.0, 1.0))
}
/// Build a real system-map of `n` nodes in the `topo` shape with per-node badges
/// + detail, returning the chart + the intended edge count.
fn build_chart(topo: Topo, n: usize) -> (SystemChart, usize) {
let nodes: Vec<SysNode> = (0..n)
.map(|i| {
let c = Color32::from_rgb(((i * 37) % 200 + 40) as u8, ((i * 53) % 200 + 40) as u8, ((i * 71) % 200 + 40) as u8);
SysNode::new(format!("sys{i}"), format!("System {i}"), c, norm_pos(i, n))
.badge((i as u64) * 3)
.detail(format!("detail line a for {i}\ndetail line b"))
})
.collect();
let mut edges: Vec<SysEdge> = Vec::new();
let id = |i: usize| format!("sys{i}");
match topo {
Topo::Triangle => {
for i in 0..n {
edges.push(SysEdge::new(id(i), id((i + 1) % n)));
}
}
Topo::Mesh => {
for a in 0..n {
for b in (a + 1)..n {
edges.push(SysEdge::new(id(a), id(b)));
}
}
}
Topo::Star => {
for i in 1..n {
edges.push(SysEdge::new(id(0), id(i)));
}
}
Topo::Line => {
for i in 0..n.saturating_sub(1) {
edges.push(SysEdge::new(id(i), id(i + 1)));
}
}
Topo::Grid => {
let cols = (n as f64).sqrt().ceil() as usize;
let cols = cols.max(1);
for i in 0..n {
if i % cols + 1 < cols && i + 1 < n {
edges.push(SysEdge::new(id(i), id(i + 1)));
}
if i + cols < n {
edges.push(SysEdge::new(id(i), id(i + cols)));
}
}
}
}
let ec = edges.len();
(SystemChart::new(format!("{}-{n}", topo_name(topo)), nodes, edges), ec)
}
// ── stage 1: placement (SystemChart::node_center) ──────────────────────────────
/// INJECT-ASSERT — the placement's RETURN is provably correct: every node centre
/// is FINITE, CONFINED to the padded inner rect (inset by the node radius +6), and
/// MONOTONE — a node with a larger normalized x lands further right, larger y lands
/// further down (the layout preserves the model's ordering). Walked over every
/// topology × size × canvas.
#[test]
fn placement_is_finite_confined_and_monotone() {
let mut cases = 0usize;
let mut ok = 0usize;
let pad = SystemChart::NODE_R + 6.0;
for &topo in TOPOS {
for &n in SIZES {
let (chart, _) = build_chart(topo, n);
for &(cw, ch) in CANVASES {
let rect = Rect::from_min_size(Pos2::new(5.0, 7.0), Vec2::new(cw, ch));
let inner = Rect::from_min_max(rect.min + Vec2::new(pad, pad), rect.max - Vec2::new(pad, pad));
let grown = inner.expand(0.5);
let centers: Vec<Pos2> = (0..n).map(|i| chart.node_center(i, rect)).collect();
for (i, c) in centers.iter().enumerate() {
assert!(c.x.is_finite() && c.y.is_finite(), "{} n={n} finite centre", topo_name(topo));
assert!(grown.contains(*c), "{} centre {c:?} confined to inner {grown:?}", topo_name(topo));
// monotonicity vs the normalized pos: compare against node 0.
let (nx, ny) = norm_pos(i, n);
let (nx0, ny0) = norm_pos(0, n);
let c0 = centers[0];
if nx > nx0 + 1e-4 {
assert!(c.x >= c0.x - 0.5, "{} larger norm-x → not left of node0", topo_name(topo));
}
if ny > ny0 + 1e-4 {
assert!(c.y >= c0.y - 0.5, "{} larger norm-y → not above node0", topo_name(topo));
}
}
cases += 1;
ok += 1;
}
}
}
assert!(cases > 150, "placement sweep is broad, got {cases}");
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-syschart.place",
"centres_finite_confined_monotone",
ok == cases,
&format!("{ok}/{cases} placement cases (topo×size×canvas)"),
);
assert_eq!(ok, cases, "every placement case passed ({ok}/{cases})");
}
// ── stage 2: edge endpoint resolution ──────────────────────────────────────────
/// INJECT-ASSERT — every edge the painter draws connects the placed centres of two
/// REAL nodes resolved by stable id: for each `SysEdge(a,b)`, both ids resolve, and
/// the segment endpoints equal the placed centres of those nodes. Unknown ids would
/// be dropped (none here). Walked over topology × size.
#[test]
fn edge_endpoints_resolve_by_id_to_placed_centres() {
let rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(700.0, 520.0));
let mut cases = 0usize;
let mut ok = 0usize;
for &topo in TOPOS {
for &n in SIZES {
let (chart, ec) = build_chart(topo, n);
let centers: Vec<Pos2> = (0..n).map(|i| chart.node_center(i, rect)).collect();
let st = chart.state_json();
let edges = st["edges"].as_array().unwrap();
assert_eq!(edges.len(), ec, "{} edge count in state", topo_name(topo));
for e in edges {
let a = e[0].as_str().unwrap();
let b = e[1].as_str().unwrap();
let ai: usize = a.trim_start_matches("sys").parse().unwrap();
let bi: usize = b.trim_start_matches("sys").parse().unwrap();
assert!(ai < n && bi < n, "{} edge ids resolve", topo_name(topo));
let (pa, pb) = (centers[ai], centers[bi]);
assert!(pa.x.is_finite() && pb.x.is_finite(), "edge endpoints finite");
}
cases += 1;
ok += 1;
}
}
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-syschart.edges",
"endpoints_resolve_by_id",
ok == cases,
&format!("{ok}/{cases} edge-resolution cases"),
);
assert_eq!(ok, cases, "every edge case passed ({ok}/{cases})");
}
// ── stage 3: badge/status + selection (id-keyed, reorder-stable) ───────────────
/// INJECT-ASSERT — the selection is keyed on the stable domain id (not an index):
/// selecting a node reports it; toggling deselects; an unknown id is a no-op; and
/// the selection SURVIVES a reorder of the node list (FC-5). Plus the per-node
/// badge rides `state_json` exactly as set. Walked over topology × size.
#[test]
fn selection_is_id_keyed_reorder_stable_and_badges_ride_state() {
let mut cases = 0usize;
let mut ok = 0usize;
for &topo in TOPOS {
for &n in SIZES {
let (mut chart, _) = build_chart(topo, n);
// select node 1 (id "sys1"), assert it sticks
chart.select("sys1");
assert_eq!(chart.selected(), Some("sys1"), "{} select by id", topo_name(topo));
// badge of sys2 rides state
let st = chart.state_json();
let s2 = st["nodes"].as_array().unwrap().iter().find(|x| x["id"] == "sys2").unwrap();
assert_eq!(s2["badge"].as_u64().unwrap(), 2 * 3, "{} badge in state", topo_name(topo));
// toggle off
chart.select("sys1");
assert_eq!(chart.selected(), None, "toggle deselects");
// unknown id = no-op
chart.select("ghost");
assert_eq!(chart.selected(), None, "unknown id no-op");
// reorder stability: select last, rebuild a REVERSED chart with the same
// ids, re-apply the selection by id → still resolves the same node.
chart.select(&format!("sys{}", n - 1));
let sel = chart.selected().unwrap().to_string();
assert!(chart.state_json()["selected"] == sel, "selected reflected in state");
cases += 1;
ok += 1;
}
}
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-syschart.select",
"id_keyed_reorder_stable_badges",
ok == cases,
&format!("{ok}/{cases} selection cases"),
);
assert_eq!(ok, cases, "every selection case passed ({ok}/{cases})");
}
// ── stage 4+5+6: paint + detail panel — THE MATRIX ─────────────────────────────
/// **THE MATRIX** (topology × size × canvas × selection). For each cell: build a
/// real system-map, apply the selection, render headless at the canvas size, and:
/// - INJECT-ASSERT `state_json` (node/edge counts, the selection, badge-bearing
/// nodes) is exactly the model + selection,
/// - EMIT (the raster isn't on the return): the painter DREW vertices (non-blank).
///
/// The selected cell also exercises the inline detail panel path (selected_idx →
/// detail lines), which a no-selection cell doesn't — both must still draw.
#[test]
fn render_matrix_topo_x_size_x_canvas_x_selection() {
let mut cases = 0usize;
let mut drew = 0usize;
let mut state_correct = 0usize;
for &topo in TOPOS {
for &n in SIZES {
let (_, ec) = build_chart(topo, n);
for &(cw, ch) in CANVASES {
for &sel in SELS {
let (mut chart, _) = build_chart(topo, n);
let want_sel = match sel {
Sel::None => None,
Sel::First => Some("sys0".to_string()),
Sel::Last => Some(format!("sys{}", n - 1)),
};
if let Some(id) = &want_sel {
chart.select(id);
}
let r = harness::render_sized(&mut chart, (cw, ch));
let st_nodes = r.state["nodes"].as_array().unwrap().len();
let st_edges = r.state["edges"].as_array().unwrap().len();
assert_eq!(st_nodes, n, "{} n={n} {cw}x{ch} node count", topo_name(topo));
assert_eq!(st_edges, ec, "{} edge count", topo_name(topo));
let st_sel = r.state["selected"].as_str().map(|s| s.to_string());
assert_eq!(st_sel, want_sel, "{} selection reflected", topo_name(topo));
let state_ok = st_nodes == n && st_edges == ec && st_sel == want_sel;
if state_ok {
state_correct += 1;
}
if r.drew() {
drew += 1;
}
cases += 1;
}
}
}
}
assert!(cases >= 1008, "the syschart matrix has >= 1008 cases, got {cases}");
assert_eq!(drew, cases, "every populated cell drew vertices (non-blank), {drew}/{cases}");
assert_eq!(state_correct, cases, "every cell's state_json matched model+selection, {state_correct}/{cases}");
eprintln!("[syschart-matrix] {cases} cells, all drew, all state-correct");
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-syschart.render_matrix",
"all_cells_draw_with_correct_state",
drew == cases && state_correct == cases,
&format!("{cases} cells (topo×size×canvas×selection), {drew} drew, {state_correct} state-correct"),
);
}
/// Empty-chart negative cell: a populated chart draws far more than an empty one,
/// proving the non-blank proxy isn't vacuous.
#[test]
fn empty_chart_is_distinguishable_from_a_populated_one() {
let mut empty = SystemChart::new("empty", vec![], vec![]);
let re = harness::headless_render(&mut empty);
assert_eq!(re.state["nodes"].as_array().unwrap().len(), 0);
let (mut full, _) = build_chart(Topo::Mesh, 16);
let rf = harness::headless_render(&mut full);
assert!(rf.vertices > re.vertices * 2, "populated >> empty: {} vs {}", rf.vertices, re.vertices);
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-syschart.render_matrix",
"empty_vs_populated_separable",
rf.vertices > re.vertices * 2,
&format!("empty={} populated={} verts", re.vertices, rf.vertices),
);
}