facett-map/tests/lane_flag_on_screen.rs
//! **"A flag saying GPU or CPU when rendering osm 2d, so i know whats being used"** —
//! The owner, 2026-08-23. The 2D twin of `facett-map3d/tests/lane_flag.rs`.
//!
//! # Why the oracle is the a11y tree and not a pixel
//!
//! `facett_core::devid::badge` learned this the hard way and says so in its own doc:
//! *"Painted-only text is invisible to every check we have — which is how a badge ships
//! green and never appears."* So the chip is emitted as a real AccessKit node and the
//! oracle is `query_by_label` over that tree — which proves the chip was laid out at a
//! real rect in a real pass.
//!
//! # Why THREE states, and why the third one is the whole point
//!
//! `MapFacet::way_lane()` is a **plan**, not an observation. On the GPU route the paint
//! callback is merely *placed*; nothing is out of the device until
//! `MapPaintCallback::prepare` has encoded a frame. Reporting that interval as `GPU` is a
//! boast and reporting it as `CPU` is a lie — and the lie is the one this codebase keeps
//! getting caught by. On 2026-08-23, in this very crate, `paint_order_pixels` was found
//! passing on the CPU painter while the device lane painted the wrong order, and korp's
//! `log_map3d_lane` published `gpu=false` before the first paint on a box with a 4090
//! (read as the genuine defect by three people, 2026-08-06).
//!
//! So `Pending` is asserted as its own answer here, and asserted to be **not** `CPU`.
//!
//! # The three claims, and the RED probe for each
//!
//! | claim | RED probe |
//! |---------------------------------------------|---------------------------------------------------------------------------|
//! | `the_cpu_painter_shows_cpu_on_the_pane` | make `lane_flag` answer `Gpu` whenever the feature is on |
//! | `the_gpu_lane_shows_gpu_on_the_pane` | drop the `lane_frames() > 0` arm so it can never leave `Pending` |
//! | `a_lane_that_has_not_encoded_shows_neither` | make the `Pending` arm answer `Cpu` — the collapse the type exists to stop |
//!
//! Run (the GPU arms need the real device and open NO window):
//! ```text
//! env -u WAYLAND_DISPLAY -u DISPLAY \
//! cargo test -p facett-map --features wgpu --test lane_flag_on_screen -- --test-threads=1 --nocapture
//! ```
#![cfg(feature = "wgpu")]
use egui_kittest::kittest::Queryable;
use facett_core::render::lane::{lane_flag_atom, LaneFlag};
use facett_map::layer::{LatLon, MapFacet, MapTheme, MapView, PointsLayer, WaysLayer};
use facett_map::Way;
const CANVAS: egui::Vec2 = egui::vec2(520.0, 400.0);
/// **The shared one-device-at-a-time lock** — `common/one_gpu.rs`, the SAME static every
/// other rendering file in this directory takes. This file used to declare its own
/// `static … Mutex<()>`; five files declared a copy of the same two lines, which is the
/// twin LAW 5 forbids — and a twinned LOCK is worse than a twinned helper, because two
/// mutexes over one device serialise nothing the moment a file reaches the device
/// through the other one's name.
#[path = "common/one_gpu.rs"]
mod one_gpu;
fn view() -> MapView {
MapView::new(LatLon::new(59.33, 18.06), 12.0, (CANVAS.x, CANVAS.y))
}
/// A handful of real roads — UNFILLED ways, so the GPU line lane is eligible for them.
fn roads() -> Vec<Way> {
let pal = facett_map::MapPalette::cartographic(false);
let tags = [("highway".to_string(), "motorway".to_string())];
(0..8_i64)
.filter_map(|i| {
let lat = 59.30 + f64::from(i as i32) * 0.004;
Way::from_lonlat_with(i + 1, &[(18.00, lat), (18.12, lat + 0.002)], &tags, &pal)
})
.collect()
}
/// A facet the GPU line lane WANTS: real unfilled ways in an enabled layer.
fn facet_with_roads() -> MapFacet {
let ways = roads();
assert!(ways.len() >= 8, "fixture: the roads must actually classify");
assert!(ways.iter().all(|w| w.fill.is_none()), "fixture: they must be LINES, or the lane declines");
MapFacet::new("lane-flag", view(), MapTheme::default())
.with_layer(Box::new(WaysLayer::new("osm-roads", ways)))
}
/// A facet the GPU line lane cannot want: **no way layer at all**, only point markers.
/// `way_lane_plan_scan` finds no eligible layer and answers `CpuOnly::NoLineWays`, so this
/// is `Cpu` whatever else is installed in the process — the CPU arm is therefore
/// independent of which test ran first, which a `renderer_installed()`-based fixture would
/// not be (that flag is a process-global atomic, set by the first install and never
/// cleared).
fn facet_points_only() -> MapFacet {
MapFacet::new("lane-flag-cpu", view(), MapTheme::default()).with_layer(Box::new(PointsLayer::new(
"hits",
vec![(18.06, 59.33), (18.08, 59.31), (18.04, 59.35)],
egui::Color32::from_rgb(220, 60, 60),
)))
}
/// A fresh real `RenderState` with `GpuMapRenderer` installed — what korp's eframe
/// creation closure does. Deliberately NOT `egui_kittest::wgpu::default_wgpu_setup()`,
/// whose selector comment reads *"Prefer software rasterizers"*: a green off that would be
/// measuring llvmpipe on a box with a 4090.
fn fresh_render_state() -> egui_wgpu::RenderState {
let setup = facett_core::render::gpu::adapter_wgpu::facett_wgpu_options().wgpu_setup;
let rs = egui_kittest::wgpu::create_render_state(setup, Default::default());
facett_map::gpu::install_renderer(&rs);
rs
}
/// The three atom labels. Their spelling lives in ONE place
/// (`facett_core::render::lane::lane_flag_atom`) and is asked for here rather than typed,
/// so this file cannot drift from what the pane emits. The kittest query scaffolding below
/// is duplicated in `facett-map3d/tests/lane_flag.rs` on purpose — a dev-only helper crate
/// is not worth minting for twelve lines, and the part that MUST NOT drift is the atom
/// spelling, which is shared.
fn atoms() -> [String; 3] {
[lane_flag_atom(LaneFlag::Gpu), lane_flag_atom(LaneFlag::Cpu), lane_flag_atom(LaneFlag::Pending)]
}
/// Assert the pane carries EXACTLY `want` and none of the other two.
///
/// "the GPU label is present" would be satisfied by a pane that paints all three, or by a
/// chip that prints every state it knows. "exactly one, and it is this one" is not.
fn assert_shows_exactly(harness: &egui_kittest::Harness<'_>, want: LaneFlag, ctx: &str) {
let wanted = lane_flag_atom(want);
let present: Vec<String> =
atoms().into_iter().filter(|a| harness.query_by_label(a.as_str()).is_some()).collect();
assert_eq!(
present,
vec![wanted.clone()],
"{ctx}: the pane must carry EXACTLY the {want:?} flag ({wanted:?}); the a11y tree \
carries {present:?}"
);
}
// ─────────────────────────────────────────────────────────────────────────────
/// **The CPU painter shows CPU.** A points-only facet gives the line lane nothing to take
/// (`CpuOnly::NoLineWays`), so the CPU painter genuinely owns the frame and the pane has to
/// say so.
///
/// RED: make `MapFacet::lane_flag` answer `Gpu` whenever the `wgpu` feature is compiled in
/// (i.e. report the BUILD instead of the frame) — the tree then carries `lane: GPU` on a
/// pane no GPU touched.
#[test]
fn the_cpu_painter_shows_cpu_on_the_pane() {
let _turn = one_gpu::hold();
let mut f = facet_points_only();
assert!(!f.way_lane().is_gpu(), "fixture: the plan must really be the CPU painter");
assert_eq!(f.lane_flag(), LaneFlag::Cpu, "…and the flag must agree with the plan");
let mut harness =
egui_kittest::Harness::builder().with_size(CANVAS).build_ui(move |ui| f.ui(ui));
harness.run_steps(2);
assert_shows_exactly(&harness, LaneFlag::Cpu, "points-only facet");
}
/// **The GPU lane shows GPU** — on the real device, after a frame has actually been
/// encoded.
///
/// Non-vacuity is the frame counter: the label is only allowed to read `GPU` because
/// `lane_frames()` really moved, so a hardwired `Gpu` arm would satisfy the tree assertion
/// and fail this one.
///
/// RED: delete the `lane_frames() > 0` arm from `MapFacet::lane_flag` and return
/// `Pending` for the GPU plan — the tree then carries `lane: —` on a pane the 4090 drew.
#[test]
fn the_gpu_lane_shows_gpu_on_the_pane() {
use egui_kittest::wgpu::WgpuTestRenderer;
let _turn = one_gpu::hold();
facett_core::render::lane::reset_for_test();
let mut f = facet_with_roads();
let mut harness = egui_kittest::Harness::builder()
.with_size(CANVAS)
.renderer(WgpuTestRenderer::from_render_state(fresh_render_state()))
.build_ui(move |ui| f.ui(ui));
// Every step is RENDERED, not just stepped: `Harness::step` only walks egui, and
// `render` is what submits the frame's paint callbacks to the device. The upload-once
// latch puts the payload on the first callback, so a `run_steps(n); render()` shape
// submits only the last frame — whose callback carries no geometry — and the lane sits
// legitimately at zero. Same reasoning as `map_facet_gpu_lane::drive`.
for _ in 0..4 {
harness.render().expect("the wgpu backend produced a frame");
harness.step();
}
// ── THE CHIP IS REALLY PAINTED, in the colours the type declares ─────────────
//
// The a11y assertions below prove the chip was LAID OUT at a real rect in a real
// pass. They cannot prove a single pixel was written — `devid::badge`'s own doc is
// about exactly the opposite failure (painted glyphs no check can see), and this is
// its mirror: an atom with no paint behind it.
//
// So the framebuffer is asked too. MEASURED off this fixture, oden 2026-08-23: the
// chip occupies y = 10..25 at x = 16, plate (10, 22, 14) with text (132, 217, 161) —
// which is `LaneFlag::Gpu.colors()` exactly. The assertion is that the GPU plate is
// present in the top-left corner and the CPU plate is NOT, so a chip painted in the
// wrong state's colours fails even though its atom would read correctly.
let img = harness.render().expect("a frame to read the chip out of");
let (gpu_fg, gpu_bg) = LaneFlag::Gpu.colors();
let (_, cpu_bg) = LaneFlag::Cpu.colors();
let corner: Vec<[u8; 4]> = (0..40)
.flat_map(|x| (0..32).map(move |y| (x, y)))
.filter(|&(x, y)| x < img.width() && y < img.height())
.map(|(x, y)| img.get_pixel(x, y).0)
.collect();
let has = |c: egui::Color32| {
let want = [c.r(), c.g(), c.b(), 255];
corner.contains(&want)
};
assert!(
has(gpu_bg),
"the GPU chip's plate {:?} is nowhere in the pane's top-left 40x32 — the a11y atom \
exists but nothing was painted behind it",
[gpu_bg.r(), gpu_bg.g(), gpu_bg.b()]
);
assert!(
!has(cpu_bg),
"the CPU chip's plate {:?} is painted in the corner of a pane the GPU lane drew",
[cpu_bg.r(), cpu_bg.g(), cpu_bg.b()]
);
// …and the TEXT ink too, so a bare rectangle cannot pass for a legible chip.
assert!(
corner.iter().any(|p| {
let d = |a: u8, b: u8| i32::from(a).abs_diff(i32::from(b));
d(p[0], gpu_fg.r()) + d(p[1], gpu_fg.g()) + d(p[2], gpu_fg.b()) < 40
}),
"the chip's plate is there but its text ink {:?} is not — an empty plate is not a flag",
[gpu_fg.r(), gpu_fg.g(), gpu_fg.b()]
);
let frames = facett_map::gpu::lane_frames();
eprintln!("LOOK: the 2D lane encoded {frames} frame(s)");
assert!(
frames > 0,
"the GPU lane encoded ZERO frames, so this pane is the CPU painter and the GPU \
claim below would be about nothing"
);
assert_shows_exactly(&harness, LaneFlag::Gpu, "roads facet on the real device");
}
/// **A lane that has not encoded a frame shows NEITHER — and specifically not CPU.**
///
/// This is the state the three-value flag exists for, and it is a real one: the plan says
/// GPU, the callback is placed, and nothing has come out of the device yet — exactly what
/// `note_lane_idle_reason` already publishes as
/// `facett_core::render::lane::AWAITING_FIRST_ENCODE`.
///
/// It is made deterministic rather than raced for: the harness is built (which installs
/// the renderer, so the plan really is the GPU route), then the lane registry is reset so
/// the encoded-frame count is genuinely zero, and then the pane is **laid out but never
/// rendered** — `step`, never `render` — so no callback can encode anything.
/// `renderer_installed()` is a separate atomic from the registry, so the reset zeroes the
/// frames without un-choosing the lane.
///
/// RED: make the `Pending` arm of `MapFacet::lane_flag` answer `Cpu`. The tree then carries
/// `lane: CPU` for a pane whose renderer has never been asked to do anything — which is
/// precisely the false verdict this whole type exists to prevent.
#[test]
fn a_lane_that_has_not_encoded_a_frame_shows_neither_gpu_nor_cpu() {
use egui_kittest::wgpu::WgpuTestRenderer;
let _turn = one_gpu::hold();
let mut f = facet_with_roads();
let mut harness = egui_kittest::Harness::builder()
.with_size(CANVAS)
.renderer(WgpuTestRenderer::from_render_state(fresh_render_state()))
.build_ui(move |ui| f.ui(ui));
// Zero the encoded-frame count AFTER the install, so the plan is the GPU route and the
// observation is honestly empty.
facett_core::render::lane::reset_for_test();
assert_eq!(facett_map::gpu::lane_frames(), 0, "fixture: the frame count is genuinely zero");
assert!(
facett_map::gpu::renderer_installed(),
"fixture: the renderer must still be installed — otherwise the plan is NoRenderer \
and this test would be measuring the CPU arm again"
);
// LAID OUT, NEVER RENDERED. `step` walks egui; only `render` submits callbacks.
harness.step();
assert_shows_exactly(&harness, LaneFlag::Pending, "GPU plan, nothing encoded");
// And say the dangerous thing explicitly, so the intent survives a future edit that
// loosens `assert_shows_exactly`.
assert!(
harness.query_by_label(lane_flag_atom(LaneFlag::Cpu).as_str()).is_none(),
"a pane whose GPU lane has simply not drawn yet must NEVER read as the CPU painter"
);
}