Clear P12-I11: casting-off widow-rebalance evens the stub last system
RS-1 honestly failed the Minimal casting_off_quality threshold under the
reference engraver: greedy first-fit left a two-measure stub last system
(width CV 0.6145 -> clamped 1.0 > 0.90). Cleared the honest way — an
engrave-side balance pass, no Quality Metric Catalog or core-spec change.
Casting-off gains a second phase, a widow rebalance
(casting::rebalance_widows, run between the greedy walk and vertical
stacking): it moves whole trailing measures from a region's penultimate
system into its final one, choosing the shift that minimizes the larger of
the two distribution penalties the catalog defines for the break family —
the width imbalance (casting_off_quality, the CV of the region's system
widths) and the non-final break penalty (system_break_penalty, the mean of
|W-w|/W over non-final systems). distribution_cost computes each raw by the
same formula as quality.rs's casting_off_raw / system_break_raw (mean not
worst, abs not clamp), so the rebalance optimizes the values the metric
census will report. The two axes pull against each other, so their min-max
lands on a 6/4 split for RS-1 (casting_off 1.0 -> 0.4463, system_break
0.254 -> 0.677, every axis <= 0.90) — with comfortable margin, over the
fragile full-balance 5/5 (system_break 0.889, a hair under 0.90).
Scope is tight: only a region's last boundary moves, and only when greedy
placed it (an Automatic boundary with no break requirement or page force
pinned to its slot); a user/IR-anchored or page-forced boundary is never
disturbed, the penultimate system keeps >= 1 measure, the final never grows
past its predecessor, and the system count is unchanged — so every
break-count and page-assignment invariant (and all break-constraint tests)
hold untouched.
The casting_off 0.5 anchor and the 0.90 Minimal column were vindicated, not
relaxed: the engraver improved, no anchor rescale / threshold loosening /
RS-1 override. Core spec Chapter 9's "Minimal makes no optimality claim"
already permits the heuristic, so nothing normative changed (no .tex/PDF
rebuild). P12-I12 (the Standard-tier spacing floor on short scores) stays
open.
- engrave: rebalance_widows + distribution_cost + two-phase module docs;
ENGRAVER_VERSION 2 -> 3 (a wrapping score's baked geometry differs from
pure greedy); three new casting tests (even-split preference, the
mean-not-worst break penalty for 3+ systems, and the resolved final
system); the_wrapping_fixture_is_measured_honestly re-pinned to the 6/4
values (both axes floor-warn under Standard, status untouched).
- testkit: RS-1 minimal_xfail row removed (promoted to a plain Pass); the
suite ships no xfail rows.
- render-svg: ten_measure.engrave.{svg,snapshot} goldens regenerated
(view_box width 83.99 -> 64.95; still two systems).
- process trail: PASS12_BATCH I11 struck; PASS12_RATIFICATION_LOG
"no spec change" section; engrave DECISIONS casting-off decision 9 +
quality item 7 + candidate promoted.
860 workspace tests pass; clippy -D warnings, fmt --check, rustdoc
-D warnings all clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
This commit is contained in:
parent
b9a9921d50
commit
bc9fb27d36
|
|
@ -162,12 +162,44 @@ inputs the solver cannot measure.
|
|||
with the user's override id). The pathological-soft path keeps the old
|
||||
warning semantics under a new, honest name
|
||||
(`a_pathological_soft_break_is_skipped_and_recorded_as_ir_override`).
|
||||
9. **Deferred refinements** (named, not implied): per-system justification
|
||||
9. **Widow rebalance (casting-off phase 2) — the honest P12-I11 fix.** Greedy
|
||||
first-fit (decision 1) is optimal for *page fill* — it packs each non-final
|
||||
system as full as the width allows — but that leaves a region's **final**
|
||||
system whatever is left over, often a narrow stub (a "widow") the
|
||||
`casting_off_quality` axis penalizes as a global casting-off failure. A
|
||||
second phase (`casting::rebalance_widows`) evens the split: it moves whole
|
||||
trailing measures from a region's penultimate system into its final one,
|
||||
choosing the shift that **minimizes the larger of the two distribution
|
||||
penalties the Quality Metric Catalog defines for the break family** — the
|
||||
width imbalance (`casting_off_quality`, the CV of the region's system widths)
|
||||
and the non-final break penalty (`system_break_penalty`, the mean of
|
||||
`|W − w|/W` over non-final systems) — each computed by the same formula as
|
||||
the axis it stands in for, so the rebalance optimizes the values the metric
|
||||
census will report, not a proxy. The
|
||||
two axes pull against each other (filling non-final systems worsens
|
||||
imbalance; equalizing widths worsens underfill) and both share the catalog's
|
||||
`0.5` anchor, so the raw quantities compare directly and the minimizer of
|
||||
their maximum is the width that best satisfies both. Scope: only a region's
|
||||
**last** boundary moves, and only when greedy placed it (an `Automatic`
|
||||
boundary with no break requirement or page force pinned to its slot); a
|
||||
user/IR-anchored or page-forced boundary is never disturbed, the penultimate
|
||||
system keeps ≥ 1 measure, and the final system never grows past its
|
||||
predecessor. The **system count is unchanged**, so decision 2's break
|
||||
structure, decision 5's page assignment, and every break-count test invariant
|
||||
hold. Result on **P12-I11**: RS-1 casts six/four instead of eight/two
|
||||
(`casting_off` 1.0 → 0.4463, every axis ≤ 0.90), the suite's asserted Xfail
|
||||
row is promoted to a plain Pass, with **no Quality Metric Catalog change** —
|
||||
the engraver improved, the `0.5` anchor and the `0.90` Minimal column stood.
|
||||
`ENGRAVER_VERSION` 2 → 3 (a wrapping score's baked geometry differs from pure
|
||||
greedy); the `ten_measure` render goldens were regenerated. Still a Minimal
|
||||
heuristic, not an optimality claim.
|
||||
10. **Deferred refinements** (named, not implied): per-system justification
|
||||
(stretching the soft springs so every full system ends at the right
|
||||
margin); the vertical spring solve (band heights are carried, not yet
|
||||
renegotiated; systems stack by real content extents); widow/orphan control
|
||||
and optimal/lookahead casting-off quality (a `Standard`-tier concern, with
|
||||
`casting_off_quality` in the metric vector); casting-off caching /
|
||||
renegotiated; systems stack by real content extents); orphan control and
|
||||
optimal/lookahead casting-off quality beyond decision 9's tail-only widow
|
||||
rebalance — a `Standard`-tier concern (full-region rebalancing, and
|
||||
justification-aware casting-off once systems can stretch); casting-off caching /
|
||||
incremental re-cast (the spec's incremental-layout section names the
|
||||
casting-off cache; `solve_incremental` currently re-solves from scratch,
|
||||
which remains observationally equivalent); per-system clef/key restatement
|
||||
|
|
@ -298,27 +330,30 @@ testkit's reference-suite harness.
|
|||
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.
|
||||
v0.1 entries now measure clean on every Minimal axis. RS-1's
|
||||
`casting_off_quality` was 1.0 under engraver **v2**'s pure greedy first-fit
|
||||
(the stub last line, above the Minimal 0.90 threshold — carried as a
|
||||
documented xfail row in the testkit harness, P12-I11); the **v3**
|
||||
widow-rebalance phase (casting-off decision 9) evens the split to
|
||||
`casting_off` = 0.4463, clearing the miss with no catalog change, and the
|
||||
xfail row is promoted to a plain Pass. One finding the catalog's
|
||||
threshold-tuning open question anticipated remains (a Pass-12/QMC candidate
|
||||
below, P12-I12): `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 — a *diagnostic* floor, not a Minimal failure.
|
||||
|
||||
### 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-I11 — RESOLVED (engraver v3 widow rebalance).** First measured vectors
|
||||
(engraver v2) cast 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.
|
||||
Resolved the **honest way (option (a))**: casting-off decision 9's
|
||||
widow-rebalance phase evens the split to ~59.5/37.8 (`casting_off` = 0.4463),
|
||||
so every axis passes and the testkit Xfail row is promoted to a Pass. Option
|
||||
(b) (a QMC anchor/threshold revision — raise the anchor, relax Minimal, or add
|
||||
an RS-1 override) was deliberately **not** taken: the `0.5` anchor and the
|
||||
`0.90` Minimal column stood. `ENGRAVER_VERSION` 2 → 3; `ten_measure` render
|
||||
goldens regenerated.
|
||||
- **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
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@
|
|||
//! "resolve\[s\] page and system breaks"; Chapter 7 §"ResolvedLayoutIR" defines
|
||||
//! the page/system tree this pass populates).
|
||||
//!
|
||||
//! ## The algorithm (greedy first-fit)
|
||||
//! ## The algorithm (greedy first-fit, then a widow rebalance)
|
||||
//!
|
||||
//! [`SolverTier::Minimal`](epiphany_layout_ir::SolverTier) requires the break
|
||||
//! constraint family to be supported and every hard constraint satisfied (or an
|
||||
//! honest `Unsatisfiable`); it makes **no optimality claim**, so casting-off is
|
||||
//! a deterministic greedy first-fit, not an optimal (Knuth–Plass-style) break
|
||||
//! search:
|
||||
//! a deterministic two-phase heuristic, not an optimal (Knuth–Plass-style) break
|
||||
//! search. Phase 1 is greedy first-fit; phase 2 (`rebalance_widows`) evens the
|
||||
//! system widths so the final system is not left a narrow stub. Phase 1:
|
||||
//!
|
||||
//! 1. **System breaking.** Per region, walk the spaced spring-slot columns in x
|
||||
//! order. Break into systems at **measure boundaries** — the barline columns
|
||||
|
|
@ -43,6 +44,15 @@
|
|||
//! flat glyph/stroke lists remain the renderer's and hit-tester's single
|
||||
//! coordinate space — no per-page transform exists anywhere downstream.
|
||||
//!
|
||||
//! Phase 2 (`rebalance_widows`, run between system breaking and stacking)
|
||||
//! moves whole trailing measures from a region's penultimate system into its
|
||||
//! final one to even their widths — the anti-widow refinement, choosing the
|
||||
//! shift that minimizes the larger of the two distribution penalties the
|
||||
//! Quality Metric Catalog defines for the break family (width imbalance vs
|
||||
//! non-final underfill). It leaves the system *count* unchanged and never
|
||||
//! disturbs a constraint-pinned boundary, so the break structure phase 1
|
||||
//! established still holds.
|
||||
//!
|
||||
//! ## Region-spanning strokes
|
||||
//!
|
||||
//! A stroke confined to one system (a stem, a ledger, a barline-anchored mark)
|
||||
|
|
@ -455,6 +465,12 @@ pub(crate) fn cast_off(
|
|||
);
|
||||
}
|
||||
|
||||
// ---- Widow rebalance (casting-off phase 2) ----------------------------
|
||||
// Greedy first-fit fills every non-final system maximally, which can leave
|
||||
// a region's final system a narrow stub; even the system widths without
|
||||
// moving any constraint-pinned boundary or changing the system count.
|
||||
rebalance_widows(&mut systems, ®ion_slots, &reqs, width_limit);
|
||||
|
||||
// ---- Stroke fates ------------------------------------------------------
|
||||
// Which system each slot landed in, and each region's slot span / per-system
|
||||
// clip intervals (the interior cut points for system-spanning strokes).
|
||||
|
|
@ -905,6 +921,197 @@ fn walk_region(
|
|||
});
|
||||
}
|
||||
|
||||
/// **Widow rebalance** — the casting-off pass's second phase (module docs). The
|
||||
/// greedy first-fit walk fills each non-final system as full as the content
|
||||
/// width allows, which is optimal for *page fill* but can leave the region's
|
||||
/// **final** system a narrow stub (a "widow") — exactly what the Quality Metric
|
||||
/// Catalog's `casting_off_quality` axis penalizes. This pass evens the region's
|
||||
/// system widths by moving whole trailing measures from the penultimate system
|
||||
/// into the final one.
|
||||
///
|
||||
/// The shift is chosen to **minimize the larger of the two distribution
|
||||
/// penalties the catalog defines for the break family**: the system-width
|
||||
/// *imbalance* (`casting_off_quality`, the coefficient of variation of the
|
||||
/// region's system widths) and the non-final *break* penalty
|
||||
/// (`system_break_penalty`, the mean of `|W − w|/W` over non-final systems).
|
||||
/// Each is computed by the same formula the metric census uses (see
|
||||
/// [`distribution_cost`]). The two axes pull against each other —
|
||||
/// filling non-final systems (few, wide systems) worsens imbalance; equalizing
|
||||
/// widths (empty non-final systems) worsens underfill — and both share the
|
||||
/// catalog's `0.5` worst-tolerable anchor, so their raw quantities are compared
|
||||
/// directly and the minimizer of their maximum is the width that best satisfies
|
||||
/// both. It is not a claim of optimality (Minimal makes none); it is a
|
||||
/// deterministic anti-widow heuristic.
|
||||
///
|
||||
/// Only a region's **last** boundary moves, and only when greedy placed it — an
|
||||
/// `Automatic` boundary with no break requirement or page force pinned to its
|
||||
/// slot. A user/IR-anchored or page-forced boundary is never disturbed, and the
|
||||
/// **system count is unchanged**, so page assignment and every break-count
|
||||
/// invariant the walk established still hold. The penultimate system keeps at
|
||||
/// least its own first measure (never emptied), and the final system never
|
||||
/// grows wider than its predecessor (no mirror-image imbalance).
|
||||
fn rebalance_widows(
|
||||
systems: &mut [SystemPlan],
|
||||
region_slots: &[Vec<SlotInfo>],
|
||||
reqs: &BTreeMap<SpringSlotId, Vec<BreakReq>>,
|
||||
width_limit: f32,
|
||||
) {
|
||||
if !(width_limit.is_finite() && width_limit > 0.0) {
|
||||
return; // unbounded width: nothing wraps, nothing to even out
|
||||
}
|
||||
let w_limit = f64::from(width_limit);
|
||||
// A region's systems are a contiguous run in `systems` (walk_region appends
|
||||
// them per region, in region order); rebalance each run independently.
|
||||
let mut start = 0;
|
||||
while start < systems.len() {
|
||||
let region = systems[start].region;
|
||||
let mut end = start;
|
||||
while end < systems.len() && systems[end].region == region {
|
||||
end += 1;
|
||||
}
|
||||
rebalance_region(
|
||||
&mut systems[start..end],
|
||||
®ion_slots[region],
|
||||
reqs,
|
||||
w_limit,
|
||||
);
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
|
||||
/// Rebalances one region's contiguous run of systems (see [`rebalance_widows`]).
|
||||
fn rebalance_region(
|
||||
run: &mut [SystemPlan],
|
||||
slots: &[SlotInfo],
|
||||
reqs: &BTreeMap<SpringSlotId, Vec<BreakReq>>,
|
||||
w_limit: f64,
|
||||
) {
|
||||
let n = run.len();
|
||||
if n < 2 {
|
||||
return; // a single system has no widow to fix
|
||||
}
|
||||
let (prev, last) = (n - 2, n - 1);
|
||||
// The final boundary must be a greedy one to move it: an `Automatic` system
|
||||
// break with no break requirement or page force pinned to its slot.
|
||||
let Some(boundary) = run[last].boundary else {
|
||||
return;
|
||||
};
|
||||
if boundary.source != DecisionSource::Automatic
|
||||
|| run[last].page_forced
|
||||
|| reqs.contains_key(&boundary.slot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let width = |idx: &[usize]| -> f64 {
|
||||
let lo = idx
|
||||
.iter()
|
||||
.map(|&k| slots[k].lo)
|
||||
.fold(f32::INFINITY, f32::min);
|
||||
let hi = idx
|
||||
.iter()
|
||||
.map(|&k| slots[k].hi)
|
||||
.fold(f32::NEG_INFINITY, f32::max);
|
||||
if hi > lo {
|
||||
f64::from(hi - lo)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
// Widths of the systems before the penultimate stay fixed (only the last
|
||||
// boundary moves); the objective's coefficient of variation ranges over all.
|
||||
let fixed: Vec<f64> = run[..prev].iter().map(|p| width(&p.slots)).collect();
|
||||
// Measure-start positions within the penultimate system — local indices into
|
||||
// its slot list. The first is the system's own opening (immovable); a split
|
||||
// at a later one moves that measure and the rest into the final system.
|
||||
let starts: Vec<usize> = run[prev]
|
||||
.slots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, &k)| slots[k].measure_barline.is_some())
|
||||
.map(|(local, _)| local)
|
||||
.collect();
|
||||
if starts.len() < 2 {
|
||||
return; // the penultimate system has one measure — nothing to lend
|
||||
}
|
||||
// Baseline: the greedy split (move nothing). Iterate candidate splits from
|
||||
// the fewest measures moved (latest start) so ties keep the fuller
|
||||
// predecessor; accept only a strict improvement.
|
||||
let mut best_cost = distribution_cost(
|
||||
&fixed,
|
||||
width(&run[prev].slots),
|
||||
width(&run[last].slots),
|
||||
w_limit,
|
||||
);
|
||||
let mut best_split: Option<usize> = None;
|
||||
for &split in starts.iter().skip(1).rev() {
|
||||
let kept = &run[prev].slots[..split];
|
||||
let moved = &run[prev].slots[split..];
|
||||
let last_slots: Vec<usize> = moved.iter().chain(&run[last].slots).copied().collect();
|
||||
let (w_prev, w_last) = (width(kept), width(&last_slots));
|
||||
if w_last > w_prev {
|
||||
continue; // never grow the final system past its predecessor
|
||||
}
|
||||
let cost = distribution_cost(&fixed, w_prev, w_last, w_limit);
|
||||
if cost < best_cost - 1e-9 {
|
||||
best_cost = cost;
|
||||
best_split = Some(split);
|
||||
}
|
||||
}
|
||||
if let Some(split) = best_split {
|
||||
let moved: Vec<usize> = run[prev].slots[split..].to_vec();
|
||||
let new_boundary_slot = slots[run[prev].slots[split]].id;
|
||||
run[prev].slots.truncate(split);
|
||||
let mut new_last = moved;
|
||||
new_last.extend_from_slice(&run[last].slots);
|
||||
run[last].slots = new_last;
|
||||
run[last].boundary = Some(Boundary {
|
||||
slot: new_boundary_slot,
|
||||
source: DecisionSource::Automatic,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// The rebalance objective (see [`rebalance_widows`]): the larger of the two raw
|
||||
/// distribution penalties over a region's system widths — the **break** penalty
|
||||
/// and the width **imbalance**. Both normalize against the catalog's shared
|
||||
/// `0.5` anchor, so comparing and taking the max of the raw quantities orders
|
||||
/// candidates exactly as the max of the two normalized metrics does. Each raw is
|
||||
/// computed by the *same* formula as the axis it stands in for, so the rebalance
|
||||
/// optimizes against the values the `quality` module will report:
|
||||
///
|
||||
/// * **break** — `quality::system_break_raw`'s mean of `|W − w| / W` over the
|
||||
/// **non-final** systems (absolute, so an overfull non-final system is
|
||||
/// penalized too);
|
||||
/// * **imbalance** — `quality::casting_off_raw`'s coefficient of variation over
|
||||
/// **all** the region's system widths.
|
||||
fn distribution_cost(fixed: &[f64], w_prev: f64, w_last: f64, w_limit: f64) -> f64 {
|
||||
let mut widths: Vec<f64> = fixed.to_vec();
|
||||
widths.push(w_prev);
|
||||
widths.push(w_last);
|
||||
let count = widths.len();
|
||||
// Break penalty: mean absolute deviation from the content width over the
|
||||
// non-final systems (the final system is exempt) — `system_break_raw`.
|
||||
let non_final = &widths[..count - 1];
|
||||
let breaks = if non_final.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
non_final
|
||||
.iter()
|
||||
.map(|&w| (w_limit - w).abs() / w_limit)
|
||||
.sum::<f64>()
|
||||
/ non_final.len() as f64
|
||||
};
|
||||
// Imbalance: the coefficient of variation of all system widths — `casting_off_raw`.
|
||||
let mean = widths.iter().sum::<f64>() / count as f64;
|
||||
let imbalance = if mean > 0.0 {
|
||||
let variance = widths.iter().map(|w| (w - mean) * (w - mean)).sum::<f64>() / count as f64;
|
||||
variance.sqrt() / mean
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
breaks.max(imbalance)
|
||||
}
|
||||
|
||||
/// Decides how a stroke rides the cast systems (see [`StrokeFate`]).
|
||||
fn stroke_fate(
|
||||
stroke: &Stroke,
|
||||
|
|
@ -1194,4 +1401,73 @@ mod tests {
|
|||
-(148.5 + INTER_PAGE_GAP) - 7.5
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distribution_cost_prefers_the_even_split_over_greedy_and_full_balance() {
|
||||
// The RS-1 two-system candidates, glyph-ink widths (staff spaces) from
|
||||
// the ten-measure fixture at the default 90-wide content area. The
|
||||
// widow rebalance minimizes the larger of imbalance (CV) and worst
|
||||
// non-final underfill; the greedy 8/2 stub and the fully balanced 5/5
|
||||
// both score worse than the six/four split it settles on.
|
||||
let w = 90.0;
|
||||
let greedy = distribution_cost(&[], 78.57, 18.76, w); // 8/2 stub
|
||||
let six_four = distribution_cost(&[], 59.52, 37.80, w); // rebalanced
|
||||
let five_five = distribution_cost(&[], 50.00, 47.33, w); // full balance
|
||||
assert!(
|
||||
six_four < greedy && six_four < five_five,
|
||||
"6/4 ({six_four:.4}) must beat greedy ({greedy:.4}) and 5/5 ({five_five:.4})"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distribution_cost_uses_the_mean_break_penalty_not_the_worst() {
|
||||
// For three or more systems the break term must be the MEAN of |W-w|/W
|
||||
// over the non-final systems — the same quantity `quality::system_break_raw`
|
||||
// reports — not the worst single system. With one full leading system
|
||||
// fixed at W, the balanced 45/45 tail must beat the 60/30 tail: its width
|
||||
// CV is lower, and the mean break penalty (diluted by the full leading
|
||||
// system) does not dominate. A worst-underfill proxy would wrongly prefer
|
||||
// 60/30 (its lone short system is less empty), inverting the choice.
|
||||
let w = 90.0;
|
||||
let fixed = [90.0]; // one full non-final system
|
||||
let balanced = distribution_cost(&fixed, 45.0, 45.0, w);
|
||||
let uneven = distribution_cost(&fixed, 60.0, 30.0, w);
|
||||
assert!(
|
||||
balanced < uneven,
|
||||
"the mean break penalty prefers the balanced tail: {balanced:.4} vs {uneven:.4}"
|
||||
);
|
||||
// Pin the mean-not-max semantics exactly: over the non-final systems
|
||||
// [90, 45] the break penalty is mean(0, 0.5) = 0.25, below the width CV,
|
||||
// so the objective here is the CV of [90, 45, 45].
|
||||
let widths = [90.0_f64, 45.0, 45.0];
|
||||
let mean = widths.iter().sum::<f64>() / 3.0;
|
||||
let cv = (widths.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / 3.0).sqrt() / mean;
|
||||
assert!(
|
||||
(balanced - cv).abs() < 1e-12,
|
||||
"the objective should equal the width CV here: {balanced} vs {cv}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_widow_rebalance_evens_the_final_system() {
|
||||
use crate::Engraver;
|
||||
use epiphany_layout_ir::{to_constrained, to_logical, ConstraintSolver, SolverConfig};
|
||||
// The ten-measure fixture wraps into two systems under the default A4
|
||||
// geometry. Greedy first-fit alone leaves a two-measure stub final
|
||||
// system (its width barely a quarter of the first's); the widow
|
||||
// rebalance evens the split so the final system is a substantial
|
||||
// fraction of its predecessor — while the system *count* is unchanged.
|
||||
let input = to_constrained(&to_logical(
|
||||
&epiphany_testkit::fixtures::ten_measure_single_staff(0x000A_11CE),
|
||||
));
|
||||
let report = Engraver::default().solve(&input, &SolverConfig::default());
|
||||
let page = &report.layout.pages[0];
|
||||
assert_eq!(page.systems.len(), 2, "the fixture wraps into two systems");
|
||||
let first = page.systems[0].bounding_box.size.width.0;
|
||||
let last = page.systems[1].bounding_box.size.width.0;
|
||||
assert!(
|
||||
last > 0.5 * first,
|
||||
"the rebalanced final system is not a stub: {last} vs {first}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@
|
|||
//! bearings) — then a **casting-off pass** (the [`casting`] module; Chapter 9
|
||||
//! §"The Constraint-Solving Stage": the solver "resolve\[s\] page and system
|
||||
//! breaks"): greedy first-fit system breaking at measure boundaries against a
|
||||
//! [`PageGeometry`], vertical system stacking at the vertical-band model's
|
||||
//! inter-system gap, page assignment by content height, and a real populated
|
||||
//! page/system tree (Chapter 7 §"ResolvedLayoutIR"). Every chosen break is
|
||||
//! [`PageGeometry`], a widow-rebalance phase that evens a region's system widths
|
||||
//! so the final system is not left a stub, vertical system stacking at the
|
||||
//! vertical-band model's inter-system gap, page assignment by content height,
|
||||
//! and a real populated page/system tree (Chapter 7 §"ResolvedLayoutIR"). Every
|
||||
//! chosen break is
|
||||
//! recorded as an [`epiphany_layout_ir::EngravingDecision`] whose target is a
|
||||
//! `MUSCLOID` id synthesized under
|
||||
//! [`epiphany_layout_ir::SynthesisKind::EngravedBreak`], attributed to the user
|
||||
|
|
@ -116,8 +118,11 @@ pub struct Engraver {
|
|||
/// The implementation version of this solver (Chapter 9: within a fixed version,
|
||||
/// identical input produces identical output). Distinct from the stub's `0`;
|
||||
/// bumped to `2` when the casting-off pass landed (the resolved geometry of a
|
||||
/// wrapping score differs from version `1`'s single endless system).
|
||||
pub const ENGRAVER_VERSION: SolverVersion = SolverVersion(2);
|
||||
/// wrapping score differs from version `1`'s single endless system), and to `3`
|
||||
/// when casting-off gained its widow-rebalance phase (a wrapping score's system
|
||||
/// breaks — and so its baked geometry — differ again from version `2`'s pure
|
||||
/// greedy first-fit).
|
||||
pub const ENGRAVER_VERSION: SolverVersion = SolverVersion(3);
|
||||
|
||||
impl Engraver {
|
||||
/// An engraver casting off against the given page geometry.
|
||||
|
|
|
|||
|
|
@ -538,12 +538,16 @@ mod tests {
|
|||
// 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.
|
||||
// page-fill axis degenerates to exactly 0.0). Greedy first-fit alone
|
||||
// would leave a two-measure stub last system (width CV 0.61 -> the
|
||||
// clamped worst 1.0); casting-off's widow-rebalance evens that into a
|
||||
// six/four split (system widths ~59.5 vs ~37.8 staff spaces, CV ~0.22),
|
||||
// so casting_off measures ~0.45 — comfortably inside the Minimal 0.90
|
||||
// threshold. The trade-off is on the break axis: the non-final system
|
||||
// is deliberately left ~66% full (not greedy's ~87%), so system_break
|
||||
// rises to ~0.68 — still well inside Minimal. Both axes sit above the
|
||||
// Standard profile's floor (0.8 x 0.35 = 0.28), so both fire the
|
||||
// SHOULD-level diagnostic, which per the catalog never changes status.
|
||||
let report = Engraver::default().solve(&ten_measure(), &SolverConfig::default());
|
||||
let vector = &report.metric_vector;
|
||||
assert_eq!(vector.collision_penalty.0, 0.0);
|
||||
|
|
@ -552,28 +556,31 @@ mod tests {
|
|||
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: {}",
|
||||
(0.55..0.85).contains(&vector.system_break_penalty.0),
|
||||
"the non-final system is evened below 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!(
|
||||
(0.3..0.6).contains(&vector.casting_off_quality.0),
|
||||
"the rebalanced split is even, not a clamped-worst stub: {}",
|
||||
vector.casting_off_quality.0
|
||||
);
|
||||
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
|
||||
}
|
||||
)));
|
||||
// Both the casting-off and the system-break axes exceed 0.8 x their
|
||||
// Standard threshold, so the SHOULD-level floor diagnostic fires for
|
||||
// each — the two sides of the rebalance trade-off, honestly reported —
|
||||
// and, per the catalog, the status is untouched by them.
|
||||
let floored = |metric: QualityMetricKind| {
|
||||
report.warnings.iter().any(|w| {
|
||||
matches!(w.kind, SolverWarningKind::QualityFloorApproached { metric: m } if m == metric)
|
||||
})
|
||||
};
|
||||
assert!(floored(QualityMetricKind::CastingOff));
|
||||
assert!(floored(QualityMetricKind::SystemBreak));
|
||||
assert_eq!(report.status, SolveStatus::Solved);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ provenance_count=147
|
|||
layer_count=1
|
||||
hard_constraint_count=90
|
||||
xml_well_formed=true
|
||||
view_box=[5.4999995 -26.090391 83.99065 20.589813]
|
||||
view_box=[5.4999986 -26.090391 64.94533 20.589813]
|
||||
class_counts:
|
||||
barline=10
|
||||
clef=1
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="839.9065" height="205.8981" viewBox="0 0 83.9906 20.5898">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="649.4532" height="205.8981" viewBox="0 0 64.9453 20.5898">
|
||||
<!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines inlined as paths; geometry is the resolved layout verbatim, no engraving performed here; every glyph and stroke carries a data-prov trace to its score-graph source -->
|
||||
<g transform="translate(-5.5 -5.5006) scale(1 -1)">
|
||||
<g data-layer="0">
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="01550dc9d118134419219044f188e28a" data-source-kind="6" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-12.8926" x2="87.4256" y2="-12.8926" stroke="#000000" stroke-width="0.13" data-prov="5235156954d3cdda987a3da1af222a55" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-11.8926" x2="87.4256" y2="-11.8926" stroke="#000000" stroke-width="0.13" data-prov="10cbe3e182e937b8e3d90a03397b2e97" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-10.8926" x2="87.4256" y2="-10.8926" stroke="#000000" stroke-width="0.13" data-prov="94825cd467fe6fb512d271e66b59674a" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-9.8926" x2="87.4256" y2="-9.8926" stroke="#000000" stroke-width="0.13" data-prov="bc2cd26cec8e4650eedcf04fbc91320f" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-8.8926" x2="87.4256" y2="-8.8926" stroke="#000000" stroke-width="0.13" data-prov="c46b9445a4c5ff909f50b13f9025c4a3" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-12.8926" x2="68.3803" y2="-12.8926" stroke="#000000" stroke-width="0.13" data-prov="5235156954d3cdda987a3da1af222a55" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-11.8926" x2="68.3803" y2="-11.8926" stroke="#000000" stroke-width="0.13" data-prov="10cbe3e182e937b8e3d90a03397b2e97" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-10.8926" x2="68.3803" y2="-10.8926" stroke="#000000" stroke-width="0.13" data-prov="94825cd467fe6fb512d271e66b59674a" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-9.8926" x2="68.3803" y2="-9.8926" stroke="#000000" stroke-width="0.13" data-prov="bc2cd26cec8e4650eedcf04fbc91320f" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-8.8926" x2="68.3803" y2="-8.8926" stroke="#000000" stroke-width="0.13" data-prov="c46b9445a4c5ff909f50b13f9025c4a3" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="fe116eb75ff697f9f2451c10c05349c6" data-source-kind="2" data-kind="stroke"/>
|
||||
<line x1="14.5399" y1="-13.8926" x2="14.5399" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="dbd573fbc0c21f9b07a8537f658b048c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="12.7444" y1="-13.8926" x2="14.5251" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="07ffb45f25e59b0955bd4e248522b2ef" data-source-kind="1" data-kind="stroke"/>
|
||||
|
|
@ -58,47 +58,47 @@
|
|||
<line x1="64.519" y1="-13.8926" x2="66.2997" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="68c691f2336e5a322116641b9f2f7054" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="68.1795" y1="-13.8926" x2="68.1795" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="cfa457e70519a1826f321a56cb3fd03b" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="66.5997" y1="-13.8926" x2="68.3803" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="3c3f0d6b5ab00374d7628cd6f9876bc9" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="71.6758" y1="-13.8926" x2="71.6758" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="273d04eeece605789035a29d80023c2c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="69.8803" y1="-13.8926" x2="71.661" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="9ec226573f8ea2320fc444c562163ddb" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="73.7565" y1="-13.8926" x2="73.7565" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="a23b09fda2784b566bf1de48e89a2baa" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="71.961" y1="-13.8926" x2="73.7417" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="9e3345d5dddffbb9cf263a1708fc2589" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="75.8371" y1="-13.8926" x2="75.8371" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="edf9ca4611cfc17faace6241cae3b73d" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="74.0417" y1="-13.8926" x2="75.8223" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="ea06a5b83026202fbe3eed55ca3680c9" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="77.7022" y1="-13.8926" x2="77.7022" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="f1e3c0367633266d5f188e5bd6d98ad4" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="76.1223" y1="-13.8926" x2="77.903" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="b8d324f0620ebb59f66346b61a4d214e" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="81.1985" y1="-13.8926" x2="81.1985" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="2bf824905032462ee8c9988b780feabf" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="79.403" y1="-13.8926" x2="81.1837" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="579f0f80918268f1ba03890d14832b56" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="83.2791" y1="-13.8926" x2="83.2791" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="08a65aebf8baaded74feb3ec7cc36d18" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="81.4837" y1="-13.8926" x2="83.2643" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="f04f04c436808c53ab1c2b169d7f5a33" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="85.3598" y1="-13.8926" x2="85.3598" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="d155779804ad660f4466745153872349" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="83.5643" y1="-13.8926" x2="85.345" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="3b9174fe7361dbfc454fbe17b9ec81b1" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="87.2248" y1="-13.8926" x2="87.2248" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="af73177552d5fa95b746caf62d000623" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="85.645" y1="-13.8926" x2="87.4256" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="46e6eac9f03c1e09a15cb0bb07628570" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="10.5605" y1="-23.5904" x2="10.5605" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="7c839e4fb5f2522c8fde3b48577eafae" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="8.765" y1="-23.5904" x2="10.5457" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="0e4ec9f37979b06c54f0e2d499df5bf1" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="12.6412" y1="-23.5904" x2="12.6412" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="0c8e0b8170c827494e606d4541ce2c89" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="10.8457" y1="-23.5904" x2="12.6263" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="61f1ed037baf82bf6594769a9205df07" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="14.7218" y1="-23.5904" x2="14.7218" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="42480ffc14ad129d8dec456e7bad317c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="12.9263" y1="-23.5904" x2="14.707" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="adc82c9de5d3ed333e335c32f7690a6b" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="16.8025" y1="-23.5904" x2="16.8025" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="0f408610c63793003f04e3af5a03493f" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="15.007" y1="-23.5904" x2="16.7877" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="1dee0e17b4b9167cda8e51b45e2ab47c" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="18.8832" y1="-23.5904" x2="18.8832" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="99d50b32a7d1eac1019c14132a834144" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="17.0877" y1="-23.5904" x2="18.8684" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="c33f0e929fa3ea4230c07aca80f60c04" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="20.9639" y1="-23.5904" x2="20.9639" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="572e96450f21b18771e510c6c41444dc" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="19.1684" y1="-23.5904" x2="20.949" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="67463b29bfb160641501f497f6134aa6" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="23.0445" y1="-23.5904" x2="23.0445" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="68935c410dbe357c3f80b01c6e6d3cb1" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="21.249" y1="-23.5904" x2="23.0297" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="75f41055f9e23911ca51158dab6488df" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="24.2903" y1="-23.5904" x2="24.2903" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="9e9ba65603d31963bdc6d55dfacc5eba" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="23.3297" y1="-23.5904" x2="25.1104" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="66a37f88caecfe799ec34469d2302afd" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="10.5605" y1="-23.5904" x2="10.5605" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="273d04eeece605789035a29d80023c2c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="8.765" y1="-23.5904" x2="10.5457" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="9ec226573f8ea2320fc444c562163ddb" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="12.6411" y1="-23.5904" x2="12.6411" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="a23b09fda2784b566bf1de48e89a2baa" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="10.8457" y1="-23.5904" x2="12.6263" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="9e3345d5dddffbb9cf263a1708fc2589" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="14.7218" y1="-23.5904" x2="14.7218" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="edf9ca4611cfc17faace6241cae3b73d" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="12.9263" y1="-23.5904" x2="14.707" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="ea06a5b83026202fbe3eed55ca3680c9" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="16.5868" y1="-23.5904" x2="16.5868" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="f1e3c0367633266d5f188e5bd6d98ad4" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="15.007" y1="-23.5904" x2="16.7877" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="b8d324f0620ebb59f66346b61a4d214e" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="20.0831" y1="-23.5904" x2="20.0831" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="2bf824905032462ee8c9988b780feabf" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="18.2877" y1="-23.5904" x2="20.0683" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="579f0f80918268f1ba03890d14832b56" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="22.1638" y1="-23.5904" x2="22.1638" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="08a65aebf8baaded74feb3ec7cc36d18" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="20.3683" y1="-23.5904" x2="22.149" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="f04f04c436808c53ab1c2b169d7f5a33" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="24.2445" y1="-23.5904" x2="24.2445" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="d155779804ad660f4466745153872349" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="22.449" y1="-23.5904" x2="24.2297" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="3b9174fe7361dbfc454fbe17b9ec81b1" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="26.1095" y1="-23.5904" x2="26.1095" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="af73177552d5fa95b746caf62d000623" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="24.5296" y1="-23.5904" x2="26.3103" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="46e6eac9f03c1e09a15cb0bb07628570" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="29.6058" y1="-23.5904" x2="29.6058" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="7c839e4fb5f2522c8fde3b48577eafae" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="27.8103" y1="-23.5904" x2="29.591" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="0e4ec9f37979b06c54f0e2d499df5bf1" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="31.6865" y1="-23.5904" x2="31.6865" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="0c8e0b8170c827494e606d4541ce2c89" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="29.891" y1="-23.5904" x2="31.6717" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="61f1ed037baf82bf6594769a9205df07" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="33.7672" y1="-23.5904" x2="33.7672" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="42480ffc14ad129d8dec456e7bad317c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="31.9717" y1="-23.5904" x2="33.7523" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="adc82c9de5d3ed333e335c32f7690a6b" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="35.8478" y1="-23.5904" x2="35.8478" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="0f408610c63793003f04e3af5a03493f" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="34.0523" y1="-23.5904" x2="35.833" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="1dee0e17b4b9167cda8e51b45e2ab47c" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="37.9285" y1="-23.5904" x2="37.9285" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="99d50b32a7d1eac1019c14132a834144" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="36.133" y1="-23.5904" x2="37.9137" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="c33f0e929fa3ea4230c07aca80f60c04" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="40.0092" y1="-23.5904" x2="40.0092" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="572e96450f21b18771e510c6c41444dc" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="38.2137" y1="-23.5904" x2="39.9944" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="67463b29bfb160641501f497f6134aa6" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="42.0899" y1="-23.5904" x2="42.0899" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="68935c410dbe357c3f80b01c6e6d3cb1" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="40.2944" y1="-23.5904" x2="42.075" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="75f41055f9e23911ca51158dab6488df" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="43.3356" y1="-23.5904" x2="43.3356" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="9e9ba65603d31963bdc6d55dfacc5eba" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="42.375" y1="-23.5904" x2="44.1557" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="66a37f88caecfe799ec34469d2302afd" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="40a9a55985c8bf8b1eb66b6d8c8e7b7a" data-source-kind="12" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="10eb9773d25f0d7d64f92b36f88e5b38" data-source-kind="14" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="86a982398112115027c54222505dd143" data-source-kind="15" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="c183669ac91f35eb7a0040b48fde85ab" data-source-kind="25" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-22.5904" x2="25.6976" y2="-22.5904" stroke="#000000" stroke-width="0.13" data-prov="e5c54b1ebf58ed5292d1b98f73ad084e" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-21.5904" x2="25.6976" y2="-21.5904" stroke="#000000" stroke-width="0.13" data-prov="9ee2d26461a7bc2f28eaf20e07b55ee0" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-20.5904" x2="25.6976" y2="-20.5904" stroke="#000000" stroke-width="0.13" data-prov="c5feea54a17beefce1088337f84e39fc" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-19.5904" x2="25.6976" y2="-19.5904" stroke="#000000" stroke-width="0.13" data-prov="688349b3e25cac395b2dcca6e297dcfe" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-18.5904" x2="25.6976" y2="-18.5904" stroke="#000000" stroke-width="0.13" data-prov="c146782303bbe4c29c6228235090facf" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-22.5904" x2="44.7429" y2="-22.5904" stroke="#000000" stroke-width="0.13" data-prov="e5c54b1ebf58ed5292d1b98f73ad084e" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-21.5904" x2="44.7429" y2="-21.5904" stroke="#000000" stroke-width="0.13" data-prov="9ee2d26461a7bc2f28eaf20e07b55ee0" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-20.5904" x2="44.7429" y2="-20.5904" stroke="#000000" stroke-width="0.13" data-prov="c5feea54a17beefce1088337f84e39fc" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-19.5904" x2="44.7429" y2="-19.5904" stroke="#000000" stroke-width="0.13" data-prov="688349b3e25cac395b2dcca6e297dcfe" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-18.5904" x2="44.7429" y2="-18.5904" stroke="#000000" stroke-width="0.13" data-prov="c146782303bbe4c29c6228235090facf" data-source-kind="3" data-kind="stroke"/>
|
||||
<path d="M1.504 1.66C1.496 1.708 1.504 1.712 1.528 1.736C1.96 2.14 2.288 2.648 2.288 3.26C2.288 3.608 2.192 3.952 2.028 4.192C1.968 4.28 1.864 4.392 1.82 4.392C1.764 4.392 1.64 4.288 1.56 4.2C1.264 3.872 1.168 3.372 1.168 2.956C1.168 2.724 1.196 2.464 1.224 2.3C1.232 2.252 1.236 2.244 1.188 2.204C0.612 1.728 0 1.156 0 0.348C0 -0.348 0.476 -1.008 1.456 -1.008C1.548 -1.008 1.652 -1 1.732 -0.984C1.776 -0.976 1.784 -0.972 1.792 -1.02C1.84 -1.288 1.9 -1.636 1.9 -1.824C1.9 -2.416 1.5 -2.488 1.264 -2.488C1.048 -2.488 0.944 -2.424 0.944 -2.372C0.944 -2.344 0.98 -2.332 1.072 -2.304C1.196 -2.268 1.34 -2.16 1.34 -1.928C1.34 -1.708 1.2 -1.52 0.956 -1.52C0.688 -1.52 0.528 -1.732 0.528 -1.98C0.528 -2.24 0.684 -2.632 1.288 -2.632C1.556 -2.632 2.076 -2.512 2.076 -1.832C2.076 -1.604 2.004 -1.224 1.96 -0.976C1.952 -0.928 1.956 -0.932 2.012 -0.908C2.416 -0.748 2.684 -0.408 2.684 0.044C2.684 0.556 2.308 1.008 1.72 1.008C1.616 1.008 1.616 1.008 1.604 1.08ZM1.88 3.772C2.012 3.772 2.12 3.664 2.12 3.444C2.12 3 1.74 2.64 1.424 2.364C1.396 2.34 1.38 2.344 1.372 2.396C1.356 2.5 1.348 2.636 1.348 2.764C1.348 3.388 1.636 3.772 1.88 3.772ZM1.444 1.048C1.456 0.972 1.456 0.976 1.384 0.952C1.032 0.832 0.804 0.516 0.804 0.176C0.804 -0.184 0.992 -0.44 1.264 -0.532C1.296 -0.544 1.344 -0.556 1.372 -0.556C1.404 -0.556 1.42 -0.536 1.42 -0.512C1.42 -0.484 1.388 -0.472 1.36 -0.46C1.192 -0.388 1.072 -0.216 1.072 -0.032C1.072 0.196 1.228 0.368 1.472 0.436C1.536 0.452 1.544 0.448 1.552 0.404L1.752 -0.788C1.76 -0.832 1.756 -0.832 1.696 -0.844C1.632 -0.856 1.552 -0.864 1.472 -0.864C0.772 -0.864 0.32 -0.476 0.32 0.08C0.32 0.316 0.36 0.632 0.692 1.008C0.932 1.276 1.116 1.424 1.304 1.576C1.344 1.608 1.352 1.604 1.36 1.56ZM1.72 0.412C1.712 0.46 1.716 0.472 1.764 0.468C2.088 0.44 2.356 0.168 2.356 -0.184C2.356 -0.436 2.204 -0.64 1.98 -0.752C1.932 -0.776 1.924 -0.776 1.916 -0.728Z" transform="translate(8.5599 -11.8926)" fill="#000000" data-prov="2bc09f9490decea3e13f24459f1b64f4" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(13.0444 -13.8926)" fill="#000000" data-prov="870b54f8730bed8251f4f891a3c4b233" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(15.1251 -13.8926)" fill="#000000" data-prov="5833135b23eaf581bccad90222f64e7e" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
|
|
@ -124,32 +124,32 @@
|
|||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(62.7383 -13.8926)" fill="#000000" data-prov="9645397274cb84055aeba5639f8b06a5" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(64.819 -13.8926)" fill="#000000" data-prov="9578182658c00300531491532d13f66a" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(66.8997 -13.8926)" fill="#000000" data-prov="0a4dcf79021a9ba0366091e34fab456c" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(70.1803 -13.8926)" fill="#000000" data-prov="4b3905877c48341998d76f07cdb9eff5" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(72.261 -13.8926)" fill="#000000" data-prov="4582d540c93386289e704713ab2deeeb" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(74.3417 -13.8926)" fill="#000000" data-prov="b6d88aa94cc5462fed11ef21a9b9d6df" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(76.4223 -13.8926)" fill="#000000" data-prov="88fff9d9a3f4097be33105eff4f0664a" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(79.703 -13.8926)" fill="#000000" data-prov="1c0a15caddb0e912285270f67ff23a3b" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(81.7836 -13.8926)" fill="#000000" data-prov="82e4c22515ed591ba37576873988586e" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(83.8643 -13.8926)" fill="#000000" data-prov="6304bcb23912da87e33c77509f7985f4" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(85.945 -13.8926)" fill="#000000" data-prov="99878edbfc3bbb1e8eca463cccd584ce" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(9.065 -23.5904)" fill="#000000" data-prov="d10593c1a375a3b07eb107dae9e897b3" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(11.1457 -23.5904)" fill="#000000" data-prov="ba9722cd8f95336b0804751c84983573" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(13.2263 -23.5904)" fill="#000000" data-prov="103fdcd52932b1c8f5fab0036c2dd002" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(15.307 -23.5904)" fill="#000000" data-prov="fcd49faa62e91333932dbac75206c859" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(17.3877 -23.5904)" fill="#000000" data-prov="cc13049cf24d89de78d5dcc3bed18d7f" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(19.4684 -23.5904)" fill="#000000" data-prov="88a15f00dc6e06aaf9f2532f05f6197e" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(21.549 -23.5904)" fill="#000000" data-prov="cd16148313e9a4f391ab0cabfeb1ee4c" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(23.6297 -23.5904)" fill="#000000" data-prov="df598a181d9acccffc346705fce5da9b" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(9.065 -23.5904)" fill="#000000" data-prov="4b3905877c48341998d76f07cdb9eff5" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(11.1457 -23.5904)" fill="#000000" data-prov="4582d540c93386289e704713ab2deeeb" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(13.2263 -23.5904)" fill="#000000" data-prov="b6d88aa94cc5462fed11ef21a9b9d6df" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(15.307 -23.5904)" fill="#000000" data-prov="88fff9d9a3f4097be33105eff4f0664a" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(18.5877 -23.5904)" fill="#000000" data-prov="1c0a15caddb0e912285270f67ff23a3b" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(20.6683 -23.5904)" fill="#000000" data-prov="82e4c22515ed591ba37576873988586e" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(22.749 -23.5904)" fill="#000000" data-prov="6304bcb23912da87e33c77509f7985f4" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(24.8297 -23.5904)" fill="#000000" data-prov="99878edbfc3bbb1e8eca463cccd584ce" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(28.1103 -23.5904)" fill="#000000" data-prov="d10593c1a375a3b07eb107dae9e897b3" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(30.191 -23.5904)" fill="#000000" data-prov="ba9722cd8f95336b0804751c84983573" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(32.2717 -23.5904)" fill="#000000" data-prov="103fdcd52932b1c8f5fab0036c2dd002" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(34.3523 -23.5904)" fill="#000000" data-prov="fcd49faa62e91333932dbac75206c859" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(36.433 -23.5904)" fill="#000000" data-prov="cc13049cf24d89de78d5dcc3bed18d7f" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(38.5137 -23.5904)" fill="#000000" data-prov="88a15f00dc6e06aaf9f2532f05f6197e" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(40.5944 -23.5904)" fill="#000000" data-prov="cd16148313e9a4f391ab0cabfeb1ee4c" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(42.675 -23.5904)" fill="#000000" data-prov="df598a181d9acccffc346705fce5da9b" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(11.5444 -12.8926)" fill="#000000" data-prov="867b15e7cfaeccccfe301517166fe106" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(21.0671 -12.8926)" fill="#000000" data-prov="2d8913235b36cc0b1c41dc5238cba014" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(30.5897 -12.8926)" fill="#000000" data-prov="b263edd8a4514fa237d4e7942c04be8c" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(40.1124 -12.8926)" fill="#000000" data-prov="abc1bd57e0116cfc47c4f84f0c22b2e6" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(49.635 -12.8926)" fill="#000000" data-prov="9f811a0578b546d35393e29bb06afe9b" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(59.1577 -12.8926)" fill="#000000" data-prov="7c62f742d0a60d8a6177a714ef9241af" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(68.6803 -12.8926)" fill="#000000" data-prov="0b1e63cbd07616159ee70000e3c76056" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(78.203 -12.8926)" fill="#000000" data-prov="f21f4e337050d50f36f7d905196db794" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(7.565 -22.5904)" fill="#000000" data-prov="f4a3968aa56472207e4e921c44dfa1d7" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(25.4104 -22.5904)" fill="#000000" data-prov="d99180b4838ec1269b3f4caee0201968" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(7.565 -22.5904)" fill="#000000" data-prov="0b1e63cbd07616159ee70000e3c76056" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(17.0877 -22.5904)" fill="#000000" data-prov="f21f4e337050d50f36f7d905196db794" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(26.6103 -22.5904)" fill="#000000" data-prov="f4a3968aa56472207e4e921c44dfa1d7" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(44.4557 -22.5904)" fill="#000000" data-prov="d99180b4838ec1269b3f4caee0201968" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
|
@ -31,8 +31,11 @@
|
|||
//! [`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).
|
||||
//! than silently waived. As of engrave v3 every v0.1 entry passes every Minimal
|
||||
//! threshold, so no entry carries an xfail row: RS-1's original
|
||||
//! `casting_off_quality` miss (P12-I11 — greedy first-fit's stub last system)
|
||||
//! was cleared by the engraver's casting-off widow-rebalance phase, the machinery
|
||||
//! that would have asserted the miss now stands ready for the next one.
|
||||
//!
|
||||
//! Following the testkit's library-module-per-harness policy (DECISIONS F0),
|
||||
//! this module holds the machinery and `tests/reference_suite.rs` asserts it —
|
||||
|
|
@ -118,24 +121,16 @@ pub fn entries() -> Vec<SuiteEntry> {
|
|||
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",
|
||||
}],
|
||||
// Passes every Minimal threshold under the reference engraver
|
||||
// (engrave v3, QMC v0.1.0 anchors). Greedy first-fit alone left a
|
||||
// two-measure stub last system (width CV 0.61 -> clamped 1.0 > the
|
||||
// 0.90 threshold, the original P12-I11 miss); casting-off's
|
||||
// widow-rebalance phase now evens the split to a six/four
|
||||
// distribution (width CV ~0.22 -> casting_off ~0.45), clearing the
|
||||
// miss the honest way — an engrave-side balance pass, goldens
|
||||
// regenerated, no QMC anchor/threshold relaxation. See DECISIONS.md
|
||||
// and the engrave casting module.
|
||||
minimal_xfail: &[],
|
||||
},
|
||||
SuiteEntry {
|
||||
id: "RS-2",
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ 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-I11~~ **RESOLVED (engrave v3 widow-rebalance)** | `epiphany-engrave` I | RS-1 honestly failed the Minimal casting-off threshold under the reference engraver (measured 1.0 vs 0.90): greedy first-fit left a two-measure stub last system (width CV 0.6145 ≥ the 0.5 anchor). **Resolved the honest way — an engrave-side casting-off balance pass, no catalog change:** casting-off gained a second **widow-rebalance** phase that moves whole trailing measures from a region's penultimate system into its final one, choosing the shift that minimizes the larger of the two distribution penalties the catalog defines for the break family (width imbalance vs non-final underfill; both share the 0.5 anchor). RS-1 now casts six/four instead of eight/two: casting_off 1.0 → **0.4463**, system_break 0.254 → 0.677, every axis ≤ 0.90. `ENGRAVER_VERSION` 2 → 3; render goldens regenerated; the suite's Xfail row promoted to a plain Pass. The `casting_off` 0.5 anchor and the Minimal column stood — the engraver improved, not the threshold — so the Quality Metric Catalog is normatively unchanged. | ✅ done |
|
||||
| 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) |
|
||||
|
||||
|
||||
|
|
@ -80,7 +80,10 @@ work added C1..C4 (re-anchoring), D1 (bundle operation index), and E1..E5
|
|||
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.
|
||||
2026-07-03) added I11..I12. A follow-up (2026-07-03) resolved I11 (struck
|
||||
through above): casting-off gained a widow-rebalance phase (engrave v3), so
|
||||
RS-1 passes every Minimal threshold with no catalog change — I12 (the
|
||||
Standard-tier spacing floor on short scores) stays open.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -48,3 +48,15 @@ content modification, ResolveConflict beyond the concurrent case, RespellPitch
|
|||
v0 migration fallback, Transpose interval algebra), plus the audit's Push-3
|
||||
wiring tracks (constraints, overrides, validation modes, edit barriers,
|
||||
operation index) — those are code work, not spec alignment.
|
||||
|
||||
## P12-I11 resolution (2026-07-03) — no spec change
|
||||
|
||||
The batch's one measured-and-tracked conformance miss, **P12-I11** (RS-1
|
||||
honestly failed the Minimal `casting_off_quality` threshold — greedy first-fit
|
||||
left a two-measure stub last system, width CV 0.6145 clamped to 1.0 > 0.90),
|
||||
is resolved **entirely on the implementation side**, so it appears here only to
|
||||
record that the Quality Metric Catalog and the core spec were **not** changed.
|
||||
|
||||
| Item | Disposition | Spec locus | Consumer |
|
||||
|---|---|---|---|
|
||||
| P12-I11 casting-off stub last system | **no spec change (engrave fix)** — casting-off gained a second **widow-rebalance** phase (`epiphany-engrave` v3): it moves whole trailing measures from a region's penultimate system into its final one, choosing the shift that minimizes the larger of the two distribution penalties the catalog already defines for the break family (`casting_off_quality` width imbalance vs `system_break_penalty` non-final underfill; both share the 0.5 anchor). RS-1 casts six/four instead of eight/two — `casting_off` 1.0 → 0.4463, every axis ≤ 0.90 — so the suite's asserted Xfail row is promoted to a plain Pass. This is the deliberately-chosen *honest* resolution: the `casting_off` 0.5 anchor and the Minimal 0.90 column were **vindicated, not relaxed** (the engraver improved, no anchor rescale / threshold loosening / RS-1 per-entry override). Core spec Chapter 9's "Minimal makes no optimality claim" already permits the heuristic; nothing normative changed | — (no spec locus; core spec + Quality Metric Catalog unchanged) | `epiphany-engrave` (`casting::rebalance_widows`, `ENGRAVER_VERSION` 3); `epiphany-testkit` (RS-1 xfail row removed); render goldens regenerated |
|
||||
|
|
|
|||
Loading…
Reference in New Issue