diff --git a/harness/experiments/scenario-label-wgpu-20260913/README.md b/harness/experiments/scenario-label-wgpu-20260913/README.md index 980320f6..a2a8371c 100644 --- a/harness/experiments/scenario-label-wgpu-20260913/README.md +++ b/harness/experiments/scenario-label-wgpu-20260913/README.md @@ -1,6 +1,16 @@ # Actual WGPU label-ink evaluator — 2026-09-13 -Status: authored, **not compiled or executed**. Source inspected at `345f7b3`, evidence checkout `2f90cf2`; the integration owner must capture exact file hashes when running. Only this new experiment directory changes. The measured-label module, corridor candidate and production renderer remain untouched. +Status: **second caller policy authored, not compiled or executed**. The first policy completed an actual Windows WGPU run with eight corridor findings; its raw evidence remains untouched. This revision changes only this harness's caller positioning/width policy and adds backplate-containment checks. The measured-label module, corridor helper and production renderer remain untouched. + +## Preserved first-policy result and second-policy question + +The parent gate `.local/scenario-label-wgpu-gates-20260913` completed 32 cases with zero invalid controls, eight findings (all italic) and 12 advance-spill cases. Every finding was `outside_corridor_x_plus_one_pixel`; every case had `outside_plate=0` and `blocker_changed_pixels=0`. The actual resolved italic face was Segoe UI Italic. The first-policy source hash was `48236A9A6FD0156A9DB4CAF7520D48F00FE8697202E036B697A03C27632CED95`; its stable executable hash was `BE5B044A974AF61E471D0A887C8DF1DCD73C5CC582AC5E8CCEBCD343D77752E2`. The gate's receipts/manifests bind the exact module and dependency inputs. Parent owns publication; these local paths describe inspected evidence, not a cross-machine handoff. + +Independent readback of narrow italic `text.rgba` confirmed the left-side violations: at zoom0.65/DPI1 the true corridor began at physical x60.8125, with one violating pixel in column59; at1/1 it began73.5, with two in column71; at1.75/1 it began100.6875, with seven across96..99; at1/1.5 it began110.25, with five across106..108. All four counts apply the unchanged channel threshold and one-physical-pixel halo. This supports testing reserved internal space; it does not prove a universal three-unit glyph overhang bound. + +**Policy 2:** keep the true world corridor at x36.25 with width160 or760, and keep camera, viewport and blocker positions unchanged. Reserve 3 world units on each horizontal side: resolve the label against width154 or754, and paint at x39.25. Grow its backplate by the existing3 horizontal/2 vertical world units. Never grow or shift the true corridor to make the result pass. All 32 strings/styles/zoom/DPI/control combinations remain; narrower displayed prefixes may legitimately change because the available advance budget decreased. + +New oracles require the constructed backplate's exact world left/right edges to remain within the true corridor, with no epsilon or clamp, and measure its actual painted yellow pixels against that corridor (strict and one-pixel-halo counts). A world containment failure or painted spill beyond the halo is a candidate finding. The first policy did not test backplate-within-corridor containment; the additional requirement is recorded explicitly instead of rewriting its historical result. Widths at or below the six-unit reserve are outside these 32 probes; no empty-corridor product behavior is selected here. ## Question and competing expectations @@ -10,7 +20,7 @@ The experiment distinguishes three claims: 1. **Advance-only hypothesis:** every painted pixel lies in the returned layout rectangle. Count strict violations and violations after a one-physical-pixel halo. Violations are evidence, not automatic evaluator failure: the module explicitly does not promise ink containment. 2. **Candidate padding hypothesis:** adding 3 horizontal / 2 vertical world units to that rectangle contains ink, with a separately reported one-physical-pixel halo. This is an unaccepted probe policy. A violation beyond the halo is a finding, not grounds to silently increase the constant. -3. **Corridor/blocker hypothesis:** ink does not extend horizontally beyond the supplied interval plus one physical pixel, and does not change the painted interiors of nearby activation-like blockers. These are fixed independent probes, not the `widest_clear` algorithm or real Scene membership. +3. **Corridor/blocker hypothesis:** ink does not extend horizontally beyond the fixed true interval plus one physical pixel, the backplate's world edges stay within that interval, its painted pixels remain within the reported halo, and text does not change painted blocker interiors. These are independent probes, not the `widest_clear` algorithm or real Scene membership. Strict counts remain visible even when the one-pixel halo passes; the halo is an explicit experimental allowance, not proof of zero overlap. Pixel difference threshold is greater than 3 in any 8-bit RGBA channel. We retain raw pixels so weaker ink and alternative tolerances can be investigated without rerendering. @@ -21,12 +31,12 @@ Strict counts remain visible even when the one-pixel halo passes; the halo is an All 32 cases are evaluated in one dedicated executable process. Global fonts initialize before a fresh renderer and `RenderSession`; no font mutation is allowed until process exit. Each frame resets renderer geometry while retaining its genuine WGPU caches. Cases cover: - A realistic existing scenario-style call label, CJK, decomposed combining marks, and an italic label beginning with `jfj`. -- Widths 160 and 760 world units. The wide case must preserve the explicit full source; the narrow case must show an ellipsis and retain a nonempty proper source prefix. +- True corridor widths160 and760 world units, giving policy2 text budgets154 and754. The wide case must preserve the explicit full source; the narrow case must show an ellipsis and retain a nonempty proper source prefix. - `(zoom, DPI)` of `(0.65,1)`, `(1,1)`, `(1.75,1)`, `(1,1.5)`. Fractional camera translation and text origins exercise subpixel positioning. The independently constructed `canvas::Text` control must paint the same pixels as the guarded payload. A second actual Frame shifts camera translation by exactly 16 physical pixels; its pixels must equal the original shifted by 16. These checks can reveal wrong coordinate assumptions before any ink conclusion. The viewport has generous right/bottom margin; any observed ink touching its boundary invalidates the probe because clipping may have hidden overhang. -Text-only pixels against white provide the ink mask. A separate decoration render contains yellow backplate then blue blocker rectangles. A composite adds actual text. Actual opaque blue interior pixels must remain unchanged. Because the blockers are painted after the backplate, this specifically evaluates foreground text interference; it does not infer that arbitrary backplate geometry never overlaps a blocker or that every antialiased border remains untouched. The independent world rectangles provide 1 world unit of clearance between a maximally wide padded plate and its blocker. +Text-only pixels against white provide the ink mask. A separate decoration render contains yellow backplate then blue blocker rectangles. A composite adds actual text. Actual opaque blue interior pixels must remain unchanged. Because the blockers are painted after the backplate, this specifically evaluates foreground text interference; it does not infer that every antialiased blocker border remains untouched. Policy2 separately tests backplate world containment and actual yellow plate pixels, including attenuated antialiased edges. Its fixed blockers begin4 world units outside the true corridor; the maximum backplate may reach the corridor boundary but cannot consume that clearance. Actual shaped glyph IDs and resolved font faces are recorded as a **coverage gate only**. Glyph ID zero, no glyphs, or no resolved italic/oblique face for the italic case invalidates that case. CJK missing-font boxes cannot count as CJK acceptance. The inspected shaping buffer does not supply the ink bounds. System fonts may differ across machines; face identities/families/styles are recorded, but this is not a portable pixel-golden suite or a complete font-file provenance recorder. Pin and hash relevant system font files for a cross-machine comparison. @@ -57,7 +67,7 @@ The successful attempt4 used Iced `ee73abd722c72024` and unicode-segmentation `8 -NewRunDirectory $freshRun ``` -The script is also unexecuted and requires PowerShell 7 (`Path.GetRelativePath`). Compile/run only after acquiring the parent's sole compiler/GPU resource. Before/after hashes do not detect a file temporarily changed and restored during linking; exclusive input ownership remains required. This is not a hermetic toolchain capture: implicitly searched system SDK libraries, clang's auxiliary executables/DLLs, runtime dependencies, system fonts and GPU drivers outside the explicit directories are not all hashed. Preserve that environment separately. The renderer exposes the backend name `wgpu`, not complete adapter identity through `Headless`; retain driver/backend logs and hardware identity separately. `WGPU_BACKEND` is recorded if set. Do not infer native acceptance from this offscreen run. +The script completed the first-policy build/run described above and requires PowerShell7 (`Path.GetRelativePath`). The second-policy Rust revision remains uncompiled/unexecuted. Select a NEW run directory; never reuse the first raw gate. Compile/run only after acquiring the parent's sole compiler/GPU resource. Before/after hashes do not detect a file temporarily changed and restored during linking; exclusive input ownership remains required. This is not a hermetic toolchain capture: implicitly searched system SDK libraries, clang's auxiliary executables/DLLs, runtime dependencies, system fonts and GPU drivers outside the explicit directories are not all hashed. Preserve that environment separately. The renderer exposes the backend name `wgpu`, not complete adapter identity through `Headless`; retain driver/backend logs and hardware identity separately. `WGPU_BACKEND` is recorded if set. Do not infer native acceptance from this offscreen run. ## Artifacts and interpretation diff --git a/harness/experiments/scenario-label-wgpu-20260913/main.rs b/harness/experiments/scenario-label-wgpu-20260913/main.rs index 195ed771..10149c7b 100644 --- a/harness/experiments/scenario-label-wgpu-20260913/main.rs +++ b/harness/experiments/scenario-label-wgpu-20260913/main.rs @@ -13,8 +13,11 @@ use scenario_label::{Cache, Label, RenderSession, Style}; use std::{collections::BTreeSet, fmt::Write as _, fs, future::Future, path::Path, sync::Arc}; const PAN: Vector = Vector::new(37.25, 23.5); +// POSITION remains the TRUE corridor origin, unchanged from empirical policy 1. const POSITION: Point = Point::new(36.25, 40.5); const PIXEL_DELTA: u8 = 3; +const HORIZONTAL_INSET: f32 = 3.; +const POLICY: &str = "2-fixed-corridor-inset-3-world-units"; struct Case { name: &'static str, @@ -24,7 +27,10 @@ struct Case { } fn cases() -> [Case; 4] { - let advanced = Style { shaping: Shaping::Advanced, ..Style::default() }; + let advanced = Style { + shaping: Shaping::Advanced, + ..Style::default() + }; [ Case { name: "realistic-default", @@ -48,7 +54,10 @@ fn cases() -> [Case; 4] { name: "italic-advanced", source: "jfj · Verify valve qualification, pressure relief and technician signoff", style: Style { - font: Font { style: iced::font::Style::Italic, ..Font::DEFAULT }, + font: Font { + style: iced::font::Style::Italic, + ..Font::DEFAULT + }, ..advanced }, require_italic: true, @@ -61,8 +70,12 @@ fn cases() -> [Case; 4] { fn block_on(future: impl Future) -> T { struct WakeThread(std::thread::Thread); impl std::task::Wake for WakeThread { - fn wake(self: Arc) { self.0.unpark(); } - fn wake_by_ref(self: &Arc) { self.0.unpark(); } + fn wake(self: Arc) { + self.0.unpark(); + } + fn wake_by_ref(self: &Arc) { + self.0.unpark(); + } } let waker = std::task::Waker::from(Arc::new(WakeThread(std::thread::current()))); let mut context = std::task::Context::from_waker(&waker); @@ -76,13 +89,19 @@ fn block_on(future: impl Future) -> T { } #[derive(Clone, Copy)] -enum Pass { Text, ManualText, Decoration, Composite } +enum Pass { + Text, + ManualText, + Decoration, + Composite, +} struct Surface { logical: Size, physical: Size, dpi: f32, zoom: f32, + text_position: Point, advance: Rectangle, plate: Rectangle, corridor: Rectangle, @@ -91,21 +110,48 @@ struct Surface { impl Surface { fn new(label: &Label, session: &RenderSession, zoom: f32, dpi: f32, width: f32) -> Self { - let advance = label.bounds_at(session, zoom, POSITION).unwrap(); + let text_position = Point::new(POSITION.x + HORIZONTAL_INSET, POSITION.y); + let advance = label.bounds_at(session, zoom, text_position).unwrap(); // Explicit EXPERIMENT candidate: 3 horizontal / 2 vertical world units. // It is not an accepted production padding or an assertion about ink. - let plate = grow(advance, 3., 2.); - let corridor = Rectangle { width, ..advance }; + let plate = grow(advance, HORIZONTAL_INSET, 2.); + // Do not derive corridor.x from the shifted text or widen the corridor + // to excuse overhang. Its original origin/width and blockers stay fixed. + let corridor = Rectangle { + x: POSITION.x, + width, + ..advance + }; let blockers = [ - Rectangle { x: POSITION.x - 12., y: POSITION.y - 12., width: 8., height: 52. }, - Rectangle { x: POSITION.x + width + 4., y: POSITION.y - 12., width: 8., height: 52. }, + Rectangle { + x: POSITION.x - 12., + y: POSITION.y - 12., + width: 8., + height: 52., + }, + Rectangle { + x: POSITION.x + width + 4., + y: POSITION.y - 12., + width: 8., + height: 52., + }, ]; let physical = Size::new( ((PAN.x + (POSITION.x + width + 70.) * zoom) * dpi).ceil() as u32, ((PAN.y + (POSITION.y + 90.) * zoom) * dpi).ceil() as u32, ); let logical = Size::new(physical.width as f32 / dpi, physical.height as f32 / dpi); - Self { logical, physical, dpi, zoom, advance, plate, corridor, blockers } + Self { + logical, + physical, + dpi, + zoom, + text_position, + advance, + plate, + corridor, + blockers, + } } fn pixels(&self, rect: Rectangle) -> Rectangle { @@ -119,7 +165,12 @@ impl Surface { } fn grow(rect: Rectangle, x: f32, y: f32) -> Rectangle { - Rectangle { x: rect.x - x, y: rect.y - y, width: rect.width + 2. * x, height: rect.height + 2. * y } + Rectangle { + x: rect.x - x, + y: rect.y - y, + width: rect.width + 2. * x, + height: rect.height + 2. * y, + } } fn render( @@ -138,11 +189,19 @@ fn render( frame.translate(Vector::new(PAN.x + pixel_shift / surface.dpi, PAN.y)); frame.scale(surface.zoom); if matches!(pass, Pass::Decoration | Pass::Composite) { - frame.fill_rectangle(surface.plate.position(), surface.plate.size(), Color::from_rgb8(255, 230, 120)); + frame.fill_rectangle( + surface.plate.position(), + surface.plate.size(), + Color::from_rgb8(255, 230, 120), + ); // Backplate precedes activation-like blockers; text remains foreground. // These are probes, not imported Scene activations or a corridor planner. for blocker in surface.blockers { - frame.fill_rectangle(blocker.position(), blocker.size(), Color::from_rgb8(0, 40, 255)); + frame.fill_rectangle( + blocker.position(), + blocker.size(), + Color::from_rgb8(0, 40, 255), + ); } } if !matches!(pass, Pass::Decoration) { @@ -151,7 +210,7 @@ fn render( // use actual Frame::fill_text; no alternate renderer is involved. canvas::Text { content: label.displayed(session, surface.zoom).unwrap().to_owned(), - position: POSITION, + position: surface.text_position, color: Color::BLACK, size: style.size, line_height: style.line_height, @@ -161,13 +220,18 @@ fn render( ..canvas::Text::default() } } else { - label.canvas_text(session, surface.zoom, POSITION, Color::BLACK).unwrap() + label + .canvas_text(session, surface.zoom, surface.text_position, Color::BLACK) + .unwrap() }; frame.fill_text(text); } renderer.draw_geometry(frame.into_geometry()); let pixels = Headless::screenshot(renderer, surface.physical, surface.dpi, Color::WHITE); - assert_eq!(pixels.len(), surface.physical.width as usize * surface.physical.height as usize * 4); + assert_eq!( + pixels.len(), + surface.physical.width as usize * surface.physical.height as usize * 4 + ); pixels } @@ -199,8 +263,13 @@ fn ink(pixels: &[u8], surface: &Surface) -> Ink { let corridor = surface.pixels(surface.corridor); let mut out = Ink::default(); for (i, rgba) in pixels.chunks_exact(4).enumerate() { - if !differs(rgba, &[255, 255, 255, 255]) { continue; } - let (x, y) = (i % surface.physical.width as usize, i / surface.physical.width as usize); + if !differs(rgba, &[255, 255, 255, 255]) { + continue; + } + let (x, y) = ( + i % surface.physical.width as usize, + i / surface.physical.width as usize, + ); out.count += 1; out.bounds = Some(match out.bounds { None => (x, y, x + 1, y + 1), @@ -211,15 +280,25 @@ fn ink(pixels: &[u8], surface: &Surface) -> Ink { out.outside_plate += usize::from(!inside(plate, x, y)); out.outside_plate_plus_one_pixel += usize::from(!inside(grow(plate, 1., 1.), x, y)); let px = x as f32 + 0.5; - out.outside_corridor_x_plus_one_pixel += usize::from(px < corridor.x - 1. || px >= corridor.x + corridor.width + 1.); - out.touches_viewport += usize::from(x == 0 || y == 0 || x + 1 == surface.physical.width as usize || y + 1 == surface.physical.height as usize); + out.outside_corridor_x_plus_one_pixel += + usize::from(px < corridor.x - 1. || px >= corridor.x + corridor.width + 1.); + out.touches_viewport += usize::from( + x == 0 + || y == 0 + || x + 1 == surface.physical.width as usize + || y + 1 == surface.physical.height as usize, + ); } out } // Independent inspection of the actual shaped glyphs is ONLY a coverage gate: // metrics from this buffer do not serve as the painted-ink oracle. -fn font_coverage(displayed: &str, style: Style, zoom: f32) -> (usize, usize, bool, BTreeSet) { +fn font_coverage( + displayed: &str, + style: Style, + zoom: f32, +) -> (usize, usize, bool, BTreeSet) { let size = Pixels(style.size.0 * zoom); let line_height = match style.line_height { LineHeight::Absolute(value) => value.0 * zoom, @@ -227,10 +306,18 @@ fn font_coverage(displayed: &str, style: Style, zoom: f32) -> (usize, usize, boo }; let mut fonts = graphics_text::font_system().write().unwrap(); let mut shapes = graphics_text::Cache::new(); - let (_, entry) = shapes.allocate(fonts.raw(), graphics_text::cache::Key { - content: displayed, size: size.0, line_height, font: style.font, - bounds: Size::INFINITE, shaping: style.shaping, align_x: Alignment::Default, - }); + let (_, entry) = shapes.allocate( + fonts.raw(), + graphics_text::cache::Key { + content: displayed, + size: size.0, + line_height, + font: style.font, + bounds: Size::INFINITE, + shaping: style.shaping, + align_x: Alignment::Default, + }, + ); let (mut glyphs, mut missing, mut italic) = (0, 0, false); let mut faces = BTreeSet::new(); for run in entry.buffer.layout_runs() { @@ -239,7 +326,10 @@ fn font_coverage(displayed: &str, style: Style, zoom: f32) -> (usize, usize, boo missing += usize::from(glyph.glyph_id == 0); if let Some(face) = fonts.raw().db().face(glyph.font_id) { italic |= face.style != graphics_text::cosmic_text::fontdb::Style::Normal; - faces.insert(format!("{:?} {:?} {:?}", glyph.font_id, face.families, face.style)); + faces.insert(format!( + "{:?} {:?} {:?}", + glyph.font_id, face.families, face.style + )); } } } @@ -250,39 +340,126 @@ fn save_pixels(dir: &Path, name: &str, pixels: &[u8], size: Size) { fs::write(dir.join(format!("{name}.rgba")), pixels).unwrap(); // Standard portable pixmap for immediate image inspection, no image crate. let mut ppm = format!("P6\n{} {}\n255\n", size.width, size.height).into_bytes(); - for pixel in pixels.chunks_exact(4) { ppm.extend_from_slice(&pixel[..3]); } + for pixel in pixels.chunks_exact(4) { + ppm.extend_from_slice(&pixel[..3]); + } fs::write(dir.join(format!("{name}.ppm")), ppm).unwrap(); } -fn evaluate(renderer: &mut iced::Renderer, session: &RenderSession, case: &Case, zoom: f32, dpi: f32, width: f32, dir: &Path) -> (usize, usize, bool) { +fn evaluate( + renderer: &mut iced::Renderer, + session: &RenderSession, + case: &Case, + zoom: f32, + dpi: f32, + width: f32, + dir: &Path, +) -> (usize, usize, bool) { fs::create_dir(dir).unwrap(); let mut cache = Cache::default(); - let label = cache.resolve(session, case.source, width, zoom, case.style).unwrap(); + let text_budget = width - 2. * HORIZONTAL_INSET; + assert!( + text_budget > 0., + "these 32 probes require a positive inset text budget" + ); + let label = cache + .resolve(session, case.source, text_budget, zoom, case.style) + .unwrap(); let displayed = label.displayed(session, zoom).unwrap(); let retained = label.retained_source(session, zoom).unwrap(); let surface = Surface::new(&label, session, zoom, dpi, width); - let text = render(renderer, &surface, &label, session, case.style, Pass::Text, 0.); - let manual = render(renderer, &surface, &label, session, case.style, Pass::ManualText, 0.); - let moved = render(renderer, &surface, &label, session, case.style, Pass::Text, 16.); - let decoration = render(renderer, &surface, &label, session, case.style, Pass::Decoration, 0.); - let composite = render(renderer, &surface, &label, session, case.style, Pass::Composite, 0.); + let text = render( + renderer, + &surface, + &label, + session, + case.style, + Pass::Text, + 0., + ); + let manual = render( + renderer, + &surface, + &label, + session, + case.style, + Pass::ManualText, + 0., + ); + let moved = render( + renderer, + &surface, + &label, + session, + case.style, + Pass::Text, + 16., + ); + let decoration = render( + renderer, + &surface, + &label, + session, + case.style, + Pass::Decoration, + 0., + ); + let composite = render( + renderer, + &surface, + &label, + session, + case.style, + Pass::Composite, + 0., + ); let measured_ink = ink(&text, &surface); let (glyphs, missing, italic, faces) = font_coverage(displayed, case.style, zoom); - let payload_mismatch = text.chunks_exact(4).zip(manual.chunks_exact(4)).filter(|(a, b)| differs(a, b)).count(); + let payload_mismatch = text + .chunks_exact(4) + .zip(manual.chunks_exact(4)) + .filter(|(a, b)| differs(a, b)) + .count(); let mut translation_mismatch = 0; let mut blocker_changed = 0; let mut blocker_samples = 0; + let plate_inside_corridor_x = surface.plate.x >= surface.corridor.x + && surface.plate.x + surface.plate.width <= surface.corridor.x + surface.corridor.width; + let corridor_pixels = surface.pixels(surface.corridor); + let mut painted_plate_samples = 0; + let mut painted_plate_outside_corridor_x = 0; + let mut painted_plate_outside_corridor_x_plus_one_pixel = 0; for (i, rgba) in moved.chunks_exact(4).enumerate() { let x = i % surface.physical.width as usize; - let expected = if x >= 16 { &text[(i - 16) * 4..(i - 16) * 4 + 4] } else { &[255, 255, 255, 255] }; + let expected = if x >= 16 { + &text[(i - 16) * 4..(i - 16) * 4 + 4] + } else { + &[255, 255, 255, 255] + }; translation_mismatch += usize::from(differs(rgba, expected)); } - for (before, after) in decoration.chunks_exact(4).zip(composite.chunks_exact(4)) { + for (i, (before, after)) in decoration + .chunks_exact(4) + .zip(composite.chunks_exact(4)) + .enumerate() + { // Sample actual opaque blue blocker interiors, not a guessed screen box. if before[0] < 8 && before[1] < 60 && before[2] > 245 { blocker_samples += 1; blocker_changed += usize::from(differs(before, after)); } + // Yellow plate over white has full red and attenuated blue. This mask + // also includes weak antialiased plate edges; blue blockers cannot match. + if before[0] > 247 && before[2] < 252 { + painted_plate_samples += 1; + let x = (i % surface.physical.width as usize) as f32 + 0.5; + painted_plate_outside_corridor_x += usize::from( + x < corridor_pixels.x || x >= corridor_pixels.x + corridor_pixels.width, + ); + painted_plate_outside_corridor_x_plus_one_pixel += usize::from( + x < corridor_pixels.x - 1. || x >= corridor_pixels.x + corridor_pixels.width + 1., + ); + } } let source_expectation = if width > 500. { displayed == case.source && retained == (0..case.source.len()) @@ -293,44 +470,78 @@ fn evaluate(renderer: &mut iced::Renderer, session: &RenderSession, case: &Case, let invalid = usize::from(!source_expectation) + usize::from(glyphs == 0 || missing > 0 || (case.require_italic && !italic)) + usize::from(measured_ink.count == 0 || measured_ink.touches_viewport > 0) - + usize::from(payload_mismatch > 0 || translation_mismatch > 0 || blocker_samples == 0); + + usize::from( + payload_mismatch > 0 + || translation_mismatch > 0 + || blocker_samples == 0 + || painted_plate_samples == 0, + ); let finding = usize::from(measured_ink.outside_plate_plus_one_pixel > 0) + usize::from(measured_ink.outside_corridor_x_plus_one_pixel > 0) - + usize::from(blocker_changed > 0); + + usize::from(blocker_changed > 0) + + usize::from(!plate_inside_corridor_x) + + usize::from(painted_plate_outside_corridor_x_plus_one_pixel > 0); let mut report = format!( - "source={:?}\ndisplayed={displayed:?}\nretained={retained:?}\nstyle={:?}\nzoom={zoom}\ndpi={dpi}\nwidth={width}\nphysical={:?}\nadvance_world={:?}\nplate_world={:?}\nblockers_world={:?}\nink={measured_ink:#?}\nglyphs={glyphs}\nmissing_glyphs={missing}\nresolved_italic={italic}\npayload_mismatch_pixels={payload_mismatch}\ntranslation_mismatch_pixels={translation_mismatch}\nblocker_samples={blocker_samples}\nblocker_changed_pixels={blocker_changed}\ninvalid_checks={invalid}\ncandidate_findings={finding}\n", + "policy={POLICY}\nsource={:?}\ndisplayed={displayed:?}\nretained={retained:?}\nstyle={:?}\nzoom={zoom}\ndpi={dpi}\nwidth={width}\ntext_budget={text_budget}\nhorizontal_inset={HORIZONTAL_INSET}\nphysical={:?}\nadvance_world={:?}\nplate_world={:?}\nblockers_world={:?}\nink={measured_ink:#?}\nglyphs={glyphs}\nmissing_glyphs={missing}\nresolved_italic={italic}\npayload_mismatch_pixels={payload_mismatch}\ntranslation_mismatch_pixels={translation_mismatch}\nblocker_samples={blocker_samples}\nblocker_changed_pixels={blocker_changed}\nplate_inside_corridor_x={plate_inside_corridor_x}\npainted_plate_samples={painted_plate_samples}\npainted_plate_outside_corridor_x={painted_plate_outside_corridor_x}\npainted_plate_outside_corridor_x_plus_one_pixel={painted_plate_outside_corridor_x_plus_one_pixel}\ninvalid_checks={invalid}\ncandidate_findings={finding}\n", case.source, case.style, surface.physical, surface.advance, surface.plate, surface.blockers, ); - for face in faces { writeln!(&mut report, "resolved_face={face}").unwrap(); } + writeln!( + &mut report, + "corridor_world={:?}\ntext_position={:?}", + surface.corridor, surface.text_position + ) + .unwrap(); + for face in faces { + writeln!(&mut report, "resolved_face={face}").unwrap(); + } fs::write(dir.join("metrics.txt"), report).unwrap(); - for (name, pixels) in [("text", &text), ("decoration", &decoration), ("composite", &composite)] { + for (name, pixels) in [ + ("text", &text), + ("decoration", &decoration), + ("composite", &composite), + ] { save_pixels(dir, name, pixels, surface.physical); } - if payload_mismatch > 0 { save_pixels(dir, "manual-control", &manual, surface.physical); } - if translation_mismatch > 0 { save_pixels(dir, "shift-control", &moved, surface.physical); } + if payload_mismatch > 0 { + save_pixels(dir, "manual-control", &manual, surface.physical); + } + if translation_mismatch > 0 { + save_pixels(dir, "shift-control", &moved, surface.physical); + } (invalid, finding, measured_ink.outside_advance > 0) } fn main() { let args: Vec<_> = std::env::args_os().collect(); - assert_eq!(args.len(), 2, "usage: scenario-label-wgpu.exe NEW_OUTPUT_DIRECTORY"); + assert_eq!( + args.len(), + 2, + "usage: scenario-label-wgpu.exe NEW_OUTPUT_DIRECTORY" + ); let out = Path::new(&args[1]); fs::create_dir(out).expect("output directory must be new; preserve prior runs"); // Initialize the system font database before the new renderer. No fonts are // loaded after this point, through all screenshots and coverage inspection. let version = graphics_text::font_system().read().unwrap().version(); - let mut renderer = block_on(::new(Font::DEFAULT, Pixels(16.), Some("wgpu"))) - .expect("WGPU adapter/device unavailable: infrastructure failure, never a skipped pass"); + let mut renderer = block_on(::new( + Font::DEFAULT, + Pixels(16.), + Some("wgpu"), + )) + .expect("WGPU adapter/device unavailable: infrastructure failure, never a skipped pass"); assert_eq!(Headless::name(&renderer), "wgpu"); let session = RenderSession::after_fonts_settled_and_renderer_reset().unwrap(); - let mut summary = format!("font_version={version:?}\nrenderer=wgpu\nchannel_delta_threshold={PIXEL_DELTA}\nimported_module=crates/oa-desktop/src/behavior/scenario_label.rs\n"); + let mut summary = format!( + "policy={POLICY}\nhorizontal_inset={HORIZONTAL_INSET}\nfont_version={version:?}\nrenderer=wgpu\nchannel_delta_threshold={PIXEL_DELTA}\nimported_module=crates/oa-desktop/src/behavior/scenario_label.rs\n" + ); let (mut cases_run, mut invalid, mut findings, mut advance_spill_cases) = (0, 0, 0, 0); for case in cases() { for (zoom, dpi) in [(0.65, 1.), (1., 1.), (1.75, 1.), (1., 1.5)] { for width in [160., 760.] { let name = format!("{}-z{zoom}-dpi{dpi}-w{width}", case.name); let dir = out.join(&name); - let (bad, found, advance_spill) = evaluate(&mut renderer, &session, &case, zoom, dpi, width, &dir); + let (bad, found, advance_spill) = + evaluate(&mut renderer, &session, &case, zoom, dpi, width, &dir); // Summary is only an index; full numeric ink fields are per case. advance_spill_cases += usize::from(advance_spill); writeln!(&mut summary, "{name}: invalid={bad} findings={found}").unwrap(); @@ -345,6 +556,10 @@ fn main() { summary.push_str("Advance spill is measured evidence, not automatically a failure. Candidate findings reject this probe's padding/corridor assumptions. This is not Scene/native acceptance.\n"); fs::write(out.join("summary.txt"), &summary).unwrap(); print!("{summary}"); - if invalid > 0 { std::process::exit(3); } - if findings > 0 { std::process::exit(2); } + if invalid > 0 { + std::process::exit(3); + } + if findings > 0 { + std::process::exit(2); + } }