16 KiB
epiphany-layout-ir — decisions and Pass 11 candidates
This file records (a) the implementation decisions the QUICKSTART asked each
agent to make once and document, and (b) the ambiguities discovered while
building epiphany-layout-ir, batched as Pass 11 candidates for the spec
rather than improvised in code (QUICKSTART, Process notes: "Ambiguities go into
a batch, not into code … Don't open Pass 11 until you have at least three such
items batched.").
RATIFIED (Pass 11, 2026-06-21). layout P11-2 (
LayoutObjectIdderivation) is ratified intocore_spec.tex§"Provenance" (req:layoutir:object-id-derivation): the spec pins aMUSCLOID-tagged derivation keying multiply-manifested objects on(source, region)and synthesized objects on(source, synthesis_kind, stable_semantic_instance_key). Layout ids are non-canonical, so theMUSCLOIDtag is flagged for Track A and not yet wired in code: this crate still mints provisional ids (untagged; synthesized borrowsMUSCCONF) because the frozen determinism crate exposes noMUSCLOIDtag (see thestable_layout_idbullet below). Adopting the spec'd derivation is Track A (solver/renderer) work. layout P11-1 (layout→ops dependency) stays a crate-topology call for the G–K re-cut. Seespec/PASS11_RATIFICATION_LOG.md.
Scope
The crate implements the Chapter 7 interface surface: all four stages, the logical composite taxonomy, overrides and cross-region objects, time-axis payloads and trait, spring slots and constraints, vertical bands, resolved pages/systems, render configuration, glyph-catalog identity, and incremental cache/dependency types. Per the QUICKSTART's prototype framing, the algorithms behind those interfaces remain simple; the constraint solver still returns validated input geometry verbatim and performs no production engraving, casting-off, quality optimization, or rendering.
The round-trip's strict stage-equality assertion (the full Provenance of every
object is preserved object-for-object) reflects this prototype's 1:1
projection — one layout object per laid-out score-graph object. A later stage
that flattens a composite object into multiple glyphs would relax that assertion
to source-coverage (every glyph's source is a laid-out object, every laid-out
object is covered); the provenance-preservation contract itself is unchanged.
Implementation decisions (QUICKSTART "Decisions you'll need to make")
- Replica ID entropy / 2. event-arena storage / 3. chunk store — N/A to this crate (Agents B and D).
- Async or sync — sync only. No async traits anywhere;
#![forbid(unsafe_code)]. - MSRV — workspace 1.77. No exotic features.
Local decisions
-
f32 IR coordinates, quantized at serialization. IR coordinates are single-precision staff spaces (
StaffSpace(f32), Chapter 7 §7.2: "Single- precision floating point MUST be used for IR coordinates"). Quantization to the canonical1/1024grid happens only when serializing canonicalResolvedLayoutIRoutput — exactly as Appendix D §"Quantized Layout Coordinates" prescribes: "Internal solvers MAY use floating point during computation; canonical serialization rounds toQuantizedCoord." [ResolvedLayoutIR::canonical_bytes] is that boundary; it round-trips f32 jitter below1/2048staff space to identical bytes. (An earlier draft of this crate quantized throughout the pipeline; that contradicted the explicit Chapter 7 f32 requirement and is corrected here.) -
Depend on
epiphany-opsforOperationKindTag. The QUICKSTART lists Agent E's dependencies as "A and B," but also assigns Agent E the edit-barrier types withOperationKindTag-basedprohibited_operation_kinds.OperationKindTagis Agent C's canonical discriminator type (Chapter 6); reproducing it here would create a second definition that could drift. We therefore take a single, narrow dependency onepiphany-opsfor that one type. This is sound:epiphany-opsdoes not depend on this crate (no cycle), and Agent E lands after Agent C. See Pass 11 candidate 1. -
ObjectKindfor edit barriers is theTypedObjectIddiscriminant. The spec'sEditBarrier.affected_object_kinds: Vec<ObjectKind>needs a score-graph object class key. TheObjectKindinepiphany-opsis a narrow system-counter-collision kind (Voice/Pitch/Registered), semantically unrelated, so we define a localObjectKind(pub u16)over theTypedObjectIddiscriminant — the natural object-class key in this codebase. -
Edit-barrier scopes and conditions are evaluated precisely. A
Region/StaffInstance/AnalysisLayer/PitchSpacebarrier prohibits only objects within that scope; the editor (which holds the score) supplies the candidate object's structural location viaEditContext. The known conditionsObjectExistsandObjectHasExtensionDataare evaluated via anEditOraclethe editor implements, not hardcoded totrue. Only genuinely unknown narrowing — aRegisteredscope or an unknownRegisteredcondition — is treated conservatively (as matching), per Chapter 8 §"Behavior Under Unknown Extensions". This avoids over-prohibiting edits to objects demonstrably outside a known scope or to objects a known condition excludes. -
stable_layout_idand the engraving-decision id borrow a domain tag. A layout object's stable id istrunc128(BLAKE3(source.canonical_bytes()))— a pure function of its source, so it is invariant under insertion/removal/ reordering of other objects (Chapter 7 §"Provenance"). It is not domain- separated, and the engraving-decision id borrows theMUSCCONFtag with a literalengraving-decisiontype prefix, because the frozen determinism crate (Agent A) defines no layout-object domain tag. Pass 11 ratified the target derivation — aMUSCLOID-tagged hash (req:layoutir:object-id-derivation) — but adopting it is Track A work (the determinism crate must first expose the layout-namespace tag); these ids stay provisional until then. See the header note. -
Repeated manifestations get per-
(source, region)ids. A score-graph object manifested within a region is laid out per manifestation: its stable id derives from(source, region)viamanifestation_layout_id/Provenance::manifested. A staff manifested in two time-disjoint regions (Chapter 5 §"Region Overlap and Concurrency") therefore yields two distinct layout objects — both visual staves are preserved, neither dropped — and the ids do not collide. The id is still independent of traversal position (it depends on region identity, not order), so it stays stable across relayouts. Score-level cross-cutting objects, which have a single manifestation, keep a source-only id (Provenance::projected). -
GlyphObjectId/VerticalBandIdreuse the provenance hash. A glyph'sGlyphObjectIdis its provenancestable_id(already manifestation-aware); a staff band'sVerticalBandIdis the staff layout object's manifestation id (VerticalBand::staff_manifestation), so two manifestations of a staff get two distinct bands. Both are stable across relayouts. -
Bundled Bravura metrics are a representative slice.
BRAVURA_METRICSholds ~two dozen real-Bravura glyphs (noteheads, clefs, accidentals, rests, flags, time signatures, barlines, dynamics) with advance, bounding box, and named anchors, in1/1024-staff-space units, tracking theBRAVURA_VERSIONrelease. Enough to exercise theMUSCFNTMcatalog identity and theGlyphCatalogmetric-lookup interface end to end without shipping a font file; render-data lookup and a full catalog are out-of-core concerns (Chapter 7 §"Glyph Catalog Interface"). -
Glyph identity flows to the resolved/render stages.
ResolvedGlyphandRenderPrimitivecarry an owned-or-borrowedGlyphReference, so the renderer knows what symbol to draw and the canonical encoding is injective in glyph identity — swapping two glyphs' names (even with the consulted-name set, and so the metrics hash, unchanged) changes the bytes. -
Comprehensive, rejecting canonical encoding for
ResolvedLayoutIR. The canonical output (ResolvedLayoutIR::canonical_bytes, viaCanonicalEncode) covers the full resolved layout — every glyph's provenance (source, stable id, synthesis kind, sorted/deduped dependencies), glyph name, and quantized position, every engraving decision, and the complete catalog identity — so any change that distinguishes two layouts (a swapped glyph, an altered engraving decision, a different manifestation id, a different font version) changes the bytes. A non-finite or out-of-range coordinate is rejected with a panic (faulting in every build), never normalized to the origin (Appendix D: invalid geometry is rejected). -
Each glyph is routed to its own staff's band.
GlyphObject.vertical_band(Chapter 7 §"Glyph-Level Objects") points at the band of the staff the glyph belongs to —LayoutObjectcarries that staff association, so a region manifesting two staves gets a staff band per staff with each glyph in exactly one (no cross-staff contamination). Region-level glyphs (the region object, cross-cutting, free-graphic) go to a margin band; multi-staff regions also carry emptyInterStaffGapspring bands between staves. Staff-band ids are the staff layout object's manifestation id (distinct per region). -
Free-graphic and hybrid graphic objects are projected.
to_logicalandlaid_out_object_idsprojectregion.content.graphic_objects()(Chapter 5 §"Graphic Content"), so free-graphic and hybrid regions are not silently dropped. -
Synthesized-object ids include kind and a stable semantic key.
Provenance::synthesized(source, kind, instance_key, deps)derives its id from(source, synthesis_kind, instance_key). The key describes the object's role and never traversal order, so insertion or reordering cannot renumber existing synthesized objects. -
Glyph-catalog interface:
Send + Sync, metrics + render data,SemVerversion, anchors as a map.GlyphCatalogisSend + Sync(shareable across parallel re-engraving) with bothmetricsandrender_data. The in-tree Bravura catalog bundles metrics but no outlines/bitmaps, so itsrender_datahonestly returnsNone(reportingSomewould claim data that does not exist).font_versionisOption<SemVer>, set to the SHA-pinnedbravura-1.392release the in-tree metrics are extracted from — the same font the renderer's outlines come from, so reserved metrics and drawn ink agree. The font declares a single decimal version ("Version 1.392"), recorded verbatim asSemVer { major: 1, minor: 392, patch: 0 }; seeBRAVURA_VERSIONfor the canonical mapping rule. Glyph anchors are a map keyed by name: the catalog hash sorts them by name and rejects a duplicate name (a panic), so the hash never depends on anchor slice order (Appendix D §"Ordered Iteration"). every catalog method, includingidentity, is object-safe; owned font, glyph, and anchor names support a runtime-loadeddyn GlyphCatalogwithout leaking strings. -
Chapter 9 interface in full shape; no quality-metric computation. The
ConstraintSolverinterface is implemented as the spec defines it:Send + Sync,solve/solve_incremental, aSolverConfigwithprofile/budget/tie_breaking, aSolveReportwithunsatisfied_constraints/warnings/metric_vector/budget_used/state(and the warning kinds, includingQualityFloorApproached/ExtensionWarning), and anInvalidationSetwithslots/bands/constraints/glyphs. The render boundary'sRenderIRProducer::produce(resolved, scale, config)takes the spec'sScaleContext/RenderConfiguration. The quality-metric/tie-breaking types exist; what the QUICKSTART defers is normalization computation. The exact non-optional interface is preserved: the stub reports the non-conformanceSolverTier::Stubrung (M5 follow-up — notMinimal, since a passthrough that evaluates no constraints and computes no quality metrics must not claim the lowest conformance tier;Stuborders belowMinimal), an all-worstQualityMetricVector, and rejects explicit constraints it cannot evaluate rather than claiming them satisfied. -
Constraint references are validated (M5 follow-up).
ConstrainedLayoutIR::validate()now also checks theLayoutConstraintvector:NoCollision/Align/PositionWithinmust name glyphs in the set,SystemBreakAt/PageBreakAtmust name existing spring slots, and aPositionWithinregion must be finite/non-negative. Dangling constraint references are rejected (UnknownConstraintGlyph/UnknownConstraintSlot/InvalidConstraintRegion) rather than silently accepted.Registered(extension) constraints stay opaque/conservative. Score-graph source validation (that aProvenance::sourcenames a real graph object) still belongs at theto_logicalboundary, which holds theScore. -
ScoreVersionis content-sensitive (M5 follow-up). It is now derived from the whole score's canonical bytes (Agent B's whole-score codec) rather than the layout projection's object identities, so a pure content edit (a respelling, a duration change) that changes no identifier still changes the version — required for correct incremental-layout cache invalidation (Chapter 7 §"Incremental Layout"). -
The time axis has real behavior (M5 follow-up). Previously the
TimeAxisModelcarried bareVec<SpringSlotId>andproject/affected_slotsignored their arguments (returning the first slot / all slots) — inert payload. Each axis now holds orderedSlotPlacement { time, slot }entries:project(time)returns the slot covering a time (the greatest placement at or before it),affected_slots(range)returns the slots in a half-open time range, andslots()lists them in time order. The spacing stage (to_constrained) populates each region's axis from its resolved spring slots (TimeAxisModel::with_placements), and the populated axis is carried onConstrainedLayoutRegion, so the axis is a real, consumed artifact rather than an empty placeholder. (The slot times are still the prototype's wall-clock spacing columns; mapping a metric region's measure/beat grid to musical times is the next layer, but the axis machinery now genuinely consumes whatever times the spacing assigns.)
Pass 11 candidates (ambiguities for the spec, not resolved in code)
-
Agent E's stated dependency set vs. the edit-barrier types. The QUICKSTART says Agent E "depends on A and B," but assigns it the edit-barrier types, which reference Agent C's
OperationKindTag, and the spec'sEditBarrieradditionally referencesObjectKindandExtensionId(Chapter 8, the bundle's chapter). The dependency note, the type assignment, and the type's chapter home are in tension; the spec should either bless a layout→ops dependency for the discriminator type or relocate the edit-barrier types. -
Provenance / layout-object id derivation is unspecified. Chapter 7 declares
LayoutObjectId(pub u128)and requires stability across relayouts but specifies neither the derivation, whether it is domain-separated (Appendix D §"Domain-Separated Preimages" would suggest a dedicatedMUSC*tag), how a multiply-manifested object (a staff in two regions) is identified — v0 keys it on(source, region)— nor how synthesized objects are keyed — v0 uses(source, synthesis_kind, stable_semantic_instance_key). The spec should pin the derivation, manifestation-context key, and synthesized-object key, and register a layout domain tag if separation is required.