The third pipeline primitive kind, review-hardened (5 verified findings fixed pre-commit). A `Curve` (four control points, mirroring `Stroke`) threads through all three IR stages, the canonical-encode fingerprint (a 5th u32 count prefix; width-lock 4→5), the round-trip provenance chains and count identity, `to_render`, the stub solver, engrave remap + casting, SVG path emission, and hit-testing. Slurs draw as one cubic bézier per slur carrying the slur's exact provenance (no synthesis). LayoutContent::Slur resolves each endpoint event to a Note column at to_logical (SlurEndpoint At/Unresolved — E1's honest-placement discipline); a symmetric arc whose apex sits `height` from the endpoint line; curvature_override direction+height honored, style.line (dashed) deferred to Push 3. Honest non-drawing (traced anchor kept) for an unresolved endpoint, a non-left-to-right span, or a cross-staff slur (no single staff — would float at yo=0). Authored height/thickness sanitized to defaults when non-positive (a negative thickness would else fail validation and blank the layout). Hit-test: HitShape::Curve (Copy) flattens the cubic to a 16-segment capsule inside contains/intersects (one region per curve); a slur click resolves to Slur generically (no editor arm; edit ops refuse it). render-svg: stroked unfilled <path C> after strokes/before glyphs + curve_count + content-hull bounds. engrave: HorizontalRemap::curves; casting curve_system = Rigid-to- start-system (no de Casteljau split — deferred; break-spanning slur draws whole in its start system, kept for the source surjection); ENGRAVER_VERSION 4→5; slur_shape_penalty now 0.0 by construction (Minimal draws the ideal arc). Existing SVG goldens changed only in the provenance-note comment (curves now enumerated) — geometry byte-identical; 6 snapshots gained curve_count; new ten_measure_with_slurs goldens. 929 tests, conformance 8/8. 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