The chosen Phase-3 attack, run as two parallel waves. 829 workspace tests pass; clippy -D warnings, fmt, and rustdoc clean; all three spec documents build with zero undefined references. Casting-off (epiphany-engrave/src/casting.rs, wired into the Engraver): - Greedy first-fit system breaking per region at measure-start barline columns; a measure that would overflow the content width starts a new system. Hard SystemBreakAt/PageBreakAt always honoured; soft breaks honoured unless pathological (skipped with the spec's warning + IrOverride-recorded decision). - Vertical stacking from real content extents with the inter-system gap read from the vertical band model; page overflow starts the next page. World frame: pages stacked vertically, coordinates baked into glyphs/strokes, so the SVG renderer, hit-testing, and the GUI viewport are unchanged. - Real ResolvedPage/ResolvedSystem trees (1-based page numbers, content bounding boxes, staves from staff-line segments, measures from barline columns); every chosen break appends an EngravingDecision with MUSCLOID EngravedBreak provenance, UserOverride-attributed via the new ConstrainedLayoutIR break_origins sidecar; staff lines split per system with synthesized continuation provenance. - Break-constraint evaluation flips: satisfied iff the layout breaks at the slot. The two single-system tests invert deliberately (a hard break is now honoured; a user break is honoured and attributed instead of warned). Geometric constraints evaluate in the pre-casting spaced frame (documented). - Page geometry is engraver-side PageGeometry (A4 portrait at an 8 mm staff: page 105 x 148.5 staff spaces, margins 7.5, content 90 x 133.5; arithmetic documented) — Canvas.layout_defaults has no graph home and is a schema-major addition (P12-I7). ENGRAVER_VERSION = 2. Goldens regenerated: ten_measure_single_staff engraves as 2 systems (viewBox 84x20.6, was 103x11); valid_score_rich as 3 systems; stub goldens byte-identical. K1 schema-fill (Operation Catalog 0.4.0 -> 0.5.0, ratified first; wire discriminants strictly appended): - CreateStaff (24 / tag InsertStaff 24): set-union mint of a global Staff; CreateStaffInstance now preconditions that its referenced staff is live. - SetTimeSignature (25): value-carrying meter-change LWW keyed by (region, resolved position); the carried TimeSignature mints set-union; StructuralFieldCollision on meter_sequence. - SetTempoSegment (26): LWW keyed by (scope, resolved start) over the score or region tempo map; a write that would malform the map refuses with the appended PreconditionFailureReason 11 (TempoMapMalformed). - SetStaffLayout (27): LWW advisory over the staff instance's three inline layout fields. - Create score/canvas remain deliberately unavailable slots: the root and canvas are inline singletons with no addressable object model (P12-K8), not force-designed. Value-restoring undo (P11-C8 narrowed; catalog §UndoTransaction rewritten and per-primitive undo notes updated): - Canonical-order write chains (base-seeded) across all eleven LWW families. StrictInverse restores each written key to its chain-predecessor value iff the transaction's write is still the key's last writer, else refuses the whole undo with a TransactionConflict naming the superseder; BestEffort restores the still-last keys. Clean compensations are Applied; only minted- object tombstone repairs ride AppliedWithRepair (no new repair vocabulary). Mixed mint+overwrite transactions compose; strand guards refuse tombstoning mints still referenced by live non-members. - Undo-of-undo pinned and tested: restorations are chain writes, so undoing the undo's transaction restores the undone value, and a second undo of the same transaction conflicts (absence-restores repeat idempotently — documented asymmetry, P12-K11). - Permutation invariance pinned across five delivery orders; the convergence generators gain the new ops and a tx-then-undo flow. - Still deferred in normative text: delete resurrection (needs a system-derived tag outside the ratified closed set), Transpose inversion (P12-K2), Cascade dependent closure. Trackers: Binary Format companion 0.1.0 -> 0.2.0 (appended wire/tag tables, PreconditionFailureReason 11, payload layouts, history row — a schema-minor evolution under its own rules); nine new Pass-12 rows (C5, K8-K11, I7-I10); core-spec OperationKind listing gains the four kinds; revision-history rows in core spec and companion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd |
||
|---|---|---|
| .. | ||
| src | ||
| tests | ||
| Cargo.toml | ||
| DECISIONS.md | ||
| README.md | ||
README.md
epiphany-core
The Epiphany score graph: the in-memory representation of all musical
content in a score, implementing the normative requirements of Chapters 2–5
of the core specification (spec/core_spec.pdf). This is Agent B's crate per
spec/QUICKSTART.md — the largest scope — building only on Agent A's
epiphany-determinism.
The graph is the canonical truth about the music; layout, serialization, and editing operations are downstream projections and consumers of it. — Chapter 5, Design Principles
What's here
| Area | Items | Spec |
|---|---|---|
| Identifiers | ReplicaId (+ SYSTEM_DERIVED), OperationId, the typed 128-bit family (EventId, PitchId, VoiceId, …), TypedObjectId, IdentityContext, derive_system_id |
Ch. 5 §"Identifiers"; Ch. 6 §"Operation Identity" |
| Time | RationalTime (inline-or-promoted), MusicalPosition/MusicalDuration (typed algebra), WallClockTime/WallClockDuration, TimeAnchor/AnchorOffset, EventPosition/EventDuration/ConcreteDuration, TimeSignature/BeatGroup, NotatedComponent/NoteValue |
Ch. 3 |
| Tempo | TempoMap, Tempo, TempoSegment, TempoShape with closed-form musical_to_wallclock/wallclock_to_musical over constant/linear/exponential segments (curve deferred → TempoError) |
Ch. 3 §"Tempo Map" |
| Pitch | Pitch, ScalePosition, IdentifiedPitch, PitchSpelling, the spelling-attachment subsystem, ReferencePitch, spell (single-pitch simplest spelling), all three equivalences (scale_position_equivalent, enharmonic_equivalent, sounding_equivalent) |
Ch. 2; Ch. 4 registry ids |
| Pre-passes | derive_annotations: the real spelling pre-pass (Temperley line-of-fifths) + notational-decomposition pre-pass (metric greedy-aligned splitting) as canonical derived annotations (not stored), the eligibility TaxonomyReport, and resolve_spelling (authored-override precedence) |
Ch. 2 §"Spelling Pre-Pass"; Ch. 3 §"Notational Decomposition" |
| Events | the Event taxonomy (7 variants) and the slotmap-backed EventArena |
Ch. 5 §"The Event Arena" |
| Graph | Canvas, Region, Staff vs StaffInstance, Voice/VoiceOrigin, Measure, BarlineAlignmentGroup, aleatoric EventOrderingDAG (acyclic by construction), the full cross-cutting registry, the full top-level Score |
Ch. 5 |
| Indexes | ScoreIndexes: the four mandatory indexes (event-time, cross-cutting-reference, measure, spelling-attachment) |
Ch. 5 §"Indexes" |
| Invariants | check_invariants over all 19 enumerated graph invariants, with a typed InvariantViolation witness per check |
Ch. 5 §"Graph Invariants" |
| Generators | generators::valid_score/valid_score_rich (positive), violating_score (negative, per invariant), shrink (witness minimizer) |
QUICKSTART, Agent B hand-off |
The identity discipline this crate enforces
- Replica + counter, big-endian canonical bytes. Every typed identifier is
(replica << 64) | counter; its canonical 16-byte form isto_be_bytes()(8-byte replica, 8-byte counter) and the numericOrdis the Appendix-D lexicographic byte order. Identity is exact, never tolerant. - A reserved system namespace.
ReplicaId::SYSTEM_DERIVEDis rejected byReplicaId::generate/from_entropy; system-derived ids (derive_system_id,derive_promoted_voice_id) live only in that namespace, with counterstrunc64(BLAKE3(domain_tag || canonical_inputs))viaepiphany-determinism. - Cross-kind confusion is a compile error. Each object kind has its own
newtype;
TypedObjectIdtags them apart with a discriminant that is part of canonical content.
Graph invariants as property tests
The Chapter 5 invariants are property tests in CI, not runtime assertions in
release builds (QUICKSTART). check_invariants returns every violation with a
small witness. For each invariant generators provides:
- a positive generator (
valid_score/arbitrary_graph_corpus) whose output passes every check, and - a negative generator (
violating_score) plus a shrinker (shrink) that minimizes a violating graph to a small witness while retaining only the structure the violation needs.
Generation is deterministic (a vendored SplitMix64), so a failing case reproduces from its seed — no platform entropy enters generation (Appendix D §"Randomness").
Implementation decisions
Per QUICKSTART "Decisions you'll need to make" (full rationale in DECISIONS.md):
- Replica entropy:
getrandom(decision 1).ReplicaId::generatere-draws until the value is not the reserved namespace. - Event-arena storage:
slotmap(decision 2) plus a hash index for the requiredO(1)EventIdlookup and generation-checked stale handles. - Sync only (decision 4): no async anywhere.
- Current stable Rust (decision 5); MSRV pinned at the workspace's 1.77.
RationalTime's promoted arm usesnum-rational'sBigRational, the spec's reference design (Ch. 3 §"Recommended Implementation").unsafeis forbidden crate-wide (#![forbid(unsafe_code)]).
Hand-off criteria (QUICKSTART, Agent B)
- Every invariant has both a generator and a shrinker
(
generators::{valid_score, violating_score, shrink}; one per invariant, property-tested), plus targeted tests for the cross-cutting/anchor/tie sub-rules. - The arbitrary-graph corpus runs clean
(
generators::tests::positive_corpus_runs_clean, 500 graphs), and a breadth corpus (valid_score_rich: concurrent metric/proportional/ aleatoric regions, measures, triplet, tie, spanner, marker, chord symbol, decomposition, tombstones) runs clean over 200 seeds. cargo test -p epiphany-coreclean (69 unit + 5 integration).cargo clippy --all-targets -- -D warningsclean;cargo docclean underRUSTDOCFLAGS="-D warnings".
Depth of the invariant checks
The checks are not surface-level. In particular: invariant 3 computes per-clock
event intervals and detects both disorder and overlap; invariant 7 resolves
region extents to absolute wall-clock coordinates (wall-clock leaves, plus
event/region/measure-start anchors — an event anchor is its region origin plus
its region-relative position) and only skips pairs that can't be placed without
the deferred tempo map; invariant 9 sweeps every reachable anchor (region
extents, meter changes, measure starts, clef/key changes, user breaks, spanners,
spelling ranges); invariant 10 resolves all graph references — cross-cutting
anchor targets, annotation layers, tuplet parents, graphic objects, and
event-internal references (indeterminate alternatives, trajectory event-pitches,
graphic-event objects, cue sources); invariant 11 covers every id kind, plus
tombstone/live collisions, SYSTEM_DERIVED misuse (including the score's own
identity context), and arena index/well-formedness integrity (catching
post-get_mut corruption); invariant 17 validates explicit and implicit
(pitch-id-ascending) tie pairings, per-class adjacency, and the cross-voice
position rule; invariant 18 recomputes the deterministic promoted-voice
derivation. Enharmonic equivalence is a sounding notion (octave matters:
C4 ≠ C5). Empty pitched events are rejected at the arena boundary and re-checked;
IdentityContext::try_new rejects the reserved replica and counters use
checked_add so a counter is never silently reused.
Known bounded limitations (deferred dependencies)
- Tempo conversion integrates the piecewise map in closed form for
Constant/Linear/Exponentialsegments (Chapter 3 §"Conversion"); onlyTempoShape::Curveis deferred to the open numerical algorithm (TempoError::CurveIntegrationUnsupported). Segment boundaries that cannot be placed without the score graph, and malformed segment sequences, return aTempoError, never a wrong answer. The inverse round-trips ordinary rhythms via a documented continued-fraction approximation (DECISIONS P11-7). - Region time-overlap (invariant 7) resolves extents to absolute wall-clock,
now including musical event positions placed through the region's effective
tempo map (its
local_tempo_map, else the score map). Extents that still cannot be placed (no tempo defined, or a deferred curve) are skipped rather than rejected. Sound (no false positives), incomplete (DECISIONS P11-4). - System-promoted voice derivation (invariant 18) retains the winning and
losing operation ids on
VoiceOrigin::SystemPromoted; the checker recomputes the exact four-input derivation used byepiphany-ops. - The Chapter 4 tuning catalog —
PitchSpace/TuningSystem/AccidentalRegistrydefinitions, the built-in catalog, the hierarchical resolver, and the position→frequency resolution function — is not an Agent B deliverable (the QUICKSTART lists those as referenced-by-id). This crate models the identifiers and the score-levelScoreTuningContext;Pitch::sounding_equivalenttakes a caller-supplied frequency resolver. See DECISIONS P11-5.
Scope boundaries
The full Chapter 5 top-level Score shape is modeled (metadata, instruments,
staff groups, parts, tuning context, tempo map, analysis layers, views) along
with the complete CrossCuttingRegistry (slurs, ties, beams, tuplets, spanners,
markers, repeats, analytical annotations, comments, graphic gestures, lyrics,
chord symbols). The reference- and identity-bearing fields are modeled in depth;
deeper bodies (tuning resolution, tempo-curve integration, part layout, view
recipes, glyph/engraving detail) are Chapters 3/4/7 and later companions.
Engraving-display detail (StemConfiguration, ClefChange,
KeySignatureChange, articulations, dynamics, line styles, spanner/marker
visual kinds) is introduced informally here and fully defined in Chapter 7 —
it belongs to Agent E (epiphany-layout-ir), and this crate carries minimal,
clearly-marked placeholders for it. Operation envelopes, stamps (HLC), causal
contexts, the canonical reduction, tombstone tracking, and conflict records
are Chapter 6 / Agent C (epiphany-ops); epiphany-core defines only the
OperationId they hang off and the tombstone-aware invariants.
Ambiguities discovered while building are not resolved in code — they are
batched as Pass 11 candidates in DECISIONS.md (QUICKSTART, Process notes).