From b9a9921d50c58f1bfa2c50eead978703771f1082 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Fri, 3 Jul 2026 10:39:05 -0400 Subject: [PATCH] Phase 3 tranche 2: quality conformance companions, real metrics, multi-system click fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 857 workspace tests pass; clippy -D warnings, fmt, and rustdoc clean; both new companions build with zero undefined references. Quality Metric Catalog v0.1.0 (spec/quality_metric_catalog.tex, new): - Formal definitions for all nine normative quality metrics, each with a raw measurement over resolved geometry and a clamped-linear normalization n = min(1, raw/R_worst) with pinned anchors. - The vacuous-geometry rule (a metric over absent geometry evaluates to 0.0; the notated-but-unrendered honesty edge is an open question), all-1.0 default tie-breaking weights, and the per-tier threshold table — Minimal's uniform 0.90 deliberately fails the all-worst placeholder, forcing real measurement. - Pins QualityMetricKind (referenced but never defined by the core spec) and the registered SolverProfile catalog (Draft selects the Minimal threshold column; Standard/Publication select Standard). - QualityFloorApproached fires at 0.8x the applicable threshold and is status-neutral by requirement. Reference Suite v0.1.0 (spec/reference_suite.tex, new): - Six entries referenced by deterministic builder + seed (RS-1 ten_measure_single_staff, RS-2 valid_score_rich, RS-3..6 corpus fixtures), each with the declared A4-at-8mm-staff solve geometry (Canvas.layout_defaults has no graph home yet, P12-I7). - All entries required at Minimal; the same set is the pre-declared Standard bar (no implementation claims Standard yet). Fixed- expectation tests deliberately unused in v0.1. Real metrics in the engraver (engrave/src/quality.rs, new; layout-ir/src/quality.rs = the catalog constants transcribed): - QualityMetricVector::unmeasured() replaced with computed values: collision sweep with the catalog's same-slot-cluster and stroke exclusions, per-system spacing CV, vertical gap deviations, system-break slack, page fill, casting-off width CV, symbol density; slur/beam vacuously 0.0 (no drawn geometry exists). - Bit-identical across repeated solves (tested); floor warnings never change solve status; malformed inputs keep unmeasured(). The two all-worst test pins now assert real values; the StubSolver's unmeasured() stays (Stub genuinely computes nothing). Reference-suite harness (testkit reference_suite module + tests): - Each RS entry asserts the four-condition Minimal pass (hard constraints, byte/bit determinism, well-formed Minimal report, every axis within threshold) under the F1 Pass/Xfail discipline, with the measured table printed per run. - HONEST FINDING, day one: RS-1 fails Minimal casting-off (measured 1.0 vs 0.90) — greedy first-fit leaves a two-measure stub last system (width CV 0.6145). Encoded as an asserted Xfail row (fails on XPASS) and filed as P12-I11 (engrave balance pass, or catalog revision). P12-I12: the Standard spacing floor warns on short scores with wide lead measures. Multi-system click-to-insert fix (editor-core): - Casting-off exposed two inversion breaks: position_anchors fed a non-monotonic cross-system anchor list into a monotonic inverter (system-2 clicks resolved to system-1 times), and nearest_manifestation found only system 1's staff-line segment (system-2 clicks got system-1 pitch geometry). - Fixed with a containing-system lookup over the resolved pages tree (containment, else nearest by vertical distance), per-system staff resolution, and per-system anchor filtering; degenerate-geometry fallback preserves the flat path, so all 84 pre-existing editor-core tests pass unmodified. - Five regression tests through the real Engraver over the wrapped ten-measure fixture, each shown to fail without the fix; testkit gains dev-only dependencies on editor-core and engrave. Trackers: P12-I11/I12 filed; DECISIONS entries in engrave, layout-ir, and testkit; Phase-3 memory updated. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd --- Cargo.lock | 2 + crates/epiphany-editor-core/src/lib.rs | 492 ++++++- crates/epiphany-engrave/DECISIONS.md | 112 +- crates/epiphany-engrave/src/casting.rs | 10 + crates/epiphany-engrave/src/lib.rs | 153 +- crates/epiphany-engrave/src/quality.rs | 650 ++++++++ crates/epiphany-layout-ir/DECISIONS.md | 24 + crates/epiphany-layout-ir/src/lib.rs | 5 + crates/epiphany-layout-ir/src/quality.rs | 341 +++++ crates/epiphany-layout-ir/src/solver.rs | 28 +- crates/epiphany-testkit/Cargo.toml | 12 +- crates/epiphany-testkit/DECISIONS.md | 50 + crates/epiphany-testkit/src/lib.rs | 6 + .../epiphany-testkit/src/reference_suite.rs | 335 +++++ .../tests/multisystem_click.rs | 255 ++++ .../epiphany-testkit/tests/reference_suite.rs | 113 ++ spec/PASS12_BATCH.md | 5 + spec/quality_metric_catalog.pdf | Bin 0 -> 138999 bytes spec/quality_metric_catalog.tex | 1308 +++++++++++++++++ spec/reference_suite.pdf | Bin 0 -> 77667 bytes spec/reference_suite.tex | 756 ++++++++++ 21 files changed, 4586 insertions(+), 71 deletions(-) create mode 100644 crates/epiphany-engrave/src/quality.rs create mode 100644 crates/epiphany-layout-ir/src/quality.rs create mode 100644 crates/epiphany-testkit/src/reference_suite.rs create mode 100644 crates/epiphany-testkit/tests/multisystem_click.rs create mode 100644 crates/epiphany-testkit/tests/reference_suite.rs create mode 100644 spec/quality_metric_catalog.pdf create mode 100644 spec/quality_metric_catalog.tex create mode 100644 spec/reference_suite.pdf create mode 100644 spec/reference_suite.tex diff --git a/Cargo.lock b/Cargo.lock index 49ebbd0..cb20006 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1280,6 +1280,8 @@ dependencies = [ "epiphany-bundle", "epiphany-core", "epiphany-determinism", + "epiphany-editor-core", + "epiphany-engrave", "epiphany-layout-ir", "epiphany-ops", ] diff --git a/crates/epiphany-editor-core/src/lib.rs b/crates/epiphany-editor-core/src/lib.rs index db320df..e2c9f46 100644 --- a/crates/epiphany-editor-core/src/lib.rs +++ b/crates/epiphany-editor-core/src/lib.rs @@ -65,7 +65,7 @@ use epiphany_core::{ use epiphany_layout_ir::{ active_clef, manifestation_layout_id, staff_step_pitch, to_constrained, to_logical, to_render, ConstraintSolver, ExtensionRef, HitTestMap, LayoutContent, LayoutObjectId, LogicalLayoutIR, - Point, RenderIR, ResolvedLayoutIR, SolverConfig, TimePoint, + Point, Rect, RenderIR, ResolvedLayoutIR, ResolvedSystem, SolverConfig, TimePoint, }; use epiphany_ops::{ advisory_violations, AcceptOutcome, AuthorId, CausalContext, DeleteEventOp, @@ -543,14 +543,23 @@ impl EditorSession { }) } - /// The manifested staff a world `point` is nearest, by 2D proximity — its - /// `(region, staff instance)` and the staff's step-origin `y`. Horizontal span - /// first (which region — one staff tiles across regions that can share a y band), - /// then the vertical band (which staff within it). The bottom staff line carries - /// the staff's manifestation id as its stroke `stable_id`, which is how a rendered - /// line maps back to `(region, staff_instance)`. Both halves of click-to-insert — - /// [`Self::staff_pitch_at`] (pitch) and [`Self::position_at`] (position) — select - /// the staff/region through this. `None` on a non-finite point or no staff line. + /// The manifested staff a world `point` is nearest — its `(region, staff + /// instance)` and the staff's step-origin `y`. Both halves of click-to-insert — + /// [`Self::staff_pitch_at`] (pitch) and [`Self::position_at`] (position) — + /// select the staff/region through this. `None` on a non-finite point or no + /// staff line. + /// + /// When the layout carries real cast-off page geometry, the staff is resolved + /// *within the system under the click* ([`Self::system_manifestation`]): + /// casting-off splits a staff's lines per system, and only the first segment + /// keeps the manifestation `stable_id`, so the flat scan below would always + /// answer with system 1's origin/span. Without cast geometry (a solver that + /// does not cast off, e.g. the stub), the flat scan is the whole story: + /// horizontal span first (which region — one staff tiles across regions that + /// can share a y band), then the vertical band (which staff within it). The + /// bottom staff line carries the staff's manifestation id as its stroke + /// `stable_id`, which is how a rendered line maps back to + /// `(region, staff_instance)`. fn nearest_manifestation(&self, point: Point) -> Option<(RegionId, &StaffInstance, f32)> { // Reject a non-finite click up front: `dist_to_band`'s `<`/`>` would let a // NaN fall through as distance 0 (matching every staff), and downstream a @@ -559,8 +568,9 @@ impl EditorSession { if !point.x.0.is_finite() || !point.y.0.is_finite() { return None; } - // A 5-line staff spans four staff spaces above its bottom line. - const STAFF_SPAN: f32 = 4.0; + if let Some(found) = self.system_manifestation(point) { + return Some(found); + } let mut best: Option<(RegionId, &StaffInstance, f32)> = None; let mut best_dist = (f32::INFINITY, f32::INFINITY); for (region, si) in self.score.staff_instances() { @@ -592,14 +602,114 @@ impl EditorSession { best } + /// The manifested staff under `point`, resolved through the cast-off page tree + /// — the multi-system path of [`Self::nearest_manifestation`]. Finds the system + /// under the click ([`Self::containing_system`]), reads the region it manifests + /// from its provenance, picks the vertically nearest staff band among the + /// system's staff records, and recovers that staff's step origin **in this + /// system**. `None` when no system carries real geometry (the caller then falls + /// back to the flat stroke scan), or when the containing system carries no + /// usable staff record. + fn system_manifestation(&self, point: Point) -> Option<(RegionId, &StaffInstance, f32)> { + let system = self.containing_system(point)?; + // A system manifests one region, and carries it as its provenance source + // whether it is the region's first system (the region's own provenance) or + // a later one (synthesized under `EngravedBreak` *from the region*) — read + // the identity from the data rather than assuming which system this is. + let TypedObjectId::Region(region) = system.provenance.source else { + return None; + }; + // The nearest staff band vertically: within one system the region is fixed, + // and its staves are stacked in disjoint y bands, so — unlike the flat + // scan, where x picks the region first — the vertical distance alone is + // the discriminator. + let staff = system + .staves + .iter() + .filter(|s| rect_is_real(&s.bounding_box)) + .min_by(|a, b| { + let da = dist_to_band(point.y.0, rect_y_band(&a.bounding_box)); + let db = dist_to_band(point.y.0, rect_y_band(&b.bounding_box)); + da.total_cmp(&db) + })?; + // The staff record's provenance is its bottom-most rendered line *in this + // system* (`build_system` in the engraver's casting pass); that stroke's + // height is the exact step origin the pitch math expects. Fall back to + // deriving it from the staff's box, whose vertical extent is the 5-line + // span padded by the line half-thickness on both sides — the bottom line + // sits half the (span + padding) height above the box bottom, minus half + // the span. + let origin = self + .resolved + .strokes + .iter() + .find(|s| s.provenance.stable_id == staff.provenance.stable_id) + .map(|s| s.from.y.0) + .unwrap_or_else(|| { + let b = &staff.bounding_box; + b.origin.y.0 + b.size.height.0 / 2.0 - STAFF_SPAN / 2.0 + }); + let si = self + .score + .staff_instances() + .find(|(r, si)| *r == region && si.staff == staff.staff) + .map(|(_, si)| si)?; + Some((region, si, origin)) + } + + /// The cast-off system whose bounding box contains `point`, or — when the + /// point is in the gutter between systems — the **nearest system by vertical + /// distance**: systems on a page all start at the left margin, so they overlap + /// in x and are disjoint in y, making the y band the discriminator (and a + /// click slightly above/below a system still resolves, mirroring the flat + /// path's nearest-staff tolerance). Only a system with real (non-degenerate) + /// geometry is a candidate: a solver that does not cast off (the stub) emits + /// zero-size boxes, and those must not capture clicks — `None` sends the + /// caller down the flat single-system path unchanged. + fn containing_system(&self, point: Point) -> Option<&ResolvedSystem> { + if !point.x.0.is_finite() || !point.y.0.is_finite() { + return None; + } + let mut nearest: Option<&ResolvedSystem> = None; + let mut nearest_dy = f32::INFINITY; + for system in self.resolved.pages.iter().flat_map(|p| p.systems.iter()) { + let bounds = &system.bounding_box; + if !rect_is_real(bounds) { + continue; + } + if rect_contains(bounds, point) { + return Some(system); + } + let dy = dist_to_band(point.y.0, rect_y_band(bounds)); + // Strict `<`: on a tie, the earlier system in page/reading order wins + // (deterministic, and the gutter midpoint resolves upward). + if dy < nearest_dy { + nearest_dy = dy; + nearest = Some(system); + } + } + nearest + } + /// The musical position a world `point` snaps to on the beat grid — the /// **horizontal half** of a click-to-insert. Finds the metric region under the /// cursor, inverts the click's `x` to a raw musical position (piecewise-linear /// through the region's rendered event anchors), then snaps it to `grid`. `None` /// if the click is off any staff, the region is non-metric (a proportional or /// aleatoric region has no musical position to land on), `grid` is non-positive, - /// or the region has fewer than two rendered metric events to fix a scale from. + /// or there are fewer than two rendered metric events to fix a scale from. /// The vertical half (the pitch) is [`Self::staff_pitch_at`]. + /// + /// In a cast-off multi-system layout the inverse works **within the system + /// under the click**: each system restarts at the page's left margin, so one x + /// names a different time on each system. A click right of a system's last + /// anchor extrapolates that system's end segment (the empty staff after its + /// last note — the same end-extrapolation as the flat layout, and it may name + /// a time that *renders* on the next system: the result is a musical position, + /// not a system-local one); a click left of its first anchor extrapolates + /// backward and clamps at the region origin; and a system rendering fewer than + /// two of the region's anchors yields `None`, the per-system reading of the + /// two-anchor rule above. pub fn position_at(&self, point: Point, grid: &GridResolution) -> Option { if !grid.step.is_positive() { return None; @@ -610,9 +720,16 @@ impl EditorSession { if !self.region_is_metric(region) { return None; } + // Constrain the anchors to the system under the click: casting-off bakes + // every system back to the left margin, so the region-wide anchor list is + // x-non-monotonic in time, and inverting through it would map a later + // system's click onto the first system's times. Without cast geometry + // (`containing_system` is `None` — the stub) the whole region is one flat + // monotonic run, unchanged. + let system_box = self.containing_system(point).map(|s| s.bounding_box); // Two anchors fix the x→time scale; with fewer, the spacing density is // unknown, so there is nothing to extrapolate an empty-space position from. - let anchors = self.position_anchors(region); + let anchors = self.position_anchors(region, system_box.as_ref()); if anchors.len() < 2 { return None; } @@ -672,7 +789,17 @@ impl EditorSession { /// in ascending time order — the samples the horizontal inverse interpolates. A /// glyph maps to its onset through its `Pitch`/`Event` provenance source; the /// leftmost glyph at an onset (the notehead/stem column) fixes that onset's x. - fn position_anchors(&self, region: RegionId) -> Vec<(MusicalPosition, f32)> { + /// + /// With `within` (a cast-off system's bounding box), only glyphs positioned + /// inside that box are sampled: casting-off restarts every system at the left + /// margin, so the region-wide list is x-non-monotonic in time, and the inverse + /// must see a single system's monotonic run. `None` samples the whole region — + /// the flat single-system behavior. + fn position_anchors( + &self, + region: RegionId, + within: Option<&Rect>, + ) -> Vec<(MusicalPosition, f32)> { // Source id (event or one of its pitches) → the event's metric onset. let mut onset: HashMap = HashMap::new(); let mut pitches: Vec<&IdentifiedPitch> = Vec::new(); @@ -705,6 +832,11 @@ impl EditorSession { if glyph.provenance.synthesis.is_some() { continue; } + // Constrain to the requested system's box: a glyph on another system + // must not contribute an anchor to this system's monotonic run. + if within.is_some_and(|bounds| !rect_contains(bounds, glyph.position)) { + continue; + } if let Some(at) = onset.get(&glyph.provenance.source) { let x = glyph.position.x.0; by_onset @@ -1995,6 +2127,9 @@ fn staff_step(pitch: &Pitch, steps: i32) -> Option { Some(moved) } +/// A 5-line staff spans four staff spaces above its bottom line. +const STAFF_SPAN: f32 = 4.0; + /// The distance from height `y` to a staff's line band `(bottom, top)`: zero inside /// the band, else the gap to the nearer edge. Used to pick the staff a click is over. fn dist_to_band(y: f32, (bottom, top): (f32, f32)) -> f32 { @@ -2007,6 +2142,35 @@ fn dist_to_band(y: f32, (bottom, top): (f32, f32)) -> f32 { } } +/// Whether a resolved bounding box carries **real** cast-off geometry: finite +/// origin and strictly positive extent on both axes. A solver that does not cast +/// off (the stub) emits `Rect::default()` — zero-size — boxes, which must not +/// capture clicks; the callers fall back to the flat single-system paths instead. +fn rect_is_real(rect: &Rect) -> bool { + let width = rect.size.width.0; + let height = rect.size.height.0; + rect.origin.x.0.is_finite() + && rect.origin.y.0.is_finite() + && width.is_finite() + && height.is_finite() + && width > 0.0 + && height > 0.0 +} + +/// A rect's vertical band as `(bottom, top)`, the shape [`dist_to_band`] takes. +fn rect_y_band(rect: &Rect) -> (f32, f32) { + (rect.origin.y.0, rect.origin.y.0 + rect.size.height.0) +} + +/// Whether `point` lies within `rect`, edges included (a glyph exactly on a +/// system's edge belongs to that system). +fn rect_contains(rect: &Rect, point: Point) -> bool { + point.x.0 >= rect.origin.x.0 + && point.x.0 <= rect.origin.x.0 + rect.size.width.0 + && point.y.0 >= rect.origin.y.0 + && point.y.0 <= rect.origin.y.0 + rect.size.height.0 +} + /// Inverts an `x` coordinate to a raw musical position through `(onset, x)` anchors /// in ascending order (`>= 2`, leftmost first) — the horizontal inverse before grid /// snapping. Within the anchored span it interpolates the bracketing segment; outside @@ -2734,6 +2898,13 @@ mod tests { } /// `region`'s rendered bottom staff line as `(left_x, right_x, origin_y)`. + /// + /// **Flat-layout (stub) helper**: it finds the stroke carrying the staff's + /// manifestation id, which in a cast-off layout is only the *first* system's + /// segment. Every test here runs on the [`StubSolver`], which never splits a + /// line, so the first segment is the whole line; multi-system geometry is + /// exercised via [`install_two_system_geometry`] and, over the real engraver, + /// by the testkit's `multisystem_click` integration test. fn region_staff_line(session: &EditorSession, region: RegionId) -> (f32, f32, f32) { let (_, si) = session .score() @@ -2765,7 +2936,7 @@ mod tests { fn position_at_snaps_a_click_to_the_beat_grid() { let session = open_rich(0x5EED); let region = a_region_with(&session, true); - let anchors = session.position_anchors(region); + let anchors = session.position_anchors(region, None); assert!( anchors.len() >= 2, "the metric region renders multiple notes" @@ -2861,7 +3032,7 @@ mod tests { // The onset's anchor must be the notehead x, not the (leftmost) accidental // — the exact check, independent of how coarse the grid is. let anchor_x = session - .position_anchors(region) + .position_anchors(region, None) .into_iter() .find(|(o, _)| o == onset) .map(|(_, x)| x) @@ -2916,6 +3087,293 @@ mod tests { assert_eq!(session.position_at(at, &zero), None); } + /// Where [`install_two_system_geometry`] puts each system's staff bottom line + /// (the step origin), in world y: system 1 on top, system 2 below it. + const SYS1_ORIGIN_Y: f32 = 0.0; + const SYS2_ORIGIN_Y: f32 = -20.0; + + /// Overwrites `session`'s resolved geometry with a hand-built **two-system + /// cast-off layout** over its single metric region — the shape the real + /// engraver produces and the stub never does. The first half of the region's + /// onsets renders on system 1, the rest on system 2; both systems start at the + /// same left margin (x restarts, so the region-wide anchor list is + /// x-non-monotonic in time) and sit in disjoint y bands. Each system carries a + /// staff record whose provenance is its own bottom-line stroke — system 1 the + /// staff's manifestation provenance, system 2 a synthesized continuation — + /// exactly as the engraver's casting pass writes them. Only the resolved + /// geometry is replaced (render/hit-test stay the stub's): these tests + /// exercise the resolved-geometry queries alone. + /// + /// Returns the region, each event as `(onset, anchor x, system index)` in + /// onset order, and the two system bounding boxes. + fn install_two_system_geometry( + session: &mut EditorSession, + ) -> (RegionId, Vec<(MusicalPosition, f32, usize)>, Rect, Rect) { + use epiphany_layout_ir::{ + BoundingBox, GlyphReference, GlyphStyle, Margins, Provenance, ResolvedGlyph, + ResolvedPage, ResolvedStaff, Size2D, StaffSpace, Stroke, SynthesisInstanceKey, + SynthesisKind, + }; + + let region = a_region_with(session, true); + let staff = session + .score() + .staff_instances() + .find(|(r, _)| *r == region) + .map(|(_, si)| si.staff) + .expect("the metric region has a staff instance"); + let events = region_pitched_events(session, region); + assert!( + events.len() >= 4, + "four onsets give each system two anchors to fix a scale" + ); + assert!( + events.windows(2).all(|w| w[0].0 < w[1].0), + "onsets are strictly ascending (distinct)" + ); + let half = events.len() / 2; + + let staff_source = TypedObjectId::Staff(staff); + // System 1 keeps the staff's manifestation provenance; system 2's line is + // an engraver-synthesized continuation with its own stable id — the split + // casting-off performs on a system-spanning stroke. + let line_provenance = [ + Provenance::manifested(staff_source, region, vec![]), + Provenance::synthesized( + staff_source, + SynthesisKind::EngravedBreak, + SynthesisInstanceKey(1), + vec![], + ), + ]; + let origins = [SYS1_ORIGIN_Y, SYS2_ORIGIN_Y]; + + let systems: Vec = origins + .iter() + .zip(&line_provenance) + .enumerate() + .map(|(s, (&origin, provenance))| ResolvedSystem { + provenance: if s == 0 { + Provenance::projected(TypedObjectId::Region(region), vec![]) + } else { + Provenance::synthesized( + TypedObjectId::Region(region), + SynthesisKind::EngravedBreak, + SynthesisInstanceKey(2), + vec![], + ) + }, + bounding_box: Rect { + origin: Point::new(0.0, origin - 2.0), + size: Size2D { + width: StaffSpace(90.0), + height: StaffSpace(STAFF_SPAN + 4.0), + }, + }, + staves: vec![ResolvedStaff { + provenance: provenance.clone(), + staff, + bounding_box: Rect { + origin: Point::new(0.0, origin - 0.05), + size: Size2D { + width: StaffSpace(88.0), + height: StaffSpace(STAFF_SPAN + 0.1), + }, + }, + }], + measures: Vec::new(), + }) + .collect(); + let strokes: Vec = origins + .iter() + .zip(&line_provenance) + .map(|(&y, provenance)| Stroke { + provenance: provenance.clone(), + from: Point::new(0.0, y), + to: Point::new(88.0, y), + thickness: StaffSpace(0.1), + layer: 0, + style: GlyphStyle::default(), + }) + .collect(); + + let mut placed: Vec<(MusicalPosition, f32, usize)> = Vec::new(); + let glyphs: Vec = events + .iter() + .enumerate() + .map(|(i, (onset, pid))| { + let system = usize::from(i >= half); + let local = if system == 0 { i } else { i - half }; + // 20 staff spaces per quarter, both systems restarting at x = 10. + let x = 10.0 + 20.0 * local as f32; + placed.push((onset.clone(), x, system)); + ResolvedGlyph { + provenance: Provenance::manifested(TypedObjectId::Pitch(*pid), region, vec![]), + glyph: GlyphReference::borrowed("noteheadBlack"), + position: Point::new(x, origins[system] + 1.0), + transform: None, + bounding_box: BoundingBox::new(0.0, -0.5, 1.2, 0.5), + style: GlyphStyle::default(), + layer: 0, + } + }) + .collect(); + + let (sys1_box, sys2_box) = (systems[0].bounding_box, systems[1].bounding_box); + session.resolved.pages = vec![ResolvedPage { + provenance: Provenance::projected(TypedObjectId::Region(region), vec![]), + number: 1, + size: Size2D::default(), + margins: Margins::default(), + systems, + free_objects: Vec::new(), + }]; + session.resolved.glyphs = glyphs; + session.resolved.strokes = strokes; + (region, placed, sys1_box, sys2_box) + } + + #[test] + fn containing_system_requires_real_cast_geometry() { + // The stub's page tree carries only degenerate (zero-size) system boxes: + // no system may capture a click, and the flat single-system path stays in + // charge — which is what keeps every pre-casting behavior unchanged. + let session = open_plain(1); + assert!( + !session.resolved().pages.is_empty(), + "the stub emits a page tree" + ); + assert!(session.containing_system(Point::new(1.0, 0.0)).is_none()); + let region = a_region_with(&session, true); + let at = point_on_region_staff(&session, region); + assert!( + session.staff_pitch_at(at).is_some(), + "the flat path still resolves the click" + ); + } + + #[test] + fn staff_pitch_at_reads_the_clicked_system_origin() { + let mut session = open_plain(1); + let (_region, _placed, sys1, sys2) = install_two_system_geometry(&mut session); + + // Same staff-relative height, one click per system: the pitch must match — + // system 2's step origin is its own bottom line, not system 1's. (The + // regression: only the first line segment keeps the manifestation stable + // id, so the flat path read every system-2 click against system 1's + // origin, ~20 staff spaces off.) + let p1 = session + .staff_pitch_at(Point::new(30.0, SYS1_ORIGIN_Y + 1.0)) + .expect("a staff under the system-1 click"); + let p2 = session + .staff_pitch_at(Point::new(30.0, SYS2_ORIGIN_Y + 1.0)) + .expect("a staff under the system-2 click"); + assert_eq!( + p1.staff_instance, p2.staff_instance, + "one staff, two systems" + ); + assert_eq!( + (p2.nominal, p2.octave), + (p1.nominal, p1.octave), + "the same staff-relative height names the same pitch in either system" + ); + + // The containing system is keyed on the click's y — full containment first… + let in_sys2 = session + .containing_system(Point::new(30.0, SYS2_ORIGIN_Y + 1.0)) + .expect("system 2 contains the point"); + assert_eq!(in_sys2.bounding_box, sys2); + // …and a click in the inter-system gutter resolves to the nearest system + // by vertical distance (mirroring the nearest-staff tolerance), never to + // nothing. + let just_under_sys1 = Point::new(30.0, rect_y_band(&sys1).0 - 1.0); + assert_eq!( + session + .containing_system(just_under_sys1) + .expect("the gutter still resolves") + .bounding_box, + sys1 + ); + let just_over_sys2 = Point::new(30.0, rect_y_band(&sys2).1 + 1.0); + assert_eq!( + session + .containing_system(just_over_sys2) + .expect("the gutter still resolves") + .bounding_box, + sys2 + ); + } + + #[test] + fn position_at_inverts_within_the_clicked_system() { + let mut session = open_plain(1); + let (region, placed, _sys1, sys2) = install_two_system_geometry(&mut session); + let half = placed.iter().filter(|(_, _, s)| *s == 0).count(); + // The fixture's onsets are consecutive quarters, so a quarter grid puts + // every rendered onset on the grid. + let quarter = grid(1, 4); + let step = MusicalDuration(RationalTime::new(1, 4).unwrap()); + + // Every anchor click snaps to its own onset — in both systems. + for (onset, x, system) in &placed { + let y = if *system == 0 { + SYS1_ORIGIN_Y + } else { + SYS2_ORIGIN_Y + } + 1.0; + let gp = session + .position_at(Point::new(*x, y), &quarter) + .expect("a metric position under the click"); + assert_eq!( + &gp.position, onset, + "the click snaps to the clicked system's onset" + ); + } + // The regression pinned directly: system 2's first anchor shares its x + // with system 1's first anchor but is a *later* time. + let (first_sys2_onset, x0, _) = placed[half].clone(); + let gp = session + .position_at(Point::new(x0, SYS2_ORIGIN_Y + 1.0), &quarter) + .expect("a metric position under the click"); + assert_eq!(gp.position, first_sys2_onset); + assert!( + gp.position > placed[0].0, + "a system-2 click is not a system-1 time" + ); + + // Anchor filtering: within system 2's box the run is monotonic in x and + // carries exactly the second half of the onsets; the unfiltered + // region-wide list is x-non-monotonic (the hazard the filter removes). + let filtered = session.position_anchors(region, Some(&sys2)); + assert_eq!(filtered.len(), placed.len() - half); + assert!(filtered.windows(2).all(|w| w[0].1 < w[1].1)); + assert_eq!(filtered[0].0, first_sys2_onset); + let flat = session.position_anchors(region, None); + assert_eq!(flat.len(), placed.len()); + assert!( + !flat.windows(2).all(|w| w[0].1 < w[1].1), + "the region-wide anchor list is x-non-monotonic across systems" + ); + + // End extrapolation stays within the clicked system: one anchor gap right + // of a system's last note is that system's next grid slot. For system 1 + // that names the time system 2 renders first — the result is a musical + // position, not a system-local one. + let (last_onset, last_x, _) = placed.last().cloned().unwrap(); + let past = session + .position_at(Point::new(last_x + 20.0, SYS2_ORIGIN_Y + 1.0), &quarter) + .expect("empty space past the last note still resolves"); + assert_eq!(past.position, last_onset + step.clone()); + let (sys1_last_onset, sys1_last_x, _) = placed[half - 1].clone(); + let hang = session + .position_at( + Point::new(sys1_last_x + 20.0, SYS1_ORIGIN_Y + 1.0), + &quarter, + ) + .expect("system 1's trailing space still resolves"); + assert_eq!(hang.position, sys1_last_onset + step); + } + #[test] fn position_at_rejects_non_finite_clicks() { let session = open_rich(0x5EED); @@ -3088,7 +3546,7 @@ mod tests { position: &MusicalPosition, y: f32, ) -> Point { - let anchors = session.position_anchors(region); + let anchors = session.position_anchors(region, None); let (p0, x0) = (anchors[0].0 .0.to_f64(), anchors[0].1 as f64); let last = anchors.last().unwrap(); let (p1, x1) = (last.0 .0.to_f64(), last.1 as f64); diff --git a/crates/epiphany-engrave/DECISIONS.md b/crates/epiphany-engrave/DECISIONS.md index e48e4f3..a016161 100644 --- a/crates/epiphany-engrave/DECISIONS.md +++ b/crates/epiphany-engrave/DECISIONS.md @@ -36,9 +36,12 @@ report `SolverTier::Stub`, never `Minimal` (Chapter 9 §"Conformance Tiers"). landed and now reports `Minimal` — which it fully earns after casting-off: the break constraint family is genuinely supported (spec §"Conformance Tiers", Minimal row), and `Minimal` makes no optimality claim, so greedy first-fit -casting-off is legitimate. The quality-metric vector stays the conservative -all-worst placeholder (`QualityMetricVector::unmeasured`) until the Quality Metric -Catalog lands (`Standard` tier work). +casting-off is legitimate. Since the Quality Metric Catalog companion's +ratification, the solve also reports a **real quality-metric vector** — +accurate metric vectors are part of the Minimal claim — computed per the +catalog's formulas (see "Quality metrics (2026-07)" below). The all-worst +placeholder (`QualityMetricVector::unmeasured`) remains only for malformed +inputs the solver cannot measure. ## Implementation decisions (QUICKSTART "Decisions you'll need to make") @@ -221,3 +224,106 @@ resolved: itself, not its artefacts). Carried as `Registered(SYSTEM_CONTINUATION_SYNTHESIS)`; the spec should either add a continuation kind or bless the registered id. + +## Quality metrics (2026-07) — decisions + +The Quality Metric Catalog companion (v0.1.0) ratified the nine normative +axes' formal definitions, anchors, thresholds, and the +`QualityFloorApproached` trigger; `Engraver::resolve` now computes the real +vector (the private `quality` module), replacing the all-worst placeholder. +The catalog's normative constants (anchors, the Minimal/Standard threshold +table, the 0.8 warning fraction, the tier/profile→column mappings) are +transcribed once in `epiphany_layout_ir::quality` and consumed here and by the +testkit's reference-suite harness. + +1. **Where each axis's inputs come from.** All nine are pure functions of the + constrained input, the cast layout, and the declared page geometry — data + the pipeline already had (see the `quality` module docs for the per-axis + map). The casting pass exposes its own glyph→system assignment + (`CastLayout::system_of_slot`, `region_of_system`) so the census ranges + over what the solve actually did, never a reconstruction. Slot identity + (the collision axis's same-column exclusion) is the glyph's + `horizontal_slot` in the constrained input, index-parallel to the resolved + glyph list. Widths/columns/densities use glyph **ink boxes** per the + catalog's measurement domain (strokes are not glyphs); page spans use the + resolved page tree's system bounding boxes. +2. **Vacuous axes.** `slur_shape_penalty` and `beam_slope_penalty` are exactly + `0.0`: the pipeline draws no slur or beam geometry (both exist logically, + not as curves/segments), so their contributing-unit sets are empty and the + catalog's vacuous-geometry rule (`req:qmc:vacuous`) applies. The catalog's + "notated-but-unrendered" open question explicitly owns this honesty edge; + the axes are wired so the first slur/beam-drawing release is measured from + day one. +3. **Vertical density's unit set.** `to_constrained` declares `InterStaffGap` + bands but **no** `InterSystemGap` bands (the casting pass reads + `VerticalBand::inter_system_gap` directly). Implemented units: (a) the + input's `InterStaffGap` bands, adjacency reconstructed from + `inter_staff_gap_id(region, g)` (gap *g* separates the region's staves + *g−1*/*g*), realized separation measured between the adjacent staff bands' + resolved ink extents within a common system — i.e. what the resolved + geometry actually shows, since constrained `y` is pass-through; (b) the + casting pass's realized inter-system gaps (consecutive systems on a page), + measured from the resolved page tree against the same constructor's + preferred height the stacking consulted. Today (b) measures realized ≡ + preferred (raw 0), and (a) is empty for every single-staff-per-region + score; a multi-staff region honestly measures ~1.0 because the constrained + stage's fixed 12-staff-space pitch is far from the band model's preferred + 2.0 gap — the metric is truthful, the vertical spring solve that would + negotiate it is the deferred work. +4. **Floor warnings never change the status.** Catalog + `req:qmc:floor-warning`: the `QualityFloorApproached` warning "is + diagnostic: emitting it does not change the solve's status". Implemented + literally: `status` is computed before the metric census, and quality + warnings are appended after — a solve with clean constraints stays + `Solved` even when it carries quality diagnostics. (This is also + load-bearing for downstream regression locks that assert `Solved` on + fixtures whose casting-off quality honestly warns.) The applicable + threshold column is the one the config's profile selects + (`profile_thresholds`: Draft→Minimal, Standard/Publication→Standard; + default profile Standard), so `SolverConfig` is now threaded into + `resolve`. +5. **Malformed inputs stay unmeasured.** A structurally invalid or + forged-catalog input has no trustworthy geometry (the census would sweep + unverified boxes), so it keeps `QualityMetricVector::unmeasured()` and + earns no floor diagnostics. An `Unsatisfiable` solve of a *valid* problem + is measured honestly — its real geometry exists. +6. **No-flip verification.** Existing tests asserting `Solved` on healthy + fixtures were re-run against the real metrics: none flipped (warnings + cannot flip status, and no metric enters the status computation). Two + engrave tests asserting `warnings.is_empty()` after an honoured break were + narrowed to "no `LargeSoftConstraintViolation`": their micro-fixtures + (two-note scores broken at the last note column) honestly cast off into + wildly uneven system widths, so the casting-off axis fires its SHOULD-level + floor diagnostic — the metric is telling the truth about the layout, and + the tests' actual claim (an honoured break is not a *soft violation*) is + preserved exactly. +7. **Measured reality on the reference suite (first real vectors).** The six + v0.1 entries measure clean on every axis except two findings the catalog's + threshold-tuning open question anticipated (both reported as Pass-12/QMC + candidates below): RS-1's `casting_off_quality` = 1.0 (the greedy stub + last line, above the Minimal 0.90 threshold — tracked as a documented + xfail row in the testkit harness), and `spacing_distortion` on 3–8-column + entries (0.36–0.41) sits above the Standard column's 0.32 warning floor, + so short scores warn under the default Standard profile. + +### Pass 12 candidates (quality metrics) + +- **P12 (proposed) — QMC: RS-1 fails the Minimal casting-off threshold under + the reference engraver.** First measured vectors (this crate, engraver v2): + greedy first-fit casts the RS-1 fixture into glyph spans ~78.6/18.8 staff + spaces → width CV 0.61 ≥ the 0.5 anchor → clamped 1.0 > the Minimal 0.90 + threshold. Two consistent resolutions: (a) a casting-off balance pass in + the engraver (a geometry change requiring golden regeneration and a solver + version bump), or (b) a QMC minor revision (raise the `casting_off_quality` + anchor toward ~1.0, or give Minimal a per-axis relaxation / the Reference + Suite an RS-1 override). Until ratified either way, the testkit harness + carries the miss as an asserted Xfail row (budget-harness discipline), so + it cannot rot silently. +- **P12 (proposed) — QMC: the Standard spacing floor warns on short scores.** + With uniform preferred widths, few-column systems (3–8 columns with a wide + clef/key lead) measure spacing CV 0.36–0.41 — above the Standard column's + 0.8 × 0.40 = 0.32 warning floor, so the default profile emits + `QualityFloorApproached(Spacing)` on tiny, healthy scores. Consider either + a duration/lead-aware refinement of the axis (the catalog's optical-spacing + open question) or excluding the lead column from the advance sequence in a + QMC minor revision. diff --git a/crates/epiphany-engrave/src/casting.rs b/crates/epiphany-engrave/src/casting.rs index 12dbc4e..9f3acea 100644 --- a/crates/epiphany-engrave/src/casting.rs +++ b/crates/epiphany-engrave/src/casting.rs @@ -178,6 +178,14 @@ pub(crate) struct CastLayout { pub system_start_slots: BTreeSet, /// Slots at which a page begins: the first slot of each page's first system. pub page_start_slots: BTreeSet, + /// Which system (global index, page order) each realized slot landed in — + /// the casting pass's own assignment, which the quality-metric census + /// ranges over (a slot absent here was claimed by no region and its glyphs + /// belong to no per-system aggregate). + pub system_of_slot: BTreeMap, + /// The region each system slices, indexed by global system index (the + /// per-region grouping the casting-off quality metrics aggregate by). + pub region_of_system: Vec, } /// One realized spring slot in spaced (pre-casting) coordinates, with the @@ -759,6 +767,8 @@ pub(crate) fn cast_off( decisions, system_start_slots, page_start_slots, + system_of_slot, + region_of_system: systems.iter().map(|plan| plan.region).collect(), } } diff --git a/crates/epiphany-engrave/src/lib.rs b/crates/epiphany-engrave/src/lib.rs index a4fc8ee..c0de83c 100644 --- a/crates/epiphany-engrave/src/lib.rs +++ b/crates/epiphany-engrave/src/lib.rs @@ -45,10 +45,17 @@ //! Having earned it, [`Engraver::tier`] reports [`SolverTier::Minimal`] — which //! (Chapter 9 §"Conformance Tiers" / QUICKSTART) means *hard constraints //! satisfied, no claim about optimality* — greedy first-fit casting-off is -//! legitimate at this tier. It therefore makes **no normalized-metric claim**: -//! the quality-metric vector stays the conservative all-worst "no claim" -//! placeholder ([`QualityMetricVector::unmeasured`]) until the Quality Metric -//! Catalog lands (Phase 3 / `Standard`). Still deferred to a later tier: the +//! legitimate at this tier. The solve reports a **real quality-metric vector**: +//! the private `quality` module computes all nine normative axes per the +//! ratified *Quality Metric Catalog* companion (collision census, spacing +//! regularity, break/page/casting-off distribution, vertical gap deviation; +//! slur/beam shape are vacuous-`0.0` because no drawn slur/beam geometry exists +//! yet), normalized through the catalog's pinned anchors +//! ([`epiphany_layout_ir::quality`]), with +//! [`SolverWarningKind::QualityFloorApproached`] diagnostics against the +//! threshold column the config's profile selects. The all-worst +//! [`QualityMetricVector::unmeasured`] placeholder remains only for malformed +//! inputs the solver cannot measure. Still deferred to a later tier: the //! **vertical spring pass** (glyph `y` within a system is the constrained //! natural staff layout, preserved verbatim; systems stack by real content //! extents), per-system justification/stretch, and optimal break search. @@ -63,16 +70,17 @@ //! [`epiphany-render-svg`]: ../epiphany_render_svg/index.html pub mod casting; +mod quality; mod spacing; use std::collections::{BTreeMap, BTreeSet}; use epiphany_layout_ir::{ - all_available, Axis, BravuraCatalog, ConstrainedLayoutIR, ConstraintId, ConstraintSolver, - ConstraintStrength, GlyphCatalog, GlyphObject, GlyphObjectId, InvalidationSet, - LayoutConstraint, Point, QualityMetricVector, Rect, ResolvedGlyph, ResolvedLayoutIR, - SolveReport, SolveStatus, SolverBudgetUsed, SolverConfig, SolverState, SolverTier, - SolverVersion, SolverWarning, SolverWarningKind, SpringSlotId, Stroke, + all_available, profile_thresholds, Axis, BravuraCatalog, ConstrainedLayoutIR, ConstraintId, + ConstraintSolver, ConstraintStrength, GlyphCatalog, GlyphObject, GlyphObjectId, + InvalidationSet, LayoutConstraint, Point, QualityMetricVector, Rect, ResolvedGlyph, + ResolvedLayoutIR, SolveReport, SolveStatus, SolverBudgetUsed, SolverConfig, SolverState, + SolverTier, SolverVersion, SolverWarning, SolverWarningKind, SpringSlotId, Stroke, }; pub use casting::{PageGeometry, INTER_PAGE_GAP, SYSTEM_CONTINUATION_SYNTHESIS}; @@ -128,14 +136,20 @@ impl Engraver { /// slots (each glyph to its slot's `x`, baseline `y` preserved), then the /// casting-off pass (system breaking, vertical stacking, page assignment — /// see [`casting`]), then evaluation of the declared constraints by - /// strength. A malformed input — an unknown glyph, a forged catalog - /// identity, or invalid structure — yields [`SolveStatus::InternalError`]; a - /// valid problem whose `Required` constraints cannot all be satisfied yields - /// [`SolveStatus::Unsatisfiable`] (naming the unsatisfied constraints). Both - /// are diagnostic-only; neither panics. Violated `Preferred` constraints - /// yield soft-violation warnings under [`SolveStatus::SolvedWithWarnings`] - /// — a valid, renderable layout. - fn resolve(&self, input: &ConstrainedLayoutIR) -> SolveReport { + /// strength, then the **quality-metric census** (the private `quality` + /// module): all nine normative axes of the Quality Metric Catalog computed + /// over the cast geometry, with `QualityFloorApproached` warnings against + /// the threshold column the config's profile selects (diagnostic — per the + /// catalog they never change the status). A malformed input — an unknown + /// glyph, a forged catalog identity, or invalid structure — yields + /// [`SolveStatus::InternalError`] with the all-worst unmeasured vector + /// (nothing trustworthy to measure); a valid problem whose `Required` + /// constraints cannot all be satisfied yields + /// [`SolveStatus::Unsatisfiable`] (naming the unsatisfied constraints), its + /// real geometry measured honestly. Neither panics. Violated `Preferred` + /// constraints yield soft-violation warnings under + /// [`SolveStatus::SolvedWithWarnings`] — a valid, renderable layout. + fn resolve(&self, input: &ConstrainedLayoutIR, config: &SolverConfig) -> SolveReport { let structural_valid = input.validate().is_ok(); // Short-circuit before catalog construction so an unknown glyph yields a @@ -237,6 +251,27 @@ impl Engraver { }); } + // The quality-metric census (Quality Metric Catalog): measured whenever + // the geometry is trustworthy — structure valid (the cast ran) and the + // catalog identity genuine (the glyph boxes the census sweeps are the + // real bundled metrics). A malformed input keeps the all-worst + // unmeasured placeholder: there is nothing honest to measure. The + // floor warnings reference the threshold column the config's profile + // selects (Draft -> Minimal, Standard/Publication -> Standard); per the + // catalog they are diagnostic and never change `status`, which was + // fixed above. + let metric_vector = match (&cast, catalog_valid) { + (Some(cast), true) => { + let vector = quality::measure(input, cast, &self.geometry); + warnings.extend(quality::floor_warnings( + &vector, + profile_thresholds(config.profile), + )); + vector + } + _ => QualityMetricVector::unmeasured(), + }; + // The final layout is the cast world frame: real pages and systems, // glyph/stroke positions baked, the engraver's break decisions appended // to the pipeline's (Chapter 7 §"ResolvedLayoutIR": decisions "including @@ -268,11 +303,9 @@ impl Engraver { }, unsatisfied_constraints, warnings, - // Minimal makes no normalized-metric claim (Chapter 9 / QUICKSTART: - // "satisfies hard constraints but makes no normalized-metric claims"; - // the Quality Metric Catalog is Phase 3), so the vector is the - // conservative all-worst "no claim" placeholder, like the stub's. - metric_vector: QualityMetricVector::unmeasured(), + // The real nine-axis census computed above (or the honest all-worst + // placeholder for a malformed input the solver could not measure). + metric_vector, budget_used: SolverBudgetUsed { // The horizontal pass and the casting-off walk each touch every // slot once; report the spacing pass's touch honestly. @@ -547,9 +580,11 @@ fn within(g: &ResolvedGlyph, region: &Rect) -> bool { impl ConstraintSolver for Engraver { fn tier(&self) -> SolverTier { // Minimal (Chapter 9): it evaluates and satisfies the IR's declared hard - // constraints, reporting honestly which (if any) it cannot. It makes no - // normalized-metric claim — `Minimal` means hard constraints satisfied, - // not optimal quality (the Quality Metric Catalog is Phase 3 / `Standard`). + // constraints, reporting honestly which (if any) it cannot, and computes + // real quality-metric vectors per the Quality Metric Catalog — accurate + // reports being part of the Minimal claim. `Minimal` still makes no + // optimality claim (greedy first-fit casting-off is legitimate here); + // the Standard tier's tighter thresholds are not claimed. SolverTier::Minimal } @@ -557,8 +592,8 @@ impl ConstraintSolver for Engraver { ENGRAVER_VERSION } - fn solve(&self, input: &ConstrainedLayoutIR, _config: &SolverConfig) -> SolveReport { - self.resolve(input) + fn solve(&self, input: &ConstrainedLayoutIR, config: &SolverConfig) -> SolveReport { + self.resolve(input, config) } fn solve_incremental( @@ -566,13 +601,13 @@ impl ConstraintSolver for Engraver { input: &ConstrainedLayoutIR, _prior: &SolverState, _invalidations: &InvalidationSet, - _config: &SolverConfig, + config: &SolverConfig, ) -> SolveReport { // The scaffold recomputes spacing from scratch, which is trivially // observationally equivalent to a scoped incremental solve (Chapter 9 // §"Observational Equivalence"). Real incremental scoping is Minimal-tier // work. - self.resolve(input) + self.resolve(input, config) } } @@ -588,14 +623,33 @@ mod tests { #[test] fn reports_the_minimal_tier_it_has_earned() { + use epiphany_layout_ir::{MINIMAL_THRESHOLDS, QUALITY_METRIC_KINDS}; // It evaluates the declared hard constraints, so it reports Minimal — above - // the interface-only stub, below the metric-claiming Standard tier. + // the interface-only stub, below the tighter-threshold Standard tier. assert_eq!(Engraver::default().tier(), SolverTier::Minimal); assert!(Engraver::default().tier() > StubSolver.tier()); assert!(Engraver::default().tier() < SolverTier::Standard); - // Minimal makes no normalized-metric claim (the catalog is Phase 3). + // Accurate metric vectors are part of the Minimal claim (Chapter 9; + // Quality Metric Catalog): the vector is *real* — never the all-worst + // unmeasured placeholder — collision-free on this clean fixture, and + // every axis is a valid NormalizedMetric within the catalog's Minimal + // threshold column (the fixture's three regions each cast onto a + // single system, so the break-family axes degenerate to exactly 0.0 + // under the vacuous-geometry rule). let report = Engraver::default().solve(&fixture(), &SolverConfig::default()); - assert_eq!(report.metric_vector, QualityMetricVector::unmeasured()); + assert_ne!(report.metric_vector, QualityMetricVector::unmeasured()); + assert_eq!(report.metric_vector.collision_penalty.0, 0.0); + for kind in QUALITY_METRIC_KINDS { + let value = report.metric_vector.axis(kind).0; + assert!( + value.is_finite() && (0.0..=1.0).contains(&value), + "{kind:?}" + ); + assert!( + value <= MINIMAL_THRESHOLDS.axis(kind), + "{kind:?} = {value} exceeds its Minimal threshold" + ); + } assert_eq!(Engraver::default().version(), ENGRAVER_VERSION); assert_ne!(Engraver::default().version(), StubSolver.version()); } @@ -795,7 +849,19 @@ mod tests { }); let report = Engraver::default().solve(&input, &SolverConfig::default()); assert_eq!(report.status, SolveStatus::Solved, "{:?}", report.warnings); - assert!(report.warnings.is_empty()); + // The honoured break is never reported as a soft violation. (The + // report legitimately carries QualityFloorApproached diagnostics: this + // two-note micro-score casts off into wildly uneven system widths, + // which the casting-off axis honestly measures — quality warnings are + // diagnostic and, per the catalog, never change the status.) + assert!( + !report.warnings.iter().any(|w| matches!( + w.kind, + SolverWarningKind::LargeSoftConstraintViolation { .. } + )), + "an honoured break must not surface as a soft violation: {:?}", + report.warnings + ); assert!(report.satisfied_hard_constraints); assert_eq!(system_count(&report.layout), 2); assert!(report @@ -906,7 +972,19 @@ mod tests { let engraver = Engraver::default(); let report = engraver.solve(&constrained, &SolverConfig::default()); assert_eq!(report.status, SolveStatus::Solved, "{:?}", report.warnings); - assert!(report.warnings.is_empty(), "an honoured break never warns"); + // An honoured break never warns *about the break* (no soft violation). + // The report may carry QualityFloorApproached diagnostics — this + // few-note score's user break honestly leaves a stub last system, + // which the casting-off axis measures; quality warnings never change + // the status per the catalog. + assert!( + !report.warnings.iter().any(|w| matches!( + w.kind, + SolverWarningKind::LargeSoftConstraintViolation { .. } + )), + "an honoured break never surfaces as a soft violation: {:?}", + report.warnings + ); assert!(report.satisfied_hard_constraints); assert!(report.unsatisfied_constraints.is_empty()); assert!( @@ -1216,8 +1294,11 @@ mod tests { assert_eq!(resolved.provenance, original.provenance); assert_eq!(resolved.glyph, original.glyph); } - // The metric vector is the honest all-worst placeholder (no metrics yet). - assert_eq!(report.metric_vector, QualityMetricVector::unmeasured()); + // The metric vector is real — computed per the Quality Metric Catalog, + // never the all-worst placeholder — and this clean pipeline fixture is + // collision-free under the full same-system census. + assert_ne!(report.metric_vector, QualityMetricVector::unmeasured()); + assert_eq!(report.metric_vector.collision_penalty.0, 0.0); } #[test] diff --git a/crates/epiphany-engrave/src/quality.rs b/crates/epiphany-engrave/src/quality.rs new file mode 100644 index 0000000..0c3f3a8 --- /dev/null +++ b/crates/epiphany-engrave/src/quality.rs @@ -0,0 +1,650 @@ +//! **Real quality-metric computation** — the nine normative axes of the +//! *Quality Metric Catalog* companion (v0.1.0, Chapter 3), measured over what +//! the pipeline already produced: the resolved world-frame geometry +//! ([`CastLayout`]), the constrained input (slot identity, vertical bands), and +//! the declared page geometry. Normalization anchors, threshold tables, and the +//! warning fraction are the catalog's, transcribed in +//! [`epiphany_layout_ir::quality`]. +//! +//! Every measurement here is a pure function of the solve's inputs and its +//! resolved output — no clocks, no entropy, fixed iteration order — so repeated +//! identical solves yield bitwise-identical vectors (catalog +//! `req:qmc:determinism`). Where a metric's contributing-unit set is empty the +//! axis is exactly `0.0` (the catalog's vacuous-geometry rule, +//! `req:qmc:vacuous`), never a sentinel. +//! +//! ## Where each axis's inputs come from +//! +//! * **`collision_penalty`** — full pairwise same-system sweep over resolved +//! glyph ink boxes (positions from casting, boxes from the catalog metrics), +//! excluding same-slot pairs (slot identity = the glyph's +//! `horizontal_slot` in the constrained input; the resolved glyph list is +//! index-parallel to it) and strokes (not glyphs, never swept). +//! * **`spacing_distortion`** — per-system column advances: the distinct +//! resolved x of each glyph-bearing slot realized in the system (its first +//! member's baseline — the spacing pass's own column reference). +//! * **`slur_shape_penalty` / `beam_slope_penalty`** — **vacuous 0.0**: the +//! pipeline draws no slur or beam geometry (slurs/beams exist logically, +//! not as curves/segments), so the contributing-unit sets are empty. The +//! catalog pins vacuous-0.0 deliberately and owns the honesty edge (its +//! "notated-but-unrendered" open question): rendering completeness is +//! governed by constraint families and visual acceptance, not these axes. +//! * **`vertical_density_penalty`** — realized gaps against the band model's +//! preferred heights: the constrained input's `InterStaffGap` bands +//! (adjacent staff bands' resolved ink extents; the constrained stage's +//! fixed staff stacking is preserved verbatim, so this measures what the +//! resolved geometry actually shows), plus the casting pass's realized +//! inter-system gaps (consecutive systems on a page) against +//! [`VerticalBand::inter_system_gap`]'s preferred height — the same +//! constructor the stacking consults. (`to_constrained` declares no +//! `InterSystemGap` bands, so the realized page-tree gaps are the honest +//! measurable unit set; see DECISIONS.) +//! * **`system_break_penalty`** — per-region non-final systems: `|W − w_s| / W` +//! with `W` the declared content width and `w_s` the system's glyph-ink +//! span. +//! * **`page_fill_efficiency`** — non-final pages: unfilled fraction of the +//! declared content height, spans from the resolved page tree's system +//! bounding boxes (top of first system to bottom of last). +//! * **`casting_off_quality`** — per-region CV of system glyph-ink widths, +//! final system included (regions with ≥ 2 systems, all widths positive). +//! * **`symbol_density_uniformity`** — per-region CV of glyphs-per-width +//! density over systems with positive width. + +use std::collections::{BTreeMap, BTreeSet}; + +use epiphany_layout_ir::quality::{ + anchors, normalize, MetricThresholds, QUALITY_FLOOR_FRACTION, QUALITY_METRIC_KINDS, +}; +use epiphany_layout_ir::{ + inter_staff_gap_id, ConstrainedLayoutIR, GlyphObject, GlyphObjectId, QualityMetricVector, + SolverWarning, SolverWarningKind, SpringSlotId, VerticalBand, VerticalBandId, VerticalBandKind, +}; + +use crate::casting::{CastLayout, PageGeometry}; + +/// The population coefficient of variation (catalog §"The Measurement Domain"): +/// defined for `k >= 2` values with positive mean; `None` otherwise. +fn cv(values: &[f64]) -> Option { + if values.len() < 2 { + return None; + } + let mean = values.iter().sum::() / values.len() as f64; + if mean <= 0.0 { + return None; + } + let variance = + values.iter().map(|v| (v - mean) * (v - mean)).sum::() / values.len() as f64; + Some(variance.sqrt() / mean) +} + +/// The arithmetic mean over a contributing-unit set, with the catalog's +/// vacuous-geometry rule in aggregate form: the mean over an empty set is `0`. +fn mean_or_zero(values: &[f64]) -> f64 { + if values.is_empty() { + 0.0 + } else { + values.iter().sum::() / values.len() as f64 + } +} + +/// One glyph's resolved ink box `[left, bottom, right, top]` (f64, exact from +/// the f32 geometry). +fn ink_box(cast: &CastLayout, input: &ConstrainedLayoutIR, index: usize) -> [f64; 4] { + let resolved = &cast.glyphs[index]; + let bounds = &input.glyphs[index].bounding_box; + [ + f64::from(resolved.position.x.0 + bounds.left.0), + f64::from(resolved.position.y.0 + bounds.bottom.0), + f64::from(resolved.position.x.0 + bounds.right.0), + f64::from(resolved.position.y.0 + bounds.top.0), + ] +} + +/// Per-system aggregates over the casting pass's own glyph→system assignment. +struct SystemCensus { + /// Region each system slices (parallel to the other vectors). + region: Vec, + /// Glyph indices per system, in input order. + members: Vec>, + /// Glyph-ink span `w_s` per system (0 for a glyph-less system). + width: Vec, + /// Column reference x per realized slot per system, ascending and distinct. + columns: Vec>, +} + +fn census(input: &ConstrainedLayoutIR, cast: &CastLayout) -> SystemCensus { + let count = cast.region_of_system.len(); + let mut members: Vec> = vec![Vec::new(); count]; + let mut spans: Vec> = vec![None; count]; + // Column reference: the slot's first member (input order) — the same + // convention the spacing and casting passes use for a slot's reference x. + let mut columns: Vec> = vec![BTreeMap::new(); count]; + for (index, glyph) in input.glyphs.iter().enumerate() { + let Some(&system) = cast.system_of_slot.get(&glyph.horizontal_slot) else { + // A slot no region claimed: positioned by no system, so its glyphs + // join no per-system aggregate (catalog §"The Measurement Domain"). + continue; + }; + members[system].push(index); + let [left, _, right, _] = ink_box(cast, input, index); + spans[system] = Some(match spans[system] { + Some((lo, hi)) => (lo.min(left), hi.max(right)), + None => (left, right), + }); + columns[system] + .entry(glyph.horizontal_slot) + .or_insert_with(|| f64::from(cast.glyphs[index].position.x.0)); + } + let width = spans + .iter() + .map(|span| span.map(|(lo, hi)| (hi - lo).max(0.0)).unwrap_or(0.0)) + .collect(); + let columns = columns + .into_iter() + .map(|by_slot| { + let mut xs: Vec = by_slot.into_values().collect(); + xs.sort_by(f64::total_cmp); + xs.dedup(); + xs + }) + .collect(); + SystemCensus { + region: cast.region_of_system.clone(), + members, + width, + columns, + } +} + +/// `collision_penalty` (catalog §`collision_penalty`): colliding unordered +/// same-system, different-slot glyph pairs per glyph. Ink boxes must intersect +/// with positive area in both axes; edge-touching boxes do not collide; +/// same-slot pairs (a column's internal cluster — chord heads, their +/// accidentals, dots) are excluded; strokes are not glyphs and join no pair. +fn collision_raw(input: &ConstrainedLayoutIR, cast: &CastLayout, census: &SystemCensus) -> f64 { + let population = cast.glyphs.len(); + if population == 0 { + return 0.0; + } + let mut colliding_pairs: u64 = 0; + for members in &census.members { + // Interval sweep over left edges: a pair can only overlap horizontally + // while the candidate's left edge is inside the anchor's span. + let mut boxes: Vec<(usize, [f64; 4])> = members + .iter() + .map(|&index| (index, ink_box(cast, input, index))) + .collect(); + boxes.sort_by(|a, b| a.1[0].total_cmp(&b.1[0]).then(a.0.cmp(&b.0))); + for i in 0..boxes.len() { + let (index_a, a) = boxes[i]; + for &(index_b, b) in boxes.iter().skip(i + 1) { + if b[0] >= a[2] { + break; // sorted by left edge: nothing further overlaps in x + } + if input.glyphs[index_a].horizontal_slot == input.glyphs[index_b].horizontal_slot { + continue; // same-column cluster: excluded by the catalog + } + let overlap_x = a[2].min(b[2]) - a[0].max(b[0]); + let overlap_y = a[3].min(b[3]) - a[1].max(b[1]); + if overlap_x > 0.0 && overlap_y > 0.0 { + colliding_pairs += 1; + } + } + } + } + colliding_pairs as f64 / population as f64 +} + +/// `spacing_distortion` (catalog §`spacing_distortion`): mean per-system CV of +/// column advances, over systems realizing at least three columns. +fn spacing_raw(census: &SystemCensus) -> f64 { + let mut per_system = Vec::new(); + for columns in &census.columns { + if columns.len() < 3 { + continue; + } + let advances: Vec = columns.windows(2).map(|pair| pair[1] - pair[0]).collect(); + if let Some(value) = cv(&advances) { + per_system.push(value); + } + } + mean_or_zero(&per_system) +} + +/// `vertical_density_penalty` (catalog §`vertical_density_penalty`): mean +/// relative deviation `|r − p| / p` over the realized inter-staff and +/// inter-system gaps (see the module docs for the unit reconstruction). +fn vertical_raw(input: &ConstrainedLayoutIR, cast: &CastLayout, census: &SystemCensus) -> f64 { + let mut per_unit: Vec = Vec::new(); + + // --- InterStaffGap bands declared by the constrained input ------------- + let index_of: BTreeMap = input + .glyphs + .iter() + .enumerate() + .map(|(index, glyph)| (GlyphObject::id(glyph), index)) + .collect(); + let system_of_glyph = |index: usize| -> Option { + cast.system_of_slot + .get(&input.glyphs[index].horizontal_slot) + .copied() + }; + for (region_index, region) in input.regions.iter().enumerate() { + // The region's laid-out staff bands, top staff first, ordered within + // the region's first system (systems translate rigidly, so within- + // system y order is the region's staff order). + let first_system = census.region.iter().position(|&r| r == region_index); + let Some(first_system) = first_system else { + continue; + }; + let region_glyphs: BTreeSet = region.glyphs.iter().copied().collect(); + let mut staves: Vec<(f64, Vec)> = Vec::new(); + for band in &input.vertical_bands { + if !matches!(band.kind, VerticalBandKind::Staff(_)) { + continue; + } + if !band.members.iter().any(|id| region_glyphs.contains(id)) { + continue; + } + let members: Vec = band + .members + .iter() + .filter_map(|id| index_of.get(id).copied()) + .collect(); + let top_in_first = members + .iter() + .filter(|&&index| system_of_glyph(index) == Some(first_system)) + .map(|&index| ink_box(cast, input, index)[3]) + .fold(f64::NEG_INFINITY, f64::max); + if top_in_first.is_finite() { + staves.push((top_in_first, members)); + } + } + // Top staff first. + staves.sort_by(|a, b| b.0.total_cmp(&a.0)); + + // The region's declared inter-staff gap bands, by their derived ids + // (gap g separates the region's staves g−1 and g, per to_constrained). + let region_layout_id = region.provenance.stable_id; + for gap in 1.. { + let gap_id = inter_staff_gap_id(region_layout_id, gap); + let Some(band) = input.vertical_bands.iter().find(|band| band.id == gap_id) else { + break; + }; + let preferred = f64::from(band.preferred_height.0); + if preferred <= 0.0 || staves.len() <= gap { + continue; + } + let upper = &staves[gap - 1].1; + let lower = &staves[gap].1; + // Realized iff the adjacent content shares a system; measure the + // separation there (rigid system translation makes every common + // system agree). + let common: BTreeSet = upper + .iter() + .filter_map(|&index| system_of_glyph(index)) + .filter(|system| { + lower + .iter() + .any(|&index| system_of_glyph(index) == Some(*system)) + }) + .collect(); + let Some(&system) = common.iter().next() else { + continue; + }; + let upper_bottom = upper + .iter() + .filter(|&&index| system_of_glyph(index) == Some(system)) + .map(|&index| ink_box(cast, input, index)[1]) + .fold(f64::INFINITY, f64::min); + let lower_top = lower + .iter() + .filter(|&&index| system_of_glyph(index) == Some(system)) + .map(|&index| ink_box(cast, input, index)[3]) + .fold(f64::NEG_INFINITY, f64::max); + let realized = (upper_bottom - lower_top).max(0.0); + per_unit.push((realized - preferred).abs() / preferred); + } + } + + // --- Realized inter-system gaps (consecutive systems on a page) -------- + let preferred = f64::from( + VerticalBand::inter_system_gap(VerticalBandId(0)) + .preferred_height + .0, + ); + if preferred > 0.0 { + for page in &cast.pages { + for pair in page.systems.windows(2) { + let upper_bottom = f64::from(pair[0].bounding_box.origin.y.0); + let lower_top = + f64::from(pair[1].bounding_box.origin.y.0 + pair[1].bounding_box.size.height.0); + let realized = (upper_bottom - lower_top).max(0.0); + per_unit.push((realized - preferred).abs() / preferred); + } + } + } + + mean_or_zero(&per_unit) +} + +/// `system_break_penalty` (catalog §`system_break_penalty`): mean +/// `|W − w_s| / W` over each region's non-final systems, defined only for a +/// finite positive content width. +fn system_break_raw(census: &SystemCensus, content_width: f64) -> f64 { + if !(content_width.is_finite() && content_width > 0.0) { + return 0.0; + } + let mut per_unit = Vec::new(); + for (system, ®ion) in census.region.iter().enumerate() { + let last_of_region = census.region.iter().rposition(|&r| r == region); + if last_of_region == Some(system) { + continue; // a short last line is not a break failure + } + per_unit.push((content_width - census.width[system]).abs() / content_width); + } + mean_or_zero(&per_unit) +} + +/// `page_fill_efficiency` (catalog §`page_fill_efficiency`): mean unfilled +/// fraction over non-final pages, spans measured from the resolved page tree +/// (top of the first system's content extent to the bottom of the last's). +fn page_fill_raw(cast: &CastLayout, content_height: f64) -> f64 { + if !(content_height.is_finite() && content_height > 0.0) || cast.pages.len() < 2 { + return 0.0; + } + let mut per_unit = Vec::new(); + for page in &cast.pages[..cast.pages.len() - 1] { + let (Some(first), Some(last)) = (page.systems.first(), page.systems.last()) else { + continue; + }; + let top = f64::from(first.bounding_box.origin.y.0 + first.bounding_box.size.height.0); + let bottom = f64::from(last.bounding_box.origin.y.0); + let fill = ((top - bottom) / content_height).min(1.0); + per_unit.push(1.0 - fill); + } + mean_or_zero(&per_unit) +} + +/// `casting_off_quality` (catalog §`casting_off_quality`): mean per-region CV +/// of system widths — final system included — over regions cast onto at least +/// two systems, each with positive width. +fn casting_off_raw(input: &ConstrainedLayoutIR, census: &SystemCensus) -> f64 { + let mut per_region = Vec::new(); + for region in 0..input.regions.len() { + let widths: Vec = census + .region + .iter() + .zip(&census.width) + .filter(|&(&r, _)| r == region) + .map(|(_, &w)| w) + .collect(); + if widths.len() < 2 || widths.iter().any(|&w| w <= 0.0) { + continue; + } + if let Some(value) = cv(&widths) { + per_region.push(value); + } + } + mean_or_zero(&per_region) +} + +/// `symbol_density_uniformity` (catalog §`symbol_density_uniformity`): mean +/// per-region CV of per-system symbol density (glyphs per staff space of +/// content width), over regions with at least two positive-width systems. +fn symbol_density_raw(input: &ConstrainedLayoutIR, census: &SystemCensus) -> f64 { + let mut per_region = Vec::new(); + for region in 0..input.regions.len() { + let densities: Vec = census + .region + .iter() + .enumerate() + .filter(|&(system, &r)| r == region && census.width[system] > 0.0) + .map(|(system, _)| census.members[system].len() as f64 / census.width[system]) + .collect(); + if densities.len() < 2 { + continue; + } + if let Some(value) = cv(&densities) { + per_region.push(value); + } + } + mean_or_zero(&per_region) +} + +/// Computes the full nine-axis [`QualityMetricVector`] for a cast layout, per +/// the Quality Metric Catalog's formulas and pinned anchors. Pure and +/// deterministic: a function of the constrained input, the cast output, and +/// the declared page geometry. +pub(crate) fn measure( + input: &ConstrainedLayoutIR, + cast: &CastLayout, + geometry: &PageGeometry, +) -> QualityMetricVector { + let census = census(input, cast); + let content_width = f64::from(geometry.content_width()); + let content_height = f64::from(geometry.content_height()); + QualityMetricVector { + collision_penalty: normalize( + collision_raw(input, cast, &census), + anchors::COLLISION_R_WORST, + ), + spacing_distortion: normalize(spacing_raw(&census), anchors::SPACING_R_WORST), + // No drawn slur geometry exists in this pipeline (slurs are logical + // objects, not curves): the contributing-unit set is empty, so the + // axis is exactly 0.0 per the catalog's vacuous-geometry rule. The + // catalog's "notated-but-unrendered" open question owns the honesty + // edge; the definition is pinned so the first slur-drawing release is + // measured from day one. + slur_shape_penalty: normalize(0.0, anchors::SLUR_SHAPE_R_WORST), + // Same vacuous rule: no drawn beam segments exist in this pipeline. + beam_slope_penalty: normalize(0.0, anchors::BEAM_SLOPE_R_WORST), + vertical_density_penalty: normalize( + vertical_raw(input, cast, &census), + anchors::VERTICAL_DENSITY_R_WORST, + ), + system_break_penalty: normalize( + system_break_raw(&census, content_width), + anchors::SYSTEM_BREAK_R_WORST, + ), + page_fill_efficiency: normalize( + page_fill_raw(cast, content_height), + anchors::PAGE_FILL_R_WORST, + ), + casting_off_quality: normalize( + casting_off_raw(input, &census), + anchors::CASTING_OFF_R_WORST, + ), + symbol_density_uniformity: normalize( + symbol_density_raw(input, &census), + anchors::SYMBOL_DENSITY_R_WORST, + ), + extension_metrics: Vec::new(), + } +} + +/// The `QualityFloorApproached` warnings a computed vector earns (catalog +/// §"The `QualityFloorApproached` Warning", `req:qmc:floor-warning`): one per +/// axis whose value exceeds [`QUALITY_FLOOR_FRACTION`] × the applicable +/// threshold — the column selected by the solve's profile. The warning is +/// diagnostic; per the catalog it does **not** change the solve's status. +pub(crate) fn floor_warnings( + vector: &QualityMetricVector, + thresholds: &MetricThresholds, +) -> Vec { + QUALITY_METRIC_KINDS + .iter() + .filter_map(|&kind| { + let value = vector.axis(kind).0; + let threshold = thresholds.axis(kind); + let floor = QUALITY_FLOOR_FRACTION * threshold; + (value > floor).then(|| SolverWarning { + kind: SolverWarningKind::QualityFloorApproached { metric: kind }, + affected_objects: Vec::new(), + message: format!( + "quality metric {kind:?} at {value:.4} exceeds {QUALITY_FLOOR_FRACTION} x \ + the profile's threshold {threshold:.2} (floor {floor:.3})" + ), + }) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::Engraver; + use epiphany_layout_ir::{ + to_constrained, to_logical, ConstrainedLayoutIR, ConstraintSolver, QualityMetricKind, + QualityMetricVector, SolveStatus, SolverConfig, SolverProfile, SolverWarningKind, + QUALITY_METRIC_KINDS, + }; + + /// The QUICKSTART ten-measure hand-off fixture: wraps into two systems + /// under the default A4 geometry — the multi-system measurement case. + fn ten_measure() -> ConstrainedLayoutIR { + to_constrained(&to_logical( + &epiphany_testkit::fixtures::ten_measure_single_staff(0x000A_11CE), + )) + } + + fn axes(vector: &QualityMetricVector) -> [f64; 9] { + let mut values = [0.0; 9]; + for (slot, kind) in values.iter_mut().zip(QUALITY_METRIC_KINDS) { + *slot = vector.axis(kind).0; + } + values + } + + #[test] + fn metric_vectors_are_bitwise_deterministic() { + // Catalog `req:qmc:determinism`: identical solve inputs yield + // bitwise-identical vectors within one implementation version — the + // metrics are a pure function of the resolved output and the inputs. + let input = ten_measure(); + let a = Engraver::default().solve(&input, &SolverConfig::default()); + let b = Engraver::default().solve(&input, &SolverConfig::default()); + assert_eq!(a.layout.canonical_bytes(), b.layout.canonical_bytes()); + for (x, y) in axes(&a.metric_vector).iter().zip(axes(&b.metric_vector)) { + assert_eq!( + x.to_bits(), + y.to_bits(), + "metric f64s must be bit-identical" + ); + } + } + + #[test] + fn the_wrapping_fixture_is_measured_honestly() { + // The ten-measure fixture under the default geometry, measured for + // real (values pinned loosely; the goldens pin the geometry itself): + // no cross-column collisions; regular spacing; a single page (the + // page-fill axis degenerates to exactly 0.0); and — the honest part — + // greedy first-fit leaves a two-measure stub last system (glyph spans + // ~78.6 vs ~18.8 staff spaces), which the casting-off axis measures at + // its clamped worst (CV 0.61 >= the 0.5 anchor -> 1.0). That is the + // exact "stub final system" failure the catalog says the axis exists + // to catch; the value is truthful, not a defect in the census. + let report = Engraver::default().solve(&ten_measure(), &SolverConfig::default()); + let vector = &report.metric_vector; + assert_eq!(vector.collision_penalty.0, 0.0); + assert!(vector.spacing_distortion.0 > 0.0 && vector.spacing_distortion.0 < 0.3); + assert_eq!(vector.slur_shape_penalty.0, 0.0, "vacuous: no drawn slurs"); + assert_eq!(vector.beam_slope_penalty.0, 0.0, "vacuous: no drawn beams"); + assert_eq!(vector.page_fill_efficiency.0, 0.0, "vacuous: single page"); + assert!( + vector.system_break_penalty.0 > 0.0 && vector.system_break_penalty.0 < 0.35, + "the non-final system is nearly full: {}", + vector.system_break_penalty.0 + ); + assert_eq!( + vector.casting_off_quality.0, 1.0, + "the stub last line is honestly at the clamped worst" + ); + assert!( + vector.symbol_density_uniformity.0 < 0.1, + "density is even though widths are not: {}", + vector.symbol_density_uniformity.0 + ); + // The casting-off axis exceeds 0.8 x its threshold in every ratified + // column, so the SHOULD-level floor diagnostic fires — and, per the + // catalog, the status is untouched by it. + assert!(report.warnings.iter().any(|w| matches!( + w.kind, + SolverWarningKind::QualityFloorApproached { + metric: QualityMetricKind::CastingOff + } + ))); + assert_eq!(report.status, SolveStatus::Solved); + } + + #[test] + fn floor_warnings_reference_the_profiles_threshold_column() { + // The b-flat scale's spacing distortion (~0.41: eight columns whose + // flat-bearing columns advance wider) sits between the Standard + // column's floor (0.8 x 0.40 = 0.32) and the Minimal column's + // (0.8 x 0.90 = 0.72) — so the default Standard profile warns about + // Spacing and the Draft profile (which selects the Minimal column per + // the catalog's profile registry) does not. + let score = epiphany_testkit::corpus::corpus() + .into_iter() + .find(|fixture| fixture.name == "b_flat_major_scale") + .expect("corpus entry exists"); + let input = to_constrained(&to_logical(&(score.build)())); + let spacing_warned = |profile: SolverProfile| { + let config = SolverConfig { + profile, + ..SolverConfig::default() + }; + Engraver::default() + .solve(&input, &config) + .warnings + .iter() + .any(|w| { + matches!( + w.kind, + SolverWarningKind::QualityFloorApproached { + metric: QualityMetricKind::Spacing + } + ) + }) + }; + assert!(spacing_warned(SolverProfile::Standard)); + assert!(spacing_warned(SolverProfile::Publication)); + assert!(!spacing_warned(SolverProfile::Draft)); + // The metric itself is profile-independent — only the diagnostic + // column changes. + let value = Engraver::default() + .solve(&input, &SolverConfig::default()) + .metric_vector + .spacing_distortion + .0; + assert!((0.32..=0.72).contains(&value), "spacing = {value}"); + } + + #[test] + fn a_malformed_input_stays_unmeasured() { + // A structurally invalid input has no trustworthy geometry: the vector + // is the honest all-worst placeholder, not a vacuous all-best zero. + let mut input = ten_measure(); + input.glyphs[0].baseline = epiphany_layout_ir::Point::new(f32::NAN, 0.0); + let report = Engraver::default().solve(&input, &SolverConfig::default()); + assert_eq!(report.status, SolveStatus::InternalError); + assert_eq!(report.metric_vector, QualityMetricVector::unmeasured()); + // ... and no floor diagnostics are derived from a placeholder. + assert!(!report + .warnings + .iter() + .any(|w| matches!(w.kind, SolverWarningKind::QualityFloorApproached { .. }))); + } + + #[test] + fn realized_inter_system_gaps_measure_the_band_models_preferred_height() { + // The casting pass stacks systems at the vertical-band constructor's + // preferred inter-system gap, so the vertical-density axis measures + // realized == preferred (raw 0.0) on the wrapping fixture — the honest + // near-zero the catalog's rationale describes, *measured* from the + // resolved page tree rather than assumed. + let report = Engraver::default().solve(&ten_measure(), &SolverConfig::default()); + assert_eq!(report.metric_vector.vertical_density_penalty.0, 0.0); + } +} diff --git a/crates/epiphany-layout-ir/DECISIONS.md b/crates/epiphany-layout-ir/DECISIONS.md index 1afe0f1..eafa42c 100644 --- a/crates/epiphany-layout-ir/DECISIONS.md +++ b/crates/epiphany-layout-ir/DECISIONS.md @@ -421,3 +421,27 @@ object is covered); the provenance-preservation contract itself is unchanged. P12-I2 wired it: `epiphany-determinism` reserves the built-in `DomainTag::LAYOUT_OBJECT_ID` and `provenance.rs` (and the engraving-decision id) route through it. See the ratified-block note at the top of this file. + +## Quality Metric Catalog constants (`src/quality.rs`, 2026-07) + +**Decision: the catalog's normative constants live in this crate, as a pure +transcription.** The Quality Metric Catalog companion (v0.1.0) pins the nine +axes' normalization anchors (`R_worst`), the clamped-linear normalization form +`n = min(1, raw / R_worst)`, the Minimal/Standard threshold table, the +`QualityFloorApproached` warning fraction (0.8), and the tier/profile → +threshold-column mappings (Minimal has its own column; Standard and Advanced +use the Standard column; profiles Draft → Minimal column, Standard and +Publication → Standard column, Standard the default). Both consumers — the +`epiphany-engrave` solver (computing vectors and floor diagnostics) and the +`epiphany-testkit` reference-suite harness (asserting per-tier thresholds) — +need the same numbers, and this crate is the only one both already depend on, +so the constants live here (`quality.rs`) with doc comments citing the +companion by chapter/section. **Every value is transcribed, none invented**; +a change to any of them is a catalog revision first, mirrored here. The +module is additive: no canonical encoding is touched (metric values remain +diagnostic-only, structurally outside `ResolvedLayoutIR` — the catalog's own +requirement), and the `StubSolver` still computes nothing and keeps its +all-worst `unmeasured()` vector, which a transcription test pins as excluded +by the Minimal column ("measuring is part of the Minimal claim"). The catalog +also blesses the existing `TieBreakingWeights::default()` (all 1.0) as the +normative defaults — pinned by test rather than re-declared. diff --git a/crates/epiphany-layout-ir/src/lib.rs b/crates/epiphany-layout-ir/src/lib.rs index b9de1f6..6e6e047 100644 --- a/crates/epiphany-layout-ir/src/lib.rs +++ b/crates/epiphany-layout-ir/src/lib.rs @@ -73,6 +73,7 @@ pub mod glyph; pub mod hittest; pub mod logical; pub mod provenance; +pub mod quality; pub mod render; pub mod resolved; pub mod roundtrip; @@ -126,6 +127,10 @@ pub use provenance::{ continuation_instance_key, manifestation_layout_id, stable_layout_id, synthesized_layout_id, LayoutObjectId, Provenance, SynthesisInstanceKey, SynthesisKind, SynthesisRegistryId, }; +pub use quality::{ + normalize, profile_thresholds, r_worst, tier_thresholds, MetricThresholds, MINIMAL_THRESHOLDS, + QUALITY_FLOOR_FRACTION, QUALITY_METRIC_KINDS, STANDARD_THRESHOLDS, +}; pub use render::{ to_render, ColorConfiguration, ColorSpace, PassthroughRenderProducer, RasterizationConfiguration, RenderConfiguration, RenderIR, RenderIRProducer, RenderPrimitive, diff --git a/crates/epiphany-layout-ir/src/quality.rs b/crates/epiphany-layout-ir/src/quality.rs new file mode 100644 index 0000000..2d5c5c3 --- /dev/null +++ b/crates/epiphany-layout-ir/src/quality.rs @@ -0,0 +1,341 @@ +//! The Quality Metric Catalog's normative constants (companion specification +//! *Epiphany — Quality Metric Catalog*, v0.1.0): the per-axis normalization +//! anchors, the per-tier metric threshold table, the profile→threshold-column +//! mapping, and the `QualityFloorApproached` warning fraction. +//! +//! This module is a **transcription**, not an invention: every number here is +//! pinned by the catalog and cited to its chapter. Solvers that compute real +//! metrics (e.g. `epiphany-engrave`) normalize raw measurements through +//! [`normalize`] with the [`anchors`] of catalog Chapter 3 ("The Nine Normative +//! Metrics"), and reference the threshold tables of catalog Chapter 5 +//! ("Per-Tier Metric Thresholds") — as does the reference-suite harness in +//! `epiphany-testkit`. The in-crate [`StubSolver`](crate::StubSolver) computes +//! no metrics and touches none of this (it stays on the all-worst +//! [`QualityMetricVector::unmeasured`] placeholder, the honest "no claim" +//! vector the catalog's vacuous-geometry requirement reserves for a solver +//! that computes no metrics at all). + +use crate::solver::{ + NormalizedMetric, QualityMetricKind, QualityMetricVector, SolverProfile, SolverTier, +}; + +/// The nine normative metric axes in their catalog order (catalog §"The +/// Normative Metric Set and `QualityMetricKind`", Table "kind-mapping"). +pub const QUALITY_METRIC_KINDS: [QualityMetricKind; 9] = [ + QualityMetricKind::Collision, + QualityMetricKind::Spacing, + QualityMetricKind::SlurShape, + QualityMetricKind::BeamSlope, + QualityMetricKind::VerticalDensity, + QualityMetricKind::SystemBreak, + QualityMetricKind::PageFill, + QualityMetricKind::CastingOff, + QualityMetricKind::SymbolDensity, +]; + +/// The per-axis normalization anchors `R_worst` (catalog Chapter 3): each +/// normative metric defines a dimensionless raw measurement `raw >= 0` and a +/// pinned anchor, and normalizes by the clamped-linear map +/// `n = min(1, raw / R_worst)` (catalog §"Normalization Form", +/// `req:qmc:normalization-form`). Implementations MUST use these anchors; +/// arbitrary normalization is non-conforming. +pub mod anchors { + /// `collision_penalty` (catalog §`collision_penalty`): colliding + /// cross-column pairs per glyph; one collision per twenty glyphs is + /// worst-tolerable. + pub const COLLISION_R_WORST: f64 = 0.05; + /// `spacing_distortion` (catalog §`spacing_distortion`): mean per-system + /// CV of column advances; CV 1.0 is spacing with no discernible + /// regularity. + pub const SPACING_R_WORST: f64 = 1.0; + /// `slur_shape_penalty` (catalog §`slur_shape_penalty`): mean deviation of + /// the arc ratio from the ideal band `[0.08, 0.25]`; a semicircular slur + /// (deviation 0.25) is worst-tolerable. + pub const SLUR_SHAPE_R_WORST: f64 = 0.25; + /// `beam_slope_penalty` (catalog §`beam_slope_penalty`): mean slope excess + /// over 0.25; slope 0.5 (deviation 0.25) is worst-tolerable. + pub const BEAM_SLOPE_R_WORST: f64 = 0.25; + /// `vertical_density_penalty` (catalog §`vertical_density_penalty`): mean + /// relative gap deviation `|r - p| / p`; a gap off by its own preferred + /// size is worst-tolerable. + pub const VERTICAL_DENSITY_R_WORST: f64 = 1.0; + /// `system_break_penalty` (catalog §`system_break_penalty`): mean + /// `|W - w_s| / W` over non-final systems; half-empty (or half-overflowing) + /// non-final systems are worst-tolerable. + pub const SYSTEM_BREAK_R_WORST: f64 = 0.5; + /// `page_fill_efficiency` (catalog §`page_fill_efficiency`): mean unfilled + /// fraction of non-final pages; three-quarters empty is worst-tolerable. + pub const PAGE_FILL_R_WORST: f64 = 0.75; + /// `casting_off_quality` (catalog §`casting_off_quality`): mean per-region + /// CV of system widths (final system included); CV 0.5 is worst-tolerable. + pub const CASTING_OFF_R_WORST: f64 = 0.5; + /// `symbol_density_uniformity` (catalog §`symbol_density_uniformity`): + /// mean per-region CV of glyphs-per-width densities; CV 0.5 is + /// worst-tolerable. + pub const SYMBOL_DENSITY_R_WORST: f64 = 0.5; +} + +/// The pinned anchor `R_worst` for a normative axis (catalog Chapter 3; see +/// [`anchors`]). +pub fn r_worst(kind: QualityMetricKind) -> f64 { + match kind { + QualityMetricKind::Collision => anchors::COLLISION_R_WORST, + QualityMetricKind::Spacing => anchors::SPACING_R_WORST, + QualityMetricKind::SlurShape => anchors::SLUR_SHAPE_R_WORST, + QualityMetricKind::BeamSlope => anchors::BEAM_SLOPE_R_WORST, + QualityMetricKind::VerticalDensity => anchors::VERTICAL_DENSITY_R_WORST, + QualityMetricKind::SystemBreak => anchors::SYSTEM_BREAK_R_WORST, + QualityMetricKind::PageFill => anchors::PAGE_FILL_R_WORST, + QualityMetricKind::CastingOff => anchors::CASTING_OFF_R_WORST, + QualityMetricKind::SymbolDensity => anchors::SYMBOL_DENSITY_R_WORST, + } +} + +/// The catalog's clamped-linear normalization (catalog §"Normalization Form", +/// `req:qmc:normalization-form`): `n = min(1, raw / R_worst)`, so `raw = 0` +/// (the ideal) normalizes to `0.0` and `raw >= R_worst` (the worst-tolerable +/// anchor and beyond) normalizes to `1.0`. +/// +/// `raw` must be a finite, non-negative measurement and `r_worst` a positive +/// anchor, per the catalog; the result is a valid [`NormalizedMetric`] by +/// construction. +pub fn normalize(raw: f64, r_worst: f64) -> NormalizedMetric { + assert!( + raw.is_finite() && raw >= 0.0, + "a raw quality measurement must be finite and non-negative (got {raw})" + ); + assert!( + r_worst > 0.0, + "a normalization anchor must be positive (got {r_worst})" + ); + NormalizedMetric::new((raw / r_worst).min(1.0)) +} + +/// One column of the catalog's per-tier threshold table (catalog Chapter 5, +/// Table "tier-thresholds"): the maximum permitted [`NormalizedMetric`] value +/// per axis for a reference-suite entry evaluated at that tier. +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct MetricThresholds { + pub collision_penalty: f64, + pub spacing_distortion: f64, + pub slur_shape_penalty: f64, + pub beam_slope_penalty: f64, + pub vertical_density_penalty: f64, + pub system_break_penalty: f64, + pub page_fill_efficiency: f64, + pub casting_off_quality: f64, + pub symbol_density_uniformity: f64, +} + +impl MetricThresholds { + /// The column's threshold for one axis. + pub fn axis(&self, kind: QualityMetricKind) -> f64 { + match kind { + QualityMetricKind::Collision => self.collision_penalty, + QualityMetricKind::Spacing => self.spacing_distortion, + QualityMetricKind::SlurShape => self.slur_shape_penalty, + QualityMetricKind::BeamSlope => self.beam_slope_penalty, + QualityMetricKind::VerticalDensity => self.vertical_density_penalty, + QualityMetricKind::SystemBreak => self.system_break_penalty, + QualityMetricKind::PageFill => self.page_fill_efficiency, + QualityMetricKind::CastingOff => self.casting_off_quality, + QualityMetricKind::SymbolDensity => self.symbol_density_uniformity, + } + } +} + +/// The **Minimal** threshold column (catalog Chapter 5, Table +/// "tier-thresholds"): uniformly `0.90` — relaxed but non-vacuous, excluding +/// layouts at an axis's worst-tolerable anchor and the all-worst unmeasured +/// placeholder ("measuring is part of the Minimal claim"). +pub const MINIMAL_THRESHOLDS: MetricThresholds = MetricThresholds { + collision_penalty: 0.90, + spacing_distortion: 0.90, + slur_shape_penalty: 0.90, + beam_slope_penalty: 0.90, + vertical_density_penalty: 0.90, + system_break_penalty: 0.90, + page_fill_efficiency: 0.90, + casting_off_quality: 0.90, + symbol_density_uniformity: 0.90, +}; + +/// The **Standard** threshold column (catalog Chapter 5, Table +/// "tier-thresholds"): professional engraving quality — collisions bounded +/// tightest (`0.25`), the break family at `0.35`, distribution/vertical proxies +/// at `0.40`, slur/beam shape at `0.30`. +pub const STANDARD_THRESHOLDS: MetricThresholds = MetricThresholds { + collision_penalty: 0.25, + spacing_distortion: 0.40, + slur_shape_penalty: 0.30, + beam_slope_penalty: 0.30, + vertical_density_penalty: 0.40, + system_break_penalty: 0.35, + page_fill_efficiency: 0.40, + casting_off_quality: 0.35, + symbol_density_uniformity: 0.40, +}; + +/// The `QualityFloorApproached` warning fraction (catalog §"The +/// `QualityFloorApproached` Warning", `req:qmc:floor-warning`): a solver SHOULD +/// warn for axis `k` when `k`'s computed value exceeds **0.8×** the applicable +/// threshold — the one selected by the solve's [`SolverProfile`] +/// ([`profile_thresholds`]). The warning is diagnostic: emitting it does not +/// change the solve's status. +pub const QUALITY_FLOOR_FRACTION: f64 = 0.8; + +/// The threshold column a **conformance tier** is evaluated against on the +/// reference suite (catalog Chapter 5): `Minimal` has its own relaxed column; +/// `Standard` the professional column; `Advanced` imposes the Standard column +/// on the nine normative axes (plus per-extension thresholds, +/// `req:qmc:advanced`, which this table does not model). `Stub` is below every +/// conformance tier and is evaluated against nothing — it computes no metrics +/// and passes no suite. +pub fn tier_thresholds(tier: SolverTier) -> Option<&'static MetricThresholds> { + match tier { + SolverTier::Stub => None, + SolverTier::Minimal => Some(&MINIMAL_THRESHOLDS), + SolverTier::Standard | SolverTier::Advanced => Some(&STANDARD_THRESHOLDS), + } +} + +/// The threshold column a **registered profile** selects (catalog Chapter 6, +/// `req:qmc:profiles`): `Draft` → the Minimal column (few warnings, fast +/// iteration); `Standard` and `Publication` → the Standard column (no column +/// tighter than Standard is ratified in v0.1). This is the column the solver's +/// own `QualityFloorApproached` diagnostics reference during ordinary solves; +/// suite evaluation at a claimed tier always uses that *tier's* column +/// ([`tier_thresholds`]). +pub fn profile_thresholds(profile: SolverProfile) -> &'static MetricThresholds { + match profile { + SolverProfile::Draft => &MINIMAL_THRESHOLDS, + SolverProfile::Standard | SolverProfile::Publication => &STANDARD_THRESHOLDS, + } +} + +impl QualityMetricVector { + /// The vector's value for one normative axis, by its + /// [`QualityMetricKind`] (catalog Table "kind-mapping": each kind names + /// exactly one vector field). + pub fn axis(&self, kind: QualityMetricKind) -> NormalizedMetric { + match kind { + QualityMetricKind::Collision => self.collision_penalty, + QualityMetricKind::Spacing => self.spacing_distortion, + QualityMetricKind::SlurShape => self.slur_shape_penalty, + QualityMetricKind::BeamSlope => self.beam_slope_penalty, + QualityMetricKind::VerticalDensity => self.vertical_density_penalty, + QualityMetricKind::SystemBreak => self.system_break_penalty, + QualityMetricKind::PageFill => self.page_fill_efficiency, + QualityMetricKind::CastingOff => self.casting_off_quality, + QualityMetricKind::SymbolDensity => self.symbol_density_uniformity, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::solver::TieBreakingWeights; + + #[test] + fn normalization_is_the_catalogs_clamped_linear_map() { + assert_eq!(normalize(0.0, 0.5).0, 0.0); + assert_eq!(normalize(0.25, 0.5).0, 0.5); + assert_eq!(normalize(0.5, 0.5).0, 1.0); + // At and beyond the anchor clamps to the worst-tolerable 1.0. + assert_eq!(normalize(3.0, 0.5).0, 1.0); + } + + #[test] + #[should_panic(expected = "finite and non-negative")] + fn normalization_rejects_a_negative_raw() { + let _ = normalize(-0.1, 0.5); + } + + #[test] + fn minimal_is_uniformly_more_permissive_than_standard() { + // Catalog Table "tier-thresholds": "Minimal is uniformly more + // permissive than Standard on every axis." + for kind in QUALITY_METRIC_KINDS { + assert!( + MINIMAL_THRESHOLDS.axis(kind) > STANDARD_THRESHOLDS.axis(kind), + "{kind:?}" + ); + // Both columns are valid NormalizedMetric bounds. + assert!((0.0..=1.0).contains(&MINIMAL_THRESHOLDS.axis(kind))); + assert!((0.0..=1.0).contains(&STANDARD_THRESHOLDS.axis(kind))); + } + } + + #[test] + fn the_minimal_column_excludes_the_unmeasured_placeholder() { + // Catalog Chapter 5 rationale: "a solver reporting the unmeasured 1.0 + // placeholder cannot pass the Minimal suite" — measuring is part of + // the Minimal claim. + let unmeasured = QualityMetricVector::unmeasured(); + assert!(QUALITY_METRIC_KINDS + .iter() + .any(|&k| unmeasured.axis(k).0 > MINIMAL_THRESHOLDS.axis(k))); + } + + #[test] + fn tier_and_profile_columns_map_per_the_catalog() { + // Tiers (catalog ch5): Minimal has its own column; Standard and + // Advanced share the Standard column; Stub is evaluated against nothing. + assert_eq!(tier_thresholds(SolverTier::Stub), None); + assert_eq!( + tier_thresholds(SolverTier::Minimal), + Some(&MINIMAL_THRESHOLDS) + ); + assert_eq!( + tier_thresholds(SolverTier::Standard), + Some(&STANDARD_THRESHOLDS) + ); + assert_eq!( + tier_thresholds(SolverTier::Advanced), + Some(&STANDARD_THRESHOLDS) + ); + // Profiles (catalog ch6): Draft → Minimal column; Standard and + // Publication → Standard column; Standard is the default profile. + assert_eq!( + profile_thresholds(SolverProfile::Draft), + &MINIMAL_THRESHOLDS + ); + assert_eq!( + profile_thresholds(SolverProfile::Standard), + &STANDARD_THRESHOLDS + ); + assert_eq!( + profile_thresholds(SolverProfile::Publication), + &STANDARD_THRESHOLDS + ); + assert_eq!(SolverProfile::default(), SolverProfile::Standard); + } + + #[test] + fn default_tie_breaking_weights_are_the_catalogs_normative_defaults() { + // Catalog Chapter 4 (`req:qmc:weights`): every one of the nine weights + // defaults to 1.0 — blessing the implementation's existing `Default`. + let w = TieBreakingWeights::default(); + for value in [ + w.collision, + w.spacing, + w.slur_shape, + w.beam_slope, + w.vertical_density, + w.system_break, + w.page_fill, + w.casting_off, + w.symbol_density, + ] { + assert_eq!(value, 1.0); + } + } + + #[test] + fn every_axis_has_a_positive_anchor() { + for kind in QUALITY_METRIC_KINDS { + assert!(r_worst(kind) > 0.0, "{kind:?}"); + } + } +} diff --git a/crates/epiphany-layout-ir/src/solver.rs b/crates/epiphany-layout-ir/src/solver.rs index 51cd85c..1e684e0 100644 --- a/crates/epiphany-layout-ir/src/solver.rs +++ b/crates/epiphany-layout-ir/src/solver.rs @@ -13,11 +13,12 @@ //! constraints declared it stays a renderable passthrough but claims no //! satisfaction (see [`StubSolver`]). //! -//! **Quality-metric *computation* is deliberately not implemented** (QUICKSTART: -//! "only the interface — don't implement quality metrics"): the +//! **The stub computes no quality metrics** (QUICKSTART: "only the interface — +//! don't implement quality metrics"): the //! [`QualityMetricVector`]/[`NormalizedMetric`] *types* and the -//! [`TieBreakingWeights`] exist (the interface requires them), but the -//! normalization functions of the Quality Metric Catalog are not. The +//! [`TieBreakingWeights`] exist (the interface requires them), and the Quality +//! Metric Catalog's normative anchors and threshold tables are transcribed in +//! [`crate::quality`] for solvers that do measure (`epiphany-engrave`). The //! `StubSolver` is not a conformant solver and passes no reference suite, so it //! reports the [`SolverTier::Stub`] tier (the honest non-conformance rung, below //! `Minimal`) and an all-worst metric vector. Those values are deliberately @@ -90,8 +91,9 @@ pub struct SolverVersion(pub u32); /// The conformance profile under which to solve (Chapter 9 §"The Solver /// Interface": `SolverConfig.profile` — selects metric thresholds and the active -/// constraint/extension set). The per-profile thresholds live in the Quality -/// Metric Catalog, deferred with the quality metrics. +/// constraint/extension set). The registered profile catalog and each profile's +/// threshold column are the Quality Metric Catalog's Chapter 6, transcribed as +/// [`crate::quality::profile_thresholds`]. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] pub enum SolverProfile { /// Fast, low-quality (draft) profile. @@ -104,8 +106,9 @@ pub enum SolverProfile { } /// Tie-breaking weights among layouts of equivalent quality (Chapter 9 -/// §"Quality Metrics": `TieBreakingWeights`). The normative defaults live in the -/// Quality Metric Catalog (deferred); v0 defaults every weight to `1.0`. +/// §"Quality Metrics": `TieBreakingWeights`). The normative defaults are the +/// Quality Metric Catalog's Chapter 4: every weight `1.0` — exactly this +/// type's [`Default`]. #[derive(Copy, Clone, PartialEq, Debug)] pub struct TieBreakingWeights { pub collision: f64, @@ -206,13 +209,14 @@ pub struct ExtensionMetric { } /// The quality metric vector for a layout (Chapter 9 §"Quality Metrics": -/// `QualityMetricVector`). v0 carries the type but computes **no** values: an -/// interface-only solver reports the conservative all-worst placeholder +/// `QualityMetricVector`). An interface-only solver that computes no metrics +/// reports the conservative all-worst placeholder /// ([`QualityMetricVector::unmeasured`], every metric `1.0`), never a measured /// value, so a caller cannot mistake an unmeasured layout for a good one. (The /// derived [`Default`] is all-`0.0`/nominal-best and is *not* what the stub -/// reports; the normalization functions of the Quality Metric Catalog are -/// deferred.) +/// reports.) A measuring solver computes each axis per the Quality Metric +/// Catalog's formulas, normalized through [`crate::quality::normalize`] with +/// the catalog's pinned anchors ([`crate::quality::anchors`]). #[derive(Clone, PartialEq, Debug, Default)] pub struct QualityMetricVector { pub collision_penalty: NormalizedMetric, diff --git a/crates/epiphany-testkit/Cargo.toml b/crates/epiphany-testkit/Cargo.toml index 51d0243..5d75fc3 100644 --- a/crates/epiphany-testkit/Cargo.toml +++ b/crates/epiphany-testkit/Cargo.toml @@ -16,11 +16,17 @@ epiphany-ops.workspace = true # now drives the real crate instead of an in-tree stub. epiphany-layout-ir.workspace = true -# The Chapter 10 performance benches (worklist F1) are the only dev-dependency -# user: the budget-gate logic itself lives in `src/budget.rs` on plain `std`, -# so the library builds without criterion. +# The Chapter 10 performance benches (worklist F1) run under criterion; the +# budget-gate logic itself lives in `src/budget.rs` on plain `std`, so the +# library builds without it. The editor-core + engrave pair drives the +# multi-system click-to-insert regression test (`tests/multisystem_click.rs`) +# over the real casting-off engraver — dev-only, so the library dependency +# graph is unchanged (engrave's own dev-dependency on this crate makes the +# cycle dev-only, which cargo permits). [dev-dependencies] criterion.workspace = true +epiphany-editor-core.workspace = true +epiphany-engrave.workspace = true # Drives the whole suite at scale outside the unit-test timeout — the analogue # of epiphany-determinism's `fuzz_roundtrip` and epiphany-bundle's `fuzz_crash`. diff --git a/crates/epiphany-testkit/DECISIONS.md b/crates/epiphany-testkit/DECISIONS.md index 3b2b4cc..0829d5d 100644 --- a/crates/epiphany-testkit/DECISIONS.md +++ b/crates/epiphany-testkit/DECISIONS.md @@ -211,3 +211,53 @@ once ≥3 ambiguities accumulate (same rule as v0 → Pass 11). Agent H's landin contributed five candidates (P12-H1…P12-H5, recorded in `crates/epiphany-core/DECISIONS.md`), which crosses the threshold, so the batch is open. F does not resolve these; F collects them. + +## F5 — The Reference Suite harness (`src/reference_suite.rs`, 2026-07) + +The Reference Suite companion (v0.1.0) charters the v0.1 entry set — six +scores named by reference-implementation **builder and seed** — and its +non-normative Harness Binding chapter says the executable binding "is +delivered with the reference implementation." This module is that binding, +in the F0 shape: a library module holding the machinery +(`entries`/`evaluate_minimal`/`table`), asserted by +`tests/reference_suite.rs` with **one test per entry** so a failure names its +entry, plus pins for the entry-set shape, the declared A4 default geometry +(the companion's solve-configuration requirement — asserted against +`Engraver::default().geometry()`), and RS-2's builder identity +(`corpus gen_valid_score_rich` ≡ `generators::valid_score_rich(0xF302)`, +byte-for-byte). + +**Solver-parametric on purpose.** The library module takes +`&dyn ConstraintSolver`; the integration test supplies the real `Engraver`. +This keeps `epiphany-engrave` a dev-only dependency (the library dependency +graph is unchanged, mirroring the multi-system click test) while the harness +itself stays reusable against any solver claiming Minimal. + +**The four-condition pass rule, with the F1 Pass/Xfail discipline on +condition 4.** `evaluate_minimal` asserts the companion's per-entry rule +exactly: hard-constraint satisfaction (renderable, non-partial, +`satisfied_hard_constraints`, nothing unsatisfied); internal determinism +(byte-identical `ResolvedLayoutIR` canonical bytes *and* bitwise-identical +metric vectors across repeated solves); a well-formed, accurate report +(Minimal tier claim, every axis a finite `[0,1]` value, never the unmeasured +placeholder); and every axis at or below the Quality Metric Catalog's +Minimal-column threshold. The first real measurement (2026-07) found exactly +one miss: **RS-1's `casting_off_quality` = 1.0** — greedy first-fit leaves a +two-measure stub last system (glyph spans ~78.6/18.8 staff spaces, width CV +0.61 ≥ the 0.5 anchor), the exact failure the axis exists to catch, on a +layout that is byte-locked by the render goldens. Waiving it silently would +fake conformance; failing the workspace would misreport a ratified-spec +tension as a code bug. So condition 4 carries the budget harness's (F1) +discipline: the miss is a **documented `minimal_xfail` row asserted to still +miss** — if the layout or the catalog changes and RS-1 comes within +threshold, the harness fails demanding promotion (remove the row), exactly +like an F1 `XPASS`. The row's resolution is spec-side and tracked in +`epiphany-engrave/DECISIONS.md`'s Pass-12 candidates (casting-off balance +pass with golden regeneration, or a QMC anchor/threshold minor revision — +the catalog's own threshold-tuning open question anticipated this). + +**Eligibility tiers vs. solver tiers.** The corpus `Tier` +(Common/Edge/Torture) is Agent H's eligibility taxonomy; the suite's tiers +(Minimal/Standard) are Chapter 9 conformance tiers. The companion carries the +same caution; the harness resolves corpus entries by `name` string only and +never reads the corpus tier. diff --git a/crates/epiphany-testkit/src/lib.rs b/crates/epiphany-testkit/src/lib.rs index f670e20..083ea0a 100644 --- a/crates/epiphany-testkit/src/lib.rs +++ b/crates/epiphany-testkit/src/lib.rs @@ -108,6 +108,12 @@ pub mod roundtrip; pub mod corpus; pub mod prepass_harness; +// Phase 3: the Reference Suite companion's executable binding — the six-entry +// v0.1 set solved by the real engraver under the declared configuration, with +// the four-condition Minimal pass rule (`tests/reference_suite.rs` asserts it +// per entry). Library-module-per-harness per DECISIONS F0. +pub mod reference_suite; + pub mod convergence; pub mod equivocation; pub mod migration; diff --git a/crates/epiphany-testkit/src/reference_suite.rs b/crates/epiphany-testkit/src/reference_suite.rs new file mode 100644 index 0000000..5a0655e --- /dev/null +++ b/crates/epiphany-testkit/src/reference_suite.rs @@ -0,0 +1,335 @@ +//! The **Reference Suite harness** — the executable binding of the *Reference +//! Suite* companion (v0.1.0) the companion's non-normative Harness Binding +//! chapter says is "delivered with the reference implementation". +//! +//! The six v0.1 entries ([`entries`]) are transcribed from the companion's +//! Chapter 3 ("The v0.1 Entry Set"), each named by reference-implementation +//! builder and seed exactly as the companion's score-referencing rule +//! (`req:refsuite:referencing`) prescribes: a seeded test-kit builder +//! (`fixtures::ten_measure_single_staff`), a core generator the corpus pins +//! (`generators::valid_score_rich` seed `0xF302` = corpus entry +//! `gen_valid_score_rich`), or a zero-argument corpus entry resolved by its +//! `name` string. [`evaluate_minimal`] runs one entry through the standard +//! pipeline (`to_logical` → `to_constrained` → `solve`) under the companion's +//! declared solve configuration — the solver's documented default A4 geometry +//! and the default [`SolverConfig`] (`Standard` profile, unbounded +//! deterministic budget, the Quality Metric Catalog's default tie-breaking +//! weights) — and asserts the companion's four-condition per-entry pass rule +//! (`req:refsuite:pass`) at the **Minimal** tier: +//! +//! 1. every hard constraint satisfied (renderable, non-partial status); +//! 2. internal determinism (byte-identical `ResolvedLayoutIR` canonical bytes +//! *and* bitwise-identical metric vectors across repeated solves); +//! 3. a well-formed, diagnostically accurate report (`Minimal` tier claim, +//! a metric vector that is valid — finite, in `[0,1]` — and computed, +//! never the all-worst placeholder); +//! 4. every normative metric at or below the Quality Metric Catalog's +//! Minimal-column threshold for its axis (no v0.1 entry overrides them). +//! +//! Condition 4 carries the budget harness's **Pass/Xfail discipline** +//! ([`crate::budget`], DECISIONS F1): an axis listed in +//! [`SuiteEntry::minimal_xfail`] is a *documented, measured* threshold miss — +//! asserted to still miss, so the marking cannot rot (an `XPASS` fails the +//! harness demanding promotion), and reported for spec-side resolution rather +//! than silently waived. v0.1 ships exactly one such row (RS-1's +//! `casting_off_quality`; see the entry and the crate's DECISIONS). +//! +//! Following the testkit's library-module-per-harness policy (DECISIONS F0), +//! this module holds the machinery and `tests/reference_suite.rs` asserts it — +//! one test per entry, so a failure names its entry. The module is +//! solver-parametric (the crate's dependency on `epiphany-engrave` is +//! dev-only); the integration test supplies the real `Engraver`. + +use epiphany_core::Score; +use epiphany_layout_ir::{ + to_constrained, to_logical, ConstraintSolver, QualityMetricKind, QualityMetricVector, + SolveStatus, SolverConfig, SolverTier, MINIMAL_THRESHOLDS, QUALITY_METRIC_KINDS, +}; + +use crate::corpus::corpus; +use crate::fixtures; + +/// A documented Minimal-threshold miss (the budget harness's `Xfail` shape): +/// the axis and the reason it is expected to exceed its threshold today. +#[derive(Copy, Clone, Debug)] +pub struct MinimalXfail { + pub axis: QualityMetricKind, + pub reason: &'static str, +} + +/// One v0.1 suite entry (Reference Suite companion, Table "entries"). +pub struct SuiteEntry { + /// The companion's entry id (`RS-1` … `RS-6`). + pub id: &'static str, + /// The companion's entry title. + pub title: &'static str, + /// The companion's construction reference (builder + seed / corpus name). + pub construction: &'static str, + /// Deterministic score construction per that reference. + pub build: fn() -> Score, + /// Documented, measured Minimal-threshold misses (see the module docs). + pub minimal_xfail: &'static [MinimalXfail], +} + +fn corpus_score(name: &str) -> Score { + let fixture = corpus() + .into_iter() + .find(|fixture| fixture.name == name) + .unwrap_or_else(|| panic!("corpus entry {name} named by the Reference Suite is missing")); + (fixture.build)() +} + +fn rs1() -> Score { + fixtures::ten_measure_single_staff(0x000A_11CE) +} +fn rs2() -> Score { + // The companion cites `generators::valid_score_rich(0xF302)`, "identically + // reachable as the test-kit corpus entry `gen_valid_score_rich`" — resolve + // through the corpus and pin the identity in `rs2_construction_reproduces`. + corpus_score("gen_valid_score_rich") +} +fn rs3() -> Score { + corpus_score("b_flat_major_scale") +} +fn rs4() -> Score { + corpus_score("two_voice_counterpoint") +} +fn rs5() -> Score { + corpus_score("notes_and_rests") +} +fn rs6() -> Score { + corpus_score("meter_three_four") +} + +/// The companion's cited construction for RS-2, for the identity pin: the +/// corpus entry must reproduce this score graph bit-for-bit. +pub fn rs2_cited_builder() -> Score { + epiphany_core::generators::valid_score_rich(0xF302) +} + +/// The v0.1 entry set, exactly the six entries of the companion's +/// Table "entries". Every entry is required at the Minimal tier; the same six +/// constitute the Standard subset (not asserted here: no implementation claims +/// Standard as of this suite version). +pub fn entries() -> Vec { + vec![ + SuiteEntry { + id: "RS-1", + title: "Ten-measure single staff", + construction: "fixtures::ten_measure_single_staff(0x000A_11CE)", + build: rs1, + // Measured 2026-07 (engrave v2, QMC v0.1.0 anchors): greedy + // first-fit casting-off leaves a two-measure stub last system + // (glyph spans ~78.6 vs ~18.8 staff spaces, width CV 0.61 >= the + // 0.5 anchor, clamped to 1.0 > the Minimal 0.90 threshold). The + // metric is truthful — this is the exact stub-last-line failure + // the catalog says the axis exists to catch — and the layout is + // byte-locked by the render goldens, so the miss is recorded here + // pending either a casting-off balance pass (a coordinated + // golden-regenerating change) or a QMC anchor/threshold revision + // (the catalog's own threshold-tuning open question). See + // DECISIONS.md. + minimal_xfail: &[MinimalXfail { + axis: QualityMetricKind::CastingOff, + reason: "greedy first-fit leaves a stub last system (width CV \ + 0.61 >= the 0.5 anchor -> 1.0 > 0.90); tracked for a \ + casting-off balance pass or a QMC v0.1 threshold \ + revision", + }], + }, + SuiteEntry { + id: "RS-2", + title: "Rich multi-region score", + construction: "generators::valid_score_rich(0xF302) = corpus gen_valid_score_rich", + build: rs2, + minimal_xfail: &[], + }, + SuiteEntry { + id: "RS-3", + title: "B-flat major scale", + construction: "corpus b_flat_major_scale", + build: rs3, + minimal_xfail: &[], + }, + SuiteEntry { + id: "RS-4", + title: "Two-voice counterpoint", + construction: "corpus two_voice_counterpoint", + build: rs4, + minimal_xfail: &[], + }, + SuiteEntry { + id: "RS-5", + title: "Notes and rests", + construction: "corpus notes_and_rests", + build: rs5, + minimal_xfail: &[], + }, + SuiteEntry { + id: "RS-6", + title: "Three-four meter line", + construction: "corpus meter_three_four", + build: rs6, + minimal_xfail: &[], + }, + ] +} + +/// What evaluating one entry measured, for the report table. +pub struct EntryOutcome { + pub id: &'static str, + pub title: &'static str, + pub status: SolveStatus, + pub metrics: QualityMetricVector, + /// Axes that exceeded their Minimal threshold under a documented xfail row. + pub xfailed: Vec, +} + +/// Evaluates one entry's four-condition **Minimal** pass +/// (`req:refsuite:pass`), panicking with the entry's id on the first violated +/// condition. Returns the measured outcome for the report table. +pub fn evaluate_minimal(solver: &dyn ConstraintSolver, entry: &SuiteEntry) -> EntryOutcome { + let id = entry.id; + // The solve the entry declares: the standard pipeline under the default + // solver configuration (`req:refsuite:solve-config`). The page geometry is + // the solver's construction-time parameter; the integration test pins the + // reference solver's default to the companion's declared A4 numbers. + let constrained = to_constrained(&to_logical(&(entry.build)())); + let config = SolverConfig::default(); + let report = solver.solve(&constrained, &config); + let again = solver.solve(&constrained, &config); + + // Condition 3 (tier claim): the suite evaluates a Minimal-tier claim. + assert_eq!( + solver.tier(), + SolverTier::Minimal, + "{id}: the solver under test must claim the Minimal tier" + ); + + // Condition 1: every hard constraint satisfied. Unsatisfiable and + // budget-exhausted partial solves are failures, not exemptions. + assert!( + matches!( + report.status, + SolveStatus::Solved | SolveStatus::SolvedWithWarnings + ), + "{id}: not a fully solved layout: {:?}", + report.status + ); + assert!( + report.satisfied_hard_constraints, + "{id}: hard constraints unsatisfied" + ); + assert!( + report.unsatisfied_constraints.is_empty(), + "{id}: unsatisfied constraints reported: {:?}", + report.unsatisfied_constraints + ); + + // Condition 2: internal determinism — byte-identical canonical layout and + // bitwise-identical metric vectors across repeated identical solves. + assert_eq!( + report.layout.canonical_bytes(), + again.layout.canonical_bytes(), + "{id}: repeated solves differ in canonical ResolvedLayoutIR bytes" + ); + for kind in QUALITY_METRIC_KINDS { + assert_eq!( + report.metric_vector.axis(kind).0.to_bits(), + again.metric_vector.axis(kind).0.to_bits(), + "{id}: repeated solves differ on {kind:?}" + ); + } + + // Condition 3 (report accuracy): the metric vector is valid and computed + // per the Quality Metric Catalog — never a placeholder. + for kind in QUALITY_METRIC_KINDS { + let value = report.metric_vector.axis(kind).0; + assert!( + value.is_finite() && (0.0..=1.0).contains(&value), + "{id}: {kind:?} = {value} is not a valid NormalizedMetric" + ); + } + assert_ne!( + report.metric_vector, + QualityMetricVector::unmeasured(), + "{id}: the metric vector is the unmeasured placeholder" + ); + + // Condition 4: every normative metric within the Minimal threshold column + // (no v0.1 entry declares an override), under the Pass/Xfail discipline. + let mut xfailed = Vec::new(); + for kind in QUALITY_METRIC_KINDS { + let value = report.metric_vector.axis(kind).0; + let threshold = MINIMAL_THRESHOLDS.axis(kind); + match entry.minimal_xfail.iter().find(|row| row.axis == kind) { + Some(row) => { + assert!( + value > threshold, + "{id}: XPASS on {kind:?} ({value} <= {threshold}) — the measured miss \ + was resolved; promote the entry by removing its xfail row ({})", + row.reason + ); + xfailed.push(kind); + } + None => assert!( + value <= threshold, + "{id}: {kind:?} = {value} exceeds its Minimal threshold {threshold}" + ), + } + } + + EntryOutcome { + id: entry.id, + title: entry.title, + status: report.status, + metrics: report.metric_vector, + xfailed, + } +} + +/// One aligned report row per outcome, for the printed metric table (run the +/// integration test with `--nocapture` to see it). +pub fn table(outcomes: &[EntryOutcome]) -> String { + let mut out = String::new(); + out.push_str(&format!( + "{:<5} {:<24} {:>9} {:>9} {:>9} {:>9} {:>9} {:>9} {:>9} {:>9} {:>9}\n", + "entry", + "title", + "collision", + "spacing", + "slur", + "beam", + "vertical", + "sysbreak", + "pagefill", + "castoff", + "density" + )); + for outcome in outcomes { + let value = |kind: QualityMetricKind| { + let v = outcome.metrics.axis(kind).0; + if outcome.xfailed.contains(&kind) { + format!("{v:.4}*") + } else { + format!("{v:.4}") + } + }; + out.push_str(&format!( + "{:<5} {:<24} {:>9} {:>9} {:>9} {:>9} {:>9} {:>9} {:>9} {:>9} {:>9}\n", + outcome.id, + outcome.title, + value(QualityMetricKind::Collision), + value(QualityMetricKind::Spacing), + value(QualityMetricKind::SlurShape), + value(QualityMetricKind::BeamSlope), + value(QualityMetricKind::VerticalDensity), + value(QualityMetricKind::SystemBreak), + value(QualityMetricKind::PageFill), + value(QualityMetricKind::CastingOff), + value(QualityMetricKind::SymbolDensity), + )); + } + out.push_str("(* = documented Minimal xfail row, asserted to still miss)\n"); + out +} diff --git a/crates/epiphany-testkit/tests/multisystem_click.rs b/crates/epiphany-testkit/tests/multisystem_click.rs new file mode 100644 index 0000000..fa4a161 --- /dev/null +++ b/crates/epiphany-testkit/tests/multisystem_click.rs @@ -0,0 +1,255 @@ +//! Regression: **multi-system click-to-insert** over the real casting-off +//! engraver. +//! +//! Casting-off wraps the ten-measure QUICKSTART fixture into two stacked +//! systems, each baked back to the page's left margin. The editor's click +//! resolution predated casting-off and assumed one flat system, which broke in +//! two ways: the horizontal inverse gathered a region's anchors across *every* +//! system (an x-non-monotonic list, mapping system-2 clicks to system-1 times), +//! and the vertical inverse always found the staff's *first* line segment (the +//! only one that keeps the manifestation stable id), reading system-2 clicks +//! against system 1's origin. These tests pin the system-aware resolution end +//! to end — `EditorSession` over `Engraver::default()` — where the editor-core +//! unit tests use hand-built geometry. + +use std::collections::BTreeMap; + +use epiphany_core::{ + CmnNominal, EventPosition, IdentifiedPitch, MusicalDuration, MusicalPosition, PitchId, + RationalTime, Score, TypedObjectId, +}; +use epiphany_editor_core::{EditorSession, GridResolution}; +use epiphany_engrave::Engraver; +use epiphany_layout_ir::{Point, Rect, ResolvedSystem}; +use epiphany_testkit::fixtures::ten_measure_single_staff; + +/// Every pitch's metric onset, from the score graph (the fixture is 40 quarter +/// notes at `k/4`, one pitch per event — the ground truth a click must recover). +fn pitch_onsets(score: &Score) -> BTreeMap { + let mut onsets = BTreeMap::new(); + let mut pitches: Vec<&IdentifiedPitch> = Vec::new(); + for (_, _, voice) in score.voices() { + for eid in &voice.events { + let Some(event) = score.events.get(*eid) else { + continue; + }; + let EventPosition::Musical(at) = event.position() else { + continue; + }; + pitches.clear(); + event.collect_identified_pitches(&mut pitches); + for ip in &pitches { + onsets.insert(ip.id, at.clone()); + } + } + } + onsets +} + +/// Whether `point` lies within `rect`, edges included. +fn rect_contains(rect: &Rect, point: Point) -> bool { + point.x.0 >= rect.origin.x.0 + && point.x.0 <= rect.origin.x.0 + rect.size.width.0 + && point.y.0 >= rect.origin.y.0 + && point.y.0 <= rect.origin.y.0 + rect.size.height.0 +} + +/// The noteheads rendered inside `bounds`, as `(x, pitch)` in ascending x — the +/// non-synthesized pitch-sourced glyphs the horizontal inverse anchors on. +fn noteheads_within(session: &EditorSession, bounds: &Rect) -> Vec<(f32, PitchId)> { + let mut heads: Vec<(f32, PitchId)> = session + .resolved() + .glyphs + .iter() + .filter(|g| g.provenance.synthesis.is_none() && rect_contains(bounds, g.position)) + .filter_map(|g| match g.provenance.source { + TypedObjectId::Pitch(pid) => Some((g.position.x.0, pid)), + _ => None, + }) + .collect(); + heads.sort_by(|a, b| a.0.total_cmp(&b.0)); + heads +} + +/// A system's staff step origin: the bottom line of its (single) staff **in this +/// system**. The staff record's provenance is that line segment's — the exact +/// world y the vertical inverse must measure from. +fn system_origin_y(session: &EditorSession, system: &ResolvedSystem) -> f32 { + let staff = system + .staves + .first() + .expect("a cast system records its staff"); + session + .resolved() + .strokes + .iter() + .find(|s| s.provenance.stable_id == staff.provenance.stable_id) + .map(|s| s.from.y.0) + .expect("the staff record's bottom line renders") +} + +fn open_two_system_session() -> (EditorSession, BTreeMap) { + let score = ten_measure_single_staff(1); + let onsets = pitch_onsets(&score); + let session = + EditorSession::open(score, Box::new(Engraver::default())).expect("the fixture renders"); + (session, onsets) +} + +fn quarter() -> GridResolution { + GridResolution::quarter() +} + +fn eighth() -> GridResolution { + GridResolution { + step: MusicalDuration(RationalTime::new(1, 8).expect("1/8 is a valid duration")), + } +} + +/// The two systems the A4 default geometry casts the fixture into (the +/// documented `PageGeometry::default` behavior), page 1 top-first. +fn two_systems(session: &EditorSession) -> (Rect, Rect) { + let page = session + .resolved() + .pages + .first() + .expect("the engraver emits a page"); + assert_eq!( + page.systems.len(), + 2, + "A4 default geometry wraps the ten-measure fixture into two systems" + ); + (page.systems[0].bounding_box, page.systems[1].bounding_box) +} + +#[test] +fn a_system_2_click_resolves_to_its_own_time_and_pitch() { + let (session, onsets) = open_two_system_session(); + let (sys1, sys2) = two_systems(&session); + + let sys1_heads = noteheads_within(&session, &sys1); + let sys2_heads = noteheads_within(&session, &sys2); + assert!(!sys1_heads.is_empty() && !sys2_heads.is_empty()); + // Sanity: casting really split the run — system 2 carries strictly later music. + let sys1_max = sys1_heads.iter().map(|(_, p)| &onsets[p]).max().unwrap(); + let sys2_min = sys2_heads.iter().map(|(_, p)| &onsets[p]).min().unwrap(); + assert!(sys2_min > sys1_max, "system 2 renders later onsets"); + + let system2 = &session.resolved().pages[0].systems[1]; + let origin = system_origin_y(&session, system2); + + // (b)+(c): a known system-2 notehead — click its x, one staff space above the + // *system-2* bottom line, and the horizontal inverse must answer that note's + // onset (not the system-1 time the flat x-scale would give, since system 2 + // restarts at the left margin under system 1's x range). + let (x, pid) = sys2_heads[0]; + let expected = onsets[&pid].clone(); + let click = Point::new(x, origin + 1.0); + let gp = session + .position_at(click, &quarter()) + .expect("a metric position under the click"); + assert_eq!( + gp.position, expected, + "a system-2 notehead click snaps to that note's onset" + ); + + // The vertical inverse measures from system 2's own bottom line: one staff + // space above it under the fixture's default treble clef is G4. (Against + // system 1's origin — the regression — the same point is ~20 staff spaces + // below the staff.) + let pitch = session + .staff_pitch_at(click) + .expect("a staff under the click"); + assert_eq!( + (pitch.nominal, pitch.octave), + (CmnNominal::G, 4), + "one staff space above the system-2 bottom line is G4 under treble" + ); + + // A click in the inter-system gutter (just above system 2's box) still + // resolves — the nearest system by vertical distance — and inverts on + // system 2's x-scale. + let gutter = Point::new(x, sys2.origin.y.0 + sys2.size.height.0 + 0.25); + assert!( + gutter.y.0 < sys1.origin.y.0 - 0.25, + "the gutter point is outside both boxes, nearer system 2" + ); + let from_gutter = session + .position_at(gutter, &quarter()) + .expect("a between-systems click still resolves"); + assert_eq!(from_gutter.position, expected); +} + +#[test] +fn a_system_1_click_still_resolves_as_before() { + let (session, onsets) = open_two_system_session(); + let (sys1, _) = two_systems(&session); + let system1 = &session.resolved().pages[0].systems[0]; + let origin = system_origin_y(&session, system1); + + // (e): every system-1 notehead resolves exactly as in the flat layout — its + // own onset, and G4 one staff space above the bottom line. + for (x, pid) in noteheads_within(&session, &sys1) { + let click = Point::new(x, origin + 1.0); + let gp = session + .position_at(click, &quarter()) + .expect("a metric position under the click"); + assert_eq!(gp.position, onsets[&pid], "the click snaps to the onset"); + let pitch = session + .staff_pitch_at(click) + .expect("a staff under the click"); + assert_eq!((pitch.nominal, pitch.octave), (CmnNominal::G, 4)); + } +} + +#[test] +fn insert_into_system_2_empty_space_lands_on_the_clicked_slot() { + let (mut session, onsets) = open_two_system_session(); + let (_, sys2) = two_systems(&session); + + // (d): the fixture fills every quarter, so the empty space inside system 2 is + // the off-beat between two of its noteheads. Click halfway between two + // adjacent system-2 anchors on an eighth grid: the inverse interpolates to + // the half-beat, and the insert must land there — a system-1 inversion would + // put it four-plus measures early. + let (click_x, origin, expected) = { + let heads = noteheads_within(&session, &sys2); + assert!(heads.len() >= 2, "system 2 renders adjacent noteheads"); + let (ax, a_pid) = heads[0]; + let (bx, _) = heads[1]; + let system2 = &session.resolved().pages[0].systems[1]; + let expected = onsets[&a_pid].clone() + + MusicalDuration(RationalTime::new(1, 8).expect("1/8 is a valid duration")); + ( + (ax + bx) / 2.0, + system_origin_y(&session, system2), + expected, + ) + }; + let click = Point::new(click_x, origin + 1.0); + let placed = session + .position_at(click, &eighth()) + .expect("a metric position under the click"); + assert_eq!( + placed.position, expected, + "the click names the off-beat slot" + ); + + let outcome = session + .insert_note_at(click, &eighth()) + .expect("the insert applies (make-room splits the covered quarter)"); + assert!(outcome.graph_changed); + + // The new eighth note exists at the clicked musical position. + let eighth_dur = MusicalDuration(RationalTime::new(1, 8).expect("1/8 is a valid duration")); + let landed = session.score().voices().any(|(_, _, voice)| { + voice.events.iter().any(|eid| { + session.score().events.get(*eid).is_some_and(|event| { + event.position() == &EventPosition::Musical(expected.clone()) + && event.duration() + == &epiphany_core::EventDuration::Musical(eighth_dur.clone()) + }) + }) + }); + assert!(landed, "the inserted eighth note sits at the clicked slot"); +} diff --git a/crates/epiphany-testkit/tests/reference_suite.rs b/crates/epiphany-testkit/tests/reference_suite.rs new file mode 100644 index 0000000..bbd9a4c --- /dev/null +++ b/crates/epiphany-testkit/tests/reference_suite.rs @@ -0,0 +1,113 @@ +//! The Reference Suite companion's v0.1 entry set, asserted against the real +//! engraver: one test per entry (so a failure names its entry), the entry-set +//! shape, the declared solve configuration, and the printed metric table +//! (visible with `--nocapture`). +//! +//! The harness machinery lives in `epiphany_testkit::reference_suite` +//! (library-module-per-harness, DECISIONS F0); this file binds it to +//! `epiphany_engrave::Engraver` — the crate's dev-only dependency — under the +//! companion's declared default A4 geometry and default solver configuration. + +use epiphany_engrave::Engraver; +use epiphany_testkit::reference_suite::{entries, evaluate_minimal, rs2_cited_builder, table}; + +/// The suite's solver under test: the reference engraver at its documented +/// default geometry — which `default_geometry_is_the_declared_a4_configuration` +/// pins to the companion's declared numbers. +fn solver() -> Engraver { + Engraver::default() +} + +fn run(id: &str) { + let entry_set = entries(); + let entry = entry_set + .iter() + .find(|entry| entry.id == id) + .expect("entry id"); + let outcome = evaluate_minimal(&solver(), entry); + print!("{}", table(std::slice::from_ref(&outcome))); +} + +#[test] +fn the_v01_entry_set_is_the_companions_six() { + // Reference Suite companion, Table "entries": exactly RS-1..RS-6, all + // required at Minimal (the harness evaluates every one; none is optional). + let ids: Vec<&str> = entries().iter().map(|entry| entry.id).collect(); + assert_eq!(ids, ["RS-1", "RS-2", "RS-3", "RS-4", "RS-5", "RS-6"]); +} + +#[test] +fn default_geometry_is_the_declared_a4_configuration() { + // The companion's solve-configuration requirement declares every v0.1 + // entry solved at A4 portrait / 8 mm staff: page 105 x 148.5 staff spaces, + // 7.5-staff-space margins, hence a 90 x 133.5 content area. The reference + // engraver's default *is* that geometry; the suite runs on it. + let geometry = solver().geometry(); + assert_eq!(geometry.size.width.0, 105.0); + assert_eq!(geometry.size.height.0, 148.5); + for margin in [ + geometry.margins.top, + geometry.margins.right, + geometry.margins.bottom, + geometry.margins.left, + ] { + assert_eq!(margin.0, 7.5); + } + assert_eq!(geometry.content_width(), 90.0); + assert_eq!(geometry.content_height(), 133.5); +} + +#[test] +fn rs2_construction_reproduces_the_cited_builder() { + // The companion cites `generators::valid_score_rich(0xF302)` and says the + // corpus entry `gen_valid_score_rich` pins the same seed: the two must + // reproduce the same score graph bit-for-bit (builder-and-seed + // referencing, `req:refsuite:referencing`). + let via_corpus = (entries()[1].build)(); + assert_eq!( + via_corpus.canonical_bytes(), + rs2_cited_builder().canonical_bytes() + ); +} + +#[test] +fn rs1_ten_measure_single_staff_passes_minimal() { + run("RS-1"); +} + +#[test] +fn rs2_rich_multi_region_score_passes_minimal() { + run("RS-2"); +} + +#[test] +fn rs3_b_flat_major_scale_passes_minimal() { + run("RS-3"); +} + +#[test] +fn rs4_two_voice_counterpoint_passes_minimal() { + run("RS-4"); +} + +#[test] +fn rs5_notes_and_rests_passes_minimal() { + run("RS-5"); +} + +#[test] +fn rs6_meter_three_four_passes_minimal() { + run("RS-6"); +} + +#[test] +fn minimal_suite_metric_table() { + // The whole suite in one aligned table (run with --nocapture): the + // measured per-entry metric values behind the per-entry passes above. + let solver = solver(); + let outcomes: Vec<_> = entries() + .iter() + .map(|entry| evaluate_minimal(&solver, entry)) + .collect(); + print!("{}", table(&outcomes)); +} diff --git a/spec/PASS12_BATCH.md b/spec/PASS12_BATCH.md index f49bb2f..7935d4c 100644 --- a/spec/PASS12_BATCH.md +++ b/spec/PASS12_BATCH.md @@ -66,6 +66,8 @@ code instead is the failure mode this batch exists to prevent. | P12-I8 | `epiphany-engrave` I | Break-constraint satisfaction predicate: implemented as "a `SystemBreakAt`/`PageBreakAt` is satisfied iff the final layout starts a system/page at that slot" (a region-first slot is trivially satisfied). Ch7/Ch9 never define satisfaction for break constraints; ratify the predicate. | G / Pass 12 (solver) | | P12-I9 | `epiphany-layout-ir` I | Honouring a user break must attribute the decision to its override (`DecisionSource::UserOverride(id)`), but constraints carry no override identity; implemented via a `ConstrainedLayoutIR.break_origins` sidecar populated by `to_constrained`. Bless the sidecar or widen the normalized constraint record. | G / Pass 12 (solver) | | P12-I10 | `epiphany-layout-ir` I | System-spanning strokes split at system boundaries need synthesized provenance for continuation segments; implemented as `SynthesisKind::Registered(SYSTEM_CONTINUATION_SYNTHESIS)` with a deterministic `(original, ordinal)` instance key. Add a first-class continuation synthesis kind or bless the registered id. | G / Pass 12 (provenance) | +| P12-I11 | `epiphany-engrave` I | RS-1 honestly fails the Minimal casting-off threshold under the reference engraver (measured 1.0 vs 0.90): greedy first-fit leaves a two-measure stub last system (width CV 0.6145 ≥ the 0.5 anchor). Resolutions: an engrave-side casting-off balance pass (golden regeneration + solver version bump), or a Quality Metric Catalog minor revision (anchor rescale / Minimal relaxation / RS-1 per-entry override). Tracked bidirectionally by the suite harness's asserted Xfail row. | G / Pass 12 (quality) | +| P12-I12 | `epiphany-engrave` I | The Standard-tier spacing floor warns on short healthy scores: 3–8-column entries with a wide clef/key lead measure spacing CV 0.36–0.41 > the 0.32 Standard floor. Consider a lead-aware or duration-aware refinement of the spacing_distortion raw measurement (the catalog's optical-spacing open question). | G / Pass 12 (quality) | ## Not yet open elsewhere @@ -76,6 +78,9 @@ spec-compliance audit follow-up, alongside K3/K4). The 2026-07 Push-3 wiring work added C1..C4 (re-anchoring), D1 (bundle operation index), and E1..E5 (edit barriers). The Phase-3 first tranche (casting-off + K1 schema-fill + value-restoring undo, 2026-07-02) added C5, K8..K11, and I7..I10. +The second tranche (Quality Metric Catalog 0.1.0 + Reference Suite 0.1.0 + +real engraver metrics + the suite harness + the multi-system click fix, +2026-07-03) added I11..I12. Agent J's Binary Format companion now exists (`spec/binary_format.tex`, v0.1.0): it ratified the P12-D1/E1/E2/E3 inputs (struck through above) and discharged the crates' provisional-codec notes diff --git a/spec/quality_metric_catalog.pdf b/spec/quality_metric_catalog.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2eb9ade20660446921b8a7da12c786dd584e9a54 GIT binary patch literal 138999 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 literal 0 HcmV?d00001 diff --git a/spec/quality_metric_catalog.tex b/spec/quality_metric_catalog.tex new file mode 100644 index 0000000..87904bf --- /dev/null +++ b/spec/quality_metric_catalog.tex @@ -0,0 +1,1308 @@ +% !TEX program = xelatex +% +% Epiphany --- Quality Metric Catalog (companion specification) +% Companion to the Core Specification. Compile with XeLaTeX. +% +% This document is versioned independently of the Core Specification +% (independent semver; see the Versioning note in the front matter). Its preamble +% is intentionally a self-contained copy of the core specification's preamble so +% the two documents build independently; factoring a shared preamble file is a +% later cleanup, not a v0.1 deliverable. + +\documentclass[11pt,letterpaper]{report} + +% --------------------------------------------------------------------------- +% Packages +% --------------------------------------------------------------------------- +\usepackage{fontspec} +\usepackage{geometry} +\geometry{ + letterpaper, + top=1.05in, + bottom=1.05in, + left=1.15in, + right=1.15in, + headheight=15pt +} + +\usepackage[english]{babel} +\usepackage{microtype} +\usepackage{parskip} +\usepackage{xcolor} +\usepackage{hyperref} +\usepackage{enumitem} +\usepackage{titlesec} +\usepackage{fancyhdr} +\usepackage{booktabs} +\usepackage{array} +\usepackage{longtable} +\usepackage{listings} +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{tcolorbox} +\tcbuselibrary{breakable, skins} + +% --------------------------------------------------------------------------- +% Color palette (shared with the core specification) +% --------------------------------------------------------------------------- +\definecolor{epiphanyteal}{HTML}{1A4044} +\definecolor{epiphanygold}{HTML}{8E6E2E} +\definecolor{epiphanyink}{HTML}{1F1B16} +\definecolor{epiphanyslate}{HTML}{6B6660} +\definecolor{epiphanycream}{HTML}{F8F4ED} +\definecolor{epiphanymist}{HTML}{ECE8E0} +\definecolor{epiphanycode}{HTML}{2A2520} +\definecolor{epiphanycrimson}{HTML}{7A2424} + +\hypersetup{ + colorlinks=true, + linkcolor=epiphanyteal, + citecolor=epiphanyteal, + urlcolor=epiphanygold, + pdftitle={Epiphany --- Quality Metric Catalog}, + pdfauthor={The Epiphany Project}, + pdfsubject={Quality Metric Catalog companion for the Epiphany music notation platform}, + pdfkeywords={music notation, engraving, quality metrics, normalization, conformance tiers, solver profiles}, + bookmarksnumbered=true, + bookmarksopen=true +} + +% --------------------------------------------------------------------------- +% Typography (shared with the core specification) +% --------------------------------------------------------------------------- +\setmainfont{TeX Gyre Pagella}[Numbers={OldStyle, Proportional}, Ligatures={TeX, Common}] +\setsansfont{TeX Gyre Heros}[Scale=0.94, Ligatures={TeX, Common}] +\setmonofont{TeX Gyre Cursor}[Scale=0.88, Ligatures={TeX}] +\newfontfamily\titlefont{TeX Gyre Pagella}[Numbers={OldStyle}, Ligatures={TeX, Common}] +\newcommand{\tablenums}[1]{{\addfontfeatures{Numbers={Lining,Tabular}}#1}} +\newcommand{\sectionsc}[1]{{\addfontfeatures{Letters=SmallCaps}#1}} + +% --------------------------------------------------------------------------- +% Section styling (shared with the core specification) +% --------------------------------------------------------------------------- +\titleformat{\chapter}[display] + {\normalfont\filright} + {\raggedright\color{epiphanygold}\fontsize{14pt}{16pt}\selectfont + \scshape Chapter\ \thechapter} + {16pt} + {\raggedright\color{epiphanyteal}\fontsize{32pt}{36pt}\selectfont\bfseries} + [\vspace{4pt}{\color{epiphanygold}\rule{2in}{0.6pt}}] +\titlespacing*{\chapter}{0pt}{-20pt}{30pt} +\titleformat{\section} + {\normalfont\Large\bfseries\color{epiphanyteal}} + {\color{epiphanygold}\thesection}{1em}{} +\titleformat{\subsection} + {\normalfont\large\bfseries\color{epiphanyteal}} + {\color{epiphanygold}\thesubsection}{1em}{} +\titleformat{\subsubsection} + {\normalfont\normalsize\bfseries\color{epiphanyink}} + {\thesubsubsection}{1em}{} + +% --------------------------------------------------------------------------- +% Headers and footers (shared with the core specification) +% --------------------------------------------------------------------------- +\pagestyle{fancy} +\fancyhf{} +\renewcommand{\headrulewidth}{0pt} +\renewcommand{\footrulewidth}{0pt} +\fancyhead[L]{\small\scshape\color{epiphanyslate}Epiphany --- Quality Metric Catalog} +\fancyhead[R]{\small\itshape\color{epiphanyslate}\leftmark} +\fancyfoot[C]{\small\color{epiphanyslate}\thepage} +\renewcommand{\headrule}{ + \color{epiphanygold!50}\hrule width\headwidth height 0.4pt + \vspace{1pt} + \color{epiphanygold!30}\hrule width\headwidth height 0.2pt +} + +% --------------------------------------------------------------------------- +% Code listing style (shared with the core specification) +% --------------------------------------------------------------------------- +\lstdefinelanguage{Rust}{ + keywords={fn,let,mut,pub,struct,enum,impl,trait,for,in,if,else,match,return, + use,mod,crate,self,Self,as,where,move,async,await,const,static, + ref,type,unsafe,extern,dyn,box,break,continue,loop,while}, + keywordstyle=\color{epiphanyteal}\bfseries, + ndkeywords={i8,i16,i32,i64,i128,u8,u16,u32,u64,u128,f32,f64,bool,char,str, + String,Vec,Option,Result,Box,Rc,Arc,HashMap,BTreeMap, + NonZeroU16,NonZeroU32,NonZeroU64,Duration,Timestamp}, + ndkeywordstyle=\color{epiphanygold}\bfseries, + sensitive=true, + comment=[l]{//}, + morecomment=[s]{/*}{*/}, + commentstyle=\color{epiphanyslate}\itshape, + stringstyle=\color{epiphanycrimson}, + morestring=[b]", + morestring=[b]' +} +\lstset{ + basicstyle=\ttfamily\small\color{epiphanycode}, + backgroundcolor=\color{epiphanycream}, + frame=leftline, + rulecolor=\color{epiphanygold!60}, + framesep=8pt, + framerule=1.5pt, + xleftmargin=10pt, + xrightmargin=4pt, + breaklines=true, + showstringspaces=false, + numberstyle=\tiny\color{epiphanyslate}, + numbersep=10pt, + captionpos=b, + aboveskip=10pt, + belowskip=10pt, + language=Rust +} + +% --------------------------------------------------------------------------- +% Custom environments (shared with the core specification) +% --------------------------------------------------------------------------- +\newtcolorbox{openquestion}[1][]{ + enhanced, breakable, + colback=epiphanymist, colframe=epiphanycrimson, + fonttitle=\bfseries\color{white}, title={\scshape\hspace{2pt}Open Question}, + coltitle=white, colbacktitle=epiphanycrimson, + arc=1pt, boxrule=0pt, leftrule=2pt, + left=10pt, right=10pt, top=8pt, bottom=8pt, + attach boxed title to top left={xshift=0pt, yshift=0pt}, + boxed title style={arc=0pt, sharp corners, boxrule=0pt, left=6pt, right=8pt, top=2pt, bottom=2pt}, + #1 +} +\newtcolorbox{rationale}[1][]{ + enhanced, breakable, + colback=epiphanymist, colframe=epiphanyteal, + fonttitle=\bfseries\color{white}, title={\scshape\hspace{2pt}Rationale}, + coltitle=white, colbacktitle=epiphanyteal, + arc=1pt, boxrule=0pt, leftrule=2pt, + left=10pt, right=10pt, top=8pt, bottom=8pt, + attach boxed title to top left={xshift=0pt, yshift=0pt}, + boxed title style={arc=0pt, sharp corners, boxrule=0pt, left=6pt, right=8pt, top=2pt, bottom=2pt}, + #1 +} +\newtcolorbox{requirement}[1][]{ + enhanced, breakable, + colback=white, colframe=epiphanygold, + fonttitle=\bfseries\color{white}, title={\scshape\hspace{2pt}Requirement}, + coltitle=white, colbacktitle=epiphanygold, + arc=1pt, boxrule=0pt, leftrule=2pt, + left=10pt, right=10pt, top=8pt, bottom=8pt, + attach boxed title to top left={xshift=0pt, yshift=0pt}, + boxed title style={arc=0pt, sharp corners, boxrule=0pt, left=6pt, right=8pt, top=2pt, bottom=2pt}, + #1 +} +\newtcolorbox{nongoal}[1][]{ + enhanced, breakable, + colback=epiphanymist, colframe=epiphanyslate, + fonttitle=\bfseries\color{white}, title={\scshape\hspace{2pt}Non-Goal}, + coltitle=white, colbacktitle=epiphanyslate, + arc=1pt, boxrule=0pt, leftrule=2pt, + left=10pt, right=10pt, top=8pt, bottom=8pt, + attach boxed title to top left={xshift=0pt, yshift=0pt}, + boxed title style={arc=0pt, sharp corners, boxrule=0pt, left=6pt, right=8pt, top=2pt, bottom=2pt}, + #1 +} + +\newcommand{\MUST}{\textbf{MUST}} +\newcommand{\MUSTNOT}{\textbf{MUST}\nobreak\ \textbf{NOT}} +\newcommand{\SHOULD}{\textbf{SHOULD}} +\newcommand{\SHOULDNOT}{\textbf{SHOULD}\nobreak\ \textbf{NOT}} +\newcommand{\MAY}{\textbf{MAY}} + +\setlist[itemize]{topsep=2pt, itemsep=3pt, parsep=0pt} +\setlist[enumerate]{topsep=2pt, itemsep=3pt, parsep=0pt} +\setlist[description]{topsep=2pt, itemsep=5pt, parsep=0pt} +\AtBeginDocument{\color{epiphanyink}} + +% --------------------------------------------------------------------------- +% Document +% --------------------------------------------------------------------------- +\begin{document} + +\begin{titlepage} + \thispagestyle{empty} + \centering + \vspace*{2.2in} + {\color{epiphanygold}\rule{3in}{0.8pt}}\\[18pt] + {\titlefont\fontsize{34pt}{38pt}\selectfont\color{epiphanyteal}\bfseries Epiphany}\\[10pt] + {\Large\scshape\color{epiphanyslate}Quality Metric Catalog}\\[6pt] + {\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt] + {\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt] + {\normalsize\color{epiphanyink}Version 0.1.0 --- Phase 3 (the normative metric set: formal definitions, normalization, weights, tier thresholds, profile registry)}\\[4pt] + {\small\color{epiphanyslate}Normative for the metrics and thresholds it defines} + \vfill +\end{titlepage} + +\tableofcontents + +% =========================================================================== +\chapter{About This Companion} +\label{ch:about} + +The \emph{Quality Metric Catalog} is a companion to the Epiphany Core +Specification. It fulfils the delegation of the core specification's +\sectionsc{Companion Specifications} appendix --- the section labeled +\texttt{sec:deferred:companions} --- which charters this document to +deliver ``per-metric normalization functions mapping raw measurements to +\texttt{NormalizedMetric} values, default tie-breaking weights, per-tier +metric thresholds, and the formal definition of each quality metric in the +normative metric set.'' + +This release (v0.1.0) delivers all four chartered items, plus two small +registries the core specification names but defers here: + +\begin{itemize} + \item the formal definition of each of the \textbf{nine normative metric + axes} --- the measured phenomenon, the raw measurement over resolved + layout geometry, and the normalization function with its pinned anchor + constant (Chapter~\ref{ch:metrics}); + \item the \textbf{default tie-breaking weights} + (Chapter~\ref{ch:weights}); + \item the \textbf{per-tier metric thresholds} for the Minimal and + Standard conformance tiers, the Advanced-tier extension rule, and the + \texttt{QualityFloorApproached} warning trigger + (Chapter~\ref{ch:thresholds}); + \item the \texttt{QualityMetricKind} enumeration, which the core + specification references (as the payload of the + \texttt{QualityFloorApproached} solver warning) but never lists + (Section~\ref{sec:model:kind}); + \item the \textbf{registered \texttt{SolverProfile} catalog}, which the + core specification's vocabulary appendix explicitly defers to this + companion (Chapter~\ref{ch:profiles}); + \item the \textbf{Standard-tier constraint family} declaration, which the + core specification's Standard-tier requirement points at this companion + (Section~\ref{sec:thresholds:families}). +\end{itemize} + +This document does \emph{not} cover: + +\begin{itemize} + \item the reference suite's test scores, per-tier entry inclusion, and any + per-entry threshold overrides --- those are the \emph{Reference Suite} + companion's; + \item performance conformance (edit traces, frame budgets) --- the + \emph{Performance Reference Suite} companion's; + \item the reference solving algorithm --- the non-normative + \emph{Reference Algorithm} companion's. +\end{itemize} + +\section{Relationship to the Core Specification} +\label{sec:about:relationship} + +This companion does not restate the metric framework; it \emph{references} +it. The framework --- the \texttt{NormalizedMetric} validity rules (finite, +in $[0.0, 1.0]$, lower is better), the \texttt{QualityMetricVector} field +set, extension metrics, the \texttt{TieBreakingWeights} structure, the +Pareto-frontier design target, the conformance-tier ladder, and the +suite-based conformance model --- is the core specification's Chapter~9 +(\sectionsc{The Constraint Solver Interface}, the \texttt{ch:solver} +chapter), in particular its \sectionsc{Quality Metrics}, +\sectionsc{Conformance Tiers}, and \sectionsc{Conformance: The Reference +Suite} sections (\texttt{sec:solver:quality}, +\texttt{sec:solver:tiers}, \texttt{sec:solver:conformance}). + +Two core requirements bind this document into the conformance story: + +\begin{itemize} + \item The core \sectionsc{Quality Metrics} normalization requirement: + ``Per-metric normalization functions (mapping raw measurements to + $[0.0,1.0]$) are specified in the Quality Metric Catalog companion + document. Implementations \MUST{} use the catalog's normalization; + arbitrary normalization is non-conforming.'' Chapter~\ref{ch:metrics} + is that normalization. + \item The core tie-breaking requirement: ``Tie-breaking weights \MUST{} + have normative defaults specified in the Quality Metric Catalog.'' + Chapter~\ref{ch:weights} is those defaults. +\end{itemize} + +Where this document and a ratified core requirement disagree, \textbf{the +core requirement governs} and the discrepancy is a defect in this document. +Graph types, the layout IR pipeline +(\texttt{LogicalLayoutIR} $\rightarrow$ \texttt{ConstrainedLayoutIR} +$\rightarrow$ \texttt{ResolvedLayoutIR}), +the spring-slot and vertical-band models, and the built-in +\texttt{LayoutConstraint} kinds are the core specification's Chapter~7 +(\texttt{ch:layout-ir}); this document's formulas range over those +structures without redefining them. + +\begin{rationale} +\textbf{Versioning.} This companion is versioned independently of the core +specification (independent semver), like the Operation Catalog and the +Binary Format companions. Metric definitions and thresholds are expected to +be tuned on a faster cadence than the solver framework: threshold revisions +informed by reference-suite experience are \textsc{minor} revisions here and +require no core-spec change, while a change to the metric \emph{field set} +(a new normative axis) is a core-spec change first, mirrored here. +\end{rationale} + +\section{Conformance} +\label{sec:about:conformance} + +The metric definitions, normalization functions, default weights, threshold +tables, and profile registry in this document are \textbf{normative}. A +solver that reports a \texttt{QualityMetricVector} computed by any function +other than the ones defined here is non-conforming, per the core +\sectionsc{Quality Metrics} requirement quoted above. + +Conformance \emph{claims} are evaluated on the Reference Suite companion's +entry set: a solver claiming tier $T$ must keep every normative metric +within tier $T$'s threshold (Chapter~\ref{ch:thresholds}) on every suite +entry required at tier $T$. This document defines \emph{what is measured +and how much is tolerable}; the Reference Suite companion defines +\emph{on which scores}. + +Two boundaries of that claim, developed in Chapter~\ref{ch:model}: + +\begin{itemize} + \item Metric values are \emph{diagnostic}, never canonical state + (Section~\ref{sec:model:diagnostic}). No byte of canonical document + state depends on them. + \item Numeric agreement across implementations is \emph{not} required + (Section~\ref{sec:model:determinism}). The cross-implementation + contract is threshold conformance, not value equality. +\end{itemize} + +% =========================================================================== +\chapter{The Metric Model} +\label{ch:model} + +\section{Diagnostic Status} +\label{sec:model:diagnostic} + +The quality metric vector rides on the \texttt{SolveReport} (core +specification Chapter~9, \sectionsc{The Solver Report}: +\texttt{SolveReport.metric\_vector}). It describes the layout; it is not +part of the layout. The solver's canonical output --- +\texttt{ResolvedLayoutIR} --- carries no metric field, and the core +specification's observational-equivalence rule is stated over +\texttt{ResolvedLayoutIR} bytes alone. + +\begin{requirement} +\label{req:qmc:diagnostic} +Quality metrics are \textbf{diagnostic output}, never canonical state. + +\begin{itemize} + \item A \texttt{QualityMetricVector} appears only on the + \texttt{SolveReport}. The canonical serialized form of + \texttt{ResolvedLayoutIR} \MUSTNOT{} contain quality-metric values, + and a \texttt{NormalizedMetric} value \MUSTNOT{} enter canonical + document bytes by any other path. + \item Two solves whose \texttt{ResolvedLayoutIR} values are + byte-identical under canonical serialization are observationally + equivalent regardless of their metric vectors. A metric value + \MUSTNOT{} be an input to any canonical-state decision. +\end{itemize} +\end{requirement} + +\begin{rationale} +Keeping metrics off the canonical path is what makes them safely +improvable. A solver revision that measures more honestly (or a catalog +revision that tunes a formula) changes reports, warnings, and conformance +verdicts --- but not one byte of any document. The reference implementation +already has this shape: \texttt{ResolvedLayoutIR} has no metric field, the +\texttt{SolveReport} is never serialized, and no consumer reads the vector +to make a state decision. +\end{rationale} + +\section{Determinism and Numeric Agreement} +\label{sec:model:determinism} + +\begin{requirement} +\label{req:qmc:determinism} +Within one implementation version, metric computation \MUST{} be +deterministic: identical solve inputs (the same +\texttt{ConstrainedLayoutIR}, configuration, and declared page geometry) +\MUST{} yield bitwise-identical \texttt{QualityMetricVector} values. + +Across implementations (and across versions of one implementation), +numeric agreement is \textbf{not} required. Two conforming solvers \MAY{} +report different metric values for the same score; the +cross-implementation contract is the core specification's four +suite-conformance conditions --- in particular, that every metric is within +the claimed tier's threshold on every required suite entry --- not value +equality. + +Metric values are ordinary IEEE~754 \texttt{f64} values subject to the +core \texttt{NormalizedMetric} validity rules (finite, in $[0.0, 1.0]$). +This document imposes no additional quantization, rounding, or evaluation- +order discipline on their computation. +\end{requirement} + +\begin{rationale} +Different conforming solvers legitimately produce different layouts, so +their metric values differ even under identical formulas; demanding numeric +agreement would smuggle cross-implementation layout equality in through the +diagnostics. Within-implementation determinism, by contrast, is load- +bearing: reproducible reports are what make threshold conformance testable +and regressions attributable. +\end{rationale} + +\section{The Normative Metric Set and \texttt{QualityMetricKind}} +\label{sec:model:kind} + +The nine normative metric axes are the nine non-extension fields of the +core specification's \texttt{QualityMetricVector}. The core references a +\texttt{QualityMetricKind} enumeration (the payload of +\texttt{SolverWarningKind::QualityFloorApproached}) without listing it; +this catalog pins it. + +\begin{requirement} +\label{req:qmc:kind} +The \texttt{QualityMetricKind} enumeration is exactly: + +\begin{lstlisting}[language=Rust] +pub enum QualityMetricKind { + Collision, + Spacing, + SlurShape, + BeamSlope, + VerticalDensity, + SystemBreak, + PageFill, + CastingOff, + SymbolDensity, +} +\end{lstlisting} + +Each kind names exactly one \texttt{QualityMetricVector} field and exactly +one \texttt{TieBreakingWeights} field, per +Table~\ref{tab:kind-mapping}. Extension metrics are not +\texttt{QualityMetricKind} values; they are identified by +\texttt{ExtensionMetricId}. +\end{requirement} + +\begin{table}[h] +\centering +\small +\begin{tabular}{lll} +\toprule +\textbf{Kind} & \textbf{Vector field} & \textbf{Weight field} \\ +\midrule +\texttt{Collision} & \texttt{collision\_penalty} & \texttt{collision} \\ +\texttt{Spacing} & \texttt{spacing\_distortion} & \texttt{spacing} \\ +\texttt{SlurShape} & \texttt{slur\_shape\_penalty} & \texttt{slur\_shape} \\ +\texttt{BeamSlope} & \texttt{beam\_slope\_penalty} & \texttt{beam\_slope} \\ +\texttt{VerticalDensity} & \texttt{vertical\_density\_penalty} & \texttt{vertical\_density} \\ +\texttt{SystemBreak} & \texttt{system\_break\_penalty} & \texttt{system\_break} \\ +\texttt{PageFill} & \texttt{page\_fill\_efficiency} & \texttt{page\_fill} \\ +\texttt{CastingOff} & \texttt{casting\_off\_quality} & \texttt{casting\_off} \\ +\texttt{SymbolDensity} & \texttt{symbol\_density\_uniformity} & \texttt{symbol\_density} \\ +\bottomrule +\end{tabular} +\caption{The nine normative axes: kind, vector field, tie-breaking weight.} +\label{tab:kind-mapping} +\end{table} + +A naming caution: three field names read as higher-is-better words --- +\begin{center} +\texttt{page\_fill\_efficiency},\ \texttt{casting\_off\_quality},\ \texttt{symbol\_density\_uniformity} +\end{center} +--- but they are not. The core specification fixes the orientation of +\emph{every} normative metric ($0.0$ best, $1.0$ worst tolerable), and +the definitions in Chapter~\ref{ch:metrics} follow it: each of the three +measures a \emph{deficiency} (unfilled page area, uneven casting-off, +uneven density). + +\section{The Measurement Domain} +\label{sec:model:domain} + +Every raw measurement in Chapter~\ref{ch:metrics} is a deterministic +function of three inputs, all of which exist at the moment the solver +assembles its \texttt{SolveReport}: + +\begin{enumerate} + \item the solve's resolved output $L$ (a \texttt{ResolvedLayoutIR}: + positioned glyphs with bounding boxes, strokes, and the page/system + tree); + \item the solve's constrained input $C$ (a \texttt{ConstrainedLayoutIR}: + horizontal spring slots, vertical bands, declared constraints); + \item the declared page geometry the solve was configured with: the + content width $W$ and content height $H$, in staff spaces. (The score + graph has no home for page geometry yet --- the core names + \texttt{Canvas.layout\_defaults} without defining it, tracked as + Pass-12 row P12-I7 --- so the geometry is a solver parameter, and the + Reference Suite companion requires each suite entry to declare it.) +\end{enumerate} + +Notation used throughout Chapter~\ref{ch:metrics}: + +\begin{itemize} + \item $G$ is the set of resolved glyphs of $L$. For $g \in G$, the + \emph{ink box} $B(g) = [l_g, r_g] \times [b_g, t_g]$ is the glyph's + bounding box translated to its resolved position. Strokes (staff + lines, ledger lines, stems, barline strokes) are not members of $G$. + \item $\mathit{sys}(g)$ is the system that positioned $g$ under the + solve's casting-off; every system belongs to exactly one region, and + every page carries an ordered list of systems. A glyph positioned by + no system belongs to no collision pair and to no per-system + aggregate. + \item $\mathit{slot}(g)$ is the horizontal spring slot of $g$'s source + glyph in $C$ --- the musical time column that groups a chord's + noteheads with their accidentals, dots, and same-column symbols. + \item For a system $s$: its \emph{columns} are the ascending sequence of + distinct resolved baseline $x$-coordinates + $x^s_1 < \dots < x^s_{m_s}$ of the glyph-bearing slots realized in + $s$; its \emph{advances} are $a^s_i = x^s_{i+1} - x^s_i$ for + $i = 1, \dots, m_s - 1$ (equivalently, the spacing pass's per-slot + advances); $w_s$ is the width of $s$'s content extent (the horizontal + span of the ink boxes assigned to $s$); $n_s$ is the number of glyphs + assigned to $s$. + \item $\mathrm{CV}(v_1, \dots, v_k)$, defined for $k \ge 2$ with + $\operatorname{mean} > 0$, is the population standard deviation + divided by the arithmetic mean. + \item The arithmetic mean over an \emph{empty} index set is defined as + $0$ (this is the vacuous-geometry rule of + Section~\ref{sec:model:vacuous} in aggregate form). +\end{itemize} + +Because numeric agreement across implementations is not required +(Requirement~\ref{req:qmc:determinism}), a formula may reference the +solve's \emph{own} internal assignments --- which glyph landed in which +system, which columns a system realizes --- without threatening +conformance: the assignments are deterministic within an implementation +version, which is all the metric contract needs. No formula in this +catalog requires an optical-spacing model, font metrics beyond glyph +bounding boxes, or any geometry class the layout pipeline does not +produce. + +\section{The Vacuous-Geometry Rule} +\label{sec:model:vacuous} + +Each axis in Chapter~\ref{ch:metrics} names its \emph{contributing units}: +the glyph pairs, systems, pages, gaps, slurs, or beams the raw measurement +ranges over. A layout may simply not contain a metric's geometry class --- +no drawn slurs, no beams, a single system, a single page. + +\begin{requirement} +\label{req:qmc:vacuous} +When a normative metric's contributing-unit set is empty for a given +layout, the metric \MUST{} evaluate to exactly $0.0$: where there is +nothing to penalize, the penalty is zero. In particular: + +\begin{itemize} + \item a layout containing no drawn slur geometry has + $\texttt{slur\_shape\_penalty} = 0.0$; + \item a layout containing no drawn beam geometry has + $\texttt{beam\_slope\_penalty} = 0.0$; + \item a region cast onto a single system contributes no units to + \texttt{system\_break\_penalty}, \texttt{casting\_off\_quality}, or + \texttt{symbol\_density\_uniformity}, and a single-page layout + contributes no units to \texttt{page\_fill\_efficiency} --- each axis + degenerates exactly as its per-axis definition states; + \item a solve configured without positive finite content bounds ($W$ or + $H$) has an empty contributing set for every axis defined over that + bound. +\end{itemize} + +An implementation \MUSTNOT{} report a sentinel (such as $1.0$) for a +metric whose contributing-unit set is empty. The all-worst placeholder +vector remains correct only for a solver that \emph{computes no metrics at +all} and claims no conformance tier (the core's \texttt{Stub} tier). +\end{requirement} + +\begin{openquestion} +\textbf{The notated-but-unrendered honesty edge.} A score whose +\emph{source} notates slurs, engraved by a solver that draws no slur +geometry, scores $\texttt{slur\_shape\_penalty} = 0.0$ under this rule --- +the axis sees no drawn slurs and finds nothing to penalize, even though +the output is arguably \emph{worse} than a badly-drawn slur. v0.1 +deliberately pins vacuous-$0.0$: the metric axes evaluate the geometry the +solver produced, and \emph{rendering completeness} --- whether notated +content is realized at all --- is governed by constraint families and +visual acceptance testing, not by the quality metrics. Should a future +revision instead score notated-but-unrendered geometry classes at the +worst value, so that the metric vector cannot flatter an incomplete +renderer? Resolving this requires a normative definition of ``notated +content that demands drawn geometry,'' which does not exist yet. +\end{openquestion} + +\section{Normalization Form} +\label{sec:model:normalization} + +Every normative axis uses the same one-parameter normalization shape, so +that anchors --- not curve families --- are the entire tuning surface. + +\begin{requirement} +\label{req:qmc:normalization-form} +Each normative metric defines a raw measurement +$\mathit{raw} \ge 0$ (dimensionless, per its axis definition) and a pinned +anchor constant $R_{\mathrm{worst}} > 0$. The normalized value is the +clamped-linear map +\[ + n \;=\; \min\!\left(1,\; \frac{\mathit{raw}}{R_{\mathrm{worst}}}\right), +\] +so that $\mathit{raw} = 0$ (the ideal) normalizes to $0.0$ and +$\mathit{raw} \ge R_{\mathrm{worst}}$ (the worst-tolerable anchor and +beyond) normalizes to $1.0$. Implementations \MUST{} use the per-axis raw +measurements and anchors of Chapter~\ref{ch:metrics} exactly; per the core +specification, arbitrary normalization is non-conforming. Extension +metrics \MAY{} use other normalization shapes but \MUSTNOT{} change +orientation or range. +\end{requirement} + +% =========================================================================== +\chapter{The Nine Normative Metrics} +\label{ch:metrics} + +Each section below defines one axis under a fixed template: the +\emph{phenomenon} (what an engraver would point at), the \emph{contributing +units} (what the raw measurement ranges over --- the set whose emptiness +triggers Requirement~\ref{req:qmc:vacuous}), the \emph{raw measurement}, +and the \emph{normalization anchor} with a brief justification. All +lengths are in staff spaces; all raw measurements are dimensionless +ratios. + +Four axes measure horizontal-distribution phenomena at different +granularities, and the boundaries are deliberate: + +\begin{itemize} + \item \texttt{spacing\_distortion} is \emph{within-system} advance + regularity; + \item \texttt{system\_break\_penalty} is the \emph{per-break} absolute + cost of each chosen system break (looseness or overflow of + non-final systems); + \item \texttt{casting\_off\_quality} is \emph{across-system} width + evenness, including the final system (the stub-last-line failure); + \item \texttt{symbol\_density\_uniformity} is \emph{across-system} + crowding evenness (equal widths can hide very different symbol + densities). +\end{itemize} + +\section{\texttt{collision\_penalty}} +\label{sec:metrics:collision} + +\textbf{Phenomenon.} Overlapping ink between symbols that belong to +different musical time columns: a notehead striking the previous column's +accidental, a chord symbol over a barline, any cross-column ink contact. +Professional engraving contains none. + +\begin{requirement} +\label{req:qmc:collision} +\textbf{Contributing units:} unordered glyph pairs $\{g, h\} \subseteq G$ +with $\mathit{sys}(g) = \mathit{sys}(h)$ and +$\mathit{slot}(g) \ne \mathit{slot}(h)$. + +A pair \emph{collides} when its ink boxes intersect with positive area in +both axes: +\[ +\begin{gathered} + \min(r_g, r_h) - \max(l_g, l_h) > 0 + \quad\text{and}\\ + \min(t_g, t_h) - \max(b_g, b_h) > 0 . +\end{gathered} +\] +Edge-touching boxes do not collide. Pairs sharing a horizontal spring slot +are \textbf{excluded}: a column's internal cluster --- a chord's noteheads, +their accidentals, dots, and other same-slot symbols --- is arranged by the +constrained stage, and its legitimate internal ink contact is not a +spacing failure of the solver. Strokes are not glyphs and join no pair: +staff lines legitimately cross every notehead. + +\textbf{Raw measurement:} with $P$ the set of colliding pairs, +\[ + \mathit{raw} \;=\; \frac{|P|}{|G|} + \qquad (\mathit{raw} = 0 \text{ when } G = \emptyset). +\] + +\textbf{Normalization:} $R_{\mathrm{worst}} = 0.05$; +$n = \min(1, \mathit{raw} / 0.05)$. +\end{requirement} + +\begin{rationale} +The anchor says: one cross-column collision per twenty glyphs is +unmistakably broken layout --- the worst a report should be able to +distinguish. The count is divided by the glyph population, not by the pair +population, so that the measure does not vanish quadratically on large +scores: a score with one collision per page stays visible. The reference +pipeline evaluates overlap today only for \emph{declared} +\texttt{NoCollision} constraints; this axis is the full pairwise +same-system sweep over ink boxes, which is new but cheap work over data +the resolved layout already carries. +\end{rationale} + +\section{\texttt{spacing\_distortion}} +\label{sec:metrics:spacing} + +\textbf{Phenomenon.} Uneven horizontal distribution within a system: +columns bunched together here and stretched apart there, where the +underlying spring model asked for near-uniform advances. + +\begin{requirement} +\label{req:qmc:spacing} +\textbf{Contributing units:} systems $s$ with $m_s \ge 3$ (at least two +advances). + +\textbf{Raw measurement:} per unit, +$\mathit{raw}_s = \mathrm{CV}(a^s_1, \dots, a^s_{m_s - 1})$; the axis raw +value is the arithmetic mean of $\mathit{raw}_s$ over contributing units. + +\textbf{Normalization:} $R_{\mathrm{worst}} = 1.0$; +$n = \min(1, \mathit{raw})$. +\end{requirement} + +\begin{rationale} +A coefficient of variation of $1.0$ means the typical column advance +deviates from the mean by the whole mean --- spacing with no discernible +regularity. v0.1 defines \emph{geometric} regularity deliberately: the +reference spring model's preferred widths are uniform, so regular advances +are exactly what its ideal output looks like, and the collision minima +(accidental overhangs, wide columns) that legitimately perturb advances +are modest on realistic scores. +\end{rationale} + +\begin{openquestion} +\textbf{Optical spacing at the Standard tier.} Mature engraving spaces +columns proportionally to musical duration (with an optical correction), +not uniformly; under a duration-proportional model, this axis's ideal +would be ``advances proportional to the column's duration share,'' and a +perfectly optically-spaced line would score \emph{worse} than a uniform +one under the v0.1 definition. When the layout pipeline gains +duration-aware preferred widths, should the Standard tier redefine +$\mathit{raw}_s$ as deviation from the duration-proportional ideal while +Minimal keeps geometric regularity? v0.1 defines geometric regularity +only. +\end{openquestion} + +\section{\texttt{slur\_shape\_penalty}} +\label{sec:metrics:slur} + +\textbf{Phenomenon.} Badly-shaped slur arcs: flat, tape-like slurs or +bulging semicircles, measured against the shallow-arc norm of engraving +practice. + +\begin{requirement} +\label{req:qmc:slur} +\textbf{Contributing units:} drawn slur curves in $L$ with chord length +$c > 0$, where the \emph{chord} is the segment between the curve's +endpoints and the \emph{apex height} $h \ge 0$ is the maximum +perpendicular distance from the curve to its chord. + +\textbf{Raw measurement:} per unit, with arc ratio $\rho = h / c$, +\[ + \mathit{raw}_u \;=\; \max\bigl(0,\;\; 0.08 - \rho,\;\; \rho - 0.25\bigr), +\] +i.e.\ the shortfall below the ideal band $[0.08, 0.25]$ or the excess +above it; the axis raw value is the arithmetic mean over contributing +units. + +\textbf{Normalization:} $R_{\mathrm{worst}} = 0.25$; +$n = \min(1, \mathit{raw} / 0.25)$. +\end{requirement} + +\begin{rationale} +The band $[0.08, 0.25]$ brackets the shallow arcs engraving practice +prefers: an arc rising less than about $1/12$ of its span reads as a +straight line; one rising more than a quarter of its span begins to bulge. +The anchor makes a semicircular slur ($\rho = 0.5$, $\mathit{raw}_u = +0.25$) exactly worst-tolerable, and a completely flat slur ($\rho = 0$, +$\mathit{raw}_u = 0.08$) roughly a third of the way to failing. + +The v0.1 reference pipeline draws no slur geometry (slurs exist logically, +not as curves), so this axis evaluates to $0.0$ today under the +vacuous-geometry rule --- the definition is pinned now so that the first +implementation to draw slurs is measured from its first release. +\end{rationale} + +\section{\texttt{beam\_slope\_penalty}} +\label{sec:metrics:beam} + +\textbf{Phenomenon.} Over-steep beams. Engraving practice keeps beam +slants gentle regardless of the melodic interval they span. + +\begin{requirement} +\label{req:qmc:beam} +\textbf{Contributing units:} drawn beam segments in $L$ with horizontal +run $\Delta x > 0$ (endpoint-to-endpoint). + +\textbf{Raw measurement:} per unit, with absolute slope +$\sigma = |\Delta y| / \Delta x$, +\[ + \mathit{raw}_u \;=\; \max\bigl(0,\; \sigma - 0.25\bigr); +\] +the axis raw value is the arithmetic mean over contributing units. + +\textbf{Normalization:} $R_{\mathrm{worst}} = 0.25$; +$n = \min(1, \mathit{raw} / 0.25)$. +\end{requirement} + +\begin{rationale} +Slopes up to $0.25$ (about $14^\circ$) are penalty-free --- within the +range engraving manuals tolerate for short, wide-interval beams --- and the +anchor places $\sigma = 0.5$ (about $27^\circ$, roughly double any +published maximum) at worst-tolerable. Like the slur axis, this is pinned +ahead of implementation: the v0.1 reference pipeline draws no beam +geometry, so the axis evaluates to $0.0$ under the vacuous-geometry rule. +\end{rationale} + +\section{\texttt{vertical\_density\_penalty}} +\label{sec:metrics:vertical} + +\textbf{Phenomenon.} Vertical crowding or sprawl: inter-staff and +inter-system gaps realized far from the spacing the band model asked for. + +\begin{requirement} +\label{req:qmc:vertical} +\textbf{Contributing units:} vertical bands of $C$ of kind +\texttt{InterStaffGap} or \texttt{InterSystemGap} with preferred height +$p > 0$ that are realized in $L$ (the adjacent content they separate was +laid out). + +\textbf{Raw measurement:} per unit, with $r \ge 0$ the realized vertical +separation between the adjacent content extents the band separates +(measured in resolved coordinates), +\[ + \mathit{raw}_u \;=\; \frac{|r - p|}{p}; +\] +the axis raw value is the arithmetic mean over contributing units. + +\textbf{Normalization:} $R_{\mathrm{worst}} = 1.0$; +$n = \min(1, \mathit{raw})$. +\end{requirement} + +\begin{rationale} +A gap off by its own preferred size --- staves twice as far apart as asked, +or fully collapsed --- is unambiguous vertical failure; proportional +deviation makes one anchor serve both tight inter-staff gaps and wide +inter-system gaps. The v0.1 reference pipeline preserves constrained $y$ +verbatim (the vertical spring solve is deferred), so realized gaps equal +preferred gaps wherever bands are realized and the axis reports its +honest near-zero; the definition is what makes a future vertical solve +measurable. +\end{rationale} + +\section{\texttt{system\_break\_penalty}} +\label{sec:metrics:system-break} + +\textbf{Phenomenon.} Bad break choices, one system at a time: a non-final +system left loose (broken far short of the available width) or overfull +(content past the content width). + +\begin{requirement} +\label{req:qmc:system-break} +\textbf{Contributing units:} non-final systems --- for each region, every +system the casting-off produced except the region's last --- defined only +when the declared content width $W$ is finite and positive. + +\textbf{Raw measurement:} per unit, +\[ + \mathit{raw}_s \;=\; \frac{|W - w_s|}{W}, +\] +penalizing looseness ($w_s < W$) and overflow ($w_s > W$) alike; the axis +raw value is the arithmetic mean over contributing units. + +\textbf{Normalization:} $R_{\mathrm{worst}} = 0.5$; +$n = \min(1, \mathit{raw} / 0.5)$. + +A region cast onto a single system contributes no units (the break axis +degenerates to nothing-to-penalize, per +Requirement~\ref{req:qmc:vacuous}); the final system of each region is +never a unit, because a short last line is not a break failure. +\end{requirement} + +\begin{rationale} +Non-final systems half-empty on average --- or overflowing by half the +content width --- mark casting-off that has effectively failed, hence the +$0.5$ anchor. The raw quantities are exactly what the reference +casting-off pass already computes: per-system content extents against the +declared content width, with breaks chosen among barline candidates. +\end{rationale} + +\section{\texttt{page\_fill\_efficiency}} +\label{sec:metrics:page-fill} + +\textbf{Phenomenon.} Underfilled non-final pages: vertical white space a +better page-break policy would have used. Despite the field's name, the +metric follows the fixed orientation --- it measures \emph{unfilled} +fraction, so $0.0$ is best. + +\begin{requirement} +\label{req:qmc:page-fill} +\textbf{Contributing units:} non-final pages of $L$, defined only when +the declared content height $H$ is finite and positive. + +\textbf{Raw measurement:} per unit, with $\mathit{span}_p$ the vertical +extent of page $p$'s content (from the top of its first system's content +extent to the bottom of its last system's content extent) and fill +fraction $f_p = \min(1, \mathit{span}_p / H)$, +\[ + \mathit{raw}_p \;=\; 1 - f_p; +\] +the axis raw value is the arithmetic mean over contributing units. + +\textbf{Normalization:} $R_{\mathrm{worst}} = 0.75$; +$n = \min(1, \mathit{raw} / 0.75)$. + +A single-page layout contributes no units; the final page is never a +unit, because a short last page is not a fill failure. +\end{requirement} + +\begin{rationale} +A non-final page three-quarters empty is a page break with no plausible +justification --- worst-tolerable. The span-based fill fraction is +computable directly from the casting-off pass's vertical cursor walk and +per-system extents, and clamping $f_p$ at $1$ keeps slight margin +overshoot from producing a negative raw value. +\end{rationale} + +\section{\texttt{casting\_off\_quality}} +\label{sec:metrics:casting-off} + +\textbf{Phenomenon.} Uneven casting-off across a region's systems taken as +a whole: some lines full, others sparse --- including the classic failure +this axis exists to catch, a stub final system carrying one straggling +measure. Despite the field's name, $0.0$ is best. + +\begin{requirement} +\label{req:qmc:casting-off} +\textbf{Contributing units:} regions whose casting-off produced at least +two systems, each with content-extent width $w_s > 0$. + +\textbf{Raw measurement:} per unit region $R$, +\[ + \mathit{raw}_R \;=\; \mathrm{CV}\bigl(\, w_s : s \in \mathrm{systems}(R) \,\bigr), +\] +over \emph{all} of the region's systems, the final system included; the +axis raw value is the arithmetic mean over contributing units. + +\textbf{Normalization:} $R_{\mathrm{worst}} = 0.5$; +$n = \min(1, \mathit{raw} / 0.5)$. + +A single-system region contributes no units. +\end{requirement} + +\begin{rationale} +Including the final system is the deliberate difference from +\texttt{system\_break\_penalty} (which exempts it): a lone stub last line +drags the width spread up and is penalized \emph{here}, as a global +casting-off failure rather than a per-break one. The anchor: per-system +widths whose standard deviation is half their mean describe a page where +line lengths visibly disagree. +\end{rationale} + +\section{\texttt{symbol\_density\_uniformity}} +\label{sec:metrics:symbol-density} + +\textbf{Phenomenon.} Uneven crowding across systems: one line crammed with +symbols, the next sparse --- even when the lines' widths agree. Despite the +field's name, $0.0$ is best. + +\begin{requirement} +\label{req:qmc:symbol-density} +\textbf{Contributing units:} regions whose casting-off produced at least +two systems with $w_s > 0$. + +\textbf{Raw measurement:} per unit region $R$, with per-system symbol +density $\rho_s = n_s / w_s$ (glyphs per staff space of content width), +\[ + \mathit{raw}_R \;=\; \mathrm{CV}\bigl(\, \rho_s : s \in \mathrm{systems}(R),\ w_s > 0 \,\bigr); +\] +the axis raw value is the arithmetic mean over contributing units. + +\textbf{Normalization:} $R_{\mathrm{worst}} = 0.5$; +$n = \min(1, \mathit{raw} / 0.5)$. + +A single-system region contributes no units. +\end{requirement} + +\begin{rationale} +Width evenness (\texttt{casting\_off\_quality}) and density evenness are +independent failures: equal-width systems can still alternate between +sixteenth-note walls and whole-note deserts when break choices ignore +content weight. Density varying by half its mean across systems reads as +visibly uneven engraving, hence the shared $0.5$ anchor. +\end{rationale} + +% =========================================================================== +\chapter{Default Tie-Breaking Weights} +\label{ch:weights} + +The core specification requires normative default +\texttt{TieBreakingWeights}: they select among Pareto-equivalent layouts, +deterministically, and are ``the basis for reference-suite conformance.'' + +\begin{requirement} +\label{req:qmc:weights} +The normative default tie-breaking weights are $1.0$ for every one of the +nine fields of \texttt{TieBreakingWeights}: + +\begin{center} +\small +\begin{tabular}{lc@{\hspace{2.5em}}lc} +\toprule +\textbf{Weight} & \textbf{Default} & \textbf{Weight} & \textbf{Default} \\ +\midrule +\texttt{collision} & \tablenums{1.0} & \texttt{system\_break} & \tablenums{1.0} \\ +\texttt{spacing} & \tablenums{1.0} & \texttt{page\_fill} & \tablenums{1.0} \\ +\texttt{slur\_shape} & \tablenums{1.0} & \texttt{casting\_off} & \tablenums{1.0} \\ +\texttt{beam\_slope} & \tablenums{1.0} & \texttt{symbol\_density} & \tablenums{1.0} \\ +\texttt{vertical\_density} & \tablenums{1.0} & & \\ +\bottomrule +\end{tabular} +\end{center} + +Implementations \MAY{} let users customize weights, per the core +specification; conformance evaluation on the reference suite uses these +defaults. +\end{requirement} + +\begin{rationale} +No aesthetic priority ordering among the nine axes has been ratified, and +inventing one ahead of measurement experience would encode a preference no +evidence supports. Uniform weights are the honest neutral default --- they +make tie-breaking deterministic (the core's actual requirement) without +pretending to a house style. They also bless the reference +implementation's existing \texttt{Default} for \texttt{TieBreakingWeights} +(every field $1.0$). Revisions of this catalog are expected to tune the +defaults once reference-suite experience shows which axes dominate +perceived quality. +\end{rationale} + +% =========================================================================== +\chapter{Per-Tier Metric Thresholds} +\label{ch:thresholds} + +\section{The Default Threshold Table} +\label{sec:thresholds:table} + +A tier's threshold for an axis is the maximum permitted +\texttt{NormalizedMetric} value on a reference-suite entry evaluated at +that tier. The core specification fixes the relationship: Minimal-tier +thresholds are relaxed relative to Standard; the Standard tier corresponds +to professional engraving quality. + +\begin{table}[h] +\centering +\small +\begin{tabular}{lcc} +\toprule +\textbf{Axis} & \textbf{Minimal (max)} & \textbf{Standard (max)} \\ +\midrule +\texttt{collision\_penalty} & \tablenums{0.90} & \tablenums{0.25} \\ +\texttt{spacing\_distortion} & \tablenums{0.90} & \tablenums{0.40} \\ +\texttt{slur\_shape\_penalty} & \tablenums{0.90} & \tablenums{0.30} \\ +\texttt{beam\_slope\_penalty} & \tablenums{0.90} & \tablenums{0.30} \\ +\texttt{vertical\_density\_penalty} & \tablenums{0.90} & \tablenums{0.40} \\ +\texttt{system\_break\_penalty} & \tablenums{0.90} & \tablenums{0.35} \\ +\texttt{page\_fill\_efficiency} & \tablenums{0.90} & \tablenums{0.40} \\ +\texttt{casting\_off\_quality} & \tablenums{0.90} & \tablenums{0.35} \\ +\texttt{symbol\_density\_uniformity} & \tablenums{0.90} & \tablenums{0.40} \\ +\bottomrule +\end{tabular} +\caption{Default per-tier maximum \texttt{NormalizedMetric} values. +Minimal is uniformly more permissive than Standard on every axis.} +\label{tab:tier-thresholds} +\end{table} + +\begin{requirement} +\label{req:qmc:thresholds} +The default per-tier thresholds are given by +Table~\ref{tab:tier-thresholds}. A solver claiming a tier \MUST{} keep +every normative metric at or below the tier's threshold on every +reference-suite entry required at that tier, per the core specification's +suite-conformance conditions. The Reference Suite companion \MAY{} +override these defaults for individual entries; absent an override, the +values of Table~\ref{tab:tier-thresholds} govern. +\end{requirement} + +\begin{rationale} +\textbf{Minimal = 0.90 everywhere: relaxed but non-vacuous.} A Minimal +solver may be aesthetically mediocre --- the core says so --- but it must +not be \emph{pathological}, and its metric vectors must be accurate. A +uniform $0.90$ admits every honestly-mediocre layout while excluding two +things: layouts at an axis's worst-tolerable anchor, and the all-worst +placeholder vector of a solver that computes nothing. That second +exclusion is deliberate --- a solver reporting the unmeasured $1.0$ +placeholder cannot pass the Minimal suite, which is exactly the +honest-tier discipline: measuring is part of the Minimal claim. + +\textbf{Standard = 0.25--0.40 per axis: professional quality.} Collisions +get the tightest bound ($0.25$: at most one cross-column collision per +eighty glyphs) because they are the most jarring single defect. The +break-family axes ($0.35$) sit slightly tighter than the distribution and +vertical axes ($0.40$), whose v0.1 definitions are coarser proxies +(geometric spacing regularity; a not-yet-solved vertical dimension). Slurs +and beams ($0.30$) allow modest shape deviation across a piece. All values +are round v0.1 defaults chosen to be defensible, not optimal; the tuning +open question below owns their evolution. +\end{rationale} + +\section{The Advanced Tier} +\label{sec:thresholds:advanced} + +\begin{requirement} +\label{req:qmc:advanced} +The Advanced tier imposes the Standard-tier thresholds of +Table~\ref{tab:tier-thresholds} on the nine normative axes, \emph{plus} +per-extension thresholds on extension metrics: a registered extension +whose layout requirements are part of the Advanced reference suite +\MUST{} declare, in its extension declaration, a maximum +\texttt{NormalizedMetric} value for each extension metric it contributes, +and an Advanced-tier solver \MUST{} meet each declared threshold on every +Advanced suite entry that exercises that extension. An extension metric +with no declared threshold imposes no Advanced-tier obligation. +\end{requirement} + +\section{The \texttt{QualityFloorApproached} Warning} +\label{sec:thresholds:floor} + +The core specification gives \texttt{SolverWarningKind} a +\texttt{QualityFloorApproached} variant carrying a +\texttt{QualityMetricKind} payload, without defining its trigger. This +catalog pins it. + +\begin{requirement} +\label{req:qmc:floor-warning} +A solver \SHOULD{} emit a \texttt{QualityFloorApproached} warning for +metric kind $k$ when the computed value of $k$'s axis exceeds +$\mathbf{0.8}$ times the applicable threshold for that axis. The +applicable threshold is the one selected by the solve's +\texttt{SolverProfile} (Chapter~\ref{ch:profiles}); +the warning fraction is pinned at $0.8$ exactly. The warning is +diagnostic: emitting it does not change the solve's status, and a value +\emph{over} the threshold still warns (it exceeds $0.8$ of it a +fortiori) --- threshold \emph{enforcement} exists only in reference-suite +evaluation, not in ordinary solves. +\end{requirement} + +\section{Standard-Tier Constraint Families} +\label{sec:thresholds:families} + +The core specification's Standard-tier requirement obliges a Standard +solver to ``support every Standard-tier constraint family declared in the +Quality Metric Catalog.'' This section is that declaration. + +\begin{requirement} +\label{req:qmc:standard-families} +The Standard-tier constraint families are the core specification's +built-in layout-constraint surface (Chapter~7, +\sectionsc{ConstrainedLayoutIR}): + +\begin{itemize} + \item the \textbf{spring families}: horizontal spring slots and vertical + bands, with their min/preferred/max and stretch/compress parameters; + \item the five built-in \texttt{LayoutConstraint} kinds: + \texttt{NoCollision}, \texttt{Align}, \texttt{PositionWithin}, + \texttt{SystemBreakAt}, and \texttt{PageBreakAt} (both + \texttt{Hard} and \texttt{Soft} break kinds). +\end{itemize} + +These same families constitute ``the standard constraint families'' of +the core's Minimal-tier requirement: Minimal and Standard support the +same family set and differ in metric thresholds and incremental-solving +obligations, not in constraint vocabulary. +\texttt{LayoutConstraint::Registered} (extension-contributed) families +are per-extension obligations of the Advanced tier only. +\end{requirement} + +\begin{openquestion} +\textbf{Threshold tuning.} Every number in +Table~\ref{tab:tier-thresholds} and every anchor constant in +Chapter~\ref{ch:metrics} is a v0.1 default pinned ahead of measurement +experience: no implementation has yet reported real vectors across the +reference suite. Once the reference implementation computes real metrics +on the v0.1 entry set, are the Standard columns achievable-but-meaningful +(neither trivially passed nor unreachable), and do any anchors need +rescaling? Threshold and anchor revisions are \textsc{minor} versions of +this catalog and are expected. +\end{openquestion} + +% =========================================================================== +\chapter{The Registered Profile Catalog} +\label{ch:profiles} + +The core specification's vocabulary appendix defines +\texttt{SolverProfile} as a registered profile identifier that ``selects +the solver's hard-constraint set, normalized-metric thresholds, +tie-breaking weights, and active extension catalog,'' and defers the +registry to this companion. + +\begin{requirement} +\label{req:qmc:profiles} +The registered \texttt{SolverProfile} catalog is exactly three profiles: +\texttt{Draft}, \texttt{Standard}, and \texttt{Publication}. Their +selections: + +\begin{center} +\small +\begin{tabular}{lllll} +\toprule +\textbf{Profile} & \textbf{Constraint families} & \textbf{Threshold column} & +\textbf{Weights} & \textbf{Extensions} \\ +\midrule +\texttt{Draft} & Standard-tier set & Minimal + & defaults & none required \\ +\texttt{Standard} & Standard-tier set & Standard + & defaults & none required \\ +\texttt{Publication} & Standard-tier set & Standard + & defaults & none required \\ +\bottomrule +\end{tabular} +\end{center} + +\begin{itemize} + \item \emph{Constraint families}: all three profiles activate the + Standard-tier constraint families of + Requirement~\ref{req:qmc:standard-families}; hard constraints are + never traded away by any profile (the core's + hard-constraints-are-inviolable rule). + \item \emph{Threshold column}: the column of + Table~\ref{tab:tier-thresholds} the profile selects --- the thresholds + against which Requirement~\ref{req:qmc:floor-warning}'s warning + fraction is evaluated during ordinary solves. \texttt{Draft} selects + the Minimal column (few warnings, fast iteration); + \texttt{Standard} and \texttt{Publication} select the Standard + column. In v0.1 no column tighter than Standard is ratified; + \texttt{Publication} is registered now so that documents and + configurations can name it, and a future revision \MAY{} give it a + tighter column without a schema change. + \item \emph{Weights}: all three profiles use the default tie-breaking + weights of Requirement~\ref{req:qmc:weights}. + \item \emph{Extensions}: no profile requires an active extension + catalog; extensions activate by document declaration, not by + profile. +\end{itemize} + +\texttt{Standard} is the default profile. +\end{requirement} + +\begin{rationale} +\textbf{Profiles are configuration; tiers are claims.} A +\texttt{SolverProfile} is a runtime input (\texttt{SolverConfig.profile}) +that any solver may be asked to run under; a conformance tier is a claim +about the solver evaluated on the reference suite. The two meet in +exactly one place: the profile's threshold column determines which +thresholds the solver's own \texttt{QualityFloorApproached} diagnostics +reference during ordinary solves. Suite evaluation at a claimed tier +always uses that \emph{tier's} column, whatever profile the solver runs +under day to day. The three-profile registry matches the reference +implementation's existing \texttt{SolverProfile} enum +(\texttt{Draft} / \texttt{Standard} / \texttt{Publication}, default +\texttt{Standard}) so that registration blesses shipped reality rather +than inventing a parallel one. +\end{rationale} + +% =========================================================================== +\chapter{Revision History} +\label{ch:history} + +\begin{longtable}{p{2cm} p{2.5cm} p{9cm}} + \toprule + \textbf{Date} & \textbf{Section} & \textbf{Change} \\ + \midrule + \endhead + \today & All & 0.1.0 --- Initial companion: pins the diagnostic-only + status of quality metrics, within-implementation determinism without + cross-implementation numeric agreement, the \texttt{QualityMetricKind} + enumeration, the measurement domain, and the vacuous-geometry rule; + defines all nine normative axes (phenomenon, raw measurement over + resolved geometry, clamped-linear normalization with pinned anchors); + sets the default tie-breaking weights (all $1.0$); establishes the + Minimal/Standard threshold table, the Advanced extension rule, the + \texttt{QualityFloorApproached} trigger ($0.8\times$ threshold), and + the Standard-tier constraint family declaration; registers the + \texttt{Draft}/\texttt{Standard}/\texttt{Publication} profile catalog. + Open questions: the notated-but-unrendered honesty edge, optical + spacing at the Standard tier, threshold tuning pending reference-suite + experience. \\ +\bottomrule +\end{longtable} + +\end{document} diff --git a/spec/reference_suite.pdf b/spec/reference_suite.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4cac66acf0b475505a9dbf9c9bf64767a2437972 GIT binary patch literal 77667 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