epiphany/crates/epiphany-layout-ir
Levi Neuwirth 9b5339f8f3 Schema major 2 Phase D: the repeat-authoring pair (code tranche)
CreateRepeatStructure / DeleteRepeatStructure land end-to-end,
review-hardened (five-dimension workflow review; 19 findings triaged —
12 verifier agents lost to a session limit were verified by hand).

epiphany-ops:
- CreateRepeatStructureOp { repeat } (lp(RepeatStructure), wire 28,
  BORN AT V2: schema_major always 2 — kind/voltas are unconditional
  fields) and DeleteRepeatStructureOp { repeat } (bare id, wire 29,
  major 0: minimal stamping over a major-0 layout; the discriminant a
  schema-minor append). OperationKindTag 28/29 name-verbatim with
  validating decode; golden tables grown to 30; migrate/v0 identity
  arms; lib re-exports.
- Reduction: set-union mint (live-id => AlreadyApplied without value
  comparison — the cross-cutting discipline; RecreateContentMismatch
  scope unchanged) with the ALL-anchor-sites-live precondition;
  delete-wins tombstone; the "Repeat structure / Anchor" rule-table
  row wired into BOTH the ledger (reanchor_for_tombstone arm, min-id
  survivor, cascade when dry) and the graph (every dead site rewrites
  to the same survivor), agreement asserted by tests incl. the
  DalSegno jump-target case.
- Undo: materialize_graph_tombstones gains RepeatStructure — and the
  pre-existing SPANNER GHOST fix (an undone spanner mint never left
  the graph), regression-locked.
- validate.rs: VoltaEndingsIllFormed advisory (non-empty, 1-based,
  strictly ascending; interactive-only).
- Canonical-base honesty: blake3 re-pinned for the corpus stream
  shift, its comment corrected (the seeded repeat creates all no-op),
  and the property covered surgically by
  the_canonical_base_embeds_no_repeat_values (two reductions
  differing only in repeat v2 content => byte-identical bases).

epiphany-core:
- RepeatStructure::anchor_sites()/anchor_sites_mut(): THE single
  site-set walk (start/end, jump targets, volta spans), consumed by
  reduction, the editor barrier seam, the invariant walk, and the
  cross-reference index — replacing five hand-rolled copies and
  fixing a SIXTH, silently-stale one (indexes.rs had never learned
  the Phase-B kind/volta anchors; regression-locked).
- RepeatStructure joins the CanonicalValue seam (strict per-value
  decode); decode-fuzz corpus gains valid_score_rich_with_repeats
  (DalSegno + voltas) CORPUS-LOCAL — shared render fixtures stay
  repeat-free until E1 (zero golden churn).

editor-core / gui / testkit / layout-ir:
- Barrier seam: repeat_event_refs + subjects_of arms +
  graph_structure_endpoints arm, gated end-to-end by
  repeat_authoring_kinds_derive_subjects_and_gate_on_barriers.
- Generators: operation_payload -> below(30); graph-session gen ->
  below(14); ops fuzz gen_payload -> below(27); layout_stub
  gen_operation_kind_tag unstaled 24..=29 (the Phase-3 tags were
  already missing); GUI name table.

Spec touch-ups (review findings): catalog says "strictly ascending";
the survivor tie-break is now explicit in the core rule-table row and
catalog (identifier-order minimum among multiple survivors;
proximity-aware four-key selection deferred, as for spanners).

Trail: ops + core DECISIONS entries; spec/PASS13_CANDIDATES.md opens
the Pass-13 ledger at three candidates (P13-K1; P13-D1 undo-driven
tombstones bypass ledger re-anchoring — pre-existing class; P13-D2
cue-cascade repair-trail ordering).

Full gate: fmt, clippy -D warnings, rustdoc -D warnings, 30 workspace
suites, conformance scale 1 (8/8).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-07 20:06:18 -04:00
..
src Schema major 2 Phase D: the repeat-authoring pair (code tranche) 2026-07-07 20:06:18 -04:00
Cargo.toml Land epiphany-layout-ir (Agent E): layout IR + solver interface 2026-06-19 19:58:01 -04:00
DECISIONS.md Pass 12 G-pass: ratify the full batch (28 rows) — spec tranche 2026-07-07 10:23:30 -04:00
README.md Land epiphany-layout-ir (Agent E): layout IR + solver interface 2026-06-19 19:58:01 -04:00

README.md

epiphany-layout-ir

The Epiphany layout intermediate representation and constraint-solver interface, implementing the normative requirements of Chapter 7 ("Layout Intermediate Representation") and the interface of Chapter 9 ("Constraint-Solver Interface") of the core specification (spec/core_spec.pdf). This is Agent E's crate per spec/QUICKSTART.md — it lands last among the implementation crates, building on Agent A's epiphany-determinism and Agent B's epiphany-core (and on Agent C's OperationKindTag for the edit-barrier types — see DECISIONS.md).

The IR sits between the score graph and two downstream consumers: the constraint solver, which resolves spacing and positioning, and the renderer, which produces final visual output. — Chapter 7

The score graph is the canonical truth about the music; this crate is a downstream projection of it. The transformation is a pipeline of four stages, each with its own type and a deterministic, provenance-preserving contract for the next.

