crates/nornir-warehouse-nav/tests/robot_world_3d_rotate.rs
//! **robot_world_3d_rotate** — the RED-when-broken proof that the "3D rotating" showpiece spins
//! the CAMERA, not the LAYOUT.
//!
//! The world graph gained a 3D mode (a nested-spherical `galaxy_3d` layout + an orthographic
//! camera) and a **"3D rotating"** checkbox that auto-advances the camera azimuth over time so the
//! galaxy spins slowly around its vertical axis. The one invariant that keeps it a *live* showpiece
//! instead of a stutter-box: rotation must re-run only the cheap PROJECTION each frame — the
//! Barnes–Hut/`galaxy_3d` layout stays cached (0 rebuilds across rotation frames).
//!
//! This guard lives in its OWN test binary so the process-wide `world_build_calls()` counter
//! reflects only this test (each `cargo test` integration file is a separate process). It toggles
//! 3D + rotate on, asserts `state_json` reports both true, then runs N repaint frames and asserts
//! the projection angle CHANGED while the layout rebuild count stayed EXACTLY 0 — the same
//! frame-budget pattern as `robot_world_frame_budget.rs`.
//!
//! NOTE: with rotation on, `ui()` calls `ctx.request_repaint()` every frame, so `Harness::run()`
//! (which loops until no immediate repaint is requested) would never settle — the rotation frames
//! use `run_steps(N)`, which runs EXACTLY N frames and advances the animation clock each step.
#![cfg(not(target_arch = "wasm32"))]
use std::cell::RefCell;
use std::rc::Rc;
use egui::vec2;
use egui_kittest::kittest::Queryable;
use egui_kittest::Harness;
use facett_core::Facet;
use nornir_warehouse_nav::{world_build_calls, WarehouseNav};
/// ROBOT SCENARIO — enter the world graph at the 1000-node preview the demo uses, turn on 3D + the
/// "3D rotating" checkbox, and prove the spin is PROJECTION-ONLY: over N repaint frames the camera
/// azimuth (`rotate_angle`) sweeps while `world_build_calls()` stays flat (0 relayouts).
#[test]
fn world_3d_rotation_spins_the_camera_not_the_layout() {
let nav = Rc::new(RefCell::new(WarehouseNav::from_fixture_with_functions(
1000,
)));
let n = nav.clone();
let mut h = Harness::builder()
.with_size(vec2(1200.0, 800.0))
.build_ui(move |ui| {
n.borrow_mut().ui(ui);
});
// ── Enter the world graph at the 1000-node preview, in 3D, rotating. ──
nav.borrow_mut().update_json(r#"{"world_target":1000}"#);
nav.borrow_mut().update_json(r#"{"world":true}"#);
nav.borrow_mut().update_json(r#"{"world_3d":true}"#);
h.run(); // renders + builds the galaxy_3d layout once (rotate still off → run() settles)
// The 3D layout is live: the preview lane reads galaxy_3d and all three tiers are present.
let s = Facet::state_json(&*nav.borrow());
assert_eq!(s["world"], true, "world mode on: {s}");
assert_eq!(s["world_3d"], true, "3D mode on: {s}");
assert_eq!(
s["preview_lane"], "galaxy_3d",
"the 3D galaxy layout lane is active: {s}"
);
assert!(
s["preview_repo_nodes"].as_u64().unwrap() >= 1,
"repos tier present: {s}"
);
assert!(
s["preview_crate_nodes"].as_u64().unwrap() >= 1,
"crates tier present: {s}"
);
assert!(
s["preview_fn_nodes"].as_u64().unwrap() >= 1,
"functions tier present: {s}"
);
// The demo's 1000-node preview really is populated here.
assert!(
s["preview_nodes"].as_u64().unwrap() >= 100,
"the 1000-node preview is populated: {s}"
);
// The "3D rotating" checkbox is actually rendered in the pane (wired into `ui()`), not off in a
// code path nobody reaches.
assert!(
h.query_by_label("3D rotating").is_some(),
"the '3D rotating' checkbox renders in the world controls"
);
// Turn the spin on through the headless twin of the checkbox.
nav.borrow_mut().update_json(r#"{"world_rotate":true}"#);
let s = Facet::state_json(&*nav.borrow());
assert_eq!(
s["world_rotate"], true,
"the 3D rotating checkbox reads on: {s}"
);
// ── The invariant: N rotation frames advance the ANGLE but rebuild the layout 0 times. ──
let angle0 = s["rotate_angle"]
.as_f64()
.expect("rotate_angle is reported");
let base_builds = world_build_calls();
const FRAMES: usize = 24;
h.run_steps(FRAMES); // EXACTLY N frames (run() would spin forever while rotating)
let builds_delta = world_build_calls() - base_builds;
let s = Facet::state_json(&*nav.borrow());
let angle1 = s["rotate_angle"]
.as_f64()
.expect("rotate_angle is reported");
// 0 layout rebuilds across the whole spin — the camera moved, the galaxy_3d layout did not.
assert_eq!(
builds_delta, 0,
"rotation is projection-only: {FRAMES} spin frames rebuilt the layout {builds_delta} times (must be 0)"
);
// The projection angle genuinely advanced (a live, moving showpiece — not a frozen frame).
let moved = (angle1 - angle0).abs();
assert!(
moved > 1e-3,
"the camera azimuth advanced over {FRAMES} frames (Δ={moved:.4} rad, was {angle0:.4} → {angle1:.4})"
);
// Turning rotation OFF freezes the angle — no further drift, still 0 rebuilds.
nav.borrow_mut().update_json(r#"{"world_rotate":false}"#);
let steady = world_build_calls();
h.run_steps(6);
let s = Facet::state_json(&*nav.borrow());
let angle2 = s["rotate_angle"].as_f64().unwrap();
assert_eq!(
world_build_calls() - steady,
0,
"a static 3D world does not rebuild either"
);
assert!(
(angle2 - angle1).abs() < 1e-4,
"azimuth is frozen once rotation is off"
);
let ok = builds_delta == 0 && moved > 1e-3 && (angle2 - angle1).abs() < 1e-4;
// This is a SHIPPED surface (the warehouse landing pane's live 3D showpiece), so it owes the
// coverage board a row — reuse edda's testmatrix rather than hand-roll a harness.
#[cfg(feature = "testmatrix")]
nornir_testmatrix::functional_status(
"nornir-warehouse-nav",
"world_3d_rotation_spins_the_camera_not_the_layout",
ok,
"the '3D rotating' checkbox auto-spins the world galaxy around its vertical axis by advancing \
only the orthographic camera azimuth: over 24 repaint frames at the 1000-node preview the \
projection angle sweeps (Δ>1e-3 rad) while world_build_calls() stays EXACTLY 0 — proving the \
galaxy_3d layout is reused from cache, never rebuilt per frame; turning rotation off freezes \
the angle with 0 further rebuilds",
);
let _ = ok;
}