The make-room enabler: a ModifyEvent that moves a metric event's span (different Musical position or duration) is now applied to the graph and the owning voice re-sorted by ascending position (id-tiebroken, the order an insert maintains), so invariant 3 (VoiceEventsSortedNonOverlap) holds. The deferral documented in DECISIONS.md is lifted. modify_event computes one sanction — the move is a valid metric move (PlacementVerdict::Moved) and the replacement is well-formed — and uses it to gate both the graph mutation (graph_replace_event) and the voice_occupancy update, so the canonical index and the graph never diverge. The verdict is read from voice_occupancy, the graph-independent placement index, so reduce() and reduce_onto() agree on it; a move with a non-positive span or one that would overlap a live sibling is refused as a clean NoOp(EventDurationInvalid) rather than skipped silently. A non-metric move stays deferred, and a malformed (empty) pitched replacement is neither materialized nor allowed to move occupancy. Tests: trim frees the voice slot (a later insert fits); a move onto a sibling is refused; a trim materializes in the graph (reduce_onto, invariants hold); a non-metric event is not rewritten onto the musical grid; a malformed move does not free the slot. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NAtEiJtt9yKVV1zjKYmZhS |
||
|---|---|---|
| .. | ||
| epiphany-bundle | ||
| epiphany-core | ||
| epiphany-determinism | ||
| epiphany-editor-core | ||
| epiphany-editor-gui | ||
| epiphany-engrave | ||
| epiphany-layout-ir | ||
| epiphany-ops | ||
| epiphany-render-svg | ||
| epiphany-testkit | ||