The reviewer signed off on the inter-staff slice with one named residual risk: the 3+-staff cumulative shift cascade was documented and traced correct but had no fixture behind it -- valid_score_rich's "three staves" are three separate single-staff regions, so each lands in its own system and the cascade never runs. three_staff_close_content puts three staves in ONE region with deliberately asymmetric pressure: the upper pair collides hard (C1 against C7), the lower pair only gently. That asymmetry is what makes the fixture discriminating. Sizing each pair independently -- the plausible wrong implementation -- measures the lower pair against the middle staff's ORIGINAL position, hands the bottom staff only its own small correction, and drags it back up through the middle staff. Verified by mutation, not by assertion alone: with the cascade removed the bottom staff's shift collapses from 34.68 to 4.56 against the middle staff's 15.06, and both the shift ordering and the staff-line-gap assertions fail. two_staff_close_content still passes under that same mutation, which is precisely why the new fixture was needed. Writing the test also corrected a wrong mental model, now recorded in DECISIONS.md: a shift INCREMENT generally exceeds the lower pair's own raw correction, because the upper staff's descent has itself eaten into that pair's gap and must be undone. The first version of the test asserted the opposite and failed against a correct solve. The fixture additionally pins curve attribution against a three-band choice -- the slur must still find the bottom staff, not merely the nearer of two -- and carries its own render golden. No existing golden churns. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| Cargo.toml | ||
| DECISIONS.md | ||
| README.md | ||
README.md
epiphany-engrave
Agent I's engraving constraint solver (spec Chapter 9): turns a
ConstrainedLayoutIR into a ResolvedLayoutIR with real geometry. It is the
production-side replacement for epiphany-layout-ir's interface-only
StubSolver; the two live in separate crates so the spec's core/product boundary
stays sharp.
Status: Minimal tier, with casting-off
Engraverruns a deterministic horizontal spacing pass (the first axis of the two-pass spring layout): each spring slot is placed left-to-right by a collision-aware advance derived from real glyph bearings.- A casting-off pass (Phase 3's layout track) then breaks the spaced line
into systems at measure boundaries (greedy first-fit against a
PageGeometry— default A4 portrait at an 8 mm staff), stacks systems vertically at the vertical-band model's inter-system gap, assigns pages by content height, and populates the realResolvedPage/ResolvedSystemtree. Every position is baked into a single y-up world frame (pages stacked vertically), so the SVG renderer and hit-testing consume the flat glyph/stroke lists unchanged. - The IR's declared constraints are evaluated — geometric families against
the pre-casting spaced frame, break constraints against the final break
structure (hard breaks are always honoured; a pathological soft break is
skipped and recorded as an
IrOverridedecision). Chosen breaks are recorded asEngravingDecisions withSynthesisKind::EngravedBreaktargets, attributed to the user override that requested them when one did. - It reports
SolverTier::Minimal: hard constraints (break family included) satisfied, no optimality claim — the quality-metric vector stays the honest all-worst placeholder until the Quality Metric Catalog lands.
Deferred: the vertical soft-spring solve, per-system justification/stretch,
optimal break search, widow/orphan control, and casting-off caching. See
DECISIONS.md.
use epiphany_engrave::Engraver;
use epiphany_layout_ir::{ConstraintSolver, SolverConfig};
let report = Engraver::default().solve(&constrained_ir, &SolverConfig::default());
assert!(report.satisfied_hard_constraints);
let resolved = report.layout; // real pages/systems; hand to epiphany-render-svg