epiphany/crates/epiphany-engrave
Levi Neuwirth ca687d8e7e Push 3 audit follow-up: modify anchor validation, spaced slur_shape, stale docs
Three source-audit findings:

High — ModifyCrossCutting could still introduce dangling spanner anchors.
create_cross_cutting was fixed (P13-D3) to validate anchor_object_refs(), but
modify_cross_cutting still validated only endpoints() — empty for a spanner's
region/measure anchors — so a live event-anchored spanner could be MODIFIED onto
a missing RegionId/MeasureId and written into the graph past the core invariant
that checks spanner anchors at all three kinds. Fixed by mirroring create's
anchor_object_refs() liveness precondition in modify (endpoints() still feeds the
event-only referent index). Regression extends
create_cross_cutting_spanner_preconditions_region_measure_anchors with a modify
case (verified to fail without the fix: the dangling modify reached the graph).

Medium — slur_shape_penalty measured the constrained (pre-remap) curves, not the
drawn shape. The Engraver remaps curves before casting, so an ideal-in-source
slur could read ideal even after horizontal re-spacing visibly flattened or
steepened it; the catalog units are "drawn slurs." Now measured over the SPACED
whole curves (post-remap, pre-split) — threaded into quality::measure — so
re-spacing distortion is honestly captured while a break-spanning slur is still
measured whole (not as flatter fragments).

Low — stale comments: CastLayout.curves and curve_fate said break-spanning
curves draw whole with de Casteljau deferred (they now split); SlurContent.line
said non-solid slurs surface a diagnostic (they now render dashed/dotted); the
system_derived_rewrite doc called the never-minted system-pitch introduction an
unfixed Pass-13 residue (P13-K1 now rejects it).

940 tests, clippy 0, docs -D warnings, conformance 8/8, zero golden churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 18:54:39 -04:00
..
src Push 3 audit follow-up: modify anchor validation, spaced slur_shape, stale docs 2026-07-08 18:54:39 -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 Push 3 audit follow-up: modify anchor validation, spaced slur_shape, stale docs 2026-07-08 18:54:39 -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