What's here

Area Items Spec
Stage 1 — logical LogicalLayoutIR, LayoutRegion, composite LayoutObject, overrides/cross-region objects, to_logical Ch. 7 §"LogicalLayoutIR"
Stage 2 — constrained ConstrainedLayoutIR, SpringSlot, LayoutConstraint, GlyphObject, to_constrained Ch. 7 §"ConstrainedLayoutIR"
Stage 3 — resolved ResolvedLayoutIR, pages/systems/staves/measures, ResolvedGlyph Ch. 7 §"ResolvedLayoutIR"
Stage 4 — render (interface only) RenderIR, RenderPrimitive, RenderIRProducer, to_render Ch. 7 §"RenderIR"
Time axis canonical TimeAxisModel plus dynamic TimeAxis, including registered payload preservation Ch. 7 §"Layout Regions"
Provenance Provenance, LayoutObjectId, SynthesisKind, stable_layout_id (a pure function of the source — stable across relayouts) Ch. 7 §"Provenance"
Engraving decisions EngravingDecision/EngravingDecisionId/EngravingDecisionKind, DecisionSource Ch. 7 §"Engraving Decisions"
Vertical bands VerticalBand/VerticalBandId/VerticalBandKind Ch. 7 §"Vertical Bands"
Incremental cache LayoutCache, DependencyIndex, granular stage caches and invalidation Ch. 7 §"Incremental Layout and Caching"
Glyph catalog GlyphCatalog (metric-lookup interface) + in-tree BravuraCatalog, GlyphCatalogIdentity, SmuflVersion, FontId, GlyphMetric/GlyphAnchor, BRAVURA_METRICS/BRAVURA_VERSION, metrics_hash_for (MUSCFNTM-tagged) Ch. 7 §"Glyph Catalog Interface" / §7.3.2
Edit barriers EditBarrier, BarrierScope, BarrierCondition, ObjectKind, EditContext (precise scope evaluation), keyed on Agent C's OperationKindTag Ch. 8 §"Edit Barriers"
Solver interface ConstraintSolver (solve/solve_incremental, Send + Sync), SolverConfig/SolverBudget, SolverState, InvalidationSet, SolveReport, SolveStatus, SolverTier/SolverVersion, the v0 StubSolver Ch. 9
Round-trip round_trip, RoundTripReport, laid_out_object_ids Ch. 7 (v0 acceptance criterion 6)

The stub solver

Per the QUICKSTART, the v0 constraint solver is a stub: StubSolver returns SolveStatus::Solved with the input geometry verbatim (each glyph's resolved position is exactly its constrained baseline), preserves provenance, and reports all hard constraints satisfied. The real solver — Cassowary or otherwise — comes later (Chapter 9 specifies the interface, not the algorithm); v0 only needs to round-trip IR through the solver interface to prove the contracts hold. The one stub validates the full Chapter 7 §7.3.2 catalog identity and all slot, band, and geometry cross-references before reporting Solved. Explicit constraints are rejected because this interface-only solver cannot honestly evaluate them.

The round-trip (v0 acceptance criterion 6)

round_trip runs graph → LogicalLayoutIRConstrainedLayoutIR → stub-solved ResolvedLayoutIRRenderIR and asserts the contract every stage must satisfy:

  • the stub solver reports Solved with all hard constraints satisfied;
  • the complete Provenance of every object — source, synthesis, dependencies, and stable_id — survives every stage unchanged;
  • no two objects ever share a stable_id, so manifestation multiplicity is preserved (a source manifested in two regions stays two layout objects);
  • the stub solver returns the input geometry verbatim;
  • the set of score-graph sources recovered from the RenderIR is exactly the set laid out — a surjection onto graph identity (one source may back several manifestations, each with its own stable id).

Agent F's testkit drives this same entry point (layout_stub::round_trip) on the 10-measure single-staff hand-off fixture and the rich multi-region generator.

Algorithmic scope

Per the QUICKSTART ("a prototype baseline, not the product"), this crate implements the Chapter 7 IR contracts and interface types, not a production engraving engine. The real spacing and casting-off algorithms, quality-metric computation, constraint solver, and renderer remain later implementations of these interfaces.

Determinism

IR coordinates are single-precision staff spaces (StaffSpace(f32), Chapter 7 §7.2); the canonical ResolvedLayoutIR output quantizes them to the 1/1024 grid at serialization time (ResolvedLayoutIR::canonical_bytes), exactly as Appendix D §"Quantized Layout Coordinates" prescribes — quantization absorbs all f32 variation below 1/2048 staff space, so the canonical output is independent of the floating-point environment. The glyph-catalog identity hashes its consulted metrics (advance, bounding box, named anchors) under the MUSCFNTM domain tag, and the edit-barrier types carry a canonical encoding with set-valued fields emitted in canonical byte order (sorted and de-duplicated). See DECISIONS.md.

Tests

cargo test -p epiphany-layout-ir covers each module plus the round-trip on Agent B's valid_score / valid_score_rich generators. The end-to-end v0 acceptance gate (criterion 6) lives in the testkit.