epiphany/crates/epiphany-engrave
Levi Neuwirth 28f210e16f E2 follow-up: surface slur style deferral, conservative curve hit capsule
Three source-audit findings on 81b7f42, fixed:

1. Slur kind and line style were dropped at projection and every slur drew
   solid with no signal. SlurContent now carries `kind` (SlurKind) and `line`
   (LineStyle) through the projection, and a non-Solid authored line style
   emits a `LayoutDiagnosticKind::SlurLineStyleNotRendered` — the curve still
   draws (solid, ink + provenance kept), but the ignored dash/dotted intent is
   surfaced, not papered over. The Minimal tier still draws one canonical arc
   per kind (kind-aware rendering is higher-tier); the kind is now preserved
   for it. The slur fixture's editorial slur is authored Dashed to exercise it.

2. Curve hit-testing flattened to a fixed 16 chords and tested capsules at
   exactly half_width, so a thin/high-curvature slur's true ink between samples
   could miss a click. contains/intersects_rect now inflate the capsule by a
   proven flattening-error bound (h²/8·max‖B''‖ = (3/4N²)·max second-difference)
   — provably conservative, so a click on the drawn arc never misses. The AABB
   (control hull ± half_width) already bounds the true ink, unchanged.

3. The engrave crate-level doc still said "no drawn slur geometry exists yet";
   updated to slur_shape 0.0 by construction (Minimal draws the ideal arc),
   beam_slope still vacuous.

931 tests, docs clean, conformance 8/8. Zero golden churn (dashed slur draws
solid; layout diagnostics don't reach the render output).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 16:08:05 -04:00
..
src E2 follow-up: surface slur style deferral, conservative curve hit capsule 2026-07-08 16:08:05 -04:00
Cargo.toml Phase 3 tranche 1: casting-off, K1 schema-fill, value-restoring undo 2026-07-02 21:55:26 -04:00
DECISIONS.md Schema major 2 Phase E2: slur curves + cubic-bézier primitive 2026-07-08 15:33:00 -04:00
README.md Phase 3 tranche 1: casting-off, K1 schema-fill, value-restoring undo 2026-07-02 21:55:26 -04:00

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

  • Engraver runs 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 real ResolvedPage/ResolvedSystem tree. 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 IrOverride decision). Chosen breaks are recorded as EngravingDecisions with SynthesisKind::EngravedBreak targets, 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