epiphany/spec/core_spec.tex

16904 lines
683 KiB
TeX

% !TEX program = xelatex
%
% Epiphany --- Core Specification
% A specification document for the Epiphany music notation platform.
% Compile with XeLaTeX.
\documentclass[11pt,letterpaper]{report}
% ---------------------------------------------------------------------------
% Packages
% ---------------------------------------------------------------------------
\usepackage{fontspec}
\usepackage{geometry}
\geometry{
letterpaper,
top=1.05in,
bottom=1.05in,
left=1.15in,
right=1.15in,
headheight=15pt
}
\usepackage[english]{babel}
\usepackage{microtype}
\usepackage{parskip}
\usepackage{xcolor}
\usepackage{hyperref}
\usepackage{enumitem}
\usepackage{titlesec}
\usepackage{fancyhdr}
\usepackage{booktabs}
\usepackage{array}
\usepackage{longtable}
\usepackage{listings}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{graphicx}
\usepackage{caption}
\usepackage{subcaption}
\usepackage{tikz}
\usepackage{float}
\usepackage{tcolorbox}
\tcbuselibrary{breakable, skins}
% ---------------------------------------------------------------------------
% Color palette: refined classical-modern
% - Deep teal as the primary structural color
% - Antique gold as the accent and titling color
% - Warm parchment-tinted neutrals
% ---------------------------------------------------------------------------
\definecolor{epiphanyteal}{HTML}{1A4044} % deep teal --- primary
\definecolor{epiphanygold}{HTML}{8E6E2E} % antique gold --- accent
\definecolor{epiphanyink}{HTML}{1F1B16} % warm near-black for body text
\definecolor{epiphanyslate}{HTML}{6B6660} % warm slate gray
\definecolor{epiphanycream}{HTML}{F8F4ED} % parchment cream
\definecolor{epiphanymist}{HTML}{ECE8E0} % slightly deeper parchment
\definecolor{epiphanycode}{HTML}{2A2520} % warm dark for code
\definecolor{epiphanycrimson}{HTML}{7A2424} % deep crimson for warnings
\hypersetup{
colorlinks=true,
linkcolor=epiphanyteal,
citecolor=epiphanyteal,
urlcolor=epiphanygold,
pdftitle={Epiphany --- Core Specification},
pdfauthor={The Epiphany Project},
pdfsubject={Core specification for the Epiphany music notation platform},
pdfkeywords={music notation, engraving, FOSS, Rust, microtonality, CRDT},
bookmarksnumbered=true,
bookmarksopen=true
}
% ---------------------------------------------------------------------------
% Typography: classical serif with refined sans for chapter titles
% - TeX Gyre Pagella: Palatino-derived; warm, classical, highly legible
% - TeX Gyre Heros: clean grotesque for sans elements
% - TeX Gyre Cursor: monospace for code
% ---------------------------------------------------------------------------
\setmainfont{TeX Gyre Pagella}[
Numbers={OldStyle, Proportional},
Ligatures={TeX, Common}
]
\setsansfont{TeX Gyre Heros}[
Scale=0.94,
Ligatures={TeX, Common}
]
\setmonofont{TeX Gyre Cursor}[
Scale=0.88,
Ligatures={TeX}
]
% Lining figures for tables (oldstyle for body, lining for technical)
\newcommand{\tablenums}[1]{{\addfontfeatures{Numbers={Lining,Tabular}}#1}}
% A display-weight version of Pagella for the title page
\newfontfamily\titlefont{TeX Gyre Pagella}[
Numbers={OldStyle},
Ligatures={TeX, Common}
]
% Small-caps helpers
\newcommand{\sectionsc}[1]{{\addfontfeatures{Letters=SmallCaps}#1}}
% ---------------------------------------------------------------------------
% Section styling
% ---------------------------------------------------------------------------
\titleformat{\chapter}[display]
{\normalfont\filright}
{\raggedright\color{epiphanygold}\fontsize{14pt}{16pt}\selectfont
\scshape Chapter\ \thechapter}
{16pt}
{\raggedright\color{epiphanyteal}\fontsize{32pt}{36pt}\selectfont\bfseries}
[\vspace{4pt}{\color{epiphanygold}\rule{2in}{0.6pt}}]
\titlespacing*{\chapter}{0pt}{-20pt}{30pt}
\titleformat{\section}
{\normalfont\Large\bfseries\color{epiphanyteal}}
{\color{epiphanygold}\thesection}{1em}{}
\titleformat{\subsection}
{\normalfont\large\bfseries\color{epiphanyteal}}
{\color{epiphanygold}\thesubsection}{1em}{}
\titleformat{\subsubsection}
{\normalfont\normalsize\bfseries\color{epiphanyink}}
{\thesubsubsection}{1em}{}
% ---------------------------------------------------------------------------
% Headers and footers
% ---------------------------------------------------------------------------
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0pt}
\fancyhead[L]{\small\scshape\color{epiphanyslate}Epiphany}
\fancyhead[R]{\small\itshape\color{epiphanyslate}\leftmark}
\fancyfoot[C]{\small\color{epiphanyslate}\thepage}
% Subtle ornamental rule under the page header
\renewcommand{\headrule}{
\color{epiphanygold!50}\hrule width\headwidth height 0.4pt
\vspace{1pt}
\color{epiphanygold!30}\hrule width\headwidth height 0.2pt
}
% ---------------------------------------------------------------------------
% Code listing style
% ---------------------------------------------------------------------------
\lstdefinelanguage{Rust}{
keywords={fn,let,mut,pub,struct,enum,impl,trait,for,in,if,else,match,return,
use,mod,crate,self,Self,as,where,move,async,await,const,static,
ref,type,unsafe,extern,dyn,box,break,continue,loop,while},
keywordstyle=\color{epiphanyteal}\bfseries,
ndkeywords={i8,i16,i32,i64,i128,u8,u16,u32,u64,u128,f32,f64,bool,char,str,
String,Vec,Option,Result,Box,Rc,Arc,HashMap,BTreeMap,
NonZeroU16,NonZeroU32,NonZeroU64,Duration,Timestamp},
ndkeywordstyle=\color{epiphanygold}\bfseries,
sensitive=true,
comment=[l]{//},
morecomment=[s]{/*}{*/},
commentstyle=\color{epiphanyslate}\itshape,
stringstyle=\color{epiphanycrimson},
morestring=[b]",
morestring=[b]'
}
\lstset{
basicstyle=\ttfamily\small\color{epiphanycode},
backgroundcolor=\color{epiphanycream},
frame=leftline,
rulecolor=\color{epiphanygold!60},
framesep=8pt,
framerule=1.5pt,
xleftmargin=10pt,
xrightmargin=4pt,
breaklines=true,
showstringspaces=false,
numberstyle=\tiny\color{epiphanyslate},
numbersep=10pt,
captionpos=b,
aboveskip=10pt,
belowskip=10pt,
language=Rust
}
% ---------------------------------------------------------------------------
% Custom environments
% ---------------------------------------------------------------------------
% Open question or unresolved design issue
\newtcolorbox{openquestion}[1][]{
enhanced, breakable,
colback=epiphanymist, colframe=epiphanycrimson,
fonttitle=\bfseries\color{white},
title={\scshape\hspace{2pt}Open Question},
coltitle=white,
colbacktitle=epiphanycrimson,
arc=1pt, boxrule=0pt,
leftrule=2pt,
left=10pt, right=10pt, top=8pt, bottom=8pt,
attach boxed title to top left={xshift=0pt, yshift=0pt},
boxed title style={
arc=0pt, sharp corners, boxrule=0pt,
left=6pt, right=8pt, top=2pt, bottom=2pt,
},
#1
}
% Design rationale
\newtcolorbox{rationale}[1][]{
enhanced, breakable,
colback=epiphanymist, colframe=epiphanyteal,
fonttitle=\bfseries\color{white},
title={\scshape\hspace{2pt}Rationale},
coltitle=white,
colbacktitle=epiphanyteal,
arc=1pt, boxrule=0pt,
leftrule=2pt,
left=10pt, right=10pt, top=8pt, bottom=8pt,
attach boxed title to top left={xshift=0pt, yshift=0pt},
boxed title style={
arc=0pt, sharp corners, boxrule=0pt,
left=6pt, right=8pt, top=2pt, bottom=2pt,
},
#1
}
% Normative requirement
%
% Numbered within chapter (this document has chapters) so that every
% requirement carries a unique, human-citable number. tcolorbox's own
% "auto counter, number within=..." keys are not used: their counter-step
% for \label purposes runs inside an internal \sbox ("phantom") whose local
% \@currentlabel assignment is discarded when that box closes, so a plain
% \label written in the box body (as every requirement's is, and per this
% pass's rules must remain) binds to the counter of whatever sectioning
% unit last really stepped -- reproducing the very bug this counter is
% meant to fix. Stepping a plain counter via a `code=` key runs directly in
% the environment's own group, so \@currentlabel sticks for the body's
% \label to see.
\newcounter{requirement}[chapter]
\renewcommand{\therequirement}{\thechapter.\arabic{requirement}}
\newtcolorbox{requirement}[1][]{
enhanced, breakable,
colback=white, colframe=epiphanygold,
fonttitle=\bfseries\color{white},
code={\refstepcounter{requirement}},
title={\scshape\hspace{2pt}Requirement~\therequirement},
coltitle=white,
colbacktitle=epiphanygold,
arc=1pt, boxrule=0pt,
leftrule=2pt,
left=10pt, right=10pt, top=8pt, bottom=8pt,
attach boxed title to top left={xshift=0pt, yshift=0pt},
boxed title style={
arc=0pt, sharp corners, boxrule=0pt,
left=6pt, right=8pt, top=2pt, bottom=2pt,
},
#1
}
% Non-goal: explicitly out of scope
\newtcolorbox{nongoal}[1][]{
enhanced, breakable,
colback=epiphanymist, colframe=epiphanyslate,
fonttitle=\bfseries\color{white},
title={\scshape\hspace{2pt}Non-Goal},
coltitle=white,
colbacktitle=epiphanyslate,
arc=1pt, boxrule=0pt,
leftrule=2pt,
left=10pt, right=10pt, top=8pt, bottom=8pt,
attach boxed title to top left={xshift=0pt, yshift=0pt},
boxed title style={
arc=0pt, sharp corners, boxrule=0pt,
left=6pt, right=8pt, top=2pt, bottom=2pt,
},
#1
}
% Normative-language helpers (RFC 2119 style). MUST NOT and SHOULD NOT
% are emitted as two adjacent textbf groups separated by a regular
% control space; this prevents line breaks between the words while
% still producing a standard ASCII space character in the PDF that
% pdftotext extracts cleanly. The \nobreak prevents the line-break
% the regular space would otherwise allow.
\newcommand{\MUST}{\textbf{MUST}}
\newcommand{\MUSTNOT}{\textbf{MUST}\nobreak\ \textbf{NOT}}
\newcommand{\SHOULD}{\textbf{SHOULD}}
\newcommand{\SHOULDNOT}{\textbf{SHOULD}\nobreak\ \textbf{NOT}}
\newcommand{\MAY}{\textbf{MAY}}
% List spacing
\setlist[itemize]{topsep=2pt, itemsep=3pt, parsep=0pt}
\setlist[enumerate]{topsep=2pt, itemsep=3pt, parsep=0pt}
\setlist[description]{topsep=2pt, itemsep=5pt, parsep=0pt}
% Body text color
\AtBeginDocument{\color{epiphanyink}}
% ---------------------------------------------------------------------------
% Document
% ---------------------------------------------------------------------------
\begin{document}
% ----- Title page -----
\begin{titlepage}
\thispagestyle{empty}
\begin{tikzpicture}[remember picture, overlay]
% Top ornamental rule
\draw[epiphanygold, line width=1.2pt]
([yshift=-0.85in, xshift=1.15in]current page.north west) --
([yshift=-0.85in, xshift=-1.15in]current page.north east);
\draw[epiphanygold, line width=0.4pt]
([yshift=-0.93in, xshift=1.15in]current page.north west) --
([yshift=-0.93in, xshift=-1.15in]current page.north east);
% Bottom ornamental rule
\draw[epiphanygold, line width=0.4pt]
([yshift=0.93in, xshift=1.15in]current page.south west) --
([yshift=0.93in, xshift=-1.15in]current page.south east);
\draw[epiphanygold, line width=1.2pt]
([yshift=0.85in, xshift=1.15in]current page.south west) --
([yshift=0.85in, xshift=-1.15in]current page.south east);
\end{tikzpicture}
\centering
\vspace*{0.7in}
% Small superscript framing
{\color{epiphanyslate}\scshape\fontsize{11pt}{13pt}\selectfont
A music notation platform\par}
\vspace{0.3in}
% The main title --- Epiphany
{\titlefont\fontsize{72pt}{76pt}\selectfont
\color{epiphanyteal}
Epiphany\par}
\vspace{0.12in}
% Subtle ornamental separator
\begin{tikzpicture}
\draw[epiphanygold, line width=0.5pt] (-1.3,0) -- (-0.3,0);
\node at (0,0) {\color{epiphanygold}\large$\diamond$};
\draw[epiphanygold, line width=0.5pt] (0.3,0) -- (1.3,0);
\end{tikzpicture}
\vspace{0.2in}
{\color{epiphanyink}\fontsize{18pt}{22pt}\selectfont
\itshape Core Specification\par}
\vspace{0.9in}
% Tagline
\begin{minipage}{0.78\textwidth}
\centering
\color{epiphanyink}
\fontsize{12pt}{17pt}\selectfont
A foundational specification for an open, Rust-native music
notation platform: pitch and time primitives, the score graph,
the layout intermediate representation, the file format, and
the constraint-solver interface.
\end{minipage}
\vspace{0.7in}
% Status banner
\begin{tikzpicture}
\node[
draw=epiphanygold,
line width=0.5pt,
fill=epiphanycream,
inner sep=10pt,
minimum width=3.5in,
] (status) {
\color{epiphanyteal}
\scshape\fontsize{10pt}{12pt}\selectfont
Working Draft \quad$\cdot$\quad \today
};
\end{tikzpicture}
\vspace{0.5in}
\begin{minipage}{0.72\textwidth}
\centering
\itshape\color{epiphanyslate}
\footnotesize
This specification defines the foundational layer of the
Epiphany platform: the data model, engraving engine, and file
format on which all higher-level features are built. The user
interface, audio engine, plugin runtime, and platform
integrations are layered above the core and are addressed in
separate specifications.
\end{minipage}
\vfill
\end{titlepage}
% ----- Front matter -----
\pagenumbering{roman}
\chapter*{Status of This Document}
\addcontentsline{toc}{chapter}{Status of This Document}
This draft is \textbf{structurally stabilized}. Substantive
review passes through the core type model, graph invariants,
concurrent reduction semantics, file-format atomicity, solver
conformance, and the determinism contract have been completed.
The architecture is no longer under active redesign; further
revisions are expected to be additive (companion specifications,
normative algorithm choices for the open canonical algorithms,
extension registry contents) rather than structural.
The remaining blockers to full conformance are companion
specifications and the canonical algorithms identified in
Appendix~\ref{app:determinism}
Section~\ref{sec:det:open}:
\begin{itemize}
\item Several companion specifications referenced throughout
the document have not yet been delivered:
Appendix~\ref{app:deferred} catalogs them and distinguishes
them from accidentally missing content.
\item Two algorithms affect canonical state and have not
yet received one of the dispositions in
Appendix~\ref{app:determinism}
Section~\ref{sec:det:open}: the tempo curve
integration algorithm and the
\texttt{wallclock\_to\_musical} root-finding algorithm.
Cross-implementation byte equality of canonical score state
\MUSTNOT{} be claimed for outputs derived from these
algorithms until they are normatively specified or
profile-declared (Appendix~\ref{app:determinism}
Section~\ref{sec:det:layers}). The spelling pre-pass and the
notational-decomposition algorithm received the
profile-declared-by-versioned-identifier disposition in
Pass~12
(Requirements~\ref{req:pitch:spelling-algorithm}
and~\ref{req:time:decomposition-algorithm}).
\item Extension registry catalogs are referenced by typed
identifier but not enumerated here; they are delivered as
separate, versioned registry documents.
\end{itemize}
What conformance can be established now:
\begin{itemize}
\item The core data model can be structurally implemented and
tested against the invariants in
Chapter~\ref{ch:graph} Section~\ref{sec:graph:invariants}.
\item The operation framework
(Chapter~\ref{ch:semops}) can be implemented end-to-end
against the canonical reduction algorithm.
\item The file format
(Chapter~\ref{ch:format}) can be implemented atomically and
crash-recoverably against the fixed prelude and superblock
protocol.
\item Solver implementations can be developed against the
interface contract (Chapter~\ref{ch:solver}); reference-suite
conformance becomes checkable when the Reference Suite
companion is delivered.
\end{itemize}
The normative keywords \MUST, \MUSTNOT, \SHOULD, \SHOULDNOT, and \MAY{} are
to be interpreted as described in RFC 2119 when they appear in all
capital letters.
\chapter*{Conventions}
\addcontentsline{toc}{chapter}{Conventions}
\begin{description}
\item[Code samples] Rust syntax is used for type signatures, struct
definitions, and algorithmic illustrations. Code samples are
illustrative unless explicitly labeled normative.
\item[Normative requirements] Statements containing \MUST, \MUSTNOT,
\SHOULD, \SHOULDNOT, or \MAY{} are normative. Conforming
implementations must respect them.
\item[Non-normative content] Discussion, rationale, examples, and
diagrams are non-normative unless explicitly stated.
\item[Forward references] The specification builds bottom-up. Later
chapters depend on earlier ones; forward references are minimized but
occasionally unavoidable and are marked when they occur.
\end{description}
\tableofcontents
\clearpage
\chapter*{Reading Guide}
\addcontentsline{toc}{chapter}{Reading Guide}
This document is long. The following tables summarize the most
load-bearing concepts and where they are defined. They are
non-normative; the chapters they reference are authoritative.
\section*{Score Graph Identity Hierarchy}
The score graph is organized as a containment hierarchy of
identified objects. Each level introduces a distinct concept of
identity.
\begin{longtable}{p{2.8cm} p{2.6cm} p{8.2cm}}
\toprule
\textbf{Level} & \textbf{Identifier} & \textbf{Role} \\
\midrule
\endhead
Score & --- & Document root. Carries metadata, instruments,
global staves, staff groups, tuning context, tempo map,
canvas. \\
Canvas & --- & Spatial root. Partitioned into regions. \\
Region & \texttt{RegionId} & Spatial-temporal container.
Declares time model (metric/proportional/aleatoric) and
content model (staff-based/free-graphic/hybrid). \\
Staff & \texttt{StaffId} & Global, abstract staff identity
persisting across regions. Declared once at score level. \\
Staff instance & \texttt{StaffInstanceId} & Region-local
manifestation of a staff. One \texttt{Staff} may have
multiple instances across regions. \\
Voice & \texttt{VoiceId} & Polyphonic line within a staff
instance. Carries \texttt{VoiceOrigin}: user-declared,
imported, or system-promoted. \\
Event & \texttt{EventId} & Rhythmic atom: pitched, unpitched,
rest, indeterminate, trajectory, graphic, cue. Lives in the
score's event arena. \\
Identified pitch & \texttt{PitchId} & Stable identity for a
pitch within an event. Enables spelling attachments,
respelling, stable tie pairing through chord reordering. \\
\bottomrule
\end{longtable}
\section*{Canonical versus Non-Canonical}
Many file-format and runtime objects exist in two forms.
Canonical objects define the document; non-canonical objects
accelerate access but never affect canonical state.
\begin{longtable}{p{4.4cm} p{4.0cm} p{5.2cm}}
\toprule
\textbf{Object} & \textbf{Status} & \textbf{Notes} \\
\midrule
\endhead
Operation envelope set & Canonical &
The replicated grow-only CRDT defining the document
(Chapter~\ref{ch:semops}). \\
Operation envelope block & Canonical (storage) &
Soft 1\,MiB blocks on disk
(Chapter~\ref{ch:format}). \\
Operation index & Non-canonical &
Acceleration of operation-id lookup. Rebuildable. \\
Canonical-base snapshot & Canonical &
The manifest's \texttt{canonical\_base}: at most one active,
with declared causal frontier and reduction algorithm
version. Required for pruned documents. \\
Acceleration snapshot & Non-canonical &
Listed in \texttt{acceleration\_snapshots}. Cache only.
Discardable. \\
Manifest & Canonical (index) &
Table of roots; itself a chunk; referenced from the active
superblock. \\
Materialized score graph & Derivative &
Deterministic reduction of the operation set under the
active reduction algorithm. \\
Layout cache, glyph cache, render artifacts &
Non-canonical & Always discardable
(Section~\ref{sec:semops:caches}). \\
Conflict registry & Canonical &
Stable, addressable, user-visible records of operations
that could not apply cleanly. \\
\bottomrule
\end{longtable}
\section*{Operation Lifecycle}
Every operation traverses four phases between user intent and
its visible effect.
\begin{longtable}{p{2.2cm} p{12.5cm}}
\toprule
\textbf{Phase} & \textbf{Description} \\
\midrule
\endhead
Prepare & Local UI command validates enough to construct a
well-formed envelope. Advisory only; failure here means the
UI rejects the user's action. Does not bind canonical
semantics. \\
Commit & The envelope is added to the replicated operation
set. After commit, the operation is part of canonical state
and \MUSTNOT{} be discarded outside the pruning protocol. \\
Reduce & During materialization, the operation is processed
by the canonical reduction algorithm against working state.
Deterministic: every replica with the same operation set
produces the same output. \\
Report & The reduction produces a deterministic
\texttt{OperationEffect}: \texttt{Applied},
\texttt{AppliedWithRepair}, \texttt{Conflicted},
\texttt{TombstonedTarget}, or \texttt{NoOp}. Effects are
visible graph facts. \\
\bottomrule
\end{longtable}
\section*{Determinism Layers}
The specification distinguishes five layers of determinism.
Conflating them is the most common source of impossible
requirements.
\begin{longtable}{p{3.8cm} p{10.0cm}}
\toprule
\textbf{Layer} & \textbf{Contract} \\
\midrule
\endhead
Canonical score determinism &
Identical operation sets produce identical materialized
score states across all conforming implementations, subject
to all canonical algorithms having received the dispositions
in Appendix~\ref{app:determinism}. \\
Canonical serialization &
Same canonical state $\to$ same chunk hashes, same text
projection, same canonical iteration order. \\
Layout determinism &
Within one solver implementation at a fixed version:
byte-equal output for identical input. Across
implementations: reference-suite thresholds, not byte equal. \\
Conformance determinism &
Reference-suite-based. Cross-implementation byte equality
is not required for layout. \\
Non-canonical caches &
May vary freely across runs, platforms, implementations.
\MUSTNOT{} affect canonical state. \\
\bottomrule
\end{longtable}
\section*{Solver Tiers}
Solver implementations declare a conformance tier. Tiers are
orthogonal to file-format profiles
(Chapter~\ref{ch:format}).
\begin{longtable}{p{3.0cm} p{11.0cm}}
\toprule
\textbf{Tier} & \textbf{Obligations} \\
\midrule
\endhead
Minimal &
Hard-constraint validity, within-implementation determinism,
well-formed \texttt{SolveReport}, CMN + metric regions,
Minimal reference-suite subset. Aesthetic quality may be
mediocre. \\
Standard &
Minimal $+$ Standard-tier thresholds on the full reference
suite, Measure-local invalidation under incremental solving,
all Standard constraint families. Professional engraving
quality for common-practice notation. \\
Advanced &
Standard $+$ proportional and aleatoric regions, microtonal
accidentals, extension-contributed constraints, System-local
invalidation under incremental solving. \\
\bottomrule
\end{longtable}
\section*{Chapter Map}
\begin{longtable}{p{1.6cm} p{12.4cm}}
\toprule
\textbf{Ch.} & \textbf{Content} \\
\midrule
\endhead
\ref{ch:intro} & Scope, design principles, glossary of key
terms used throughout. \\
\ref{ch:pitch} & Pitch as two intrinsic layers (scale
position and acoustic), spelling subsystem with stacked
accidentals, analytical layers. \\
\ref{ch:time} & Rational time, time anchors with
\texttt{AnchorOffset}, three region time models, tempo map
with explicit segment endpoints, sounding duration vs.
notational decomposition. \\
\ref{ch:tuning} & Pitch spaces (including CMN and JI), tuning
systems, score tuning context, accidental registries, glyph
references. \\
\ref{ch:graph} & Score graph, event arena, cross-cutting
structures, regions, staff identity (Staff vs.
StaffInstance), polymeter via per-staff metric grids, voice
origin, graph invariants. \\
\ref{ch:semops} & Operation framework, canonical reduction,
DVV causal contexts, operation effects, tombstones, conflict
records, transactions, re-anchoring, undo, LWW discipline. \\
\ref{ch:layout-ir} & The four-stage layout intermediate
representation; provenance and incremental invalidation. \\
\ref{ch:format} & Fixed prelude, two superblock slots, atomic
commit, content-addressed BLAKE3 chunks,
operation-envelope blocks, snapshots as canonical bases,
edit barriers, text projection. \\
\ref{ch:solver} & Solver interface, SolveReport, deterministic
budgets, NormalizedMetric, three conformance tiers,
reference-suite conformance, six invalidation scopes. \\
\ref{ch:perf} & Performance requirements: frame budgets, cold
open, memory, audio-thread discipline, measurement. \\
\ref{ch:extension} & Consolidated catalog of extension points
by chapter of origin. \\
\ref{app:deferred} & Intentionally deferred types: companion
specifications, externally provided components, open
canonical algorithms. \\
\ref{app:glossary} & Glossary of defined terms with
chapter cross-references. \\
\ref{app:refs} & Bibliography and references. \\
\ref{app:determinism} & Determinism contract: the legal code
for reproducibility. \\
\ref{app:bytes} & Canonical byte-layout reference: every ratified
discriminant, derivation preimage, and primitive encoding in one
place, for the Binary Format companion to import. \\
\ref{app:history} & Revision history. \\
\bottomrule
\end{longtable}
% ----- Main matter -----
\clearpage
\pagenumbering{arabic}
% ===========================================================================
\chapter{Introduction and Scope}
\label{ch:intro}
\section{Purpose}
This document specifies the core layer of \textit{Epiphany}, a music
notation platform. The core comprises the abstract musical data
model, the engraving engine that transforms that data into a typeset
layout, and the on-disk file format that serializes both. Everything
else built atop the platform---editing UI, audio engine, plugin
runtime, collaboration, export pipelines---depends on this core and
must conform to its interfaces.
The name \textit{Epiphany} evokes both the original sense of the
word---a manifestation made visible---and the experience of music
itself, which strikes the listener and the creator alike. Music
notation is, in a literal sense, the act of making the inaudible
visible: a body of work this central to that task deserves a name
of corresponding weight.
\section{Scope}
\textbf{In scope:}
\begin{itemize}
\item Pitch, time, and duration primitives.
\item The score graph: the in-memory representation of musical content.
\item The layout intermediate representation produced by the engraving
engine.
\item The file format: the canonical on-disk projection of the score
graph.
\item The constraint-solver interface consumed by the engraving engine.
\item Extension points for non-Common-Music-Notation systems, including
microtonal pitch spaces and graphic-notation regions.
\end{itemize}
\textbf{Out of scope for this document:}
\begin{nongoal}
The following are explicitly out of scope for this core specification.
Each is addressed by a separate specification layered atop the core.
\begin{itemize}
\item User interface, input handling, and editing operations.
\item Audio synthesis, playback, and plugin (VST3/AU/CLAP/LV2) hosting.
\item The plugin runtime (WebAssembly capability-based extensions).
\item Real-time collaboration transport and federation protocols.
\item Import and export pipelines for foreign formats (MusicXML, MEI,
LilyPond, MIDI, etc.), although the core defines the data
sufficient to make lossless interchange feasible.
\item Machine-learning components (transcription, OMR, engraving
assistance).
\item Platform-specific integrations (file pickers, sharing, accessibility
APIs).
\end{itemize}
\end{nongoal}
\section{Design Goals}
\begin{description}
\item[Correctness over convenience.] The data model must represent
music accurately, even when this is harder than a convenient
approximation. MIDI-style pitch numbers, for example, are inadequate
and rejected; see Chapter~\ref{ch:pitch}.
\item[Extensibility without bolt-ons.] Microtonality, graphic notation,
historical notations, and future notational systems must be
expressible within the core data model, not as escape hatches.
\item[Determinism.] Given identical input, the engraving engine must
produce identical output. The on-disk format must serialize
deterministically.
\item[Incrementality.] The data model and layout engine must support
fine-grained incremental edits without full recomputation.
\item[Reducibility.] All editing operations on the score graph
must have well-defined reduction rules under canonical
deterministic reduction (Chapter~\ref{ch:semops}), enabling
collaboration and version control as natural consequences of
the data model rather than bolt-on features.
\item[Performance.] Layout and rendering of a 100-page orchestral score
must remain interactive on commodity hardware. See
Chapter~\ref{ch:perf} for budgets.
\item[Privacy.] The core must operate entirely offline. No core
functionality may depend on network access.
\end{description}
\section{Document Structure}
The remainder of this document is organized bottom-up:
\begin{enumerate}
\item Chapter~\ref{ch:pitch} defines pitch: its layered structure, the
spelling subsystem, scale positions, and acoustic realization.
\item Chapter~\ref{ch:time} defines time and duration primitives.
\item Chapter~\ref{ch:tuning} defines tuning systems, pitch spaces, and
accidental registries.
\item Chapter~\ref{ch:graph} defines the score graph: how primitives
compose into staves, voices, regions, and the overall score
structure, including the canvas/region model that accommodates
non-CMN notations.
\item Chapter~\ref{ch:semops} defines the semantic operations on the
score graph: the canonical set of mutations and their reduction
semantics under concurrent edits.
\item Chapter~\ref{ch:layout-ir} defines the layout intermediate
representation: the contract between the score graph (input) and the
engraving engine (consumer).
\item Chapter~\ref{ch:format} defines the on-disk file format, including
serialization, chunking, the operation-envelope set and its
deterministic reduction, and schema evolution.
\item Chapter~\ref{ch:solver} defines the constraint-solver interface
consumed by the engraving engine.
\item Chapter~\ref{ch:perf} states performance requirements and
measurement methodology.
\item Chapter~\ref{ch:extension} describes the extension points by
which the core supports notation grammars beyond Common Music
Notation.
\end{enumerate}
% ===========================================================================
\chapter{Pitch}
\label{ch:pitch}
This chapter specifies the pitch primitive: the data type representing
a single sounding (or to-be-sounded) note's identity in the score graph.
Pitch is the most consequential primitive in the core because nearly every
higher-level operation---transposition, key analysis, accidental rendering,
playback frequency, interchange, voice-leading analysis---is defined in
terms of it. The definition is therefore deliberately verbose.
\section{Design Principles}
\label{sec:pitch:principles}
\begin{description}
\item[Separation of identity layers.] Pitch comprises three
independent semantic layers: scale position (analytical identity),
acoustic realization (sounding frequency), and spelling (notational
appearance). Each is a first-class concern and \MUST{} be
representable independently. Conflating them is a design error.
\item[Spelling is attachment, not field.] A pitch carries its scale
position and acoustic identity intrinsically. Spelling is attached
externally through a separate indexed subsystem (see
Section~\ref{sec:pitch:spelling}). A pitch may have zero, one, or
multiple spelling attachments.
\item[Grammar neutrality.] The pitch type \MUST{} support Common Music
Notation as a fast path while remaining capable of representing
pitches in arbitrary user-defined notation grammars. CMN is a
privileged default, not a hardcoded ceiling.
\item[Microtonality is not an extension.] Microtonal pitches are
representable using the same primitive as 12-tone equal-tempered
pitches. No separate type, no escape hatch.
\item[Unpitched events are not pitches.] Unpitched percussion and
similar events \MUSTNOT{} be represented using the pitch type.
See Section~\ref{sec:pitch:not-pitch}.
\end{description}
\section{The \texttt{Pitch} Type}
\label{sec:pitch:type}
A pitch is a record of two intrinsic layers:
\begin{lstlisting}[language=Rust]
/// A pitch's intrinsic identity. Spellings are attached externally;
/// see Section~\ref{sec:pitch:spelling}.
pub struct Pitch {
/// The analytical identity within a pitch space. Determines
/// transposition, key analysis, and Roman-numeral behavior.
pub scale_position: ScalePosition,
/// The acoustic identity: what frequency this pitch produces when
/// sounded, expressed relative to a tuning system.
pub acoustic: AcousticPitch,
}
\end{lstlisting}
The two layers are independent. A given scale position can be acoustically
realized in many ways depending on the active tuning system; a given
acoustic realization can correspond to many scale positions in different
analytical contexts. The pitch carries both; neither is derivable from
the other in general.
\begin{rationale}
Storing both layers, rather than deriving one from the other, costs
a small number of bytes per pitch and saves a large amount of complexity
downstream. Derivation requires the renderer, the analyzer, the
transcriber, and the importer to each agree on a derivation procedure;
storage makes the pitch self-describing.
\end{rationale}
\begin{lstlisting}[language=Rust]
/// A closed range of pitches, used for advisory declarations: an
/// instrument's playable compass and an indeterminate event's pitch hint.
/// Both bounds are full pitches.
pub struct PitchRange {
pub lowest: Pitch,
pub highest: Pitch,
}
\end{lstlisting}
A \texttt{PitchRange} is \emph{advisory}. Whether a given pitch lies within
it is decidable only when the pitch and both bounds share a pitch space that
defines an order (the common CMN case); cross-space or partially-ordered
comparisons are treated as ``not out of range'' --- a sound-but-incomplete
check, as with region time overlap. A range is well-formed when
\texttt{lowest} does not sort above \texttt{highest} in a common ordered
space. A solver \MAY{} flag an out-of-range pitch but \MUSTNOT{} treat the
range as a hard constraint. The type is defined with schema major~1, closing
the gap in which \texttt{IndeterminacyHints} and \texttt{Instrument} named a
\texttt{PitchRange} that no section defined.
\section{Scale Position}
\label{sec:pitch:scale-position}
A scale position locates the pitch within a \emph{pitch space}. A pitch
space is the analytical universe in which scale-degree and interval
relationships are defined.
\begin{lstlisting}[language=Rust]
pub struct ScalePosition {
/// The pitch space this position is defined within. References an
/// identifier in the built-in pitch-space catalog
/// (`sec:tuning:builtin`); a score selects from this catalog and
/// does not define its own entries.
pub space: PitchSpaceId,
/// The position within that space. Interpretation is space-defined.
pub position: PitchSpacePosition,
}
\end{lstlisting}
The \texttt{PitchSpacePosition} type is a tagged union supporting the
common cases with a fast representation, plus a registry-id escape hatch
for arbitrary grammars:
\begin{lstlisting}[language=Rust]
pub enum PitchSpacePosition {
/// Common Music Notation: a diatonic nominal plus chromatic
/// alteration plus octave. The fast path for tonal Western music.
Cmn {
nominal: CmnNominal, // C, D, E, F, G, A, B
alteration: i8, // chromatic steps of the enclosing space
octave: i8, // scientific pitch notation
},
/// N-tone integer position. Used for 12-tone serial music, EDO
/// systems, and any space where pitches are indexed as integers.
Integer {
space_size: u16, // e.g., 12, 19, 31, 53
index: i32, // absolute index; octave is implicit
},
/// Just-intonation lattice position. Used for JI pitch spaces.
/// Each component is the integer power of the corresponding prime
/// in the space's prime basis. The pitch space declares the basis
/// ordering; by convention prime 2 is first, so the leading
/// component carries octave information.
JiVector {
components: Vec<i32>,
},
/// A registered position whose interpretation is defined by the
/// pitch space's grammar plugin. Used for maqamat, gamelan,
/// historical modes, and other non-lattice, non-CMN grammars.
Registered(PositionRegistryId),
}
\end{lstlisting}
\begin{requirement}
\label{req:pitch:alteration-unit}
For a \texttt{PitchSpacePosition::Cmn} enclosed by a
\texttt{ScalePosition}, \texttt{alteration} \MUST{} be measured in steps of
that pitch space's chromatic layer. Its zero point for each nominal \MUST{}
be the space's \texttt{nominal\_to\_chromatic} mapping. A
\texttt{PitchSpaceModification::CmnChromatic} value \MUST{} use that same
unit in every pitch space whose accidental registry contains it. Thus one
step is a semitone in \texttt{cmn-12} and a quarter-tone in
\texttt{cmn-24}; a flat is respectively $-1$ and $-2$.
When a bare \texttt{PitchSpacePosition::Cmn} is embedded as
\texttt{ScoreTuningContext.reference.position}, the score's
\texttt{default\_pitch\_space} \MUST{} supply the chromatic layer and mapping
for this rule. Any other context embedding a bare CMN position \MUST{}
identify the pitch space that denominates it.
\end{requirement}
\begin{requirement}
\label{req:pitch:ji-vector-basis}
For \texttt{PitchSpacePosition::JiVector}:
\begin{itemize}
\item \texttt{components.len()} \MUST{} equal the number of primes
in the enclosing pitch space's declared prime basis.
\item The basis ordering is established by the pitch space; the
built-in JI pitch spaces (\texttt{ji-5limit},
\texttt{ji-7limit}, \texttt{ji-11limit}) order primes
ascending starting with 2.
\item The vector $[a_0, a_1, a_2, \ldots]$ denotes the ratio
$p_0^{a_0} \cdot p_1^{a_1} \cdot p_2^{a_2} \cdots$ where
$p_i$ is the $i$-th prime in the basis. With the conventional
basis $\{2, 3, 5, \ldots\}$, the first component holds
octave information.
\item Pitch spaces \MAY{} declare octave-reduced equivalence,
in which case the first component is normalized to a canonical
range. Without such a declaration, full register is preserved.
\end{itemize}
\end{requirement}
\begin{requirement}
\label{req:pitch:default-pitch-space}
Every score \MUST{} select at least one pitch space from the
built-in catalog (Section~\ref{sec:tuning:builtin}). The default
pitch space for CMN scores is \texttt{cmn-12}, which is
diatonic-over-chromatic in structure (seven diatonic nominals
A--G over twelve chromatic positions), anchored to the score's
reference pitch as resolved through the score tuning context
(Chapter~\ref{ch:tuning}). Scores \MAY{} select additional pitch
spaces from that catalog; pitches in different spaces cannot be
directly compared and operations between them \MUST{} go through an
explicit space-conversion mechanism.
\end{requirement}
\begin{rationale}
Score-local pitch-space definition --- a score introducing a pitch
space of its own, beyond selecting from the built-in catalog --- is
deferred to a later schema major. The Chapter~\ref{ch:tuning} type
surface (\texttt{PitchSpace}, \texttt{PositionStructure},
\texttt{IntervalAlgebra}, and their kin) has never had a consumer;
committing a registry for it to the wire now would freeze that
surface permanently under \texttt{req:binfmt:frozen-layout}, before
any implementation has exercised it. Selecting from the built-in
catalog is the only reading the data model has ever supported: neither
\texttt{Score} nor \texttt{ScoreTuningContext} carries a pitch-space
or tuning-system registry, and none is added here.
\end{rationale}
\subsection{Octave Convention}
\label{sec:pitch:octave}
For the \texttt{Cmn} variant, the octave field uses Scientific Pitch
Notation: middle C is C4. This convention is normative throughout the
specification.
\begin{requirement}
\label{req:pitch:scientific-pitch-octaves}
The \texttt{octave} field of \texttt{PitchSpacePosition::Cmn} \MUST{}
follow Scientific Pitch Notation. Middle C is C4. The lowest A on a
standard piano is A0. C-1 is one octave below the lowest C on a
standard piano.
\end{requirement}
\begin{rationale}
Scientific Pitch Notation is the dominant modern convention in
English-language music theory and notation software. MIDI octave
numbering, where middle C is sometimes C3 and sometimes C5 depending
on vendor, is rejected as a source of foot-guns. Helmholtz notation is
expressible at the display layer; the data model commits to Scientific.
\end{rationale}
\subsection{The \texttt{CmnNominal} Type}
\label{sec:pitch:nominal}
\begin{lstlisting}[language=Rust]
#[repr(u8)]
pub enum CmnNominal { C = 0, D = 1, E = 2, F = 3, G = 4, A = 5, B = 6 }
\end{lstlisting}
The discriminants are normative: they define the diatonic step ordering
used by transposition algorithms.
\subsection{Transposition and the \texttt{Interval} Type}
\label{sec:pitch:interval}
Transposition acts on \emph{scale position}, not on acoustic realization.
It moves a pitch's analytical identity through the pitch space and leaves
the tuning context that sounds it untouched. A transposition therefore
requires no tuning catalog (Chapter~\ref{ch:tuning}) --- tuning decides what
frequency a scale position sounds at, not how to add an interval to one. It
requires an interval:
\begin{lstlisting}[language=Rust]
/// A signed interval: the transposition primitive. Defined here and
/// referenced by Instrument.transposition (Chapter 5), which is the
/// written-versus-sounding interval of a transposing instrument
/// (a B-flat clarinet is -1 diatonic, -2 chromatic).
pub struct TranspositionInterval {
/// Signed diatonic steps: how far the nominal moves along C..B.
/// A perfect fifth up is +4 (C -> G); an octave up is +7.
pub diatonic_steps: i32,
/// Signed steps in the enclosing pitch space's chromatic layer.
/// A perfect fifth in cmn-12 is +7 and an octave is +12.
/// The corresponding values are space-relative.
pub chromatic_steps: i32,
}
\end{lstlisting}
Both components are load-bearing. The diatonic component fixes the
\emph{spelling} --- which nominal, hence which staff line the notehead
occupies --- and the chromatic component fixes the \emph{sound}. Neither
determines the other: an augmented second $(+1, +3)$ and a minor third
$(+2, +3)$ sound alike and are written differently, while a diminished
sixth $(+5, +7)$ and a perfect fifth $(+4, +7)$ are written a step apart
and sound the same. A scalar chromatic-step count cannot express the
difference, and an implementation carrying only one component is not
transposing but altering.
\begin{requirement}
\label{req:pitch:transposition}
Let $n$ be the \texttt{nominal}'s normative discriminant
(Section~\ref{sec:pitch:nominal}). Let the enclosing pitch space have a
\texttt{DiatonicOverChromatic} structure with $C$ chromatic positions per
octave and nominal mapping $m$. A
\texttt{PitchSpacePosition::Cmn} position's \emph{absolute chromatic
coordinate} is
\[
s \;=\; m(\texttt{nominal}) \;+\; \texttt{alteration}
\;+\; C \cdot \texttt{octave}.
\]
Transposing that position by \texttt{TranspositionInterval \{ d, c \}},
where $c$ is measured in steps of the same chromatic layer, \MUST{} produce
\begin{align*}
\texttt{nominal}' &= \textsc{CmnNominal}\big((n + d) \bmod 7\big), \\
\texttt{octave}' &= \texttt{octave} + \lfloor (n + d) / 7 \rfloor, \\
\texttt{alteration}' &= (s + c) -
\big(m(\texttt{nominal}') + C \cdot \texttt{octave}'\big),
\end{align*}
where $\bmod$ and $\lfloor \cdot \rfloor$ are Euclidean (the remainder is
non-negative). The diatonic component alone selects the nominal and the
octave; the alteration absorbs exactly the residue.
A transposition \MUST{} be refused --- atomically, changing nothing --- when
any target satisfies any of:
\begin{itemize}
\item its \texttt{scale\_position.position} is not \texttt{Cmn}, so the
\texttt{Interval} above has no defined action on it;
\item its enclosing pitch space cannot be resolved to a compatible
\texttt{DiatonicOverChromatic} structure and nominal mapping;
\item its \texttt{acoustic.realization} is
\texttt{AcousticRealization::AbsoluteHz}, which overrides the tuning
system, so moving the scale position would move the notehead without
moving the sound;
\item the computed $\texttt{alteration}'$ or $\texttt{octave}'$ does not
fit its field.
\end{itemize}
An implementation \MUSTNOT{} saturate, clamp, guess a pitch-space
structure, or partially apply a transposition in any of these cases.
\end{requirement}
\begin{requirement}
\label{req:pitch:space-capability-refusal}
An implementation \MUST{} fail closed when it cannot establish the
pitch-space structure required to interpret a position. In particular,
transposition \MUST{} report a refusal when the enclosing CMN chromatic
layer or nominal mapping is unavailable, and a computation claiming a
12-chromatic pitch class or absolute semitone \MUST{} report
unavailability unless it can establish a twelve-position chromatic layer.
The \texttt{Cmn} position discriminant alone \MUSTNOT{} be treated as proof
of either capability.
\end{requirement}
\begin{rationale}
The refusal cases are the four ways a transposition can silently lie. A
non-CMN position has no nominal to move, so a chromatic shift would have to
invent one. An unresolved pitch space supplies neither the octave size nor
the nominal mapping, so arithmetic would have to guess both.
An \texttt{AbsoluteHz} pitch declares its own frequency: it can be respelled
or it can be resounded, but a transposition that claims to do both does one
and pretends to the other. And saturation is the worst of the four, because
it is invisible --- a transposition that clamps has produced a pitch nobody
asked for, reported success, and destroyed the information needed to notice.
Refusing atomically, rather than skipping the offending target, follows from
the same reasoning: a chord transposed except for one note is not a
transposed chord, it is a different chord. Contrast the treatment of
\emph{tombstoned} targets, which are skipped rather than refused --- a
deleted pitch is not an untransposable pitch, it is a pitch the operation
has nothing to say about.
\end{rationale}
\section{Acoustic Realization}
\label{sec:pitch:acoustic}
The acoustic layer specifies the sounding frequency. It does not carry
a frequency directly; instead it references a tuning system that, given
the pitch's scale position, resolves to a frequency.
\begin{lstlisting}[language=Rust]
pub struct AcousticPitch {
/// The tuning system governing this pitch's frequency.
/// May be inherited; see Section~\ref{sec:pitch:tuning-inherit}.
pub tuning: TuningReference,
/// How the tuning system resolves to a frequency.
pub realization: AcousticRealization,
}
pub enum AcousticRealization {
/// Resolve through the tuning system using the scale position alone.
/// This is the default case for ordinary CMN.
Implicit,
/// An explicit offset in cents from what the tuning system would
/// otherwise produce. For inflections, expressive retuning, or
/// just-intonation adjustments within a tempered piece.
CentsOffset(f64),
/// An explicit absolute frequency in Hertz, overriding the tuning
/// system. For spectral music or fixed-frequency electronic parts.
AbsoluteHz(f64),
}
\end{lstlisting}
\subsection{Cents as the Offset Unit}
Cents (one cent equals one hundredth of an equal-tempered semitone) are
the normative offset unit. \texttt{f64} provides far more precision than
the ear can resolve in any context.
\subsection{Tuning Reference and Inheritance}
\label{sec:pitch:tuning-inherit}
A \texttt{TuningReference} is either an explicit tuning-system identifier
or an inheritance marker:
\begin{lstlisting}[language=Rust]
pub enum TuningReference {
/// Inherit the tuning system from the enclosing scope.
Inherit,
/// Explicitly named tuning system.
Explicit(TuningSystemId),
}
\end{lstlisting}
Resolution proceeds by walking outward: pitch, then voice, then staff,
then region, then score. The score's tuning system \MUST{} be explicit;
\texttt{Inherit} at the score level is a malformed document. Tuning
systems and their resolution rules are defined in detail in
Chapter~\ref{ch:tuning}.
\subsection{Reference Pitch}
\label{sec:pitch:reference}
A pitch's \texttt{TuningReference} resolves, through the hierarchical
walk, to a tuning system. The \emph{reference pitch} (the position
and frequency that anchor that tuning system's structure to absolute
Hertz) is resolved separately through the same hierarchical walk, and
is a property of the score's tuning context, not of the tuning system
itself.
The pitch type does not own or imply a reference frequency. Two
scores using the same tuning system (12-TET) but different references
(A4 = 440\,Hz versus A4 = 415\,Hz) share their tuning structure
entirely; only their reference pitch differs.
The full reference-pitch model, including the \texttt{ReferencePitch}
type and the score tuning context that owns it, is specified in
Chapter~\ref{ch:tuning} (Section~\ref{sec:tuning:reference}).
\section{Spelling}
\label{sec:pitch:spelling}
Spelling determines what the performer sees on the page for a given
pitch: the staff position, the accidental glyph (if any), and any
ancillary notational marks (parenthesized accidentals, courtesy
accidentals, microtonal accidentals).
Spelling \MUSTNOT{} be a field on the pitch type. Spellings are stored
externally, indexed by pitch identifier, and managed by the spelling
subsystem defined in this section.
\begin{rationale}
Spelling as a field on pitch causes destructive overwrites on
respelling, prevents partial spelling (where only some pitches have
user-chosen spellings and the rest are inferred), prevents multiple
spellings per pitch (analytical layers), and prevents provenance
tracking. Externalizing spelling resolves all four.
\end{rationale}
\subsection{The Spelling Attachment}
\begin{lstlisting}[language=Rust]
pub struct SpellingAttachment {
/// What this attachment applies to: a single pitch or a scope.
pub scope: SpellingScope,
/// The directive: explicit spelling or a rule for inference.
pub directive: SpellingDirective,
/// Provenance of this attachment.
pub source: SpellingSource,
/// Tie-break priority among attachments. Higher wins. See
/// Section~\ref{sec:pitch:precedence} for the configurable
/// precedence model.
pub priority: i32,
/// Optional analytical layer. None means the engraved layer;
/// Some(id) means an alternative analytical layer that does not
/// affect the engraved score.
pub layer: Option<AnalysisLayerId>,
}
pub enum SpellingScope {
/// Applies to one specific pitch.
Pitch(PitchId),
/// Applies to all pitches matching the selector within a time range.
Range {
start: TimeAnchor,
end: TimeAnchor,
voices: VoiceSelector,
},
}
pub enum SpellingDirective {
/// An explicit spelling for a single pitch. Only valid with
/// SpellingScope::Pitch.
Explicit(PitchSpelling),
/// A rule for inferring spellings within a scope.
Rule(SpellingRule),
}
pub struct PitchSpelling {
/// The nominal: the staff position. For CMN, this is one of A-G.
/// For other grammars, an identifier into the grammar's nominal
/// registry.
pub nominal: SpellingNominal,
/// Stack of accidentals applied to the nominal, in stacking
/// order: innermost (closest to the notehead) first.
/// Empty vector means no accidental glyph is drawn, which does
/// not necessarily mean "natural"; see
/// Section~\ref{sec:pitch:absent-accidental}.
pub accidentals: Vec<AccidentalId>,
/// The octave designation.
pub octave: i8,
/// Optional rendering hints: parenthesized, cautionary, editorial,
/// small-print, etc. Non-normative for sounding pitch.
pub render_hints: SpellingRenderHints,
}
pub enum SpellingNominal {
/// CMN nominals: the fast path.
Cmn(CmnNominal),
/// Integer nominals for N-tone systems.
Integer(i32),
/// Registered nominals for grammar-specific systems.
Registered(NominalRegistryId),
}
\end{lstlisting}
\subsubsection{Accidental Stack Semantics}
\label{sec:pitch:accidental-stack}
\begin{requirement}
\label{req:pitch:accidental-stack}
The \texttt{accidentals} vector \MUST{} be interpreted as follows:
\begin{itemize}
\item The stored order is normative for engraving: index 0 is
the innermost glyph (closest to the notehead), with subsequent
indices placed further outward.
\item For built-in additive accidental systems, the pitch-space
modification contributed by the stack is the sum of the
modifications of its members, and is independent of stored
order. Reordering the stack changes engraving appearance but
\MUSTNOT{} change sounding pitch.
\item Custom accidental systems \MAY{} declare non-additive
combination semantics only through their registered
\texttt{AccidentalCombination} (Section~\ref{sec:tuning:accidentals}).
Otherwise, accidentals are treated as commutative under their
pitch effect.
\item Two accidentals with the same \texttt{AccidentalId} in one
stack constitute a malformed spelling, unless the accidental's
registered combination semantics explicitly permits repetition.
The conventional double-sharp and double-flat are represented
as single accidental definitions, not as repeated singles.
\item An empty \texttt{accidentals} vector means no glyph is
drawn; it is distinct from a vector containing only a natural
sign (which explicitly cancels prior alterations and is drawn).
\end{itemize}
\end{requirement}
\subsection{Spelling Sources}
Every spelling attachment carries provenance:
\begin{lstlisting}[language=Rust]
pub enum SpellingSource {
/// The user explicitly chose this spelling.
UserChosen,
/// Inferred by the spelling pre-pass from key signature and
/// context. Lowest default precedence. The pre-pass does not
/// store attachments (its output is a derived annotation);
/// this variant ranks that output in the precedence order.
Inferred,
/// Imported from a foreign format.
Imported { format: ForeignFormatId },
/// Propagated from a transposition or other edit operation.
Propagated { from: PitchId },
/// An analytical spelling on a non-engraved layer.
Analytical,
}
\end{lstlisting}
\begin{requirement}
\label{req:pitch:spelling-provenance}
Editing operations that respell a pitch \MUST{} produce attachments
with source \texttt{UserChosen}. Editing operations that move or
transpose a pitch \MUST{} produce attachments with source
\texttt{Propagated}. Foreign-format importers \MUST{} produce
attachments with source \texttt{Imported}. The spelling pre-pass
(Section~\ref{sec:pitch:prepass}) \MUSTNOT{} mint attachments: its
inferred spellings are derived annotations reported with
\texttt{Inferred} provenance, promoted to a stored
\texttt{UserChosen} attachment only by an explicit editing
operation.
\end{requirement}
\subsection{Configurable Precedence}
\label{sec:pitch:precedence}
When multiple attachments target the same pitch on the same analysis
layer, the renderer resolves the conflict by precedence. The default
precedence is:
\begin{enumerate}
\item \texttt{UserChosen} (highest)
\item \texttt{Imported}
\item \texttt{Propagated}
\item \texttt{Inferred} (lowest)
\end{enumerate}
\begin{requirement}
\label{req:pitch:spelling-precedence}
Every score \MUST{} carry a \texttt{SpellingPrecedence} configuration.
The configuration \MUST{} assign a total ordering over the variants of
\texttt{SpellingSource}. The configuration \MAY{} differ from the
default; for example, a workflow that treats MusicXML imports as
authoritative may rank \texttt{Imported} above \texttt{UserChosen}.
Conflicts are broken first by precedence, then by the \texttt{priority}
field of the attachment (higher wins), then by canonical attachment
order (the attachment appearing earliest in the score's canonical
serialization wins). Attachments carry no creation timestamp: a
timestamp tie-break would hang resolution on non-canonical state.
\end{requirement}
\subsection{The Spelling Pre-Pass}
\label{sec:pitch:prepass}
When a score is materialized (on load, after an edit, or ahead of
rendering), the spelling pre-pass computes an inferred spelling for
every spelling-eligible pitch. Its output is a \emph{canonical
derived annotation}: a deterministic function of the materialized
score graph, the active profile, and the versioned
\texttt{SpellingAlgorithmId}, recomputed on materialization and
never stored as graph state or serialized into canonical chunks.
Where a stored attachment targets the pitch, the resolved annotation
reports that attachment's spelling and provenance per the precedence
configuration (Section~\ref{sec:pitch:precedence}); the pre-pass
supplies the \texttt{Inferred}-provenance spelling that applies when
no attachment outranks it. The pre-pass \MUST{} be deterministic:
given the same materialized graph, precedence configuration, and
algorithm version, it \MUST{} produce identical annotations.
The pre-pass operates as follows:
\begin{enumerate}
\item Collect all pitches in the score, partitioned by voice and
measure.
\item For each partition, in time order, resolve each pitch's spelling
using the applicable context inputs (which of these a given
algorithm version consumes is part of its versioned definition;
see Requirement~\ref{req:pitch:spelling-algorithm}):
\begin{enumerate}
\item The active key signature for the staff.
\item The accidental context of prior pitches in the same measure
and voice.
\item The melodic context (preceding interval).
\item The harmonic context (concurrent pitches in other voices).
\item Any active scope-level \texttt{Rule} attachments.
\end{enumerate}
\item Report an \texttt{Inferred}-provenance derived annotation for
each pitch.
\end{enumerate}
Because derived annotations are not stored, a change to the algorithm
version deterministically invalidates them without any state
migration. Implementations \MAY{} cache derived annotations and
\MAY{} recompute incrementally (re-running only the affected measure
and voice, and forward through measures whose context is altered,
until the context stabilizes); a cache \MUST{} be invalidated when
the derivation key --- graph, profile, algorithm version --- changes,
and neither caching nor incrementality may be observable in the
annotations produced.
\begin{rationale}
Deriving inferred spellings---rather than storing them as
attachments---keeps algorithm output out of canonical state: two
replicas at the same (graph, profile, algorithm version) agree on
annotations byte-for-byte without ever exchanging them, an
algorithm upgrade cannot strand stale inferred state, and
serialization never churns on re-inference. Inferred spellings
remain inspectable in the UI and editable by promotion: accepting
one mints a \texttt{UserChosen} attachment through an editing
operation, which then outranks the pre-pass.
\end{rationale}
\begin{requirement}
\label{req:pitch:spelling-algorithm}
\textbf{Spelling algorithm disposition (ratified Pass 12).}
The spelling pre-pass receives the \emph{profile-declared by
versioned identifier} disposition of Appendix~\ref{app:determinism}
Section~\ref{sec:det:open}. The reserved identifier
\texttt{SpellingAlgorithmId} \texttt{"default"} denotes, at
version~1, a Temperley-style line-of-fifths preference
algorithm: each partition's tonal context is estimated as a
centre of gravity on the line of fifths from the accidental,
melodic, and harmonic context, and each pitch takes the
enharmonic spelling nearest that centre. The ascending-runs-take-
sharps / descending-runs-take-flats convention participates
\emph{only as a tiebreak} within the centre-of-gravity rule; an
isolated chromatic run with no tonal context may therefore
receive the enharmonic the convention alone would not pick, and
a voice-leading refinement is a future algorithm version, not a
deviation. Which of the context inputs enumerated above an
algorithm version consumes is part of its versioned definition:
\texttt{"default"} version~1 does \emph{not} consult declared
key signatures (a key-aware refinement is a future version) and
is region-time-model-independent --- pitches in aleatoric and
proportional regions are spelled by the same rule as metric
ones, and no region-specific spelling pass exists. A profile
requesting any other identifier \MUST{} error;
implementations \MUSTNOT{} silently substitute a different
algorithm.
\end{requirement}
\begin{requirement}
\label{req:pitch:authored-uninferred}
\textbf{Authored annotations for inference-ineligible targets
(ratified Pass 12).}
A stored spelling attachment whose target the pre-pass produces
\emph{no} inferred spelling for (an inference-ineligible pitch)
\MUST{} still surface in the resolved derived annotations: the
winning attachment under the precedence configuration
(Section~\ref{sec:pitch:precedence}) is reported with its
authored provenance, exactly as it would have been had it
outranked an inferred spelling. An authored attachment is
precisely how a user notates what the algorithm cannot infer;
the derived-annotation surface \MUSTNOT{} render it invisible.
Annotation taxonomy counts these authored-only resolutions
distinctly from authored-over-inferred overrides. The same rule
applies to the decomposition pre-pass
(Section~\ref{sec:time:notrhythm}).
\end{requirement}
\subsection{Absent Accidentals}
\label{sec:pitch:absent-accidental}
A \texttt{PitchSpelling} with an empty \texttt{accidentals} vector
means no accidental glyph is drawn at this position. This is distinct
from a vector containing an explicit natural sign. The distinction
matters in two cases:
\begin{itemize}
\item A pitch on a line affected by the key signature carries no
accidental and sounds the key-signature pitch; drawing a natural
would be a respelling.
\item A pitch that follows an in-measure accidental on the same line
and octave inherits that accidental; an empty vector means
inheritance applies, while a vector containing a natural cancels
the inheritance.
\end{itemize}
\begin{requirement}
\label{req:pitch:absent-versus-natural}
The renderer \MUST{} distinguish between an empty
\texttt{accidentals} vector (no glyph) and a vector containing only
a natural (explicit natural glyph) when materializing a spelling.
Promotion from empty to natural-only, or vice versa, is a
respelling operation.
\end{requirement}
\subsection{Analytical Layers}
\label{sec:pitch:layers}
A pitch may carry multiple spelling attachments on different analytical
layers. The engraved layer (\texttt{layer: None}) governs what the
performer sees. Other layers (\texttt{layer: Some(AnalysisLayerId)})
record alternative spellings used by analytical views, theory tools,
or pedagogical overlays.
The \texttt{AnalysisLayer} type itself is defined in
Chapter~\ref{ch:graph} (Section~\ref{sec:graph:layers}); a layer
identifier here refers to that authoritative definition.
\begin{requirement}
\label{req:pitch:spelling-layer-fallback}
Resolution of a pitch's spelling for a given view \MUST{} consider
only attachments whose \texttt{layer} matches the view's active layer.
The engraved view's active layer is \texttt{None}. Analytical views
may set an explicit layer; if no attachment on that layer matches,
the view \MUST{} fall back to the engraved layer.
\end{requirement}
\section{Pitch Identifiers}
\label{sec:pitch:ids}
Every pitch in the score graph carries a stable identifier:
\begin{lstlisting}[language=Rust]
pub struct PitchId(pub u128);
\end{lstlisting}
\begin{requirement}
\label{req:pitch:pitch-id-stability}
Pitch identifiers \MUST{} be stable across edits: a pitch's identifier
is assigned at creation and never reassigned. Pitch identifiers
\MUST{} be unique within a score. Pitch identifiers \MUST{} be
collision-resistant across distributed editing scenarios; the
recommended generation strategy is a 128-bit value combining a
per-replica identifier and a monotonic counter, with the exact format
defined in Chapter~\ref{ch:semops}.
\end{requirement}
\section{What Pitch Is Not}
\label{sec:pitch:not-pitch}
Several musical phenomena resemble pitches but are not represented by
the pitch type:
\begin{description}
\item[Unpitched events.] Snare drum hits, claves, cymbal strokes, and
other unpitched percussion are sounding events with staff positions
but no pitched identity. They are represented by a separate
\texttt{UnpitchedEvent} type (see Chapter~\ref{ch:graph}). They have
no scale position and no acoustic pitch in the sense defined here.
\item[Rests.] A rest is a duration without a sounding event. Rests are
represented by a separate \texttt{Rest} type. They carry timing and
voice information but no pitch.
\item[Indeterminate-pitch events.] Events whose pitch is deliberately
indeterminate (text instructions like ``highest possible note,''
aleatoric pitch material, certain graphic-notation events) are
represented by an \texttt{IndeterminatePitchEvent} type, which may
carry hints (range, contour, density) but no concrete pitch.
\item[Pitch trajectories.] Glissandi, portamenti, and continuous pitch
bends are not single pitches but trajectories between or around
pitches. They are represented as relationships between pitched
events plus a trajectory shape, defined in Chapter~\ref{ch:graph}.
\end{description}
\begin{rationale}
Forcing every sounding event into a pitch-shaped hole creates
invariant-breaking edge cases throughout the codebase. Sibling types
under a common \texttt{SoundingEvent} (or similar) abstraction keep
each type coherent and let operations branch cleanly.
\end{rationale}
\section{Equality and Comparison}
\label{sec:pitch:eq}
Two pitches are \emph{structurally equal} if and only if their scale
positions and acoustic realizations are equal field-by-field. Structural
equality is the default \texttt{Eq} implementation.
Three additional equivalence relations are defined:
\begin{description}
\item[Sounding equivalence.] Two pitches are sounding-equivalent if
they resolve to the same frequency under their respective tuning
systems. Computed, not derived from the type.
\item[Scale-position equivalence.] Two pitches are scale-position-
equivalent if their \texttt{ScalePosition} fields are equal, ignoring
acoustic realization.
\item[Enharmonic equivalence.] Two pitches are enharmonically
equivalent if they are sounding-equivalent under 12-tone equal
temperament, regardless of their actual tuning system. Used for
foreign-format export and certain analytical operations.
\end{description}
\begin{requirement}
\label{req:pitch:pitch-equivalence-functions}
Implementations \MUST{} provide functions for each of the three
computed equivalences. Implementations \MUSTNOT{} conflate structural
equality with sounding or enharmonic equivalence.
\end{requirement}
\section{Forward References}
\begin{itemize}
\item Tuning systems, pitch spaces, and accidental registries are
defined in Chapter~\ref{ch:tuning}.
\item The score graph, including how pitches are embedded in events,
voices, and staves, is defined in Chapter~\ref{ch:graph}.
\item \texttt{TimeAnchor}, \texttt{VoiceSelector}, \texttt{PitchId}
generation, and the reduction rules governing spelling
attachments under concurrent edits are defined in
Chapter~\ref{ch:semops}.
\end{itemize}
% ===========================================================================
\chapter{Time and Duration}
\label{ch:time}
This chapter specifies the time and duration primitives: how positions
and durations are represented, how time signatures and tempo maps are
modelled, how notational rhythm is decomposed from sounding duration,
how tuplets and other groupings work, and how the core supports metric,
proportional, and aleatoric time models simultaneously.
\section{Design Principles}
\label{sec:time:principles}
\begin{description}
\item[Exact rational arithmetic.] Musical positions and durations
\MUST{} be exact rationals. Floating-point time is rejected as a
source of accumulated error; integer-tick time is rejected as
incompatible with arbitrary tuplet nesting. Exactness is
non-negotiable.
\item[Two clocks.] Musical time (measured in whole-note rationals)
and wall-clock time (measured in fixed-point seconds) are distinct
primitives. Conversion between them is a pure function of the
tempo map.
\item[Position and duration are distinct types.] A position and a
duration share an underlying representation but are not
interchangeable. The algebra (position $+$ duration $\rightarrow$ position;
position $-$ position $\rightarrow$ duration; position $+$ position is
undefined) is enforced at the type level.
\item[Sounding duration is data; notational rhythm is derived.]
A note's sounding duration is intrinsic; its notated decomposition
(notehead values, ties, dots, tuplet membership) is produced by a
deterministic pre-pass analogous to the spelling pre-pass and is
overridable per-event by an authored attachment.
\item[Multiple time models coexist.] A score \MUST{} support metric,
proportional, and aleatoric regions, possibly concurrent on
different staves. The region's declared time model determines the
interpretation of positions within it.
\item[Time anchors, not absolute positions.] Stored references to
points in time \MUST{} anchor to identified objects (events,
measures, regions) plus offsets. Absolute positions \MUSTNOT{}
be stored where they could shift under edits.
\end{description}
\section{The Rational Time Type}
\label{sec:time:rational}
The fundamental time primitive is an exact rational number. The
specification states the contract; implementations are free to choose
representations meeting it.
\begin{requirement}
\label{req:time:exact-rational-time}
The rational time type \MUST{} behave as an exact rational with at
least 32-bit numerator and 32-bit denominator range in normalized
form. Operations \MUST{} produce exact results; on representation
overflow, the implementation \MUST{} either widen to an exact
unbounded representation or return an error. Silent loss of precision
is forbidden. Implementations \MAY{} use any representation that
meets this contract, including packed inline-or-heap-promoted designs.
\end{requirement}
\subsection{Recommended Implementation: Inline-or-Promoted}
\label{sec:time:impl}
The recommended implementation packs the common case (denominators
under $2^{32}$) into 8 bytes and promotes to a heap-allocated
arbitrary-precision rational on overflow. This is normative for
conformance only inasmuch as the observable behavior must match;
it is illustrative as a reference design.
\begin{lstlisting}[language=Rust]
/// A musical time value: position or duration. Exact rational.
pub enum RationalTime {
/// Inline case: fits in 8 bytes.
Small(SmallRational),
/// Promoted case: heap-allocated arbitrary-precision rational.
/// Used only when arithmetic overflows the inline range.
Large(Arc<BigRational>),
}
/// The inline rational: numerator i32, denominator NonZeroU32.
/// Always normalized: gcd(|numerator|, denominator) == 1.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct SmallRational {
numerator: i32,
denominator: NonZeroU32,
}
\end{lstlisting}
\begin{requirement}
\label{req:time:rational-normalization}
All instances of the small rational \MUST{} be normalized: the
greatest common divisor of the absolute value of the numerator and
the denominator \MUST{} equal one, and the denominator \MUST{} be
strictly positive. The constructor \MUST{} enforce normalization.
\end{requirement}
\subsection{Promotion and Demotion}
Arithmetic operations on two small rationals are performed in widened
intermediate types ($i64$ numerator, $u64$ denominator) and the
normalized result is examined. If the result fits in the small
representation, it is returned as \texttt{Small}; otherwise it is
returned as \texttt{Large}.
\begin{requirement}
\label{req:time:rational-promotion}
Arithmetic that exceeds the small representation's range \MUST{}
silently promote to the large representation. The user of the type
\MUST{} observe no behavioral difference between small-only and
small-or-large arithmetic; the contract is exact arithmetic on
exact rationals.
Implementations \MAY{} demote a \texttt{Large} value back to
\texttt{Small} when its normalized representation fits, but
\MUSTNOT{} make demotion observable (e.g., by changing
equality or hash behavior across the variant boundary).
\end{requirement}
\subsection{Equality and Ordering}
Two rational time values are equal if and only if their normalized
representations represent the same rational number. Equality between
\texttt{Small} and \texttt{Large} variants \MUST{} compare the
underlying values, not the variants.
Ordering is the standard rational ordering. Comparison between rationals
in the small representation is performed by cross-multiplication in
widened intermediate types to avoid overflow.
\subsection{Units}
The unit of musical time is the \emph{whole note}. A quarter note has
duration $\frac{1}{4}$. A triplet eighth has duration $\frac{1}{12}$.
A quintuplet sixteenth inside a duplet half (an unusual but legal
construct) has duration $\frac{1}{2} \cdot \frac{1}{2} \cdot \frac{1}{5}
= \frac{1}{20}$.
\begin{requirement}
\label{req:time:whole-note-unit}
The whole note \MUST{} be the unit of musical time. The duration of
a whole note is the rational $\frac{1}{1}$. All other durations are
expressed as rationals relative to the whole note.
\end{requirement}
\begin{rationale}
The whole note is the only unit under which durations form a
multiplicative algebra with $1$ as identity. Quarter-note units would
make ``the duration of a whole note'' equal to $4$, which is
awkward; PPQ-tick units would force a choice of resolution. The
whole-note unit also matches the etymology of note-value names: a
half note is half of a whole note.
\end{rationale}
\section{Position and Duration as Distinct Types}
\label{sec:time:newtypes}
The rational time representation is shared between two distinct types:
\texttt{MusicalPosition} and \texttt{MusicalDuration}. Both wrap a
\texttt{RationalTime}. Their distinction is enforced at the type level
to prevent algebraic errors.
\begin{lstlisting}[language=Rust]
/// A point in musical time, relative to the origin of a time region.
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MusicalPosition(RationalTime);
/// A span of musical time.
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MusicalDuration(RationalTime);
impl Add<MusicalDuration> for MusicalPosition {
type Output = MusicalPosition;
// ...
}
impl Add<MusicalDuration> for MusicalDuration {
type Output = MusicalDuration;
// ...
}
impl Sub<MusicalPosition> for MusicalPosition {
type Output = MusicalDuration;
// ...
}
// MusicalPosition + MusicalPosition is intentionally not implemented.
\end{lstlisting}
\begin{requirement}
\label{req:time:position-duration-types}
Implementations \MUST{} provide distinct types for position and
duration. The type system \MUST{} prevent adding two positions. The
difference of two positions \MUST{} yield a duration; the sum of a
position and a duration \MUST{} yield a position.
\end{requirement}
\begin{rationale}
The illustrative Rust signatures derive \texttt{Clone}, not
\texttt{Copy}, because \texttt{RationalTime} contains an
\texttt{Arc<BigRational>} variant that cannot satisfy
\texttt{Copy}. Implementations \MAY{} provide \texttt{Copy}
semantics through a compact inline-or-pointer-tagged representation
whose \texttt{Large} case is a non-owning interior pointer rather
than an \texttt{Arc}; such implementations satisfy the contract
here. Conformance does not require \texttt{MusicalPosition} or
\texttt{MusicalDuration} to be \texttt{Copy}.
\end{rationale}
\section{Wall-Clock Time}
\label{sec:time:wallclock}
Wall-clock time is the time measured by the playback engine, by SMPTE
timecode, and by external audio and video sources. It is distinct from
musical time and is represented as a fixed-point integer count of a
sub-second unit.
\begin{lstlisting}[language=Rust]
/// A point in wall-clock time, in nanoseconds from a region origin.
/// 64-bit signed: range +/- ~292 years.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct WallClockTime(i64); // nanoseconds
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct WallClockDuration(i64); // nanoseconds
\end{lstlisting}
\begin{requirement}
\label{req:time:nanosecond-wallclock}
Wall-clock time \MUST{} be a fixed-point integer count of
nanoseconds. Floating-point wall-clock time is forbidden in stored
data. Implementations \MAY{} convert to floating-point for transient
computation provided no stored or serialized value loses precision.
\end{requirement}
\begin{rationale}
Nanosecond resolution provides over four orders of magnitude more
precision than the human ear can resolve and aligns with the
resolution conventions of professional audio interfaces and
SMPTE-derived clocks. The 292-year range is sufficient for any
conceivable musical work.
\end{rationale}
\subsection{Wall-Clock and Musical Time Coexist}
A score event may be anchored in either musical or wall-clock time.
Musical-time anchors are the default for ordinary notation; wall-clock
anchors are used for film hit points, audio reference markers, fixed
electronic parts in scores with live ensemble, and similar
sync-critical content. Both anchor types may coexist in the same
score, possibly on the same staff.
Conversion between musical and wall-clock time is performed through
the tempo map (Section~\ref{sec:time:tempomap}).
\section{Time Anchors}
\label{sec:time:anchors}
Stored references to points in time \MUST{} be expressed as
\emph{time anchors}: references to identified objects plus offsets.
Absolute positions \MUSTNOT{} be stored where they could be invalidated
by edits to the score.
\begin{lstlisting}[language=Rust]
pub enum TimeAnchor {
/// Anchored to a specific event. Survives edits that do not
/// delete the event.
Event { id: EventId, offset: AnchorOffset },
/// Anchored to a measure boundary. Survives measure reordering
/// (the anchor follows the measure).
Measure {
id: MeasureId,
position: MeasurePosition,
offset: AnchorOffset,
},
/// Anchored to the start or end of a region.
Region { id: RegionId, edge: RegionEdge, offset: AnchorOffset },
/// Anchored to absolute wall-clock time within the score.
/// Used for film and audio sync.
WallClock { time: WallClockTime },
}
/// An offset applied to an anchor target. The variant kind is
/// constrained by the target's enclosing region's time model.
pub enum AnchorOffset {
/// Offset in musical time. Valid for targets in metric regions
/// and for aleatoric regions whose anchoring discipline
/// admits musical coordinates.
Musical(MusicalDuration),
/// Offset in wall-clock time. Valid for targets in proportional
/// regions and for aleatoric regions whose anchoring discipline
/// admits wall-clock coordinates.
WallClock(WallClockDuration),
/// No offset; the anchor refers to the target's reference point
/// exactly. Valid in any region.
Zero,
}
pub enum MeasurePosition { Start, End }
pub enum RegionEdge { Start, End }
\end{lstlisting}
\begin{requirement}
\label{req:time:anchor-references}
Stored \emph{references to external time points} (cross-cutting
endpoints, marker locations, attachment anchors, anything
pointing at an object outside the referencing object's own
voice and region) \MUST{} use the \texttt{TimeAnchor} type.
Absolute musical positions \MUSTNOT{} appear as such references
in stored data: a reference to ``measure 47, beat 3'' that does
not survive a measure insertion in measure 12 is a broken
reference.
An event's own position within its owning voice and region
\MAY{} be stored as an \texttt{EventPosition} (carrying a
\texttt{MusicalPosition} for events in metric regions, or a
\texttt{WallClockTime} for events in proportional regions,
etc.). Event-local positions are part of the event's intrinsic
data, not references to other objects. Operations that shift a
voice or region update the contained events' positions
directly; they do not need anchor indirection because the
containment relationship makes the dependency explicit.
Implementations \MAY{} compute and cache absolute positions
from anchors for performance; such caches \MUST{} be
invalidated when their underlying anchored objects change.
\end{requirement}
\begin{requirement}
\label{req:time:anchor-offset-kind}
The variant of an \texttt{AnchorOffset} \MUST{} agree with the
time model of the anchor target's enclosing region:
\begin{itemize}
\item For targets in metric regions: \texttt{AnchorOffset::Musical}
or \texttt{AnchorOffset::Zero}.
\item For targets in proportional regions:
\texttt{AnchorOffset::WallClock} or \texttt{AnchorOffset::Zero}.
\item For targets in aleatoric regions: any offset variant
consistent with the region's
\texttt{AleatoricAnchoringDiscipline}
(Section~\ref{sec:graph:aleatoric-discipline}).
\end{itemize}
Implementations \MUST{} reject anchors whose offset variant
contradicts the target region's time model.
\end{requirement}
\subsection{Anchor Resolution}
Resolving a time anchor to an absolute position is a pure function of
the score graph: locate the anchored object, query its position within
its region, and add the offset. Resolution \MUST{} be deterministic.
\subsection{Anchor Stability Under Edits}
The chosen anchor type determines what edits the anchor survives:
\begin{itemize}
\item An \texttt{Event} anchor survives any edit that does not delete
the anchored event. Edits that move the event (insertions before
it, tempo changes) move the anchor with it.
\item A \texttt{Measure} anchor survives insertions and deletions of
other measures, and survives reordering. It is invalidated only by
deletion of the anchored measure.
\item A \texttt{Region} anchor survives edits within the region. It
is invalidated only by deletion of the region.
\item A \texttt{WallClock} anchor is unaffected by musical edits but
may correspond to different musical positions after tempo changes.
\end{itemize}
\begin{requirement}
\label{req:time:orphaned-anchor-handling}
When an anchored object is deleted, anchors targeting it \MUST{} be
either re-anchored to a surviving object or marked as orphaned. The
semantic operations chapter (Chapter~\ref{ch:semops}) specifies
re-anchoring rules per operation.
\end{requirement}
\section{Time Signatures and Meter}
\label{sec:time:meter}
A time signature defines the metric organization of a passage: where
bar lines fall, where beats group, and what accent pattern is implied.
Time signatures are objects in the score graph; they are not merely
display numerator/denominator pairs.
\begin{lstlisting}[language=Rust]
pub struct TimeSignature {
/// Display form. Determines what the performer sees.
pub display: TimeSignatureDisplay,
/// The total duration of one measure under this signature.
pub measure_duration: MusicalDuration,
/// Beat groupings within the measure. Drives beaming, accent
/// inference, and the notational-decomposition pre-pass.
pub beat_groups: Vec<BeatGroup>,
}
pub enum TimeSignatureDisplay {
/// Standard: a numerator over a power-of-two denominator.
Standard { numerator: u16, denominator: PowerOfTwo },
/// Compound display: e.g., 3+2+3/8.
Compound { numerators: Vec<u16>, denominator: PowerOfTwo },
/// Irrational meter: denominator is not a power of two.
/// e.g., 4/6, 5/12. The denominator names a non-power-of-two
/// note value.
Irrational { numerator: u16, denominator: NonZeroU16 },
/// Mixed denominators: e.g., 1/4 + 1/8 + 1/16 displayed additively.
MixedDenominators { components: Vec<(u16, NonZeroU16)> },
/// No visible time signature (free meter or proportional).
None,
/// Custom symbol (cut time, common time, etc.) or
/// grammar-specific.
Symbolic(SymbolId),
}
pub struct BeatGroup {
/// Duration of this beat group.
pub duration: MusicalDuration,
/// Subdivision hint for beaming and accent.
pub subdivision: Option<MusicalDuration>,
/// Accent strength relative to other beat groups in the measure.
/// Higher values indicate stronger accents.
pub accent: u8,
}
\end{lstlisting}
\begin{requirement}
\label{req:time:beat-group-sum}
The sum of the durations of a time signature's beat groups \MUST{}
equal the measure duration. Implementations \MUST{} reject time
signatures whose beat groups do not sum to the measure duration.
\end{requirement}
\subsection{Irrational Meters}
Time signatures whose denominator is not a power of two
(e.g., 4/6, 5/12) are first-class. Their measure duration is computed
as a rational: a 4/6 measure has duration $\frac{4}{6} = \frac{2}{3}$
whole notes. No special case in the time model; only the display layer
treats the unusual denominator.
\subsection{Polymetric and Polytemporal Scores}
A score \MAY{} have different time signatures concurrent on different
staves. Each staff carries its own meter sequence; bar lines align where
the rational positions coincide, and may misalign otherwise. The score
graph chapter (Chapter~\ref{ch:graph}) defines how concurrent meters
are represented; this chapter establishes only that the time model
supports them.
\section{Tempo and the Tempo Map}
\label{sec:time:tempomap}
The tempo map is the function mapping musical positions to wall-clock
positions. It is a piecewise definition over musical time:
\begin{lstlisting}[language=Rust]
pub struct TempoMap {
/// Piecewise segments, in non-overlapping musical-time order.
pub segments: Vec<TempoSegment>,
/// Tempo before the first segment, if any. None means the first
/// segment's start_tempo applies from time zero.
pub initial: Option<Tempo>,
}
pub struct TempoSegment {
/// Start of this segment, as a TimeAnchor.
pub start: TimeAnchor,
/// End of this segment. None means "valid until the next segment
/// in segments order, or until the end of the score if this is
/// the final segment." Implementations resolve None lazily.
pub end: Option<TimeAnchor>,
/// Tempo at the start of this segment.
pub start_tempo: Tempo,
/// Tempo at the end of this segment.
/// Required if shape != Constant; ignored (and SHOULD be None)
/// if shape == Constant. A Constant segment holds start_tempo
/// throughout.
pub end_tempo: Option<Tempo>,
/// Shape of the tempo change over this segment.
pub shape: TempoShape,
}
pub enum TempoShape {
/// Constant tempo throughout: end_tempo MUST be None or equal to
/// start_tempo.
Constant,
/// Linear interpolation from start_tempo to end_tempo.
Linear,
/// Exponential (continuous-rate) interpolation from start_tempo
/// to end_tempo.
Exponential,
/// Arbitrary curve, parameterized by control points. The control
/// points define intermediate tempos between start_tempo and
/// end_tempo.
Curve(TempoCurve),
}
pub struct Tempo {
/// Beats per minute. The beat unit is part of the tempo
/// specification: a quarter-note BPM and a dotted-quarter BPM
/// at the same numeric value differ in actual rate.
pub bpm: f64,
/// Note value taken as one beat.
pub beat_unit: MusicalDuration,
}
\end{lstlisting}
\begin{requirement}
\label{req:time:tempo-segment-order}
The tempo map's segments \MUST{} be non-overlapping when resolved
to absolute musical positions, and \MUST{} appear in
monotonically-increasing start order. Two adjacent segments where
the earlier segment's resolved end equals the later segment's
resolved start form a continuous tempo curve; the later segment's
\texttt{start\_tempo} \SHOULD{} equal the earlier segment's resolved
end tempo when continuity is intended.
A segment with \texttt{end == None} extends until the next
segment's resolved start or, if no later segment exists, until the
end of the score. Implementations \MUST{} resolve open-ended
segments deterministically.
At musical positions not covered by any segment (gaps between
segments, or before the first segment when \texttt{initial} is
\texttt{None}), the tempo \MUST{} be the most recent applicable
segment's terminating tempo, or the next applicable segment's
\texttt{start\_tempo} if no earlier segment exists.
\end{requirement}
\subsection{Conversion}
Conversion between musical and wall-clock time integrates the tempo map.
For constant segments, conversion is multiplication. For linear and
exponential segments, conversion has a closed-form solution. For curve
segments, conversion is numerical.
\begin{requirement}
\label{req:time:linear-interpolates-speed}
For a \texttt{TempoShape::Linear} segment, the quantity that varies
linearly across the segment is \emph{speed} --- whole notes per
second --- as a function of fractional position within the segment,
\emph{not} beats-per-minute and \emph{not} beat period. Speed is
derived from a \texttt{Tempo} as
$\text{speed} = \text{bpm} \times w(\text{beat\_unit}) / 60$, where
$w(\cdot)$ is the beat unit's value in whole notes. Writing
$s_0, s_1$ for the segment's start and end speeds and
$u \in [0,1]$ for fractional position, $s(u) = s_0 + (s_1 - s_0)\,u$,
and the wall-clock duration of the segment of musical length
$\ell$ is the closed form
$\int_0^1 \ell\,\mathrm{d}u / s(u) = (\ell / (s_1 - s_0))\,
\ln(s_1 / s_0)$ (and $\ell / s_0$ when $s_1 = s_0$).
A \texttt{TempoShape::Exponential} segment interpolates speed
\emph{geometrically}: $s(u) = s_0\,(s_1/s_0)^{u}$, i.e. a constant
continuous rate of change of speed.
\end{requirement}
\begin{rationale}
``Linear interpolation from \texttt{start\_tempo} to
\texttt{end\_tempo}'' is ambiguous about which quantity is linear:
bpm, period, or speed. We pin \emph{speed} because it is the only
beat-unit-agnostic choice. A tempo map may change beat unit across
segments (a \texttt{quarter = 120} segment abutting a
\texttt{dotted-quarter = 80} segment); interpolating bpm would make
the wall-clock schedule depend on the arbitrary beat unit chosen to
notate each endpoint, whereas interpolating speed depends only on
the sounding rate. When both endpoints share a beat unit, speed-linear
and bpm-linear coincide, so the common case matches a conductor's
``linear accelerando'' intuition. Because this choice fixes the
derived wall-clock schedule for every playback engine, it is stated
normatively rather than left to the implementation.
\end{rationale}
\begin{requirement}
\label{req:time:tempo-map-conversion}
The tempo map \MUST{} expose two conversion functions:
\texttt{musical\_to\_wallclock} and \texttt{wallclock\_to\_musical}.
Both \MUST{} be deterministic: given identical tempo maps and inputs,
they \MUST{} produce identical outputs across runs and platforms.
Numerical integration and inversion, where used, \MUST{} use a
deterministic algorithm with documented tolerance and iteration
bounds.
Tempo curves \MUST{} be monotonically increasing in musical time
(i.e., tempo may vary but musical time always advances) so that
\texttt{wallclock\_to\_musical} is single-valued. Implementations
\MUST{} reject tempo segments whose shape produces a non-monotonic
mapping (such curves are musically meaningless).
\end{requirement}
\begin{openquestion}
The specific numerical-integration algorithm for
\texttt{TempoShape::Curve} (Romberg integration, adaptive
Gauss-Kronrod, or a normative method) and the specific
root-finding algorithm for \texttt{wallclock\_to\_musical} are
unresolved. Both algorithms affect canonical state and must
therefore receive one of the dispositions in
Appendix~\ref{app:determinism}
Section~\ref{sec:det:open}: normatively specified in this
document or a named companion, profile-declared by versioned
identifier, or explicitly marked non-canonical. Until that
disposition is made, cross-implementation byte equality of
derived canonical state (notably tempo-driven audio
scheduling) \MUSTNOT{} be claimed.
\end{openquestion}
\begin{rationale}
Determinism of tempo conversion is necessary for reproducible audio
output, click-track stability, and SMPTE sync. Non-determinism here
would produce playback drift that compounds over long pieces.
\end{rationale}
\section{Sounding Duration and Notational Decomposition}
\label{sec:time:notrhythm}
An event has a single \emph{sounding duration} (an exact rational).
Its \emph{notational decomposition} is the sequence of notehead values,
augmentation dots, and ties used to draw that duration on the staff.
The decomposition is computed from the sounding duration by a
deterministic pre-pass as a \emph{canonical derived annotation} ---
recomputed on materialization, never stored graph state --- under the
same output model as the spelling pre-pass
(Section~\ref{sec:pitch:prepass}). The user may accept the inferred
decomposition or override it by authoring a decomposition attachment,
which takes precedence.
\subsection{The Notational Decomposition}
\begin{lstlisting}[language=Rust]
pub struct NotationalDecomposition {
/// Sequence of notated components. Their durations sum to the
/// event's sounding duration.
pub components: Vec<NotatedComponent>,
/// Provenance, analogous to SpellingSource.
pub source: DecompositionSource,
}
pub struct NotatedComponent {
/// Base note value: whole, half, quarter, eighth, etc.
pub base_value: NoteValue,
/// Number of augmentation dots (0..=4 typical).
pub dots: u8,
/// Tuplet membership, if any.
pub tuplet: Option<TupletId>,
/// Whether this component is tied to the next.
pub tied_to_next: bool,
}
pub enum DecompositionSource {
UserChosen,
Inferred,
Imported { format: ForeignFormatId },
Propagated { from: EventId },
}
\end{lstlisting}
The structure deliberately mirrors the spelling attachment model
(Section~\ref{sec:pitch:spelling}): same sources, same source-rank
discipline, same pre-pass output model. Unlike spelling, decomposition
precedence is \emph{not configurable} (ratified Pass~12): the fixed
default source order \texttt{UserChosen} $>$ \texttt{Imported} $>$
\texttt{Propagated} $>$ \texttt{Inferred} applies, with the score's
canonical attachment order as tie-break among equal-rank authored
attachments; \texttt{DecompositionAttachment} carries no
\texttt{priority} field and the graph carries no
\texttt{DecompositionPrecedence} configuration. Making precedence
configurable would add a canonical \texttt{Score} field (a
schema-major change under the Binary Format companion's evolution
rule) for which no consumer exists; the decision may be revisited at
a future schema major if a use case appears.
\subsection{The Decomposition Pre-Pass}
When a score is materialized, the decomposition pre-pass computes
inferred decompositions, reported as derived annotations; an authored
higher-precedence attachment overrides the inferred value in the
resolved annotation. An authored attachment targeting an event the
pre-pass produces \emph{no} inferred decomposition for (ungriddable,
non-metric, or an inapplicable event kind) surfaces as the resolved
annotation on its own, per
Requirement~\ref{req:pitch:authored-uninferred} --- authoring is how
a user notates exactly what the algorithm cannot infer. The
algorithm is, in outline:
\begin{enumerate}
\item For each event in time order within a voice and measure,
examine the sounding duration and the current beat-group context.
\item Decompose the duration into the longest leading note value
that fits within the current beat group without crossing a stronger
accent boundary.
\item If the duration is not exhausted, produce a tie and continue
decomposition from the next beat-group boundary.
\item Augmentation dots are applied where they shorten the
decomposition without crossing an accent boundary that the dot would
obscure.
\item Tuplet membership is recorded for events that fall within a
tuplet group (Section~\ref{sec:time:tuplets}).
\end{enumerate}
\begin{requirement}
\label{req:time:decomposition-determinism}
The decomposition pre-pass \MUST{} be deterministic: given
identical materialized graph, configuration, and algorithm
version, it \MUST{} produce identical decompositions. Its output
is a derived annotation, never stored graph state. Implementations
\MAY{} cache and \MAY{} recompute incrementally (re-running only
the affected voice and measure, with propagation only as far as
the context is altered); neither may be observable in the
annotations produced.
\end{requirement}
\begin{requirement}
\label{req:time:decomposition-algorithm}
\textbf{Decomposition algorithm disposition (ratified Pass 12).}
The decomposition pre-pass receives the \emph{profile-declared
by versioned identifier} disposition of
Appendix~\ref{app:determinism} Section~\ref{sec:det:open}. The
reserved identifier \texttt{DecompositionAlgorithmId}
\texttt{"default"} denotes, at version~1, the integer-grid
metric splitter, whose scope bounds are part of its normative
definition:
\begin{itemize}
\item a single governing meter per region --- a mid-region
meter change reduces cleanly into the region's grid (the
Operation Catalog pins those semantics), but derived
notation honours only the first governing meter until a
wider algorithm version lands;
\item the region origin is assumed to fall on a barline
(anacrusis/pickup handling deferred);
\item compound-meter beat grouping uses the dyadic default;
\item tuplet nesting and cross-beat tuplet members are out of
scope;
\item at most one augmentation dot
(\texttt{MAX\_DOTS}~$=$~1); a double-dotted value is
written as tied components --- correct, if not the most
compact.
\end{itemize}
A wider algorithm is a \emph{version bump}, which
deterministically invalidates derived output under the
derived-annotation model --- no state migration. Style-choice
selection among multiple valid decompositions (e.g.,
$\frac{3}{8}$ in $\frac{4}{4}$ as a dotted quarter versus a
quarter tied to an eighth) is fixed by the versioned algorithm;
user-configurable alternate rule sets, if ever wanted, are new
registered identifiers. A profile requesting any identifier
other than a registered one \MUST{} error; implementations
\MUSTNOT{} silently substitute.
\end{requirement}
\section{Tuplets as Grouping Objects}
\label{sec:time:tuplets}
A tuplet is a grouping object in the score graph that records
notational intent: that a span of events forms a triplet, quintuplet,
or other irregular grouping with a visible bracket and number. Tuplets
do not modify the sounding durations of their members; sounding
durations are always concrete rationals.
\begin{lstlisting}[language=Rust]
pub struct Tuplet {
pub id: TupletId,
/// The actual:notated ratio. A triplet is 3:2; a quintuplet is
/// 5:4; an irregular grouping like 7:4 has ratio 7 over 4.
pub ratio: TupletRatio,
/// Range of events in this tuplet, by event ID.
pub members: Vec<EventId>,
/// Display: bracket style, number placement, parentheses, etc.
pub display: TupletDisplay,
/// Nesting: if this tuplet is itself inside another, the parent.
pub parent: Option<TupletId>,
}
pub struct TupletRatio {
// Fields are private: a TupletRatio is constructed only through the
// checked `new`, so a degenerate ratio is never representable (see
// the requirement below). The fields are read via `actual()`/`notated()`.
actual: u32,
notated: u32,
}
impl TupletRatio {
/// Returns `None` for a degenerate ratio (either term zero, or
/// `actual == notated`); otherwise the well-formed ratio.
pub fn new(actual: u32, notated: u32) -> Option<TupletRatio> { /* checked */ }
pub fn actual(&self) -> u32 { /* ... */ }
pub fn notated(&self) -> u32 { /* ... */ }
}
\end{lstlisting}
\begin{requirement}
\label{req:time:tuplet-ratio-construction}
A \texttt{TupletRatio} is \emph{degenerate} if either term is zero
or if \texttt{actual == notated} (a ratio that expresses no
augmentation or diminution). Degenerate ratios \MUST{} be rejected
at construction: a conforming constructor of \texttt{TupletRatio}
(and therefore of any \texttt{Tuplet}) \MUST{} fail rather than
produce a value with \texttt{actual == 0}, \texttt{notated == 0}, or
\texttt{actual == notated}. This is a construction-time rejection,
not a runtime graph invariant (Section~\ref{sec:graph:invariants}):
a degenerate ratio is never a representable graph state, so no
invariant restores it after the fact.
\end{requirement}
\subsection{Tuplet Nesting}
Tuplets \MAY{} nest to arbitrary depth. The sounding duration of an
event inside nested tuplets is computed directly from its rational
duration, not from the tuplet ratios; the ratios are notational and
analytical.
\begin{rationale}
Storing sounding durations directly (rather than as base-value times
a stack of tuplet ratios) keeps the time model regular: every event
has a single rational duration, full stop. Moving a tuplet, splitting
it, or deleting it never alters the durations of its members. Tuplets
thus become a pure annotation layer, which dramatically simplifies
edit operations.
\end{rationale}
\subsection{Tuplet Consistency}
\begin{requirement}
\label{req:time:tuplet-duration-consistency}
A tuplet's notated ratio \MUST{} be consistent with the sounding
durations of its members: the sum of member durations \MUST{} equal
the duration that the tuplet's notated value indicates when scaled
by the ratio. For example, a 3:2 eighth-note triplet of three members
has total sounding duration $\frac{1}{4}$; the sum of the three
member durations \MUST{} equal $\frac{1}{4}$.
This is a structural invariant. Edit operations that would violate
it \MUST{} either adjust member durations or be rejected. The
semantic operations chapter specifies which.
\end{requirement}
\section{Region Time Models}
\label{sec:time:regions}
A region of the score declares one of three time models. The declared
model determines how positions within the region are interpreted,
displayed, and played back.
\begin{lstlisting}[language=Rust]
pub enum RegionTimeModel {
/// Metric time: measures, beats, exact rational positions.
/// The dominant model for Western notated music.
Metric(MetricTimeModel),
/// Proportional time: events placed in space, where horizontal
/// position is wall-clock time. No measures, no beats.
Proportional(ProportionalTimeModel),
/// Aleatoric time: events with partial or unspecified ordering.
Aleatoric(AleatoricTimeModel),
}
\end{lstlisting}
\subsection{Metric Time}
\begin{lstlisting}[language=Rust]
pub struct MetricTimeModel {
/// Sequence of meter changes within this region.
pub meters: Vec<MeterChange>,
/// Tempo map for this region. May inherit from the score.
pub tempo: TempoMapReference,
}
\end{lstlisting}
Metric regions have measures, beats, and time signatures as described
in Sections~\ref{sec:time:meter} and~\ref{sec:time:tempomap}. Positions
within them are \texttt{MusicalPosition} values.
\subsection{Proportional Time}
\begin{lstlisting}[language=Rust]
pub struct ProportionalTimeModel {
/// Total wall-clock duration of the region.
pub duration: WallClockDuration,
/// Horizontal space per second. Drives the engraver's spacing.
/// Stored as a layout hint; the actual spacing is decided by the
/// constraint solver.
pub space_per_second: SpaceUnit,
/// Optional reference grid markings (vertical lines every N
/// seconds, for example) for performer reference.
pub grid: Option<ProportionalGrid>,
}
\end{lstlisting}
Events in proportional regions carry \texttt{WallClockTime} positions
directly. There are no measures, no beats, no rational durations.
Notational duration is computed from wall-clock duration by the
notational decomposition pre-pass using a proportional-mode rule set,
which generally produces unbeamed events with explicit duration
notations or graphic-duration symbols.
\begin{requirement}
\label{req:time:proportional-region-time}
Events in a proportional region \MUST{} use \texttt{WallClockTime}
for positions and \texttt{WallClockDuration} for durations. The
tempo map \MUSTNOT{} be applied to convert proportional-region
positions; they are already in wall-clock time.
\end{requirement}
\subsection{Aleatoric Time}
\begin{lstlisting}[language=Rust]
pub struct AleatoricTimeModel {
/// Ordering constraints among events: a directed acyclic graph
/// where an edge a -> b means "a precedes b."
pub ordering: EventOrderingDAG,
/// Anchoring discipline: declares what event coordinate kinds
/// this region permits and whether duration-bound kinds may
/// be mixed. See Section~\ref{sec:time:aleatoric-anchoring}.
pub anchoring: AleatoricAnchoringDiscipline,
/// Optional per-event interval bounds. An event with start
/// bounds [t1, t2] may begin at any time in that window.
/// Bound kinds are constrained by the anchoring discipline.
pub bounds: HashMap<EventId, EventBounds>,
/// Approximate or maximum total duration, for layout.
pub duration_hint: WallClockDuration,
}
pub enum AleatoricAnchoringDiscipline {
/// Events in this region carry musical-time coordinates only.
Musical,
/// Events in this region carry wall-clock coordinates only.
WallClock,
/// Events may carry either kind, but each event's position and
/// duration kinds must agree with each other. Mixed-kind
/// duration bounds are not permitted.
EitherPerEvent,
/// Events may carry either kind freely, and duration bounds
/// may mix kinds (one bound musical, the other wall-clock).
/// Used for free-time scores synchronized to external media.
FreelyMixed,
}
pub struct EventBounds {
pub start: Option<TimeBounds>,
pub end: Option<TimeBounds>,
}
pub enum TimeBounds {
MusicalRange { min: MusicalPosition, max: MusicalPosition },
WallClockRange { min: WallClockTime, max: WallClockTime },
Unbounded,
}
\end{lstlisting}
Aleatoric regions support both unordered event collections (any
permutation of events is valid) and time-bracket notation (events with
specified start and end windows, à la Cage's later notation). The DAG
encoding admits both as cases.
\subsubsection{Anchoring Discipline}
\label{sec:time:aleatoric-anchoring}
\label{sec:graph:aleatoric-discipline}
\begin{requirement}
\label{req:time:aleatoric-anchoring-discipline}
Every aleatoric region \MUST{} declare an
\texttt{AleatoricAnchoringDiscipline}. Events within the region
\MUST{} carry coordinate kinds consistent with the discipline:
\begin{itemize}
\item \texttt{Musical}: events use \texttt{EventPosition::Musical}
and \texttt{EventDuration::Musical} (or
\texttt{EventDuration::Indeterminate} whose bounds are musical).
\item \texttt{WallClock}: events use
\texttt{EventPosition::WallClock} and
\texttt{EventDuration::WallClock} (or
\texttt{EventDuration::Indeterminate} whose bounds are
wall-clock).
\item \texttt{EitherPerEvent}: events choose per-event, but an
event's position and duration kinds \MUST{} agree, and
\texttt{DurationBounds} \MUST{} use a single
\texttt{ConcreteDuration} variant.
\item \texttt{FreelyMixed}: no agreement required;
\texttt{DurationBounds} \MAY{} mix kinds.
\end{itemize}
Implementations \MUST{} reject events whose coordinate kinds
violate the region's declared discipline.
\end{requirement}
\begin{requirement}
\label{req:time:ordering-dag-acyclic}
The ordering DAG \MUST{} be acyclic. Cycles \MUST{} be rejected at
construction. Two events with no path between them in the DAG are
unordered.
\end{requirement}
\begin{rationale}
A DAG with optional interval bounds expresses every aleatoric form
the project intends to support: unordered collections (empty DAG),
partial orderings (sparse DAG), strict orderings (chain DAG), and
time-brackets (DAG plus bounds). Higher-level constructs
(mobile-form notation, statistical scores) can be built atop this
primitive in later specifications without altering the core.
\end{rationale}
\subsection{Concurrent Time Models}
Different staves in the same score \MAY{} declare different time
models. A metric solo line over a proportional aleatoric texture, for
example, is expressible: each staff's region declares its own model.
Synchronization between concurrent time models is by wall-clock time
or by explicit cue points (see Chapter~\ref{ch:graph}).
\section{The Notion of ``Now''}
\label{sec:time:now}
The core does not define playback state; it does not have a notion of
``current position'' or ``transport time.'' Such state belongs to the
audio engine, which consumes the core's time model. The core defines
only the static time structure of the score.
\section{Forward References}
\begin{itemize}
\item Event identifiers (\texttt{EventId}), measure identifiers
(\texttt{MeasureId}), and region identifiers (\texttt{RegionId})
are defined in Chapter~\ref{ch:graph}.
\item The full event taxonomy (pitched, unpitched, rest, indeterminate,
trajectory) is defined in Chapter~\ref{ch:graph}.
\item Re-anchoring rules for time anchors when their targets are
deleted are defined per-operation in Chapter~\ref{ch:semops}.
\item \texttt{SpaceUnit} is defined normatively in
Chapter~\ref{ch:graph} as of schema major~2 (it entered canonical
state through \texttt{StaffLineConfiguration}).
\texttt{TupletDisplay} and other purely display-oriented types are
defined in Chapter~\ref{ch:layout-ir}.
\end{itemize}
% ===========================================================================
\chapter{Tuning Systems and Pitch Spaces}
\label{ch:tuning}
This chapter specifies the three intertwined registries that complete
the primitives layer: \emph{pitch spaces} (the analytical universes in
which scale-position arithmetic lives), \emph{accidental registries}
(the catalogs of position-modifying glyphs), and \emph{tuning systems}
(the frequency-resolution mechanisms). It also specifies the
hierarchical resolution rules that determine which tuning applies to
each pitch in a score.
\section{Design Principles}
\label{sec:tuning:principles}
\begin{description}
\item[Pitch space and tuning system are distinct.] A pitch space
defines what pitches exist and how they relate analytically. A
tuning system defines what frequencies those pitches produce.
The two are independent: a single pitch space (e.g., CMN) admits
many tuning systems (12-TET, meantone, well-temperaments, just
intonation, Pythagorean); a single tuning structure may be anchored
at different reference frequencies.
\item[Accidentals belong to pitch spaces.] An accidental defines a
\emph{position modification} within a pitch space. The tuning system
handles the frequency consequences of that modification. The chromatic
semitone implied by a sharp has different cent-values in 12-TET,
quarter-comma meantone, and Pythagorean tuning, but it is the same
accidental denoting the same position.
\item[Reference pitch is score-level, not tuning-level.] A score
declares a reference pitch (position plus frequency); the tuning
system defines structure relative to that reference. ``12-TET at
A=440'' and ``12-TET at A=415'' share the same tuning system; the
reference differs.
\item[Resolution is hierarchical.] Tuning, pitch space, and accidental
registry are resolved by walking outward from the pitch: pitch,
voice, staff, region, score. Inheritance markers permit any level
to defer to the enclosing scope.
\item[Built-in defaults, extensible by users.] The core ships a
baseline catalog of pitch spaces, tuning systems, and accidental
registries sufficient for the common 95\% case. Scores and grammar
plugins extend these without forking.
\item[SMuFL primary, custom fallback.] Glyph references default to
SMuFL codepoints. Custom glyphs are supported as fallback. SMuFL
version is recorded per score.
\end{description}
\section{Pitch Spaces}
\label{sec:tuning:space}
A pitch space is the analytical universe in which scale-degree and
interval relationships are defined. It is the algebra; the tuning
system is the physics.
\begin{lstlisting}[language=Rust]
pub struct PitchSpace {
pub id: PitchSpaceId,
/// Human-readable name (e.g., "CMN 12-tone chromatic").
pub name: String,
/// Optional description and provenance notes.
pub description: Option<String>,
/// What positions exist and how they are structured.
pub positions: PositionStructure,
/// How intervals between positions are defined.
pub interval_algebra: IntervalAlgebra,
/// The accidental registry valid in this pitch space.
pub accidental_registry: AccidentalRegistryId,
/// The nominal registry: named position-letters
/// (e.g., A-G for CMN).
pub nominal_registry: NominalRegistryId,
/// Rules governing how pitches transpose within this space.
pub transposition: TranspositionBehavior,
/// Default rules for the spelling pre-pass when this pitch space
/// is active.
pub spelling_rules: SpellingRuleSet,
}
\end{lstlisting}
\subsection{Position Structure}
\label{sec:tuning:positions}
The \texttt{PositionStructure} declares the shape of the pitch space.
Three families cover the vast majority of useful cases; a fourth is the
escape hatch for grammars defined entirely by plugin.
\begin{lstlisting}[language=Rust]
pub enum PositionStructure {
/// Chromatic: a fixed number of equally-numbered positions per
/// octave, with no hierarchical diatonic substructure.
/// Includes EDOs and serial 12-tone.
Chromatic { positions_per_octave: u16 },
/// Diatonic over chromatic: a smaller set of named nominals
/// (e.g., A-G for CMN) plus accidental modifications reaching
/// the full chromatic range. CMN's structure.
DiatonicOverChromatic {
nominals_per_octave: u16,
chromatic_positions_per_octave: u16,
/// Mapping from each nominal to its position in the
/// chromatic layer (e.g., C=0, D=2, E=4, F=5, G=7, A=9, B=11
/// for CMN).
nominal_to_chromatic: Vec<u16>,
},
/// Just intonation lattice: positions defined by exact rational
/// ratios from a tonic, organized along prime-axis dimensions
/// (3-limit, 5-limit, 7-limit, etc.).
JiLattice {
limit: u8, // prime limit
generators: Vec<JiRatio>, // one per prime dimension
},
/// Grammar-defined: structure is opaque to the core and resolved
/// by a grammar plugin. Used for maqam, gamelan, raga frameworks,
/// and other systems whose position structure is not flat or
/// hierarchical in the above senses.
Registered(PositionStructureRegistryId),
}
\end{lstlisting}
\begin{requirement}
\label{req:tuning:diatonic-chromatic-mapping}
The \texttt{DiatonicOverChromatic} variant \MUST{} have a
\texttt{nominal\_to\_chromatic} mapping of length equal to
\texttt{nominals\_per\_octave}, with each entry strictly less than
\texttt{chromatic\_positions\_per\_octave}. The mapping \MUST{} be
strictly increasing.
\end{requirement}
\subsection{Interval Algebra}
An interval algebra defines how distances between positions are
computed and how transposition is realized. For chromatic spaces the
algebra is integer arithmetic modulo the position count; for
diatonic-over-chromatic the algebra distinguishes diatonic from
chromatic intervals (a diatonic third may correspond to three or four
chromatic semitones depending on quality).
\begin{lstlisting}[language=Rust]
pub enum IntervalAlgebra {
/// Single-axis integer arithmetic. Two positions are subtracted
/// to yield a single integer interval.
Chromatic,
/// Two-axis arithmetic distinguishing diatonic and chromatic
/// components. CMN's algebra: a major third is (3 diatonic steps,
/// 4 chromatic steps); a diminished fourth is (3 diatonic steps,
/// 4 chromatic steps) -- distinguishable from the major third only
/// by spelling intent.
DiatonicChromatic,
/// Multi-axis JI lattice arithmetic.
JiVector { dimensions: u8 },
/// Grammar-defined algebra.
Registered(IntervalAlgebraRegistryId),
}
\end{lstlisting}
\subsection{Nominal Registry}
A nominal registry catalogs the named position-letters of a pitch
space. For CMN this is the seven-element set $\{C, D, E, F, G, A, B\}$.
For 12-tone serial music the nominal registry may simply be the
integers $0$ through $11$. For maqam the nominals are the names of
maqam degrees.
\begin{lstlisting}[language=Rust]
pub struct NominalRegistry {
pub id: NominalRegistryId,
pub nominals: Vec<NominalDefinition>,
}
pub struct NominalDefinition {
pub id: NominalId,
/// Canonical name.
pub name: String,
/// Display glyph, typically a letter or symbol.
pub display: GlyphReference,
/// Default position in the pitch space's chromatic layer, if
/// applicable.
pub default_position: Option<u16>,
}
\end{lstlisting}
\subsection{Transposition Behavior}
Transposition behavior governs how scale positions move under interval
operations and how spellings follow.
\begin{lstlisting}[language=Rust]
pub enum TranspositionBehavior {
/// Diatonic transposition: shift by N diatonic steps within a
/// chosen key. Accidentals adjust to fit the destination key.
Diatonic,
/// Chromatic transposition: shift by N chromatic steps. Spellings
/// follow rules of the SpellingRuleSet.
Chromatic,
/// Compound: support both, parameterized at the operation site.
Compound,
/// Grammar-defined.
Registered(TranspositionRegistryId),
}
\end{lstlisting}
\subsection{Spelling Rule Sets}
A pitch space carries a default spelling rule set that the spelling
pre-pass (Section~\ref{sec:pitch:prepass}) consults when inferring
spellings for unspelled pitches.
\begin{lstlisting}[language=Rust]
pub struct SpellingRuleSet {
pub id: SpellingRuleSetId,
pub name: String,
/// Algorithmic family this rule set belongs to. The specific
/// algorithm is referenced by id and resolved against the
/// implementation's registered spelling algorithms.
pub algorithm: SpellingAlgorithmId,
/// Algorithm-specific parameters.
pub parameters: SpellingParameters,
}
\end{lstlisting}
\begin{openquestion}
The default spelling algorithm is now ratified
(Requirement~\ref{req:pitch:spelling-algorithm}:
\texttt{"default"} = Temperley-style line-of-fifths preference,
version~1). Still open: the catalog of \emph{additional}
registered spelling algorithms (candidates include
Longuet-Higgins line-of-fifths distance, Cambouropoulos
pitch-spelling, and Meredith PS13) and their parameter schemas,
which are normative once registered.
\end{openquestion}
\section{Accidental Registries}
\label{sec:tuning:accidentals}
An accidental registry catalogs the position-modifying glyphs valid
within a pitch space. Each accidental defines a glyph, a position
modification, and engraving metadata.
\begin{lstlisting}[language=Rust]
pub struct AccidentalRegistry {
pub id: AccidentalRegistryId,
pub name: String,
pub accidentals: Vec<AccidentalDefinition>,
/// Whether scores may extend this registry with score-local
/// accidentals. Most registries permit this; some (e.g., a
/// closed conformance profile) may not.
pub extensible: bool,
}
pub struct AccidentalDefinition {
pub id: AccidentalId,
/// Canonical name. Used for serialization, accessibility, and
/// foreign-format export.
pub name: String,
/// Glyph reference, typically SMuFL.
pub glyph: GlyphReference,
/// The position modification this accidental applies.
pub modification: PitchSpaceModification,
/// Engraving metadata.
pub engraving: AccidentalEngraving,
/// Combination behavior with other accidentals.
pub combination: AccidentalCombination,
}
\end{lstlisting}
\subsection{Pitch Space Modifications}
\begin{lstlisting}[language=Rust]
pub enum PitchSpaceModification {
/// CMN-style integer offset in chromatic steps of the enclosing
/// pitch space (`req:pitch:alteration-unit`).
CmnChromatic(i8),
/// Integer step offset in an EDO position space.
EdoSteps(i16),
/// Modification expressed as an exact rational ratio. Used for
/// JI accidentals such as the HEJI syntonic-comma symbols
/// (81/80) and septimal-comma symbols (64/63).
JiRatio { numerator: i32, denominator: NonZeroU32 },
/// Modification expressed in cents. Used for Sagittal-style
/// precise microtonal accidentals.
Cents(f64),
/// Modification defined by a grammar plugin.
Registered(ModificationRegistryId),
}
\end{lstlisting}
\begin{requirement}
\label{req:tuning:accidental-modification-compatibility}
An accidental's modification \MUST{} be expressible in the interval
algebra of every pitch space that references its registry. A
\texttt{CmnChromatic} modification is valid only in spaces with
\texttt{DiatonicChromatic} or compatible algebra; an \texttt{EdoSteps}
modification is valid only in \texttt{Chromatic} or \texttt{Registered}
spaces with appropriate compatibility. Implementations \MUST{} reject
scores referencing an accidental in a space whose algebra does not
admit the modification.
\end{requirement}
\subsection{Accidental Engraving Metadata}
\texttt{AccidentalEngraving} is reachable from canonical score state ---
via \texttt{AccidentalDefinition} inside
\texttt{ScoreAccidentalExtensions}, which hangs off
\texttt{ScoreTuningContext} --- so every field it carries must itself be
canonical. Its bounding box therefore cannot reuse
Chapter~\ref{ch:layout-ir}'s \texttt{BoundingBox}, which is built on
\texttt{StaffSpace} (single-precision \texttt{f32}) and belongs to the
non-canonical resolved-layout cache. This chapter defines its own box
type over \texttt{SpaceUnit} instead:
\begin{lstlisting}[language=Rust]
/// A bounding box over canonical space units, used for engraving
/// metadata that lives in canonical score state. Distinct from
/// Chapter 7's `BoundingBox` (built on `StaffSpace`, single precision,
/// for the non-canonical resolved-layout cache): this type's edges are
/// `SpaceUnit` (`CanonicalF64`), per
/// `req:determinism:canonical-floating-point`.
pub struct EngravingBoundingBox {
pub left: SpaceUnit,
pub right: SpaceUnit,
pub top: SpaceUnit,
pub bottom: SpaceUnit,
}
\end{lstlisting}
\begin{lstlisting}[language=Rust]
pub struct AccidentalEngraving {
/// Bounding box in canonical space units, relative to the glyph's
/// anchor point.
pub bounding_box: EngravingBoundingBox,
/// Where the glyph attaches to the note: typically the geometric
/// center or a custom anchor for compound glyphs.
pub anchor: AnchorPoint,
/// Advance width for horizontal spacing computations.
pub advance_width: SpaceUnit,
/// Stacking order when multiple accidentals attach to one note.
/// Lower values are placed closer to the notehead.
pub stacking_order: i32,
/// Whether this glyph should be drawn with parentheses by default
/// (e.g., editorial or cautionary accidentals).
pub default_parenthesized: bool,
}
\end{lstlisting}
\begin{rationale}
Engraving metadata that lives in canonical state must carry canonical
precision. Chapter~\ref{ch:layout-ir}'s coordinates are
single-precision by requirement
(Requirement~\ref{req:layoutir:staff-space-coordinates}) and reach the
wire in the \texttt{LayoutCache}, which the Binary Format companion
defines as an independent \emph{non-canonical} chunk --- so
\texttt{StaffSpace} is correct there. But
\texttt{accidental\_extensions} lives on
\texttt{ScoreTuningContext}, which \emph{is} canonical, and
Requirement~\ref{req:determinism:canonical-floating-point} requires
canonical stored floats to be finite IEEE~754 binary64.
\texttt{EngravingBoundingBox} makes \texttt{AccidentalEngraving}
wholly core-typed, over the same \texttt{SpaceUnit} that
\texttt{advance\_width} already uses, and removes what would
otherwise be a backwards dependency from this chapter onto
Chapter~\ref{ch:layout-ir}. Chapter~\ref{ch:layout-ir}'s
\texttt{BoundingBox} is unaffected and remains the correct type for
resolved-layout coordinates.
\end{rationale}
\subsection{Combination Behavior}
Most accidentals do not combine; a note carries one accidental at a
time. Some systems (HEJI, certain microtonal notations) permit stacking
multiple accidental glyphs on a single note to express compound
modifications.
\begin{lstlisting}[language=Rust]
pub enum AccidentalCombination {
/// Stands alone; replaces any prior accidental on the same note.
Solitary,
/// May stack with members of the listed compatibility groups.
/// Stacking order is determined by the engraving metadata.
Stacking { compatible_groups: Vec<AccidentalGroupId> },
}
\end{lstlisting}
\subsection{Score-Local Extensions}
A score \MAY{} extend a referenced accidental registry with additional
accidental definitions, provided the registry's \texttt{extensible}
flag is true. Extensions are stored on the score and override or
augment the base registry during resolution.
\begin{lstlisting}[language=Rust]
pub struct ScoreAccidentalExtensions {
pub base: AccidentalRegistryId,
pub additions: Vec<AccidentalDefinition>,
pub overrides: Vec<AccidentalDefinition>,
}
\end{lstlisting}
\section{Glyph References and SMuFL}
\label{sec:tuning:smufl}
The Standard Music Font Layout (SMuFL) is the normative source for
glyph references in the core. Every standard accidental, notehead,
articulation, dynamic, and ornament has a SMuFL codepoint. Non-standard
glyphs are supported via custom glyph references.
\begin{lstlisting}[language=Rust]
pub enum GlyphReference {
/// SMuFL codepoint, resolved against the active SMuFL font.
Smufl(u32),
/// Custom glyph defined by the score or a plugin.
Custom(CustomGlyphId),
/// Composite glyph: multiple glyph references rendered as a
/// single accidental. Used for compound HEJI symbols and similar.
Composite(Vec<GlyphReference>),
}
\end{lstlisting}
\subsection{SMuFL Versioning}
\begin{requirement}
\label{req:tuning:smufl-version-fallback}
Every score \MUST{} declare the SMuFL version it targets. Resolving a
SMuFL glyph reference whose codepoint is not present in the active
font's SMuFL version \MUST{} produce a deterministic fallback (the
reference SMuFL substitution glyph, or a clearly-marked missing-glyph
indicator); it \MUSTNOT{} silently fail.
\end{requirement}
\begin{lstlisting}[language=Rust]
pub struct SmuflVersion {
pub major: u16,
/// The fractional part, normalized to hundredths.
pub minor_centi: u16,
}
\end{lstlisting}
A SMuFL version's fractional part is stored \emph{normalized to
hundredths} in \texttt{minor\_centi}, not as the literal digits that
follow the decimal point: 1.12 $\to$ \tablenums{12}, 1.18 $\to$
\tablenums{18}, 1.20 $\to$ \tablenums{20}, 1.3 $\to$ \tablenums{30},
1.4 $\to$ \tablenums{40}. SMuFL versions are decimal fractions, and
this repository's published minor releases did not arrive in digit
order: 1.12 and 1.18 shipped before 1.3 and 1.4. A literal-digit
encoding (\texttt{minor: 3} for 1.3, \texttt{minor: 12} for 1.12) is
\textbf{non-conforming} because the derived ordering on
\texttt{(major, minor)} then sorts 1.3 and 1.4 \emph{before} 1.12,
backwards versus the real release history. The hundredths
normalization also collapses the 1.2/1.20 spelling ambiguity: both
denote the same release and both normalize to \tablenums{20}. This is
the single \texttt{SmuflVersion} type used throughout the
specification --- by \texttt{SmuflVersionRequirement} below and, in
Chapter~\ref{ch:layout-ir}, by \texttt{GlyphCatalog::smufl\_version()}
(Section~\ref{sec:layoutir:catalog}) and \texttt{GlyphCatalogIdentity}
(Section~\ref{sec:layoutir:catalog-identity}).
\begin{rationale}
For reference, SMuFL's minor releases in their real order, each with
its normalized \texttt{minor\_centi} value: 1.12
(\tablenums{12}), 1.18 (\tablenums{18}), 1.20 (\tablenums{20}), 1.3
(\tablenums{30}), 1.4 (\tablenums{40}).
\end{rationale}
\begin{lstlisting}[language=Rust]
pub struct SmuflVersionRequirement {
/// Minimum SMuFL version required by this score.
pub minimum: SmuflVersion,
/// SMuFL version this score was authored against. Used for
/// detecting whether newer glyphs are in use.
pub authored_against: SmuflVersion,
}
\end{lstlisting}
\section{Tuning Systems}
\label{sec:tuning:system}
A tuning system maps positions in a pitch space to frequencies. Given
a reference pitch (position plus frequency) and the system's resolution
rules, every position in the space resolves to a determinate frequency.
\begin{lstlisting}[language=Rust]
pub struct TuningSystem {
pub id: TuningSystemId,
/// Human-readable name.
pub name: String,
/// The pitch space whose positions this tuning resolves.
pub pitch_space: PitchSpaceId,
/// How the tuning resolves positions to frequencies, given a
/// reference pitch. The reference itself is supplied separately
/// (see Section~\ref{sec:tuning:reference}).
pub resolution: TuningResolution,
/// Optional historical or provenance notes.
pub description: Option<String>,
}
\end{lstlisting}
\subsection{Tuning Resolution}
\begin{lstlisting}[language=Rust]
pub enum TuningResolution {
/// N-tone equal temperament: each step is the Nth root of the
/// octave ratio. Includes 12-TET when N=12.
EqualTemperament { divisions_per_octave: u16 },
/// Explicit per-position ratios. Each entry is a ratio relative
/// to the reference position.
PerPositionRatios(Vec<PositionRatio>),
/// Procedural definition: a registered tuning function that
/// computes frequencies from a reference. Historical temperaments
/// (Werckmeister, Vallotti, meantone variants) live here.
Function {
function: TuningFunctionId,
parameters: TuningParameters,
},
/// A base resolution with per-position overrides. Used for
/// split-accidental keyboards and similar irregular tunings.
Overlay {
base: Box<TuningResolution>,
overrides: Vec<PositionOverride>,
},
/// Imported from a tuning file (Scala .scl, MIDI Tuning Standard,
/// etc.). The data is captured at import; the original file is
/// referenced for provenance but is not required at runtime.
Imported {
format: TuningFileFormat,
data: Arc<ImportedTuningData>,
},
/// Adaptive tuning: resolution depends on the harmonic context
/// at the moment of sounding. Used for adaptive JI and similar
/// systems.
Adaptive {
function: AdaptiveTuningFunctionId,
parameters: AdaptiveTuningParameters,
},
}
\end{lstlisting}
\subsection{The Resolution Contract}
\begin{requirement}
\label{req:tuning:tuning-resolution-determinism}
Every tuning resolution \MUST{} expose a deterministic function from
pitch-space position (plus, where applicable, harmonic context) to
frequency in Hertz, given a reference pitch. Determinism \MUST{}
hold across runs and platforms; numerical computations \MUSTNOT{}
depend on floating-point rounding modes.
\end{requirement}
\subsection{Adaptive Tuning}
Adaptive tuning systems compute frequency from position \emph{plus
harmonic context}. A registered adaptive tuning function \MAY{}
resolve position per sonority --- for example, tuning a given E
differently depending on whether it sounds as the major third of a C
chord, the perfect fifth of an A chord, or a passing tone --- subject
only to the purity constraint below
(Requirement~\ref{req:tuning:adaptive-tuning-purity}). The built-in
\texttt{ji-adaptive-5limit}, at its pinned version~1
(Requirement~\ref{req:tuning:adaptive-default-version}), does not: it
consumes only the harmonic context's tonal centre.
\begin{lstlisting}[language=Rust]
pub struct HarmonicContext {
/// Concurrently sounding pitches at the moment of resolution.
pub concurrent: Vec<PitchId>,
/// Recently sounding pitches, with decay weighting.
pub recent: Vec<(PitchId, f64)>,
/// The active key or modal center, if known.
pub key_context: Option<KeyContext>,
/// User-supplied context hints attached to the score.
pub hints: Vec<ContextHint>,
}
\end{lstlisting}
The harmonic context is constructed by the audio engine (or by analysis
tooling) and passed to the tuning resolution function. Static tuning
systems ignore it; adaptive tuning systems consume it.
\begin{requirement}
\label{req:tuning:adaptive-tuning-purity}
Adaptive tuning resolution \MUST{} be a pure function of position
and harmonic context. Implementations \MAY{} cache resolution
results, but \MUST{} invalidate caches when the harmonic context
changes.
\end{requirement}
\begin{requirement}
\label{req:tuning:adaptive-default-version}
\textbf{The built-in adaptive function, version 1.} The catalog
identifier \texttt{ji-adaptive-5limit} resolves to
\texttt{TuningResolution::Adaptive} with function identity
\texttt{"default-v1"} --- the version is part of the
machine-visible identifier string, not prose beside it. Version~1
is a pure function of (position, anchor pitch class):
\begin{itemize}
\item the position resolves through the construction of
Requirement~\ref{req:tuning:ji-static-construction}, transposed
so the anchor takes the role of $1/1$;
\item the anchor is the tonal centre supplied by the harmonic
context (Requirement~\ref{req:tuning:adaptive-anchor-derivation}
where that context is score-graph-derived); C (chromatic
position~0) when no tonal centre is supplied;
\item \texttt{concurrent}, \texttt{recent}, \texttt{hints},
\texttt{parameters}, and mode are \emph{ignored} by version~1,
exactly as \texttt{"default"} version~1 of
Requirement~\ref{req:pitch:spelling-algorithm} ignores the
context inputs it does not consult: consuming any of them is a
new version, not a silent behaviour change under the same
identity;
\item every result derives from the anchor and the reference
pitch alone. No adjustment is ever carried forward from a
previous resolution, so the construction is comma-drift-free by
shape, not by a correction step.
\end{itemize}
An unregistered or unknown \texttt{AdaptiveTuningFunctionId}
\MUST{} be a hard error; there is no silent fallback.
\texttt{AdaptiveTuningFunctionId} remains an extension point and
other functions \MAY{} be registered, but the built-in
\texttt{ji-adaptive-5limit} is bound to \texttt{"default-v1"} and
that binding \MUSTNOT{} be overridden.
\end{requirement}
\begin{requirement}
\label{req:tuning:adaptive-anchor-derivation}
Where a \texttt{HarmonicContext}'s tonal centre is derived from a
score graph, the anchor pitch class \MUST{} be computed from the
prevailing key signature as
\[
\texttt{anchor\_pc} = (7 \times \texttt{fifths}) \bmod 12,
\]
reading \texttt{fifths} (\texttt{KeySignature}, Chapter~\ref{ch:graph})
as the major tonic; mode is not consulted, so a signature of 0
anchors at C whether the prevailing key is C major or A minor. The
\emph{prevailing} key signature is the one on the staff containing
the pitch being resolved, taken from that staff's
\texttt{key\_sequence} (\texttt{StaffInstance::key\_sequence},
Chapter~\ref{ch:graph}) at the latest \texttt{KeySignatureChange}
whose anchor is at or before the pitch's onset. Both selections ---
staff and time --- \MUST{} be made this way: without them, a
polytonal or modulating score would resolve differently in two
conforming implementations, which
Requirement~\ref{req:tuning:tuning-resolution-determinism} forbids.
\end{requirement}
\section{Reference Pitch}
\label{sec:tuning:reference}
The reference pitch is the anchor that converts the tuning system's
structural ratios into absolute frequencies. It is a property of the
score, not of the tuning system.
\begin{lstlisting}[language=Rust]
pub struct ReferencePitch {
/// The pitch-space position chosen as the reference.
/// Conventionally A4 in CMN, but configurable.
pub position: PitchSpacePosition,
/// The frequency in Hertz assigned to that position.
/// Conventional values: 440 Hz (modern standard), 415 Hz (Baroque),
/// 430 Hz (early Classical), 442 Hz (some modern orchestras).
pub frequency_hz: f64,
}
\end{lstlisting}
\begin{requirement}
\label{req:tuning:reference-pitch}
Every score \MUST{} declare a reference pitch. The reference pitch
\MUST{} be expressible as a valid position within the score's default
pitch space. When that position is \texttt{Cmn}, the default pitch space
\MUST{} supply its chromatic layer and nominal mapping under
Requirement~\ref{req:pitch:alteration-unit}. The frequency \MUST{} be
positive and finite.
\end{requirement}
\subsection{Multiple Reference Pitches}
A score \MAY{} declare additional reference pitches at the staff or
region level, overriding the score default. This supports
mixed-instrumentation scores in which different instruments use
different concert-pitch references (e.g., a piece for period and modern
instruments with different A frequencies).
\section{Score Tuning Context and Hierarchical Resolution}
\label{sec:tuning:context}
The \emph{score tuning context} is the top-level structure declaring
the tuning environment for a score. Per-scope overrides extend it.
\begin{lstlisting}[language=Rust]
pub struct ScoreTuningContext {
/// Default pitch space for the score.
pub default_pitch_space: PitchSpaceId,
/// Default tuning system. Its pitch_space must match (or be
/// compatible with) the default_pitch_space.
pub default_tuning_system: TuningSystemId,
/// The reference pitch for the score.
pub reference: ReferencePitch,
/// Score-local accidental registry extensions.
pub accidental_extensions: Vec<ScoreAccidentalExtensions>,
/// SMuFL version targeting.
pub smufl: SmuflVersionRequirement,
/// Per-scope overrides, in the order they apply.
pub overrides: Vec<TuningOverride>,
}
pub struct TuningOverride {
pub scope: TuningScope,
pub pitch_space: Option<PitchSpaceId>,
pub tuning_system: Option<TuningSystemId>,
pub reference: Option<ReferencePitch>,
}
pub enum TuningScope {
Voice(VoiceId),
Staff(StaffId),
Region(RegionId),
Range { start: TimeAnchor, end: TimeAnchor, voices: VoiceSelector },
}
\end{lstlisting}
\subsection{Resolution Order}
For a given pitch, resolution proceeds by walking outward through
scopes from most specific to most general until each component
(pitch space, tuning system, reference) is determined.
\begin{requirement}
\label{req:tuning:tuning-resolution-order}
Resolution \MUST{} proceed in the following order, halting at the
first scope that supplies a non-inherited value for each component:
\begin{enumerate}
\item The pitch's own \texttt{AcousticPitch} (an explicit
\texttt{TuningReference::Explicit} or
\texttt{AcousticRealization::AbsoluteHz} short-circuits the
walk).
\item The voice containing the pitch.
\item The staff containing the voice.
\item Each region enclosing the pitch, from innermost to outermost.
\item The score's default tuning context.
\end{enumerate}
Each of the three components (pitch space, tuning system, reference)
is resolved independently along this chain.
\end{requirement}
\begin{rationale}
Independent per-component resolution allows a passage to override
only the parts that differ. A period-instrument inset in a modern
orchestra score may override only the reference frequency (415 Hz
instead of 440 Hz) while inheriting the pitch space and tuning
structure from the parent context. Conversely, a microtonal passage
in an otherwise 12-TET score may override the pitch space and
tuning system but inherit the reference.
\end{rationale}
\subsection{Compatibility Constraints}
\begin{requirement}
\label{req:tuning:tuning-system-compatibility}
When a tuning override is applied, the resolved tuning system's
declared \texttt{pitch\_space} \MUST{} either equal the resolved
pitch space or be declared compatible with it via a registered
compatibility mapping. Implementations \MUST{} reject configurations
in which the resolved pitch space and the resolved tuning system's
pitch space disagree without a compatibility mapping.
\end{requirement}
\section{Built-in Catalog}
\label{sec:tuning:builtin}
The core ships with a baseline catalog of pitch spaces, tuning systems,
and accidental registries sufficient for the common case. The catalog
is normative: a conforming implementation \MUST{} provide these
identifiers with the specified semantics.
\subsection{Built-in Pitch Spaces}
\begin{longtable}{p{3.5cm} p{10cm}}
\toprule
\textbf{Identifier} & \textbf{Description} \\
\midrule
\endhead
\texttt{cmn-12} & Common Music Notation, 7 diatonic nominals (A--G)
over 12 chromatic positions. Standard sharps, flats, double-sharps,
and double-flats. The default for Western tonal and post-tonal music. \\
\texttt{cmn-24} & CMN over 24 chromatic positions with
\texttt{nominal\_to\_chromatic} $=[0,4,8,10,14,18,22]$ and quarter-tone
alteration steps. A flat is $-2$ and a half-flat is $-1$. Used for
Arabic-derived microtonal practice in CMN-compatible notation. \\
\texttt{edo-19} & 19-tone equal division of the octave.
Notable for its closer-to-just major thirds. \\
\texttt{edo-22} & 22-tone equal division. Distinguishes harmonic
intervals collapsed in 12-TET. \\
\texttt{edo-31} & 31-tone equal division. Approximates quarter-comma
meantone almost exactly. \\
\texttt{edo-53} & 53-tone equal division. Excellent approximation
of 5-limit JI. \\
\texttt{edo-72} & 72-tone equal division. Common in research and
contemporary microtonal practice. \\
\texttt{ji-5limit} & 5-limit just intonation lattice with HEJI
accidentals. Three-dimensional; prime basis $\{2, 3, 5\}$ in
ascending order (leading component is the octave, prime 2). \\
\texttt{ji-7limit} & 7-limit JI lattice with HEJI accidentals.
Four-dimensional; prime basis $\{2, 3, 5, 7\}$ in ascending order. \\
\texttt{ji-11limit} & 11-limit JI lattice with HEJI accidentals.
Five-dimensional; prime basis $\{2, 3, 5, 7, 11\}$ in ascending
order. \\
\texttt{maqam-base} & Skeletal maqam framework with quarter-flat
and quarter-sharp \texttt{CmnChromatic} accidentals, denominated by the
space's chromatic layer under Requirement~\ref{req:pitch:alteration-unit}.
Deep coverage of specific maqamat is the province of grammar plugins. \\
\texttt{gamelan-slendro} & Five-tone slendro framework. \\
\texttt{gamelan-pelog} & Seven-tone pelog framework. \\
\bottomrule
\end{longtable}
\begin{rationale}
The three JI spaces include prime 2 in their basis, per
Requirement~\ref{req:pitch:ji-vector-basis}, rather than assuming octave
equivalence. A \texttt{JiVector} is an absolute position, not a pitch
class: without the prime-2 exponent, \texttt{ji-5limit} could not
distinguish C4 from C5. None of these three declares octave-reduced
equivalence, so full register is preserved, as that requirement's
default already states.
\end{rationale}
\paragraph{Conformance note.}
At this revision the default spelling pre-pass is structurally
12-chromatic and therefore reports \texttt{spelling\_unavailable} for
\texttt{cmn-24} positions. This does not prevent storing or engraving an
explicitly authored spelling, nor does it change the space-relative
transposition algebra; automatic 24-chromatic spelling inference is deferred.
\subsection{Built-in Tuning Systems}
\begin{longtable}{p{4cm} p{9.5cm}}
\toprule
\textbf{Identifier} & \textbf{Description} \\
\midrule
\endhead
\texttt{tet-12} & 12-tone equal temperament. The default. \\
\texttt{tet-19} & 19-tone equal temperament. \\
\texttt{tet-22} & 22-tone equal temperament. \\
\texttt{tet-31} & 31-tone equal temperament. \\
\texttt{tet-53} & 53-tone equal temperament. \\
\texttt{tet-72} & 72-tone equal temperament. \\
\texttt{pythagorean} & Pure-fifth (3:2) Pythagorean tuning. \\
\texttt{meantone-1/4-comma} & Quarter-comma meantone (pure major
thirds). \\
\texttt{meantone-1/6-comma} & Sixth-comma meantone. \\
\texttt{meantone-1/5-comma} & Fifth-comma meantone. \\
\texttt{werckmeister-iii} & Werckmeister III well temperament. \\
\texttt{werckmeister-iv} & Werckmeister IV well temperament. \\
\texttt{vallotti} & Vallotti well temperament. \\
\texttt{kirnberger-ii} & Kirnberger II well temperament. \\
\texttt{kirnberger-iii} & Kirnberger III well temperament. \\
\texttt{young-ii} & Thomas Young's second temperament. \\
\texttt{ji-static-5limit-C} & Static 5-limit JI anchored to C tonic. \\
\texttt{ji-static-5limit-G} & Static 5-limit JI anchored to G tonic. \\
\texttt{ji-static-5limit-D} & Static 5-limit JI anchored to D tonic. \\
\texttt{ji-adaptive-5limit} & Adaptive 5-limit JI; resolves based on
harmonic context. \\
\bottomrule
\end{longtable}
\begin{requirement}
\label{req:tuning:builtin-tuning-catalog}
All built-in catalog identifiers \MUST{} resolve in a conforming
implementation. The semantics specified above are normative.
Implementations \MAY{} provide additional built-ins; they \MUSTNOT{}
redefine the semantics of those listed.
\end{requirement}
\subsection{Temperament Constructions}
\label{sec:tuning:temperament-constructions}
This subsection gives the normative construction for each of the ten
non-equal-tempered, non-just-intonation identifiers in the catalog
above. Per the pitch-space/tuning-system independence stated in
Section~\ref{sec:tuning:principles}, all ten are constructions over
\texttt{cmn-12}'s twelve chromatic positions per octave (C,
C$\sharp$/D$\flat$, D, \ldots, B) --- not over the open-ended
\texttt{ji-5limit} lattice pitch space, which is a separate built-in
with its own structure. Cents figures throughout are derived from the
stated ratio or fraction-of-comma, never primary.
\subsubsection{\texttt{pythagorean}}
A chain of eleven pure $3/2$ fifths (twelve notes); every other interval
is stacked fifths reduced by octaves. No comma is tempered anywhere in
the chain --- the entire Pythagorean comma ($531441/524288 \approx
23.460$ cents) is concentrated in the single interval where the chain
does not close.
The twelve-note selection is a choice of \emph{which} eleven consecutive
fifths to keep, not a consequence of ``pure fifths'' alone. The
conventional cut runs E$\flat$--B$\flat$--F--C--G--D--A--E--B--F$\sharp$--C$\sharp$--G$\sharp$
(C at the center), leaving the wolf on the diminished sixth
G$\sharp$--E$\flat$. An equally valid alternative cuts the spiral one
step the other way (chain D$\flat$--\ldots--F$\sharp$, wolf at
F$\sharp$--D$\flat$); either is a legitimate Pythagorean tuning. The
E$\flat$--G$\sharp$ cut is the construction this identifier denotes.
\begin{longtable}{p{1.8cm} p{4.5cm} p{4.5cm}}
\toprule
\textbf{Note} & \textbf{Ratio} & \textbf{Cents (derived)} \\
\midrule
\endhead
C & $1/1$ & 0.000 \\
C$\sharp$ & $2187/2048$ & 113.685 \\
D & $9/8$ & 203.910 \\
E$\flat$ & $32/27$ & 294.135 \\
E & $81/64$ & 407.820 \\
F & $4/3$ & 498.045 \\
F$\sharp$ & $729/512$ & 611.730 \\
G & $3/2$ & 701.955 \\
G$\sharp$ & $6561/4096$ & 815.640 \\
A & $27/16$ & 905.865 \\
B$\flat$ & $16/9$ & 996.090 \\
B & $243/128$ & 1109.775 \\
\bottomrule
\end{longtable}
The closing wolf fifth G$\sharp\to$E$\flat$ (octave-reduced) is
$262144/177147 \approx 678.495$ cents.
\textbf{Closure (non-circulating, by design).} Eleven pure fifths at
701.955~c each, plus the closing wolf at 678.495~c:
$11 \times 701.955 + 678.495 = 8400.000$~c exactly (seven octaves, as
any assignment of twelve distinct pitch classes must sum to by
construction). Relative to twelve
\emph{pure} fifths ($12 \times 701.955 = 8423.460$~c), this construction
is short by exactly $8423.460 - 8400.000 = 23.460$~c --- one Pythagorean
comma, concentrated entirely on the single G$\sharp$--E$\flat$ interval
rather than distributed. That concentration is what makes the tuning
non-circulating: no pair of its twelve notes can be treated as
interchangeable fifths the way a well temperament's can.
\subsubsection{\texttt{meantone-1/4-comma}, \texttt{meantone-1/5-comma}, \texttt{meantone-1/6-comma}}
Meantone is a \emph{regular} temperament: every one of the twelve fifths
in the chain is tempered by the same fraction of the \emph{syntonic}
comma ($81/80 \approx 21.506$ cents) --- not the Pythagorean comma used
by the well temperaments below. All twelve fifths are tempered
uniformly, so there is no ``which fifths'' selection the way there is
for a well temperament; there is still a twelve-note chain-closing
choice, structurally identical to \texttt{pythagorean}'s, and the same
conventional cut places the wolf between G$\sharp$ and E$\flat$.
The tempered fifth is $(3/2) \cdot (80/81)^{1/n}$ for $1/n$-comma. For
$n=4$ this collapses to the exact form $5^{1/4}$; $n=5$ and $n=6$ are
irrational and are given only as derived cents.
\begin{longtable}{p{2.6cm} p{4.2cm} p{2.8cm} p{3.6cm}}
\toprule
\textbf{Variant} & \textbf{Fifth ratio} & \textbf{Cents (derived)} &
\textbf{Narrowing vs.\ pure $3/2$} \\
\midrule
\endhead
1/4-comma & $(3/2)(80/81)^{1/4} = 5^{1/4}$ exactly & 696.578 &
5.377~c $= \tfrac{1}{4} \cdot 21.506$~c \\
1/5-comma & $(3/2)(80/81)^{1/5}$ & 697.654 &
4.301~c $= \tfrac{1}{5} \cdot 21.506$~c \\
1/6-comma & $(3/2)(80/81)^{1/6}$ & 698.371 &
3.584~c $= \tfrac{1}{6} \cdot 21.506$~c \\
\bottomrule
\end{longtable}
Four ascending 1/4-comma fifths, lowered by two octaves, give the just
major third: $4 \times 696.578 - 2 \times 1200 = 386.31$~c, the cents of
$5/4$ --- the defining property of quarter-comma meantone.
\textbf{Closure (non-circulating, by design; all three).} Because all
twelve fifths are tempered equally, the residual wolf is the twelfth,
closing interval forced to bring the total to 8400~c (seven octaves):
\begin{longtable}{p{2.6cm} p{3.4cm} p{3.6cm} p{3.4cm}}
\toprule
\textbf{Variant} & \textbf{11 $\times$ tempered fifth} &
\textbf{Wolf (12th, closing)} & \textbf{Wolf vs.\ pure $3/2$} \\
\midrule
\endhead
1/4-comma & 7662.363~c & 737.637~c & $+35.682$~c (wide) \\
1/5-comma & 7674.191~c & 725.809~c & $+23.854$~c (wide) \\
1/6-comma & 7682.077~c & 717.923~c & $+15.968$~c (wide) \\
\bottomrule
\end{longtable}
All three wolves land on the wide side of a pure fifth --- the opposite
sense from \texttt{pythagorean}'s narrow wolf.
\subsubsection{\texttt{werckmeister-iii}}
Fifths C--G, G--D, D--A, and B--F$\sharp$ are each narrowed by 1/4 of
the \emph{Pythagorean} comma; the remaining eight fifths are pure.
Because only four of twelve fifths are tempered and the pure fifths
absorb none of the comma, all twelve notes remain usable as a tonic:
there is no wolf.
\begin{longtable}{p{9.5cm} p{2.5cm}}
\toprule
\textbf{Fifths} & \textbf{Tempering} \\
\midrule
\endhead
C--G, G--D, D--A, B--F$\sharp$ & narrow, 1/4 Pythagorean comma (4) \\
A--E, E--B, F$\sharp$--C$\sharp$, C$\sharp$--G$\sharp$,
G$\sharp$--E$\flat$, E$\flat$--B$\flat$, B$\flat$--F, F--C &
pure (8) \\
\bottomrule
\end{longtable}
Tempered-fifth cents: $(3/2)/(3^{12}/2^{19})^{1/4} \approx 696.090$
(narrowing 5.865~c $= \tfrac{1}{4} \cdot 23.460$~c).
\textbf{Closure.} $4 \times 5.865 = 23.460$~c exactly --- the chain
closes.
\subsubsection{\texttt{werckmeister-iv}}
Fifths C--G, D--A, E--B, F$\sharp$--C$\sharp$, and B$\flat$--F are each
narrowed by 1/3 of the Pythagorean comma; fifths G$\sharp$--D$\sharp$
and E$\flat$--B$\flat$ are each \emph{widened} by 1/3 of the Pythagorean
comma; the remaining five fifths are pure.
\begin{longtable}{p{9.5cm} p{2.5cm}}
\toprule
\textbf{Fifths} & \textbf{Tempering} \\
\midrule
\endhead
C--G, D--A, E--B, F$\sharp$--C$\sharp$, B$\flat$--F &
narrow, 1/3 Pythagorean comma (5) \\
G$\sharp$--D$\sharp$, E$\flat$--B$\flat$ &
wide, 1/3 Pythagorean comma (2) \\
G--D, A--E, B--F$\sharp$, C$\sharp$--G$\sharp$, F--C & pure (5) \\
\bottomrule
\end{longtable}
Narrow-fifth cents: $(3/2)/(3^{12}/2^{19})^{1/3} \approx 694.135$
(narrowing 7.820~c $= \tfrac{1}{3} \cdot 23.460$~c). Wide-fifth cents:
$(3/2) \cdot (3^{12}/2^{19})^{1/3} \approx 709.775$ (widening 7.820~c).
No wolf: all twelve notes remain usable as a tonic.
\textbf{Closure.} $5 \times 7.820 - 2 \times 7.820 = 3 \times 7.820 =
23.460$~c exactly --- the chain closes.
\subsubsection{\texttt{vallotti}}
Fifths F--C, C--G, G--D, D--A, A--E, and E--B (six consecutive) are each
narrowed by 1/6 of the Pythagorean comma; the other six fifths are pure.
\begin{longtable}{p{9.5cm} p{2.5cm}}
\toprule
\textbf{Fifths} & \textbf{Tempering} \\
\midrule
\endhead
F--C, C--G, G--D, D--A, A--E, E--B & narrow, 1/6 Pythagorean comma (6) \\
B--F$\sharp$, F$\sharp$--C$\sharp$, C$\sharp$--G$\sharp$,
G$\sharp$--E$\flat$, E$\flat$--B$\flat$, B$\flat$--F & pure (6) \\
\bottomrule
\end{longtable}
Tempered-fifth cents: $(3/2)/(3^{12}/2^{19})^{1/6} \approx 698.045$
(narrowing 3.910~c $= \tfrac{1}{6} \cdot 23.460$~c). No wolf.
\textbf{Closure.} $6 \times 3.910 = 23.460$~c exactly --- the chain
closes.
\subsubsection{\texttt{kirnberger-ii}}
Fifths D--A and A--E are each narrowed by 1/2 the syntonic comma;
fifth F$\sharp$--D$\flat$ (i.e.\ F$\sharp$--C$\sharp$, named with the
flat spelling because the chain below is built outward from
D$\flat$) is narrowed by a schisma (the difference between the
Pythagorean and syntonic commas, $32805/32768 \approx 1.9537$~c); the
remaining nine fifths are pure.
\begin{longtable}{p{9.5cm} p{2.5cm}}
\toprule
\textbf{Fifths} & \textbf{Tempering} \\
\midrule
\endhead
D$\flat$--A$\flat$, A$\flat$--E$\flat$, E$\flat$--B$\flat$,
B$\flat$--F, F--C, C--G, G--D, E--B, B--F$\sharp$ & pure (9) \\
D--A, A--E & narrow, 1/2 syntonic comma (2) \\
F$\sharp$--D$\flat$ (closing) & narrow, 1 schisma (1) \\
\bottomrule
\end{longtable}
Cents by note, built by stacking the chain
D$\flat$--A$\flat$--E$\flat$--B$\flat$--F--C--G--D--A--E--B--F$\sharp$
from C~$= 1/1$:
\begin{longtable}{p{2.2cm} p{3.5cm}}
\toprule
\textbf{Note} & \textbf{Cents (derived)} \\
\midrule
\endhead
C & 0.000 \\
D$\flat$ & 90.225 \\
D & 203.910 \\
E$\flat$ & 294.135 \\
E & 386.314 \\
F & 498.045 \\
F$\sharp$ & 590.224 \\
G & 701.955 \\
A$\flat$ & 792.180 \\
A & 895.112 \\
B$\flat$ & 996.090 \\
B & 1088.269 \\
\bottomrule
\end{longtable}
\textbf{Closure.} $2 \times 10.753$ (half-syntonic-comma fifths) $+ 1
\times 1.9537$ (schisma fifth) $= 21.5063 + 1.9537 = 23.4600$~c exactly
--- the chain closes.
\subsubsection{\texttt{kirnberger-iii}}
Fifths C--G, G--D, D--A, and A--E (four consecutive) are each narrowed
by 1/4 the syntonic comma; fifth F$\sharp$--D$\flat$ is narrowed by a
schisma (same position and same chain skeleton as
\texttt{kirnberger-ii}); the remaining seven fifths are pure.
\begin{longtable}{p{9.5cm} p{2.5cm}}
\toprule
\textbf{Fifths} & \textbf{Tempering} \\
\midrule
\endhead
D$\flat$--A$\flat$, A$\flat$--E$\flat$, E$\flat$--B$\flat$,
B$\flat$--F, F--C, E--B, B--F$\sharp$ & pure (7) \\
C--G, G--D, D--A, A--E & narrow, 1/4 syntonic comma (4) \\
F$\sharp$--D$\flat$ (closing) & narrow, 1 schisma (1) \\
\bottomrule
\end{longtable}
Cents by note, same chain skeleton as \texttt{kirnberger-ii}, from
C~$= 1/1$:
\begin{longtable}{p{2.2cm} p{3.5cm}}
\toprule
\textbf{Note} & \textbf{Cents (derived)} \\
\midrule
\endhead
C & 0.000 \\
D$\flat$ & 90.225 \\
D & 193.157 \\
E$\flat$ & 294.135 \\
E & 386.314 \\
F & 498.045 \\
F$\sharp$ & 590.224 \\
G & 696.578 \\
A$\flat$ & 792.180 \\
A & 889.735 \\
B$\flat$ & 996.090 \\
B & 1088.269 \\
\bottomrule
\end{longtable}
\textbf{Closure.} $4 \times 5.377$ (quarter-syntonic-comma fifths) $+ 1
\times 1.9537$ (schisma fifth) $= 21.5063 + 1.9537 = 23.4600$~c exactly
--- the chain closes.
\subsubsection{\texttt{young-ii}}
Thomas Young's \emph{second} temperament. Fifths C--G, G--D, D--A, A--E,
E--B, and B--F$\sharp$ (six consecutive) are each narrowed by 1/6 of the
Pythagorean comma; the remaining six fifths are pure.
\begin{longtable}{p{9.5cm} p{2.5cm}}
\toprule
\textbf{Fifths} & \textbf{Tempering} \\
\midrule
\endhead
C--G, G--D, D--A, A--E, E--B, B--F$\sharp$ &
narrow, 1/6 Pythagorean comma (6) \\
F$\sharp$--C$\sharp$, C$\sharp$--G$\sharp$, G$\sharp$--E$\flat$,
E$\flat$--B$\flat$, B$\flat$--F, F--C & pure (6) \\
\bottomrule
\end{longtable}
This is the same six-tempered/six-pure, 1/6-Pythagorean-comma
construction as \texttt{vallotti}, rotated: \texttt{young-ii}'s
tempered run starts at C; \texttt{vallotti}'s starts at F. Tempered-fifth
cents are therefore identical to \texttt{vallotti}'s: $\approx 698.045$
(narrowing 3.910~c $= \tfrac{1}{6} \cdot 23.460$~c). No wolf.
\textbf{Closure.} $6 \times 3.910 = 23.460$~c exactly --- the chain
closes.
\subsection{The Static 5-Limit Construction}
\label{sec:tuning:ji-static}
\begin{requirement}
\label{req:tuning:ji-static-construction}
The static 5-limit just-intonation systems are constructed from the
contiguous lattice block $\{3^a 5^b \mid a \in [-1,2],\ b \in
[-1,1]\}$ --- twelve cells, octave-reduced, assigned in ascending
order to the twelve chromatic positions of \texttt{cmn-12} starting
from the anchor, which takes the role of $1/1$. The block is
generated by its bounds; nothing is selected and nothing is
discarded.
\end{requirement}
The twelve, anchor-relative (position 0 is the anchor):
\begin{longtable}{p{1.4cm} p{2.6cm} p{2.2cm} p{3.4cm}}
\toprule
\textbf{Step} & \textbf{Cell} & \textbf{Ratio} &
\textbf{Cents (derived)} \\
\midrule
\endhead
0 & $3^{0}5^{0}$ & $1/1$ & 0.000 \\
1 & $3^{-1}5^{-1}$ & $16/15$ & 111.731 \\
2 & $3^{2}5^{0}$ & $9/8$ & 203.910 \\
3 & $3^{1}5^{-1}$ & $6/5$ & 315.641 \\
4 & $3^{0}5^{1}$ & $5/4$ & 386.314 \\
5 & $3^{-1}5^{0}$ & $4/3$ & 498.045 \\
6 & $3^{2}5^{1}$ & $45/32$ & 590.224 \\
7 & $3^{1}5^{0}$ & $3/2$ & 701.955 \\
8 & $3^{0}5^{-1}$ & $8/5$ & 813.686 \\
9 & $3^{-1}5^{1}$ & $5/3$ & 884.359 \\
10 & $3^{2}5^{-1}$ & $9/5$ & 1017.596 \\
11 & $3^{1}5^{1}$ & $15/8$ & 1088.269 \\
\bottomrule
\end{longtable}
\texttt{ji-static-5limit-C}, \texttt{ji-static-5limit-G}, and
\texttt{ji-static-5limit-D} are this one construction at three anchors,
the named note taking the role of $1/1$. The catalog keeps exactly
these three rows; additional anchors are an implementation extension
and \MUSTNOT{} be read as a conformance obligation.
No 12-note set built this way is inversionally symmetric: $45/32$ is
present and its inverse $64/45$ is not.
\subsection{Default Score Configuration}
A newly-created score with no explicit tuning context \MUST{} default to:
\begin{itemize}
\item Pitch space: \texttt{cmn-12}.
\item Tuning system: \texttt{tet-12}.
\item Reference pitch: A4 = 440\,Hz.
\item SMuFL version: the highest SMuFL version supported by the
implementation.
\end{itemize}
\section{Forward References}
\begin{itemize}
\item \texttt{VoiceId}, \texttt{StaffId}, \texttt{RegionId},
\texttt{VoiceSelector}, and the score graph's hierarchical
organization are defined in Chapter~\ref{ch:graph}.
\item \texttt{KeyContext} is not defined anywhere in this
specification; completing it is out of scope for this document.
Whatever completes it \MUST{} expose a tonal-centre chromatic
pitch class consistent with
Requirement~\ref{req:tuning:adaptive-anchor-derivation}.
Chapter~\ref{ch:graph} defines \texttt{KeySignature}, not
\texttt{KeyContext}. \texttt{ContextHint} and
\texttt{AdaptiveTuningParameters} are likewise undefined; the
built-in \texttt{ji-adaptive-5limit}, version~1
(Requirement~\ref{req:tuning:adaptive-default-version}), ignores
both, so defining them now would freeze a type surface on a
chapter with no consumer.
\item The specific spelling algorithms referenced by
\texttt{SpellingAlgorithmId} remain an open question; see the
related open question in Section~\ref{sec:pitch:prepass}.
\end{itemize}
% ===========================================================================
\chapter{The Score Graph}
\label{ch:graph}
This chapter specifies the score graph: the in-memory representation of
all musical content in a score. The graph is the canonical truth about
the music; layout, serialization, and editing operations are downstream
projections and consumers of it. This chapter is the largest in the
specification because it threads together every primitive defined in
Chapters~\ref{ch:pitch}--\ref{ch:tuning} into a coherent whole.
\section{Design Principles}
\label{sec:graph:principles}
\begin{description}
\item[Hybrid topology.] The graph is a tree of containment overlaid
with cross-cutting structures that hold references. The tree
determines existence: deleting a parent deletes its descendants.
Cross-cutting structures hold references that may become dangling
and require re-anchoring per the rules defined in this chapter
and in Chapter~\ref{ch:semops}.
\item[Canvas before staff.] The spatial root of a score is the
canvas, a 2D space partitioned into regions. Staves exist inside
regions, not as a top-level concept. This permits regions of
free graphic content to coexist with regions of staff-based CMN
without forcing one paradigm to subsume the other.
\item[Arena storage.] Events are stored in a flat arena owned by
the score. Voices, cross-cutting structures, and analysis layers
hold event identifiers, not events. Arena storage enables fast
bulk iteration, simple cross-cutting reference, and clean CRDT
semantics.
\item[Parts are projections.] A part is a view onto the score
defined by a part-extraction specification. Parts do not live
in the canvas; they are separate objects that select and reshape
canvas content for printed extraction.
\item[Typed identifiers.] Every named object kind has its own
identifier type. Cross-kind confusion is a compile-time error.
\item[Identifiers are stable and deterministic.] Identifiers are
assigned at creation, never reassigned, and generated by a scheme
that is deterministic per replica.
\end{description}
\section{Top-Level Score Structure}
\label{sec:graph:score}
The score is the root object. All other objects in the specification
exist within or reference into a score. Conflict records are
deliberately \emph{not} part of the score graph: the
\texttt{ConflictRegistry} produced by canonical reduction is a
component of the canonical \emph{materialized state}
(Chapter~\ref{ch:semops}), recomputed with it, and persisting there
until resolved or dismissed by explicit operations.
\begin{lstlisting}[language=Rust]
pub struct Score {
/// Bibliographic and authorship metadata.
pub metadata: ScoreMetadata,
/// The spatial root: regions and their staff/graphic content.
pub canvas: Canvas,
/// Abstract instrument definitions used by staves.
pub instruments: Vec<Instrument>,
/// Globally-identified staves of the score. Each region's
/// staff instances reference these by StaffId.
pub staves: Vec<Staff>,
/// Optional staff groupings (grand staves, choral groups,
/// bracketed orchestral sections). Used by engraving and parts.
pub staff_groups: Vec<StaffGroup>,
/// Per-part view definitions for extraction.
pub parts: Vec<PartDefinition>,
/// Cross-cutting structures: slurs, ties, hairpins, markers,
/// graphic gestures, comments, and analytical annotations.
pub cross_cutting: CrossCuttingRegistry,
/// Tuning systems, pitch spaces, and reference pitch.
pub tuning_context: ScoreTuningContext,
/// The score-level tempo map. Regions may declare local tempo
/// maps that override this.
pub tempo_map: TempoMap,
/// Flat arena of all events in the score.
pub events: EventArena,
/// Spelling attachments index (see Chapter 2).
pub spelling_attachments: SpellingAttachmentIndex,
/// Notational decomposition attachments index (see Chapter 3).
pub decomposition_attachments: DecompositionAttachmentIndex,
/// Analysis layers and view definitions.
pub analysis_layers: Vec<AnalysisLayer>,
pub views: Vec<ViewDefinition>,
/// Replica identifier and identifier generation state.
pub identity: IdentityContext,
}
pub struct StaffGroup {
pub id: StaffGroupId,
pub name: Option<String>,
pub kind: StaffGroupKind,
/// A non-authoritative denormalized projection of group membership
/// (genesis tranche G3a, ratified disposition B, filed as P13-S16).
/// \texttt{Staff.group} below is the sole authority: this field
/// \MUSTNOT{} be read to decide whether a staff is in a group, and MAY
/// be stale in both directions --- a member missing here while
/// \texttt{Staff.group} names this group, or a staff listed here while
/// its own \texttt{Staff.group} is absent or names a different group.
pub members: Vec<StaffId>,
}
pub enum StaffGroupKind {
/// Grand staff: typically piano, organ, harp.
GrandStaff,
/// Bracketed group: orchestral sections.
Bracket,
/// Sub-bracket: divisi within a section.
SubBracket,
/// Choral group: SATB and similar.
Choral,
/// Custom group defined by a layout extension.
Registered(StaffGroupKindRegistryId),
}
\end{lstlisting}
\subsection{Score Metadata}
\begin{lstlisting}[language=Rust]
pub struct ScoreMetadata {
pub title: Option<String>,
pub subtitle: Option<String>,
pub composer: Option<String>,
pub lyricist: Option<String>,
pub arranger: Option<String>,
pub copyright: Option<String>,
pub creation_timestamp: Timestamp,
pub modification_timestamp: Timestamp,
pub additional: Vec<MetadataEntry>,
}
pub struct MetadataEntry {
pub key: String,
pub value: MetadataValue,
}
/// The value of an additional metadata entry (defined schema major 2).
/// Closed small union; growth is by appended variant.
pub enum MetadataValue {
Text(String),
Integer(i64),
Flag(bool),
}
/// A calendar timestamp (defined schema major 2): nanoseconds since the
/// Unix epoch, UTC, no zone. Distinct from WallClockTime, which is
/// *performance* time within a score. The zero value is the "unset"
/// convention (genesis without a declared creation time).
pub struct Timestamp(pub i64);
\end{lstlisting}
The metadata structure is deliberately small. Extended bibliographic,
catalog, and editorial-apparatus metadata is the province of foreign-
format mappings and analytical layers, not the core.
\texttt{additional} is an \emph{ordered authored list}, not a map
(defined schema major 2): entry order is preserved verbatim in
canonical bytes, and duplicate keys are permitted --- foreign formats
legitimately carry repeated keys (multiple contributors, multiple
identifiers of one scheme). Consumers wanting map semantics take the
first entry per key.
\begin{requirement}
\label{req:graph:metadata-timestamps}
\textbf{Timestamps are strictly authored (ratified with schema
major 2).} \texttt{creation\_timestamp} and
\texttt{modification\_timestamp} are stored, \emph{authored} values:
genesis takes the creation timestamp explicitly (the empty-document
constructor defaults it to the epoch-zero unset convention), and a
\texttt{SetMetadata} operation carries both verbatim as part of the
metadata value. Implementations \MUSTNOT{} write either field
implicitly --- an automatic modification-time update would rewrite
canonical bytes on every edit, churning content addresses and breaking
replica byte-agreement for identical operation histories. A client
that wants a live modification time surfaces one from non-canonical
state (the operation log's stamps); it does not store it here.
\end{requirement}
\section{Identifiers}
\label{sec:graph:ids}
Every named object in the graph carries a typed 128-bit identifier.
\begin{lstlisting}[language=Rust]
pub struct EventId(u128);
pub struct VoiceId(u128);
pub struct StaffId(u128);
pub struct StaffInstanceId(u128);
pub struct StaffGroupId(u128);
pub struct RegionId(u128);
pub struct InstrumentId(u128);
pub struct PartDefinitionId(u128);
pub struct MeasureId(u128);
pub struct BarlineAlignmentGroupId(u128);
pub struct SlurId(u128);
pub struct TieId(u128);
pub struct BeamId(u128);
pub struct SpannerId(u128);
pub struct MarkerId(u128);
pub struct AnalyticalAnnotationId(u128);
pub struct CommentId(u128);
pub struct GraphicObjectId(u128);
pub struct GraphicGestureId(u128);
pub struct TimeSignatureId(u128);
pub struct ConflictId(u128);
pub struct TransactionId(u128);
// ... and so on for every named object kind
/// A tagged identifier over every typed identifier kind in the
/// score graph. Used wherever an object is referenced generically
/// (cross-cutting endpoints, conflict affected_objects, repair
/// records, edit barriers). The variant tag is part of canonical
/// content: distinct variants with the same underlying u128 are
/// distinct TypedObjectIds.
pub enum TypedObjectId {
Event(EventId),
Pitch(PitchId),
Voice(VoiceId),
Staff(StaffId),
StaffInstance(StaffInstanceId),
StaffGroup(StaffGroupId),
Region(RegionId),
Instrument(InstrumentId),
PartDefinition(PartDefinitionId),
Measure(MeasureId),
BarlineAlignmentGroup(BarlineAlignmentGroupId),
Slur(SlurId),
Tie(TieId),
Beam(BeamId),
Spanner(SpannerId),
Marker(MarkerId),
AnalyticalAnnotation(AnalyticalAnnotationId),
Comment(CommentId),
GraphicObject(GraphicObjectId),
GraphicGesture(GraphicGestureId),
TimeSignature(TimeSignatureId),
AnalysisLayer(AnalysisLayerId),
Tuplet(TupletId),
RepeatStructure(RepeatStructureId),
LyricLine(LyricLineId),
ChordSymbol(ChordSymbolId),
View(ViewId),
/// Extension-defined object kind, identified by registry id
/// plus the extension's own 128-bit identifier.
Registered(ObjectKindRegistryId, u128),
}
impl TypedObjectId {
/// Canonical byte form for hashing, ordering, and equality.
/// Variant tag is encoded as a 16-bit big-endian discriminant
/// followed by the variant payload's canonical bytes.
pub fn canonical_bytes(&self) -> Vec<u8> { /* ... */ }
}
\end{lstlisting}
\begin{requirement}
\label{req:graph:typed-object-id-discriminants}
The \texttt{TypedObjectId} variant tag \MUST{} be encoded as a
16-bit big-endian discriminant followed by the variant payload's
canonical bytes. The discriminant of each variant is its
declaration-order index in the following closed table; this
assignment is normative and stable, and \MUSTNOT{} be reordered:
\begin{center}
\begin{tabular}{r l @{\qquad} r l}
\toprule
\textbf{Disc.} & \textbf{Variant} &
\textbf{Disc.} & \textbf{Variant} \\
\midrule
0 & \texttt{Event} & 14 & \texttt{Spanner} \\
1 & \texttt{Pitch} & 15 & \texttt{Marker} \\
2 & \texttt{Voice} & 16 & \texttt{AnalyticalAnnotation} \\
3 & \texttt{Staff} & 17 & \texttt{Comment} \\
4 & \texttt{StaffInstance} & 18 & \texttt{GraphicObject} \\
5 & \texttt{StaffGroup} & 19 & \texttt{GraphicGesture} \\
6 & \texttt{Region} & 20 & \texttt{TimeSignature} \\
7 & \texttt{Instrument} & 21 & \texttt{AnalysisLayer} \\
8 & \texttt{PartDefinition} & 22 & \texttt{Tuplet} \\
9 & \texttt{Measure} & 23 & \texttt{RepeatStructure} \\
10 & \texttt{BarlineAlignmentGroup} & 24 & \texttt{LyricLine} \\
11 & \texttt{Slur} & 25 & \texttt{ChordSymbol} \\
12 & \texttt{Tie} & 26 & \texttt{View} \\
13 & \texttt{Beam} & 27 & \texttt{Registered} \\
\bottomrule
\end{tabular}
\end{center}
The object-kind set is closed at these 28 discriminants
(\texttt{0}--\texttt{27}). Every named object kind in the score
graph has a dedicated discriminant; kinds introduced by registered
extensions \MUST{} use discriminant \texttt{27}
(\texttt{Registered}) and are distinguished by their
\texttt{ObjectKindRegistryId}, never by extending this table. The
\texttt{Registered(reg, raw)} payload is encoded as the 128-bit
\texttt{reg.canonical\_bytes()} (16 big-endian bytes) followed by
the 16 big-endian bytes of the \texttt{raw} \texttt{u128}, for a
total canonical form of $2 + 16 + 16 = 34$ bytes.
\texttt{ObjectKindRegistryId} is a 128-bit value.
\end{requirement}
\subsection{Identifier Generation}
\begin{requirement}
\label{req:graph:replica-counter-identifiers}
Identifiers \MUST{} be 128-bit values composed of a 64-bit replica
identifier and a 64-bit monotonic counter local to that replica.
The replica identifier \MUST{} be unique among all replicas of the
score; collision resistance is achieved by random initialization of
the replica identifier at creation time with at least 64 bits of
entropy from a cryptographically appropriate source. The counter
\MUST{} be monotonically increasing within a replica and \MUSTNOT{}
be reused, even if the corresponding object is deleted.
\end{requirement}
\begin{lstlisting}[language=Rust]
pub struct IdentityContext {
/// This replica's identifier. Generated at score creation.
pub replica_id: ReplicaId,
/// Monotonic counter for new identifiers. Per-kind counters
/// optional; a single counter suffices.
pub next_counter: u64,
}
pub struct ReplicaId(u64);
impl ReplicaId {
/// Reserved replica identifier for deterministically-derived
/// system identifiers (system-promoted voices, content-derived
/// conflict IDs, future deterministic synthetic identifiers).
/// User-authored replicas MUST NOT use this value.
pub const SYSTEM_DERIVED: ReplicaId = ReplicaId(0xffff_ffff_ffff_ffff);
}
\end{lstlisting}
\begin{rationale}
Replica-plus-counter identifiers are deterministic given a replica
and a creation order, which simplifies reasoning about CRDT merge
semantics and reproducible builds of generated scores. UUIDv7 was
considered and rejected: the timestamp embedding leaks ordering
information that the CRDT layer would have to reconstruct anyway,
and the random portion gives less collision resistance per bit than
a properly initialized replica identifier.
\end{rationale}
\subsection{System-Derived Identifier Namespace}
\label{sec:graph:system-derived}
Some identifiers are not author-authored: they arise during
deterministic reduction (system-promoted voices, content-derived
conflict identifiers, attachment-deduplication identifiers). To
prevent collisions with user-authored identifiers, the spec
reserves a dedicated replica namespace.
\begin{requirement}
\label{req:graph:system-derived-namespace}
The replica identifier \texttt{ReplicaId::SYSTEM\_DERIVED}
(\texttt{0xffff\_ffff\_ffff\_ffff}) is reserved for
deterministically-derived system identifiers. User-authored
replicas \MUSTNOT{} use this value as their \texttt{ReplicaId}.
Implementations creating a new score \MUST{} reject this value
when generating the local replica identifier and \MUST{}
regenerate the random portion until a non-reserved value is
obtained.
Because a system-derived identifier is content-derived, the
identified object's intrinsic content is \emph{immutable}: a
reduction \MUST{} refuse an operation that would rewrite the
intrinsic content of a \texttt{SYSTEM\_DERIVED}-namespace object
in place (ratified Pass~12; the Operation Catalog pins the
precondition and its failure reason). Rewriting the content would
silently invalidate the id's content derivation; the sanctioned
path is minting a replacement object.
System-derived identifiers within the
\texttt{ReplicaId::SYSTEM\_DERIVED} namespace \MUST{} have their
64-bit counter portion derived deterministically by BLAKE3
truncation:
\begin{lstlisting}[language=Rust]
fn derive_system_counter(
domain_tag: &[u8; 8],
canonical_inputs: &[u8],
) -> u64 {
let mut preimage = Vec::new();
preimage.extend_from_slice(domain_tag);
preimage.extend_from_slice(canonical_inputs);
let hash = blake3(&preimage);
u64::from_be_bytes(hash[0..8].try_into().unwrap())
}
\end{lstlisting}
The built-in (reserved) domain tags for system-derived identifiers
are a closed set of three:
\begin{itemize}
\item \texttt{"MUSCSVCE"} for system-promoted voices
(Section~\ref{sec:graph:promoted-voices}).
\item \texttt{"MUSCSPCH"} for system-derived pitches (rare;
only when promotion logic introduces a synthetic pitch;
Section~\ref{sec:graph:system-derived-pitch}).
\item \texttt{"MUSCSANM"} for integrity-anomaly identifiers
(Section~\ref{sec:graph:system-collisions}). Anomalies are
core, not an extension concern, so their tag is built in
alongside the other two.
\item Additional domain tags introduced by registered
extensions \MUST{} begin with \texttt{"MUSCS"}, have length
exactly 8 bytes, and \MUSTNOT{} collide with the three
reserved tags above.
\end{itemize}
Two replicas reducing the same operation set \MUST{} derive
identical system identifiers from identical canonical inputs.
\end{requirement}
\subsection{System-Derived Counter Collisions}
\label{sec:graph:system-collisions}
The 64-bit counter portion of a system-derived identifier is
derived by BLAKE3 truncation. Truncation to 64 bits offers
sufficient collision resistance against accidental collision
(roughly $2^{32}$ system-derived identifiers of the same kind
before a 50\% chance of any pair colliding), but is weaker than
the rest of the identifier story and is potentially vulnerable
to deliberate construction of colliding canonical inputs. A
collision means the identity layer has failed: canonical state
derived from a colliding identifier cannot be trusted.
\begin{lstlisting}[language=Rust]
/// An integrity anomaly that takes the document out of ordinary
/// canonical operation. Distinct from ConflictRecord (an
/// ordinary canonical-state fact): integrity anomalies indicate
/// that the structural assumptions underlying canonical state
/// have failed.
pub struct IntegrityAnomaly {
pub id: IntegrityAnomalyId,
pub kind: IntegrityAnomalyKind,
}
pub enum IntegrityAnomalyKind {
/// A system-derived identifier counter collision.
SystemIdentifierCollision {
kind: ObjectKind,
colliding_counter: u64,
input_set_a: SerializedCanonicalInputs,
input_set_b: SerializedCanonicalInputs,
},
/// An OperationSlot is Equivocated. The slot's candidates
/// are referenced; reduction has excluded the operation.
OperationSlotEquivocated {
operation_id: OperationId,
},
/// A replica's stream has been quarantined per the HLC
/// monotonicity rule. The AnomalousReplicaSegment is
/// referenced.
ReplicaStreamQuarantined {
replica: ReplicaId,
first_bad_counter: u64,
},
/// A registered anomaly defined by an extension or
/// transport.
Registered(IntegrityAnomalyRegistryId),
}
/// The kind of object whose system-derived identifier collided.
/// Open vocabulary, but intentionally narrower than TypedObjectId:
/// only the kinds actually minted into the system-derived namespace
/// appear here.
pub enum ObjectKind {
Voice, // system-promoted voices (MUSCSVCE)
Pitch, // content-derived synthetic pitches (MUSCSPCH)
Registered(OperationKindRegistryId),
}
\end{lstlisting}
\begin{requirement}
\label{req:graph:integrity-anomaly-id}
An \texttt{IntegrityAnomalyId} \MUST{} be derived by the
system-derived identifier function of
Section~\ref{sec:graph:system-derived} with the reserved domain tag
\texttt{"MUSCSANM"} over the anomaly kind's canonical bytes:
\texttt{derive\_system\_id::<IntegrityAnomalyId>(b"MUSCSANM",
\&kind.to\_canonical\_bytes())}. Because the identity is content-derived
from the kind, two replicas observing the same structural failure
derive the same anomaly identifier and therefore agree on anomaly
identity across the network.
\end{requirement}
\begin{requirement}
\label{req:graph:object-kind-vocab}
\texttt{ObjectKind} (the kind field of
\texttt{SystemIdentifierCollision}) is an \emph{open} vocabulary
whose normative core set is exactly \texttt{Voice} and
\texttt{Pitch}, with a \texttt{Registered} escape for
extension-introduced system-derived kinds. It is deliberately
narrower than \texttt{TypedObjectId}'s 28-kind table
(Requirement~\ref{req:graph:typed-object-id-discriminants}):
\texttt{ObjectKind} enumerates only the kinds that are actually
minted into the \texttt{ReplicaId::SYSTEM\_DERIVED} namespace ---
today, promoted voices and synthetic pitches --- because those are
the only kinds for which a system-derived counter collision is
possible. A new core variant is added here only when a new object
kind begins being minted into the system-derived namespace.
The discriminant byte of \texttt{ObjectKind} is its declaration-order
index in the listing above: \texttt{Voice} $= 0$, \texttt{Pitch}
$= 1$, \texttt{Registered} $= 2$. Because \texttt{ObjectKind} is the
\texttt{kind} field of a \texttt{SystemIdentifierCollision} anomaly,
this byte enters the canonical-input preimage of the resulting
\texttt{IntegrityAnomalyId}
(Requirement~\ref{req:graph:integrity-anomaly-id}); two replicas
observing the same structural failure agree on its identity only if
they agree on this byte. The assignment is therefore normative and
stable and \MUSTNOT{} be reordered.
\end{requirement}
\begin{requirement}
\label{req:graph:system-id-collision}
Implementations \MUST{} perform a collision check during
reduction whenever a new system-derived identifier is minted.
If a newly derived 64-bit counter, within the same typed
identifier kind and within the
\texttt{ReplicaId::SYSTEM\_DERIVED} namespace, collides with a
previously-derived counter from \emph{different} canonical
inputs:
\begin{itemize}
\item An \texttt{IntegrityAnomaly} of kind
\texttt{SystemIdentifierCollision} is recorded in the
document's diagnostic integrity-anomaly register. This
register is distinct from the canonical conflict registry:
conflict records are ordinary canonical-state facts;
integrity anomalies indicate the structural assumptions
underlying canonical state have failed.
\item Canonical reduction \MUSTNOT{} continue past the
collision. The colliding object's identifier is held
pending recovery: neither input set is allowed to occupy
the collided counter in canonical state.
\item Readers \MUST{} open such a bundle in diagnostic
recovery mode, surfacing the collision prominently and
prohibiting ordinary editing of the affected portion of
canonical state.
\item Recovery is by external action: a profile-declared
deterministic policy, a transport-level credential
revocation, or an explicit user reconciliation operation.
No automatic in-spec resolution is defined.
\end{itemize}
Authoring implementations \SHOULD{} detect predictable
collisions before authoring (e.g., by canonicalizing inputs
with additional disambiguating data such as the
causally-greatest operation visible at authoring time) and
\SHOULDNOT{} rely on collision frequency being negligible in
adversarial scenarios.
\end{requirement}
\begin{rationale}
Moving system-derived collisions out of \texttt{ConflictKind}
reflects the difference in kind between the two. A
\texttt{ConflictRecord} is an ordinary canonical-state fact:
the user has two valid musical edits that conflict, and the
resolution is a normal editing action. A system-derived
identifier collision is not a musical conflict; it is a
structural failure of the identity layer. Treating it as a
conflict would let canonical state grow under the collision,
which is precisely the wrong behavior for an integrity
failure. Treating it as a diagnostic anomaly halts canonical
growth until external recovery resolves the structural issue.
\end{rationale}
\subsection{System-Derived Pitch Identity}
\label{sec:graph:system-derived-pitch}
When promotion logic introduces a synthetic pitch (rare), its
\texttt{PitchId} is content-addressed from the pitch's intrinsic
identity rather than allocated from a replica counter, so that all
replicas mint the same identifier for the same synthetic pitch.
\begin{requirement}
\label{req:graph:system-derived-pitch-id}
A system-derived \texttt{PitchId} \MUST{} be derived by the
function of Section~\ref{sec:graph:system-derived} with domain tag
\texttt{"MUSCSPCH"} over a fixed canonical byte form of the pitch's
\emph{intrinsic identity}, defined as the pair (\emph{scale
position}, \emph{acoustic realization}). All variable-width string
components of this preimage (nominal names, accidental and tuning
catalog references) \MUST{} be length-prefixed and \MUST{} be
NFC-normalized at the derivation boundary, so that
canonically-equivalent Unicode spellings derive the same identifier.
The acoustic realization's \emph{tuning reference} is always part of
intrinsic identity, including the \texttt{TuningReference::Inherit}
case: \texttt{Inherit} is encoded as a distinct presence marker, not
as the absence of a field. Consequently a synthetic pitch that
inherits its tuning and one that names an explicit tuning are
\emph{distinct} identities even when they would resolve to the same
frequency in context. This makes synthetic-pitch identity stable
under later changes to the inherited tuning environment, and aligns
Invariant~11's uniqueness check with a fixed, context-free preimage.
\end{requirement}
\subsection{Identifier Stability}
\begin{requirement}
\label{req:graph:identifier-non-reuse}
An identifier, once assigned, \MUST{} never be reassigned, even
after deletion of the corresponding object. Stale identifiers
encountered in stored references \MUST{} be detected and reported
by re-anchoring rules (see Chapter~\ref{ch:semops}); they
\MUSTNOT{} silently match a different object.
\end{requirement}
\section{The Event Arena}
\label{sec:graph:arena}
Events are the rhythmic atoms of the score: pitched events, unpitched
events, rests, indeterminate events, trajectories, graphic events,
and cues. All events of all kinds in a score are stored in a single
arena.
\begin{lstlisting}[language=Rust]
pub struct EventArena {
/// Storage backing for events. Implementation-defined; the
/// observable contract is O(1) lookup by EventId and stable
/// addresses for the lifetime of the event.
storage: ArenaStorage<Event>,
/// Auxiliary index for fast time-range queries.
time_index: EventTimeIndex,
}
\end{lstlisting}
\begin{requirement}
\label{req:graph:constant-time-event-lookup}
Event lookup by \texttt{EventId} \MUST{} be $O(1)$ amortized.
Implementations \MAY{} use any storage layout meeting this contract;
vector-of-events with stable indices, slot-allocator schemes, and
generational arenas are all acceptable.
\end{requirement}
\subsection{The Event Type}
\begin{lstlisting}[language=Rust]
pub enum Event {
Pitched(PitchedEvent),
Unpitched(UnpitchedEvent),
Rest(Rest),
Indeterminate(IndeterminateEvent),
Trajectory(TrajectoryEvent),
Graphic(GraphicEvent),
Cue(CueEvent),
}
\end{lstlisting}
Every event variant carries:
\begin{itemize}
\item Its own \texttt{EventId}.
\item Its \emph{voice membership}: a \texttt{VoiceId} identifying
the unique voice that owns it.
\item Its \emph{position} within that voice (either a
\texttt{MusicalPosition} or \texttt{WallClockTime}, depending on
the enclosing region's time model).
\end{itemize}
Voice membership and position are stored on the event so that
operations addressing an event can resolve its context without
traversing parent containers.
\subsubsection{Event Position and Duration}
\label{sec:graph:event-time}
An event's temporal coordinates use union types matching its enclosing
region's time model. Position is unioned over musical and wall-clock
forms; duration is unioned over musical, wall-clock, and indeterminate
forms.
\begin{lstlisting}[language=Rust]
pub enum EventPosition {
Musical(MusicalPosition),
WallClock(WallClockTime),
}
pub enum EventDuration {
Musical(MusicalDuration),
WallClock(WallClockDuration),
Indeterminate(DurationBounds),
}
/// Bounded interval expressing an indeterminate duration. Bounds are
/// concrete (not themselves indeterminate); this prevents recursive
/// indeterminacy in the type system.
pub struct DurationBounds {
pub lower: Option<ConcreteDuration>,
pub upper: Option<ConcreteDuration>,
}
pub enum ConcreteDuration {
Musical(MusicalDuration),
WallClock(WallClockDuration),
}
\end{lstlisting}
\begin{requirement}
\label{req:graph:region-coordinate-kinds}
Event position and duration variants \MUST{} agree with the time
model of the enclosing region:
\begin{itemize}
\item Metric regions accept \texttt{EventPosition::Musical} and
\texttt{EventDuration::Musical} only.
\item Proportional regions accept \texttt{EventPosition::WallClock}
and \texttt{EventDuration::WallClock} only.
\item Aleatoric regions \MAY{} accept any combination consistent
with the region's declared anchoring discipline (see
Section~\ref{sec:graph:aleatoric-discipline}).
\end{itemize}
Implementations \MUST{} reject events whose coordinate variants
contradict their enclosing region's time model.
\end{requirement}
\begin{requirement}
\label{req:graph:duration-bound-kinds}
If both bounds of a \texttt{DurationBounds} are present, they
\MUST{} use the same \texttt{ConcreteDuration} variant, unless the
enclosing aleatoric region's anchoring discipline explicitly
permits mixed-kind bounds.
\end{requirement}
\subsubsection{Identified Pitches}
\label{sec:graph:identified-pitch}
Pitches embedded in events are wrapped in \texttt{IdentifiedPitch}
records that pair each pitch with a stable \texttt{PitchId}
(Section~\ref{sec:pitch:ids}). The identifier enables spelling
attachments, respelling operations, pitch-level reduction rules
under concurrent edits, and stable tie pairing through chord
reordering.
\begin{lstlisting}[language=Rust]
pub struct IdentifiedPitch {
pub id: PitchId,
pub pitch: Pitch,
}
\end{lstlisting}
\begin{requirement}
\label{req:graph:identified-pitch-ownership}
Every pitch embedded in an event \MUST{} be wrapped in an
\texttt{IdentifiedPitch}. The contained \texttt{PitchId} \MUST{} be
unique within the score's pitch-identity index. Pitch identifiers
\MUST{} be minted atomically with their containing event: the
operation creating an event mints \texttt{EventId} and the
\texttt{PitchId}s of all embedded pitches in a single causal step.
\end{requirement}
\subsubsection{Pitched Events}
\begin{lstlisting}[language=Rust]
pub struct PitchedEvent {
pub id: EventId,
pub voice: VoiceId,
pub position: EventPosition,
pub duration: EventDuration,
/// One or more identified pitches. A single pitch is a
/// single-note event; multiple pitches are a chord.
pub pitches: Vec<IdentifiedPitch>,
/// Articulations attached directly to the event.
pub articulations: Vec<ArticulationMark>,
/// Single-event dynamic marking (e.g., sfz, fp). Spanning
/// dynamics are cross-cutting structures, not event fields.
pub dynamic: Option<DynamicMark>,
/// Ornaments attached to the event. Ornament resolutions (the
/// realized notes of a trill, mordent, etc.) are cross-cutting
/// structures.
pub ornaments: Vec<OrnamentMark>,
/// Stem configuration: direction, length adjustment, hidden.
pub stem: StemConfiguration,
/// Whether this event is a grace note, and if so its kind.
pub grace: Option<GraceKind>,
}
pub enum GraceKind {
Acciaccatura,
Appoggiatura,
Unmeasured,
MeasuredFraction(MusicalDuration),
}
\end{lstlisting}
\begin{requirement}
\label{req:graph:pitched-event-nonempty}
A \texttt{PitchedEvent} \MUST{} have at least one pitch. Empty
pitch lists are forbidden; use \texttt{Rest} for the no-pitch case.
\end{requirement}
\subsubsection{Unpitched Events}
\begin{lstlisting}[language=Rust]
pub struct UnpitchedEvent {
pub id: EventId,
pub voice: VoiceId,
pub position: EventPosition,
pub duration: EventDuration,
/// Staff line position for the unpitched event (e.g., snare
/// drum at the middle line of a 5-line staff, or position 0
/// for a single-line staff).
pub staff_position: StaffPosition,
/// The unpitched instrument identifier, for sound mapping.
pub instrument_member: UnpitchedMemberId,
pub articulations: Vec<ArticulationMark>,
pub dynamic: Option<DynamicMark>,
pub stem: StemConfiguration,
pub grace: Option<GraceKind>,
}
\end{lstlisting}
\subsubsection{Rests}
\begin{lstlisting}[language=Rust]
pub struct Rest {
pub id: EventId,
pub voice: VoiceId,
pub position: EventPosition,
pub duration: EventDuration,
/// Optional explicit vertical positioning. If None, the engraver
/// chooses based on voice and meter context.
pub vertical_position: Option<StaffPosition>,
/// Whether this rest is visible. Invisible rests preserve timing
/// but do not render.
pub visible: bool,
}
\end{lstlisting}
\subsubsection{Indeterminate Events}
\begin{lstlisting}[language=Rust]
pub struct IndeterminateEvent {
pub id: EventId,
pub voice: VoiceId,
pub position: EventPosition,
pub duration: EventDuration,
/// What aspect of the event is indeterminate.
pub indeterminacy: IndeterminacyKind,
/// Optional hints constraining the indeterminacy.
pub hints: IndeterminacyHints,
}
pub enum IndeterminacyKind {
/// "Any pitch in this range/contour."
Pitch,
/// "Any duration within these bounds." (Realized via
/// EventDuration::Indeterminate on the event itself.)
Duration,
/// "Any of these alternatives."
Choice,
/// Combination of multiple indeterminacies.
Compound(Vec<IndeterminacyKind>),
}
pub struct IndeterminacyHints {
pub pitch_range: Option<PitchRange>,
pub pitch_contour: Option<ContourHint>,
pub density: Option<DensityHint>,
pub textual_instruction: Option<String>,
pub duration_bounds: Option<DurationBounds>,
pub alternatives: Vec<EventId>,
}
\end{lstlisting}
\subsubsection{Trajectory Events}
A trajectory event represents a continuous pitch motion: a glissando,
portamento, or pitch bend.
\begin{lstlisting}[language=Rust]
pub struct TrajectoryEvent {
pub id: EventId,
pub voice: VoiceId,
pub position: EventPosition,
pub duration: EventDuration,
/// Start and end pitch endpoints. Either may reference another
/// event's pitch by id, or may carry an explicit identified
/// pitch local to the trajectory.
pub start: TrajectoryEndpoint,
pub end: TrajectoryEndpoint,
/// Shape of the trajectory between endpoints.
pub shape: TrajectoryShape,
/// Visual representation.
pub display: TrajectoryDisplay,
}
pub enum TrajectoryEndpoint {
/// Reference to a pitch belonging to another event.
EventPitch(PitchId),
/// An explicit identified pitch local to this trajectory.
/// Such pitches participate in spelling, identity, and editing
/// the same way embedded pitches do.
ExplicitPitch(IdentifiedPitch),
}
pub enum TrajectoryShape {
Linear,
Exponential,
Curve(Vec<TrajectoryControlPoint>),
Stepwise(Vec<IdentifiedPitch>),
}
\end{lstlisting}
\subsubsection{Graphic Events}
A graphic event is an event whose primary content is graphic (drawn
or constructed) rather than notated through the standard staff system.
Used within hybrid and free-graphic regions for events that nevertheless
occupy a rhythmic slot in a voice.
\begin{lstlisting}[language=Rust]
pub struct GraphicEvent {
pub id: EventId,
pub voice: VoiceId,
pub position: EventPosition,
pub duration: EventDuration,
/// Reference to the graphic object(s) realizing this event.
pub graphics: Vec<GraphicObjectId>,
/// Optional playback parameter bindings (see
/// Section~\ref{sec:graph:timebinding}).
pub playback_bindings: Vec<PlaybackBinding>,
}
\end{lstlisting}
\subsubsection{Cue Events}
A cue is a small-print rendering of music from another voice or
instrument, displayed as a guide.
\begin{lstlisting}[language=Rust]
pub struct CueEvent {
pub id: EventId,
pub voice: VoiceId,
pub position: EventPosition,
pub duration: EventDuration,
/// The source events being cued.
pub source: Vec<EventId>,
/// Rendering hints: how the cue should be drawn (size, label,
/// abbreviation of the source instrument's name, etc.).
pub rendering: CueRendering,
}
\end{lstlisting}
\section{The Canvas}
\label{sec:graph:canvas}
The canvas is the spatial root of the score. It contains regions.
The score root and the canvas remain \emph{structural givens}, not
operation products (ratified Pass~12; narrowed by
\texttt{spec/RULING\_GENESIS\_PERSISTENCE.md}): neither object is ever
minted, addressed, or deleted by an operation, and there is still no
\texttt{TypedObjectId} kind for either. This is
deliberate --- a document that could mint its own root would admit a
genesis race under concurrent editing, and every operation would need
a defined semantics against a not-yet-existing root. The decision may
be revisited only if an addressable multi-canvas model is adopted at
a future schema major.
What the genesis ruling reverses is the narrower claim this paragraph used
to make alongside the above: that the score's \emph{contents} also arrive
outside the operation set, as part of the same empty-score-plus-bundle
genesis act. They do not. Every mutable field of \texttt{Score} becomes
operation-authored under the genesis tranche
(\texttt{spec/PLAN\_GENESIS\_OPS.md}): a document created empty
(\texttt{Score::empty}) and given only operations can materialize a
note-bearing score, reaching \texttt{Canvas.layout\_defaults}
and \texttt{Score.spelling\_precedence} (genesis tranche G2a) among other
surfaces. The root and the canvas themselves are the load-bearing
exception that survives intact.
\begin{lstlisting}[language=Rust]
pub struct Canvas {
/// Regions in this canvas. Each region declares its time and
/// content models and occupies a time and staff extent.
pub regions: Vec<Region>,
/// Canvas-level configuration: paper size, margins, default
/// system layout, page-break behavior.
pub layout_defaults: CanvasLayoutDefaults,
}
/// Canvas-level layout defaults. Page size and margins are in staff
/// spaces; a solver reads them as its default page geometry and MAY
/// override per solve. The type is defined with schema major 1 (its field
/// was named on the canvas from the first draft; only its type was
/// undefined).
pub struct CanvasLayoutDefaults {
pub page_size: CanvasSize,
pub margins: CanvasMargins,
}
/// A page size in staff spaces (1 staff space = staff height / 4).
pub struct CanvasSize {
pub width: f64,
pub height: f64,
}
/// Page margins in staff spaces.
pub struct CanvasMargins {
pub top: f64,
pub right: f64,
pub bottom: f64,
pub left: f64,
}
\end{lstlisting}
The default is \textbf{A4 portrait at an 8\,mm staff} (1 staff space =
2\,mm): page $105 \times 148.5$ staff spaces, $7.5$-staff-space margins,
hence a $90 \times 133.5$ content area. Each staff-space scalar is realized
canonically as a \texttt{CanonicalF64} leaf; the reference engraver maps a
\texttt{CanvasLayoutDefaults} onto its internal page geometry. This closes
the long-standing gap in which the canvas named \texttt{layout\_defaults} but
no chapter defined \texttt{CanvasLayoutDefaults}.
\subsection{Regions}
\begin{lstlisting}[language=Rust]
pub struct Region {
pub id: RegionId,
/// Time model: metric, proportional, or aleatoric.
pub time_model: RegionTimeModel,
/// Content model: staff-based, free graphic, or hybrid.
pub content: RegionContent,
/// Range in time that this region occupies.
pub time_extent: TimeExtent,
/// Range in vertical staff space that this region occupies.
/// A region may cover all staves (full-score region) or a subset
/// (a free-graphic gesture occupying only one staff's vertical
/// band, for example).
pub staff_extent: StaffExtent,
/// Optional tempo and tuning overrides scoped to this region.
pub local_tempo_map: Option<TempoMap>,
pub local_tuning_overrides: Vec<TuningOverride>,
/// Whether spanners (slurs first of all) may cross this region's
/// boundary into an adjacent region. Default false: cross-region
/// spanning is not permitted unless a region opts in. Added with
/// schema major 1.
pub permits_spanning_slurs: bool,
}
\end{lstlisting}
For a slur (or other spanner) whose endpoints lie in \emph{different}
regions, permission is governed conjunctively (ratified Pass~12):
the boundary is permeable only when \emph{both} endpoint regions set
\texttt{permits\_spanning\_slurs} --- a region that forbids spanning
is never crossed against its declaration, regardless of which side
the spanner starts on. The check is an authoring-time advisory only;
it never alters canonical reduction.
\begin{lstlisting}[language=Rust]
pub enum RegionContent {
/// Staff-based notation: one or more staves carrying voices and
/// events.
StaffBased(StaffBasedContent),
/// Free graphic content: no staves, only graphic objects placed
/// in the region's coordinate space.
FreeGraphic(GraphicContent),
/// Hybrid: staves with overlaid graphic content. The graphic
/// layer is rendered above (or below, configurably) the staff
/// layer.
Hybrid {
staves: StaffBasedContent,
overlay: GraphicContent,
overlay_below_staves: bool,
},
}
pub struct TimeExtent {
pub start: TimeAnchor,
pub end: TimeAnchor,
}
pub struct StaffExtent {
/// Identifiers of the globally-identified staves whose content
/// this region carries. A region carries one StaffInstance per
/// listed StaffId. The same StaffId may appear in the
/// staff_extent of adjacent regions; this is the mechanism by
/// which a staff's identity persists across regions.
pub staves: Vec<StaffId>,
}
\end{lstlisting}
\subsection{Region Overlap and Concurrency}
\begin{requirement}
\label{req:graph:region-overlap-constraint}
Regions \MAY{} overlap in time if their staff extents are disjoint.
Regions \MUSTNOT{} overlap in both time and staff extent. The
graph's construction \MUST{} reject configurations violating this
constraint.
\end{requirement}
\begin{rationale}
Concurrent regions on disjoint staves are essential for the hybrid
notation feature: a metric staff and a proportional staff sounding
simultaneously are two regions with overlapping time extents but
disjoint staff extents. Overlapping time \emph{and} staff would mean
two regions claim the same content, which is ambiguous.
\end{rationale}
\subsection{Staff-Based Content}
A staff-based region carries one or more staff \emph{instances}, each
of which manifests a globally-identified staff for the duration of the
region. The score's metric organization is per-staff (admitting
polymeter); barline alignment between staves is recorded as an
explicit relationship when meters coincide.
\begin{lstlisting}[language=Rust]
pub struct StaffBasedContent {
/// Staff instances appearing in this region. Each references a
/// globally-identified Staff and provides region-local content.
pub staff_instances: Vec<StaffInstance>,
/// Default metric grid for this region. Staves without their own
/// local_metric_grid inherit this grid. None means the region
/// has no default metric organization (acceptable for transitional
/// regions or for regions where every staff declares its own).
pub default_metric_grid: Option<MetricGrid>,
/// Explicit barline alignment groups: places where measures
/// across multiple staves are declared to coincide. Used for
/// engraving barlines that span multiple staves under polymeter
/// and for analytical purposes. Implementations MAY derive
/// some alignment groups automatically from coincident measure
/// starts; explicit groups are authoritative.
pub barline_alignment_groups: Vec<BarlineAlignmentGroup>,
/// System breaks declared by the user. The engraver may
/// override these if region settings permit (see Chapter 6).
pub user_system_breaks: Vec<TimeAnchor>,
/// Page breaks declared by the user. Same override rule.
pub user_page_breaks: Vec<TimeAnchor>,
}
/// A region-local manifestation of a globally-identified Staff.
pub struct StaffInstance {
pub id: StaffInstanceId,
/// The globally-identified staff this instance manifests.
pub staff: StaffId,
/// Voices appearing on this staff instance.
pub voices: Vec<Voice>,
/// Clef changes within this instance.
pub clef_sequence: Vec<ClefChange>,
/// Key signature changes within this instance.
pub key_sequence: Vec<KeySignatureChange>,
/// Optional local metric grid: if Some, overrides the region's
/// default_metric_grid for this staff (polymetric case).
/// If None, the staff inherits the region's default_metric_grid.
pub local_metric_grid: Option<MetricGrid>,
/// Measures belonging to this staff instance, in order. Measures
/// are per-staff (admitting polymeter); alignment across staves
/// is recorded in barline_alignment_groups.
pub measures: Vec<Measure>,
/// Optional per-instance instrument override. None means inherit
/// from the global Staff's declared instrument.
pub instrument_override: Option<InstrumentId>,
/// Optional per-instance staff line configuration override.
/// None means inherit from the global Staff's default.
pub staff_lines_override: Option<StaffLineConfiguration>,
/// Whether this instance is visible. Hidden instances preserve
/// content but are not rendered (used for empty-staff suppression).
pub visible: bool,
}
/// The clef content model (ratified schema major 2; the value types
/// landed with the visible slice and supersede the earlier ClefId
/// identifier sketch). A clef is a value, not a registry reference.
pub enum ClefShape {
/// G clef (treble family) -- reference pitch G4.
G,
/// F clef (bass family) -- reference pitch F3.
F,
/// C clef (alto / tenor family) -- reference pitch middle C (C4).
C,
/// Unpitched percussion clef -- no diatonic reference.
Percussion,
}
pub struct Clef {
pub shape: ClefShape,
/// The staff line (1 = bottom) the shape's reference pitch sits on.
pub line: i8,
/// Signed octave displacement (e.g., -1 for the vocal tenor G clef).
pub octave_shift: i8,
}
/// A key signature on the circle of fifths, validated to the
/// conventional CMN range -7 (seven flats) ..= +7 (seven sharps).
/// Constructed only through a checked constructor; decoders reject
/// out-of-range values.
pub struct KeySignature {
fifths: i8, // private; KeySignature::new rejects |fifths| > 7
}
/// A clef change at a point in a staff instance.
pub struct ClefChange {
pub anchor: TimeAnchor,
pub clef: Clef,
}
/// A key-signature change at a point in a staff instance.
pub struct KeySignatureChange {
pub anchor: TimeAnchor,
pub key: KeySignature,
}
/// A per-staff (or per-region-default) metric organization.
pub struct MetricGrid {
/// Sequence of meter changes within the scope of this grid.
pub meter_sequence: Vec<MeterChange>,
}
pub struct MeterChange {
/// Where this meter takes effect, expressed as an anchor within
/// the enclosing staff or region.
pub anchor: TimeAnchor,
/// The active time signature from this point until the next
/// MeterChange or the end of the grid's scope.
pub time_signature: TimeSignatureId,
}
/// A declaration that the measure boundaries of two or more staff
/// instances coincide at a particular point.
pub struct BarlineAlignmentGroup {
pub id: BarlineAlignmentGroupId,
/// The staff instances participating in this alignment, paired
/// with the measures whose boundaries coincide.
pub members: Vec<BarlineAlignmentMember>,
}
pub struct BarlineAlignmentMember {
pub staff_instance: StaffInstanceId,
pub measure: MeasureId,
pub position: MeasurePosition,
}
pub struct Measure {
pub id: MeasureId,
pub start: TimeAnchor,
pub time_signature: Option<TimeSignatureId>,
pub explicit_number: Option<u32>,
pub number_visibility: MeasureNumberVisibility,
}
\end{lstlisting}
\begin{requirement}
\label{req:graph:staff-owned-measures}
Measures \MUST{} belong to a single \texttt{StaffInstance}, not to
the enclosing region. This admits polymeter: different staves in
the same region \MAY{} carry different meter sequences and
consequently different measure boundaries. Barline coincidences
across staves \MUST{} be expressed via
\texttt{BarlineAlignmentGroup} entries when explicit alignment is
authoritative; otherwise alignment is derived at engraving time
from coincident measure-start positions.
\end{requirement}
\section{Staves, Voices, and Instruments}
\label{sec:graph:staves}
\subsection{Instruments}
An instrument is an abstract definition of a sounding entity.
\begin{lstlisting}[language=Rust]
pub struct Instrument {
pub id: InstrumentId,
pub name: String,
pub abbreviation: Option<String>,
/// MIDI program, sound library mapping, or unpitched instrument
/// definitions. The structure is detailed in the audio engine
/// specification (out of scope here); the core stores the
/// configuration opaquely.
pub sound_config: SoundConfiguration,
/// Default transposition (e.g., B-flat clarinet sounds a major
/// second below written).
pub transposition: Option<TranspositionInterval>,
/// Default playable range. Engraving may flag out-of-range
/// pitches; not a hard constraint.
pub range: Option<PitchRange>,
/// Default clef and staff line configuration for staves of this
/// instrument. The clef is the content-bearing Clef value defined
/// in this chapter (shape, line, octave shift) -- the value model
/// ratified with the visible slice supersedes the earlier ClefId
/// identifier sketch (reconciled schema major 2).
pub default_clef: Clef,
pub default_staff_lines: StaffLineConfiguration,
/// For unpitched instruments: definitions of each playable
/// member (snare, kick, ride bell, etc.) and their staff
/// positions.
pub unpitched_members: Vec<UnpitchedMember>,
}
/// Opaque sound configuration (defined schema major 2). The audio
/// engine specification owns the structure; the core stores the bytes
/// verbatim and never interprets them. Empty is the default.
pub struct SoundConfiguration(pub Vec<u8>);
// TranspositionInterval (defined schema major 2) is specified in
// Chapter 2, "Transposition and the Interval Type": the diatonic and
// chromatic step counts of an interval, and its action on a scale
// position. Instrument.transposition is the written-versus-sounding
// interval of a transposing instrument (a B-flat clarinet is
// -1 diatonic, -2 chromatic).
//
// Its ACTION is pinned (Chapter 2). What remains unimplemented is its
// AUTOMATIC APPLICATION at the instrument boundary: nothing in the core
// yet respells a written part into a sounding one, or resolves either to
// a frequency. Instrument.transposition is therefore still advisory --
// but for that reason, not for want of interval algebra.
/// One playable member of an unpitched instrument (defined schema
/// major 2): the identifier UnpitchedEvent.instrument_member
/// references, its display name, and its default staff position.
pub struct UnpitchedMember {
pub member: UnpitchedMemberId,
pub name: String,
pub staff_position: StaffPosition,
}
\end{lstlisting}
\texttt{UnpitchedMemberId} is an \emph{instrument-scoped small value}
(a plain 32-bit integer), not a member of the 128-bit typed-identifier
family --- it is minted by the author of the instrument definition, not
by replicas, and participates in no content derivation. Member values
\emph{should} be unique within one instrument's
\texttt{unpitched\_members}; the authoring advisory layer flags a
duplicate, and resolution is by first match in list order (canonical
bytes preserve the authored order), so a duplicate is deterministic,
merely shadowed. An \texttt{UnpitchedEvent.instrument\_member} with no
matching entry resolves to \emph{no member definition}: the event still
renders (its own \texttt{staff\_position} field governs placement ---
the member's \texttt{staff\_position} is the \emph{authoring default}
copied onto new events, not a render-time override), and sound mapping
falls back to the instrument's \texttt{sound\_config}. This
no-match tolerance is load-bearing: every pre-major-2 instrument has an
empty member list while its events carry member values.
\texttt{StaffPosition}'s vertical convention (which integer is which
line, and its orientation) remains \emph{deliberately deferred} to the
Chapter~\ref{ch:layout-ir} vertical model, with the unpitched-rendering
tranche as its landing site; until then the field is preserved and
compared verbatim, and no implementation may claim conformant unpitched
\emph{rendering}.
\subsection{Staves: Identity Versus Instance}
\label{sec:graph:staff-identity}
The specification distinguishes two staff concepts:
\begin{description}
\item[\texttt{Staff}] is the \emph{global, abstract} staff identity
persisting across the entire score. A staff is the thing a
performer is reading: the "Flute 1" staff, the "Piano upper
staff." A staff is declared once in the score and is referenced
by staff instances in any region where it appears.
\item[\texttt{StaffInstance}] is the \emph{region-local manifestation}
of a staff: the voices, measures, clef changes, key changes, and
metric grid that belong to that staff for the duration of one
region. A single \texttt{Staff} may have multiple
\texttt{StaffInstance}s across the score; each instance belongs
to exactly one region.
\end{description}
\begin{lstlisting}[language=Rust]
pub struct Staff {
pub id: StaffId,
/// Human-readable name (e.g., "Flute 1", "Violin II").
pub name: String,
/// Optional abbreviated name for subsequent systems.
pub abbreviation: Option<String>,
/// The instrument this staff renders by default. May be
/// overridden per-instance.
pub instrument: InstrumentId,
/// Default clef for new instances of this staff (the
/// content-bearing Clef value defined in this chapter; reconciled
/// schema major 2).
pub default_clef: Clef,
/// Default staff line configuration.
pub default_staff_lines: StaffLineConfiguration,
/// Visual grouping: which staff group (if any) this staff
/// belongs to (e.g., piano grand staff, choral group). The sole
/// authority for group membership (genesis tranche G3a, ratified
/// disposition B, filed as P13-S16): every consumer \MUST{} read
/// membership from this field, not from \texttt{StaffGroup.members}
/// above, which is a non-authoritative denormalized projection that MAY
/// disagree with this field in either direction.
pub group: Option<StaffGroupId>,
}
pub struct StaffLineConfiguration {
pub line_count: u8,
pub line_spacing: SpaceUnit,
pub line_style: LineStyle,
pub bracket: Option<StaffBracketKind>,
}
/// A dimension in staff spaces (defined schema major 2). Staff line
/// spacing is relative to the global staff space; 1.0 is a
/// normal-size staff, smaller values yield cue/ossia staves.
pub struct SpaceUnit(pub CanonicalF64);
/// A line drawing style (defined schema major 2). Shared by staff
/// lines and the slur/tie/spanner style records below.
pub enum LineStyle {
Solid,
Dashed,
Dotted,
}
/// A per-staff bracket adornment (defined schema major 2), distinct
/// from StaffGroup-level bracketing.
pub enum StaffBracketKind {
Brace,
Bracket,
}
\end{lstlisting}
The score's top-level structure carries a list of \texttt{Staff}
objects alongside its list of \texttt{Instrument} objects; each
region's staff instances reference these by \texttt{StaffId}.
\begin{requirement}
\label{req:graph:staff-instance-ownership}
Every \texttt{StaffInstance.staff} \MUST{} resolve to a
\texttt{Staff} declared at the score level. A single
\texttt{StaffId} \MAY{} be referenced by multiple
\texttt{StaffInstance}s in different regions: this is how a staff
maintains continuous identity across the score's regions.
A \texttt{StaffInstance} \MUST{} belong to exactly one region. The
region's \texttt{staff\_instances} list \MUST{} contain the
instance, and the instance's content is owned by that region.
Continuous attachments (e.g., key signatures, clef changes, metric
grids) \MAY{} appear on multiple staff instances of the same
\texttt{Staff} when those instances are adjacent in time; the
engraving pipeline reconstructs continuous staff appearance from
per-instance content.
\end{requirement}
\subsection{Voices}
A voice is a polyphonic line within a staff instance. Voices hold
ordered references to events; the events themselves live in the
score's arena.
\begin{lstlisting}[language=Rust]
pub struct Voice {
pub id: VoiceId,
/// Ordered references to events in this voice, sorted by
/// position. Each event has voice == this voice's id.
pub events: Vec<EventId>,
/// Default stem direction for this voice. Common conventions:
/// voice 1 stems up, voice 2 stems down on the same staff.
pub default_stem_direction: Option<StemDirection>,
/// Whether this voice is the staff's primary voice (for purposes
/// of rest placement, beam direction defaults, and similar
/// engraving decisions).
pub is_primary: bool,
/// Provenance of this voice: user-declared, imported, or
/// system-promoted by concurrent-edit conflict resolution.
pub origin: VoiceOrigin,
}
pub enum VoiceOrigin {
/// Created by an explicit user action.
UserDeclared,
/// Imported from a foreign format.
Imported { format: ForeignFormatId },
/// System-promoted to resolve a concurrent-edit collision.
/// The original_voice is the voice into which the user had
/// intended to insert; the promoted voice carries the event
/// authored by losing_operation.
SystemPromoted {
winning_operation: OperationId,
losing_operation: OperationId,
original_voice: VoiceId,
},
}
\end{lstlisting}
\begin{requirement}
\label{req:graph:unbounded-voice-count}
The number of voices per staff instance \MUST{} be unbounded in
the data model. Engraving heuristics (Chapter~\ref{ch:layout-ir}
and beyond) may flag visual issues with high voice counts; the
graph admits any number.
\end{requirement}
\begin{requirement}
\label{req:graph:event-voice-membership}
Every event \MUST{} belong to exactly one voice. Voice membership
is stored on the event (the \texttt{voice} field) and \MUST{} agree
with the membership of the voice's \texttt{events} list:
for any event $e$ in voice $v$'s events list, $e\texttt{.voice}$
\MUST{} equal $v\texttt{.id}$.
\end{requirement}
\subsubsection{System-Promoted Voices}
\label{sec:graph:promoted-voices}
When concurrent operations cause an \texttt{InsertEvent} collision
that cannot be resolved by ordering alone (because both events
have positive duration and would overlap within the target voice),
the deterministically-losing event is placed in a newly allocated
voice with \texttt{origin: VoiceOrigin::SystemPromoted}. This
preserves the non-overlap invariant without rejecting user intent.
\begin{requirement}
\label{req:graph:promoted-voice-id}
The identifier of a system-promoted voice \MUST{} be derived
deterministically from a fixed function of:
\begin{enumerate}
\item The enclosing staff instance's \texttt{StaffInstanceId}.
\item The original target voice's \texttt{VoiceId}.
\item The winning operation's \texttt{OperationId}.
\item The losing operation's \texttt{OperationId}.
\end{enumerate}
The derivation is the system-derived identifier function of
Section~\ref{sec:graph:system-derived} with domain tag
\texttt{"MUSCSVCE"} over a fixed 64-byte preimage formed by
concatenating the four identifiers in the order listed above, each
as its 16-byte big-endian canonical form:
\begin{lstlisting}[language=Rust]
fn derive_promoted_voice_id(
staff_instance: StaffInstanceId,
original_voice: VoiceId,
winning_op: OperationId,
losing_op: OperationId,
) -> VoiceId {
// 4 * 16 = 64 bytes, each component big-endian.
let mut inputs = Vec::with_capacity(64);
inputs.extend_from_slice(&staff_instance.canonical_bytes());
inputs.extend_from_slice(&original_voice.canonical_bytes());
inputs.extend_from_slice(&winning_op.canonical_bytes());
inputs.extend_from_slice(&losing_op.canonical_bytes());
derive_system_id::<VoiceId>(b"MUSCSVCE", &inputs)
}
\end{lstlisting}
The enclosing staff instance is recovered from graph containment at
reduction time; the operation does not store it redundantly.
Determinism is required so that all replicas independently arrive at
the same promoted voice identity. This derivation is the same one
whose result Invariant~18 (Section~\ref{sec:graph:invariants})
verifies; the reducer that mints the voice and the verifier that
checks it consume one derivation.
\end{requirement}
\begin{requirement}
\label{req:graph:promotion-generalization}
The promotion rule generalizes from the pairwise case to any number
of concurrent overlapping inserts into the same voice by an
order-independent pre-pass:
\begin{enumerate}
\item Bucket the concurrent \texttt{InsertEvent} operations by
their target \texttt{(staff\_instance, original\_voice)}.
\item Within a bucket, walk the operations in ascending
\texttt{OperationId} order, maintaining a \emph{retained set} of
mutually non-overlapping inserts that stay in the original voice.
\item An operation whose inserted interval does not overlap any
member of the retained set joins the retained set. An operation
whose interval overlaps some retained member is a \emph{loser}
and is promoted; the lowest-\texttt{OperationId} retained member
it overlaps is its \texttt{winning\_operation}.
\end{enumerate}
Because the walk is by \texttt{OperationId} and the retained set is
built deterministically, every replica promotes the same operations
and derives the same promoted-voice identities regardless of
delivery order. ``Overlap'' is interval overlap of the half-open
inserted spans \texttt{[start,\,end)}: the rule applies to
\emph{partial} overlaps, not only to inserts that share an identical
start position. This is a deliberate widening of the pairwise rule's
identical-start-position language; it is the correct reading because
any positive-duration overlap breaks the voice non-overlap invariant
(Invariant~3), not just a shared onset.
\end{requirement}
\begin{requirement}
\label{req:graph:promoted-voice-visibility}
System-promoted voices are first-class graph objects: they appear
in the staff instance's \texttt{voices} list, they participate in
cross-cutting structures, and they are visible to users. They are
\emph{not} hidden or transient. The layout pipeline
(Chapter~\ref{ch:layout-ir}) \SHOULD{} provide a visual indication
that a voice was system-promoted until the user normalizes it
(via a later \texttt{NormalizePromotedVoice} or
\texttt{MergeVoices} operation, specified in the operation
catalog).
Promoted voices \MUSTNOT{} silently become user-authored voices;
doing so would convert a CRDT conflict resolution into musical
authorship without the user's consent.
\end{requirement}
\begin{requirement}
\label{req:graph:voice-event-nonoverlap}
Within a voice, events \MUST{} be sorted by position and \MUSTNOT{}
overlap in time. Concurrent material on the same staff \MUST{} be
expressed via multiple voices.
\end{requirement}
\section{Cross-Cutting Structures}
\label{sec:graph:cross}
Cross-cutting structures hold references into the tree (typically
event identifiers) and represent musical phenomena that span multiple
events. The registry is partitioned by type for both type safety
and fast indexed lookup.
\begin{lstlisting}[language=Rust]
pub struct CrossCuttingRegistry {
pub slurs: Vec<Slur>,
pub ties: Vec<Tie>,
pub beams: Vec<Beam>,
pub tuplets: Vec<Tuplet>,
pub spanners: Vec<Spanner>,
pub markers: Vec<Marker>,
pub repeats: Vec<RepeatStructure>,
pub analytical: Vec<AnalyticalAnnotation>,
pub comments: Vec<Comment>,
pub graphic_gestures: Vec<GraphicGesture>,
pub lyrics: Vec<LyricLine>,
pub chord_symbols: Vec<ChordSymbol>,
}
\end{lstlisting}
\subsection{Slurs and Phrase Marks}
\label{sec:graph:slurs}
\begin{lstlisting}[language=Rust]
pub struct Slur {
pub id: SlurId,
/// First and last events under the slur.
pub start_event: EventId,
pub end_event: EventId,
/// Slur kind: legato slur, phrase mark, articulation slur,
/// editorial.
pub kind: SlurKind,
/// Optional curvature override. The engraver computes default
/// curvature; this field overrides it.
pub curvature_override: Option<CurvatureOverride>,
/// Visual style: solid, dashed, dotted; line thickness curve.
pub style: SpanStyle,
}
/// The class of a slur (defined schema major 2). The v1-to-v2
/// migration default is Legato.
pub enum SlurKind {
/// An ordinary legato slur.
Legato,
/// A phrase mark (typically longer, over sub-phrases).
Phrase,
/// An articulation slur (e.g., over a two-note sigh figure).
Articulation,
/// An editorial slur (rendered distinctly, e.g., dashed).
Editorial,
}
/// Which side of the notes a curve arcs toward (defined schema
/// major 2).
pub enum CurveDirection {
Above,
Below,
}
/// An authored curvature override (defined schema major 2). The
/// engraver computes default curvature; each present field overrides
/// that component of it. Consumed by the Standard engraving tier;
/// stored and preserved at every tier.
pub struct CurvatureOverride {
pub direction: Option<CurveDirection>,
/// Arc height at the apex.
pub height: Option<SpaceUnit>,
}
/// The visual style of a spanning mark (defined schema major 2): one
/// shared record for Slur.style, Tie.style, and Spanner.style.
/// Defaults: solid, engraver-chosen thickness.
pub struct SpanStyle {
pub line: LineStyle,
/// Line thickness; None = the engraver's default.
pub thickness: Option<SpaceUnit>,
}
\end{lstlisting}
\subsection{Ties}
\label{sec:graph:ties}
\begin{lstlisting}[language=Rust]
pub struct Tie {
pub id: TieId,
/// The two events being tied. The end event's pitch(es) must
/// match the corresponding pitches in the start event.
pub start_event: EventId,
pub end_event: EventId,
/// For chord ties: which pitches in the start event are tied
/// to which in the end event, expressed by stable PitchId pairs.
/// None means all pitches are tied by enharmonic matching in
/// pitch-id-ascending order.
pub pitch_pairing: Option<Vec<(PitchId, PitchId)>>,
/// Tie class: determines the validation profile applied to this
/// tie. The default Standard class requires same-voice immediate
/// adjacency; other classes relax the rule for editorial,
/// cross-voice, or notation-specific use.
pub class: TieClass,
/// Visual style (the shared SpanStyle record, schema major 2).
pub style: SpanStyle,
}
pub enum TieClass {
/// Standard CMN tie: same voice, immediate adjacency in voice
/// order, pitches enharmonically equivalent.
Standard,
/// Editorial tie: connects pitches the editor considers
/// continuous, spanning intervening rests or invisible events.
/// Drawn with editorial style (dashed or bracketed) by default.
Editorial,
/// Cross-voice tie: connects pitches across voice boundaries
/// within the same staff instance. Used for keyboard divisi,
/// re-voicing across hands, and similar constructions.
CrossVoice,
/// Laissez-vibrer tie: a tie with no defined end event, drawn
/// trailing from the start event. The end_event field is
/// permitted to equal the start_event for this class, with
/// pitch_pairing self-referential.
LaissezVibrer,
/// Registered class with custom validation behavior, defined
/// by a notation grammar plugin.
Registered(TieClassRegistryId),
}
\end{lstlisting}
\begin{rationale}
Pairing by stable \texttt{PitchId} rather than by positional index
preserves tie identity through chord-reordering edits and through
CRDT-style concurrent modification of the chord's pitch list.
Index-based pairing would silently retarget when pitches were
inserted, deleted, or reordered within a chord.
The tie class permits common editorial and contemporary-notation
practices (editorial dashed ties spanning rests, cross-voice ties
in keyboard scores, laissez-vibrer ties without a notated end) to
be expressed without relaxing the structural invariants of the
standard tie.
\end{rationale}
\begin{requirement}
\label{req:graph:tie-class-validation}
Tie validation is class-specific:
\begin{itemize}
\item \texttt{TieClass::Standard}: the start and end events
\MUST{} be in the same voice; the start event \MUST{}
immediately precede the end event in that voice's event
sequence; the paired pitches \MUST{} be enharmonically
equivalent under the active tuning system.
\item \texttt{TieClass::Editorial}: the start and end events
\MUST{} be in the same voice and the start \MUST{} precede the
end in voice order, but intervening events are permitted.
Paired pitches \MUST{} be enharmonically equivalent.
\item \texttt{TieClass::CrossVoice}: the start and end events
\MAY{} be in different voices on the same staff instance. The
start's resolved position \MUST{} be less than or equal to
the end's. Paired pitches \MUST{} be enharmonically equivalent.
\item \texttt{TieClass::LaissezVibrer}: the end event \MAY{}
equal the start event, in which case the tie has no notated
destination. No adjacency requirement applies.
\item \texttt{TieClass::Registered}: validation is defined by
the registered class's contract; the registry \MUST{} specify
adjacency and pitch-equivalence requirements for the class.
\end{itemize}
\end{requirement}
\subsection{Beams}
\begin{lstlisting}[language=Rust]
pub struct Beam {
pub id: BeamId,
pub events: Vec<EventId>,
pub level: u8, // primary beam level
/// Sub-beam structure for inner subdivisions. Each entry
/// describes a beam segment at a deeper level.
pub sub_beams: Vec<SubBeam>,
/// Beam angle and stem-length overrides.
pub geometry_override: Option<BeamGeometryOverride>,
}
/// A beam segment at a deeper subdivision level (defined schema
/// major 2): a contiguous subset of the owning beam's events beamed
/// together at `level` (strictly deeper than the owning beam's
/// primary level).
pub struct SubBeam {
pub level: u8,
pub events: Vec<EventId>,
}
/// An authored beam-geometry override (defined schema major 2). Each
/// present field overrides the engraver's computed geometry. Consumed
/// by the Standard engraving tier; stored and preserved at every tier.
pub struct BeamGeometryOverride {
/// Beam slope: staff spaces of rise per staff space of run
/// (dimensionless, hence not a SpaceUnit).
pub slope: Option<CanonicalF64>,
/// Vertical displacement of the beam from its default placement
/// (positive = up).
pub offset: Option<SpaceUnit>,
}
\end{lstlisting}
\subsection{Spanners}
A spanner is a generic spanning mark: hairpins, octave lines, pedal
lines, trill extensions, and similar.
\begin{lstlisting}[language=Rust]
pub struct Spanner {
pub id: SpannerId,
pub kind: SpannerKind,
pub start: TimeAnchor,
pub end: TimeAnchor,
/// Which staff or staves this spanner attaches to. Most spanners
/// target a single staff; some (system-level dynamics, pedal
/// lines spanning grand staff) target multiple.
pub staves: Vec<StaffId>,
/// Visual style (the shared SpanStyle record, schema major 2).
pub style: SpanStyle,
}
pub enum SpannerKind {
/// An unclassified spanning mark: renders as a plain line or
/// bracket. First variant deliberately (schema major 2): it is
/// the v1-to-v2 migration default -- a v1 spanner carried no
/// kind, and Generic is the honest translation of that absence.
Generic,
Hairpin(HairpinDirection),
OctaveLine(OctaveOffset),
PedalLine(PedalKind),
TrillExtension,
Glissando,
Portamento,
TextLine(TextLineDefinition),
Bracket(BracketKind),
}
/// Hairpin orientation (defined schema major 2).
pub enum HairpinDirection {
Crescendo,
Diminuendo,
}
/// Octave-line displacement in signed octaves (defined schema
/// major 2): +1 = 8va, -1 = 8vb, +2 = 15ma, -2 = 15mb. Zero is
/// representable but degenerate; the authoring advisory layer flags
/// it, reduction does not.
pub struct OctaveOffset(pub i8);
/// Pedal-line kind (defined schema major 2).
pub enum PedalKind {
Sustain,
Sostenuto,
UnaCorda,
}
/// A text line's content (defined schema major 2); the dash pattern
/// comes from the spanner's style.
pub struct TextLineDefinition {
pub text: String,
}
/// A bracket spanner's shape (defined schema major 2). Growth is by
/// appended variant.
pub enum BracketKind {
Square,
}
\end{lstlisting}
\subsection{Markers}
A marker is a point annotation: rehearsal marks, section labels,
tempo text, fermatas (which technically attach to events but may
also be region-level), and similar.
\begin{lstlisting}[language=Rust]
pub struct Marker {
pub id: MarkerId,
pub anchor: TimeAnchor,
pub kind: MarkerKind,
pub display: MarkerDisplay,
}
pub enum MarkerKind {
Rehearsal(RehearsalMark),
Section(SectionLabel),
Tempo(TempoMarking),
Coda,
Segno,
DalSegno,
DaCapo,
Fine,
Custom(CustomMarkerId),
}
\end{lstlisting}
\subsection{Repeat Structures}
\label{sec:graph:repeats}
\begin{lstlisting}[language=Rust]
pub struct RepeatStructure {
pub id: RepeatStructureId,
pub kind: RepeatKind,
pub start: TimeAnchor,
pub end: TimeAnchor,
/// For voltas: which endings apply on which passes.
pub voltas: Vec<Volta>,
}
pub enum RepeatKind {
SimpleRepeat { count: u32 },
DaCapo { end_target: TimeAnchor },
DalSegno { segno: TimeAnchor, end_target: TimeAnchor },
Volta,
}
/// One volta bracket (defined schema major 2): the passes it applies
/// on and the time span it covers. A RepeatStructure of kind Volta
/// carries one entry per ending (e.g., a first ending Volta with
/// endings = [1] and a second with endings = [2]).
pub struct Volta {
/// The pass numbers this ending plays on (1-based), ascending.
pub endings: Vec<u32>,
pub start: TimeAnchor,
pub end: TimeAnchor,
}
\end{lstlisting}
The \texttt{endings} constraints (non-empty, 1-based, strictly
ascending) are \emph{advisory}, on the \texttt{OctaveOffset} pattern:
a violating value is representable, decoders and reduction accept it,
and the authoring validation layer flags it. Rendering treats an
unlistable ending set as a plain bracket.
The v1-to-v2 migration default for a kindless v1 repeat structure is
\texttt{SimpleRepeat} with \texttt{count}~$=$~2: a v1
\texttt{RepeatStructure} \emph{meant} a repeat, and playing the span
twice is the conventional semantics of an unadorned repeat sign.
Voltas default empty.
\subsection{Analytical Annotations}
Analytical annotations record analyses of the music: Roman numerals,
form labels, motivic brackets, set-theory analyses, Schenkerian
graphs. They are first-class and may appear on dedicated analysis
layers.
\begin{lstlisting}[language=Rust]
pub struct AnalyticalAnnotation {
pub id: AnalyticalAnnotationId,
pub kind: AnalyticalKind,
pub anchor: AnnotationAnchor,
/// Optional analysis layer. None means the engraved layer;
/// Some(id) targets a specific analysis layer.
pub layer: Option<AnalysisLayerId>,
pub content: AnalyticalContent,
}
pub enum AnalyticalKind {
RomanNumeral,
FunctionalLabel,
SetTheoryLabel,
SchenkerianGraph,
FormSection,
MotivicBracket,
Custom(CustomAnalyticalKindId),
}
pub enum AnnotationAnchor {
Event(EventId),
Range { start: TimeAnchor, end: TimeAnchor },
Region(RegionId),
}
\end{lstlisting}
\subsection{Comments}
Comments are review-mode annotations: discussion threads anchored to
points or ranges in the score.
\begin{lstlisting}[language=Rust]
pub struct Comment {
pub id: CommentId,
pub anchor: AnnotationAnchor,
pub thread: Vec<CommentMessage>,
pub resolved: bool,
}
pub struct CommentMessage {
pub author: AuthorId,
pub timestamp: Timestamp,
pub body: String,
}
\end{lstlisting}
\subsection{Graphic Gestures}
A graphic gesture is a drawn or constructed graphic element that
spans events, staves, or regions and is not contained within a single
graphic region. Used for performance-direction sweeps,
multi-staff curved gestures, and similar.
\begin{lstlisting}[language=Rust]
pub struct GraphicGesture {
pub id: GraphicGestureId,
/// The graphic objects composing this gesture. Stored in the
/// canvas's graphic-object storage.
pub objects: Vec<GraphicObjectId>,
/// Anchoring: how this gesture is positioned relative to score
/// content.
pub anchoring: GestureAnchoring,
/// Optional playback parameter bindings.
pub playback_bindings: Vec<PlaybackBinding>,
}
pub enum GestureAnchoring {
/// Anchored to events: the gesture moves with them.
Events(Vec<EventId>),
/// Anchored to a time and staff range.
Range {
start: TimeAnchor,
end: TimeAnchor,
staves: Vec<StaffId>,
},
/// Free canvas coordinates: does not follow score edits.
Free(CanvasCoordinates),
}
\end{lstlisting}
\section{Graphic Content}
\label{sec:graph:graphic}
Free-graphic and hybrid regions carry graphic content: vector
primitives, drawn strokes, text, and images. Graphic content is
expressed in the region's local coordinate space.
\begin{lstlisting}[language=Rust]
pub struct GraphicContent {
pub objects: Vec<GraphicObject>,
/// The region's local coordinate system. Region transforms
/// map this to canvas coordinates.
pub coordinate_system: LocalCoordinateSystem,
}
\end{lstlisting}
\subsection{Graphic Objects}
\begin{lstlisting}[language=Rust]
pub struct GraphicObject {
pub id: GraphicObjectId,
/// The object's geometric kind.
pub kind: GraphicObjectKind,
/// Transform applied within the region's coordinate space.
pub transform: Transform2D,
/// Layer ordering: higher values draw on top of lower.
pub layer: i32,
/// Visual style: stroke color, fill, line weight, opacity.
pub style: GraphicStyle,
/// Optional time binding. See Section~\ref{sec:graph:timebinding}.
pub time_binding: Option<TimeBinding>,
}
pub enum GraphicObjectKind {
Path(PathData),
Shape(ShapePrimitive),
Text(TextData),
Image(ImageData),
Group(Vec<GraphicObjectId>),
Stroke(StrokeData),
}
pub enum ShapePrimitive {
Line { start: Point2D, end: Point2D },
Rectangle { origin: Point2D, size: Size2D },
Ellipse { center: Point2D, radii: Size2D },
Polygon(Vec<Point2D>),
Bezier(Vec<BezierSegment>),
}
pub struct StrokeData {
/// Pressure-sensitive stylus stroke samples: position, pressure,
/// tilt, and time per sample.
pub samples: Vec<StrokeSample>,
}
pub struct StrokeSample {
pub position: Point2D,
pub pressure: f32, // 0.0..=1.0
pub tilt_xy: (f32, f32), // radians
pub timestamp: WallClockTime,
}
\end{lstlisting}
\subsection{Coordinates}
\begin{requirement}
\label{req:graph:region-local-coordinates}
Graphic objects \MUST{} be stored in region-local coordinates. The
region's transform maps local coordinates to canvas coordinates.
This permits regions to be moved, resized, or otherwise transformed
without rewriting the coordinates of every contained object.
\end{requirement}
\subsection{Time Bindings}
\label{sec:graph:timebinding}
A graphic object \MAY{} carry an optional time binding, which assigns
a temporal meaning to the object beyond its visual appearance. Time
bindings are the mechanism by which a drawn curve becomes a performance
instruction (a filter sweep, an amplitude envelope, a density
trajectory).
\begin{lstlisting}[language=Rust]
pub struct TimeBinding {
/// What musical or wall-clock range this graphic spans.
pub time_extent: TimeExtent,
/// What parameter, if any, this graphic drives. None means the
/// graphic is decorative; Some specifies a named parameter.
pub parameter: Option<ParameterBinding>,
/// How the graphic's geometry maps to parameter values.
pub mapping: ParameterMapping,
}
pub struct ParameterBinding {
pub target: ParameterTarget,
pub parameter_name: String,
pub value_range: ParameterRange,
}
pub enum ParameterTarget {
/// A specific event's parameter (e.g., this event's velocity).
Event(EventId),
/// A voice-level parameter (e.g., voice expression CC).
Voice(VoiceId),
/// A staff-level parameter.
Staff(StaffId),
/// A region-level parameter.
Region(RegionId),
/// A score-global parameter (e.g., master gain).
Score,
}
pub enum ParameterMapping {
/// Vertical position in region coordinates maps linearly to
/// parameter value.
VerticalLinear,
/// Vertical position with exponential mapping.
VerticalExponential,
/// Stroke pressure maps to parameter value.
StrokePressure,
/// Stroke velocity maps to parameter value.
StrokeVelocity,
/// Custom mapping defined by a plugin.
Registered(MappingRegistryId),
}
\end{lstlisting}
\begin{rationale}
Time bindings make graphic-notation playback expressible in the core
data model without committing to specific audio engine semantics.
The core stores what parameter a graphic drives and how its
geometry maps to values; the audio engine spec consumes this
binding and applies it. Decorative graphics (binding is \texttt{None})
remain pure visual content.
\end{rationale}
\section{Parts}
\label{sec:graph:parts}
A part is a per-instrument or per-section view onto the score,
intended for printed extraction or focused performer use. Parts live
separately from the canvas; they are projections defined by a
part-extraction specification.
\begin{lstlisting}[language=Rust]
pub struct PartDefinition {
pub id: PartDefinitionId,
/// Human-readable name (e.g., "Flute 1", "Violins II").
pub name: String,
/// Which staves are included in this part, in order from top
/// to bottom.
pub staves: Vec<StaffId>,
/// Part-specific layout overrides: page size, system count,
/// cue insertions, multirest consolidation, page-turn placement.
pub layout_overrides: PartLayoutOverrides,
/// Per-event visibility overrides specific to this part (e.g.,
/// hide a cue in the full score but show it in the part).
pub visibility_overrides: Vec<VisibilityOverride>,
/// Auto-cue insertions: source staves to draw cues from when
/// the part has long rests.
pub auto_cue_sources: Vec<StaffId>,
}
\end{lstlisting}
\subsection{Parts Are Projections, Not Storage}
\begin{requirement}
\label{req:graph:part-content-projection}
A part \MUSTNOT{} store musical content directly; it \MUST{} consist
only of references to score content and overrides. Edits to score
content \MUST{} propagate to all parts that reference that content.
\end{requirement}
\section{Analysis Layers and Views}
\label{sec:graph:layers}
\subsection{Analysis Layers}
Analysis layers, introduced for spelling in Section~\ref{sec:pitch:layers}
and used by analytical annotations, are first-class objects on the
score.
\begin{lstlisting}[language=Rust]
pub struct AnalysisLayer {
pub id: AnalysisLayerId,
pub name: String,
pub description: Option<String>,
/// Which views display this layer.
pub visible_in_views: Vec<ViewId>,
}
\end{lstlisting}
\subsection{Views}
A view is a recipe for displaying a configuration of the score:
which analysis layers are active, which parts are rendered, what
overrides apply. Views are how the system supports condensed scores,
analytical views, study scores, and lead-sheet projections.
\begin{lstlisting}[language=Rust]
pub struct ViewDefinition {
pub id: ViewId,
pub name: String,
pub kind: ViewKind,
/// Which analysis layers are active in this view.
pub active_layers: Vec<AnalysisLayerId>,
/// View-specific overrides.
pub overrides: ViewOverrides,
}
pub enum ViewKind {
FullScore,
Part(PartDefinitionId),
CondensedScore(CondensationSpec),
LeadSheet(LeadSheetSpec),
Analysis(AnalysisViewSpec),
Custom(CustomViewKindId),
}
\end{lstlisting}
\section{Graph Invariants}
\label{sec:graph:invariants}
The score graph maintains a set of structural invariants. Implementations
\MUST{} preserve these invariants across all edits.
\begin{requirement}
\label{req:graph:score-graph-invariants}
The following invariants hold over every well-formed score graph:
\begin{enumerate}
\item Every event in the arena has a valid \texttt{voice} field
pointing to a voice that lists the event in its \texttt{events}
vector.
\item Every event in a voice's events list has its \texttt{voice}
field pointing back to that voice.
\item Events within a voice are sorted by position and do not
overlap in time.
\item Every event's \texttt{EventPosition} and
\texttt{EventDuration} variants agree with the time model of
its enclosing region (Section~\ref{sec:graph:event-time}). For
aleatoric regions, agreement is governed by the region's
declared \texttt{AleatoricAnchoringDiscipline}.
\item Every voice belongs to exactly one \texttt{StaffInstance};
every \texttt{StaffInstance} belongs to exactly one region;
every region belongs to the canvas.
\item Every \texttt{StaffInstance.staff} resolves to a
\texttt{Staff} declared at the score level. A single
\texttt{StaffId} \MAY{} be referenced by multiple
\texttt{StaffInstance}s in different regions (this is how
continuous staff identity is expressed across regions); a
\texttt{StaffId} \MUSTNOT{} be referenced by two
\texttt{StaffInstance}s in the same region.
\item Region time and staff extents do not simultaneously overlap
(Section~\ref{sec:graph:canvas}). A region's
\texttt{staff\_extent} lists exactly the \texttt{StaffId}s
manifested by the region's \texttt{staff\_instances}, with no
duplicates.
\item Measures belong to exactly one \texttt{StaffInstance}. A
region's metric organization is per-staff (admitting
polymeter); barline alignment across staves is recorded via
\texttt{BarlineAlignmentGroup} entries when explicit alignment
is authoritative.
\item Every \texttt{TimeAnchor}'s \texttt{AnchorOffset} variant
agrees with the time model of the target object's enclosing
region (Section~\ref{sec:time:anchors}).
\item Every cross-cutting structure's references resolve to extant
objects in the graph, except where explicit re-anchoring rules
permit transient dangling states during edits (see
Chapter~\ref{ch:semops}).
\item Every identifier in the graph is unique within its kind.
Identifiers reserved for system-derived objects (notably
\texttt{VoiceId}s of system-promoted voices) \MUST{} be derived
from the deterministic function specified for their kind and
\MUSTNOT{} collide with user-authored identifiers.
\item Every \texttt{IdentifiedPitch} in the arena has a
\texttt{PitchId} that is unique within the score's
pitch-identity index.
\item Every \texttt{SpellingScope::Pitch(PitchId)} resolves to a
live or tombstoned \texttt{PitchId} in the pitch-identity
index. A live target resolves to exactly one
\texttt{IdentifiedPitch}; a tombstoned target is preserved for
operation-log replay, undo, conflict reporting, and historical
annotation, and \MUSTNOT{} be silently re-matched to a
different live pitch.
\item Every notational decomposition attachment's target
\texttt{EventId} resolves to a live or tombstoned event;
attachments to tombstoned events follow the same preservation
discipline as for tombstoned pitches.
\item For live decomposition attachments, the targeted event's
duration matches the decomposition's component sum.
\item Every tuplet's member event durations sum to the tuplet's
structurally-required total
(Section~\ref{sec:time:tuplets}). Operations that would break
this invariant \MUST{} include compensating changes
(Section~\ref{sec:semops:deleteevent}).
\item Every \texttt{Tie}'s \texttt{pitch\_pairing} entries
reference \texttt{PitchId}s that resolve to identified pitches
belonging to the start and end events respectively. The
class-specific adjacency, voice-membership, and
pitch-equivalence rules of Section~\ref{sec:graph:ties} hold
for the tie's declared \texttt{TieClass}.
\item Every \texttt{Voice} declares an \texttt{origin} consistent
with how it was created: \texttt{UserDeclared} for explicit
user actions, \texttt{Imported} for foreign-format imports,
\texttt{SystemPromoted} for voices allocated by concurrent-edit
conflict resolution. The \texttt{VoiceId} of a
\texttt{SystemPromoted} voice \MUST{} be the deterministic
derivation specified in
Section~\ref{sec:graph:promoted-voices}.
\item Every \texttt{BarlineAlignmentGroup}'s members reference
\texttt{StaffInstance}s within the same region and
\texttt{Measure}s belonging to those instances. Group members
\MUSTNOT{} reference staff instances or measures across
region boundaries.
\item (Genesis tranche G3b.) A measure's declared time signature
\texttt{Measure.time\_signature}, when it resolves, AGREES with
the effective metric grid's active signature at the measure's
start; and consecutive measure starts within a staff instance
are separated by the governing signature's
\texttt{measure\_duration()} (BOUNDARY consistency).
\texttt{time\_signature: None} exempts only the agreement
clause --- the inherited meter still governs boundary
consistency. This invariant \MUSTNOT{} duplicate the
signature-\emph{resolution} check above (invariant 10): it
compares only already-resolving signatures. It ABSTAINS ---
raises no violation --- wherever the comparison or the
distance cannot be decided (base-ingested data may predate
this rule); this is deliberate abstention, not a soundness
gap. A pickup/anacrusis first measure has no predecessor, so
only the BOUNDARY clause is vacuous for it; the AGREEMENT
clause is not predecessor-dependent and still applies (unless
\texttt{time\_signature} is \texttt{None} or agrees with the
governing grid). Regardless of what the pickup itself
declares, its successor's boundary distance is measured
against this same governing signature's full
\texttt{measure\_duration()}, and IS flagged when that
distance is the pickup's own shorter length rather than a
full bar.
\end{enumerate}
Implementations \MUST{} reject graph configurations that violate any
of the above invariants. Edits proposed in Chapter~\ref{ch:semops}
must either preserve invariants directly or include compensating
changes that restore them within the same operation.
\end{requirement}
This enumeration contains exactly \textbf{20} invariants. (Earlier
summary material, including the QUICKSTART, referred to ``18 graph
invariants''; a subsequent revision of this document corrected that
to 19; genesis tranche G3b
(\texttt{spec/CONTRACT\_GENESIS\_G3B\_MEASURE.md}) then appended a
20th, measure-meter consistency, above --- the authoritative count is
20, matching this enumeration and the reference implementation.)
These 20 are
\emph{runtime} invariants: they hold over every well-formed graph and
are restored by compensating changes when an edit would break them.
Distinct from them are three \emph{construction-time} rejections
defined in Chapter~\ref{ch:time}, which a conforming constructor
\MUST{} enforce before a value ever enters the graph, and which no
runtime invariant restores because the offending value is never a
representable graph state:
\begin{enumerate}
\item A \texttt{TimeSignature}'s beat groups \MUST{} sum to the
measure duration (Section~\ref{sec:time:meter}).
\item An \texttt{EventOrderingDAG} \MUST{} be acyclic
(Requirement~\ref{req:time:ordering-dag-acyclic}).
\item A \texttt{TupletRatio} \MUSTNOT{} be degenerate
(Section~\ref{sec:time:tuplets},
Requirement~\ref{req:time:tuplet-ratio-construction}).
\end{enumerate}
\section{Indexes and Auxiliary Structures}
\label{sec:graph:indexes}
Performance-critical operations require indexed access. The
specification states the required indexes; implementations \MAY{}
construct additional indexes provided they are kept consistent.
\begin{requirement}
\label{req:graph:required-indexes}
Implementations \MUST{} maintain at least the following indexes:
\begin{description}
\item[Event time index.] Maps (region, voice) to a sorted view
of event positions, supporting $O(\log n)$ time-range queries.
\item[Cross-cutting reference index.] Maps each object identifier
to the cross-cutting structures referencing it, supporting
$O(1)$ amortized lookup for re-anchoring.
\item[Measure index.] Maps measure number (and measure id) to
\texttt{MeasureId} for fast navigation.
\item[Spelling attachment index.] Maps each \texttt{PitchId} to
its attachments, partitioned by analysis layer.
\end{description}
Indexes \MUST{} be invalidated and rebuilt (or incrementally
updated) on the corresponding edits. The semantic operations
chapter specifies which operations invalidate which indexes.
\end{requirement}
\section{Forward References}
\begin{itemize}
\item The complete catalog of semantic operations, their
preconditions, postconditions, and re-anchoring rules is in
Chapter~\ref{ch:semops}.
\item The mapping from score graph to layout intermediate
representation is defined in Chapter~\ref{ch:layout-ir}.
\item The on-disk serialization of every type introduced in this
chapter is defined in Chapter~\ref{ch:format}.
\item Some engraving-specific types (\texttt{StemConfiguration},
\texttt{ArticulationMark}, and similar) are introduced informally
here and fully defined in Chapter~\ref{ch:layout-ir} where their
interaction with the engraver is specified. The clef/key content
model (\texttt{Clef}, \texttt{ClefShape}, \texttt{KeySignature},
\texttt{ClefChange}, \texttt{KeySignatureChange}) and
\texttt{LineStyle} are defined normatively \emph{in this chapter}
as of schema major~2; the former \texttt{ClefId} identifier sketch
is retired in favour of embedded \texttt{Clef} values.
\end{itemize}
% ===========================================================================
\chapter{Semantic Operations and Concurrent Reduction}
\label{ch:semops}
This chapter specifies the semantic operations on the score graph: the
mutations through which the data model becomes a live model. It defines
the operation framework, the canonical reduction procedure by which a
set of operations becomes a materialized score state, the object
existence model with tombstones, the conflict-record machinery, the
re-anchoring rules, transactions, and undo. The chapter ends with
representative operation specifications.
The collaboration mechanics specified here are the foundation on which
Chapter~\ref{ch:format}'s synchronization protocols build. The
materialized score is the deterministic reduction of an operation set;
the file format stores that operation set together with optional
acceleration snapshots.
\section{Design Principles}
\label{sec:semops:principles}
\begin{description}
\item[The operation set is canonical.] A score's state is defined
by the set of operations that have been committed to it. Any
materialized graph is a deterministic reduction of that set;
caches, snapshots, and partial reductions are acceleration
structures, never the source of truth.
\item[The replicated operation set is a CRDT; the materialized
graph is not.] The grow-only set of operation envelopes with
compact causal metadata is a true CRDT: replicas independently
accumulate envelopes and converge on the same set. The
materialized score graph is not itself claimed to be a CRDT; it
is the deterministic reduction of that set, defined by the
reduction algorithm specified here.
\item[Pairwise operation merge is rejected.] Musical operations
do not, in general, commute in intention: transpose-then-respell
differs musically from respell-then-transpose; delete-then-tie
differs from tie-then-delete. The framework therefore does not
attempt to make every pair of operations algebraically commute.
Instead, operations are reduced in a canonical order, and that
order determines the outcome.
\item[Conflicts, no-ops, and rejections are replicated facts.] No
operation silently disappears because it could not apply. Every
operation has a deterministic \texttt{OperationEffect} recorded
as part of the materialized state; every replica reducing the
same operation set records the same effects with the same
reasons.
\item[Identifier-based targeting.] Operations target objects by
identifier, never by structural path. Identifiers are stable
under concurrent edits; paths are not.
\item[Tombstones preserve identity.] Deleted objects retain their
identifiers as tombstones. References to tombstoned identifiers
are not silently invalidated; they enter a tombstoned-target
state unless the operation kind specifies deterministic repair.
\item[Transactions materialize atomically.] Primitive members of a
transaction \MUSTNOT{} be independently visible as committed
score states. Either the whole transaction reduces successfully,
or the transaction transitions to a conflict effect with a
recorded conflict.
\item[Undo is forward, not backward.] Undo is a new operation that
computes a compensating edit against the current materialized
state; it is not literal time travel. History remains
append-only.
\item[Two validation modes for advisory checking.] Operations are
subject to strict precondition checking in \emph{authoring mode}
(interactive edits) and lenient checking in \emph{replay mode}
(loading historical operations or applying remote operations).
Validation modes apply only to advisory preconditions;
invariant preconditions are enforced unconditionally.
\end{description}
\section{The Operation Framework}
\label{sec:semops:framework}
\subsection{Operation Identity and Stamps}
Operations carry two related but distinct concepts: \emph{identity}
(who authored the operation, with what counter) and \emph{stamp}
(when the operation was committed, for ordering purposes).
\begin{lstlisting}[language=Rust]
/// Stable identity of an operation. Determined at authoring time.
pub struct OperationId {
pub replica: ReplicaId,
pub counter: u64,
}
/// Ordering metadata for an operation. Used to derive the canonical
/// reduction order; not part of the operation's identity.
pub struct OperationStamp {
pub hlc: HybridLogicalClock,
pub id: OperationId,
}
/// A hybrid logical clock combines wall-clock and logical components.
pub struct HybridLogicalClock {
/// Physical time component, in canonical units (see Chapter 8).
pub physical_time: WallClockTime,
/// Logical counter, advanced when physical time does not.
pub logical_counter: u32,
}
\end{lstlisting}
\begin{requirement}
\label{req:semops:operation-stamp-stability}
\texttt{OperationId} \MUST{} be stable: an operation's identity is
fixed at the moment of authoring and \MUSTNOT{} change with
reordering, retransmission, or merging into operation sets at
other replicas.
\texttt{OperationStamp} \MUST{} be set by the authoring replica
using a hybrid logical clock that respects causal order: for any
operation $A$ that is in operation $B$'s causal predecessor set,
$A$'s stamp \MUST{} be strictly less than $B$'s under the
canonical comparison (Section~\ref{sec:semops:reduction-order}).
\end{requirement}
\subsection{Causal Context via Dotted Version Vectors}
Operations carry a compact causal context, not an exhaustive
predecessor list. This is essential: exhaustive predecessor lists
scale linearly with history and become untenable.
\begin{lstlisting}[language=Rust]
/// A compact causal context: for each replica known to the authoring
/// replica, the highest contiguous counter observed, plus any
/// individual operation identifiers known but not contiguous (the
/// "dots").
pub struct CausalContext {
/// Contiguous counter highest known per replica.
pub vector: BTreeMap<ReplicaId, u64>,
/// Individual operations known but not yet contiguous in the
/// vector. Used when an authoring replica has observed an
/// operation whose causal predecessors include operations not
/// yet present.
pub dots: BTreeSet<OperationId>,
}
\end{lstlisting}
\begin{requirement}
\label{req:semops:dotted-version-vectors}
Causal contexts \MUST{} be expressed as dotted version vectors as
defined above. Exhaustive predecessor lists \MUSTNOT{} be used as
the canonical causal representation; they may appear in debugging
output or in the operation log's expanded form, but the wire and
storage representation of causal context is the DVV.
The per-replica counter floor is \textbf{zero-based} and
normative: \texttt{vector[r] == n} asserts that \emph{every}
operation \texttt{(r,\,0..=n)} is a causal predecessor --- the
contiguous range begins at counter \texttt{0}, not \texttt{1}. A
conforming implementation \MUSTNOT{} adopt a one-based floor: doing
so would shift the contiguous/dot boundary and make two
implementations disagree about which operations are pending versus
ready. The membership check (``is operation \texttt{(r,\,c)}
covered?'') is \texttt{c <= vector[r]} or \texttt{(r,\,c) in dots};
an implementation \MAY{} evaluate it by walking the known operation
ids rather than materializing the full \texttt{0..=n} range, so a
sparse high-counter context does not force work proportional to the
counter value.
Interval tree clocks and other compact causal representations
\MAY{} appear in future revisions as performance optimizations but
are non-normative in this specification.
\end{requirement}
\begin{rationale}
Dotted version vectors are well-understood, well-tested, and the
right complexity tradeoff for the human-scale collaboration model
Epiphany targets. Their normative use here closes the high-priority
review item about exhaustive predecessor lists scaling badly with
history.
\end{rationale}
\subsection{Operation Envelopes}
An operation is transmitted, stored, and reduced as an
\emph{envelope}: the operation's payload together with its identity,
ordering stamp, causal context, and optional transaction grouping.
\begin{lstlisting}[language=Rust]
pub struct OperationEnvelope {
/// Stable identifier of this operation.
pub id: OperationId,
/// Author of this operation (may differ from replica in shared
/// authoring sessions where multiple authors share a replica).
pub author: AuthorId,
/// Ordering stamp. Used for canonical reduction order; never
/// for identity.
pub stamp: OperationStamp,
/// Compact causal context: what operations were visible to the
/// authoring replica when this operation was created.
pub causal_context: CausalContext,
/// Optional transaction grouping. Multiple operations sharing a
/// TransactionId are reduced atomically.
pub transaction: Option<TransactionId>,
/// The mutation itself.
pub payload: OperationPayload,
}
pub enum OperationPayload {
Primitive(OperationKind),
/// A meta-operation that explicitly resolves a previously-recorded
/// conflict (see Section~\ref{sec:semops:conflicts}).
ResolveConflict(ResolveConflictPayload),
/// A meta-operation that compensates for a previously-committed
/// transaction; the realization of "undo" (see
/// Section~\ref{sec:semops:undo}).
UndoTransaction(UndoTransactionPayload),
/// A meta-operation that resolves an equivocated operation slot
/// by naming the chosen candidate envelope (see
/// Section~\ref{sec:semops:equivocation}; payload schema in the
/// Operation Catalog companion).
ResolveEquivocation(ResolveEquivocationPayload),
}
/// The catalog of primitive operation kinds. The variants listed
/// here are normative for the core; additional kinds are introduced
/// by registered extensions via the Registered variant. The full
/// catalog including detailed payload schemas is the Operation
/// Catalog companion specification
/// (Appendix~\ref{app:deferred}).
pub enum OperationKind {
// Event operations
InsertEvent(InsertEventOp),
DeleteEvent(DeleteEventOp),
ModifyEvent(ModifyEventOp),
// Pitch and tuning operations
RespellPitch(RespellPitchOp),
Transpose(TransposeOp),
// The faithful transposition (Push 4a); Transpose above is frozen.
TransposeInterval(TransposeIntervalOp),
InsertIdentifiedPitch(InsertIdentifiedPitchOp),
DeleteIdentifiedPitch(DeleteIdentifiedPitchOp),
ModifyIdentifiedPitch(ModifyIdentifiedPitchOp),
// Cross-cutting operations
CreateCrossCutting(CreateCrossCuttingPayload),
DeleteCrossCutting(DeleteCrossCuttingPayload),
ModifyCrossCutting(ModifyCrossCuttingPayload),
// Repeat-structure operations
CreateRepeatStructure(CreateRepeatStructureOp),
DeleteRepeatStructure(DeleteRepeatStructureOp),
// Region and structure operations
ChangeRegionTimeModel(ChangeRegionTimeModelOp),
InsertRegion(InsertRegionOp),
DeleteRegion(DeleteRegionOp),
InsertStaffInstance(InsertStaffInstanceOp),
DeleteStaffInstance(DeleteStaffInstanceOp),
InsertStaff(InsertStaffOp),
CreateVoice(CreateVoiceOp),
DeleteVoice(DeleteVoiceOp),
// Genesis tranche G1: mints an abstract instrument on the score root ---
// the single missing link between Score::empty and a note.
CreateInstrument(CreateInstrumentOp),
// Metric-model operations
SetTimeSignature(SetTimeSignatureOp),
SetTempoSegment(SetTempoSegmentOp),
SetMetricGrid(SetMetricGridOp),
// Score settings
SetMetadata(SetMetadataOp),
// Genesis tranche G2a: the two major-0 settings setters, on the
// SetMetadata advisory-LWW pattern.
SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp),
SetSpellingPrecedence(SetSpellingPrecedenceOp),
// Genesis tranche G2b: the sole genesis payload born at schema major 3,
// on the same advisory-LWW pattern. Carries TuningContextSettings, the
// authored subset of ScoreTuningContext (accidental_extensions is not
// on the wire and is left untouched by reduction).
SetTuningContext(SetTuningContextOp),
// Genesis tranche G3a: the four remaining root-level Score entity
// mints, on the InsertStaff set-union mint pattern. Staff.group is the
// sole authority for group membership (see the Staff/StaffGroup
// declarations below); CreateStaffGroup stores members as carried and
// neither maintains nor trusts it.
CreateStaffGroup(CreateStaffGroupOp),
CreatePartDefinition(CreatePartDefinitionOp),
CreateAnalysisLayer(CreateAnalysisLayerOp),
CreateView(CreateViewOp),
// Genesis tranche G3b: the final rung of the genesis ladder. Appends a
// Measure to a live StaffInstance (StaffInstance.measures), on the
// InsertStaffInstance parent-carrying mint pattern rather than the
// bare-value shape above -- Measure has no back-pointer to its parent.
CreateMeasure(CreateMeasureOp),
// Layout-semantic operations
SetUserSystemBreak(SetUserSystemBreakOp),
SetUserPageBreak(SetUserPageBreakOp),
SetStaffLayout(SetStaffLayoutOp),
// Transaction declaration. Carries metadata; member primitives
// reference the transaction by id in their envelopes.
DeclareTransaction(TransactionDescriptor),
// Extension-defined primitive operation.
Registered(OperationKindRegistryId, SerializedRegisteredOp),
}
\end{lstlisting}
\subsection{The Operation Set as CRDT}
\begin{requirement}
\label{req:semops:grow-only-operation-set}
The replicated operation set is a grow-only CRDT: replicas
accumulate envelopes by gossip, broadcast, or any other
delivery mechanism, and converge on the same set when they have
observed the same envelopes. The set is set-valued, not
multiset-valued: an envelope is a member of the set or it is
not, with no count.
A replica \MUSTNOT{} discard envelopes from the operation set
except by an explicit pruning operation (Chapter~\ref{ch:format}),
which preserves canonical state through retained base snapshots
and the post-pruning operation set.
\end{requirement}
\subsection{OperationId Collisions and Replica Equivocation}
\label{sec:semops:equivocation}
Two received envelopes may carry the same \texttt{OperationId}.
Acceptance must not depend on arrival order: if it did, two
replicas observing the same envelopes in different orders could
diverge on which one is canonical. The operation set therefore
maps each \texttt{OperationId} to an \texttt{OperationSlot}
rather than directly to an envelope.
\begin{lstlisting}[language=Rust]
pub enum OperationSlot {
/// Exactly one canonical envelope is known for this
/// OperationId. The envelope reduces normally.
Single(OperationEnvelope),
/// Two or more distinct canonical envelopes have been
/// observed for this OperationId. The slot is equivocated.
/// The operation produces no canonical effect until external
/// recovery resolves the equivocation.
Equivocated {
operation_id: OperationId,
/// Every distinct canonical envelope observed for this
/// OperationId. Identified by content hash to avoid
/// duplicating large payloads. Sorted lexicographically by
/// hash for deterministic enumeration.
candidates: BTreeSet<EnvelopeHash>,
},
}
/// BLAKE3-256 hash of a canonical envelope serialization with
/// domain tag "MUSCENVH".
pub struct EnvelopeHash(pub [u8; 32]);
\end{lstlisting}
\begin{requirement}
\label{req:semops:operation-equivocation}
Slot transitions are determined as follows, independently of
arrival order:
\begin{itemize}
\item If no slot exists for the incoming envelope's
\texttt{OperationId}: the incoming envelope is
well-formedness checked
(Section~\ref{sec:semops:acceptance}) and, if well-formed,
a \texttt{Single} slot is created.
\item If a \texttt{Single} slot exists and the incoming
envelope is byte-identical (canonical serialization) to
the slot's envelope: the duplicate is dropped silently;
the slot is unchanged.
\item If a \texttt{Single} slot exists and the incoming
envelope's canonical bytes differ from the slot's
envelope: the slot transitions to \texttt{Equivocated} with
both envelopes' hashes in its \texttt{candidates} set. The
previously-canonical envelope is retained in a diagnostic
candidate store keyed by its hash; the incoming envelope is
likewise retained. Neither envelope contributes to canonical
reduction.
\item If an \texttt{Equivocated} slot exists: the incoming
envelope's hash is added to its \texttt{candidates} set if
not already present; otherwise the duplicate is dropped.
The envelope is retained in the candidate store. The slot
remains \texttt{Equivocated}.
\end{itemize}
An equivocated slot \MUSTNOT{} contribute to canonical
reduction. Reduction proceeds as if the operation does not
exist: any operation that causally depends on the equivocated
\texttt{OperationId} is reduced under the same rule as a
missing causal predecessor (the dependent operation is
retained but produces no effect until the equivocation is
resolved).
Resolution is by explicit recovery action, not by arrival
order or content. Three resolution mechanisms are permitted:
\begin{itemize}
\item \emph{Transport-level credential revocation}: the
collaboration transport revokes the equivocating replica's
credentials and the user accepts one envelope timeline as
canonical via an explicit reconciliation operation.
\item \emph{Local user choice}: the user (or an authorized
administrator) issues an explicit
\texttt{ResolveEquivocation} operation naming the
\texttt{OperationId} and the chosen
\texttt{EnvelopeHash}, promoting the slot back to
\texttt{Single}.
\item \emph{Profile-declared deterministic policy}: a
conformance profile \MAY{} declare a deterministic
selection function (e.g., ``lowest canonical envelope
hash wins''). This is a policy choice, not a default. A
bundle reduced under a profile with a declared resolution
policy is canonical under that profile; the same bundle
reduced under a profile without such a policy remains
anomalous.
\end{itemize}
If a bundle on disk contains an equivocated slot without a
resolution policy or pending resolution operation, the bundle
is anomalous. Readers \MUST{} open it, if at all, in a
diagnostic recovery mode that surfaces the equivocation
prominently and that prohibits ordinary editing of the
affected portion of canonical state.
\end{requirement}
\begin{rationale}
Replica equivocation (the same replica producing two distinct
envelopes with the same \texttt{OperationId}) is a real
failure mode: replica-key compromise, software bug, replay
attack, or filesystem corruption can all produce it. The
\texttt{OperationSlot} model makes equivocation a
representable, order-independent state: two replicas that
have observed the same set of envelopes converge on the same
slot states regardless of the order of observation. Treating
the first arrival as canonical would let a malicious
early-arriver pin canonical state at one replica while a
late-arriver pins different state at another replica; the
slot model avoids that asymmetry by refusing to elevate
either candidate without an explicit resolution action.
Recovery is a policy choice rather than a built-in default
because the right answer depends on context: a collaborating
team with credential revocation will resolve differently
from a single-user backup-restore scenario, which differs
again from an adversarial-document forensic scenario.
\end{rationale}
\subsection{Envelope Acceptance and Malformed-Envelope Behavior}
\label{sec:semops:acceptance}
Operation envelopes arrive from local authoring and from remote
replicas. Acceptance rules determine when an envelope enters the
operation set.
\begin{requirement}
\label{req:semops:envelope-well-formedness}
An incoming envelope is well-formed if and only if all of the
following hold:
\begin{itemize}
\item Its \texttt{OperationId} consists of a 64-bit
\texttt{ReplicaId} that is not
\texttt{ReplicaId::SYSTEM\_DERIVED} (operations are not
authored by the system namespace) and a 64-bit counter.
\item Its \texttt{stamp.id} is byte-identical to its
top-level \texttt{id} field: an envelope's stamp \MUST{}
address the same operation as the envelope itself. This
eliminates an ambiguity where an authoring replica could
otherwise stamp an envelope as belonging to a different
operation than its identity declares.
\item Its \texttt{stamp.hlc.physical\_time} is a finite,
non-negative integer value, expressed in the canonical
time unit (Chapter~\ref{ch:format}).
\item Its causal context's DVV references only well-formed
replica identifiers and non-negative integer counter
values; dots are well-formed \texttt{OperationId}s.
\item Its payload deserializes successfully against the
profile's declared operation catalog.
\end{itemize}
An envelope that is not well-formed \MUST{} be rejected at
reception. Rejection is recorded in the local diagnostic log
but does \emph{not} enter the canonical operation set. Remote
replicas \MAY{} retransmit; persistent rejection indicates a
non-conforming peer.
Acceptance \MUSTNOT{} require trust in the envelope's
\texttt{stamp.hlc} content: a malicious or misconfigured peer
may emit envelopes with implausible future physical times or
zero/extreme logical counters. The canonical reduction order
(Section~\ref{sec:semops:reduction-order}) consumes the stamp
as ordering metadata; it does not validate plausibility.
Implementations \MAY{} surface clock-skew warnings in
diagnostics but \MUSTNOT{} reorder envelopes for plausibility
reasons that would deviate from the canonical reduction order.
\end{requirement}
\subsection{Per-Replica HLC Monotonicity}
\label{sec:semops:hlc-monotonicity}
Per-replica monotonicity is a property of the \emph{set} of
accepted envelopes, not of arrival order. Out-of-order delivery
is normal in gossip systems and is not a violation.
\begin{lstlisting}[language=Rust]
/// A range of envelopes from a single replica that have been
/// identified as monotonicity-violating and excluded from
/// ordinary canonical reduction. Retained for diagnostic and
/// recovery purposes.
pub struct AnomalousReplicaSegment {
pub replica: ReplicaId,
/// The smallest counter at or after which the replica's
/// stream is anomalous. All envelopes from this replica
/// with counter >= first_bad_counter are excluded from
/// canonical reduction.
pub first_bad_counter: u64,
/// The detected anomaly reason.
pub reason: ReplicaAnomalyReason,
/// Operation ids in the excluded segment, retained for
/// diagnostic recovery. Sorted by counter.
pub excluded: Vec<OperationId>,
}
pub enum ReplicaAnomalyReason {
/// The replica produced an envelope at counter c2 with a
/// stamp tuple strictly less than a previously-known
/// envelope at counter c1 < c2.
HlcMonotonicityViolation {
violating_pair: (OperationId, OperationId),
},
/// A registered anomaly reason defined by an extension or
/// transport.
Registered(ReplicaAnomalyRegistryId),
}
\end{lstlisting}
\begin{requirement}
\label{req:semops:per-replica-hlc-monotonicity}
For any two envelopes accepted into the operation set that
share the same authoring \texttt{ReplicaId}, with operation-
counter values $c_1 < c_2$:
\begin{quote}
The stamp tuple
\texttt{(stamp.hlc.physical\_time, stamp.hlc.logical\_counter,
id.counter)} of the envelope with counter $c_1$ \MUST{} be
lexicographically less than or equal to the corresponding
tuple of the envelope with counter $c_2$.
\end{quote}
Arrival order \MUSTNOT{} be used to determine whether this
property holds. If a replica receives counter 10 before
counter 9, that is a property of delivery, not of monotonicity.
If two envelopes from the same replica violate this property
(their stamp tuples are inconsistent with their counter order),
the document carries a \textbf{replica equivocation anomaly}.
An \texttt{AnomalousReplicaSegment} is created (or extended)
for the offending replica. The segment's
\texttt{first\_bad\_counter} is the smaller of the two
counters in the violating pair.
Effects on canonical reduction:
\begin{itemize}
\item Every envelope from the offending replica with counter
$\geq$ \texttt{first\_bad\_counter} is excluded from
canonical reduction. Such envelopes are retained on disk
and in memory (the CRDT property does not allow silent
removal) but are held in the
\texttt{AnomalousReplicaSegment}'s \texttt{excluded} list,
not in the canonical operation set.
\item Envelopes from the offending replica with counter
$<$ \texttt{first\_bad\_counter} remain in the canonical
operation set and reduce normally.
\item Any operation in the canonical set that causally
depends on an excluded envelope reduces under the
missing-causal-predecessor rule: the dependent operation
is retained, produces no canonical effect, and is held
pending resolution.
\item The document is marked as containing a replica-
equivocation anomaly. Ordinary editing \MAY{} continue on
the unaffected portion of canonical state; collaborative
synchronization with the equivocating replica \MUST{} be
suspended until external resolution.
\end{itemize}
Resolution paths mirror the \texttt{OperationSlot} equivocation
rules (Section~\ref{sec:semops:equivocation}): transport-level
credential revocation followed by an explicit reconciliation
operation, an explicit local recovery action, or a
profile-declared deterministic policy.
Authentication of replica identities and revocation of
compromised replicas are the responsibility of the
collaboration transport (Appendix~\ref{app:deferred}). This
chapter specifies only local detection, segment formation,
exclusion from canonical reduction, and the structural shape
of resolution.
\end{requirement}
\begin{rationale}
Excluding the violating segment from canonical reduction
rather than letting it reduce-but-mark-tainted is the safer
choice: a tainted reduction still produces canonical-looking
state derived from operations the spec has flagged as
untrustworthy. Excluding the segment keeps canonical state
derived only from operations that satisfy the monotonicity
invariant, at the cost of holding pending operations that
causally depended on the excluded segment.
\end{rationale}
\section{The Canonical Reduction}
\label{sec:semops:reduction}
The materialized score graph is the deterministic reduction of the
operation set. This section specifies how that reduction is performed.
\subsection{Operation Lifecycle}
\label{sec:semops:lifecycle}
Every operation traverses four phases:
\begin{description}
\item[Prepare.] The local UI command validates enough to construct
a well-formed operation envelope. \emph{Prepare is advisory
validation only}: failure here means the UI rejects the user's
action and never authors the operation. Prepare does not bind
semantics; an operation that successfully prepares at one replica
\MAY{} be reduced to a conflict effect at another.
\item[Commit.] The operation envelope is added to the replicated
operation set. After commit, the operation is part of canonical
state and \MUSTNOT{} be discarded outside the pruning protocol.
\item[Reduce.] During materialization, the operation is processed
by the canonical reduction algorithm against the current
materialized state. Reduction is deterministic: every replica
with the same operation set produces the same materialized
state.
\item[Report.] The reduction produces a deterministic
\texttt{OperationEffect} for each operation. Effects are visible
graph facts: users can inspect what each operation did, and
subsequent operations can address conflicts and no-ops directly.
\end{description}
\subsection{The Reduction Algorithm}
\begin{requirement}
\label{req:semops:reduction-algorithm}
The materialized score state is computed from the operation set as
follows:
\begin{enumerate}
\item Sort the operation set into the canonical reduction order
(Section~\ref{sec:semops:reduction-order}).
\item Group consecutive operations that share a transaction id
into transaction blocks.
\item For each non-transaction operation, apply the operation to
the working state, computing its \texttt{OperationEffect}.
\item For each transaction block, apply all member operations
atomically: either all succeed and the transaction reduces to
\texttt{Applied} (with each member's individual effect
recorded), or some member fails its invariant preconditions,
in which case the whole transaction reduces to a
\texttt{Conflicted} effect with a recorded conflict.
\item Record every effect in the materialized state's effect log
(which is part of canonical state, not implementation detail).
\end{enumerate}
Given the same operation set, conformant implementations \MUST{}
produce score states with byte-identical structural content and
byte-identical effect logs. Differences in implementation language,
storage layout, threading model, or hardware \MUSTNOT{} produce
divergent reductions.
\end{requirement}
\subsection{Canonical Reduction Order}
\label{sec:semops:reduction-order}
\begin{requirement}
\label{req:semops:canonical-reduction-order}
The canonical reduction order is the lexicographic ordering by:
\begin{enumerate}
\item Causal order: for operations $A$ and $B$, if $A$ is in
$B$'s causal context (transitively, via DVV closure), then
$A$ precedes $B$.
\item For operations not ordered by causal order (i.e.,
concurrent operations), lexicographic order by:
\begin{enumerate}
\item \texttt{stamp.hlc.physical\_time} (ascending)
\item \texttt{stamp.hlc.logical\_counter} (ascending)
\item \texttt{stamp.id.replica} (ascending, lexicographic on
the replica identifier's canonical byte form)
\item \texttt{stamp.id.counter} (ascending)
\end{enumerate}
\end{enumerate}
Causal order strictly dominates HLC order: wall-clock skew may
affect the ordering of concurrent operations but \MUSTNOT{} cause
a causal predecessor to sort after a causal successor. The HLC
authoring rule (Section~\ref{sec:semops:framework}) guarantees this
by construction.
\end{requirement}
\subsection{Operation Effects}
\begin{lstlisting}[language=Rust]
/// The deterministic effect of an operation under canonical
/// reduction. Recorded as part of materialized state.
pub enum OperationEffect {
/// The operation applied cleanly with no compensating changes.
Applied,
/// The operation applied with deterministic compensating changes
/// (e.g., re-anchoring records, attachment migration).
AppliedWithRepair { repairs: Vec<RepairRecord> },
/// The operation could not apply cleanly; a conflict record was
/// created in the conflict registry.
Conflicted { conflict: ConflictId },
/// The operation's target was tombstoned. The operation is
/// preserved in the operation set but has no effect on the
/// materialized graph beyond the recorded effect.
TombstonedTarget { target: TypedObjectId },
/// The operation reduces to no effect. The reason is recorded
/// and is part of canonical state.
NoOp { reason: NoOpReason },
}
pub enum NoOpReason {
/// The operation's target was tombstoned by a causally-prior
/// operation, and the target operation has no compensating
/// repair rule.
TargetTombstoned,
/// The operation duplicates a causally-prior operation's effect.
AlreadyApplied,
/// A later operation in the canonical order subsumed this
/// operation's effect (e.g., a later DeleteEvent on the same
/// target supersedes an earlier RespellPitch).
SupersededByLaterOperation { superseder: OperationId },
/// An invariant precondition that was satisfied at authoring
/// time fails when the operation is reduced under concurrent
/// edits. Distinguished from Conflicted because the operation's
/// intent is not preserved: it simply has no applicable effect.
/// The failure reason is a typed enum, not a free-form string:
/// canonical effects MUST NOT contain free-form text, since two
/// implementations could otherwise produce divergent canonical
/// state while agreeing semantically.
PreconditionFailedUnderReduction {
reason: PreconditionFailureReason,
},
/// The operation belonged to a transaction whose other members
/// failed; the transaction was conflicted, and this operation
/// produced no individual effect.
TransactionConflict,
}
pub enum PreconditionFailureReason {
/// The target object identifier did not exist in the working
/// state at the moment of reduction.
TargetMissing,
/// The target object was tombstoned by a causally-prior
/// operation. Distinguished from NoOpReason::TargetTombstoned
/// in that the kind's rules permitted an attempt at
/// repair-on-tombstone, but the repair itself failed precondition.
TargetTombstoned,
/// The operation requires a region time model (e.g., metric)
/// different from the one currently in effect at the target
/// position.
WrongRegionTimeModel,
/// A tuplet compensation declared by the operation is invalid
/// against the current tuplet structure at the target.
TupletCompensationInvalid,
/// An event duration the operation specifies is invalid in
/// the target voice or region (e.g., overlapping a tied
/// chord-onset boundary in an incompatible way).
EventDurationInvalid,
/// The operation's target position falls outside the region
/// declared by its envelope, or addresses a region that does
/// not exist.
PositionOutsideRegion,
/// A pitch-space or tuning-context precondition failed: e.g.,
/// the operation declared a pitch in a pitch space that is not
/// active at the target region.
PitchSpaceMismatch,
/// A voice precondition failed: the operation targeted a
/// voice that does not exist or has been tombstoned.
VoiceMissing,
/// An extension-declared precondition failed. The extension
/// is identified; the extension's own catalog enumerates the
/// specific precondition codes.
ExtensionPrecondition(ExtensionPreconditionId),
/// Registered precondition code defined by a versioned
/// registry.
Registered(PreconditionFailureRegistryId),
}
/// A deterministic compensating change made during reduction. Each
/// RepairRecord describes one repair the reduction performed to
/// preserve graph invariants in the presence of a tombstoning or
/// migrating operation. Recorded as part of canonical state.
pub struct RepairRecord {
/// The kind of repair performed.
pub kind: RepairKind,
/// The object affected by this repair.
pub target: TypedObjectId,
}
pub enum RepairKind {
/// A reference was re-anchored to a surviving target per the
/// re-anchoring rule table (Section~\ref{sec:semops:reanchor}).
Reanchored {
from: TypedObjectId,
to: TypedObjectId,
reason: ReanchorReason,
},
/// A spanner or beam had members removed but enough remained
/// for the structure to survive.
SpannerTruncated {
removed_members: Vec<TypedObjectId>,
},
/// A user-content reference was orphaned (target lost; reference
/// preserved for diagnostic purposes).
Orphaned,
/// A reference whose existence required its target was
/// cascade-deleted.
CascadeDeleted,
/// An attachment transitioned to tombstoned-target state.
AttachmentTombstoned,
/// A voice was promoted to a system-derived voice identity due
/// to concurrent insertion collision.
VoicePromoted {
from: VoiceId,
to: VoiceId,
},
/// A tuplet was compensated according to the operation's
/// declared TupletCompensation.
TupletCompensated {
compensation_kind: TupletCompensationKind,
},
/// A registered extension-defined repair kind.
Registered(RepairKindRegistryId),
}
\end{lstlisting}
\begin{requirement}
\label{req:semops:total-operation-effects}
Every operation in the operation set \MUST{} produce exactly one
\texttt{OperationEffect} under reduction. Effects \MUST{} be
deterministic: every replica reducing the same operation set in
the canonical order produces the same effect for each operation,
including identical \texttt{NoOpReason} values where applicable.
\end{requirement}
\subsection{Cache and Snapshot Discipline}
\label{sec:semops:caches}
\begin{requirement}
\label{req:semops:cache-transparency}
Caches, snapshots, and partial reductions are acceleration
structures only. They \MUSTNOT{} affect the canonical materialized
state, which is defined solely by the operation set, the active
conformance profile, and the normative reduction algorithm.
Implementations \MUST{} invalidate or recompute caches whenever any
of the following change:
\begin{itemize}
\item The visible operation set (additions, or pruning operations
under the file-format protocol).
\item The causal order derived from any envelope's causal
context.
\item The reduction algorithm version declared by the active
conformance profile.
\item The active conformance profile itself.
\end{itemize}
Cache hits \MUST{} return data that is bit-identical to the data a
fresh reduction would produce. Caches that cannot guarantee this
property \MUST{} be invalidated.
\end{requirement}
\section{Object Existence and Tombstones}
\label{sec:semops:tombstones}
\subsection{Add-Tombstone Semantics}
Object lifetimes in the materialized graph follow add-tombstone
semantics: an operation creates an object by minting a stable
identifier, and an operation deletes an object by tombstoning that
identifier. Tombstoned identifiers are retained; their resolution
yields a deletion record rather than the original object.
\begin{requirement}
\label{req:semops:tombstone-state-machine}
Identifiers \MUST{} have one of three states in the materialized
graph:
\begin{itemize}
\item \textbf{Live.} The identifier resolves to a current object.
\item \textbf{Tombstoned.} The identifier resolves to a deletion
record carrying the operation that performed the deletion and
the operation that originally minted the identifier.
\item \textbf{Unknown.} The identifier has not been seen by this
replica. This state is observable in collaborative scenarios
where envelopes arrive out of order; it resolves to either Live
or Tombstoned once causal predecessors arrive.
\end{itemize}
Implementations \MUSTNOT{} resurrect tombstoned identifiers
(recreating an object with a previously-tombstoned id) except
through an explicit inverse-of-delete operation in the operation
set. Independent re-creation is forbidden.
\end{requirement}
\subsection{Tombstone Retention}
\begin{requirement}
\label{req:semops:tombstone-retention}
Tombstones \MUST{} be retained while any of the following hold:
\begin{itemize}
\item Pending replication: operations referencing the tombstoned
identifier may not yet have been observed by all replicas.
\item Pending undo: a transaction containing the deletion is
within undo range.
\item Active conflict resolution: a conflict record references
the tombstoned identifier and has not been resolved.
\item Attachment resolution: a tombstoned-target attachment
remains in the score for historical or diagnostic purposes.
\end{itemize}
Tombstone garbage collection is governed by the pruning protocol
in Chapter~\ref{ch:format}. Tombstones \MUSTNOT{} be discarded
outside that protocol.
\end{requirement}
\subsection{Reference Resolution Across Tombstones}
\begin{requirement}
\label{req:semops:tombstoned-reference-resolution}
A reference to a tombstoned identifier resolves to a
\texttt{TombstonedTarget} state rather than being silently
invalidated. Operations targeting tombstoned identifiers reduce to
\texttt{TombstonedTarget} or \texttt{NoOp} effects according to
their kind's declared rules (Section~\ref{sec:semops:examples}).
Re-anchoring rules (Section~\ref{sec:semops:reanchor}) define how
cross-cutting structures and attachments respond to tombstoned
references at reduction time.
\end{requirement}
\section{Conflict Records}
\label{sec:semops:conflicts}
When an operation cannot apply cleanly, the reduction produces a
\texttt{Conflicted} effect referencing a \texttt{ConflictRecord} in
the canonical conflict registry (a component of materialized state;
see below). Conflict records are first-class: stable, addressable,
visible to users, and addressable by subsequent operations.
\subsection{Conflict Record Type}
\begin{lstlisting}[language=Rust]
pub struct ConflictRecord {
/// Content-derived identifier (Section below).
pub id: ConflictId,
/// The operations that participated in producing this conflict.
/// At least two for a true conflict; one for an operation that
/// failed precondition checking under reduction.
pub caused_by: Vec<OperationId>,
/// The kind of conflict, governing what resolution options
/// exist.
pub kind: ConflictKind,
/// Objects affected by this conflict, for diagnostic and UI
/// navigation purposes.
pub affected_objects: Vec<TypedObjectId>,
/// Current resolution state. Conflicts begin Unresolved; a
/// later ResolveConflict operation transitions them to Resolved
/// or Dismissed.
pub resolution_state: ConflictResolutionState,
}
pub enum ConflictKind {
/// Two concurrent operations attempted to write the same
/// non-LWW field. The winning operation's effect is in the
/// materialized state; the losing operation's effect is in the
/// conflict record for user inspection.
StructuralFieldCollision {
winner: OperationId,
loser: OperationId,
field: FieldPath,
},
/// A transaction's primitive members were partially applicable
/// under reduction; the whole transaction was rejected.
TransactionConflict {
transaction: TransactionId,
failed_members: Vec<OperationId>,
},
/// An operation targeted a tombstoned object and could not be
/// repaired by the kind's declared rules.
TombstonedTarget {
target: TypedObjectId,
operation: OperationId,
},
/// Re-anchoring failed: no deterministic target could be found.
ReanchorFailure {
original_referent: TypedObjectId,
referencing_object: TypedObjectId,
},
/// A change-region-time-model operation produced contained
/// events whose coordinate kinds are incompatible with the new
/// time model; the migration could not be performed
/// deterministically.
TimeModelMigrationFailure {
region: RegionId,
incompatible_events: Vec<EventId>,
},
/// A registered extension operation's reduction failed in a
/// way described by the extension. Opaque to the core.
ExtensionConflict {
kind_id: ConflictKindRegistryId,
details: SerializedExtensionConflict,
},
}
pub enum ConflictResolutionState {
Unresolved,
Resolved { by: OperationId, action: ResolutionAction },
Dismissed { by: OperationId },
}
// See Requirement (field-collision effect tag): for a
// StructuralFieldCollision the winner (later op) reads Conflicted;
// the earlier op retains Applied.
pub enum ResolutionAction {
/// Accept the losing operation's effect (replacing the winner).
AcceptLoser,
/// Reapply the winner explicitly (no semantic change; clears the
/// conflict).
KeepWinner,
/// User-authored replacement that supersedes both.
Override { override_operation: OperationId },
/// Re-anchor to a user-chosen target.
Reanchor { new_target: TypedObjectId },
/// Dismiss the conflict without changing the materialized graph:
/// the user acknowledges it and accepts the current (winner)
/// state. Selects the Dismissed resolution state.
Dismiss,
/// Custom resolution for registered conflict kinds.
Registered(ResolutionRegistryId),
}
\end{lstlisting}
\begin{requirement}
\label{req:semops:resolution-action-discriminants}
The discriminant byte of \texttt{ResolutionAction} is its
declaration-order index in the listing above: \texttt{AcceptLoser}
$= 0$, \texttt{KeepWinner} $= 1$, \texttt{Override} $= 2$,
\texttt{Reanchor} $= 3$, \texttt{Dismiss} $= 4$, \texttt{Registered}
$= 5$. A \texttt{ResolutionAction} is encoded into the
\texttt{ResolveConflict} operation payload and therefore into that
operation's content hash, so this assignment is normative and stable
and \MUSTNOT{} be reordered. \texttt{Dismiss} was assigned \texttt{4}
ahead of \texttt{Registered} (\texttt{5}) when the \texttt{Dismissed}
resolution state was made reachable by an authored operation
(Section~\ref{sec:semops:conflict-resolution} below), fixing the wire
form against a silent shift.
\end{requirement}
\begin{requirement}
\label{req:semops:field-collision-effect}
When two concurrent operations write the same non-LWW field and
reduction records a \texttt{StructuralFieldCollision}, the
per-operation \texttt{OperationEffect} is assigned as follows: the
\emph{winner} --- the operation later in canonical order, whose
choice materializes and whose processing created the conflict record
--- reads \texttt{Conflicted\{conflict\}}; the \emph{earlier}
(losing) operation retains the \texttt{Applied} effect it received
when it was first reduced. The winner carries the flag because it is
the single operation that both materializes the field and observes
the collision, which makes the assignment independent of the order
in which envelopes arrive at any replica.
\end{requirement}
\begin{rationale}
The alternative --- tagging the loser \texttt{Conflicted} (or
\texttt{NoOp\{SupersededByLaterOperation\}}) because its intent did
not survive --- is defensible and arguably more intuitive for a UI
that surfaces ``your edit was overridden.'' We pin
\emph{winner-carries} instead because it is computed at the moment
the collision is detected (when the winning operation is reduced
against already-present state), needs no second pass to retroactively
re-tag an earlier operation, and is manifestly order-independent. A
UI that wishes to tell the losing author their edit was overridden
reads the \texttt{StructuralFieldCollision} record's \texttt{loser}
field rather than the loser operation's effect.
\end{rationale}
\subsection{The Conflict Registry}
Canonical materialized state carries a top-level conflict registry
(the score graph does not --- conflict records are a reduction
product, not authored content):
\begin{lstlisting}[language=Rust]
pub struct ConflictRegistry {
pub records: Vec<ConflictRecord>,
}
\end{lstlisting}
\begin{requirement}
\label{req:semops:conflict-registry-persistence}
The conflict registry is part of canonical materialized state.
Conflict records persist until they are resolved or dismissed by
explicit \texttt{ResolveConflict} operations. Conflict records
\MUSTNOT{} be silently removed; their lifetime is governed by
the operation set, not by UI state.
The layout pipeline (Chapter~\ref{ch:layout-ir}) \SHOULD{} provide
visual indication of unresolved conflicts at their affected
objects; this is the user-facing manifestation of the conflict
registry's contents.
\end{requirement}
\subsection{ConflictId Derivation}
\label{sec:semops:conflict-id}
Conflict records are produced during deterministic reduction, not
authored by replicas. Their identifiers \MUST{} therefore be
content-derived, not minted from a local replica-plus-counter
sequence. Otherwise, two replicas reducing the same operation set
would produce conflict registries with disagreeing identifiers,
breaking canonical state.
\begin{requirement}
\label{req:semops:conflict-id-derivation}
Each \texttt{ConflictId} \MUST{} be derived deterministically from
the conflict's content via BLAKE3 truncation:
\begin{lstlisting}[language=Rust]
fn derive_conflict_id(
kind: &ConflictKind,
causing_operations: &[OperationId],
affected_objects: &[TypedObjectId],
) -> ConflictId {
let mut preimage = Vec::new();
preimage.extend_from_slice(b"MUSCCONF");
preimage.extend_from_slice(&kind.canonical_bytes());
// Sort causing operations lexicographically by canonical bytes
let mut sorted_ops = causing_operations.to_vec();
sorted_ops.sort_by(|a, b| a.canonical_bytes().cmp(&b.canonical_bytes()));
for op in &sorted_ops {
preimage.extend_from_slice(&op.canonical_bytes());
}
// Sort affected objects lexicographically by canonical bytes
let mut sorted_objs = affected_objects.to_vec();
sorted_objs.sort_by(|a, b| a.canonical_bytes().cmp(&b.canonical_bytes()));
for obj in &sorted_objs {
preimage.extend_from_slice(&obj.canonical_bytes());
}
let hash = blake3(&preimage);
ConflictId(u128::from_be_bytes(
hash[0..16].try_into().unwrap()
))
}
\end{lstlisting}
The canonical \texttt{ConflictKind} byte representation \MUST{}
include the kind discriminant and all kind payload (winning
operation, losing operation, field path, transaction id,
tombstoned target, original referent, region, extension kind
identifier, etc.), so that distinct conflicts have distinct
preimages by construction. Implementations \MUSTNOT{} introduce
an ordinal or local counter into the preimage; conflicts that
share kind, causing operations, and affected objects are by
definition the same conflict.
\end{requirement}
\begin{rationale}
Content-derived identifiers are the only mechanism that makes
the conflict registry a deterministic materialized fact. Every
replica reducing the same operation set produces the same
conflict records with the same identifiers, addressable by
subsequent \texttt{ResolveConflict} operations whose payloads
carry those identifiers by reference.
Truncation from 256 to 128 bits is safe: the birthday-collision
probability for any practical conflict count is vanishingly
small, and the same truncation discipline already governs other
128-bit identifiers in the format.
\end{rationale}
\subsection{Conflict Resolution Operations}
\label{sec:semops:conflict-resolution}
\begin{lstlisting}[language=Rust]
pub struct ResolveConflictPayload {
pub target: ConflictId,
pub action: ResolutionAction,
}
\end{lstlisting}
\begin{requirement}
\label{req:semops:conflict-resolution-state}
A \texttt{ResolveConflict} operation transitions a conflict's
state from \texttt{Unresolved} to one of two states selected by its
\texttt{action}. The \texttt{ResolutionAction::Dismiss} action
transitions the conflict to \texttt{Dismissed} (no semantic change
to the materialized graph beyond the conflict record's state: the
user acknowledges the conflict and accepts the current winner
state). Every other \texttt{action} transitions the conflict to
\texttt{Resolved} with that action applied. Both resolution states
are thus reachable by an authored operation; \texttt{Dismiss} is the
authored selector for \texttt{Dismissed}, closing the prior gap in
which \texttt{Dismissed} was a representable state with no operation
to reach it.
Concurrent \texttt{ResolveConflict} operations on the same
conflict are themselves subject to canonical reduction: the
earlier operation under the canonical order is applied; the
later operation reduces to \texttt{NoOp} with reason
\texttt{AlreadyApplied} if its action is equivalent, or to
\texttt{Conflicted} with a meta-conflict record if its action
differs.
\end{requirement}
\section{Transactions}
\label{sec:semops:transactions}
Transactions are first-class replicated operation groups. They reduce
atomically: either every primitive member applies, or the whole
transaction conflicts.
\begin{lstlisting}[language=Rust]
pub struct TransactionDescriptor {
pub id: TransactionId,
/// Human-readable label, for the undo history and diagnostics.
pub label: String,
/// Optional categorization (used by UIs and analytics).
pub category: Option<TransactionCategory>,
}
pub struct TransactionId(pub u128);
/// Open vocabulary: a minimal core set plus a Registered escape for
/// extension-defined categories.
pub enum TransactionCategory {
NoteEntry,
Structural,
Layout,
Import,
Registered(OperationKindRegistryId),
}
\end{lstlisting}
\begin{requirement}
\label{req:semops:transaction-category}
\texttt{TransactionCategory} is an \emph{open} vocabulary. Its
normative core set is exactly \texttt{NoteEntry}, \texttt{Structural},
\texttt{Layout}, and \texttt{Import}; any other category \MUST{} be
expressed as \texttt{Registered(OperationKindRegistryId)} rather than
by extending the core set. The field is advisory (consumed by UIs and
analytics, never by canonical reduction), so the minimal core set is
the right tradeoff: under-specifying is cheap to extend through
\texttt{Registered}, whereas baking speculative categories into the
core vocabulary is not. The discriminants follow declaration order
(\texttt{NoteEntry} $= 0$ through \texttt{Registered} $= 4$).
\end{requirement}
Each primitive operation belonging to a transaction carries the
transaction's identifier in its envelope's \texttt{transaction} field.
The transaction descriptor itself is a separate operation envelope
(\texttt{DeclareTransaction}) whose payload contains the
\texttt{TransactionDescriptor}.
\subsection{Transaction Descriptor Causal Ordering}
\label{sec:semops:transaction-causal}
The canonical reduction order is causal-first, then HLC, then
operation id (Section~\ref{sec:semops:reduction-order}). For the
descriptor to be available when its members are reduced, the
ordering between descriptor and members must be guaranteed by
causal dependency, not by HLC stamps.
\begin{requirement}
\label{req:semops:transaction-descriptor-causality}
Every primitive operation member of a transaction \MUST{} causally
depend on the transaction's \texttt{DeclareTransaction} envelope:
the descriptor's \texttt{OperationId} \MUST{} appear in the
member's \texttt{causal\_context} (either in the per-replica DVV
range or among its dots). Authoring implementations \MUST{}
observe and include the descriptor in the causal context of each
member they author.
Delivery order need not respect this dependency: an out-of-order
delivery in which a member arrives before its descriptor is
normal and is handled by the canonical reduction algorithm,
which sorts by causal order before reducing.
At reduction time:
\begin{itemize}
\item If the descriptor is present in the operation set and
causally precedes every member: the transaction is
well-formed; it is reduced atomically per the rule below.
\item If a primitive operation declares membership in a
transaction whose \texttt{DeclareTransaction} envelope is
absent from the operation set, or whose envelope is present
but does not causally precede the member: the member is
malformed against the transaction model. It reduces to
\texttt{NoOp\{reason: TransactionConflict\}} and contributes
to a synthesized conflict record of kind
\texttt{ConflictKind::TransactionConflict} naming the
malformed member and the missing-or-misordered descriptor.
The member is preserved in the operation set (the CRDT
property prohibits removal) but produces no effect on
canonical state beyond the recorded conflict.
\end{itemize}
\end{requirement}
\begin{rationale}
Requiring causal dependency rather than HLC ordering matches
how transactions actually arise at the authoring replica: a
user begins an edit (descriptor), then commits its primitives.
The primitives can causally observe the descriptor because they
are authored after it on the same replica. The canonical
reduction order then handles them correctly without any special
transaction-extraction phase.
This rule also makes equivocating descriptors impossible:
a member cannot claim membership in a transaction whose
descriptor it has not seen.
\end{rationale}
\begin{requirement}
\label{req:semops:atomic-transaction-reduction}
Transactions reduce atomically. During reduction:
\begin{itemize}
\item All primitive members of a transaction are gathered into
a transaction block at the point of the earliest member's
canonical position.
\item The block is reduced as a single unit. Each member is
evaluated against the working state produced by prior members
within the same block.
\item If every member's invariant preconditions are satisfied,
the transaction reduces to \texttt{Applied}; each member's
individual effect is recorded as part of the transaction's
composite effect.
\item If any member's invariant preconditions fail, the entire
transaction reduces to \texttt{Conflicted}: a conflict record
is created with kind
\texttt{ConflictKind::TransactionConflict}, listing the
failed members; no member's effect is applied; each member's
individual effect is recorded as
\texttt{NoOp\{reason: TransactionConflict\}}.
\end{itemize}
Primitive members of a transaction \MUSTNOT{} be observable as
independently committed score states. Observers see either the
pre-transaction state or the fully applied post-transaction
state; they never see a partial intermediate.
\end{requirement}
\begin{rationale}
Atomic transaction reduction is essential for the tuplet
compensation model (Section~\ref{sec:semops:deleteevent}): the
delete operation and its compensating insert (or rewrite, or
cascade) are committed together as a single transaction, so the
score is never observably broken between them.
Local materialization may stream a transaction's primitives to the
UI for responsive feedback, but the materialized canonical state
visible to remote observers transitions only at transaction
boundaries.
\end{rationale}
\section{Re-Anchoring}
\label{sec:semops:reanchor}
When an operation tombstones an object, every other object
referencing it must respond deterministically. The response is
specified as a total deterministic function.
\subsection{The Re-Anchor Function}
\begin{lstlisting}[language=Rust]
pub enum ReanchorResult {
/// The reference is replaced with a reference to a new target.
Reanchored {
new_target: TypedObjectId,
reason: ReanchorReason,
},
/// The reference is preserved but its target is tombstoned.
/// The referencing object remains in the graph in a
/// tombstoned-target state.
TombstonedTarget,
/// The referencing object is marked orphaned but retained.
Orphaned,
/// Re-anchoring cannot be performed deterministically; a
/// conflict is recorded.
Conflicted { conflict: ConflictId },
/// The referencing object is cascade-deleted (tombstoned).
CascadeDeleted,
}
pub enum ReanchorReason {
SameVoiceNearer,
SameStaffInstanceNearer,
SameStaffNearer,
SameRegionNearer,
ExplicitFallback,
DeclaredByExtension(ReanchorReasonRegistryId),
/// A rank-4 (same-canvas) proximity survivor. Appended in
/// Pass 12 (P12-C4); its wire discriminant is 6 because
/// DeclaredByExtension already owned 5 when it was appended.
SameCanvasNearer,
}
\end{lstlisting}
A re-anchor that selects a rank-4 (same-canvas) survivor records
\texttt{SameCanvasNearer} (ratified Pass~12); before the variant was
appended, such repairs were recorded \texttt{ExplicitFallback}, a
recording this revision supersedes.
\subsection{Total Ordering for "Nearest"}
The notion of ``nearest surviving anchor'' is defined as a total
ordering. There is no discretionary search and no implementation
freedom to choose among ``equally near'' candidates.
\begin{requirement}
\label{req:semops:nearest-reanchor-order}
For an object kind $K$ requiring re-anchoring to the nearest
surviving $K$, ``nearest'' is computed as the strict lexicographic
minimum over the surviving candidates of:
\begin{enumerate}
\item Containment proximity (smaller is closer): same voice
$(0)$, same staff instance $(1)$, same staff $(2)$, same
region $(3)$, same canvas $(4)$. Candidates further away than
the kind's declared maximum proximity are excluded.
\item Absolute time distance from the tombstoned referent's
resolved position, measured under a canonical conversion
(musical time within metric regions, wall-clock time within
proportional regions, declared per-region for aleatoric
regions).
\item Direction preference: forward $(0)$ before backward $(1)$,
unless the kind declares the opposite preference.
\item Object identifier (lexicographic on the typed identifier's
canonical byte form, ascending) as the final tie-break.
\end{enumerate}
If no candidate satisfies the kind's declared proximity bound, the
result is \texttt{Orphaned} (for user-content kinds such as
comments and analytical annotations) or \texttt{CascadeDeleted}
(for kinds whose existence requires their target, such as ties
and beams).
Numeric proximity bounds, direction preferences, and the
orphaned-versus-cascade choice are declared per kind in the
re-anchoring rule table below; they are part of the kind's
conformance contract and \MUSTNOT{} be reinterpreted by
implementations.
\end{requirement}
\subsection{The Re-Anchoring Rule Table}
The following table specifies, for each (referencing kind, referent
kind) pair, the re-anchoring action under canonical reduction. The
table is normative.
\begin{longtable}{p{3.0cm} p{2.6cm} p{2.4cm} p{4.4cm}}
\toprule
\textbf{Referencing} & \textbf{Referent} & \textbf{Action} &
\textbf{Parameters} \\
\midrule
\endhead
Slur & Endpoint event &
Re-anchor to the nearest surviving endpoint; cascade-delete only
when no endpoint survives &
a two-endpoint slur collapses onto its sole survivor (a
reference-clean degenerate form); proximity-aware re-targeting
is a deferred refinement \\
Slur & Interior event &
No action &
--- \\
Tie & Either endpoint &
Cascade-delete &
Tie's existence requires both endpoints. \\
Beam & Member event &
Truncate. Cascade-delete if fewer than two members remain &
proximity max: same voice \\
Tuplet & Member event &
Operation \MUST{} provide tuplet compensation
(Section~\ref{sec:semops:deleteevent}); otherwise the operation
conflicts at precondition check &
--- \\
Spanner & Anchor &
Re-anchor to the nearest surviving anchor; cascade-delete only
when no anchor survives &
as for slurs (surviving-endpoint collapse); per-spanner-kind
proximity bounds are a deferred refinement \\
Repeat structure & Anchor &
Re-anchor to the nearest surviving anchor; cascade-delete only
when no anchor survives &
as for spanners; the rule spans \emph{every} event-referencing
anchor site of the structure --- \texttt{start}/\texttt{end},
the kind's jump targets (\texttt{DaCapo.end\_target},
\texttt{DalSegno.segno}/\texttt{end\_target}), and each
volta's span. Among multiple surviving candidates ``nearest''
is currently the deterministic identifier-order minimum;
proximity-aware (four-key) selection is a deferred refinement,
as for spanners (ratified with the repeat-authoring pair,
schema-major-2 revision) \\
Marker & Anchor &
Re-anchor to nearest event in same staff instance &
proximity max: same staff instance; orphan on failure \\
Comment & Anchor &
Orphan &
User content never silently deleted. \\
Cue event & Source event &
Cascade-delete on any source deletion &
a multi-source cue cascades when \emph{any} source dies
(ratified Pass~12): losing any source breaks the cue's
quotation integrity; truncate-while-any-source-survives was
considered and rejected \\
Graphic gesture & Anchor event &
Re-anchor to nearest surviving event of same staff instance;
for Free anchoring, no action; for Range anchoring, truncate &
proximity max: same staff instance. Truncate (ratified
Pass~12): a dead event-anchored range endpoint moves to its
containing region's edge --- a start endpoint to the region
start, an end endpoint to the region end, zero offset \\
Trajectory event & Endpoint pitch &
Re-anchor: replace \texttt{EventPitch(PitchId)} with
\texttt{ExplicitPitch} capturing the tombstoned pitch's last
known value &
--- \\
Analytical annotation & Anchor &
Re-anchor to time range preserving original extent; orphan if
range cannot be reconstructed &
orphaning is the sanctioned outcome (ratified Pass~12) for a
wall-clock (region-relative) or indeterminate event span that
no stored \texttt{Range} anchor form can express without
region-origin resolution \\
Spelling attachment & Target pitch &
Transition to tombstoned-target state &
Attachment is preserved for diagnostic and undo purposes. \\
Decomposition attachment & Target event &
Transition to tombstoned-target state &
Attachment preserved for diagnostic and undo purposes. \\
Voice & Containing staff instance &
Cascade-delete &
--- \\
Event & Containing voice &
Cascade-delete &
--- \\
Region & Canvas &
Refuse (canvas is not deletable) &
--- \\
Part definition & Referenced staff &
Remove staff from part's staff list; orphan if part becomes
empty &
--- \\
\bottomrule
\end{longtable}
\begin{requirement}
\label{req:semops:synchronous-reanchoring}
Re-anchoring \MUST{} be performed as part of the same reduction
step that tombstones the referent. The resulting graph state
\MUST{} satisfy every invariant in
Section~\ref{sec:graph:invariants}. Re-anchoring actions \MUST{}
be recorded as \texttt{RepairRecord} entries in the triggering
operation's effect.
\end{requirement}
\section{Undo}
\label{sec:semops:undo}
Undo is not literal time travel: it is the authoring of a new
operation that compensates for a previously-committed transaction
against the current materialized state. This preserves the append-
only operation set and is correct under concurrent edits.
\subsection{UndoTransaction Payload}
\begin{lstlisting}[language=Rust]
pub struct UndoTransactionPayload {
/// The transaction being undone.
pub target: TransactionId,
/// Policy governing how to undo when the target's effects
/// have been partially superseded by later operations.
pub policy: UndoPolicy,
}
pub enum UndoPolicy {
/// Compute the inverse semantic operations against the current
/// materialized state. If any cannot apply cleanly (because the
/// target objects have been tombstoned, fields have been
/// further modified, etc.), the entire undo conflicts.
StrictInverse,
/// Undo what remains valid; record conflicts or no-ops for the
/// rest. The undo operation succeeds, but partial.
BestEffort,
/// Also undo operations that are causally or semantically
/// dependent on the target transaction. Dependency is computed
/// from causal contexts and explicit dependency links;
/// implementations MUST NOT infer broad musical dependence
/// heuristically.
Cascade,
}
\end{lstlisting}
\subsection{Undo Semantics}
\begin{requirement}
\label{req:semops:compensating-undo}
An \texttt{UndoTransaction} operation is committed to the operation
set like any other operation; it does not modify history. Its
reduction computes a compensating edit against the materialized
state at the point of its canonical position:
\begin{itemize}
\item For \texttt{StrictInverse}: the inverse of each primitive
in the target transaction is computed against the current
working state. If every inverse applies cleanly, the undo
transaction reduces to \texttt{Applied} with all compensating
effects recorded. If any inverse cannot apply (target
tombstoned, field modified, etc.), the undo transaction
reduces to \texttt{Conflicted} with a conflict record.
\item For \texttt{BestEffort}: inverses that apply cleanly are
applied; inverses that cannot are recorded as \texttt{NoOp}
effects with their reasons. The undo transaction reduces to
\texttt{AppliedWithRepair}.
\item For \texttt{Cascade}: the dependency set is computed first
(causal closure plus explicit dependency links). The
transaction's inverses, plus inverses of dependents in
reverse order, are applied per the chosen sub-policy
(typically \texttt{StrictInverse}).
\end{itemize}
Equality of the resulting graph state to the pre-target state is
defined as \emph{content equivalence}: every live object's fields,
every attachment, every cross-cutting structure matches. Operation
identifiers, stamps, undo stacks, caches, and storage layout are
\emph{not} required to match.
\end{requirement}
\begin{rationale}
Defining undo as a forward operation eliminates the contradiction
in earlier passes where undo had to ``restore previous state''
while remote edits had been integrated. The compensating-edit
approach respects the operation set's append-only nature and
generalizes cleanly to concurrent collaboration: undoing a
transaction whose effects have been entwined with remote edits is
expressible as a deterministic compensating operation, possibly
partial.
\end{rationale}
\section{LWW Discipline}
\label{sec:semops:lww}
Last-writer-wins reduction is permitted only for explicitly-marked
advisory scalar fields. Structural musical objects are governed by
their kind's operation-specific reduction rules.
\subsection{The LwwAdvisory Marker}
Fields eligible for LWW reduction are explicitly marked as
\texttt{LwwAdvisory}. No unmarked field may use LWW semantics.
\begin{lstlisting}[language=Rust]
/// Marker attribute, applied to fields whose concurrent modifications
/// are resolved by last-writer-wins under the canonical reduction
/// order.
#[attribute_only]
pub struct LwwAdvisory;
// Example field declarations:
//
// #[lww_advisory]
// pub title: Option<String>
//
// #[lww_advisory]
// pub composer: Option<String>
\end{lstlisting}
\subsection{Eligible Fields}
\begin{requirement}
\label{req:semops:lww-advisory-fields}
The following classes of field \MAY{} be marked
\texttt{LwwAdvisory} and reduced by last-writer-wins:
\begin{itemize}
\item Score metadata: title, subtitle, composer, lyricist,
copyright, work number, opus, dedication, free-form
annotations.
\item View preferences: which layers are active, which parts
are shown, viewport, zoom level (per-replica state in
addition; only the explicitly shared portions are LWW).
\item Layout hint preferences: default page size, default
margins, default staff spacing recommendations.
\item Page-break and system-break preferences on layout-semantic
structures (the user's stated preference, distinct from the
engraver's decision).
\item Optional advisory display hints on cross-cutting
structures: line color preference, line-style preference,
label-position preference. Endpoints, target identities,
semantic class, and attachment ownership \MUSTNOT{} be
LWW.
\end{itemize}
All other fields, including every field carrying musical
structure, identity, ownership, or semantics, \MUST{} use
operation-specific reduction rules defined per operation kind.
Implementations \MUSTNOT{} default to LWW for unmarked fields.
\end{requirement}
\begin{rationale}
LWW is acceptable for fields where the worst case of arbitrary
deterministic resolution is benign: two authors editing the
composer's name concurrently is a metadata collision, not a
musical loss. LWW is catastrophic for fields where wholesale
replacement destroys musical meaning: two authors editing a
chord's pitch list concurrently must not produce ``one author's
chord wins.'' The marker discipline makes the boundary explicit
and auditable.
\end{rationale}
\section{Validation Modes}
\label{sec:semops:validation}
Operations are subject to precondition checking. Two modes
distinguish authoring contexts:
\begin{description}
\item[Authoring mode.] Interactive edits. All preconditions are
enforced: invariant preconditions (which preserve graph
invariants) and advisory preconditions (which encode user-intent
constraints, range checks, and style policy).
\item[Replay mode.] Replaying historical operations or applying
remote operations under reduction. Only invariant preconditions
are enforced; advisory preconditions are skipped, since they
represent the authoring replica's local policy at the moment of
authoring, not invariants of the canonical state.
\end{description}
\begin{requirement}
\label{req:semops:precondition-validation-modes}
Every operation specification \MUST{} classify each precondition
as invariant or advisory. Invariant preconditions \MUST{} hold in
all modes; advisory preconditions \MUST{} hold in authoring mode
and \MAY{} fail silently in replay mode.
Implementations \MUSTNOT{} reclassify a precondition without a
corresponding revision to this specification.
\end{requirement}
\section{Per-Kind Reduction Summary}
\label{sec:semops:per-kind-summary}
The following table summarizes the reduction discipline for each
representative operation kind. Detailed specifications appear in
the next section.
\begin{longtable}{p{3.4cm} p{4.0cm} p{5.0cm}}
\toprule
\textbf{Operation} & \textbf{Reduction Rule} &
\textbf{Concurrency Outcomes} \\
\midrule
\endhead
InsertEvent &
Position-keyed insertion. Concurrent same-position inserts whose
durations overlap promote the lexicographically-greater operation
to a system-promoted voice
(Section~\ref{sec:graph:promoted-voices}) &
Voice promotion; non-overlap invariant preserved by allocation. \\
DeleteEvent &
Tombstone the event and contained pitch identifiers. Apply
tuplet compensation from the operation. Re-anchor referencing
structures &
Delete-wins against concurrent field modifications on the same
event; concurrent same-target deletes are idempotent. \\
RespellPitch &
Field overwrite by canonical reduction order; the later operation
wins. Earlier respellings of the same pitch are preserved in the
conflict record if they differ &
Conflict record records the losing spelling for user inspection. \\
Transpose &
Apply in canonical reduction order. Pitch identifiers preserved.
Operations do not commute &
Reduction order decides; conflict record entries for non-trivial
intervals only when explicit dependency rules require. \\
CreateCrossCutting &
Set union. Concurrent additions of distinct cross-cutting
structures all succeed &
Tombstoned endpoints cause re-anchoring or cascade-delete per
kind. \\
ChangeRegionTimeModel &
Structural migration. Contained events whose coordinate kinds
become incompatible cause the operation to conflict &
Concurrent contained edits authored before the migration may
conflict; explicit rebase rules per migration kind. \\
SetUserSystemBreak &
LwwAdvisory on per-location break preference &
Last-writer-wins on the break state at the same anchor. \\
\bottomrule
\end{longtable}
\section{Representative Operations}
\label{sec:semops:examples}
The remainder of the chapter specifies a representative selection of
operations, one from each major category. The full catalog is
delivered as a separate conformance specification.
These examples illustrate the contract that every operation in the
catalog \MUST{} satisfy. Each operation specification includes its
reduction rule under the canonical reduction model
(Section~\ref{sec:semops:reduction}).
\subsection{InsertEvent}
\label{sec:semops:insertevent}
\textbf{Category:} Event operations.
\begin{lstlisting}[language=Rust]
pub struct InsertEventOp {
/// The voice the event is inserted into.
pub voice: VoiceId,
/// Position within the voice.
pub position: EventPosition,
/// The event to insert. Its EventId and any contained PitchIds
/// are minted at operation-authoring time as a single causal
/// step. The event's voice field must match the target voice.
pub event: Event,
}
\end{lstlisting}
\textbf{Invariant preconditions:}
\begin{itemize}
\item The target voice exists.
\item The event's \texttt{voice} field equals the target voice's id.
\item The event's \texttt{id} is not present elsewhere in the arena.
\item Every \texttt{PitchId} contained in the event (in an
\texttt{IdentifiedPitch}, a trajectory endpoint, or a stepwise
trajectory shape) is unique within the score's pitch-identity
index.
\item The position is within the enclosing region's time extent.
\item The event's position and duration variants agree with the
enclosing region's time model.
\item Inserting at the position does not produce an event overlap
within the voice. Concurrent inserts that would otherwise produce
overlap are resolved per the reduction rule below.
\end{itemize}
\textbf{Advisory preconditions (authoring mode only):}
\begin{itemize}
\item For pitched events, every pitch is within the instrument's
declared range, if any.
\item The event's duration does not extend past a region boundary
in a way that would require splitting.
\end{itemize}
\textbf{Effect:} The event is added to the arena; the voice's event
list is updated to include the event in sorted position. Every
contained \texttt{PitchId} is registered as live in the
pitch-identity index. Any attachments declared on the event
(spellings, decompositions) are added to their respective indexes.
\textbf{Postconditions:}
\begin{itemize}
\item The voice's events list remains sorted and non-overlapping.
\item The event arena's lookup index resolves the new event.
\item The event time index includes the new event for its region
and voice.
\item Every contained pitch is resolvable through the
pitch-identity index.
\end{itemize}
\textbf{Inverse:} \texttt{DeleteEvent} of the inserted event, with
captured state equal to the event itself plus any attachments added.
\textbf{Reduction rule:} Position-keyed insertion with voice promotion
on collision. Concurrent inserts at the same position whose durations
would overlap resolve as follows: the operation with the
lexicographically-greater \texttt{OperationId} is placed in a newly
allocated voice on the same staff instance, with
\texttt{VoiceOrigin::SystemPromoted} (Section~\ref{sec:graph:promoted-voices}).
The new voice's \texttt{VoiceId} is derived deterministically from the
staff instance, the original target voice, and the two operations'
identifiers, so all replicas independently allocate the same promoted
voice. The non-overlap invariant is preserved by allocation rather
than by rejection. Subsequent user normalization operations can merge
the promoted voice back into the target, rename it, or accept it as
an intentional second voice.
\subsection{DeleteEvent}
\label{sec:semops:deleteevent}
\textbf{Category:} Event operations.
\begin{lstlisting}[language=Rust]
pub struct DeleteEventOp {
pub event: EventId,
/// Compensation for tuplet membership, if any. Required when the
/// target event is a member of one or more tuplets.
pub tuplet_compensation: TupletCompensation,
}
pub enum TupletCompensation {
/// Target is not in any tuplet; no compensation required.
NotInTuplet,
/// Replace the deleted event with a rest of the same duration.
/// The rest receives a freshly-minted EventId; the deleted
/// event's EventId and PitchIds are tombstoned.
ReplaceWithRest { new_rest: Rest },
/// Rewrite the enclosing tuplet(s) to remain structurally
/// consistent. The v1 payload names the affected tuplets by
/// id only; it does not carry rewritten values (see the
/// precondition below).
RewriteTuplets { tuplets: Vec<TupletId> },
/// Cascade-delete the entire tuplet group(s) containing the
/// target event. The listed tuplet ids are cascaded.
CascadeDeleteTuplets { tuplets: Vec<TupletId> },
}
\end{lstlisting}
\textbf{Invariant preconditions:}
\begin{itemize}
\item The target event exists in the arena (i.e., is live, not
tombstoned).
\item If the target event belongs to one or more tuplets, the
\texttt{tuplet\_compensation} field \MUST{} be a variant other
than \texttt{NotInTuplet}, and the chosen compensation \MUST{}
preserve the tuplet structural consistency invariant
(Section~\ref{sec:time:tuplets}) after the delete is applied.
\item If the target event does not belong to any tuplet, the
\texttt{tuplet\_compensation} field \MUST{} be
\texttt{NotInTuplet}.
\item For \texttt{ReplaceWithRest}: the replacement rest's
duration \MUST{} equal the deleted event's duration; the rest's
voice and position \MUST{} match the deleted event.
\item For \texttt{RewriteTuplets}: the v1 payload carries only
tuplet ids, not the rewritten ratio and member values a reducer
would need to demonstrate that the resulting structure preserves
the tuplet consistency invariant. Graph-aware reduction \MUST{}
therefore refuse the variant as an ill-formed compensation (a
precondition no-op) rather than fabricate rewritten values. A
future payload revision carrying full rewrites (per-tuplet ratio
and members, each of whose member-duration sums \MUST{} match
the tuplet's structurally-required total) reopens the variant;
until then \texttt{ReplaceWithRest} and
\texttt{CascadeDeleteTuplets} are the applicable compensations.
\item For \texttt{CascadeDeleteTuplets}: every tuplet listed
\MUST{} contain the target event as a member.
\end{itemize}
\textbf{Effect:} The event is removed from the arena and from its
voice's event list. The event's \texttt{EventId} and every contained
\texttt{PitchId} are tombstoned in their respective identity
indexes: they remain resolvable to ``deleted'' markers and \MUSTNOT{}
be reused for new objects. The chosen tuplet compensation is applied
in the same delta: a replacement rest is inserted or the tuplets are
cascade-deleted as specified (a \texttt{RewriteTuplets} compensation
is refused at precondition check; see above).
Cross-cutting structures referencing the event or its pitches are
re-anchored per the rule table (Section~\ref{sec:semops:reanchor}).
Live attachments targeting the event or its pitches transition to
tombstoned-target state and are preserved for replay, undo, and
historical reference; they are not silently deleted.
\textbf{Postconditions:}
\begin{itemize}
\item The event is no longer live in the arena.
\item Every cross-cutting structure that referenced the event has
been re-anchored or cascade-deleted.
\item The event's identifier and every contained pitch identifier
are tombstoned and resolvable only to a deletion record.
\item The tuplet structural consistency invariant holds for every
tuplet that was previously consistent.
\end{itemize}
\textbf{Inverse:} \texttt{InsertEvent} reconstructing the event from
captured state, re-promoting the tombstoned identifiers to live,
reversing the tuplet compensation (delete the replacement rest;
restore the original tuplet structure; un-cascade tuplet deletions),
and re-creating any cross-cutting structures that were
cascade-deleted as a consequence. Re-promotion preserves all stable
identifiers; new identifiers \MUSTNOT{} be minted by the inverse.
\textbf{Reduction rule:} Set intersection with delete-wins. Concurrent
deletion and modification of the same event resolves to deletion;
the modification is dropped. Concurrent re-anchoring on the same
referent resolves by the deterministic total ordering specified in
Section~\ref{sec:semops:reanchor}.
\begin{rationale}
Requiring explicit tuplet compensation prevents primitive deletion
from leaving the score in a state that violates the tuplet
structural consistency invariant. The compensation is part of the
same delta as the deletion, so concurrent observers see either
the pre-delete state or a fully consistent post-delete state, never
an intermediate broken state.
\end{rationale}
\subsection{CreateCrossCutting (Slur)}
\textbf{Category:} Cross-cutting structure operations.
\begin{lstlisting}[language=Rust]
pub struct CreateCrossCuttingOp {
pub structure: CrossCuttingStructure,
}
pub enum CrossCuttingStructure {
Slur(Slur),
Tie(Tie),
Beam(Beam),
Spanner(Spanner),
Marker(Marker),
Repeat(RepeatStructure),
Analytical(AnalyticalAnnotation),
Comment(Comment),
GraphicGesture(GraphicGesture),
}
\end{lstlisting}
The enum above is the data model's full cross-cutting family; the
\emph{wire} vocabulary the cross-cutting operations admit at this
revision (\texttt{CrossCuttingValue}, Binary Format companion) is the
four-kind subset \texttt{Tie}/\texttt{Slur}/\texttt{Beam}/%
\texttt{Spanner}. A \texttt{RepeatStructure} is authored by the
dedicated \texttt{CreateRepeatStructure}/%
\texttt{DeleteRepeatStructure} pair (Operation Catalog
\sectionsc{Repeat Structures}; ratified schema-major-2 revision), not
through \texttt{CreateCrossCutting}; the remaining variants await
their own authoring primitives.
\textbf{Invariant preconditions} (Slur case):
\begin{itemize}
\item The slur's id is unique within the cross-cutting registry.
\item The slur's start and end events both exist.
\item The start and end events are in the same voice.
\item The start event precedes (or equals) the end event in voice order.
\end{itemize}
\textbf{Advisory preconditions} (Slur case):
\begin{itemize}
\item The slur does not span a region boundary, unless explicitly
permitted by region configuration.
\end{itemize}
\textbf{Effect:} The slur is added to the cross-cutting registry's
slur list. The cross-cutting reference index is updated.
\textbf{Postconditions:}
\begin{itemize}
\item The slur is queryable by id and by referenced events.
\end{itemize}
\textbf{Inverse:} \texttt{DeleteCrossCutting} of the created slur.
\textbf{Reduction rule:} Set union. Concurrent creation of multiple
slurs over overlapping ranges is valid: both slurs exist.
\subsection{RespellPitch}
\textbf{Category:} Pitch and tuning operations.
\begin{lstlisting}[language=Rust]
pub struct RespellPitchOp {
pub pitch: PitchId,
pub new_spelling: PitchSpelling,
}
\end{lstlisting}
\textbf{Invariant preconditions:}
\begin{itemize}
\item The target pitch exists in some event in the arena (i.e.,
is live, not tombstoned).
\item The new spelling is valid in the active pitch space: every
accidental in the spelling's \texttt{accidentals} stack belongs
to the active accidental registry, the nominal exists in the
space's nominal registry, and the octave is in range.
\item Duplicate accidentals in the spelling's stack are absent
unless the accidental's registered combination semantics
explicitly permit repetition.
\item The new spelling represents the same scale position as the
pitch's current spelling, or is enharmonically equivalent under
the active tuning system. (Respelling does not change sounding
pitch.)
\end{itemize}
\textbf{Effect:} A spelling attachment with source
\texttt{UserChosen} is created (or replaces an existing
\texttt{UserChosen} attachment) targeting the pitch. The spelling
attachment index is updated. The pitch's \texttt{PitchId} is
unaffected.
\textbf{Postconditions:}
\begin{itemize}
\item Spelling resolution for the pitch yields the new spelling,
subject to the precedence configuration.
\item The pitch's scale position is unchanged.
\item The pitch's acoustic realization is unchanged.
\item The pitch's \texttt{PitchId} is unchanged.
\end{itemize}
\textbf{Inverse:} A \texttt{RespellPitch} restoring the prior
spelling (captured), or a \texttt{RemoveAttachment} if no prior
\texttt{UserChosen} attachment existed.
\textbf{Reduction rule:} Field overwrite by canonical reduction
order. Concurrent respellings of the same live pitch resolve to the
operation later in canonical order; the materialized spelling is
that operation's choice. The losing respelling is recorded in a
\texttt{ConflictRecord} of kind
\texttt{StructuralFieldCollision} if the two respellings represent
different spellings (i.e., differ in nominal, accidental stack, or
octave). The per-operation effect assignment follows the general
field-collision rule
(Requirement~\ref{req:semops:field-collision-effect}): the later
operation --- the winner, which materializes --- reads
\texttt{Conflicted}, and the earlier operation retains
\texttt{Applied}. Identical concurrent respellings reduce
idempotently: the later operation produces
\texttt{NoOp\{reason: AlreadyApplied\}} with no conflict recorded.
\subsection{ChangeRegionTimeModel}
\textbf{Category:} Region operations.
\begin{lstlisting}[language=Rust]
pub struct ChangeRegionTimeModelOp {
pub region: RegionId,
pub new_time_model: RegionTimeModel,
pub position_remapping: PositionRemapping,
}
pub enum PositionRemapping {
/// Preserve absolute time positions where possible. Events that
/// cannot be remapped (e.g., metric events under a proportional
/// model with no implied tempo) are converted by the supplied
/// conversion function.
PreserveTime { fallback: PositionConverter },
/// Discard event positions and reinsert by user-provided
/// position mapping.
Reassign(HashMap<EventId, EventPosition>),
}
\end{lstlisting}
\textbf{Invariant preconditions:}
\begin{itemize}
\item The target region exists.
\item The new time model is internally consistent.
\item The position remapping covers every event in the region, or
the remapping is \texttt{PreserveTime} with a defined fallback.
\end{itemize}
\textbf{Effect:} The region's \texttt{time\_model} is replaced. Every
event in the region has its \texttt{position} field updated according
to the remapping. The tempo map's applicability is recomputed.
\textbf{Postconditions:}
\begin{itemize}
\item Every event in the region has a valid position in the new
time model.
\item The region's invariants (events sorted, non-overlapping,
within time extent) hold.
\end{itemize}
\textbf{Inverse:} \texttt{ChangeRegionTimeModel} restoring the prior
time model, with a remapping captured from the pre-state.
\textbf{Reduction rule:} Structural migration. The operation is
applied at its canonical position. Contained events whose coordinate
kinds become incompatible with the new time model after migration
cause the operation to reduce to \texttt{Conflicted} with a
\texttt{ConflictKind::TimeModelMigrationFailure} record listing the
incompatible events; the migration does not apply. Concurrent
operations on the same region that author events in the prior time
model and reduce after the migration may themselves reduce to
\texttt{Conflicted} if their coordinate kinds disagree with the new
model. Concurrent same-target migrations resolve by canonical order;
the earlier operation applies and the later operation reduces to
\texttt{Conflicted} with a structural-field-collision conflict (the
two migrations are not generally commutable).
\begin{rationale}
Time-model migration is a structural rebase, not a field set.
Treating it as LWW would silently destroy contained musical
content whose coordinate kinds depend on the prior time model.
Conflicted reductions force the user to address the incompatibility
explicitly.
\end{rationale}
\subsection{Transpose}
\textbf{Category:} Pitch and tuning operations. This is a compound
operation, defined as a sequence of primitives, listed here as an
illustration of compounds.
\textbf{User-facing parameters:}
\begin{itemize}
\item Target scope (events, voice, staff, region, or selection).
\item Interval (diatonic, chromatic, or compound).
\item Spelling policy (preserve user spellings, respell to fit a
target key, or apply a custom rule).
\item Tuning policy (preserve, recompute under new tuning, or
apply offsets).
\end{itemize}
\textbf{Primitive decomposition:} A transpose operation expands into:
\begin{enumerate}
\item For each target pitch, a \texttt{ReplaceEvent} updating the
inner \texttt{Pitch} values while preserving every
\texttt{IdentifiedPitch}'s \texttt{PitchId}. Transposition does
not mint new pitch identifiers; it rewrites the contents of
existing identified pitches.
\item For each pitch whose spelling is recomputed, a
\texttt{RespellPitch} targeting the unchanged \texttt{PitchId}
with source \texttt{Propagated}.
\item Where the transposition crosses a clef boundary, optional
\texttt{ChangeStaffConfiguration} operations to insert clef
changes.
\end{enumerate}
The compound operation is recorded as a single transaction. Each
primitive within the transaction has its own re-anchoring and
reduction semantics. Identifier preservation ensures that
attachments, analytical layers, and cross-cutting references
targeting the transposed pitches continue to resolve correctly
after the operation.
\subsection{SetUserSystemBreak}
\textbf{Category:} Layout-semantic operations.
\begin{lstlisting}[language=Rust]
pub struct SetUserSystemBreakOp {
pub anchor: TimeAnchor,
pub region: RegionId,
pub present: bool,
}
\end{lstlisting}
\textbf{Invariant preconditions:}
\begin{itemize}
\item The region exists and is staff-based.
\item The anchor resolves within the region.
\end{itemize}
\textbf{Effect:} The region's \texttt{user\_system\_breaks} list is
updated to include or exclude the anchor. Layout caches that depend
on system breaks are invalidated.
\textbf{Postconditions:}
\begin{itemize}
\item The region's user system breaks list reflects the change.
\item Layout output, if produced subsequently, accounts for the
change subject to the engraver's override rules
(Chapter~\ref{ch:layout-ir}).
\end{itemize}
\textbf{Inverse:} \texttt{SetUserSystemBreak} with the opposite
\texttt{present} value.
\textbf{Reduction rule:} LwwAdvisory on per-anchor break preference.
Two concurrent operations setting the same break to \texttt{true}
reduce idempotently. Concurrent set-true and set-false on the same
anchor resolve by canonical reduction order; the later operation's
preference is materialized. No conflict record is produced; the
break preference is an advisory field eligible for LWW reduction
(Section~\ref{sec:semops:lww}).
\section{Operation Catalog Conformance}
\label{sec:semops:catalog}
This chapter specifies the framework. The full catalog of operations
is delivered as a separate conformance specification that extends
this chapter.
\begin{requirement}
\label{req:semops:operation-catalog-coverage}
A conforming implementation \MUST{} provide every operation
enumerated in the conformance catalog. Each operation \MUST{}
satisfy the framework requirements stated in this chapter:
\begin{itemize}
\item Declared preconditions classified as invariant or advisory.
\item Specified effect on the score graph.
\item Specified postconditions, including which invariants are
preserved.
\item A defined inverse operation.
\item A declared reduction rule under the canonical reduction
model (Section~\ref{sec:semops:reduction}).
\item Specified re-anchoring behavior for any deletions or
moves it triggers.
\end{itemize}
\end{requirement}
\begin{openquestion}
The conformance catalog is presently under development as a separate
document. It is expected to comprise approximately 60--80 primitive
operations and an open-ended set of compound operations. The catalog
is normative once published; this specification is non-final until
the catalog is delivered.
\end{openquestion}
\section{Forward References}
\begin{itemize}
\item Operation envelope storage on disk (envelope block layout,
causal-summary metadata, the operation index, snapshot
canonical-base semantics) is specified in
Chapter~\ref{ch:format}.
\item Conflict-resolution algorithms (how a
\texttt{ResolveConflict} operation transitions a conflict's
state, and the full conflict-record contract) are specified
in this chapter (Section~\ref{sec:semops:conflicts}) and in
the Operation Catalog companion
(Appendix~\ref{app:deferred}).
\item The interaction between layout-semantic operations (system
breaks, page breaks, engraving overrides) and the layout pipeline
is specified in Chapter~\ref{ch:layout-ir}.
\item The conformance catalog of all operations is a separate
deliverable that extends this chapter.
\end{itemize}
% ===========================================================================
\chapter{Layout Intermediate Representation}
\label{ch:layout-ir}
This chapter specifies the layout intermediate representation (Layout
IR): the data structures and pipeline that transform the score graph
into a typeset visual layout. The IR sits between the score graph and
two downstream consumers: the constraint solver
(Chapter~\ref{ch:solver}), which resolves spacing and positioning,
and the renderer (specified outside this document), which produces
final visual output (pixels, PDF, SVG).
The IR is a pipeline of distinct stages, each with its own type and
each with a well-defined contract for the next. The pipeline is the
spine of the engraving system; the engraving algorithms themselves
(beam grouping, slur curvature, accidental ordering, casting-off) are
the consumers and producers of stages and are specified outside this
document as engraving-algorithm specifications layered atop this IR.
\section{Design Principles}
\label{sec:layoutir:principles}
\begin{description}
\item[Pipeline of stages.] The transformation from score graph to
visual output is a pipeline of four IR stages:
\texttt{LogicalLayoutIR} (structural projection without spatial
information), \texttt{ConstrainedLayoutIR} (with constraint inputs
but unresolved positions), \texttt{ResolvedLayoutIR} (with all
positions resolved), and \texttt{RenderIR} (renderer-bound
primitives, defined only at the interface level here). Each stage
has its own type. Compilation between stages is unidirectional.
\item[Shared provenance.] Every IR object at every stage carries a
reference back to the score graph object that originated it. This
enables selection, editing back-references, error reporting, and
incremental layout invalidation.
\item[Engraving decisions are explicit.] When the engraver makes a
decision (stem direction, accidental ordering, beam consolidation),
the decision is recorded in the IR. The decision can be inspected,
overridden, and traced.
\item[Overrides exist in both worlds.] User-set engraving overrides
are authoritative in the score graph and projected into the IR
during the logical stage. The IR may also generate transient
overrides for downstream stages.
\item[Spring-based spacing.] Time-to-space projection produces
elastic spring parameters per time slot. The constraint solver
resolves springs system-wide. This applies to both horizontal
and vertical layout.
\item[Staff-space units, f32 precision.] Spatial coordinates within
the IR are expressed in staff spaces using single-precision
floating point. Conversion to absolute units (points, millimeters)
occurs only at the render boundary.
\item[Glyph metrics live elsewhere.] The IR references glyphs by
identifier and queries metrics from a font catalog. Metrics are
not embedded in IR objects.
\item[Incremental layout, fine-grained.] The IR supports incremental
re-engraving via per-object dependency tracking on score graph
objects. Edits invalidate only the layout objects whose
dependencies changed.
\item[Uniform region containers.] Staff-based, free-graphic, and
hybrid regions produce structurally identical \texttt{LayoutRegion}
containers with differing time projections and content kinds.
\end{description}
\section{Spatial Primitives}
\label{sec:layoutir:spatial}
\subsection{Units}
The IR uses two unit types:
\begin{lstlisting}[language=Rust]
/// Staff space: the fundamental unit of music engraving.
/// One staff space equals the distance between adjacent staff lines.
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
pub struct StaffSpace(pub f32);
/// Point: 1/72 inch. Used at the render boundary for absolute
/// dimensions (page sizes, font sizes, output coordinates).
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
pub struct Point(pub f32);
/// Scaling context for converting between staff spaces and points.
pub struct ScaleContext {
/// Points per staff space. Depends on the staff size chosen for
/// the score; typical values are 4--10 points per staff space.
pub points_per_staff_space: f32,
}
\end{lstlisting}
\begin{requirement}
\label{req:layoutir:staff-space-coordinates}
Spatial coordinates within the IR (in stages
\texttt{LogicalLayoutIR}, \texttt{ConstrainedLayoutIR}, and
\texttt{ResolvedLayoutIR}) \MUST{} be expressed in staff spaces.
Conversion to points occurs at the \texttt{RenderIR} boundary.
Single-precision floating point (\texttt{f32}) \MUST{} be used for
IR coordinates. Engraving precision requirements are bounded by
printer resolution (typically 600--2400 DPI); single precision
provides over 7 significant figures, comfortably exceeding any
engraving precision requirement.
\end{requirement}
\subsection{Geometric Types}
\begin{lstlisting}[language=Rust]
#[derive(Copy, Clone, Debug)]
pub struct Point2D {
pub x: StaffSpace,
pub y: StaffSpace,
}
#[derive(Copy, Clone, Debug)]
pub struct Size2D {
pub width: StaffSpace,
pub height: StaffSpace,
}
#[derive(Copy, Clone, Debug)]
pub struct Rect {
pub origin: Point2D,
pub size: Size2D,
}
#[derive(Copy, Clone, Debug)]
pub struct BoundingBox {
pub left: StaffSpace,
pub right: StaffSpace,
pub top: StaffSpace,
pub bottom: StaffSpace,
}
#[derive(Copy, Clone, Debug)]
pub struct Transform2D {
pub matrix: [[f32; 3]; 3],
}
\end{lstlisting}
\section{Provenance}
\label{sec:layoutir:provenance}
Every IR object carries a provenance record tracing it back to the
score graph object that originated it. Provenance is uniform across
all four stages; provenance records are shared structurally between
stages so that an object passing through the pipeline retains a
consistent identity.
\begin{lstlisting}[language=Rust]
pub struct Provenance {
/// The primary source: the score graph object this IR object
/// represents or derives from.
pub source: TypedObjectId,
/// For IR objects that derive from synthesizing engraving
/// decisions rather than directly from score graph objects, the
/// synthesis kind. Examples: an automatically inserted natural
/// sign, a generated rest, a system break.
pub synthesis: Option<SynthesisKind>,
/// Additional source dependencies. An IR object may depend on
/// multiple score graph objects (e.g., a beam depends on its
/// member events). All dependencies are tracked for
/// incremental layout.
pub dependencies: Vec<TypedObjectId>,
/// IR object's stable identifier across re-layouts where the
/// underlying source is unchanged.
pub stable_id: LayoutObjectId,
}
pub enum SynthesisKind {
/// An accidental inserted to cancel a prior alteration.
CancellationAccidental,
/// A natural sign generated by key signature rules.
KeySignatureNatural,
/// A rest inserted to fill a voice gap.
GeneratedRest,
/// A system or page break selected by the engraver.
EngravedBreak,
/// A multimeasure rest combining consecutive empty measures.
MultimeasureRest,
/// A cautionary key or time signature at a system break.
Cautionary,
/// A custom synthesis declared by a layout extension.
Registered(SynthesisRegistryId),
}
pub struct LayoutObjectId(pub u128);
\end{lstlisting}
\begin{requirement}
\label{req:layoutir:provenance-completeness}
Every layout object at every IR stage \MUST{} carry a non-empty
\texttt{Provenance} record. Objects whose direct \texttt{source}
does not correspond to a score graph object (engraver-synthesized
objects) \MUST{} declare a \texttt{synthesis} kind. The
\texttt{dependencies} list \MUST{} include every score graph object
whose change should invalidate this layout object.
\end{requirement}
\begin{requirement}
\label{req:layoutir:continuation-synthesis}
\textbf{System-spanning continuation.} A line primitive (stroke) that spans a
system boundary is split into per-system segments; the segments after the
first are engraver-synthesized and \MUST{} declare a \texttt{synthesis} kind.
The reserved registered kind
\texttt{SynthesisKind::Registered(SYSTEM\_CONTINUATION\_SYNTHESIS)} names this
continuation. Its \texttt{stable\_semantic\_instance\_key} (per
Requirement~\ref{req:layoutir:object-id-derivation}) is the pair
\texttt{(original,\,ordinal)} --- the pre-split stroke's object and the
segment's order within the split --- which is stable for a fixed set of system
breaks. Because \texttt{LayoutObjectId}s are non-canonical and re-derived per
layout, this key need only be stable within a layout, not across relayouts
that move the breaks.
\end{requirement}
\begin{requirement}
\label{req:layoutir:object-id-derivation}
A \texttt{LayoutObjectId} \MUST{} be stable across re-layouts whose
underlying source is unchanged. It \MUST{} be derived by
domain-separated BLAKE3 truncation with the reserved layout domain
tag \texttt{"MUSCLOID"} over a key that depends on how the object is
manifested:
\begin{itemize}
\item A layout object that manifests a single score-graph object
exactly once is keyed on \texttt{source.canonical\_bytes()}.
\item A multiply-manifested object --- one score-graph object that
appears in more than one layout context, e.g. a staff manifested
in two regions --- is keyed on the pair
\texttt{(source,\,region)}, so each manifestation receives a
distinct, stable id.
\item A synthesized object (no direct score-graph
\texttt{source}; \texttt{synthesis} is \texttt{Some}) is keyed
on the triple \texttt{(source,\,synthesis\_kind,\,
stable\_semantic\_instance\_key)}, where the
\texttt{stable\_semantic\_instance\_key} distinguishes multiple
synthesized objects sharing a source and synthesis kind by a
semantically stable discriminator (not a layout-position
ordinal, which would not survive relayout).
\end{itemize}
\texttt{LayoutObjectId}s are \emph{non-canonical}: they are not part
of document state and do not enter any content hash, so the
\texttt{"MUSCLOID"} tag lives in the layout namespace and is not one
of the reserved \emph{canonical} system tags of
Section~\ref{sec:graph:system-derived}. The fixed derivation is
pinned for incremental-relayout correctness and provenance
back-reference stability; its consumers are the solver and renderer
(Track~A), not the interchange track. Because these ids never enter
document state, no stored or interchanged artifact depends on this
derivation. The v0 reference implementation realizes this derivation
as of Pass-12 item P12-I2: \texttt{epiphany-determinism} reserves the
built-in \texttt{"MUSCLOID"} layout tag and the \texttt{layout-ir}
provenance derivations (single, multiply-manifested, and synthesized)
route through it, the synthesized case no longer borrowing
\texttt{"MUSCCONF"}. Because the ids are non-canonical, doing so
changed layout-id values but no stored or interchanged artifact.
\end{requirement}
\section{The Stage Pipeline}
\label{sec:layoutir:pipeline}
\subsection{Pipeline Overview}
\begin{lstlisting}
ScoreGraph
|
| Engraving pass:
| - Project structure
| - Make engraving decisions (stems, beams, accidentals)
| - Apply user overrides
v
LogicalLayoutIR
|
| Spacing pass:
| - Compute spring parameters per time slot
| - Compute glyph bounding boxes
| - Build collision constraints
v
ConstrainedLayoutIR
|
| Constraint solving (Chapter 9):
| - Resolve horizontal positions
| - Resolve vertical positions
| - Resolve page and system breaks
v
ResolvedLayoutIR
|
| Render projection (out of scope):
| - Convert to renderer primitives
| - Apply page templates
| - Generate draw calls
v
RenderIR
\end{lstlisting}
\subsection{Stage Contracts}
\begin{requirement}
\label{req:layoutir:deterministic-stage-transitions}
Each pipeline stage \MUST{} accept its input stage as a pure
function: identical inputs (plus identical configuration) \MUST{}
produce identical outputs. The stages are deterministic by
contract.
Stage transitions \MUST{} preserve provenance: every output object
\MUST{} carry a \texttt{Provenance} record either inherited from its
input or newly synthesized with appropriate \texttt{synthesis} kind.
\end{requirement}
\section{LogicalLayoutIR}
\label{sec:layoutir:logical}
\texttt{LogicalLayoutIR} is the structural projection of the score
graph into layout objects, with all engraving decisions made but
spatial positions unresolved. It is the output of the engraving pass
and the input to the spacing pass.
\subsection{Top-Level Structure}
\begin{lstlisting}[language=Rust]
pub struct LogicalLayoutIR {
/// Source score graph reference (typically a snapshot pointer
/// or version identifier).
pub source: ScoreVersion,
/// Layout regions, one per score graph region.
pub regions: Vec<LayoutRegion>,
/// Engraving decisions made during this pass.
pub engraving_decisions: Vec<EngravingDecision>,
/// User overrides projected from the score graph.
pub overrides: Vec<EngravingOverride>,
/// Cross-region layout objects (slurs spanning regions, etc.).
pub cross_region: Vec<CrossRegionObject>,
}
\end{lstlisting}
\subsection{Layout Regions}
\begin{lstlisting}[language=Rust]
pub struct LayoutRegion {
pub provenance: Provenance,
/// Coordinate system local to this region. The region's
/// transform maps local coordinates to canvas coordinates.
pub coordinate_system: LocalCoordinateSystem,
/// Time axis: how time positions map to horizontal positions.
/// A tagged union over the three built-in axis kinds plus a
/// registered variant for extension-defined axes. The enum
/// form is canonical for serialization, hashing, and
/// conformance comparison; implementations MAY use a trait
/// object internally for dynamic dispatch.
pub time_axis: TimeAxisModel,
/// Vertical extent: the set of staff bands this region occupies.
pub vertical_extent: VerticalExtent,
/// Layout objects within this region.
pub objects: Vec<LayoutObject>,
}
/// Canonical representation of a region's time axis. Variants
/// correspond to the three built-in region time models; the
/// Registered variant carries extension-defined axes by typed
/// registry id and serialized payload.
pub enum TimeAxisModel {
Metric(MetricTimeAxis),
Proportional(ProportionalTimeAxis),
Aleatoric(AleatoricTimeAxis),
Registered(TimeAxisRegistryId, SerializedRegisteredAxis),
}
\end{lstlisting}
\subsection{The Time Axis Trait}
For dynamic dispatch and ergonomic implementation, the
\texttt{TimeAxisModel} enum's payload types share a trait:
\begin{lstlisting}[language=Rust]
pub trait TimeAxis: Send + Sync {
/// The time model kind this axis implements.
fn kind(&self) -> TimeAxisKind;
/// Project a time position to a horizontal spring slot.
/// Returns a slot identifier; spring parameters are queried
/// separately.
fn project(&self, time: TimePoint) -> SpringSlotId;
/// Enumerate the spring slots in time order.
fn slots(&self) -> &[SpringSlotId];
/// For incremental layout: which slots are affected by a change
/// to the given time range?
fn affected_slots(&self, range: TimeRange) -> Vec<SpringSlotId>;
}
pub enum TimeAxisKind {
Metric,
Proportional,
Aleatoric,
Registered(TimeAxisRegistryId),
}
pub enum TimePoint {
Musical(MusicalPosition),
WallClock(WallClockTime),
}
pub enum TimeRange {
Musical { start: MusicalPosition, end: MusicalPosition },
WallClock { start: WallClockTime, end: WallClockTime },
}
\end{lstlisting}
The three built-in implementations:
\begin{lstlisting}[language=Rust]
pub struct MetricTimeAxis {
/// Measure boundaries and their projected spring slots.
pub measures: Vec<MeasureProjection>,
/// Per-time-slot spring parameters, indexed by slot id.
pub slots: Vec<SpringSlot>,
}
pub struct ProportionalTimeAxis {
/// Wall-clock duration of the region.
pub duration: WallClockDuration,
/// Linear horizontal projection: space per second.
pub space_per_second: StaffSpace,
pub slots: Vec<SpringSlot>,
}
pub struct AleatoricTimeAxis {
/// DAG of event ordering.
pub ordering: EventOrderingDAG,
/// Spring slots are organized by topological-order layers.
pub slots: Vec<SpringSlot>,
}
\end{lstlisting}
\subsection{Layout Objects}
A \texttt{LayoutObject} is the unit of engraving. At the
\texttt{LogicalLayoutIR} stage, layout objects are composite: a
single \texttt{Note} object contains its notehead, stem, flag,
accidental, articulations, and so on. At the
\texttt{ConstrainedLayoutIR} stage these are flattened into
individual glyph objects.
\begin{lstlisting}[language=Rust]
pub enum LayoutObject {
/// Composite note object: notehead(s), stem, flag, accidental(s),
/// articulations, ornaments, dot(s).
Note(NoteLayout),
/// Composite chord object: multiple noteheads sharing stem and
/// articulations.
Chord(ChordLayout),
/// Rest with its glyph kind (whole, half, quarter, ...).
Rest(RestLayout),
/// Beam group: multiple notes/chords beamed together.
BeamGroup(BeamGroupLayout),
/// Tuplet visual: bracket and number.
TupletDisplay(TupletDisplayLayout),
/// Slur or phrase mark: control points and style.
Slur(SlurLayout),
/// Tie: same as slur but connecting same-pitch events.
Tie(TieLayout),
/// Spanner: hairpins, octave lines, pedal lines, etc.
Spanner(SpannerLayout),
/// Marker: rehearsal marks, section labels, tempo marks.
Marker(MarkerLayout),
/// Bar line: simple, double, final, repeat, etc.
BarLine(BarLineLayout),
/// Clef, key signature, time signature glyphs.
Clef(ClefLayout),
KeySignature(KeySignatureLayout),
TimeSignatureDisplay(TimeSignatureDisplayLayout),
/// Staff: the staff lines themselves.
Staff(StaffLayout),
/// Text: lyrics, dynamics text, expressive text.
Text(TextLayout),
/// Free graphic: path, shape, image, stroke.
Graphic(GraphicLayout),
/// Multimeasure rest: consolidated empty measures.
MultimeasureRest(MultimeasureRestLayout),
/// Cue: small-print rendering of source material.
Cue(CueLayout),
/// Trajectory: glissando, portamento line.
Trajectory(TrajectoryLayout),
/// Generic group: implementation-defined.
Group(GroupLayout),
}
\end{lstlisting}
\subsection{Note Layout: A Worked Example}
To illustrate the composite-object pattern at the logical stage:
\begin{lstlisting}[language=Rust]
pub struct NoteLayout {
pub provenance: Provenance,
/// Reference to the score graph event.
pub event: EventId,
/// The notehead glyph and its staff position.
pub notehead: NoteheadLayout,
/// The stem, if any.
pub stem: Option<StemLayout>,
/// Flag, if any (single-note events with eighth-or-shorter
/// duration that are not part of a beam group).
pub flag: Option<FlagLayout>,
/// Accidental glyphs attached to the note, in stacking order.
pub accidentals: Vec<AccidentalLayout>,
/// Augmentation dots.
pub dots: Vec<DotLayout>,
/// Articulations attached to the note.
pub articulations: Vec<ArticulationLayout>,
/// Ornament glyphs.
pub ornaments: Vec<OrnamentLayout>,
/// Ledger lines required to display the note's staff position.
pub ledger_lines: Vec<LedgerLineLayout>,
/// Engraving decisions made for this note.
pub decisions: NoteDecisions,
}
pub struct NoteDecisions {
pub stem_direction: StemDirection,
pub accidental_visible: bool,
pub accidental_parenthesized: bool,
pub notehead_shape: NoteheadShape,
/// Voice membership influences default stem direction;
/// overrides may change it.
pub stem_direction_source: DecisionSource,
}
pub enum DecisionSource {
/// Decision derived from automatic engraving rules.
Automatic,
/// Decision derived from a user override in the score graph.
UserOverride(EngravingOverrideId),
/// Decision derived from an IR-stage override.
IrOverride,
}
\end{lstlisting}
\subsection{Engraving Decisions}
The pipeline records engraving decisions explicitly so they can be
inspected, overridden, and replayed.
\begin{lstlisting}[language=Rust]
pub struct EngravingDecision {
pub id: EngravingDecisionId,
pub target: LayoutObjectId,
pub kind: EngravingDecisionKind,
pub source: DecisionSource,
}
pub enum EngravingDecisionKind {
StemDirection(StemDirection),
BeamGrouping(BeamGroupingChoice),
AccidentalOrdering(Vec<AccidentalId>),
SlurDirection(SlurDirection),
LedgerLineCount(u8),
EnharmonicSpelling(PitchSpelling),
SystemBreak,
PageBreak,
NoteheadShape(NoteheadShape),
RestPosition(StaffPosition),
// ... extensible
}
\end{lstlisting}
\section{Engraving Overrides}
\label{sec:layoutir:overrides}
Engraving overrides are user assertions that the engraver's default
decision should be replaced. Overrides are authoritative in the score
graph and projected into the IR during the logical stage.
\subsection{Override Definition}
\begin{lstlisting}[language=Rust]
pub struct EngravingOverride {
pub id: EngravingOverrideId,
/// What this override targets. May reference a score graph object
/// (typical) or an IR-synthesized object (transient overrides).
pub target: OverrideTarget,
/// The override itself.
pub kind: OverrideKind,
/// Binding strength.
pub priority: OverridePriority,
/// Provenance: who set the override and when.
pub origin: OverrideOrigin,
}
pub enum OverrideTarget {
/// Targets a score graph object. Authoritative; survives reload.
ScoreGraph(TypedObjectId),
/// Targets an IR-synthesized object. Transient; cleared on
/// re-engraving unless explicitly re-applied.
IrSynthesized(LayoutObjectId),
}
pub enum OverrideKind {
StemDirection(StemDirection),
SlurDirection(SlurDirection),
SlurCurvature(CurvatureOverride),
AccidentalParenthesized(bool),
AccidentalVisible(bool),
NoteheadShape(NoteheadShape),
BeamGeometry(BeamGeometryOverride),
SystemBreak { anchor: TimeAnchor },
PageBreak { anchor: TimeAnchor },
HiddenObject,
CustomPosition(Point2D),
EnharmonicSpelling(PitchSpelling),
LedgerLineSuppression,
DotPlacement(DotPlacement),
// ... extensible
}
// System- and page-break overrides address a *position*, not an
// object: the break kind carries the break's TimeAnchor, while the
// override's ScoreGraph target names the owning region. They are
// projected during the logical stage from the score graph's
// authoritative user_system_breaks / user_page_breaks lists
// (Chapter 5). Break authorship (author, timestamp) lives in the
// operation log, not the materialized break lists, so projected
// break overrides carry OverrideOrigin::Internal until the
// snapshot-undo refinement (P11-C8) surfaces authorship.
pub enum OverridePriority {
/// The engraver MUST honor this override. Failure produces an
/// error rather than a silent override-of-the-override.
Hard,
/// The engraver SHOULD honor this override. The engraver may
/// override it only under documented exceptional conditions
/// (system overflow, unresolvable collision); when it does, the
/// override is recorded in the resulting decisions with source
/// IrOverride.
Soft,
}
pub enum OverrideOrigin {
User { author: AuthorId, timestamp: Timestamp },
Import { format: ForeignFormatId },
Plugin { plugin: PluginId },
Internal,
}
\end{lstlisting}
\subsection{Override Resolution}
\begin{requirement}
\label{req:layoutir:override-enforcement}
Overrides \MUST{} be applied during the engraving pass producing
\texttt{LogicalLayoutIR}. Hard overrides \MUST{} be honored or the
pass \MUST{} report an error. Soft overrides \MUST{} be honored
except where doing so would produce an invalid layout; when a soft
override is not honored, the resulting decision \MUST{} record the
override and the reason for its non-application.
\end{requirement}
\begin{requirement}
\label{req:layoutir:override-target-lifetime}
Overrides whose targets reference objects in the score graph (the
\texttt{ScoreGraph} variant) \MUST{} be preserved across
re-engraving. Overrides targeting IR-synthesized objects (the
\texttt{IrSynthesized} variant) are transient; they are cleared on
re-engraving unless explicitly re-applied. Implementations \MUST{}
not silently promote IR-synthesized overrides to score-graph
overrides without explicit user action.
\end{requirement}
\begin{requirement}
\label{req:layoutir:break-origin-attribution}
\textbf{Break-override attribution.} When the engraver honours a user break
override, the resulting decision \MUST{} carry
\texttt{DecisionSource::UserOverride(id)} naming the override. Because a
normalized break \emph{constraint} (\texttt{SystemBreakAt} /
\texttt{PageBreakAt}, Section~\ref{sec:layoutir:constrained}) carries no
override identity, this attribution is threaded through the projection: the
\texttt{ConstrainedLayoutIR} carries a \texttt{break\_origins} sidecar,
populated when the logical stage projects the break, mapping each user-anchored
break slot to its override id. The normalized constraint record is
deliberately \emph{not} widened to carry override identity --- attribution is a
projection concern, not a constraint-solver input.
\end{requirement}
\section{ConstrainedLayoutIR}
\label{sec:layoutir:constrained}
\texttt{ConstrainedLayoutIR} is the output of the spacing pass: the
logical IR with composite objects flattened to individual glyphs,
each glyph carrying its bounding box, anchor, and constraint inputs
to the solver.
\begin{requirement}
\label{req:layoutir:constraint-floor}
\textbf{Minimal-tier constraint-emission floor (ratified Pass 12).}
The spacing pass \MUST{} emit at least the following constraint
set --- the \emph{Minimal-tier floor}, the testable acceptance
surface for the \texttt{Minimal} conformance tier
(Chapter~\ref{ch:solver}): no-collision chains between
successive notehead-bearing columns; per-glyph containment
within the owning region's frame; and one constraint per user
break override (system and page). Richer emission sets
(inter-band collision, kerned optical adjacency, duration-
proportional spring preferences) belong to higher tiers, whose
floors are defined when those tiers are ratified.
\end{requirement}
\subsection{Glyph-Level Objects}
\begin{lstlisting}[language=Rust]
pub struct GlyphObject {
pub provenance: Provenance,
/// The glyph to render. Reference into a font catalog; metrics
/// are queried from the catalog rather than embedded here.
pub glyph: GlyphReference,
/// The spring slot this glyph belongs to (horizontal time slot).
pub horizontal_slot: SpringSlotId,
/// The vertical band this glyph belongs to.
pub vertical_band: VerticalBandId,
/// Bounding box of the glyph relative to its anchor, in staff
/// spaces. Looked up from the font catalog at IR construction
/// time and cached here.
pub bounding_box: BoundingBox,
/// Anchor point: where the glyph's reference position sits
/// within its bounding box.
pub anchor: Point2D,
/// Layer ordering: higher draws on top.
pub layer: i32,
/// Visual style applied to the glyph.
pub style: GlyphStyle,
}
\end{lstlisting}
\subsection{Spring Slots}
A spring slot is a time slot's spacing parameters: the constraint
solver's input for resolving horizontal positions.
\begin{lstlisting}[language=Rust]
pub struct SpringSlot {
pub id: SpringSlotId,
pub time: TimePoint,
/// Minimum width: the smallest the slot may compress to. The
/// constraint solver MUST NOT compress below this.
pub min_width: StaffSpace,
/// Preferred width: the natural width under unconstrained
/// spacing. Derived from Gourlay-style proportional rules.
pub preferred_width: StaffSpace,
/// Maximum width, if bounded. None means unbounded above.
pub max_width: Option<StaffSpace>,
/// Stretch factor: how readily this slot stretches when more
/// width is available than preferred. Higher = more elastic.
pub stretch_factor: f32,
/// Compress factor: how readily this slot compresses when less
/// width is available than preferred. Higher = more compressible.
pub compress_factor: f32,
/// Glyphs belonging to this slot, ordered by stacking
/// considerations (accidentals to left of notehead, etc.).
pub members: Vec<GlyphObjectId>,
}
\end{lstlisting}
\subsection{Vertical Bands}
Vertical layout uses the same spring model. A \emph{vertical band}
is a horizontal slice of the canvas (typically a staff or an
inter-staff gap) with its own spring parameters.
\begin{lstlisting}[language=Rust]
pub struct VerticalBand {
pub id: VerticalBandId,
pub kind: VerticalBandKind,
pub min_height: StaffSpace,
pub preferred_height: StaffSpace,
pub max_height: Option<StaffSpace>,
pub stretch_factor: f32,
pub compress_factor: f32,
/// Glyphs belonging to this band.
pub members: Vec<GlyphObjectId>,
}
pub enum VerticalBandKind {
Staff(StaffId),
InterStaffGap,
InterSystemGap,
MarginBand,
}
\end{lstlisting}
\begin{requirement}
\label{req:layoutir:coverage-diagnostics}
\textbf{Engraving-coverage gaps are surfaced, not guessed.} A projection
that cannot engrave an object faithfully --- a pitch with no resolved
spelling, a glyph the bound catalog
(\ref{sec:layoutir:catalog}) does not carry --- \MUST{} record a
\texttt{LayoutDiagnostic} naming the score-graph object and the kind of
gap, and \MUST{} still place the object: a fallback notehead on the clef
reference line, or a zero-extent traced anchor that keeps its provenance.
It \MUSTNOT{} silently substitute a plausible shape, and it \MUSTNOT{}
drop the object.
Both halves matter. Dropping the object breaks the round-trip surjection
(\ref{sec:layoutir:provenance}) --- a hit-test can no longer find it, and
an editor can no longer select what the author wrote. Guessing a shape
produces a score that looks engraved and is wrong, with nothing in the IR
to say so. A diagnostic is not an error: the layout is still
\emph{renderable}, and a conformance claim is unaffected. It is the record
that the engraving is incomplete at a named place.
\end{requirement}
\begin{requirement}
\label{req:layoutir:primitive-band-ownership}
\textbf{Primitive band ownership is declared, not inferred.} Every
primitive the projection presents to the solver in
\texttt{ConstrainedLayoutIR} --- \texttt{GlyphObject},
\texttt{Stroke}, and \texttt{Curve} alike --- \MUST{} declare the
\texttt{VerticalBandId} of the band that owns it, and that band
\MUST{} exist in \texttt{vertical\_bands}. A vertical solver \MUST{}
take a primitive's owning staff from that declaration and
\MUSTNOT{} infer it from the primitive's geometry. Content owned by
no staff (a page-margin annotation, a structure spanning several
staves) names a band whose kind is not \texttt{Staff}.
This is a correctness requirement, not a convenience. A primitive's
drawn position does not determine its owner: a stem shares its
horizontal column with the staff above, and a slur's endpoints are
\emph{lifted clear} of its own staff by construction, landing in the
inter-staff zone where the nearest notehead routinely belongs to the
neighbouring staff. A solver that guesses from proximity will
attribute such a primitive to the wrong staff and, on renegotiating
the gaps between a system's staves, tear it off the notes it belongs
to.
A band \MUST{} therefore exist for every staff of a region, whether
or not any glyph landed in it, and for the region's margin ---
otherwise a primitive that names one has nowhere to point. A band
MAY carry no members.
Only glyphs are \emph{members} of a band
(\texttt{VerticalBand::members}), which is what realizes the spring
solve; a stroke's or curve's band reference is one-way. What becomes
of the declaration downstream is
\ref{req:layoutir:resolved-band-ownership}.
\end{requirement}
\subsection{Constraints}
The IR carries explicit constraint inputs for the solver. Beyond
glyphs it presents the non-glyph primitives --- \texttt{Stroke} and
\texttt{Curve}, whose shapes are listed with
\ref{req:layoutir:resolved-primitives} --- since the solver positions
those too:
\begin{lstlisting}[language=Rust]
pub struct ConstrainedLayoutIR {
pub source: ScoreVersion,
pub regions: Vec<ConstrainedLayoutRegion>,
/// Horizontal spring slots across all regions.
pub horizontal_slots: Vec<SpringSlot>,
/// Vertical bands.
pub vertical_bands: Vec<VerticalBand>,
/// Glyph-level layout objects.
pub glyphs: Vec<GlyphObject>,
/// Non-glyph line primitives (staff lines, stems, barlines,
/// ledger lines, brackets) presented to the solver alongside
/// the glyphs.
pub strokes: Vec<Stroke>,
/// Cubic-Bezier curve primitives (slurs), likewise.
pub curves: Vec<Curve>,
/// Additional constraints not captured by spring parameters.
pub constraints: Vec<LayoutConstraint>,
/// Which user break override each projected break constraint came
/// from, so a solver can cite it in the decision it records.
pub break_origins: Vec<BreakOrigin>,
/// Engraving decisions, carried forward from the logical stage.
pub engraving_decisions: Vec<EngravingDecision>,
/// Engraving-coverage gaps, surfaced rather than hidden.
pub diagnostics: Vec<LayoutDiagnostic>,
/// The glyph catalog this IR's metrics were drawn from.
pub catalog: GlyphCatalogIdentity,
}
/// A projected break constraint's user-override attribution.
pub struct BreakOrigin {
pub slot: SpringSlotId,
pub class: BreakClass,
pub override_id: EngravingOverrideId,
}
/// An object the projection could not engrave faithfully.
pub struct LayoutDiagnostic {
pub source: TypedObjectId,
pub kind: LayoutDiagnosticKind,
}
pub enum LayoutDiagnosticKind {
/// A pitch reached the constrained pass with no resolved (or
/// non-CMN) spelling; its notehead is placed on the clef
/// reference line, but its true staff position is unknown.
MissingSpelling,
/// A glyph the bound catalog does not carry; the object is
/// carried as a traced anchor rather than drawn at a guessed
/// shape.
UnbundledGlyph(GlyphReference),
}
pub enum LayoutConstraint {
/// Two glyphs must not overlap in 2D space.
NoCollision { a: GlyphObjectId, b: GlyphObjectId },
/// A glyph must align with another along an axis.
Align {
a: GlyphObjectId,
b: GlyphObjectId,
axis: Axis,
},
/// A glyph must be positioned within a region of space.
PositionWithin {
glyph: GlyphObjectId,
region: Rect,
},
/// A range of slots must end at a system boundary.
SystemBreakAt { slot: SpringSlotId, kind: BreakKind },
/// A range of slots must end at a page boundary.
PageBreakAt { slot: SpringSlotId, kind: BreakKind },
/// Custom constraint declared by a layout extension.
Registered(ConstraintRegistryId, ConstraintParameters),
}
pub enum BreakKind {
Hard,
Soft,
}
\end{lstlisting}
\begin{requirement}
\label{req:layoutir:break-satisfaction}
\textbf{Break-constraint satisfaction.} A \texttt{SystemBreakAt} at
\texttt{slot} is \emph{satisfied} iff the final resolved layout starts a
system at that slot; a \texttt{PageBreakAt} iff the final layout starts a page
there. A slot that already begins a system (respectively page) --- in
particular a region's first slot --- is trivially satisfied. Satisfaction is a
predicate on the output \texttt{ResolvedLayoutIR}, evaluable by any consumer,
not on the solver's internal spring state. A solver's tier claim
(Section~\ref{sec:layoutir:resolved}) reports which \texttt{Hard} break
constraints its layout satisfies; a \texttt{Soft} break is advisory and MAY be
left unsatisfied.
\end{requirement}
\section{ResolvedLayoutIR}
\label{sec:layoutir:resolved}
\texttt{ResolvedLayoutIR} is the output of the constraint solver:
every glyph, stroke, and curve has a definitive position. This is the
IR consumed by the renderer.
\begin{lstlisting}[language=Rust]
pub struct ResolvedLayoutIR {
pub source: ScoreVersion,
/// Pages of the resolved score.
pub pages: Vec<ResolvedPage>,
/// Glyph-level objects with resolved positions.
pub glyphs: Vec<ResolvedGlyph>,
/// Non-glyph line primitives (staff lines, stems, barlines,
/// volta brackets, ...), positioned alongside the glyphs.
pub strokes: Vec<Stroke>,
/// Cubic-Bezier curve primitives (slurs, ...), positioned
/// alongside the glyphs.
pub curves: Vec<Curve>,
/// Engraving decisions, including any made by the constraint
/// solver (e.g., soft system breaks placed by casting-off).
pub engraving_decisions: Vec<EngravingDecision>,
}
pub struct ResolvedPage {
pub provenance: Provenance,
pub number: u32,
pub size: Size2D,
pub margins: Margins,
pub systems: Vec<ResolvedSystem>,
pub free_objects: Vec<GlyphObjectId>,
}
pub struct ResolvedSystem {
pub provenance: Provenance,
pub bounding_box: Rect,
pub staves: Vec<ResolvedStaff>,
pub measures: Vec<ResolvedMeasure>,
}
pub struct ResolvedGlyph {
pub provenance: Provenance,
pub glyph: GlyphReference,
pub position: Point2D,
pub transform: Option<Transform2D>,
pub bounding_box: BoundingBox,
pub style: GlyphStyle,
pub layer: i32,
}
/// A line primitive the solver positions (a staff line, stem,
/// barline, or bracket).
pub struct Stroke {
pub provenance: Provenance,
pub from: Point2D,
pub to: Point2D,
pub thickness: StaffSpace,
pub layer: i32,
pub style: GlyphStyle,
/// The vertical band this stroke belongs to: its owning staff,
/// declared by the projection that emitted it. A vertical solver
/// reads ownership from here rather than inferring it from
/// geometry. Unlike a glyph, a stroke is not listed in
/// `VerticalBand::members`; this is a one-way reference.
pub vertical_band: VerticalBandId,
}
/// A cubic-Bezier curve primitive (a slur), its four control
/// points in `p0`..`p3` drawing order.
pub struct Curve {
pub provenance: Provenance,
pub p0: Point2D,
pub p1: Point2D,
pub p2: Point2D,
pub p3: Point2D,
pub thickness: StaffSpace,
pub layer: i32,
pub style: GlyphStyle,
/// The line pattern the renderer strokes the path with
/// (a slur's authored `SpanStyle.line`): solid, dashed, or dotted.
pub line: LineStyle,
/// The vertical band this curve belongs to (see `Stroke`). A
/// slur's endpoints are lifted clear of its own staff, so its
/// owner is not recoverable from its geometry.
pub vertical_band: VerticalBandId,
}
\end{lstlisting}
\begin{requirement}
\label{req:layoutir:resolved-primitives}
\textbf{Non-glyph resolved primitives (ratified schema-major-2,
Phase~F).} Beyond glyphs, \texttt{ResolvedLayoutIR} carries
non-glyph \emph{line-stroke} primitives (staff lines, stems,
barlines, brackets) and \emph{cubic-B\'ezier curve} primitives
(slurs). Each \MUST{} carry provenance --- the basis of hit-testing
(\ref{sec:layoutir:provenance}) --- and is positioned by the solver
like a glyph: a spanning stroke or curve is re-spaced by the same
horizontal coordinate map its endpoint columns move under. These
primitives are \emph{non-canonical}: like layout identifiers and
glyph positions, they are not part of document state and enter no
content hash (\ref{req:layoutir:object-id-derivation}). A conformant
renderer draws them alongside the glyphs; the renderer encoding
itself is out of scope (\ref{sec:layoutir:render}).
\end{requirement}
\begin{requirement}
\label{req:layoutir:resolved-band-ownership}
\textbf{Band ownership through the resolved stage.} A resolved
\texttt{Stroke} and \texttt{Curve} \MUST{} retain the
\texttt{vertical\_band} they were given in
\texttt{ConstrainedLayoutIR}
(\ref{req:layoutir:primitive-band-ownership}), so a solver that
re-spaces or relocates them --- casting off into systems, or
renegotiating the gaps between a system's staves --- can attribute
each one to its owning staff. A \texttt{ResolvedGlyph} carries no
band: a glyph's ownership is consumed during the solve and its
outcome is already baked into the glyph's resolved position, so the
field would be dead weight in a rendering fingerprint.
Band ownership is \emph{non-canonical layout-attribution metadata}.
It enters no content hash and no canonical encoding
(\ref{req:layoutir:object-id-derivation}): it draws nothing, so two
layouts differing only in it are the same rendered layout. A
conformant canonical encoding of \texttt{ResolvedLayoutIR} therefore
omits \texttt{vertical\_band} even from the strokes and curves that
carry it.
\end{requirement}
\begin{requirement}
\label{req:layoutir:repeat-render}
\textbf{Minimal-tier repeat and volta rendering (ratified
schema-major-2, Phase~F).} A \texttt{RepeatStructure}
(\ref{sec:graph:repeats}) whose kind draws barlines
(\texttt{SimpleRepeat}, \texttt{Volta}) \MUST{} render a repeat
barline at each of its resolved boundaries --- the precomposed
repeat sign, either replacing a coinciding measure barline or
standing at the boundary column when none coincides, and the
repeat-dot pair beside a region-closing final barline (which is
never replaced). Each \texttt{Volta} \MUST{} render a bracket
spanning its resolved extent, with its ending numbers. A boundary
that does not resolve to a spacing column draws \emph{no} ink: a
traced anchor preserves the structure's provenance rather than
placing a mark at a false position. The jump kinds
(\texttt{DaCapo}, \texttt{DalSegno}) draw no Minimal-tier marks ---
their segno / coda / instruction text awaits a text primitive ---
and a repeat spanning more than one region renders in no single
region at this tier. The precise glyph vocabulary and spacing are
engraving-algorithm concerns (\ref{sec:layoutir:forward}).
\end{requirement}
\begin{requirement}
\label{req:layoutir:slur-curve}
\textbf{Slur rendering (ratified schema-major-2, Phase~F; extended
Push~3).} A \texttt{Slur} (\ref{sec:graph:slurs}) \MUST{} render
as a cubic-B\'ezier \texttt{Curve} arcing between its two endpoint
event columns, honoring an authored \texttt{CurvatureOverride}
(direction and apex height) when present and choosing them
otherwise. A slur whose endpoint does not resolve to a column on a
single staff of one region draws \emph{no} curve: a traced anchor
preserves its provenance rather than a floating arc. An authored
non-\texttt{Solid} \texttt{SpanStyle} line (dashed, dotted) is
rendered faithfully as that pattern (its \texttt{LineStyle} rides
the \texttt{Curve}); an implementation that instead defers the
pattern \MUST{} surface the deferral (a layout diagnostic), never
silently rendering solid. A slur whose span crosses a system break
\MUST{} split into per-system sub-curves --- the first carrying the
slur's provenance, the rest engraver-synthesized continuations
(\ref{req:layoutir:continuation-synthesis}) --- rather than drawing
whole in one system. The curvature-computing algorithm remains an
engraving-specification concern (\ref{sec:layoutir:forward}).
\end{requirement}
\section{RenderIR (Interface Only)}
\label{sec:layoutir:render}
The \texttt{RenderIR} stage is the renderer's input. Its full
specification is delivered separately; this section defines only the
interface contract.
\begin{lstlisting}[language=Rust]
pub trait RenderIRProducer {
/// Convert resolved IR to renderer-bound primitives.
fn produce(
&self,
resolved: &ResolvedLayoutIR,
scale: ScaleContext,
config: RenderConfiguration,
) -> RenderIR;
}
pub struct RenderConfiguration {
/// Target output: PDF, SVG, on-screen, print.
pub target: RenderTarget,
/// Color space and color management options.
pub color: ColorConfiguration,
/// Anti-aliasing and rasterization options.
pub rasterization: RasterizationConfiguration,
}
\end{lstlisting}
\begin{requirement}
\label{req:layoutir:render-provenance}
Implementations producing \texttt{RenderIR} \MUST{} preserve
provenance from \texttt{ResolvedLayoutIR}: every renderer primitive
\MUST{} be traceable to its originating \texttt{ResolvedLayoutIR}
primitive --- a \texttt{ResolvedGlyph}, \texttt{Stroke}, or
\texttt{Curve} --- and therefore to its score graph source. This is
the basis of hit-testing, selection, and back-reference navigation
in the UI.
The full RenderIR type and its production rules are specified in
the renderer specification, outside the scope of this document.
\end{requirement}
\section{Incremental Layout and Caching}
\label{sec:layoutir:incremental}
The pipeline supports incremental layout via per-object dependency
tracking. An edit to a score graph object invalidates only the IR
objects whose provenance lists it as a dependency.
\subsection{The Layout Cache}
\begin{lstlisting}[language=Rust]
pub struct LayoutCache {
/// Reverse index: maps score graph object ids to the layout
/// objects depending on them.
pub dependencies: DependencyIndex,
/// Cached IR stages, partitioned for granular invalidation.
pub logical: HashMap<RegionId, LogicalRegionCache>,
pub constrained: HashMap<RegionId, ConstrainedRegionCache>,
pub resolved: HashMap<SystemId, ResolvedSystemCache>,
/// Cached layout-stage outputs at finer granularity for
/// performance-critical paths.
pub fine_cache: FineLayoutCache,
}
pub struct DependencyIndex {
/// For each score graph object, the layout objects depending
/// on it. Reverse-indexed to make invalidation O(k) where k is
/// the number of dependents.
pub forward: HashMap<TypedObjectId, Vec<LayoutObjectId>>,
/// For each layout object, its dependencies. Used to verify and
/// rebuild the reverse index on cache writes.
pub reverse: HashMap<LayoutObjectId, Vec<TypedObjectId>>,
}
\end{lstlisting}
\subsection{Invalidation Rules}
\begin{requirement}
\label{req:layoutir:dependency-invalidation}
When a semantic operation (Chapter~\ref{ch:semops}) is applied to
the score graph, the following invalidation \MUST{} occur:
\begin{enumerate}
\item Every layout object whose \texttt{Provenance} lists a
mutated score graph object in its \texttt{source} or
\texttt{dependencies} is invalidated.
\item Invalidation propagates: layout objects that depend on
invalidated layout objects (via cross-region references,
spanning structures, etc.) are also invalidated.
\item Invalidated objects are cleared from the cache at their
respective stages.
\item Invalidated systems trigger casting-off re-evaluation for
themselves and any downstream systems whose horizontal extent
may shift.
\end{enumerate}
Re-engraving runs from the first invalidated stage forward, using
cached values for non-invalidated objects.
\end{requirement}
\subsection{Frame-Budget Requirements}
\begin{requirement}
\label{req:layoutir:incremental-frame-budget}
The incremental layout system \MUST{} support the frame-budget
requirements established in Chapter~\ref{ch:perf}: an edit
affecting a single system on a 100-page orchestral score \MUST{}
re-layout in under 16.7\,ms on the reference hardware profile.
Achieving this requires (a) fine-grained dependency tracking as
specified above; (b) cached glyph metrics in the font catalog;
(c) parallel re-engraving where the affected systems are
independent; (d) the constraint solver supporting partial
re-resolution of affected slots only (see Chapter~\ref{ch:solver}).
\end{requirement}
\section{Region Uniformity}
\label{sec:layoutir:region-uniformity}
Staff-based, free-graphic, and hybrid regions produce structurally
identical \texttt{LayoutRegion} containers. The differences are
expressed in:
\begin{itemize}
\item The \texttt{time\_axis}: metric, proportional, or aleatoric.
\item The \texttt{objects} mix: staff-based regions contain
primarily notation objects; free-graphic regions contain primarily
\texttt{Graphic} objects; hybrid regions contain both.
\end{itemize}
\begin{requirement}
\label{req:layoutir:uniform-region-solving}
All three region kinds \MUST{} use the same \texttt{LayoutRegion}
container type. The constraint solver \MUST{} treat regions
uniformly: it consumes spring slots and constraints without
branching on region kind.
\end{requirement}
\begin{rationale}
Uniform containers permit the constraint solver to be agnostic to
region kind, simplifying its implementation. The differences
between region kinds are encoded in the \texttt{TimeAxis}
implementation and the object mix, not in container structure.
This is the same insight that makes the file format and semantic
operations layers tractable: structural uniformity at the
containers, polymorphism in the contents.
\end{rationale}
\section{Glyph Catalog Interface}
\label{sec:layoutir:catalog}
The IR references glyphs by identifier; glyph metrics are queried
from a font catalog at IR construction. This section defines the
interface; the catalog implementation is outside the core
specification. \texttt{SmuflVersion}, below, is the same type declared
by a score's \texttt{SmuflVersionRequirement}
(Section~\ref{sec:tuning:smufl}): the glyph catalog's version and the
tuning context's declared version are one type, not two.
\begin{lstlisting}[language=Rust]
pub trait GlyphCatalog: Send + Sync {
/// Resolve a glyph reference to its metrics.
fn metrics(&self, glyph: &GlyphReference) -> Option<GlyphMetrics>;
/// Resolve a glyph reference to its rendering data.
fn render_data(&self, glyph: &GlyphReference) -> Option<GlyphRenderData>;
/// SMuFL version supported.
fn smufl_version(&self) -> SmuflVersion;
}
pub struct GlyphMetrics {
pub bounding_box: BoundingBox,
pub advance_width: StaffSpace,
pub anchors: HashMap<AnchorName, Point2D>,
}
pub struct GlyphRenderData {
/// Outline data: path commands suitable for vector rendering.
pub outline: Vec<PathCommand>,
/// Optional pre-rendered bitmap for raster targets.
pub bitmap: Option<GlyphBitmap>,
}
\end{lstlisting}
\begin{requirement}
\label{req:layoutir:glyph-catalog-lookups}
Implementations \MUST{} use a glyph catalog for metric and render-data
lookups during IR construction. Metrics \MUSTNOT{} be duplicated in
the IR; the IR carries glyph identifiers and queries the catalog.
Implementations \MAY{} cache metrics within IR objects as a
performance optimization provided cached values are invalidated when
the underlying catalog changes.
\end{requirement}
\subsection{Glyph Catalog Identity for Layout Conformance}
\label{sec:layoutir:catalog-identity}
Layout output depends on glyph metrics. For reproducible layout,
the exact glyph catalog used \MUST{} be identifiable. \texttt{smufl\_version}
below is a \texttt{SmuflVersion} (Section~\ref{sec:tuning:smufl}), ordered
by its fraction-normalized \texttt{minor\_centi} so a catalog identity's
version compares correctly against SMuFL's real release history.
\begin{lstlisting}[language=Rust]
/// A reproducibility-quality identifier for the glyph catalog
/// used to produce a layout. Required for any layout conformance
/// claim that depends on byte-equal output across runs.
pub struct GlyphCatalogIdentity {
/// SMuFL version targeted.
pub smufl_version: SmuflVersion,
/// Identifier of the specific font in use (e.g., Bravura,
/// Petaluma, Leland).
pub font_id: FontId,
/// Optional semantic version of the font, if the font
/// publisher uses versioned releases.
pub font_version: Option<SemVer>,
/// Content hash of the metric data made available to the
/// layout solve. The hash is over a canonical serialization
/// of every glyph's metrics (bounding box, advance width,
/// named anchors) for every glyph referenced by the
/// ConstrainedLayoutIR delivered to the solver, including
/// glyphs that are candidates but do not appear in the final
/// ResolvedLayoutIR. Computed by BLAKE3 with the domain
/// tag "MUSCFNTM".
pub metrics_hash: ContentHash,
}
\end{lstlisting}
\begin{requirement}
\label{req:layoutir:glyph-catalog-identity}
Any layout conformance claim subject to byte-equal output
(within-implementation determinism per
Chapter~\ref{ch:solver}) \MUST{} declare the
\texttt{GlyphCatalogIdentity} under which it was produced.
Two layouts produced with different \texttt{metrics\_hash}
values are layouts under different inputs; byte equality is
not expected.
Implementations that cannot identify their glyph catalog at
this granularity \MUST{} mark their layout output as
\emph{implementation-specific} or \emph{non-canonical};
conformance suites \MUSTNOT{} accept such output as
byte-equivalent to a reference solver's output.
The \texttt{metrics\_hash} is computed over the glyphs that
are \emph{inputs} to the layout solve (those referenced by the
\texttt{ConstrainedLayoutIR}), not only over glyphs in the
final layout. Two solver invocations with different available
candidate-glyph metrics are operating on different inputs even
if they happen to converge on the same final glyph set; this
rule makes that difference visible in the
\texttt{metrics\_hash}.
The hash is computed only over glyphs the layout solve
actually consulted, not over the entire font catalog, so that
font additions (new glyphs unused by the solve) do not
invalidate prior layouts.
\end{requirement}
\section{Forward References}
\label{sec:layoutir:forward}
\begin{itemize}
\item The constraint solver consuming \texttt{ConstrainedLayoutIR}
and producing \texttt{ResolvedLayoutIR} is specified in
Chapter~\ref{ch:solver}.
\item The serialization of layout cache data for cross-session
persistence is specified in Chapter~\ref{ch:format}.
\item The complete RenderIR specification, the engraving algorithm
specifications (beam grouping, slur curvature, casting-off,
accidental ordering), the font catalog implementation, and the
renderer are delivered as separate specifications layered on top
of this core document.
\end{itemize}
% ===========================================================================
\chapter{File Format}
\label{ch:format}
This chapter specifies the on-disk file format. Chapter~\ref{ch:semops}
established that the canonical document state is the deterministic
reduction of an operation-envelope set. This chapter specifies how
that operation set, along with acceleration structures, profile
declarations, and extension declarations, is laid out on disk in a
way that is crash-recoverable, atomically updatable, and forward-
compatible.
The byte-level encoding details (record layouts, varint conventions,
exact field bit widths) are deferred to a companion document, the
Binary Format specification. This chapter specifies \emph{what} is
stored, how the parts relate, and how the format behaves under
concurrent edits, crashes, and forward-compatibility scenarios; the
Binary Format specifies \emph{how the bits are laid out}.
The file format's name is \texttt{musc} and its file extension is
\texttt{.musc}.
\section{Design Principles}
\label{sec:format:principles}
\begin{description}
\item[Canonical state is the operation set.] The canonical
document is the operation-envelope set
(Chapter~\ref{ch:semops}). After pruning, the canonical document
is a retained base snapshot plus the operation envelopes after
that snapshot's causal frontier. Everything else stored in the
bundle (snapshots, layout caches, indexes, rendered artifacts)
is an acceleration structure and \MUST{} be discardable.
\item[Fixed prelude, atomic superblock commit.] The file begins
with a fixed-size header followed by two fixed-size superblock
slots. The superblocks are the only mutable on-disk objects in
the bundle. Commits append new chunks, write a new manifest
chunk, then flip the active superblock by writing to the
currently-inactive slot. This makes commits atomic with respect
to crashes.
\item[Content-addressed immutable chunks.] Every other on-disk
object is content-addressed by BLAKE3 of its uncompressed
payload (with domain-separated preimage). Chunks are immutable
once written. Duplicate content shares storage automatically.
\item[Manifest as a table of roots.] The manifest does not carry
user-facing data. It carries the operation-set roots, the
retained snapshot references, blob references, profile
declarations, extension declarations, and the text-projection
root. User-facing metadata (title, composer, etc.) lives in
operation envelopes or in materialized state, not in the
physical header.
\item[Crash recovery is by superblock selection.] Recovery is
simply: read both superblocks, validate, and select the highest
valid generation. Anything not reachable from the selected
superblock is unreachable garbage and can be reclaimed by a
later conservative GC pass.
\item[Forward compatibility through opaque preservation and edit
barriers.] Unknown extension chunks are preserved across reads
and writes. Unknown required extensions either prevent open or
force read-only mode. Unknown optional extensions are preserved
unless an edit crosses a declared edit barrier.
\item[Streaming reads.] The bundle is designed so that opening a
large score touches the prelude, the active superblock, the
manifest, and a small set of bootstrap chunks (the most recent
canonical-base snapshot plus the envelope index). Per-region
content loads on demand.
\item[Deterministic text projection of semantic content.] The
format admits a deterministic projection to a canonical
s-expression form for diff tooling and version control. The
text projection preserves canonical document semantics
(operation envelopes, profile declarations, extension
declarations, reduced state). It does \emph{not} preserve
chunk offsets, compression choices, cache chunks, garbage
regions, or superblock generation numbers.
\item[No encryption, no DRM.] The format \MUSTNOT{} include
encryption, access control, or rights-management features.
Privacy at rest is the responsibility of the filesystem and
operating system. The format is open and offers no mechanism by
which user content can be locked away from its author or from
other implementations.
\end{description}
\begin{nongoal}
Digital rights management (DRM), content encryption,
hardware-binding, license enforcement, and any mechanism that
could lock user content to a particular vendor or platform are
explicitly out of scope. The format is designed for openness,
durability, and user ownership of data. Implementations \MUSTNOT{}
add proprietary extensions intended to restrict access to or use
of user content.
\end{nongoal}
\section{Durable Writes}
\label{sec:format:durable}
This chapter refers throughout to ``durable flushes'' of file
contents. The definition is platform-abstracted.
\begin{description}
\item[Durable flush.] The platform operation that makes
previously-written bytes durable according to the host
filesystem contract: \texttt{fsync} on POSIX-like systems,
\texttt{FlushFileBuffers} or \texttt{NtFlushBuffersFileEx} on
Windows, equivalent calls on other platforms. A successful
durable flush guarantees that bytes written before the call are
on the persistent storage medium when the call returns.
\end{description}
\begin{requirement}
\label{req:format:durable-flush}
Implementations \MUST{} use the platform's durable flush
primitive at the points required by the atomic write protocol
(Section~\ref{sec:format:commit}). Implementations \MAY NOT{}
rely on filesystem ordering guarantees or write-back caching
alone; durability \MUST{} be requested explicitly at the commit
point.
\end{requirement}
\section{The Bundle Layout}
\label{sec:format:bundle}
A \texttt{.musc} bundle is a single file whose first 576 bytes are a
fixed prelude: a 64-byte header followed by two 256-byte superblock
slots. The remainder of the file holds variable-length content:
chunk payloads, the manifest chunk, blob payloads, the text
projection (if present), and unreachable garbage from prior commits
that has not yet been collected.
\begin{lstlisting}[language=Rust]
pub struct BundleLayout {
/// Fixed 64-byte header at offset 0.
pub header: FixedHeader,
/// Two fixed-size superblock slots at offsets 64 and 320.
pub superblock_a: Superblock, // at offset 64
pub superblock_b: Superblock, // at offset 320
/// Variable-length content begins at offset 576.
/// All chunks, the manifest, and blobs live here.
pub body: ContentRegion,
}
\end{lstlisting}
\subsection{The Fixed Header}
The header is exactly 64 bytes at file offset zero. It identifies
the format and locates the superblock slots. The header never
changes after the file is created.
\begin{lstlisting}[language=Rust]
pub struct FixedHeader {
/// Magic bytes: ASCII "MUSCBND\0" (8 bytes).
pub magic: [u8; 8],
/// Major format version. Major version changes are
/// non-backward-compatible.
pub format_major: u16,
/// Minor format version. Minor version changes are
/// backward-compatible.
pub format_minor: u16,
/// Length of this header in bytes (currently always 64).
/// Reserved for future expansion via header_length growth.
pub header_length: u32,
/// Offset of superblock slot A. Currently always 64.
pub superblock_a_offset: u64,
/// Offset of superblock slot B. Currently always 320.
pub superblock_b_offset: u64,
/// 128-bit file UUID, set at creation of the physical bundle.
/// Changes on Save As (the physical bundle is a new file).
/// Distinct from document_id, which identifies the logical work.
pub file_uuid: FileUuid,
/// Reserved bytes at offsets 48..60. MUST be zero on write;
/// MUST be ignored on read. Future minor versions MAY use
/// these bytes for additional fields whose absence is
/// interpreted as zero.
pub reserved: [u8; 12],
/// CRC-32C of bytes 0..60 of this header (i.e., all preceding
/// fields plus the reserved bytes). At fixed offset 60..64.
pub header_crc: u32,
}
\end{lstlisting}
\begin{requirement}
\label{req:format:fixed-header-integrity}
The header \MUST{} be exactly 64 bytes at file offset zero. Its
contents \MUSTNOT{} change after file creation, except for the
case where a major-version upgrade rewrites the entire file.
Readers \MUST{} verify the magic bytes and the header CRC before
consulting any other part of the file.
Reserved bytes \MUST{} be zero when written by current
implementations. Readers \MUST{} ignore the values of reserved
bytes (future versions may use them).
The \texttt{file\_uuid} is fixed for the lifetime of a physical
bundle and persists across commits to that bundle. A
filesystem-level byte copy preserves the \texttt{file\_uuid}
(the copy is the same physical bundle duplicated). Save As,
export-as-new-bundle, and derivative-work creation \MUST{}
assign a fresh \texttt{file\_uuid}; the resulting bundle is a
new physical bundle, distinct from its source.
The \texttt{file\_uuid} identifies the physical bundle, not the
logical work. Two bundles with the same \texttt{file\_uuid} are
byte-level copies of the same physical bundle; two bundles
with the same \texttt{document\_id} (defined in the manifest;
see Section~\ref{sec:format:identity}) are versions of the
same logical work, which may or may not share their
\texttt{file\_uuid}.
\end{requirement}
\subsubsection{The Container Epoch}
\label{sec:format:container-epoch}
The \texttt{format\_major} field is not only a wire-layout
discriminator. It is the container's \emph{epoch}: the statement of
which guarantees held at the moment the file was created. Because the
header never changes after creation, the epoch is a property of the
file's creation and cannot drift as the document is committed to ---
which is precisely what makes it able to carry a provenance guarantee
that a later generation must not be able to inherit.
Major \tablenums{1} is the first epoch to use that capacity. A major-1
container is one whose every base-bearing commit was validated against
a supplied reduction authority; a major-0 container carries no such
guarantee, and no sequence of commits can promote one into the other.
\begin{requirement}
\label{req:format:container-epoch}
A reader \MUST{} classify \texttt{format\_major} three ways rather
than testing it for equality with the version it writes:
\tablenums{0} is \emph{legacy} and \MUST{} be decoded deliberately as
such; \tablenums{1} is \emph{current}; any other value \MUST{} be
rejected as an unsupported format version. The classification
\MUST{} be carried as a value the reader can act on, not re-derived
by comparison at each use site.
Behaviour is then determined by the container's epoch together with
the presence of a canonical base:
\begin{itemize}
\item \emph{Legacy, no canonical base} --- the bundle \MAY{} be
opened. Nothing whose validity cannot be established is exposed.
\item \emph{Legacy, canonical base present} --- the bundle
\MUST{} be refused. A base predating any reduction authority is
not safe materialized state, and \MUSTNOT{} be served in a
read-only or otherwise restricted mode: a restricted view of
unverifiable canonical state is still unverifiable canonical
state, presented confidently.
\item \emph{Legacy, a commit attempts to add or replace a
canonical base} --- the commit \MUST{} be refused, and the
implementation \MUST{} direct the writer to repack into a fresh
major-1 container. This is the rule that makes the epoch
non-inheritable, and it is the reason a header field can carry
this guarantee where a superblock field could not.
\item \emph{Current, no canonical base} --- the bundle \MAY{} be
opened, and \MAY{} be committed to.
\item \emph{Current, canonical base present or introduced} ---
the base \MUST{} be validated against the reduction authority
the opening implementation supplies.
\end{itemize}
Where a container's canonical base disagrees with its active
superblock, that disagreement is \emph{corruption} and \MUST{} be
reported as a malformed bundle \emph{before} any epoch rule above is
applied, in either epoch. Ordering the epoch rules first would
collapse tampering into staleness and erase the distinction the
reduction authority depends on.
\end{requirement}
\subsection{The Superblock Slots}
Each superblock slot is exactly 256 bytes. The bundle has two slots
(A at offset 64, B at offset 320). One is active; the other is the
write target for the next commit. Atomic commit is the act of
writing the inactive slot and durably flushing it.
\begin{lstlisting}[language=Rust]
pub struct Superblock {
/// Magic bytes: ASCII "MUSCSUPR" (8 bytes).
pub magic: [u8; 8],
/// Generation counter. The active superblock is the one with
/// the highest valid generation.
pub generation: u64,
/// Offset of the manifest chunk for this generation.
pub manifest_offset: u64,
/// Length of the manifest chunk in bytes.
pub manifest_length: u64,
/// BLAKE3 content hash of the manifest chunk's uncompressed
/// payload, with domain-separated preimage
/// (Section~\ref{sec:format:hashing}).
pub manifest_hash: ContentHash,
/// Schema version of the manifest at this generation.
pub manifest_schema_version: SchemaVersion,
/// Reduction algorithm version that produced any
/// canonical-base snapshots in this manifest.
pub reduction_algorithm_version: ReductionAlgorithmVersion,
/// Profile under which this superblock is valid.
pub profile_id: ProfileId,
/// Commit state. Distinguishes a fully-committed superblock
/// from one that was written but whose commit had not yet
/// completed when the writer crashed (rare; the protocol does
/// not normally leave such a state visible).
pub commit_state: CommitState,
/// Wall-clock timestamp at commit, in canonical units.
/// Advisory only; superblock selection is by generation, not
/// by timestamp.
pub commit_timestamp: WallClockTime,
/// CRC-32C of the bytes of this superblock up to (but not
/// including) the CRC field. Lets readers reject torn writes.
pub superblock_crc: u32,
/// Reserved padding to 256 bytes. MUST be zero on write;
/// MUST be ignored on read.
pub reserved: [u8; /* computed */ 256 - HEADER_FIELDS_SIZE],
}
pub enum CommitState {
/// Fully committed. The manifest at manifest_offset is the
/// canonical state for this generation.
Committed,
/// Reserved for future use; writers MUST NOT produce
/// non-Committed values in this version of the format.
Reserved(u32),
}
\end{lstlisting}
\subsection{Superblock Selection}
\begin{requirement}
\label{req:format:superblock-selection}
On open, readers \MUST{} perform the following selection:
\begin{enumerate}
\item Read both superblock slots.
\item For each slot, validate: magic bytes, CRC, that the
referenced manifest chunk's actual BLAKE3 hash matches the
declared \texttt{manifest\_hash}, and that
\texttt{commit\_state} is \texttt{Committed}. A slot
failing any of these checks is \emph{not valid} for
ordinary selection.
\item A slot whose \texttt{commit\_state} is not
\texttt{Committed} is invalid for ordinary selection but
\MAY{} be inspected in diagnostic recovery mode. Writers
in this format version \MUSTNOT{} produce such states
under normal commit; observing one indicates either a
crashed writer (recovery falls back to the other slot per
the atomic-write protocol) or a non-conforming writer.
\item If both slots are valid and their generations differ by
more than one, the reader \MUST{} report an integrity anomaly
(the file may have been tampered with, accidentally merged,
or written by a non-conforming implementation). The reader
\MAY{} continue in read-only recovery mode using the
highest-generation valid slot; it \MUSTNOT{} silently treat
the file as normal.
\item If both slots are valid and their generations differ by
exactly one, the slot with the higher generation is active.
\item If both slots are valid and their generations are
\emph{equal}, the reader \MUST{} compare the slots'
\emph{load-bearing} selection fields:
\texttt{manifest\_hash}, \texttt{manifest\_schema\_version},
\texttt{reduction\_algorithm\_version}, and
\texttt{profile\_id}. The advisory fields
\texttt{commit\_timestamp} and the physical chunk
offset/length are \emph{excluded} from this comparison.
\begin{itemize}
\item If every load-bearing field is equal, the two slots
select equivalent canonical state; the reader \MUST{}
deterministically choose slot~A and open normally.
\item If any load-bearing field differs, the reader \MUST{}
report a format-layer \texttt{IntegrityAnomaly} of kind
\texttt{DivergentSameGeneration} and open the bundle
read-only. Two genuinely different committed states cannot
share a generation under a conforming writer (the
atomic-write protocol increments the generation on every
commit), so the divergence is a structural failure, not a
state the reader may silently resolve by picking one.
\end{itemize}
\item If exactly one slot is valid, that slot is active.
\item If neither slot is valid, the file is corrupt; readers
\MUST{} surface this as a hard error and \MUSTNOT{}
synthesize state.
\end{enumerate}
\end{requirement}
\begin{rationale}
The advisory fields are excluded from the equal-generation
comparison because they do not determine which canonical state a
slot selects: \texttt{commit\_timestamp} is wall-clock metadata,
and the physical offset/length merely locate the manifest chunk in
the file. Two slots that agree on the four load-bearing fields name
the same canonical document even if they were written at different
times or to different offsets, so treating an advisory-only
difference as divergence would raise false anomalies on benign
repacks. Conversely, a difference in any load-bearing field means
the two slots disagree about canonical state itself, which is the
precise condition that must not be silently resolved.
\end{rationale}
\section{The Manifest}
\label{sec:format:manifest}
The manifest is a table of roots and declarations. It is itself a
chunk (content-addressed, immutable). Each commit writes a new
manifest chunk and points the inactive superblock slot at it.
\begin{lstlisting}[language=Rust]
pub struct Manifest {
/// Identifier of the logical work. Stable across Save As
/// operations and across copies that are intended to remain
/// versions of the same work. May persist across forks if
/// the user explicitly preserves it.
pub document_id: DocumentId,
/// Optional identifier of a shared ancestor document. Used by
/// version-control workflows and fork-tracking to express
/// genealogy. Absent for documents with no declared ancestry.
pub lineage_id: Option<LineageId>,
/// Identifier of this manifest. Each commit produces a new
/// ManifestId; old manifests are retained in the chunk store
/// until garbage collection removes them subject to the
/// retention policy.
pub manifest_id: ManifestId,
/// Generation counter matching the superblock that references
/// this manifest.
pub generation: u64,
/// Operation-envelope blocks defining the canonical document.
/// Block ordering is irrelevant for canonical state (envelopes
/// are a set), and the canonical manifest encoding sorts chunk
/// references ascending by their encoded form (Appendix D), so
/// a writer cannot order this list by stamp; stamp-ordered
/// scanning goes through operation_block_summaries below.
pub operation_roots: Vec<ChunkRef>,
/// Per-block summary metadata (Section on operation-envelope
/// blocks below), keyed by the block's ChunkId in canonical
/// ascending order. Supplied by the operation layer and carried
/// opaquely by the storage layer; non-canonical and optional (a
/// block need not have an entry). Lets a reader select or skip
/// a block by causal frontier or stamp range without decoding
/// its envelopes.
pub operation_block_summaries:
BTreeMap<ChunkId, OperationBlockSummary>,
/// Optional operation index chunk: maps OperationId to
/// (block, offset) for fast random access. If absent,
/// implementations rebuild on demand by scanning blocks.
pub operation_index_root: Option<ChunkRef>,
/// The active canonical-base snapshot, if pruning has occurred.
/// At most one canonical base is active at a time. When
/// present, the canonical document is the base snapshot's
/// materialized state plus the deterministic reduction of
/// envelopes whose OperationIds are not covered by the base's
/// causal frontier.
pub canonical_base: Option<SnapshotRef>,
/// Acceleration snapshots: caches that materialize state at
/// various causal frontiers for fast incremental loading.
/// These MAY be discarded freely without affecting canonical
/// state.
pub acceleration_snapshots: Vec<SnapshotRef>,
/// Blob references for large opaque content (audio, images,
/// custom fonts).
pub blob_roots: Vec<BlobRef>,
/// Profile declarations: which conformance profiles this
/// manifest claims to satisfy.
pub profile_declarations: Vec<ProfileDeclaration>,
/// Extension declarations: which extensions contributed to
/// this manifest, with their required/optional flag and
/// edit barriers.
pub extension_declarations: Vec<ExtensionDeclaration>,
/// Text projection root chunk, if a text projection is
/// maintained in this bundle. Non-canonical accelerator.
pub text_projection_root: Option<ChunkRef>,
/// Optional integrity index root chunk: cross-references chunk
/// hashes for faster integrity verification of large bundles.
/// Non-canonical accelerator; absence is permitted.
pub integrity_root: Option<ChunkRef>,
}
\end{lstlisting}
\subsection{Canonical and Non-Canonical Manifest Roots}
\label{sec:format:roots}
The manifest's fields partition cleanly into two groups by their
relation to canonical state.
\textbf{Canonical roots} (define the document):
\begin{itemize}
\item \texttt{operation\_roots}: operation-envelope blocks
containing the canonical operation set.
\item \texttt{canonical\_base}: the active canonical base
snapshot, if pruning has occurred.
\item \texttt{blob\_roots}: blobs referenced by canonical
operations or by canonical reduced state are themselves
canonical; blobs referenced only by acceleration structures
are non-canonical.
\item \texttt{document\_id}, \texttt{lineage\_id},
\texttt{profile\_declarations},
\texttt{extension\_declarations}: structural metadata
governing how the canonical document is interpreted.
\end{itemize}
\textbf{Non-canonical reachable roots} (accelerators only):
\begin{itemize}
\item \texttt{operation\_index\_root}: rebuildable from
operation envelope blocks.
\item \texttt{acceleration\_snapshots}: caches at causal
frontiers other than the canonical base.
\item \texttt{text\_projection\_root}: derivable from canonical
state.
\item \texttt{integrity\_root}: rebuildable from chunk hashes.
\end{itemize}
\begin{requirement}
\label{req:format:manifest-reachability}
The manifest \MUST{} list every chunk that contributes to the
current canonical state, transitively through its canonical
roots (operation roots, canonical base, canonical blobs and
extension chunks). It \MAY{} additionally list non-canonical
reachable roots (acceleration snapshots, operation index, text
projection, integrity index, layout caches).
Chunks not reachable from any manifest root, canonical or
non-canonical, are unreachable; readers \MUSTNOT{} rely on
them and conservative garbage collection \MAY{} reclaim them
(Section~\ref{sec:format:gc}).
Non-canonical reachable chunks \MAY{} be rebuilt or replaced
without altering canonical state. If a non-canonical chunk's
hash fails verification, it \MUST{} be discarded and (if
needed) rebuilt; failed verification of a non-canonical chunk
is \emph{not} a corruption of the bundle.
Failed verification of a canonical chunk (operation envelope
block, canonical base snapshot, canonical blob, manifest)
\emph{is} corruption and \MUST{} be surfaced as a hard error.
User-facing metadata (title, composer, lyricist, copyright, and
similar fields) is \emph{not} part of the manifest. Such metadata
lives in the operation set and the materialized state. The
manifest carries only the structural roots and declarations that
the format needs to locate and validate content.
\end{requirement}
\subsection{File, Document, and Lineage Identity}
\label{sec:format:identity}
The format distinguishes three identifiers, each serving a
different identity question:
\begin{description}
\item[\texttt{file\_uuid}.] Physical bundle identity. Set at
file creation; persists for the lifetime of the physical
file. \textbf{Changes on Save As}: a copy created by Save As
is a new physical bundle and \MUST{} be assigned a fresh
\texttt{file\_uuid}. Used by tools that need to identify
``this exact file on disk'' (caches, backup software, file
fingerprinting).
\item[\texttt{document\_id}.] Logical work identity. Stable
across Save As copies that are intended to remain the same
work (the canonical case: ``I'm saving my piece to a backup
folder, and it's still the same piece''). \textbf{Persists
across forks at the user's discretion}: a fork that
represents a derivative work \SHOULD{} have a new
\texttt{document\_id}, while a fork that is a working copy
of the original \SHOULD{} preserve it. The format does not
enforce a policy; the editor surfaces the choice.
\item[\texttt{lineage\_id}.] Optional shared ancestor identity.
Records that two documents share a common ancestor for
version-control or genealogy purposes. Multiple documents
\MAY{} share a \texttt{lineage\_id}; the field is set when a
fork explicitly preserves ancestry information.
\end{description}
\begin{requirement}
\label{req:format:file-document-lineage-identity}
Implementations \MUST{} assign a fresh \texttt{file\_uuid} on
any Save As operation. They \MUSTNOT{} carry the source file's
UUID into the new file: doing so would conflate physical and
logical identity and break tools that rely on
\texttt{file\_uuid} for content fingerprinting.
The choice of whether to preserve \texttt{document\_id} across
a Save As is exposed to the user: the editor \SHOULD{} default
to preserving \texttt{document\_id} (the common case is
``save a copy of the same work to a new file'') and \SHOULD{}
offer a clear ``Save As Derivative Work'' or equivalent action
that mints a new \texttt{document\_id}.
The format \MUSTNOT{} treat documents with the same
\texttt{document\_id} as automatically synchronizable: that
decision belongs to the collaboration transport
(Appendix~\ref{app:deferred}).
\end{requirement}
\subsection{Manifest Encoding}
\label{sec:format:manifest-encoding}
The manifest chunk is special: it is the bootstrap entry from the
superblock into the chunk graph. A cold reader needs to decode the
manifest before it has any information from the chunk store.
\begin{requirement}
\label{req:format:uncompressed-manifest}
In this format version, the manifest chunk \MUST{} be stored
\emph{uncompressed}. Its \texttt{CompressionAlgorithm} on disk
is implicitly \texttt{None}; the superblock's
\texttt{manifest\_length} field is therefore both the on-disk
length and the uncompressed payload length. Implementations
\MUST{} reject as malformed any bundle whose manifest payload
is not directly parseable as a canonical manifest chunk's
uncompressed bytes.
This rule eliminates a bootstrap chicken-and-egg problem: a
reader can decode the manifest using only information present
in the fixed header and the active superblock, without first
consulting the manifest to learn how the manifest is compressed.
All other chunks (operation envelope blocks, snapshots,
extension data, layout caches, blobs, integrity indexes,
operation indexes, text projection chunks) \MAY{} be compressed.
Future format major versions \MAY{} permit manifest compression,
but doing so requires explicit superblock fields
(\texttt{manifest\_compression},
\texttt{manifest\_uncompressed\_length}) and is a
non-backward-compatible change.
\end{requirement}
\begin{rationale}
The manifest is small (a few kilobytes at most for typical
scores), so compression yields negligible space savings.
Eliminating the bootstrap dependency is far more valuable than
the few hundred bytes of compression headroom.
\end{rationale}
\begin{requirement}
\label{req:format:manifest-id}
The \texttt{manifest\_id} field \MUST{} be derived as
\[
\texttt{ManifestId} = \mathrm{trunc128}\!\left(
\mathrm{BLAKE3}(\texttt{"MUSCMNIF"} \Vert \texttt{document\_id}
\Vert \texttt{generation} \Vert \texttt{manifest\_body})\right),
\]
where \texttt{document\_id} is its 16-byte canonical form,
\texttt{generation} is the 8-byte little-endian \texttt{u64}, and
\texttt{manifest\_body} is the canonical manifest encoding with the
\texttt{manifest\_id} field itself \emph{excluded}. The exclusion is
normative: a conforming writer \MUST{} omit (equivalently, zero) the
\texttt{manifest\_id} field when computing the preimage, so that the
identifier does not reference itself. $\mathrm{trunc128}$ takes the
leading 16 bytes of the 32-byte BLAKE3 output. Two conforming
writers committing the same manifest body at the same generation of
the same document therefore derive identical \texttt{ManifestId}s.
\end{requirement}
\section{Content Hashing}
\label{sec:format:hashing}
Epiphany uses BLAKE3 as its single content-hashing algorithm.
\begin{requirement}
\label{req:format:blake3-content-hashes}
All content hashes in the bundle \MUST{} be BLAKE3-256 outputs
(32 bytes). Implementations \MUSTNOT{} use any other algorithm
for content addressing; SHA-256, SHA-3, and other algorithms
\MUSTNOT{} appear in this version of the format. Future major
versions \MAY{} introduce additional algorithms; such changes
require a new format major version.
\end{requirement}
\subsection{Domain-Separated Preimages}
A naive content-addressing scheme that hashes only payload bytes is
vulnerable to collisions across chunks of different semantic kinds.
Epiphany uses domain-separated preimages.
\begin{lstlisting}[language=Rust]
/// Computes the canonical hash preimage for a chunk's content.
/// The chunk's compression algorithm is metadata on the ChunkRef,
/// not part of the content identity: identical uncompressed
/// payloads produce identical hashes regardless of how they are
/// physically compressed in storage. This preserves deduplication
/// across compression choices.
fn hash_preimage(
domain_tag: &[u8; 8],
chunk_kind: ChunkKind,
schema_version: SchemaVersion,
uncompressed_length: u64,
uncompressed_payload: &[u8],
) -> Vec<u8> {
let mut preimage = Vec::new();
preimage.extend_from_slice(domain_tag);
preimage.extend_from_slice(&chunk_kind.canonical_bytes());
preimage.extend_from_slice(&schema_version.canonical_bytes());
preimage.extend_from_slice(&uncompressed_length.to_le_bytes());
preimage.extend_from_slice(uncompressed_payload);
preimage
}
\end{lstlisting}
\begin{requirement}
\label{req:format:domain-separated-chunk-hashes}
The content hash of a chunk \MUST{} be BLAKE3 of the canonical
preimage above. The domain tag, chunk kind, schema version, and
uncompressed length are part of the preimage; the compression
algorithm is \emph{not}. Two chunks with identical uncompressed
payloads, identical kinds, and identical schema versions \MUST{}
produce identical hashes regardless of their compression choices.
\end{requirement}
\begin{rationale}
Including compression algorithm in the hash would cause
identical content stored with different compression to have
different addresses, defeating deduplication and forcing churn
during recompression or repack. Including domain tag, kind, and
schema version in the preimage closes a real collision: two
semantically different chunks with identical raw bytes are no
longer interchangeable.
The domain tag is a fixed 8-byte string identifying the bundle
format and protecting against cross-format collisions if the
same BLAKE3 hash were reused in unrelated contexts. The canonical
domain tag for \texttt{.musc} chunks is \texttt{"MUSCCHNK"};
manifest chunks use \texttt{"MUSCMANI"}; blob payloads use
\texttt{"MUSCBLOB"}.
\end{rationale}
\section{Chunks}
\label{sec:format:chunks}
A chunk is an immutable, content-addressed unit of storage. Chunks
are referenced from the manifest by \texttt{ChunkRef}.
\begin{lstlisting}[language=Rust]
pub struct ChunkRef {
/// Content identifier: BLAKE3 of the canonical preimage.
pub id: ChunkId,
/// Kind of chunk. The reader uses this to dispatch parsing.
pub kind: ChunkKind,
/// Schema version targeted by this chunk's payload encoding.
pub schema_version: SchemaVersion,
/// Offset of the chunk's compressed payload in the bundle file.
pub offset: u64,
/// Length of the on-disk compressed payload.
pub compressed_length: u64,
/// Length of the uncompressed payload (used to size buffers
/// before decompression).
pub uncompressed_length: u64,
/// Compression algorithm. Part of the ChunkRef, not part of
/// the chunk's identity.
pub compression: CompressionAlgorithm,
/// Restated content hash, redundant with the id field but
/// included in the ChunkRef for fast verification without
/// fetching the chunk.
pub hash: ContentHash,
}
pub enum ChunkKind {
/// A block of operation envelopes.
OperationEnvelopeBlock,
/// An operation-id-to-block index accelerator.
OperationIndex,
/// A materialized snapshot of canonical state (full or
/// partial). Canonical if referenced as the manifest's
/// canonical_base; cache (acceleration) otherwise.
Snapshot,
/// A large opaque blob (audio, image, font, ML model).
Blob,
/// Extension-defined data, preserved opaquely by core readers.
ExtensionData,
/// The text projection's root document.
TextProjection,
/// Layout cache: derivative from canonical state, always
/// discardable.
LayoutCache,
/// Integrity index: cross-references chunk hashes.
IntegrityIndex,
/// Manifest chunk. Special: referenced from the superblock,
/// not from another chunk.
Manifest,
}
pub enum CompressionAlgorithm {
/// No compression. Stored payload bytes equal uncompressed.
None,
/// Zstandard compression. Implementations MUST support reading.
Zstd { level: u8 },
/// Other algorithms reserved for future versions.
Reserved(u8),
}
pub struct ContentHash(pub [u8; 32]);
/// Chunk identifier. A newtype around ContentHash: the chunk's
/// identifier is identical to its content hash, but the type
/// distinction is preserved to make role visible at use sites
/// (a ChunkId is what you store in a ChunkRef and what you look
/// up; a ContentHash is what you compute by hashing). Both occupy
/// the same 32 bytes.
pub struct ChunkId(pub ContentHash);
\end{lstlisting}
\begin{requirement}
\label{req:format:chunkkind-discriminants}
\texttt{ChunkKind::canonical\_bytes()} is a single byte: the
variant's declaration-order discriminant. Because that byte is part
of the chunk hash preimage (Section~\ref{sec:format:chunks},
``Domain-Separated Preimages''), this assignment is normative and
stable and \MUSTNOT{} be reordered:
\begin{center}
\begin{tabular}{r l @{\qquad} r l}
\toprule
\textbf{Disc.} & \textbf{Variant} &
\textbf{Disc.} & \textbf{Variant} \\
\midrule
0 & \texttt{OperationEnvelopeBlock} & 5 & \texttt{TextProjection} \\
1 & \texttt{OperationIndex} & 6 & \texttt{LayoutCache} \\
2 & \texttt{Snapshot} & 7 & \texttt{IntegrityIndex} \\
3 & \texttt{Blob} & 8 & \texttt{Manifest} \\
4 & \texttt{ExtensionData} & & \\
\bottomrule
\end{tabular}
\end{center}
\texttt{CompressionAlgorithm} encodes as a fixed \emph{two} bytes: a
declaration-order discriminant byte followed by a single parameter
byte that is always present. \texttt{None} $= 0$, and its parameter
byte \MUST{} be zero --- a reader \MUST{} reject a non-zero one rather
than ignore it (Section~\ref{sec:format:binary}, and the Binary Format
companion's \sectionsc{Chunk References}). \texttt{Zstd\{level\}}
$= 1$ with the \texttt{level} in the parameter byte;
\texttt{Reserved(u8)} $= 2$ with the reserved value in the parameter
byte. The parameter byte is written even for \texttt{None}, so the
encoding is fixed-width and a reader always consumes two bytes.
Compression is \emph{not} part of a
chunk's content identity (it is metadata on the \texttt{ChunkRef}),
so these discriminants are stable but do not affect chunk hashes.
\texttt{ProfileId}'s discriminants are pinned in
Section~\ref{sec:format:profiles}
(Requirement~\ref{req:format:profileid-discriminants}).
\end{requirement}
\begin{requirement}
\label{req:format:immutable-verified-chunks}
Chunks \MUST{} be immutable. Once a chunk is written and its bytes
durably flushed, those bytes \MUSTNOT{} be modified. New state
is encoded as new chunks with new identifiers; old chunks become
unreachable garbage when their referencing manifests are no
longer reachable.
Implementations \MUST{} verify chunk hashes on first read of each
chunk in a session: decompress the payload, compute the canonical
preimage's BLAKE3, and confirm the result matches the declared
\texttt{hash}. Subsequent reads \MAY{} skip verification if the
chunk has been validated within the current session. A chunk
whose hash does not match \MUST{} be treated as corrupt: if it is
a cache chunk, discard and rebuild; if it is a canonical chunk
(operation block, retained snapshot, or blob referenced from the
manifest), surface as hard corruption.
Readers \MUST{} verify that decompression of a chunk's payload
produces exactly \texttt{uncompressed\_length} bytes. Length
mismatch is treated as corruption.
\end{requirement}
\section{Operation Envelope Blocks}
\label{sec:format:opblocks}
The canonical document is stored as operation-envelope blocks. Each
block is a chunk of kind \texttt{OperationEnvelopeBlock} whose
payload is a vector of envelopes. A block is identified by its
\texttt{ChunkId}; because chunks are content-addressed, that
identifier cannot appear inside the block payload itself. Summary
metadata for a block travels in the \emph{manifest}
(\texttt{operation\_block\_summaries}), keyed by the block's chunk
id: the summary is computed by the operation layer from the block's
envelopes, and the storage layer carries it as opaque bytes without
interpreting it.
\begin{lstlisting}[language=Rust]
pub struct OperationEnvelopeBlock {
/// Operation envelopes in this block. Order within a block is
/// not normative for canonical reduction (envelopes are a set)
/// but writers SHOULD preserve authoring order within a block
/// for diagnostic purposes.
pub envelopes: Vec<OperationEnvelope>,
}
/// Manifest-side summary of one block, keyed by the block's
/// ChunkId. Non-canonical and optional: a reader MUST NOT rely
/// on a summary being present, and canonical state never depends
/// on one.
pub struct OperationBlockSummary {
/// DVV summary covering the envelopes in this block. Enables
/// readers to determine causal coverage without parsing every
/// envelope.
pub dvv_summary: CausalContext,
/// Earliest stamp in the block.
pub min_stamp: OperationStamp,
/// Latest stamp in the block.
pub max_stamp: OperationStamp,
}
\end{lstlisting}
\begin{requirement}
\label{req:format:operation-block-bounds}
Writers \SHOULD{} begin a new operation-envelope block when
adding another envelope would cause the uncompressed block
payload to exceed 1 \,MiB, except when an individual envelope's
encoded size exceeds 1\,MiB, in which case the envelope occupies
its own block.
Readers \MUST{} accept valid blocks of any size up to a profile-
declared maximum (default: 64\,MiB uncompressed). Profile
declarations \MAY{} raise or lower this bound. Blocks exceeding
the active profile's bound \MUST{} be treated as malformed.
The \texttt{operation\_roots} field of the manifest \MUST{} list
every operation-envelope block that contributes to the canonical
document. The set of envelopes \emph{is} the union of all
envelopes across all referenced blocks; block boundaries are
storage artifacts, not semantic structure.
\end{requirement}
\subsection{The Operation Index}
The optional \texttt{operation\_index\_root} chunk maps each
\texttt{OperationId} to the \texttt{ChunkRef} of its enclosing
block plus an offset within the block. It enables $O(\log n)$
lookup of an operation by id.
\begin{requirement}
\label{req:format:rebuildable-operation-index}
The operation index is an acceleration structure, not canonical.
If absent, readers rebuild it by scanning all blocks. If present
but corrupt or stale, readers \MUST{} reject the index and
rebuild from blocks.
Writers \SHOULD{} rebuild or incrementally update the operation
index at commit time when the operation set has grown
significantly since the last commit.
\end{requirement}
\section{Snapshots and Canonical Bases}
\label{sec:format:snapshots}
A snapshot is a materialized score state at a particular causal
frontier. Snapshots have two distinct roles:
\begin{description}
\item[Acceleration snapshot.] A cache of the materialized state at
some point, used to skip reduction work. Listed in the manifest
(or not), but \emph{not} declared as a canonical base.
Discardable.
\item[Canonical base.] A materialized state at a causal frontier
that the manifest declares as the base after pruning. Together
with the operation envelopes after the frontier, this
constitutes the canonical document. Not discardable while it
remains declared.
\end{description}
\begin{lstlisting}[language=Rust]
pub struct SnapshotRef {
pub snapshot_id: SnapshotId,
/// The causal frontier this snapshot materializes: the
/// envelopes whose effects are baked into the snapshot.
pub covers_causal_frontier: CausalContext,
/// Reduction algorithm version under which the snapshot was
/// produced. Snapshots produced under an earlier algorithm
/// version cannot be used as canonical bases under a later
/// algorithm without rebuilding.
pub reduction_algorithm_version: ReductionAlgorithmVersion,
/// Profile under which the snapshot was produced.
pub profile_id: ProfileId,
/// Root chunk of the snapshot's materialized state.
pub root: ChunkRef,
/// Hash of the snapshot's root chunk for fast verification.
pub hash: ContentHash,
}
\end{lstlisting}
\subsection{The Canonical Document Identity}
\begin{requirement}
\label{req:format:canonical-document-reduction}
The canonical document is defined as follows:
\begin{itemize}
\item If \texttt{canonical\_base} is \texttt{None}, the
canonical document is the deterministic reduction
(Section~\ref{sec:semops:reduction}) of the union of
envelopes in all \texttt{operation\_roots} blocks.
\item If \texttt{canonical\_base} is \texttt{Some(base)}, the
canonical document is the base snapshot's materialized
state plus the deterministic reduction of all envelopes
whose \texttt{OperationId} is \emph{not covered} by the
base's \texttt{covers\_causal\_frontier}. An
\texttt{OperationId} is covered by a \texttt{CausalContext}
if and only if it appears in the dotted version vector's
per-replica contiguous range or among its dots (i.e.,
causal-precedes-or-equals under the DVV's induced partial
order). HLC stamps \MUSTNOT{} be used to determine
coverage; only the DVV frontier is canonical.
\item A snapshot may serve as the canonical base only if its
\texttt{reduction\_algorithm\_version} equals the active
superblock's \texttt{reduction\_algorithm\_version} and its
\texttt{profile\_id} matches the manifest's profile
declarations.
\end{itemize}
At most one \texttt{canonical\_base} is active at any time.
Pruning replaces the current canonical base with a new one
whose frontier strictly dominates the previous frontier under
the DVV partial order; the old base becomes unreachable and
is subject to garbage collection.
Acceleration snapshots (\texttt{acceleration\_snapshots}) \MUST{}
be ignored for the purposes of canonical state. They exist to
accelerate cold open and incremental reduction; if their content
disagrees with what the canonical reduction would produce, they
\MUST{} be rebuilt or discarded.
Caches, indexes, layout chunks, and other materialized artifacts
in the bundle are acceleration structures only and \MUST{} be
ignored or rebuilt if they disagree with the canonical document.
\end{requirement}
\begin{requirement}
\label{req:format:reduction-authority}
An implementation \MUST{} name the reduction semantics it
implements, and \MUST{} validate a canonical base against that
value rather than against a value the base itself supplied.
Comparing a base's \texttt{reduction\_algorithm\_version} only
against the active superblock's, as
Requirement~\ref{req:format:canonical-document-reduction}
requires, is necessary but \emph{not} sufficient: a writer that
seeds the superblock from the base's own self-report makes that
comparison an identity, so it holds for every conformingly
written document and detects only tampering. The semantics the
running implementation actually implements is a third value, and
it \MUST{} participate.
Accordingly:
\begin{itemize}
\item An implementation \MUST{} expose the reduction-algorithm
version it implements, and every reader and writer of a
canonical base \MUST{} state which semantics it implements
rather than infer it from the document.
\item A reader \MUST{} refuse to open a document whose
canonical base's \texttt{reduction\_algorithm\_version}
differs from the semantics the reader implements. A document
with no canonical base \MUST{} open regardless, since it
carries no reduced state that could be stale.
\item A writer \MUST{} refuse to emit or replace a canonical
base whose \texttt{reduction\_algorithm\_version} differs
from the semantics the writer implements. A commit that does
not emit or replace the base \MUSTNOT{} be refused on this
ground.
\item The refusal \MUSTNOT{} degrade to a read-only open and
\MUSTNOT{} be reported as an integrity anomaly. A stale base
is not a restricted-but-correct view of the document; it is
state computed under different rules, and serving it
read-only would present incorrect canonical state as
authoritative.
\item A refusal on this ground \MUST{} remain distinguishable
from a malformed-document failure. A base whose version
disagrees with its own superblock is corrupt, and corruption
\MUST{} be reported as such: the two conditions detect
different faults, and collapsing them loses the distinction
this requirement rests on.
\end{itemize}
Rebuilding is out of scope for opening. An implementation
\MUSTNOT{} silently rebuild a stale base during open, because
the envelopes required to rebuild may have been pruned
(Requirement~\ref{req:format:pruning-state-preservation}); a rebuild
path is sound only
where the full pre-base history is demonstrably present.
\end{requirement}
\subsection{Pruning}
\begin{requirement}
\label{req:format:pruning-state-preservation}
Pruning is the act of removing operation envelopes that are
causally covered by a new canonical base snapshot. Pruning:
\begin{enumerate}
\item Materializes a snapshot covering the chosen causal
frontier (a DVV).
\item Replaces the current \texttt{canonical\_base} field
with a \texttt{SnapshotRef} naming the new snapshot.
\item Removes operation-envelope blocks whose envelopes are
\emph{entirely} covered by the new frontier. Blocks with
mixed coverage \MUST{} be split or retained whole;
uncovered envelopes \MUST{} remain reachable.
\item Commits the new manifest via the atomic write protocol
(Section~\ref{sec:format:commit}).
\end{enumerate}
Pruning \MUSTNOT{} alter the canonical document state: the
reduction of (base snapshot $+$ post-frontier envelopes) under
the active algorithm \MUST{} equal the reduction of the
pre-pruning envelope set.
Implementations \MUSTNOT{} prune envelopes outside the explicit
pruning protocol. Operation envelopes are not silently garbage-
collected based on age or other heuristics.
\end{requirement}
\section{Blobs}
\label{sec:format:blobs}
The blob store holds large opaque content: audio reference tracks,
embedded images, custom fonts, ML model checkpoints.
\begin{requirement}
\label{req:format:blob-hash-shape}
A \texttt{BlobId} \MUST{} be BLAKE3 over the \emph{bare} preimage
\texttt{"MUSCBLOB"} $\Vert$ \texttt{uncompressed\_payload}: the
8-byte domain tag immediately followed by the uncompressed payload
bytes, with \emph{no} chunk-kind, schema-version, or length fields.
This is deliberately \emph{unlike} the structured chunk preimage of
Section~\ref{sec:format:chunks} (which commits to kind, schema
version, and length): a blob is opaque payload with no schema, so
its identity commits only to the domain tag and the bytes. As with
chunks, the compression algorithm is metadata on the
\texttt{BlobRef} and is \emph{not} part of the blob's identity, so
identical payloads stored under different compression share a
\texttt{BlobId}.
\end{requirement}
\begin{lstlisting}[language=Rust]
pub struct BlobRef {
pub blob_id: BlobId,
pub media_type: String, // RFC 6838 media type
pub offset: u64,
pub compressed_length: u64,
pub uncompressed_length: u64,
pub compression: CompressionAlgorithm,
pub hash: ContentHash,
/// Optional declared maximum size that readers MAY use to
/// reject blobs that exceed reader resource limits.
pub declared_max_uncompressed_length: Option<u64>,
}
\end{lstlisting}
\begin{requirement}
\label{req:format:lazy-bounded-blobs}
Readers \MUST{} apply resource limits to blob loading:
uncompressed length \MUST{} be checked against the reader's
policy before decompression begins; \texttt{media\_type} \MUST{}
be validated against the reader's accepted list before any
type-specific parsing.
Implementations \MUSTNOT{} eagerly load all blobs at open;
blobs \MUST{} be streamed on demand. Operations requiring blob
content \MUST{} be designed for lazy access.
Blob content is opaque to the core. The core stores, transmits,
and integrity-checks blobs; it does not interpret their bytes.
\end{requirement}
\section{The Atomic Write Protocol}
\label{sec:format:commit}
A commit transforms the bundle from one canonical state to another.
The protocol uses two superblock slots so that a crash at any point
during commit leaves the bundle in either the pre-commit state
(active superblock unchanged) or the post-commit state (active
superblock now points at the new manifest); no intermediate state
is visible.
\begin{requirement}
\label{req:format:atomic-commit-sequence}
Commits \MUST{} follow this sequence:
\begin{enumerate}
\item Write all new chunks (envelope blocks, possibly a new
snapshot, possibly a new operation index, etc.) to file
regions outside the prelude and outside currently-reachable
chunks. Writers \MAY{} append at end-of-file or fill known-
unreachable regions, but \MUSTNOT{} overwrite any
currently-reachable chunk.
\item Durably flush the file.
\item Compute the new manifest chunk's payload and write it.
\item Durably flush the file.
\item Compute the new \texttt{Superblock} with generation
$= \text{active\_generation} + 1$ and \texttt{manifest\_offset}
pointing at the new manifest chunk.
\item Write the new superblock to the currently-inactive
superblock slot (a 256-byte write at a fixed offset).
\item Durably flush the file. \emph{This is the commit point.}
Before this flush returns successfully, the active superblock
is the old one; after it returns successfully, the active
superblock is the new one.
\end{enumerate}
After step 7, the previously-active superblock slot becomes the
inactive slot for the next commit. Garbage collection is a
separate, optional, deferred operation (Section~\ref{sec:format:gc}).
\end{requirement}
\subsection{Crash Recovery}
\begin{requirement}
\label{req:format:crash-recovery}
On open after any crash, recovery follows the superblock
selection rule (Section~\ref{sec:format:bundle}). The bundle is
always in a recoverable state:
\begin{itemize}
\item Crash between steps 1 and 7: the active superblock is
unchanged; the new chunks and the new manifest are
unreachable garbage; the document opens at the pre-commit
state. A later GC reclaims the orphaned bytes.
\item Crash during step 7: the superblock slot write may be
torn. The CRC check rejects torn writes. Recovery falls back
to the other slot. The document opens at the pre-commit
state. The partially-written slot will be overwritten by the
next successful commit.
\item Crash after step 7: the new superblock is active. The
document opens at the post-commit state. No special
recovery is needed.
\end{itemize}
Recovery \MUSTNOT{} synthesize or guess: if neither superblock
validates, the file is corrupt and \MUST{} be reported as such.
\end{requirement}
\section{Garbage Collection and Retention}
\label{sec:format:gc}
Garbage collection reclaims bytes occupied by chunks that are no
longer reachable from any retained manifest. Retention policy
governs which old manifests are preserved for rollback.
\subsection{Retention Policy}
\begin{lstlisting}[language=Rust]
pub struct RetentionPolicy {
/// Maximum number of previous manifests to retain beyond the
/// active one. 0 means "no rollback retention"; the active
/// manifest is the only one preserved. The default declared by
/// the Full profile is profile-defined.
pub retain_previous_manifests: u32,
/// Optional wall-clock duration beyond which old retained
/// manifests may be reclaimed regardless of count. None means
/// no time-based eviction.
pub retain_duration: Option<WallClockDuration>,
/// Whether to preserve manifests explicitly marked as named
/// checkpoints (user-invoked "save a version" action) beyond
/// the count and duration limits.
pub retain_named_checkpoints: bool,
}
\end{lstlisting}
\begin{requirement}
\label{req:format:manifest-retention}
The active conformance profile \MUST{} declare a
\texttt{RetentionPolicy}. Implementations \MUST{} honor the
declared policy: chunks reachable from any retained manifest
\MUSTNOT{} be garbage-collected, and retention selection
(which manifests are retained) \MUST{} be deterministic given
the manifest history and the policy.
Rollback is the act of writing a new superblock that points at
a previously-active manifest still reachable in the chunk
store. The fixed layout has exactly two superblock slots;
rollback does not require additional on-disk superblocks. It
requires only that the target manifest's reachable chunks
remain in the bundle, which the retention policy guarantees.
\end{requirement}
\subsection{Garbage Collection Rules}
\begin{requirement}
\label{req:format:conservative-garbage-collection}
Garbage collection is a conservative, optional, deferred
operation. It \MUST{} preserve every byte reachable transitively
from:
\begin{enumerate}
\item The active superblock's manifest.
\item Every retained manifest selected by the active
\texttt{RetentionPolicy}.
\item Any pending edit's working set (chunks written but not
yet committed).
\end{enumerate}
Garbage collection \MUSTNOT{} run as part of a commit's critical
path. It \MAY{} run as a background operation or as an explicit
user-invoked compaction. Pre-GC and post-GC bundles \MUST{}
contain identical canonical state and identical retained-rollback
reachability.
\end{requirement}
\section{Forward Compatibility and Edit Barriers}
\label{sec:format:fwdcompat}
The bundle must round-trip cleanly across implementations that may
not all understand every extension referenced in it.
\subsection{Extension Declarations}
\begin{lstlisting}[language=Rust]
pub struct ExtensionDeclaration {
pub extension_id: ExtensionId,
pub version: SemVer,
/// If true, an implementation that does not understand this
/// extension MUST refuse to open the bundle for editing, but
/// MAY open read-only.
pub required: bool,
/// Root chunks owned by this extension. Opaque to core
/// readers; preserved across reads and writes.
pub preserved_chunk_roots: Vec<ChunkRef>,
/// Object kinds this extension contributes to or depends on,
/// for edit-barrier evaluation.
pub affected_object_kinds: Vec<ObjectKind>,
/// Edit barriers declared by this extension.
pub edit_barriers: Vec<EditBarrier>,
}
pub struct EditBarrier {
/// Scope of the barrier: whole score, region, staff instance,
/// analysis layer, object set, pitch space, tuning context, etc.
pub scope: BarrierScope,
/// Object kinds protected by this barrier.
pub affected_object_kinds: Vec<ObjectKind>,
/// Operation kind tags prohibited by this barrier. A barrier
/// prohibits an operation class, not one exact payload, so it
/// references OperationKindTag rather than OperationKind.
pub prohibited_operation_kinds: Vec<OperationKindTag>,
/// Additional condition narrowing the barrier (e.g., applies
/// only when a specific attachment is present).
pub condition: BarrierCondition,
}
/// A discriminator-only view of OperationKind (Chapter ch:semops),
/// without payload. Used by edit barriers, conformance reports,
/// and diagnostic surfaces where the operation's class matters
/// but its payload does not. There is a canonical mapping from
/// OperationKind to OperationKindTag.
pub enum OperationKindTag {
InsertEvent,
DeleteEvent,
ModifyEvent,
RespellPitch,
Transpose,
CreateCrossCutting,
DeleteCrossCutting,
ModifyCrossCutting,
ChangeRegionTimeModel,
InsertRegion,
DeleteRegion,
InsertStaffInstance,
DeleteStaffInstance,
SetUserSystemBreak,
SetUserPageBreak,
DeclareTransaction,
Registered(OperationKindRegistryId),
// Appended after Registered (append-only vocabulary; the
// tag's wire discriminants never reorder). Phase-2/Phase-3
// operation-catalog growth:
InsertIdentifiedPitch,
DeleteIdentifiedPitch,
ModifyIdentifiedPitch,
CreateVoice,
DeleteVoice,
SetMetadata,
SetMetricGrid,
InsertStaff,
SetTimeSignature,
SetTempoSegment,
SetStaffLayout,
// Schema-major-2 revision (repeat authoring). The pair keeps
// its kind names verbatim, as the cross-cutting tags do.
CreateRepeatStructure,
DeleteRepeatStructure,
// Push 4a: the faithful transpose. Transpose above is frozen.
TransposeInterval,
// Genesis tranche G1: name-verbatim, as the two most recent prior
// additions are (the tag layer's older Create-to-Insert convention is
// not followed here).
CreateInstrument,
// Genesis tranche G2a: the two major-0 settings setters.
SetCanvasLayoutDefaults,
SetSpellingPrecedence,
// Genesis tranche G2b: name-verbatim, as the two prior additions are.
SetTuningContext,
// Genesis tranche G3a: the four remaining root-level entity mints,
// name-verbatim, as the three most recent prior additions are.
CreateStaffGroup,
CreatePartDefinition,
CreateAnalysisLayer,
CreateView,
// Genesis tranche G3b: the final rung, name-verbatim, as the four most
// recent prior additions are. Closes the genesis ladder.
CreateMeasure,
}
pub enum BarrierScope {
WholeScore,
Region(RegionId),
StaffInstance(StaffInstanceId),
AnalysisLayer(AnalysisLayerId),
ObjectSet(Vec<TypedObjectId>),
PitchSpace(PitchSpaceId),
TuningContext,
/// Registered scope kind for grammar-specific scoping.
Registered(BarrierScopeRegistryId),
}
/// Additional narrowing condition for an edit barrier. The barrier
/// applies only when its scope, affected object kinds, prohibited
/// operation kinds, and this condition all match the candidate edit.
pub enum BarrierCondition {
/// The barrier applies unconditionally within its scope.
Always,
/// The barrier applies only while the named object exists
/// (not tombstoned). Used when an extension's invariant
/// depends on a specific object remaining live.
ObjectExists(TypedObjectId),
/// The barrier applies only when the named object carries
/// data declared by the named extension. Used when the
/// invariant protected by the barrier exists only for
/// objects participating in the extension.
ObjectHasExtensionData {
object: TypedObjectId,
extension: ExtensionId,
},
/// Conjunction: all conditions must match.
All(Vec<BarrierCondition>),
/// Disjunction: any condition matching is sufficient.
Any(Vec<BarrierCondition>),
/// Negation: the inner condition must not match.
Not(Box<BarrierCondition>),
/// Registered condition kind, evaluated by an extension-aware
/// implementation. Core implementations evaluate Registered
/// conditions as Always (conservative): they treat the barrier
/// as active whenever an unknown extension's condition would
/// otherwise narrow it.
Registered(BarrierConditionRegistryId),
}
\end{lstlisting}
\subsection{Behavior Under Unknown Extensions}
\begin{requirement}
\label{req:format:unknown-extension-preservation}
When an implementation opens a bundle declaring an extension it
does not understand:
\begin{itemize}
\item If the extension is \texttt{required = true}, the
implementation \MUST{} either refuse to open the bundle or
open it strictly read-only. It \MUSTNOT{} edit.
\item If the extension is \texttt{required = false}, the
implementation \MAY{} open the bundle for editing, but \MUST{}
preserve the extension's \texttt{preserved\_chunk\_roots}
across reads and writes. The chunks are treated as opaque
bytes.
\item Edits \MUST{} be checked against every active edit
barrier. An edit matching a barrier's scope, affected object
kinds, and operation kinds is prohibited unless the user
explicitly performs an \emph{unsafe edit}.
\item An unsafe edit is an explicit user action that
acknowledges loss of extension data. The unsafe-edit
operation \MUST{} tombstone the relevant extension chunks
(so they are no longer preserved) rather than silently
breaking extension invariants.
\end{itemize}
The unsafe-edit mechanism prevents unknown extensions from
rendering a document permanently unedittable while still
preserving extension data by default.
\end{requirement}
\begin{requirement}
\label{req:format:barrier-matching}
\textbf{Barrier matching for target-free and opaque operations
(ratified Pass 12).}
An operation with no graph target (\texttt{SetMetadata},
\texttt{DeclareTransaction}) is matched by \emph{score-wide}
barriers only (\texttt{BarrierScope::WholeScore}); a barrier with
any narrower scope cannot match it, because there is no target to
test the scope against. An opaque \texttt{Registered} operation is
matched \emph{fully conservatively}: since its targets and effects
are unknown to a core implementation, it matches every active
barrier whose remaining predicates do not exclude it.
\end{requirement}
\begin{requirement}
\label{req:format:unsafe-tombstone}
\textbf{Unsafe-edit tombstone semantics (ratified Pass 12).}
Crossing a barrier by an unsafe edit immediately deactivates the
owning extension's \emph{remaining} barriers for the editing
session --- the extension's invariants are already forfeit, so its
other barriers no longer protect anything. The crossing \MUST{} be
durably recorded at the next bundle commit: the pending tombstone
set survives the session that performed the unsafe edit. A
tombstoned extension that was declared \texttt{required = true}
leaves the bundle openable read-only by implementations that
depended on that extension (its invariants can no longer be
trusted); implementations that never understood it proceed under
the unknown-extension rules above. The manifest-side \emph{byte
encoding} of the tombstone record is deferred to the Binary Format
companion (the manifest schema is frozen at major~0, so the record
must ride an append-safe channel); see the companion's open
questions.
\end{requirement}
\section{Text Projection}
\label{sec:format:textproj}
The format admits a deterministic projection to a canonical
s-expression text form. The text projection is normative.
The form itself is supplied by the \emph{Text Projection} companion
(version~0.1.0), which this section delegates to: the syntax, the canonical
layout, the parse rules, and the round-trip conformance requirement are its.
This section states \emph{what} is projected; the companion states \emph{how}.
\begin{requirement}
\label{req:format:textproj}
The text projection \MUST{} preserve, deterministically and
bidirectionally with the binary form:
\begin{itemize}
\item All operation envelopes (with their identities, stamps,
causal contexts, transaction groupings, and payloads).
\item All profile declarations.
\item All extension declarations \emph{in full} --- identity, semantic
version, required flag, affected object kinds, edit barriers, and
preserved chunk roots --- with opaque payloads in a canonical text
encoding.
\item Every \textbf{canonical blob}: a blob referenced by a canonical
operation or by canonical reduced state is a canonical root of this
document (Section~\ref{sec:format:manifest}), so the projection carries
its media type and its uncompressed payload. Blobs referenced only by
acceleration structures are non-canonical and are not projected.
\item The active canonical base snapshot's identity, frontier, and
reduction-algorithm version, \emph{with its payload inline}. The core
specification once permitted the payload to be ``referenced externally'';
that is withdrawn, because a compacted document's base is derivable from
nothing else and an external reference would make the projection lossy
(Text Projection companion,
\texttt{req:textproj:base-snapshot-inline}).
\item All canonical reduced state --- preserved by \emph{determining} it,
never by carrying a second literal copy. Reduced state is a deterministic
function of the operation set and the canonical base, so a projection
carrying both would hold two sources of truth for one fact. See the Text
Projection companion, requirement
\texttt{req:textproj:reduced-state-derived}.
\end{itemize}
The text projection \MUSTNOT{} be required to preserve:
\begin{itemize}
\item Chunk offsets within the file.
\item Compression algorithm choices.
\item Cache chunks (operation indexes, layout caches, integrity
indexes).
\item Garbage bytes from prior commits.
\item Superblock generation numbers, slot assignments, or CRCs.
\end{itemize}
Two binary bundles whose canonical document semantics are
identical \MUST{} project to identical text. Two text projections
that parse to identical canonical document semantics \MUST{}
re-serialize to binary bundles whose canonical document
semantics are identical (though their physical layout, chunking,
and compression \MAY{} differ).
\end{requirement}
\subsection{Use Cases}
The text projection serves three primary use cases:
\begin{description}
\item[Version control.] Text projections diff and merge in
line-based tools. Merge conflicts surface at the operation-
envelope level, which is the meaningful level for collaborative
editing.
\item[Format inspection and debugging.] A canonical text form
enables direct inspection of bundle contents without binary
tooling.
\item[Long-term archival.] Text formats survive transitions
between binary specifications. The text projection's structure
is stable across binary format major versions where the
semantic content is preserved.
\end{description}
For archival to hold, the text must be self-contained. Where a document has been
compacted onto a canonical base and its prior operations pruned, that base is
derivable from nothing else in the document, so the companion carries the
snapshot payload \emph{inline} rather than by reference
(\texttt{req:textproj:base-snapshot-inline}). A reference-only projection of a
compacted document would not determine the document it claims to project.
\section{Schema Versioning}
\label{sec:format:schema}
Chunk payloads are encoded against a schema declared by each
chunk's \texttt{schema\_version} field. Schemas evolve under a
semantic versioning discipline.
\begin{lstlisting}[language=Rust]
pub struct SchemaVersion {
pub major: u16,
pub minor: u16,
}
\end{lstlisting}
\begin{requirement}
\label{req:format:schema-version-compatibility}
Schema versioning rules:
\begin{itemize}
\item These rules govern \emph{schema} versions --- the manifest's
\texttt{manifest\_schema\_version} and each chunk's
\texttt{schema\_version}. The container's
\texttt{format\_major} is a separate axis, and a change to it
now means more than a change of wire layout: it declares a new
container \emph{epoch}, with guarantees that a reader may rely
on and that no later commit can confer on a container created
before them (Section~\ref{sec:format:container-epoch}). A
container format major boundary is therefore not satisfied by
re-encoding bytes; a document produced under an older epoch
acquires the new epoch's guarantees only by being repacked into
a newly created container.
\item Major version changes are non-backward-compatible.
Readers that do not support the chunk's major schema
version \MUST{} handle it according to the chunk's role:
\begin{itemize}
\item \emph{Canonical chunks} (operation envelope blocks,
the canonical base snapshot, the manifest, and blobs
referenced by canonical operations or reduced state)
\MUST{} be either parseable or the bundle \MUST{} be
opened in read-only preservation mode (or refused). A
reader that cannot parse a canonical chunk cannot know
the canonical document.
\item \emph{Non-canonical chunks} (acceleration
snapshots, operation index, layout caches, text
projection, integrity index, extension-defined chunks
declared as preservable opaque content)
\MAY{} be treated as opaque preserved content; the
reader does not need to understand them to open the
bundle, and writes preserve them verbatim.
\end{itemize}
\item Unknown canonical operation payloads (operation kinds
not in the reader's understood catalog) require the
relevant extension support or the reader \MUST{} open the
bundle read-only without materializing the unknown
operations. Materializing an unknown operation \MUSTNOT{}
proceed silently: doing so would fork canonical state
against any reader that does understand the operation.
\item Minor version changes are backward-compatible. A reader
supporting major version $N$ \MUST{} accept any minor version
$N.\textit{m}$ for $m \leq$ the reader's supported minor.
Under the positional, frozen-layout wire form, a minor change
\MUST{} only \emph{append discriminants to the append-safe
vocabularies the Binary Format companion defines} (its open
\texttt{Registered} vocabularies, plus ratified appends to
\texttt{OperationKind}, \texttt{OperationKindTag},
\texttt{OperationPayload}, and the closed value-layer unions), which
leaves the bytes of every prior value untouched; it \MUSTNOT{} add or
reorder a struct field --- adding a field, even an \texttt{Option},
occupies a new positional slot and shifts subsequent bytes, so it is a
\emph{major} change (Binary Format companion,
\sectionsc{What ``Additive'' Means Here}).
\item The manifest's \texttt{manifest\_schema\_version} declares
the schema of the manifest itself. Each chunk's
\texttt{schema\_version} field declares the schema of that
chunk's payload.
\item Schema evolution is governed by the Binary Format
companion specification, which defines the wire encoding
for each schema version.
\end{itemize}
\end{requirement}
Schema major~1 is the first data-model expansion major: it adds
\texttt{Canvas.layout\_defaults}, \texttt{Instrument.range}, and
\texttt{Region.permits\_spanning\_slurs} to the graph, and unifies the
non-canonical resolved-layout length-prefix width. Its wire form and the
byte-for-byte migration from major~0 are defined in the Binary Format
companion. The canonical-base \texttt{MaterializedState} embeds none of these
values, so it is byte-identical across the bump and stays major~0.
\texttt{Region.permits\_spanning\_slurs} also reaches the \emph{canonical}
operation layer, because \texttt{CreateRegion} embeds a full \texttt{Region},
so an operation-envelope block bearing a v1 \texttt{CreateRegion} is major~1.
(As of the genesis operation tranche, \texttt{Canvas.layout\_defaults} and
\texttt{Instrument.range} do too --- \texttt{SetCanvasLayoutDefaults}
(G2a) and \texttt{CreateInstrument} (G1) embed them --- but neither payload
is major~1, and for \emph{different} reasons.
\texttt{SetCanvasLayoutDefaults} carries the leaf
\texttt{CanvasLayoutDefaults}, which has exactly one layout: the versioning
sits in the containing \texttt{Canvas} walk, never in the leaf, so the
payload stamps major~0. \texttt{CreateInstrument} carries a whole
\texttt{Instrument}, which does have distinct major-0, major-1, and major-2
layouts --- so it is not major~1 because it is \emph{always major~2}: the
schema-major-2 appends are mandatory rather than \texttt{Option}-hidden, so
no lower-major layout for that payload exists at any value. See the Binary
Format companion \sectionsc{Schema Major 1} / \sectionsc{Schema Major 2} for
the operation-by-operation accounting.)
Consequently a major-0-only reader opens a major-1 bundle \emph{fully} only
when the bundle carries no v1 \texttt{CreateRegion} operation; otherwise it
reads the major-0 canonical base and manifest but opens read-only (it cannot
replay the v1 canonical operations), per the canonical/non-canonical rules
above. It always discards the higher-major non-canonical caches. A major-1
reader migrates a major-0 acceleration snapshot and a major-0
\texttt{CreateRegion} payload on read (default-filling the new fields).
Schema major~2 is the second data-model expansion major: it fills the
truncated bodies of the cross-cutting structures (\texttt{Slur},
\texttt{Tie}, \texttt{Beam}, \texttt{Spanner}), \texttt{RepeatStructure}
(kind and voltas), \texttt{Staff} and \texttt{StaffLineConfiguration},
\texttt{Instrument}, and \texttt{ScoreMetadata}, per this revision's
Chapter~5 definitions. The same per-payload-type rules apply: the
canonical-base \texttt{MaterializedState} embeds none of these values and
stays major~0; the filled values reach the acceleration snapshot
\emph{and}, through the nine operation payloads that embed them
(\texttt{CreateCrossCutting}, \texttt{ModifyCrossCutting},
\texttt{InsertStaff} and \texttt{InsertStaffInstance} (the Operation
Catalog's \texttt{CreateStaff} / \texttt{CreateStaffInstance}),
\texttt{SetStaffLayout}, \texttt{SetMetadata},
\texttt{CreateRepeatStructure} (the repeat-authoring revision --- it
embeds the filled \texttt{RepeatStructure} unconditionally, so it is
\emph{born at v2} and every block carrying one stamps major~2; its
\texttt{DeleteRepeatStructure} sibling carries a bare identifier, a
major-0 layout, and stamps major~0), \texttt{CreateInstrument} (genesis
tranche G1 --- it embeds the filled \texttt{Instrument} unconditionally, so
it too is \emph{born at v2}), and --- transitively,
because an embedded \texttt{Region} may carry a \texttt{StaffInstance}
whose \texttt{staff\_lines\_override} embeds the filled
\texttt{StaffLineConfiguration} --- \texttt{InsertRegion}
(\texttt{CreateRegion})), the canonical operation layer. That is nine:
\texttt{CreateCrossCutting}, \texttt{ModifyCrossCutting},
\texttt{InsertStaff}, \texttt{InsertStaffInstance}, \texttt{SetStaffLayout},
\texttt{SetMetadata}, \texttt{CreateRepeatStructure}, \texttt{CreateInstrument},
and \texttt{InsertRegion}. A block is
stamped with the \emph{lowest} schema major whose layouts decode its
bytes (the companion's minimal-stamping rule --- deterministic, so
identical content hashes identically): any v2-dependent bytes make it
major~2, and a lower-major-only reader opens such a bundle read-only. The wire form
and the total default-filling v1${\to}$v2 migration are defined in the
Binary Format companion.
\section{Format Profiles}
\label{sec:format:profiles}
A format profile defines a conformance subset that implementations
\MAY{} promise to support. Profiles enable lightweight readers to
declare their capabilities precisely.
\begin{lstlisting}[language=Rust]
pub struct ProfileDeclaration {
pub profile_id: ProfileId,
pub version: SemVer,
/// Constraints imposed by this profile (maximum block size,
/// permitted compression algorithms, required extensions, etc.)
pub constraints: ProfileConstraints,
}
pub enum ProfileId {
/// Full profile: all features supported, no restrictions.
Full,
/// Read-only profile: bundles produced under this profile MAY
/// be opened by readers that cannot edit. Editing implementations
/// SHOULD upgrade the profile on first edit.
ReadOnly,
/// Lite profile: a reduced feature set for embedded or mobile
/// readers. Restricts maximum block sizes, snapshot complexity,
/// and permitted extensions.
Lite,
/// Custom profile defined by a registered profile declaration.
Custom(ProfileRegistryId),
}
\end{lstlisting}
The constraints a profile imposes are carried in
\texttt{ProfileConstraints}, which the GC and retention rules
(Section~\ref{sec:format:gc}) reference for the mandatory
\texttt{RetentionPolicy}:
\begin{lstlisting}[language=Rust]
pub struct ProfileConstraints {
/// Maximum uncompressed operation-envelope block size a reader
/// must accept under this profile (default 64 MiB).
pub max_uncompressed_block_size: u64,
/// The RetentionPolicy this profile declares. Required: the GC
/// and retention rules read it from the active profile.
pub retention_policy: RetentionPolicy,
// Additional constraint fields (permitted compression sets,
// required-extension lists) are reserved for a later revision;
// a reader MUST tolerate their future addition under the
// minor-version compatibility rule.
}
\end{lstlisting}
\begin{requirement}
\label{req:format:profile-constraints}
Every \texttt{ProfileDeclaration} \MUST{} carry a
\texttt{ProfileConstraints}, and every \texttt{ProfileConstraints}
\MUST{} include a \texttt{retention\_policy} field. This satisfies
the requirement (Section~\ref{sec:format:gc}) that the active
conformance profile declare a \texttt{RetentionPolicy}: the policy
lives inside the active profile's constraints, not as a free-standing
manifest field.
When a bundle declares more than one profile, the
\texttt{RetentionPolicy} in force is the one declared by the
\emph{first} profile in the manifest's \texttt{profile\_declarations}
list (declaration order is canonical). A bundle that declares no
profile constraints carrying a policy uses the Full profile's
default retention policy.
\end{requirement}
\begin{requirement}
\label{req:format:profile-support}
Every bundle \MUST{} declare at least one profile in its manifest's
\texttt{profile\_declarations}. An implementation \MAY{} open a
bundle if it supports any of the declared profiles, subject to
the required-extension rules
(Section~\ref{sec:format:fwdcompat}).
\end{requirement}
\begin{requirement}
\label{req:format:profileid-discriminants}
\texttt{ProfileId} encodes as a fixed \emph{20} bytes: a
little-endian \texttt{u32} declaration-order discriminant
(\texttt{Full} $= 0$, \texttt{ReadOnly} $= 1$, \texttt{Lite} $= 2$,
\texttt{Custom} $= 3$) followed by a 16-byte
\texttt{ProfileRegistryId}. The trailing 16 bytes carry the registry
id for \texttt{Custom} and are zero for the other three variants; the
field is always present, so the encoding is fixed-width (the
superblock reserves a fixed slot for it). This assignment is
normative and stable.
\texttt{ProfileId} participates in superblock selection
(Section~\ref{sec:format:bundle}, the load-bearing field set), so
its discriminants are part of that comparison.
\end{requirement}
\section{Compression}
\label{sec:format:compression}
Each chunk \MAY{} be stored compressed. Compression is per-chunk
metadata, not part of content identity.
\begin{requirement}
\label{req:format:zstandard-support}
Conforming implementations \MUST{} support reading chunks
compressed with Zstandard at any level zstd defines. Writers
\MAY{} choose to compress or not on a per-chunk basis;
uncompressed chunks (\texttt{CompressionAlgorithm::None}) are
always permitted.
Implementations \MUSTNOT{} require a specific compression
algorithm for canonical conformance. Future major format
versions \MAY{} introduce additional algorithms via the
\texttt{Reserved} variant; such changes require a new format
major version.
Decompression \MUST{} verify the output length against the
declared \texttt{uncompressed\_length} and reject chunks whose
decompressed size disagrees.
\end{requirement}
\section{Streaming Reads}
\label{sec:format:streaming}
Cold open of a large score \MUST{} be fast: under one second to
first interactive frame on a 100-page orchestral score, per the
performance requirements (Chapter~\ref{ch:perf}). The format's
prelude and chunk-graph design support this.
\subsection{Required Locatability}
\begin{requirement}
\label{req:format:chunk-locatability}
The bundle \MUST{} guarantee that:
\begin{itemize}
\item The fixed header is at offset zero of the file.
\item Both superblock slots are at fixed offsets within the
first 576 bytes.
\item The active manifest is locatable from the active
superblock without scanning.
\item Operation-envelope blocks, the canonical base snapshot
(if any), acceleration snapshots, blobs, and extension
chunks are locatable from the manifest's roots without
scanning.
\item The operation index (if present) provides $O(\log n)$
operation-id lookup; if absent, scanning a small number of
blocks suffices.
\end{itemize}
Locatable means: a reader can determine the offset and length of
the chunk in the bundle using only previously read content.
\end{requirement}
\subsection{Cold Open Procedure}
The recommended cold open sequence:
\begin{enumerate}
\item Read the 64-byte fixed header. Verify magic and CRC.
\item Read both 256-byte superblock slots. Verify CRC on each.
Select the active slot per the superblock selection rule
(Section~\ref{sec:format:bundle}).
\item Read and verify the manifest chunk referenced by the
active superblock. The manifest is stored uncompressed
(Section~\ref{sec:format:manifest-encoding}), so this is a
single read-and-parse.
\item If the manifest's \texttt{canonical\_base} is
\texttt{Some}, read and verify the canonical base snapshot.
The snapshot provides the materialized state at its declared
causal frontier.
\item Stream the operation-envelope blocks listed in
\texttt{operation\_roots}. Apply the canonical reduction
(Chapter~\ref{ch:semops}) over the envelopes whose
\texttt{OperationId} is not covered by the canonical base's
\texttt{covers\_causal\_frontier} (if a base is present), or
over every envelope in the operation set (if no base is
present). The result is the materialized score graph.
\item Invoke the layout pipeline (Chapter~\ref{ch:layout-ir})
over the materialized graph, producing the first system's
layout. Layout caches (if reachable from the manifest as
accelerators) \MAY{} be consulted to skip work; cache misses
fall back to fresh layout computation.
\item Load remaining regions, envelope blocks, snapshot ranges,
and layout caches lazily as the user navigates.
\end{enumerate}
For unsynchronized first-frame display, implementations \MAY{}
present the canonical base's materialized state immediately while
the post-frontier reduction completes in the background; user
edits \MUST{} be deferred until reduction completes, since the
visible state is provisional until then.
\subsection{Memory Mapping}
\begin{requirement}
\label{req:format:safe-memory-mapping}
Implementations \MAY{} memory-map the bundle file for efficient
random access to chunks and blobs. The format's content-addressing
and chunk immutability guarantees make memory mapping safe:
chunks cannot change underneath a reader holding a pointer into
them. Superblock slots are 256-byte aligned and never overlap
reachable chunk content, so writers updating an inactive
superblock cannot corrupt readers accessing the active state.
\end{requirement}
\section{Binary Format Companion}
\label{sec:format:binary}
The byte-level encoding (varint conventions, exact field bit widths,
record alignment, endianness, string encoding details, struct
layouts) is delivered as a companion specification, the Binary
Format document. The Binary Format document is normative; an
implementation cannot conform to the file format specification
without conforming to the Binary Format specification.
\subsection{Ratified Convention Baseline}
\label{sec:format:codec-baseline}
Although the full Binary Format companion is still to be written, the
canonical encoding conventions shared by the core, operations, and
bundle layers are ratified now, so those three layers do not drift
apart before the companion formalizes them. The companion
\emph{inherits} this baseline rather than re-deriving it. Every
ratified discriminant table, derivation preimage, and primitive
encoding is consolidated in
Appendix~\ref{app:bytes} (the Canonical Byte-Layout Reference); the
companion imports that appendix as its starting point rather than
recovering the layouts from the three crates.
\begin{requirement}
\label{req:format:codec-conventions}
The canonical encoding of every composite structure (whole
\texttt{Score}, operation envelopes, manifest) \MUST{} follow these
conventions:
\begin{itemize}
\item Integers are little-endian. A boolean is a single
\texttt{0}/\texttt{1} byte.
\item Counts and the length prefix of every variable-width
\emph{leaf} (an identifier, a \texttt{RationalTime}, a string)
are \texttt{u32} little-endian. Length-prefixing every leaf
makes the decoder width-agnostic: it never infers a boundary
from context.
\item A tagged union is encoded as a single discriminant byte
followed by the variant payload.
\item Free-text string fields are length-prefixed UTF-8 and are
\emph{not} NFC-folded by the codec: the in-memory value is
preserved byte-exact, so \texttt{decode(encode(x)) == x}.
Catalog identifiers are NFC-normalized at construction, not in
the codec.
\end{itemize}
Certain fixed hashing preimages deliberately depart from the
single-discriminant-byte convention where a wider or order-bearing
tag is required --- notably \texttt{TypedObjectId}'s 16-bit
big-endian discriminant
(Requirement~\ref{req:graph:typed-object-id-discriminants}). Such
departures are pinned at their definitions and are not overridden by
this baseline.
\end{requirement}
\begin{requirement}
\label{req:format:rationaltime-encoding}
The primitive scalar layouts referenced throughout the canonical
encodings are ratified as follows:
\begin{itemize}
\item \texttt{RationalTime} encodes as a sign byte
(\texttt{0} = zero, \texttt{1} = positive, \texttt{2} =
negative), then the \texttt{u32}-little-endian-length-prefixed
big-endian magnitude of the numerator, then the
\texttt{u32}-little-endian-length-prefixed big-endian magnitude
of the denominator. The value \MUST{} always be stored reduced
(lowest terms, positive denominator), so the encoding is
canonical: equal rationals encode to equal bytes.
\item Wall-clock integers (\texttt{WallClockTime},
\texttt{WallClockDuration}) are little-endian fixed-width
integers, matching \texttt{QuantizedCoord}.
\end{itemize}
\end{requirement}
\begin{openquestion}
The remainder of the Binary Format companion --- the full composite
struct layouts, schema-version wire evolution, and varint details
beyond the baseline above --- is under development as a separate
specification. Its development follows this chapter and inherits the
ratified baseline (Sections~\ref{req:format:codec-conventions}
and~\ref{req:format:rationaltime-encoding}); the remaining byte-level
decisions depend on the structural decisions specified here.
\end{openquestion}
\section{Forward References}
\begin{itemize}
\item The constraint-solver interface (Chapter~\ref{ch:solver})
operates on the in-memory \texttt{ConstrainedLayoutIR}; layout
caches stored in the bundle are derivative and not part of the
canonical state.
\item The full performance requirements that this format must
satisfy are in Chapter~\ref{ch:perf}.
\item The Binary Format companion specifies byte-level encoding.
\item The Text Projection companion specifies the canonical
s-expression form.
\item The Profile Conformance companion specifies the feature
lists per profile.
\end{itemize}
% ===========================================================================
\chapter{Constraint-Solver Interface}
\label{ch:solver}
This chapter specifies the constraint-solver interface: the
\texttt{ConstrainedLayoutIR} the solver consumes, the
\texttt{ResolvedLayoutIR} it produces, the constraints it must
respect, the determinism contract it must satisfy, the diagnostic
information it must emit, and the conformance discipline by which
implementations are judged.
The chapter deliberately specifies the \emph{interface} and its
contracts rather than a particular algorithm. Implementations are
free to choose among approaches (Cassowary, linear programming
relaxations, custom heuristics, differentiable layout, learned
methods) and to evolve their choices over time. Conformance is
established by hard-constraint validity, deterministic behavior,
diagnostic completeness, and performance and quality on a reference
suite of test scores.
\section{Design Principles}
\label{sec:solver:principles}
\begin{description}
\item[The interface is normative; global optimality is not.] The
solver's input/output contract, its constraint families, its
diagnostic surface, and the discipline by which it is evaluated
are normative. Global aesthetic optimality (proving no better
layout exists) is neither decidable nor practical to verify for
arbitrary scores; it is therefore replaced by reference-suite
thresholds.
\item[Hard constraints are validity; quality metrics are evaluation
axes.] Hard constraints are the conditions under which a layout
is valid at all. Quality metrics are the axes along which valid
layouts are evaluated and compared. The solver \MUSTNOT{}
trade off a hard-constraint violation for a better quality
score: a layout that violates a hard constraint is not a layout,
regardless of its quality vector.
\item[Determinism within an implementation; equivalence across
implementations.] A given implementation at a fixed version
\MUST{} produce identical output for identical input. Different
conforming implementations \MAY{} produce different layouts;
they conform by satisfying hard constraints, meeting diagnostic
contracts, behaving deterministically internally, and passing
reference-suite thresholds. Cross-implementation byte-identity
is explicitly not required.
\item[Pareto frontiers as design target, not conformance
obligation.] Solvers \SHOULD{} seek layouts on or near the
Pareto frontier of the normative quality metrics. They are
\emph{not} required to prove they have done so universally;
conformance is reference-suite-based.
\item[Deterministic budgets, not wall-clock budgets.] Wall-clock
time \MAY{} stop interactive work, but the returned layout's
canonical form depends on deterministic counters
(iterations, constraint evaluations, search nodes), not on
scheduler luck.
\item[Incremental solving as observational equivalence.]
Incremental re-solving \MUST{} be observationally equivalent
to full solving of the declared invalidation scope under the
same profile and budget. Implementations are free to widen
scopes conservatively but \MAY NOT{} return layouts that
diverge from the canonical scoped result.
\item[Structured diagnostics, never silent corruption.] Solver
failures are first-class outputs with diagnostic information
sufficient for the editor to surface meaningful feedback. The
score graph is never invalidated by a solver failure; a solver
error means ``we could not produce a layout,'' not ``the
score is broken.''
\end{description}
\section{The Solver Interface}
\label{sec:solver:interface}
\begin{lstlisting}[language=Rust]
pub trait ConstraintSolver: Send + Sync {
/// The solver's identifying tier. Used by readers/editors to
/// decide whether this solver is sufficient for the active
/// score's declared solver tier requirement.
fn tier(&self) -> SolverTier;
/// The solver's implementation version. Identical input plus
/// identical version produces identical output (within-
/// implementation determinism).
fn version(&self) -> SolverVersion;
/// Solve from scratch.
fn solve(
&self,
input: &ConstrainedLayoutIR,
config: &SolverConfig,
) -> SolveReport;
/// Solve incrementally over the declared invalidation scope.
fn solve_incremental(
&self,
input: &ConstrainedLayoutIR,
prior: &SolverState,
invalidations: &InvalidationSet,
config: &SolverConfig,
) -> SolveReport;
}
pub struct SolverConfig {
/// Conformance profile under which to solve. Selects metric
/// thresholds, profile-specific constraints, and the active
/// extension set.
pub profile: SolverProfile,
/// Deterministic budget. Wall-clock time is advisory only.
pub budget: SolverBudget,
/// Tie-breaking weights for selecting among layouts of
/// equivalent quality.
pub tie_breaking: TieBreakingWeights,
}
pub struct SolverBudget {
/// Maximum solver iterations (algorithm-defined unit; the
/// reference algorithm counts Cassowary pivots).
pub max_iterations: u64,
/// Maximum search-tree nodes explored, for algorithms that
/// perform discrete search.
pub max_nodes: u64,
/// Maximum constraint-evaluation count.
pub max_constraint_evaluations: u64,
/// Advisory wall-clock cap, in milliseconds. The solver MAY
/// stop early when this is exceeded, but the layout it
/// returns under stoppage MUST be the same layout that the
/// deterministic budget would have produced at the
/// corresponding deterministic counter state. Wall-clock time
/// MUST NOT change the canonical layout.
pub advisory_wall_time_ms: Option<u64>,
}
pub struct SolverBudgetUsed {
pub iterations: u64,
pub nodes: u64,
pub constraint_evaluations: u64,
pub wall_time_ms: u64,
}
\end{lstlisting}
\begin{requirement}
\label{req:solver:solver-purity}
\texttt{solve} and \texttt{solve\_incremental} \MUST{} be pure
functions of their inputs within the determinism contract
(Section~\ref{sec:solver:determinism}). They \MUSTNOT{} consult
external state (system clock, environment, thread scheduling,
random sources) in ways that influence the returned layout.
Implementations \MAY{} use multi-threaded execution internally,
but the result \MUST{} be identical to a single-threaded
execution under the same deterministic budget.
\end{requirement}
\section{The Solver Report}
\label{sec:solver:report}
Every solver invocation returns a \texttt{SolveReport}. There is no
separate ``error'' return; failures and partial successes appear
as report variants distinguished by \texttt{SolveStatus}.
\begin{lstlisting}[language=Rust]
pub struct SolveReport {
pub status: SolveStatus,
/// Whether every hard constraint is satisfied.
pub satisfied_hard_constraints: bool,
/// The layout produced. Always present; under failure
/// statuses, the layout is diagnostic-only and MUST NOT be
/// used as if it were valid.
pub layout: ResolvedLayoutIR,
/// Updated solver state for subsequent incremental calls.
pub state: SolverState,
/// Unsatisfied hard constraints, if any. Empty under
/// SolveStatus::Solved.
pub unsatisfied_constraints: Vec<ConstraintId>,
/// Non-fatal warnings about the solution.
pub warnings: Vec<SolverWarning>,
/// Quality metric vector for the returned layout.
pub metric_vector: QualityMetricVector,
/// Budget consumed during this solve.
pub budget_used: SolverBudgetUsed,
}
pub enum SolveStatus {
/// All hard constraints satisfied, target quality reached.
Solved,
/// All hard constraints satisfied, but warnings were
/// generated (e.g., a soft constraint had a large violation,
/// or a layout decision was unusual and worth surfacing).
SolvedWithWarnings,
/// Deterministic budget exhausted before reaching target
/// quality. The returned layout MUST still satisfy all hard
/// constraints; it simply may not be optimal. If hard
/// constraints could not be satisfied within budget, the
/// status is Unsatisfiable, not PartialBudgetExhausted.
PartialBudgetExhausted,
/// Hard constraints cannot be simultaneously satisfied. The
/// returned layout is diagnostic-only and MUST NOT be
/// presented as a valid layout.
Unsatisfiable,
/// Solver bug or unexpected error. The returned layout is
/// diagnostic-only. Editors SHOULD surface this as a defect
/// to report.
InternalError,
}
pub struct SolverWarning {
pub kind: SolverWarningKind,
pub affected_objects: Vec<TypedObjectId>,
pub message: String,
}
pub enum SolverWarningKind {
LargeSoftConstraintViolation { constraint: ConstraintId, magnitude: f64 },
UnusualLayoutDecision(String),
QualityFloorApproached { metric: QualityMetricKind },
ExtensionWarning(ExtensionWarningId),
}
\end{lstlisting}
\begin{requirement}
\label{req:solver:report-authority}
The \texttt{SolveReport.layout} field is always populated, but
its authority depends on \texttt{status}:
\begin{itemize}
\item \texttt{Solved} or \texttt{SolvedWithWarnings}: the
layout is a valid layout that the editor may render.
\item \texttt{PartialBudgetExhausted}: the layout is a valid
layout (\texttt{satisfied\_hard\_constraints} is \texttt{true})
and the editor may render it, while noting that quality
is below the profile's target.
\item \texttt{Unsatisfiable} or \texttt{InternalError}: the
layout is diagnostic-only. Editors \MAY{} render it for the
user to see what failed, but \MUST{} clearly indicate the
layout is not authoritative.
\end{itemize}
A solver \MUSTNOT{} return \texttt{PartialBudgetExhausted} with
\texttt{satisfied\_hard\_constraints == false}. If hard constraints
cannot be satisfied within the budget, the correct status is
\texttt{Unsatisfiable}, with the partial layout marked as such.
\end{requirement}
\begin{requirement}
\label{req:solver:subconformant-report}
\textbf{Sub-conformant report shape (ratified Pass 12).}
A below-conformance passthrough solver (\texttt{SolverTier::Stub})
presented with declared constraints it does not evaluate reports
\texttt{SolvedWithWarnings} with
\texttt{satisfied\_hard\_constraints == false} and a warning
naming the unevaluated-constraint condition. The layout stays
renderable; the report makes no conformance claim and satisfies
no minimum-tier requirement. This is the one sanctioned case in
which a renderable status carries
\texttt{satisfied\_hard\_constraints == false}: the field reports
\emph{evaluated} satisfaction, and a non-evaluating solver has
nothing to claim.
\end{requirement}
\section{Constraint Families}
\label{sec:solver:families}
The solver consumes constraints in normalized form. The constraint
families enumerated here are the normative core; implementations
\MAY{} support additional families via
\texttt{LayoutConstraint::Registered}.
\subsection{Strength Levels}
\begin{lstlisting}[language=Rust]
pub enum ConstraintStrength {
/// Hard constraint. The solver MUST satisfy this constraint,
/// or return Unsatisfiable.
Required,
/// Soft constraint with an associated weight. The solver
/// minimizes the weighted violation when optimizing.
Preferred { weight: f64 },
}
\end{lstlisting}
\begin{requirement}
\label{req:solver:required-constraint-strength}
A solver \MUSTNOT{} treat a \texttt{Required} constraint as
if it were \texttt{Preferred} for any reason, including for
quality optimization. A layout that violates a \texttt{Required}
constraint is not a valid layout, regardless of its quality
vector.
\end{requirement}
\begin{requirement}
\label{req:solver:kind-strength}
\textbf{Strength is kind-determined (ratified Pass 12).}
A constraint instance carries no strength field; its strength is
determined by its kind. Break constraints take their strength
from \texttt{BreakKind}: \texttt{Hard} $\to$ \texttt{Required},
\texttt{Soft} $\to$ \texttt{Preferred} with weight $1.0$. Every
other core constraint family normalizes to \texttt{Required}.
\texttt{Registered} constraint kinds normalize conservatively to
\texttt{Required}. A future constraint family declares its
strength (and weight, if \texttt{Preferred}) in its normative
definition --- the strength channel is the \emph{kind}, not the
instance.
\end{requirement}
\subsection{Normative Constraint Families}
The constraint catalog (spring, collision, alignment, containment,
break, aesthetic, extension) is specified in
Chapter~\ref{ch:layout-ir}. Implementations \MUST{} support every
family normatively required by the active solver tier
(Section~\ref{sec:solver:tiers}).
\section{Quality Metrics}
\label{sec:solver:quality}
The solver's output is evaluated against a normative set of quality
metrics. Each metric is normalized to the closed interval
$[0.0, 1.0]$, with $0.0$ representing the best achievable value and
$1.0$ representing the worst tolerable value before a profile
threshold flags the metric as failing.
\begin{lstlisting}[language=Rust]
/// A quality metric value normalized to [0.0, 1.0].
/// Lower is always better.
pub struct NormalizedMetric(pub f64);
impl NormalizedMetric {
/// Construct, panicking if the value is not finite or is out
/// of range. Conforming implementations MUST construct only
/// valid NormalizedMetric values.
pub fn new(value: f64) -> Self {
assert!(value.is_finite());
assert!(0.0 <= value && value <= 1.0);
NormalizedMetric(value)
}
}
pub struct QualityMetricVector {
pub collision_penalty: NormalizedMetric,
pub spacing_distortion: NormalizedMetric,
pub slur_shape_penalty: NormalizedMetric,
pub beam_slope_penalty: NormalizedMetric,
pub vertical_density_penalty: NormalizedMetric,
pub system_break_penalty: NormalizedMetric,
pub page_fill_efficiency: NormalizedMetric,
pub casting_off_quality: NormalizedMetric,
pub symbol_density_uniformity: NormalizedMetric,
/// Additional metrics contributed by extensions. Each MUST be
/// a valid NormalizedMetric (finite, in [0.0, 1.0], lower is
/// better).
pub extension_metrics: Vec<ExtensionMetric>,
}
pub struct ExtensionMetric {
pub metric_id: ExtensionMetricId,
pub value: NormalizedMetric,
}
\end{lstlisting}
\begin{requirement}
\label{req:solver:normalized-metric-bounds}
Every \texttt{NormalizedMetric} value \MUST{} satisfy:
\begin{itemize}
\item It is finite (not NaN, not infinity).
\item It lies in the closed interval $[0.0, 1.0]$.
\item Lower values denote better layouts; higher values denote
worse layouts.
\end{itemize}
Per-metric normalization functions (mapping raw measurements to
$[0.0, 1.0]$) are specified in the Quality Metric Catalog
companion document. Implementations \MUST{} use the catalog's
normalization; arbitrary normalization is non-conforming.
Extension metrics \MAY{} define their own normalization function
but \MUSTNOT{} change orientation or range: $0.0$ remains best,
$1.0$ remains worst, and values must remain finite within range.
\end{requirement}
\subsection{Pareto Frontiers as Design Target}
Solvers should seek layouts on or near the Pareto frontier of the
normative metrics. This is the design intent. It is not the
conformance requirement.
\begin{requirement}
\label{req:solver:pareto-efficiency}
The normative metric set defines the axes along which layout
quality is evaluated. Solvers \SHOULD{} produce Pareto-efficient
layouts with respect to these metrics: layouts for which no
metric can be improved without worsening another. Because global
Pareto optimality is not practically verifiable for arbitrary
scores, conformance is determined by reference-suite thresholds
(Section~\ref{sec:solver:conformance}) rather than by proof of
Pareto-optimality.
When multiple Pareto-equivalent layouts exist, the configured
\texttt{TieBreakingWeights} select among them. Tie-breaking is
deterministic.
\end{requirement}
\begin{lstlisting}[language=Rust]
pub struct TieBreakingWeights {
pub collision: f64,
pub spacing: f64,
pub slur_shape: f64,
pub beam_slope: f64,
pub vertical_density: f64,
pub system_break: f64,
pub page_fill: f64,
pub casting_off: f64,
pub symbol_density: f64,
}
\end{lstlisting}
\begin{requirement}
\label{req:solver:tie-breaking-weight-defaults}
Tie-breaking weights \MUST{} have normative defaults specified
in the Quality Metric Catalog. Implementations \MAY{} permit
users to customize these weights; the defaults represent the
reference engraving aesthetic and are the basis for
reference-suite conformance.
\end{requirement}
\section{Conformance Tiers}
\label{sec:solver:tiers}
Solver conformance is graded into tiers, each declaring a subset
of features the implementation supports. Tiers are \emph{orthogonal}
to file-format profiles (Chapter~\ref{ch:format}); a document may
declare both an authoring file profile and a minimum solver tier,
and the two axes are independent.
\begin{lstlisting}[language=Rust]
pub enum SolverTier {
/// Not a conformance tier: an interface-only / passthrough
/// solver that evaluates no constraints and computes no
/// quality metrics. Ordered below every conformant tier.
Stub,
/// Minimal Layout Solver.
Minimal,
/// Standard Engraving Solver.
Standard,
/// Advanced / Extension-Aware Solver.
Advanced,
}
\end{lstlisting}
The \texttt{Stub} variant exists so scaffolding, harnesses, and
not-yet-conformant pipelines can label themselves honestly: it makes
no conformance claim, and a document requiring any conformant tier
is never satisfied by a solver declaring \texttt{Stub}.
\subsection{Minimal Layout Solver}
\begin{requirement}
\label{req:solver:minimal-tier}
A solver claiming the Minimal tier \MUST{}:
\begin{itemize}
\item Satisfy every hard constraint or return
\texttt{Unsatisfiable} (validity conformance).
\item Be deterministic within its declared implementation
version (Section~\ref{sec:solver:determinism}).
\item Produce well-formed \texttt{SolveReport} values with
accurate \texttt{satisfied\_hard\_constraints},
\texttt{unsatisfied\_constraints}, and metric vectors
(diagnostic conformance).
\item Support common-music-notation regions, metric time, and
the standard constraint families.
\item Pass the Minimal subset of the reference suite.
\end{itemize}
A Minimal solver \MAY{} produce mediocre aesthetic quality;
metric thresholds for the Minimal tier are relaxed relative to
the Standard tier.
\end{requirement}
\subsection{Standard Engraving Solver}
\begin{requirement}
\label{req:solver:standard-tier}
A solver claiming the Standard tier \MUST{}:
\begin{itemize}
\item Satisfy every Minimal-tier obligation.
\item Meet the Standard-tier metric thresholds on the
reference suite for every quality metric.
\item Support the incremental solving contract with at least
Measure-local invalidation scope
(Section~\ref{sec:solver:incremental}).
\item Support every Standard-tier constraint family declared
in the Quality Metric Catalog.
\end{itemize}
The Standard tier corresponds to ``professional engraving
quality'' for common-practice notation.
\end{requirement}
\subsection{Advanced / Extension-Aware Solver}
\begin{requirement}
\label{req:solver:advanced-tier}
A solver claiming the Advanced tier \MUST{}:
\begin{itemize}
\item Satisfy every Standard-tier obligation.
\item Support proportional and aleatoric region layout, not
only metric regions.
\item Support microtonal accidentals and their interactions
with adjacent glyphs.
\item Support extension-contributed constraint families and
extension metric thresholds for each registered extension
whose layout requirements are part of the Advanced
reference suite.
\item Support at least System-local invalidation scope
under incremental solving.
\end{itemize}
\end{requirement}
\subsection{Tier Declaration and Document Compatibility}
\begin{requirement}
\label{req:solver:solver-tier-compatibility}
Documents \MAY{} declare a minimum solver tier required for
faithful engraving in their profile declarations
(Chapter~\ref{ch:format}). A solver with tier lower than the
document's declared minimum \MAY{} open the document but
\SHOULD{} surface a clear warning that engraving quality may
not match the document's intent.
Solver tier is independent of file-format profile. A document
authored under the \texttt{Lite} file profile \MAY{} declare a
minimum solver tier of \texttt{Standard}; a document authored
under the \texttt{Full} file profile \MAY{} declare no minimum
tier and be displayable by a \texttt{Minimal} solver.
\end{requirement}
\section{Determinism}
\label{sec:solver:determinism}
The solver's determinism contract has two parts. \emph{Within-
implementation determinism} is a strict byte-equality obligation;
\emph{cross-implementation conformance} is reference-suite-based.
\subsection{Within-Implementation Determinism}
\begin{requirement}
\label{req:solver:within-implementation-byte-stability}
A solver implementation at a fixed version \MUST{} produce
byte-for-byte identical \texttt{SolveReport} output for
identical inputs, where ``identical inputs'' means:
\begin{itemize}
\item Identical \texttt{ConstrainedLayoutIR}.
\item Identical \texttt{SolverConfig} (profile, budget,
tie-breaking weights).
\item Identical font and glyph metrics (referenced by version
and content hash).
\item Identical reduction algorithm version
(Chapter~\ref{ch:semops}).
\item For incremental calls: identical prior
\texttt{SolverState} and identical invalidation set.
\item Identical solver implementation version (i.e., the same
binary).
\end{itemize}
Determinism \MUST{} hold across runs on the same machine, across
cold and warm starts, and across hardware threads.
Implementations \MUSTNOT{} use fast-math flags, processor-
dependent SIMD without canonical-output guarantees, or any
compiler options that vary floating-point results across builds
on the same platform.
Where parallelism is used internally, the solver \MUST{}
serialize the merge step so that thread scheduling does not
affect output. Deterministic budgets \MUST{} be enforced from
shared counters that capture all parallel contributions.
\end{requirement}
\subsection{Cross-Implementation Conformance}
\begin{requirement}
\label{req:solver:cross-implementation-conformance}
Cross-implementation determinism (byte-equality of outputs
across different conforming solvers) is \emph{not} required.
Different conforming solvers \MAY{} produce different layouts
for the same input. Conformance across implementations is
established by:
\begin{enumerate}
\item All hard constraints satisfied on every reference suite
score.
\item The implementation is internally deterministic per the
within-implementation rule above.
\item \texttt{SolveReport} values are well-formed and
diagnostically accurate.
\item For each reference-suite score, every quality metric is
within the tier's declared threshold.
\end{enumerate}
\end{requirement}
\begin{rationale}
Cross-implementation byte equality would freeze the ecosystem
to whatever single algorithm first claimed conformance, and
would preclude algorithmic innovation. Reference-suite
thresholds give users a meaningful quality floor while
permitting genuine implementation diversity. Within-
implementation determinism remains essential for reproducible
builds, regression testing, and predictable behavior under
collaborative editing.
\end{rationale}
\section{Incremental Solving}
\label{sec:solver:incremental}
\subsection{Invalidation Scopes}
\begin{lstlisting}[language=Rust]
pub enum InvalidationScope {
/// A single object's layout depends only on local context.
ObjectLocal,
/// Effects bounded within a single measure.
MeasureLocal,
/// Effects bounded within a single system.
SystemLocal,
/// Effects bounded within a single page.
PageLocal,
/// Effects bounded within a single region.
RegionLocal,
/// Effects may propagate across the entire score.
WholeScore,
}
pub struct InvalidationSet {
/// Declared scope. The solver MAY widen this scope
/// conservatively; it MUST NOT narrow it.
pub scope: InvalidationScope,
/// Specific invalidated entries within the declared scope.
pub slots: Vec<SpringSlotId>,
pub bands: Vec<VerticalBandId>,
pub constraints: Vec<ConstraintId>,
pub glyphs: Vec<GlyphObjectId>,
}
\end{lstlisting}
\subsection{Observational Equivalence}
\begin{requirement}
\label{req:solver:incremental-observational-equivalence}
For any invalidation set with declared scope $S$,
\texttt{solve\_incremental} \MUST{} produce a layout
observationally equivalent to \texttt{solve} called on the
same input restricted to $S$, under the same profile, the same
configuration, and the same deterministic budget. ``Observational
equivalence'' means byte-identical \texttt{ResolvedLayoutIR}
output for objects within $S$, and unchanged output for objects
outside $S$.
The solver \MAY{} widen the scope conservatively: an
\texttt{ObjectLocal} invalidation may be solved as
\texttt{MeasureLocal} if that simplifies the implementation,
provided the widened result is also observationally equivalent
to full solving at the widened scope.
The solver \MAY NOT{} narrow the scope. If
\texttt{SystemLocal} effects might propagate (e.g., an edit at
the system's end shifts subsequent systems' contents), the
invalidation \MUST{} be declared at least
\texttt{SystemLocal} or higher.
\end{requirement}
\subsection{Propagation Documentation}
\begin{requirement}
\label{req:solver:propagation-scope-contract}
Implementations \MUST{} document, per their published
conformance contract, the maximum invalidation scope they can
service for each constraint family. The documented bound is the
implementation's contract for how far edit effects propagate
before requiring whole-score re-solving.
\end{requirement}
\section{Conformance: The Reference Suite}
\label{sec:solver:conformance}
Solver conformance is established by performance on a normative
reference suite of test scores. Each tier has its own subset of
the suite and its own metric thresholds.
\begin{requirement}
\label{req:solver:reference-suite-passage}
The Reference Suite is delivered as a companion specification.
Each suite entry consists of:
\begin{itemize}
\item A test score in canonical \texttt{.musc} form.
\item Per-tier inclusion: whether the entry is required for
Minimal, Standard, or Advanced conformance.
\item Per-tier metric thresholds: the maximum permitted
\texttt{NormalizedMetric} value for each quality metric.
\item Optional fixed-expectation tests: specific layout
properties (a particular bar's width, a specific system
break location) that all conforming solvers \MUST{}
reproduce. Used sparingly, only where the suite intends to
pin an unambiguous expectation.
\end{itemize}
A solver claiming a given tier \MUST{} pass every entry
required at that tier. Failure on any single entry is
conformance failure at the claimed tier.
Suite versions are tied to specification versions; conforming
implementations \MUST{} declare which suite version they pass.
\end{requirement}
\begin{rationale}
The reference suite makes conformance checkable. The earlier
universal-Pareto formulation quantified over all possible
layouts of every possible score, which is undecidable.
Reference-suite thresholds replace ``no better layout exists''
with ``this layout is within tolerance on these specific
scores,'' which is testable, falsifiable, and operationally
useful. The cost is that conformance is only as good as the
suite's coverage; that is acceptable, because suite coverage is
visible, evolvable, and can be expanded as the spec matures.
\end{rationale}
\section{Reference Algorithm}
\label{sec:solver:reference-algo}
The reference algorithm is explanatory and diagnostic. Its purpose
is to disambiguate constraint interactions where prose alone might
leave a choice open, and to generate the expected outputs published
with the reference suite. It is \emph{not} the normative algorithm
and \emph{not} the aesthetic floor.
\begin{requirement}
\label{req:solver:algorithm-independence}
Conforming implementations are \emph{not} required to use the
reference algorithm or to reproduce its layouts, except where
a reference-suite entry explicitly fixes an expected behavior
via a fixed-expectation test. Implementations \MAY{} choose
any algorithm satisfying the contract: hard constraints,
determinism, diagnostics, reference-suite thresholds.
\end{requirement}
The reference algorithm is described in a non-normative companion
document. A natural starting point uses the Cassowary linear-
arithmetic constraint solver for the linear layer (positions,
alignments, containment) together with a separate numerical
optimization layer for aesthetic constraints (beam slopes, slur
curvatures) that are nonlinear in nature. Hybrid approaches and
alternative architectures (differentiable layout, constraint
programming, learned methods) are all conforming choices.
\section{Forward References}
\begin{itemize}
\item End-to-end performance requirements, including frame budgets
that the solver participates in, are in Chapter~\ref{ch:perf}.
Solver performance is measured in the context of the full
layout pipeline.
\item The Quality Metric Catalog companion document specifies
the exact normalization functions for each metric, the default
tie-breaking weights, and the per-tier thresholds.
\item The Reference Suite companion document specifies the
test scores, per-tier inclusion, and per-tier metric
thresholds.
\item The reference-algorithm companion document is non-normative
and describes one starting-point implementation.
\end{itemize}
% ===========================================================================
\chapter{Performance Requirements}
\label{ch:perf}
This chapter specifies performance contracts for the \emph{core
layer}. Targets are normative; they bound what conforming
implementations promise and what tests measure for conformance.
The chapter restates targets introduced informally in earlier
chapters and adds the measurement methodology.
\section{Core/Product Boundary}
\label{sec:perf:boundary}
Chapter~\ref{ch:intro} excluded the editor UI, the audio engine,
the renderer beyond the layout IR, and platform integrations from
the core's scope. Many performance properties of a working
notation system depend on those out-of-scope components: first
interactive frame after launch involves UI bootstrapping, edit
response involves UI event handling, scrolling and selection
involve a real renderer, audio acquisition involves a working
audio engine. The core cannot promise end-to-end timings for
behavior it does not own.
This chapter therefore distinguishes three obligation classes:
\begin{description}
\item[Core obligations.] The core data structures, layout IR,
solver, and file format \MUST{} admit the stated bounds. The
core's own work (reduction, layout, file writes, snapshot
acquisition) \MUST{} fit within the budgets allocated to it
in the end-to-end frame budget. The core \MUST{} expose APIs
suitable for each downstream component to do its own work
inside its remaining budget (e.g., a lock-free immutable
snapshot API for audio engines, an incremental invalidation
interface for renderers, a cancellable solve API for UIs).
\item[Product obligations.] End-to-end UI, audio, and rendering
targets (first interactive frame after launch, end-to-end
edit-to-pixel latency, end-to-end scroll smoothness,
audio-thread snapshot acquisition latency under load) are the
obligation of the product layers built atop the core. Those
targets are specified in product-specific companion documents,
not here.
\item[Performance Reference Suite obligations.] The Performance
Reference Suite (Appendix~\ref{app:deferred}) measures core
behavior only: layout time per system, file write/read time,
reduction throughput, solver budget consumption, snapshot
acquisition latency. End-to-end UI traces are out of scope
for the core's reference suite.
\end{description}
\begin{requirement}
\label{req:perf:core-product-budget-boundary}
Where this chapter states a performance target, the target is a
\emph{core obligation} unless explicitly labeled product
obligation. Core obligations \MUST{} be satisfiable by a
conforming core implementation independent of the product
layers built atop it; product-layer code is permitted to
consume budget on top of the core's portion, but the core's
own portion \MUST{} fit within the stated bound.
\end{requirement}
\section{Design Principles}
\label{sec:perf:principles}
\begin{description}
\item[Targets are part of the contract.] Core performance
requirements are not aspirations. A conforming core
implementation \MUST{} meet the stated targets on the
reference hardware profile under the stated measurement
conditions.
\item[Frame budgets dominate.] User experience is ultimately
governed by frame timing. The core's portion of each frame
budget is stated explicitly; the product layer's portion is
specified separately.
\item[Reference hardware profile.] All targets are stated
against a specified hardware profile. Implementations \MAY{}
document additional profiles (lower-end mobile, higher-end
workstation); the reference profile is the basis of
conformance.
\item[Measurement methodology is normative.] Without specified
measurement methodology, performance targets are
unenforceable. This chapter specifies how each target is
measured.
\item[Tail latency matters.] Average performance is
insufficient; targets are stated as percentiles (typically
p99 or worst-case) to reflect what users actually experience.
\end{description}
\section{Reference Hardware Profile}
\label{sec:perf:hardware}
The reference hardware profile defines the platform against which
performance conformance is measured. Implementations \MAY{} target
additional profiles; this profile is the basis of conformance.
\begin{lstlisting}
Reference Hardware Profile (2026 baseline):
Desktop reference:
- Apple silicon (M-series) or x86-64 equivalent
- 8 performance cores (or equivalent throughput)
- 16 GB RAM
- NVMe SSD storage
- Discrete or integrated GPU with Metal/Vulkan support
- Display: 1440p at 60 Hz minimum
Tablet reference:
- iPad Pro M-series or equivalent
- 8 GB RAM minimum
- SSD storage
- Display: 120 Hz Pro Motion or equivalent
\end{lstlisting}
\begin{requirement}
\label{req:perf:reference-hardware}
Conforming implementations \MUST{} meet the targets in this chapter
on the reference hardware profile. Implementations \MAY{} miss
targets on hardware below the reference profile (e.g., older
devices, low-end embedded targets); such gaps \MUST{} be documented
in the implementation's published performance contract.
The reference profile is updated periodically as commodity hardware
evolves. The version of the reference profile against which an
implementation conforms \MUST{} be declared.
\end{requirement}
\section{Frame Budgets}
\label{sec:perf:frame}
\subsection{Interactive Edit Latency}
\begin{requirement}
\label{req:perf:single-system-edit-latency}
The \emph{core's portion} of a single-system edit (operation
envelope construction, reduction, incremental layout through
\texttt{ResolvedLayoutIR}) \MUST{} complete within one frame
(16.7\,ms at 60\,Hz) at the p99 percentile, measured on the
reference hardware profile. This includes the case where the
affected system is on a 100-page orchestral score: the core's
incremental layout machinery (Chapter~\ref{ch:layout-ir}) and
the constraint solver's incremental contract
(Chapter~\ref{ch:solver}) jointly satisfy this requirement.
On 120\,Hz displays (tablet reference), the core's portion
\SHOULD{} complete within 8.3\,ms at p99. This is not required
for conformance but is the target for first-class tablet
performance.
End-to-end edit-to-pixel latency (input handling, hit testing,
render submission, display flip) is a product-layer obligation.
The core's contribution is bounded by this target so that the
product layer has the remaining frame budget to do its work.
\end{requirement}
\subsection{Multi-System Edits}
\begin{requirement}
\label{req:perf:multi-system-edit-latency}
Edits affecting up to ten systems (e.g., a transposition over
a substantial passage) \MUST{} complete within four frames
(66.7\,ms at 60\,Hz) at p99. The four-frame budget gives the
editor headroom to display a transient ``thinking'' indication
without violating perception of responsiveness.
\end{requirement}
\subsection{Full Re-Layout}
\begin{requirement}
\label{req:perf:full-relayout-latency}
Full re-layout of a 100-page orchestral score (triggered, for
example, by a tuning system change applied score-wide) \SHOULD{}
complete within two seconds at p99. This is a soft target; the
hard target on full re-layout is that the operation be
cancellable: a user beginning a different edit \MUST{} cause the
full re-layout to abort and the new edit to proceed.
\end{requirement}
\section{Cold Open}
\label{sec:perf:open}
\begin{requirement}
\label{req:perf:cold-open-latency}
The \emph{core's portion} of cold open of a 100-page orchestral
score from a \texttt{.musc} bundle \MUST{} reach
\emph{first-system-ready} state within one second on the
reference hardware profile.
First-system-ready is defined as: the prelude has been read,
the active superblock and manifest validated, the canonical
base snapshot (if any) acquired, post-frontier envelopes
reduced for at least the first system's coverage, and the
first system's \texttt{ResolvedLayoutIR} produced. Rendering,
hit-testing, and UI bootstrapping that build atop this state
are product-layer obligations.
Background loading of remaining systems \MAY{} continue after
first-system-ready and \MUSTNOT{} block user interaction
served from already-loaded state.
not-yet-visible systems is permitted; full-score loading need not
complete in one second.
\end{requirement}
\subsection{Cold Open Measurement}
Cold open is measured from process start (or, for warm processes
opening a new score, from the open API invocation) to the first
frame containing fully rendered content for the user's initial
viewport.
\subsection{Streaming Read Discipline}
\begin{requirement}
\label{req:perf:streaming-cold-open}
The cold open target \MUST{} be achieved through streaming reads
per Chapter~\ref{ch:format}: the implementation \MUSTNOT{} load
the full score into memory before displaying the first system.
Implementations failing to meet the target by eager loading are
non-conforming.
\end{requirement}
\section{Memory Budgets}
\label{sec:perf:memory}
\begin{requirement}
\label{req:perf:core-memory-budget}
Steady-state resident memory for a 100-page orchestral score
\SHOULD{} remain below 500\,MB on the reference hardware profile,
exclusive of evictable caches. Evictable caches \MAY{} push total
resident memory higher; they \MUST{} be evicted under memory
pressure without compromising correctness.
\end{requirement}
\subsection{Memory Categories}
For measurement, memory is partitioned into:
\begin{description}
\item[Core data.] The score graph, attachments, indexes, and
operation envelope set. Cannot be evicted without losing
state.
\item[Layout cache.] The cached IR stages. Evictable; rebuilds on
demand but at performance cost.
\item[Glyph and font caches.] Glyph render data and metric caches.
Evictable; small-to-moderate size.
\item[Audio buffers and reference tracks.] Audio data loaded for
playback or reference. Evictable.
\item[Implementation overhead.] Runtime, allocator metadata,
OS-level mappings.
\end{description}
\begin{requirement}
\label{req:perf:memory-category-reporting}
Implementations \MUST{} expose the core-data memory consumption
separately from evictable caches. Reporting tools \MUST{} be able
to distinguish them. The 500\,MB target applies to core data only.
\end{requirement}
\subsection{Per-Object Memory Discipline}
\begin{requirement}
\label{req:perf:per-object-memory-layout}
Implementations \MUSTNOT{} embed glyph metrics in IR objects
(Chapter~\ref{ch:layout-ir}). Implementations \MUSTNOT{} embed
spelling-attachment payloads on pitch objects
(Chapter~\ref{ch:pitch}); attachments are externally indexed.
These discipline requirements bound per-object memory at known
upper limits.
\end{requirement}
\section{Core Data Structures for Audio Thread Use}
\label{sec:perf:audio}
The audio engine is outside the core specification. The core's
obligation is to expose data structures that permit real-time
audio safety; audio-engine performance is the obligation of the
product audio layer (Section~\ref{sec:perf:boundary}).
\begin{requirement}
\label{req:perf:audio-thread-data-access}
The core's read-only data structures consumed by an audio
thread (pitch resolution, tempo map lookup, event arena
queries) \MUST{} be designed for allocation-free, lock-free
access. Specifically:
\begin{itemize}
\item Pitch frequency resolution given a tuning system \MUST{}
be performable without allocation.
\item Tempo-map conversion (musical to wall-clock and vice
versa) \MUST{} be performable without allocation.
\item Time-indexed event lookup for playback \MUST{} be
performable without allocation.
\end{itemize}
These are properties of the core's data layouts, not of any
particular audio engine. A product-layer audio engine that
consumes them is responsible for its own real-time discipline
(no syscalls on the audio thread, no priority inversion, no
unbounded loops); the core only promises that its data
structures do not preclude that discipline.
\end{requirement}
\subsection{Snapshot API}
\begin{requirement}
\label{req:perf:lock-free-snapshots}
The core \MUST{} provide an immutable-snapshot API: the
ability for any thread to acquire a read-only view of the
score graph at a consistent point in time, suitable for use
by an audio engine. Snapshot acquisition by the snapshot
reader \MUST{} be lock-free in the sense that it never blocks
on edit-thread progress: an edit in flight \MUSTNOT{} prevent
the snapshot reader from obtaining a valid (possibly older)
snapshot.
Snapshot \emph{release} \MAY{} be deferred to a non-audio
thread. The snapshot mechanism is implementation-defined
(double-buffered immutable copies, persistent data structures,
reference-counted immutable trees, hazard pointers); only the
externally observable lock-free acquisition property is
normative here.
End-to-end audio-thread acquisition latency under load is a
product-layer obligation; the core's contribution is bounded
by the lock-free guarantee.
\end{requirement}
\section{Constraint-Solver Performance Targets}
\label{sec:perf:solver}
\begin{requirement}
\label{req:perf:solver-latency}
The constraint solver (Chapter~\ref{ch:solver}) \MUST{} satisfy:
\begin{itemize}
\item Incremental solving of a single-system edit completes
within 10\,ms at p99 on the reference hardware profile.
(This is a tighter target than the overall frame budget; the
remaining 6.7\,ms accommodates the rest of the pipeline.)
\item Cold solving of a 100-page orchestral score completes
within two seconds at p99.
\item Incremental solving propagation is bounded as documented
in the implementation's published contract.
\end{itemize}
\end{requirement}
\section{File Format Performance}
\label{sec:perf:format}
\begin{requirement}
\label{req:perf:file-operation-latency}
File format operations \MUST{} satisfy:
\begin{itemize}
\item Bundle write of a typical edit (one or more operation
envelopes appended to the operation-envelope block stream,
manifest rewrite, superblock flip) completes within
50\,ms at p99 on the reference hardware profile.
\item Bundle read of the manifest and bootstrap chunks
(sufficient for first interactive frame) completes within
200\,ms at p99 for a 100-page orchestral score.
\item Operation-envelope reduction rate \MUST{} exceed
10{,}000 envelopes per second on the reference hardware
profile, measured during cold reduction from a fresh
canonical base.
\end{itemize}
\end{requirement}
\section{Measurement Methodology}
\label{sec:perf:methodology}
\begin{requirement}
\label{req:perf:measurement-methodology}
Performance conformance \MUST{} be measured using:
\begin{itemize}
\item A reference test corpus including the conformance suite
from Chapter~\ref{ch:solver}, augmented with edit traces
representative of typical authoring sessions.
\item Measurements at the p99 percentile across at least 1000
iterations per scenario.
\item Warm runs after a cold warm-up phase, unless the target
is explicitly cold (cold open, cold load).
\item Release builds with full optimization enabled, but without
fast-math or precision-compromising flags.
\item The reference hardware profile, with no other significant
load on the system during measurement.
\end{itemize}
Implementations \MUST{} publish their measurement results against
this methodology for conformance claims.
\end{requirement}
\section{Regression Testing}
\label{sec:perf:regression}
\begin{requirement}
\label{req:perf:performance-regression-gate}
Conforming implementations \SHOULD{} maintain continuous
performance regression testing against the reference corpus, with
alerts for regressions exceeding 10\% on any target. The
regression suite \SHOULD{} be a precondition for releases.
\end{requirement}
\section{Forward References}
\begin{itemize}
\item The full performance reference corpus is delivered as a
companion specification, the Performance Reference Suite.
\item Audio engine performance targets (latency, jitter, stream
discipline) are specified in the audio engine specification,
outside this document.
\item Network and synchronization performance for the collaboration
layer is specified in the collaboration specification, outside
this document.
\end{itemize}
% ===========================================================================
\chapter{Extension Points}
\label{ch:extension}
This chapter consolidates the extension points specified across
prior chapters into a single reference. Each extension point admits
implementation-defined or plugin-contributed content that the core
treats opaquely, subject to the contracts stated here.
The chapter is a navigation aid rather than a source of new
requirements: every extension point referenced here is defined in
its originating chapter, and the originating definition is
authoritative. This chapter pulls the points together for plugin
authors and future specification editors.
\section{Design Principles}
\label{sec:ext:principles}
\begin{description}
\item[Extensibility without forking.] Every extension point
permits content to be added without modifying the core
specification or its implementations. Plugins extend; they do
not fork.
\item[Identifiers, not strings.] Extension points are identified
by typed registry identifiers, not free-form strings. Registries
are explicit, versioned, and discoverable.
\item[Contracts, not contents.] Extension points specify what an
extension \emph{must satisfy} (interfaces, invariants, reduction
rules, performance bounds) rather than what an extension
\emph{must contain}.
\item[Capability-bound at the runtime.] Plugin runtime
permissions (read score, write score, network, filesystem) are
declared per extension and granted per user. This is enforced
by the plugin runtime, which is outside this specification; the
extension points are designed to admit such enforcement.
\item[Forward compatibility.] Extensions referenced in a stored
score \MUST{} be preserved across reads and writes even when the
reading implementation does not understand them (per
Chapter~\ref{ch:format}). Extensions thus survive distribution
even where they cannot be exercised.
\end{description}
\section{Notation Grammar Extensions}
\label{sec:ext:grammar}
The notation grammar layer supports extension at several points:
\begin{description}
\item[\texttt{PositionStructure::Registered}] (Chapter~\ref{ch:tuning}).
Custom position structures for pitch spaces whose organization
does not fit the chromatic, diatonic-over-chromatic, or JI
lattice families. Used for maqam, gamelan, raga, and other
non-Western grammars.
\item[\texttt{IntervalAlgebra::Registered}] (Chapter~\ref{ch:tuning}).
Custom interval algebras for grammar-specific arithmetic.
\item[\texttt{TranspositionBehavior::Registered}]
(Chapter~\ref{ch:tuning}). Custom transposition rules.
\item[\texttt{SpellingAlgorithmId}] (Chapter~\ref{ch:pitch}).
Registered spelling-inference algorithms for the spelling
pre-pass.
\item[\texttt{NominalRegistry}] (Chapter~\ref{ch:tuning}).
Score-level or grammar-level extensions to the catalog of
named position-letters.
\item[\texttt{AccidentalRegistry}] (Chapter~\ref{ch:tuning}).
Score-level extensions to the accidental catalog, subject to
the registry's \texttt{extensible} flag.
\end{description}
\section{Tuning System Extensions}
\label{sec:ext:tuning}
\begin{description}
\item[\texttt{TuningFunctionId}] (Chapter~\ref{ch:tuning}).
Registered procedural tuning functions (historical
temperaments, custom meantone variants, generative tunings).
\item[\texttt{AdaptiveTuningFunctionId}] (Chapter~\ref{ch:tuning}).
Registered adaptive tuning functions taking harmonic context as
input.
\item[\texttt{TuningFileFormat}] (Chapter~\ref{ch:tuning}).
Recognized external tuning file formats (Scala, MIDI Tuning
Standard, additional formats via registry).
\item[\texttt{CompatibilityMapping}] (Chapter~\ref{ch:tuning}).
Declared compatibility between pitch spaces and tuning systems
whose underlying spaces differ.
\end{description}
\section{Glyph and Font Extensions}
\label{sec:ext:glyphs}
\begin{description}
\item[\texttt{CustomGlyphId}] (Chapter~\ref{ch:tuning}).
Custom glyphs not in SMuFL, defined per score or per plugin.
\item[\texttt{Composite} glyph references] (Chapter~\ref{ch:tuning}).
Composite glyphs constructed from multiple primary glyph
references.
\item[\texttt{ModificationRegistryId}] (Chapter~\ref{ch:tuning}).
Registered pitch-space modifications for grammar-defined
accidentals.
\end{description}
\section{Score Graph Extensions}
\label{sec:ext:graph}
\begin{description}
\item[\texttt{ExtensionOp}] (Chapter~\ref{ch:semops}).
Implementation-defined operation kinds extending the core's
semantic operations. Extension operations \MUST{} satisfy the
operation framework's requirements (preconditions, effects,
inverse, reduction rule, re-anchoring).
\item[\texttt{CustomMarkerId}] (Chapter~\ref{ch:graph}).
Custom marker kinds beyond the core's standard catalog.
\item[\texttt{CustomAnalyticalKindId}] (Chapter~\ref{ch:graph}).
Custom analytical annotation kinds.
\item[\texttt{Custom event variants}] (Chapter~\ref{ch:graph}).
The event taxonomy is closed in the core, but the \texttt{Group}
layout object and the \texttt{Registered} fields throughout
permit composition of core types into extended structures.
\end{description}
\section{Layout IR Extensions}
\label{sec:ext:layout}
\begin{description}
\item[\texttt{SynthesisRegistryId}] (Chapter~\ref{ch:layout-ir}).
Custom synthesis kinds for engraver-generated objects.
\item[\texttt{ConstraintRegistryId}] (Chapter~\ref{ch:layout-ir}).
Registered layout constraint kinds extending the standard
constraint families.
\item[\texttt{MappingRegistryId}] (Chapter~\ref{ch:graph}).
Custom parameter mappings for graphic-object time bindings.
\item[\texttt{Custom view kinds}] (Chapter~\ref{ch:graph}).
The \texttt{ViewKind::Custom} variant admits user-defined
view kinds.
\end{description}
\section{Constraint-Solver Extensions}
\label{sec:ext:solver}
\begin{description}
\item[\texttt{LayoutConstraint::Registered}]
(Chapter~\ref{ch:layout-ir}). Custom constraint families
consumed by the solver. Extension constraints \MUST{} declare
their strength. When soft, extension constraints contribute
to the quality vector through an extension metric subject to
the \texttt{NormalizedMetric} discipline
(Chapter~\ref{ch:solver}) and to the tier-specific reference-
suite thresholds.
\end{description}
\section{File Format Extensions}
\label{sec:ext:format}
\begin{description}
\item[\texttt{FormatProfile::Custom}] (Chapter~\ref{ch:format}).
Profile definitions beyond Full, ReadOnly, and Lite.
\item[\texttt{Self-describing extension envelope}]
(Chapter~\ref{ch:format}). Plugin-contributed and
forward-compatible content is carried in self-describing
extension records that the core preserves opaquely.
\end{description}
\section{Extension Registry Contract}
\label{sec:ext:registry}
Every extension registry shares a common structural contract.
\begin{lstlisting}[language=Rust]
pub trait ExtensionRegistry {
type Id;
type Definition;
/// Resolve an identifier to its definition.
fn resolve(&self, id: Self::Id) -> Option<&Self::Definition>;
/// Enumerate all registered definitions.
fn enumerate(&self) -> Vec<&Self::Definition>;
/// Version of the registry. Score files declare which version
/// their references target.
fn version(&self) -> RegistryVersion;
}
\end{lstlisting}
\begin{requirement}
\label{req:ext:registry-versioning}
Every extension registry \MUST{} be versioned. Stored references
to registered definitions \MUST{} include the registry version
they target. Loading a score with references to registry versions
newer than the reader understands \MUST{} preserve those references
per the forward-compatibility rules of Chapter~\ref{ch:format}.
\end{requirement}
\subsection{Identifier Stability}
\begin{requirement}
\label{req:ext:registry-id-stability}
Registry identifiers \MUST{} be stable across registry versions
within a major version. Renumbering, reassignment, or repurposing
of identifiers is forbidden within a major version. Removal of a
definition \MUST{} reserve its identifier; the identifier \MUSTNOT{}
be reassigned to a different definition.
\end{requirement}
\subsection{Discoverability}
Registries \MUST{} support enumeration: an implementation can list
all known definitions and their identifiers. This enables tooling
(plugin browsers, score migration assistants, conformance testers)
to operate against unknown registries by inspection.
\section{Plugin Runtime Considerations}
\label{sec:ext:runtime}
The plugin runtime is outside this specification, but extension
points are designed to admit a capability-bound runtime as described
in the feature compendium:
\begin{itemize}
\item Extensions are isolatable: an extension's failure does not
crash the host.
\item Extensions are introspectable: their declared capabilities
and inputs are inspectable before execution.
\item Extensions are revocable: an extension can be disabled
without invalidating the scores that reference it.
\end{itemize}
\begin{requirement}
\label{req:ext:disabled-extension-preservation}
Extension points \MUST{} be designed such that disabling an
extension does not invalidate stored content referencing it. The
content remains in the score, opaque to the host, until the
extension is re-enabled or the references are explicitly removed
by the user.
\end{requirement}
\section{Conformance and Extensions}
\label{sec:ext:conformance}
Extensions do not affect core conformance directly, but they
interact with it:
\begin{requirement}
\label{req:ext:extension-conformance}
A conforming implementation:
\begin{itemize}
\item \MUST{} support every extension point as a structural
slot (i.e., scores containing extension references are
readable and writable).
\item \MUSTNOT{} require any specific extension to be installed
for core conformance.
\item \MAY{} require specific extensions for specific features
(e.g., a particular plugin for a particular grammar).
\item \MUST{} clearly distinguish between core conformance and
extension-dependent features in its documentation.
\end{itemize}
\end{requirement}
\section{Forward References}
\begin{itemize}
\item The plugin runtime (WebAssembly-based, capability-bound)
is specified in the Plugin Runtime specification, outside this
document.
\item The complete extension registry catalog is delivered as a
companion document.
\item Implementation-defined extension contributions are governed
by the implementation's published extension specification.
\end{itemize}
% ===========================================================================
\appendix
\chapter{Intentionally Deferred Types and Specifications}
\label{app:deferred}
This appendix catalogs types, concepts, and specifications that
the core specification refers to but does not fully define. Each
entry is \emph{deliberately external}: it is the responsibility
of a named companion specification or a future revision of this
document. The purpose of this appendix is to distinguish
deliberately external content from accidentally missing content,
so that reviewers and implementers can quickly tell which
unresolved references represent open work and which represent
boundaries to other documents.
\section{Companion Specifications}
\label{sec:deferred:companions}
The following are companion specifications referenced throughout
the core. None has been delivered as of this revision; each is
named here with the chapter(s) that depend on it.
\begin{description}
\item[Binary Format companion.] Byte-level encoding of all
canonical chunks: varint conventions, exact field widths,
record layouts, endianness rules, string encoding details.
Referenced from Chapter~\ref{ch:format}. The on-disk
representations specified in that chapter are structural;
the Binary Format specifies how the bits are laid out.
\item[Text Projection companion.] The canonical s-expression
text projection of the bundle: grammar, deterministic
serialization rules, base64 encoding for opaque payloads,
handling of extension data, round-trip semantics with
binary form. Referenced from Chapter~\ref{ch:format}
Section~\ref{sec:format:textproj}.
\item[Profile Conformance specification.] The complete
catalog of file-format profiles (Full, ReadOnly, Lite, plus
any normative custom profiles), with their constraints,
permitted compression algorithms, maximum block sizes,
required extensions, and conformance test discipline.
Referenced from Chapter~\ref{ch:format}
Section~\ref{sec:format:profiles}.
\item[Quality Metric Catalog.] Per-metric normalization
functions mapping raw measurements to \texttt{NormalizedMetric}
values, default tie-breaking weights, per-tier metric
thresholds, and the formal definition of each quality
metric in the normative metric set. Referenced from
Chapter~\ref{ch:solver}.
\item[Reference Suite.] The collection of test scores against
which solver conformance is established, with per-tier
inclusion, per-tier metric thresholds, and any
fixed-expectation tests. Versioned with this specification.
Referenced from Chapter~\ref{ch:solver}
Section~\ref{sec:solver:conformance}.
\item[Performance Reference Suite.] The collection of edit
traces and reference scores against which performance
conformance is established. Referenced from
Chapter~\ref{ch:perf}.
\item[Operation Catalog companion.] The complete list of
primitive operations and compound operations supported by
the core, each with full preconditions, effects, inverse,
and reduction rule. The representative operations in
Chapter~\ref{ch:semops} illustrate the contract; the full
catalog is delivered separately.
\item[Reference Algorithm companion.] A non-normative
description of one starting-point implementation of the
constraint solver. Explanatory and diagnostic only;
conforming implementations are not required to use it.
Referenced from Chapter~\ref{ch:solver}
Section~\ref{sec:solver:reference-algo}.
\end{description}
\section{Externally Provided Components}
\label{sec:deferred:external}
The following are real components on which the core depends but
which are owned by other specifications outside the
Epiphany core scope.
\begin{description}
\item[SMuFL glyph catalog.] The Standard Music Font Layout
provides the normative glyph repertoire. Referenced from
Chapter~\ref{ch:tuning} and the layout chapters. SMuFL is
maintained by the W3C Music Notation Community Group; the
core spec declares its dependency on the SMuFL version
targeted but does not redefine SMuFL.
\item[Font metric provider.] Implementations resolve glyph
geometric data (bounding boxes, anchor points, advance
widths) through a font metric provider. The provider's
interface is part of the implementation contract; specific
font metrics are external content.
\item[Audio engine specification.] Out of scope here per
Chapter~\ref{ch:intro}. The core exposes data structures
designed for allocation-free, lock-free audio-thread access
(Chapter~\ref{ch:perf}); the audio engine's internals,
latency obligations, and stream discipline are specified
separately.
\item[Plugin runtime specification.] A capability-bound,
sandboxed runtime hosts extension code. The runtime is
out of scope here; the extension points
(Chapter~\ref{ch:extension}) are designed to admit such a
runtime.
\item[Collaboration transport.] The wire protocol by which
operation envelopes are exchanged between replicas is
out of scope here. Chapter~\ref{ch:semops} specifies what
must be exchanged and how it is reduced; the transport
layer is separate.
\item[User interface and editor.] Editor commands, selection
semantics, keyboard shortcuts, gesture grammar, and
UI-level interaction patterns are out of scope. The core
exposes a programmatic operation API; UI mapping is
layered above.
\end{description}
\section{Open Canonical Algorithms}
\label{sec:deferred:open-algos}
The following algorithms affect canonical state but are not yet
specified normatively, profile-declared, or marked
non-canonical. Per Appendix~\ref{app:determinism}
Section~\ref{sec:det:open}, each \MUST{} receive one of those
dispositions before the document can claim full canonical
score determinism across implementations.
\begin{description}
\item[Spelling pre-pass algorithm.] The algorithm that
computes default spellings from key signature, harmonic
context, and notation grammar. Referenced from
Chapter~\ref{ch:pitch}. Several established algorithms
exist (Longuet-Higgins, Temperley pitch-spelling preference
rules, and others); the choice of normative algorithm or
profile-declared algorithm set is open.
\item[Notational decomposition algorithm.] The algorithm that
breaks a sounding duration into notehead values, augmentation
dots, and ties under metric context. Referenced from
Chapter~\ref{ch:time}. Conservatoire engraving manuals
describe the conventions verbosely; the formalization is
open.
\item[Tempo curve integration.] The numerical integration
algorithm for converting musical position to wall-clock
position under \texttt{TempoShape::Curve}. Referenced from
Chapter~\ref{ch:time}. Possible candidates include Romberg
integration and adaptive Gauss-Kronrod; the normative
choice is open.
\item[Wallclock-to-musical root-finding.] The root-finding
algorithm for the inverse of tempo curve integration.
Referenced from Chapter~\ref{ch:time}. Possible candidates
include bisection with monotonicity guarantees and
Brent's method; the normative choice is open.
\end{description}
\section{Extension Registry Catalogs}
\label{sec:deferred:registries}
The extension registries enumerated in
Chapter~\ref{ch:extension} (notation grammar extensions,
tuning system extensions, glyph and font extensions, score
graph extensions, layout IR extensions, solver extensions,
file format extensions) are referenced by typed registry
identifiers in the core. The concrete catalogs of registered
extensions (the contents of each registry) are delivered as
separate, versioned registry documents. Each registry document
specifies its identifier numbering, the version under which
each registered identifier was added, and the registered
content's contract.
\section{Support-Type Identity Semantics}
\label{sec:deferred:support-types}
Several identifier-like support types appear throughout the
specification by name and shape, but their detailed encoding is
delivered by companion specifications. Their semantic identity
rules are normative here.
\begin{longtable}{p{3.4cm} p{3.6cm} p{6.6cm}}
\toprule
\textbf{Type} & \textbf{Source} & \textbf{Semantic rule} \\
\midrule
\endhead
\texttt{FileUuid} &
128-bit random at bundle creation &
Physical bundle identity. Fixed for the lifetime of the
physical bundle; preserved by filesystem byte-copy; freshly
minted on Save As, export-as-new-bundle, and derivative-work
creation. \\
\texttt{DocumentId} &
128-bit random at logical-work creation &
Logical work identity. Stable across Save As copies intended
to remain versions of the same work. May persist across forks
at user discretion. \\
\texttt{LineageId} &
128-bit random at fork-with-ancestry &
Shared-ancestor identity. Optional. Multiple documents may
share a \texttt{LineageId} declaring common ancestry for
version-control or genealogy purposes. \\
\texttt{ManifestId} &
Content-derived from manifest payload &
Unique per manifest version. Derived as
\texttt{trunc128(BLAKE3("MUSCMNIF" || document\_id ||
generation || manifest\_body))} with the \texttt{manifest\_id}
field excluded from \texttt{manifest\_body}; the derivation is
ratified normatively in
Section~\ref{req:format:manifest-id}. \\
\texttt{SnapshotId} &
Content-derived from snapshot payload and frontier &
Stable for a snapshot's root and causal frontier. Two snapshots
with identical materialized state at identical frontiers have
identical ids. The Binary Format companion defines the exact
derivation. \\
\texttt{BlobId} &
BLAKE3 of blob content with domain tag \texttt{"MUSCBLOB"} &
Content hash of the blob's uncompressed payload, identical to
the blob's \texttt{ContentHash}. \\
\texttt{AuthorId} &
Implementation-defined; persists across replica reseatings &
Identifies the human author of operations, distinct from
\texttt{ReplicaId} (which identifies the device or session).
One author may operate multiple replicas; one replica may host
one author at a time. The collaboration transport companion
defines authentication. \\
\texttt{ReductionAlgorithmVersion} &
Semantic version &
Selects canonical reduction semantics. Two replicas with
different \texttt{ReductionAlgorithmVersion} may produce
different canonical states from the same operation set; the
active superblock declares the version under which the
bundle's canonical base was materialized
(Section~\ref{sec:format:bundle}). \\
\texttt{SolverProfile} &
Registered profile identifier &
Selects the solver's hard-constraint set, normalized-metric
thresholds, tie-breaking weights, and active extension
catalog (Chapter~\ref{ch:solver}). The Quality Metric Catalog
companion defines the registered profile catalog. \\
\texttt{ProfileId} (file format) &
Registered profile identifier &
Selects the file-format conformance subset (Full, ReadOnly,
Lite, or registered Custom). The Profile Conformance companion
defines the registered profile catalog. \\
\texttt{ContentHash} &
BLAKE3-256 output (32 bytes) &
Cryptographic content hash. The canonical hash for all
content-addressed objects in the bundle. \\
\texttt{ChunkId} &
Newtype around \texttt{ContentHash} &
Same 32 bytes as the underlying \texttt{ContentHash}; the
type distinction makes role visible at use sites. \\
\texttt{OperationId} &
64-bit \texttt{ReplicaId} + 64-bit counter, both
randomly initialized; counter monotonic &
Stable identity of an operation, set by the authoring replica
at the moment of authoring. Never reassigned. \\
\texttt{TransactionId} &
128-bit, author-minted &
Identifies a transaction declared by a
\texttt{DeclareTransaction} envelope. Member envelopes
reference this id in their envelope \texttt{transaction}
field. \\
\texttt{ConflictId} &
Content-derived via BLAKE3 truncation with domain tag
\texttt{"MUSCCONF"} &
Identifier of a conflict record. Derived from canonical kind,
sorted causing operations, sorted affected objects.
Section~\ref{sec:semops:conflict-id}. \\
\texttt{Typed identifiers} (EventId, PitchId, VoiceId,
StaffId, etc.) &
64-bit \texttt{ReplicaId} + 64-bit counter; or
\texttt{ReplicaId::SYSTEM\_DERIVED} + BLAKE3-derived counter
for system-derived identifiers &
Stable identity within their typed namespace.
Section~\ref{sec:graph:system-derived}. \\
\bottomrule
\end{longtable}
\begin{requirement}
\label{req:deferred:identity-semantics}
Implementations \MUST{} respect the semantic identity rules
declared above. They \MAY{} freely choose the bit-level
encoding of each type (the Binary Format companion specifies
the canonical wire encoding); they \MAY NOT{} reinterpret the
identity semantics. In particular, two distinct works \MUSTNOT{}
share a \texttt{DocumentId}; two byte-distinct chunks \MUSTNOT{}
share a \texttt{ContentHash}; two distinct operations \MUSTNOT{}
share an \texttt{OperationId}.
\end{requirement}
\section{Distinction from Open Questions}
\label{sec:deferred:vs-open}
This appendix lists content that is \emph{deliberately external}
to the core specification: it is owned by a companion document,
a future revision, or a third-party specification.
It does \emph{not} list ``open questions'' in the core's own
design space. Those are marked with the
\textsf{\textit{Open Question}} callout where they appear in
the body of the text, and they represent unresolved decisions
\emph{within} the core specification's scope. The two
categories overlap only at the four open canonical algorithms
in Section~\ref{sec:deferred:open-algos}, which are unresolved
in the core text \emph{and} whose resolution will likely take
the form of a companion specification or a profile declaration.
\chapter{Glossary}
\label{app:glossary}
This glossary defines key terms used throughout the specification.
Definitions are drawn from their introducing chapters; the chapter
reference points to the authoritative definition.
\begin{description}
\item[Acoustic pitch] (Chapter~\ref{ch:pitch}). The frequency
realization of a pitch under an active tuning system. One of the
two intrinsic layers of a \texttt{Pitch}.
\item[Adaptive tuning] (Chapter~\ref{ch:tuning}). A tuning system
whose frequency resolution depends on harmonic context, not on
pitch-space position alone.
\item[Aleatoric time] (Chapter~\ref{ch:time}). A region time model
in which events have partial or undefined ordering, expressed
as a directed acyclic graph with optional interval bounds.
\item[Anchor] (Chapter~\ref{ch:time}). A reference to a point in
time, expressed relative to an identified object (event,
measure, region, wall-clock time) plus an offset. Anchors
survive edits that do not delete their target.
\item[Arena (event arena)] (Chapter~\ref{ch:graph}). The flat
storage owned by a score holding all events of all kinds, with
$O(1)$ lookup by \texttt{EventId}.
\item[Bundle] (Chapter~\ref{ch:format}). The on-disk container
holding a score: header, manifest, chunk store, and blob store
in a single \texttt{.musc} file.
\item[Canonical reduction] (Chapter~\ref{ch:semops}). The
deterministic procedure that materializes a score graph from
the operation set: sort by canonical order, group transactions,
apply each operation against working state, record effects.
\item[Canvas] (Chapter~\ref{ch:graph}). The spatial root of a
score: a 2D space partitioned into regions of staff-based,
free-graphic, or hybrid content.
\item[Causal predecessors] (Chapter~\ref{ch:semops}). The set of
operations on which a given operation causally depends. Used by
the CRDT layer to determine concurrency. Expressed in canonical
form as a dotted version vector
(\texttt{CausalContext}); see ``Dotted version vector.''
\item[Chunk] (Chapter~\ref{ch:format}). An immutable
content-addressed unit of storage within a bundle. Identified
by the cryptographic hash of its uncompressed payload, with a
domain-separated preimage.
\item[Compound operation] (Chapter~\ref{ch:semops}). A user-facing
operation that expands into a sequence of primitive operations
applied as a transaction.
\item[Conflict record] (Chapter~\ref{ch:semops}). A first-class
graph object recording an operation that could not apply
cleanly under canonical reduction. Stable, addressable,
user-visible, and resolvable by subsequent
\texttt{ResolveConflict} operations.
\item[CRDT] (Chapters~\ref{ch:semops},~\ref{ch:format}).
Conflict-free replicated data type. In Epiphany, the
\emph{operation set} is a true CRDT (a grow-only set of
operation envelopes); the materialized score graph is
\emph{not} itself a CRDT but is the deterministic reduction
of that set.
\item[Cross-cutting structure] (Chapter~\ref{ch:graph}). A
first-class object spanning multiple tree-nodes via references:
slurs, ties, beams, spanners, markers, repeats, analytical
annotations, comments, graphic gestures, lyrics, chord symbols.
\item[Decomposition] (Chapter~\ref{ch:time}). The notational
breakdown of an event's sounding duration into notehead values,
augmentation dots, and ties. Attached externally by the
decomposition pre-pass.
\item[Delta] (Chapter~\ref{ch:semops}). The structural record of
an operation's effect, sufficient to reconstruct the pre-state
of every object the operation modifies.
\item[Document identifier] (Chapter~\ref{ch:format}). The
logical work identity (\texttt{DocumentId}), stable across
Save As copies that are intended to remain versions of the
same work. May persist across forks at the user's
discretion. Distinct from \texttt{FileUuid}
(physical bundle identity, changes on Save As) and
\texttt{LineageId} (optional shared-ancestor identity).
\item[Dotted version vector] (Chapters~\ref{ch:semops},
\ref{ch:format}). The compact causal-context representation
of operation history. Operational semantics (causal
precedence, frontier coverage, monotonic growth) are
specified in Chapter~\ref{ch:semops}; the on-disk
serialization is specified in Chapter~\ref{ch:format}.
\item[Edit barrier] (Chapter~\ref{ch:format}). A structured
declaration by an extension that certain edits would
invalidate the extension's data. Implementations not
understanding the extension refuse matching edits unless the
user performs an explicit unsafe edit.
\item[Engraving decision] (Chapter~\ref{ch:layout-ir}). A choice
made by the engraver (stem direction, beam consolidation, etc.)
recorded explicitly in the layout IR for inspection and
override.
\item[Engraving override] (Chapter~\ref{ch:layout-ir}). A user
assertion that the engraver's default decision should be
replaced. Authoritative in the score graph; projected into the
layout IR.
\item[Event] (Chapter~\ref{ch:graph}). A rhythmic atom of the
score: pitched, unpitched, rest, indeterminate, trajectory,
graphic, or cue. Owned by exactly one voice.
\item[Format profile] (Chapter~\ref{ch:format}). A named subset
of the file format that conforming implementations promise to
support. Profiles enumerated: Full, ReadOnly, Lite, Custom.
\item[Frame budget] (Chapter~\ref{ch:perf}). The time allowed for
a layout-and-render operation to maintain 60\,Hz (16.7\,ms) or
120\,Hz (8.3\,ms) interactivity.
\item[Glyph catalog identity] (Chapter~\ref{ch:layout-ir}). The
identifier (SMuFL version, font id, optional font version,
metrics hash) under which layout output was produced.
Required for any byte-equal-output layout conformance claim.
\item[Graphic content] (Chapter~\ref{ch:graph}). Free-form vector
graphics, drawn strokes, text, and images within free-graphic
or hybrid regions.
\item[Hard constraint] (Chapter~\ref{ch:solver}). A constraint
that must be satisfied; violation produces a solver error.
\item[Hybrid logical clock] (Chapters~\ref{ch:semops},
\ref{ch:format}). A clock combining physical wall-clock
time with a logical counter, used as a tie-breaker in
canonical reduction order. Operational semantics
(per-replica monotonicity, role in canonical reduction
ordering, malformed-envelope behavior) are specified in
Chapter~\ref{ch:semops}; the on-disk serialization is
specified in Chapter~\ref{ch:format}.
\item[Identifier (typed)] (Chapter~\ref{ch:graph}). A 128-bit
stable identifier for a named object: \texttt{EventId},
\texttt{VoiceId}, \texttt{StaffId}, etc. Generated by
replica-plus-counter scheme.
\item[Incremental layout] (Chapter~\ref{ch:layout-ir}). Re-layout
of only the IR objects whose dependencies have changed, with
cached values reused elsewhere.
\item[Instrument] (Chapter~\ref{ch:graph}). An abstract definition
of a sounding entity: name, transposition, range, default
sound, default clef, default staff count.
\item[Layout IR] (Chapter~\ref{ch:layout-ir}). The pipeline of
intermediate representations between score graph and renderer:
\texttt{LogicalLayoutIR}, \texttt{ConstrainedLayoutIR},
\texttt{ResolvedLayoutIR}, \texttt{RenderIR}.
\item[Manifest] (Chapter~\ref{ch:format}). The mutable index of a
bundle: a table of operation roots, optional canonical base
snapshot, acceleration snapshots, blob roots, profile
declarations, and extension declarations. Itself a
content-addressed chunk; the active manifest is selected
via the active superblock slot
(Chapter~\ref{ch:format}).
\item[Metric time] (Chapter~\ref{ch:time}). A region time model
with measures, beats, and exact rational positions. The
dominant model for Western notated music.
\item[Notation grammar] (Chapter~\ref{ch:tuning}). The declarative
definition of a notation system's pitch space, intervals,
accidentals, and spelling rules. CMN is the default; others
extend.
\item[Operation envelope] (Chapter~\ref{ch:semops}). A
transmitted, stored, and reducible record carrying an
operation's payload together with its identity, ordering
stamp, causal context, and optional transaction grouping.
The unit of replication.
\item[Operation kind] (Chapter~\ref{ch:semops}). The tagged
enumeration of primitive operation variants: InsertEvent,
DeleteEvent, RespellPitch, Transpose, ChangeRegionTimeModel,
and so on, plus \texttt{Registered} for extension-defined
primitives. The full catalog is delivered separately.
\item[Operation log] (Chapter~\ref{ch:format}). The physical
storage of the canonical operation-envelope set on disk, as
operation-envelope blocks. The canonical document is the
deterministic reduction of this envelope set, or, after
pruning, of the manifest's \texttt{canonical\_base}
snapshot plus envelopes whose \texttt{OperationId}s are not
covered by the base's causal frontier.
\item[Operation set] (Chapter~\ref{ch:semops}). The replicated
grow-only CRDT of operation envelopes. The canonical
document is its deterministic reduction.
\item[Pareto frontier (design target)]
(Chapter~\ref{ch:solver}). The set of layouts for which no
quality metric can be improved without worsening another.
Solvers \SHOULD{} seek layouts near the frontier; conformance
is reference-suite-based, not proof of Pareto optimality.
\item[Part] (Chapter~\ref{ch:graph}). A per-instrument or
per-section view onto the score for printed extraction. Parts
are projections, not storage.
\item[Pitch space] (Chapter~\ref{ch:tuning}). The analytical
universe of a pitch system: what positions exist, how they
relate, what accidentals are valid. Distinct from tuning system.
\item[Pitch spelling] (Chapter~\ref{ch:pitch}). The notational
appearance of a pitch: nominal, accidental, octave. Attached
externally by the spelling pre-pass.
\item[Primitive operation] (Chapter~\ref{ch:semops}). The atomic
unit of editing. Primitive operations compose into compound
operations and transactions.
\item[Provenance] (Chapter~\ref{ch:layout-ir}). The record on
each layout IR object tracing back to its score graph source,
enabling selection, editing back-references, and incremental
layout invalidation.
\item[Proportional time] (Chapter~\ref{ch:time}). A region time
model in which horizontal position is wall-clock time, with no
measures or beats.
\item[Re-anchoring] (Chapter~\ref{ch:semops}). The normative
response of a cross-cutting structure when its referent is
deleted: cascade delete, truncate, re-anchor, mark orphaned,
or refuse.
\item[Reference pitch] (Chapter~\ref{ch:tuning}). The anchor
converting tuning system structure to absolute frequencies:
a position and a frequency in Hertz. A property of the score,
not the tuning system.
\item[Region] (Chapter~\ref{ch:graph}). A spatial-temporal
container in the canvas declaring a time model (metric,
proportional, aleatoric) and a content model (staff-based,
free-graphic, hybrid).
\item[Repair record] (Chapter~\ref{ch:semops}). A deterministic
compensating change made during reduction to preserve graph
invariants (re-anchoring, spanner truncation, attachment
tombstoning, voice promotion, tuplet compensation). Recorded
as part of canonical state via
\texttt{OperationEffect::AppliedWithRepair}.
\item[Replica] (Chapter~\ref{ch:graph},~\ref{ch:format}). An
instance of a score on a device. Each replica has a unique
identifier and authors operations with monotonic local
sequence numbers. The replica identifier
\texttt{ReplicaId::SYSTEM\_DERIVED} is reserved for
deterministically-derived system identifiers.
\item[Retention policy] (Chapter~\ref{ch:format}). The
declared rule governing how many old manifests (and their
reachable chunks) are preserved in the bundle for rollback
purposes. Rollback is achieved through retained manifests in
the chunk store, not by retaining multiple on-disk
superblock generations.
\item[Scale position] (Chapter~\ref{ch:pitch}). The analytical
identity of a pitch within a pitch space. One of the two
intrinsic layers of a \texttt{Pitch}.
\item[Score graph] (Chapter~\ref{ch:graph}). The in-memory
representation of all musical content in a score: canonical
truth from which layout, serialization, and editing operations
derive.
\item[Semantic operation] (Chapter~\ref{ch:semops}). A mutation
of the score graph: insert event, delete measure, transpose,
respell pitch, etc.
\item[SMuFL] (Chapter~\ref{ch:tuning}). Standard Music Font
Layout. The normative source for glyph references in the core.
\item[Snapshot] (Chapter~\ref{ch:format}). A materialized score
state. As an acceleration snapshot (listed in
\texttt{acceleration\_snapshots}), derivative and discardable.
As a canonical-base snapshot (named in the manifest's
\texttt{canonical\_base} field with declared causal frontier
and reduction algorithm version), the canonical base for
pruned documents, in which case the canonical state is the
base snapshot plus envelopes whose \texttt{OperationId}s are
not covered by the base's causal frontier.
\item[Soft constraint] (Chapter~\ref{ch:solver}). A constraint
with a weight; violation is permitted but penalized in
quality metrics.
\item[Sounding duration] (Chapter~\ref{ch:time}). The exact
rational duration an event occupies in time. Intrinsic;
notational decomposition is attached separately.
\item[Spring slot] (Chapter~\ref{ch:layout-ir}). A time slot's
horizontal spacing parameters (min, preferred, max widths;
stretch, compress factors) consumed by the constraint solver.
\item[Staff] (Chapter~\ref{ch:graph}). The global, abstract
staff identity persisting across the entire score. A staff is
declared once at the score level and referenced by staff
instances in any region where it appears.
\item[Staff instance] (Chapter~\ref{ch:graph}). The
region-local manifestation of a staff: the voices, measures,
clef changes, key changes, and metric grid that belong to
that staff for one region. A single \texttt{Staff} may have
multiple \texttt{StaffInstance}s across the score.
\item[Tempo map] (Chapter~\ref{ch:time}). The function mapping
musical positions to wall-clock positions, piecewise over
musical time with constant, linear, exponential, or curve
segments.
\item[Time anchor] (Chapter~\ref{ch:time}). See ``Anchor.''
\item[Time axis] (Chapter~\ref{ch:layout-ir}). The polymorphic
layout-IR component projecting time positions to horizontal
positions. Three implementations: metric, proportional,
aleatoric.
\item[Transaction] (Chapter~\ref{ch:semops}). A replicated
operation group that reduces atomically: either every member
applies and the transaction reduces to \texttt{Applied}, or
the entire transaction reduces to \texttt{Conflicted} with a
recorded conflict.
\item[Trajectory] (Chapter~\ref{ch:graph}). A continuous
pitch-motion event: glissando, portamento, pitch bend. A
sibling to pitched events under the event taxonomy.
\item[Tuning system] (Chapter~\ref{ch:tuning}). The mechanism
mapping pitch-space positions to frequencies, given a
reference pitch. Distinct from pitch space.
\item[Tuplet] (Chapter~\ref{ch:time}). A grouping object
annotating irregular rhythmic groupings. Does not modify
sounding durations; purely notational and analytical.
\item[Typed object identifier]
(Chapter~\ref{ch:graph}). A tagged union
(\texttt{TypedObjectId}) over every identifier kind in the
score graph. Used wherever an object is referenced
generically (cross-cutting endpoints, conflict
\texttt{affected\_objects}, repair records, edit barriers).
\item[Validation mode] (Chapter~\ref{ch:semops}). The strictness
setting for operation precondition checking: strict authoring
or lenient replay.
\item[Vertical band] (Chapter~\ref{ch:layout-ir}). A horizontal
slice of the canvas with spring parameters for vertical
spacing: staff bands, inter-staff gaps, inter-system gaps,
margin bands.
\item[View] (Chapter~\ref{ch:graph}). A recipe for displaying a
configuration of the score: which layers are active, which
parts are rendered, what overrides apply.
\item[Voice] (Chapter~\ref{ch:graph}). A polyphonic line within
a staff. Owns a sequence of events.
\item[Wall-clock time] (Chapter~\ref{ch:time}). Time measured by
the playback engine or external sync source, in fixed-point
nanoseconds. Distinct from musical time.
\end{description}
\chapter{Bibliography and References}
\label{app:refs}
\section*{Engraving Treatises}
\begin{description}
\item[Behind Bars.] Elaine Gould. \emph{Behind Bars: The Definitive
Guide to Music Notation.} Faber Music, 2011. The principal
modern reference for music engraving conventions.
\item[Music Notation in the Twentieth Century.] Kurt Stone.
\emph{Music Notation in the Twentieth Century: A Practical
Guidebook.} W. W. Norton, 1980. Authoritative reference for
contemporary notation.
\item[Essentials of Music Notation.] Tom Ross. \emph{The Art of
Music Engraving and Processing.} Hansen Books, 1970. Classical
treatise on engraving conventions, still influential.
\item[Music Notation.] Gardner Read. \emph{Music Notation: A
Manual of Modern Practice.} Crescendo Publishers, 1969.
Reference for engraving practices and history.
\end{description}
\section*{Music Theory and Analysis}
\begin{description}
\item[The Cognition of Basic Musical Structures.]
David Temperley. MIT Press, 2001. Source of preference-rule
approaches to spelling and meter analysis.
\item[Pitch Spelling and the Line of Fifths.]
H. Christopher Longuet-Higgins. Foundational work on
pitch-spelling algorithms.
\item[Tonal Pitch Space.] Fred Lerdahl. Oxford University Press,
2001. Pitch-space theory as analytical framework.
\item[A Generative Theory of Tonal Music.]
Fred Lerdahl and Ray Jackendoff. MIT Press, 1983. Foundational
work in formal music theory and the source of preference-rule
methodology.
\end{description}
\section*{Tuning, Microtonality, and Just Intonation}
\begin{description}
\item[Genesis of a Music.] Harry Partch. University of Wisconsin
Press, 2nd ed. 1974. Foundational text in extended just
intonation and microtonal practice.
\item[Tuning, Timbre, Spectrum, Scale.] William Sethares.
Springer, 2nd ed. 2004. Modern treatment of tuning systems
and their psychoacoustic foundations.
\item[Helmholtz-Ellis JI Pitch Notation.] Marc Sabat and
Wolfgang von Schweinitz. The HEJI accidental system referenced
by the core's built-in JI pitch spaces.
\item[Sagittal: A Microtonal Notation System.] George D. Secor
and David C. Keenan. The Sagittal microtonal accidental system.
\end{description}
\section*{Constraint-Based Layout}
\begin{description}
\item[Cassowary Linear-Arithmetic Constraint Solver.]
Greg J. Badros, Alan Borning, and Peter J. Stuckey.
\emph{The Cassowary Linear Arithmetic Constraint Solving
Algorithm.} ACM Transactions on Computer-Human Interaction,
8(4):267-306, 2001. Source of the constraint-solver model
referenced in Chapter~\ref{ch:solver}.
\item[Music Engraving with Constraint Programming.]
Multiple authors. Survey of constraint-based approaches to
music engraving.
\item[The Gourlay Algorithm.] J. S. Gourlay. \emph{A Language
for Music Printing.} Communications of the ACM, 1986. Source
of the proportional spacing model referenced in
Chapter~\ref{ch:layout-ir}.
\end{description}
\section*{CRDT and Distributed Systems}
\begin{description}
\item[A Comprehensive Study of Convergent and Commutative
Replicated Data Types.] Marc Shapiro, Nuno Pregui\c{c}a,
Carlos Baquero, and Marek Zawirski. INRIA Technical Report
RR-7506, 2011. Foundational survey of CRDTs.
\item[Dotted Version Vectors.] Nuno Pregui\c{c}a et al.
Refinement of vector clocks used in the file format
(Chapter~\ref{ch:format}).
\item[Hybrid Logical Clocks.] Sandeep S. Kulkarni et al.
Clocks combining physical and logical time, used for total
ordering of operation envelopes
(Chapter~\ref{ch:semops}).
\end{description}
\section*{Prior-Art Notation Formats}
\begin{description}
\item[MusicXML.] Recordare LLC, later W3C Music Notation Community
Group. The dominant interchange format; surveyed for the
interchange profile work referenced in
Chapter~\ref{ch:graph}.
\item[MEI (Music Encoding Initiative).] An XML-based encoding
designed for scholarly editions and musicology. Influenced the
spec's handling of editorial apparatus and analytical layers.
\item[MNX.] W3C Music Notation Community Group draft. The
successor effort to MusicXML; informed several decisions on
layered data models.
\item[LilyPond.] David Bauer, Han-Wen Nienhuys, Jan Nieuwenhuizen.
Open-source music engraving software using a text-based input
format and a sophisticated rule-based engraver. Significant
influence on the spec's engraving philosophy.
\item[SMuFL.] W3C Music Notation Community Group. The Standard
Music Font Layout, referenced as the normative glyph catalog
in Chapter~\ref{ch:tuning}.
\end{description}
\section*{Additional References}
\begin{description}
\item[The Anatomy of a Note.] Multiple papers on the data
representation of musical notation in computer systems.
\item[Notation in Computer Music.] Various proceedings of the
International Computer Music Conference (ICMC) and the New
Interfaces for Musical Expression (NIME) workshops.
\end{description}
\chapter{Determinism Contract}
\label{app:determinism}
This appendix consolidates the reproducibility obligations that
appear throughout the specification. It defines exactly which
values are admissible in canonical state, how they are serialized,
how comparisons work, and where implementation freedom begins.
The appendix is the document's legal code for reproducibility:
where the body of the specification says ``deterministic,'' this
appendix says what that means.
\section{Thesis}
\label{sec:det:thesis}
\begin{requirement}
\label{req:determinism:platform-independent-state}
Canonical document state \MUST{} be independent of platform,
CPU, locale, thread scheduling, hash-map iteration order,
floating-point environment, compression settings, and
wall-clock timing.
Where exact equality is required, this specification defines
canonical representations. Where numerical approximation is
unavoidable, this specification defines deterministic
tolerances, quantization, and reporting rules.
Implementations \MAY{} freely choose internal data structures,
algorithms, and parallelism strategies. The canonical externally
observable result \MUSTNOT{} depend on those choices.
\end{requirement}
\section{Layers of Determinism}
\label{sec:det:layers}
The specification distinguishes five layers. Each layer has its
own determinism contract; conflating them is the most common
source of impossible requirements.
\begin{description}
\item[Canonical score determinism] (Chapter~\ref{ch:semops}).
The reduction of an operation-envelope set to a materialized
score graph is fully deterministic \emph{when every algorithm
contributing to that reduction has received one of the
dispositions in Section~\ref{sec:det:open}} (normatively
specified, profile-declared by versioned identifier, or
explicitly non-canonical). Under that condition, identical
operation sets produce identical materialized score states
across all conforming implementations, with identical object
identifiers, tombstones, conflicts, and effects. For
algorithms still in open-question state
(Section~\ref{sec:det:open}), this byte-equal cross-
implementation guarantee \MUSTNOT{} be claimed; the output
of such algorithms is either non-canonical or governed by the
profile's declared algorithm version.
\item[Canonical serialization determinism]
(Chapter~\ref{ch:format}). The bundle format's chunk hashes,
manifest encoding, text projection, and the ordering of
canonical maps and sets are deterministic. Two implementations
producing the same canonical state \MUST{} produce identical
chunk content hashes and identical text-projection bytes.
\item[Layout determinism] (Chapter~\ref{ch:solver}). Within a
single solver implementation at a fixed version, with fixed
profile, fixed budget, and fixed font/glyph metrics, layout
output is byte-identical across runs. Across different solver
implementations, layout output \MAY{} differ aesthetically
within reference-suite thresholds.
\item[Conformance determinism]. Reference-suite-based:
conforming implementations need not produce byte-identical
output to each other; they need to fall within declared
metric thresholds on every reference-suite entry. See
Chapter~\ref{ch:solver} Section~\ref{sec:solver:conformance}.
\item[Non-canonical cache determinism]. Implementations
maintain caches (operation index, layout cache, glyph metric
cache, render artifact cache). These \MAY{} vary across
runs, platforms, and implementations. They \MUSTNOT{} affect
canonical state and \MUST{} be discardable
(Chapter~\ref{ch:semops} Section~\ref{sec:semops:caches}).
\end{description}
\begin{requirement}
\label{req:determinism:determinism-layer-scope}
Byte-equality obligations \MUST{} be applied only to the layer
for which they are stated. Implementations \MUSTNOT{} interpret
``deterministic'' more strongly than its layer specifies, and
\MUSTNOT{} interpret it more weakly either.
\end{requirement}
\begin{rationale}
The conditional in canonical score determinism is essential.
Several algorithms affecting canonical state remain open
questions in this draft: the spelling pre-pass
(Chapter~\ref{ch:pitch}), the notational decomposition
algorithm (Chapter~\ref{ch:time}), and the
\texttt{wallclock\_to\_musical} root-finding algorithm
(Chapter~\ref{ch:time}). Until those algorithms receive
Section~\ref{sec:det:open} dispositions, cross-implementation
byte equality cannot be promised for outputs derived from them.
This is not a weakening of the determinism contract; it is a
precise statement of what the contract currently covers. Once
the open algorithms are specified or profile-declared, the
conditional resolves and full cross-implementation byte
equality applies to all of canonical state.
\end{rationale}
\section{Floating-Point Values in Canonical State}
\label{sec:det:fp}
\subsection{Permitted Forms}
\begin{requirement}
\label{req:determinism:canonical-floating-point}
Canonical stored floating-point values \MUST{} be finite
IEEE 754 binary64 values. NaN and infinity \MUSTNOT{} appear
in canonical chunks: implementations \MUST{} reject them at
serialization time and \MUST{} treat their presence on read as
data corruption.
The value \texttt{-0.0} (negative zero) \MUST{} be canonicalized
to \texttt{+0.0} before storage. Two floating-point values that
differ only in zero sign are equal under canonical comparison.
Floating-point values appear in advisory, acoustic, tuning,
tempo, layout, and quality-metric contexts. Where exact
identity is needed (object identifiers, operation ordering,
graph membership, hash identity), floating point \MUSTNOT{}
be used.
\end{requirement}
\subsection{Serialization}
\begin{requirement}
\label{req:determinism:floating-point-serialization}
Canonical \texttt{f64} values are serialized as little-endian
IEEE 754 binary64 octets after applying the
\texttt{-0.0 $\to$ +0.0} canonicalization. The eight serialized
bytes are the canonical representation; two values whose
canonical bytes are equal are canonically equal.
Hash preimages incorporating floating-point values
(Section~\ref{sec:format:hashing}) \MUST{} use the canonical
serialized bytes, not platform-native byte orders or
representations.
\end{requirement}
\subsection{Equality}
\begin{requirement}
\label{req:determinism:floating-point-equality}
Canonical equality of two floating-point values is byte equality
of their canonical serialized representations. The IEEE 754
notion that \texttt{NaN $\ne$ NaN} is irrelevant in canonical
state, because canonical state cannot contain NaN.
For \texttt{NormalizedMetric} values
(Chapter~\ref{ch:solver}), additional invariants apply: the
value is finite, in $[0.0, 1.0]$, and lower is better.
Comparisons of \texttt{NormalizedMetric} use IEEE 754 ordered
comparison; the \texttt{-0.0 $\to$ +0.0} canonicalization
applies before comparison.
\end{requirement}
\section{Rounding and CPU Behavior}
\label{sec:det:rounding}
\begin{requirement}
\label{req:determinism:floating-point-execution}
Numerical operations whose results enter canonical state
\MUST{} use IEEE 754 round-to-nearest, ties-to-even. The
ambient floating-point rounding mode (which on most platforms
defaults to round-to-nearest-ties-to-even but can be changed)
\MUSTNOT{} affect canonical results: implementations \MUST{}
either save/restore the rounding mode or use operations
insensitive to it.
Implementations \MUSTNOT{} compile canonical numerical
algorithms with \texttt{-ffast-math}, \texttt{-funsafe-math-
optimizations}, or equivalent flags that permit
non-IEEE-conformant transformations. Implementations \MUSTNOT{}
reorder, reassociate, or distribute floating-point expressions
in ways that can change the canonical result.
Implementations \MUSTNOT{} silently fuse multiplications and
additions into fused-multiply-add operations unless the
algorithm explicitly requests FMA. Implementations \MUSTNOT{}
depend on, or be sensitive to, ambient floating-point exception
flags or host rounding mode.
Where implementations use SIMD, GPU acceleration, or platform
math libraries (\texttt{libm}, vendor math libraries) in
canonical algorithms, they \MUST{} produce results identical
to a strict-IEEE single-threaded baseline. Differences in
transcendental function implementations (\texttt{sin},
\texttt{cos}, \texttt{exp}, \texttt{log}) across libm versions
are a real source of nondeterminism; implementations using
transcendentals in canonical algorithms \MUST{} use a
documented portable implementation (e.g., a vendored math
library or a software fallback path).
\end{requirement}
\section{Exact and Quantized Representations}
\label{sec:det:exact}
The specification prefers exact representations over
floating-point wherever possible. The following are exact by
design:
\begin{description}
\item[Time and duration.] \texttt{MusicalPosition} and
\texttt{MusicalDuration} are rational
(Chapter~\ref{ch:time}). Inline-or-promoted representation;
arithmetic is exact.
\item[Operation ordering.] Causal order from DVV; total order
by HLC and identifier (Chapter~\ref{ch:semops}). All integer-
valued.
\item[Identifiers.] All graph identifiers are 128-bit values
composed of replica plus counter (Chapter~\ref{ch:graph}).
\item[Hashes.] All canonical hashes are BLAKE3-256
(Chapter~\ref{ch:format}).
\end{description}
\subsection{Quantized Layout Coordinates}
Spatial layout coordinates require numeric computation, but
canonical output must be stable. The specification quantizes
canonical coordinates to a fixed grid.
\begin{lstlisting}[language=Rust]
/// A canonical spatial coordinate, quantized to 1/1024 of a
/// staff space. Used for canonical serialized output of
/// ResolvedLayoutIR positions. Internal solvers MAY use floating
/// point during computation; canonical serialization rounds to
/// QuantizedCoord.
pub struct QuantizedCoord {
/// Coordinate value in units of 1/1024 staff space.
pub units: i64,
}
\end{lstlisting}
\begin{requirement}
\label{req:determinism:layout-coordinate-quantization}
The canonical spatial coordinate grid is $1/1024$ staff space
per unit. Layout coordinates emitted into canonical
\texttt{ResolvedLayoutIR} \MUST{} be quantized to this grid
before serialization. The quantization rule is round-to-nearest,
ties-to-even on the integer unit value.
Implementations \MAY{} use floating-point coordinates internally
during layout computation. The act of quantization at
serialization time absorbs all floating-point variation from
the canonical output. Two implementations whose internal
computations agree to better than $1/2048$ staff space at
every coordinate produce identical canonical output after
quantization.
The grid spacing is fixed at $1/1024$ staff space for this
format version. Future major versions \MAY{} adjust the
spacing; doing so is a non-backward-compatible change.
\end{requirement}
\begin{rationale}
$1/1024$ staff space is finer than visual resolution at any
practical printing density (a staff space is typically 1.5--2\,mm,
so $1/1024$ is roughly 1.5--2\,$\mu$m, well below print
resolution). The grid is fine enough that quantization is
inaudible musically and invisible visually, but coarse enough
that floating-point noise from different solver implementations
routinely rounds away.
\end{rationale}
\section{Tolerance Classes}
\label{sec:det:tolerances}
The specification refers to numerical tolerances in several
places (acoustic comparison, layout quality thresholds, tempo
integration, solver residual). All such tolerances belong to one
of a small set of named classes, each with declared semantics.
\begin{lstlisting}[language=Rust]
pub enum ToleranceClass {
/// Acoustic pitch comparison, in cents.
AcousticCents,
/// Layout coordinate comparison, in staff spaces.
LayoutCoordinate,
/// Quality metric comparison; absolute on the [0.0, 1.0]
/// NormalizedMetric scale.
QualityMetric,
/// Tempo integration residual: maximum permitted error in
/// musical_to_wallclock or wallclock_to_musical conversion.
TempoIntegration,
/// Solver residual: maximum permitted constraint violation
/// for a soft constraint to be considered satisfied.
SolverResidual,
}
pub struct Tolerance {
pub class: ToleranceClass,
/// Absolute tolerance. The unit is implied by the class.
pub absolute: f64,
/// Optional relative tolerance, applied to nonzero values.
pub relative: Option<f64>,
/// What the tolerance governs.
pub governance: ToleranceGovernance,
}
pub enum ToleranceGovernance {
/// Tolerance affects canonical equality (rare; usually only
/// for diagnostic comparison).
Equality,
/// Tolerance is a validation threshold (constraint considered
/// satisfied if violation is below tolerance).
Validation,
/// Tolerance affects only diagnostic output; canonical state
/// is unaffected.
Diagnostic,
}
\end{lstlisting}
\begin{requirement}
\label{req:determinism:named-tolerances}
Specifications and conformance documents \MUSTNOT{} introduce
ad-hoc epsilon constants. Every numerical tolerance that
affects normative behavior \MUST{} be declared as a named
\texttt{Tolerance} value belonging to a defined
\texttt{ToleranceClass}, with an explicit unit, absolute and
optional relative bounds, and governance category.
Tolerances \MUSTNOT{} apply to identity: \texttt{PitchId},
\texttt{EventId}, \texttt{OperationId}, graph membership,
hash identity, and operation ordering are exact, never within
tolerance.
The Quality Metric Catalog, the Reference Suite, and the
Performance Reference Suite enumerate the specific tolerance
values for each profile and tier. Their values are normative
per the companion specifications.
\end{requirement}
\section{Ordered Iteration over Sets and Maps}
\label{sec:det:ordering}
Canonical output frequently depends on iterating a collection.
Hash-map and B-tree implementation order leaks platform-specific
behavior into canonical output if not controlled.
\begin{requirement}
\label{req:determinism:canonical-collection-order}
Whenever canonical output, canonical serialization, or canonical
hashing depends on iterating a set, map, or other collection,
iteration \MUST{} occur in a specified total order. The
following total orders are normative:
\begin{itemize}
\item Typed identifiers (\texttt{EventId},
\texttt{PitchId}, \texttt{VoiceId}, etc.): lexicographic
ascending on the identifier's canonical byte form
(16 bytes: 8-byte replica, 8-byte counter, big-endian).
\item Operation envelopes: causal order first (from DVV
closure), then HLC physical time, then HLC logical
counter, then \texttt{OperationId} as final tie-break.
\item Chunk references: ascending by \texttt{ChunkKind}
discriminant, then by content hash lexicographic, then by
file offset.
\item Conflict records: ascending by \texttt{ConflictId}.
\item Extension declarations: ascending by
\texttt{ExtensionId}, then by semantic version
lexicographic.
\item Strings (text fields): byte-lexicographic on the UTF-8
NFC representation (Section~\ref{sec:det:text}).
\item Rationals (\texttt{RationalTime} sequences): by
numeric value ascending.
\item Composite keys: lexicographic on the canonical
representation of the key tuple's component fields, in
declaration order.
\end{itemize}
Hash-map iteration order, B-tree implementation differences,
and similar implementation artifacts \MUSTNOT{} leak into
canonical output.
\end{requirement}
\section{Text and Unicode}
\label{sec:det:text}
Locale-dependent text behavior is a routine source of canonical
divergence. The specification fixes this at the encoding layer.
\begin{requirement}
\label{req:determinism:unicode-canonicalization}
Canonical text fields \MUST{} be encoded as UTF-8 with Unicode
NFC (Normalization Form Canonical Composition) applied. Two
texts whose NFC byte representations are equal are canonically
equal regardless of decomposition state.
Comparisons of canonical text fields for identity \MUST{} be
byte comparisons of NFC-encoded UTF-8. User-facing display
collation (sorting by locale rules, case-insensitive matching,
diacritic folding) is non-canonical and \MUSTNOT{} affect
canonical state.
Locale settings \MUSTNOT{} affect:
\begin{itemize}
\item Parsing of canonical text projections.
\item Serialization of canonical text fields.
\item Sorting of canonical map keys.
\item Decimal formatting in canonical text projections (the
text projection uses a fixed decimal-point convention;
locale comma-vs-period preferences are irrelevant).
\item Date and time formatting in canonical data (canonical
timestamps use a fixed format; locale formats are for UI
only).
\end{itemize}
Two implementations operating under different locale settings
on the same canonical document \MUST{} produce identical
canonical output, including identical chunk hashes and
identical text projections.
\end{requirement}
\section{Randomness and Parallelism}
\label{sec:det:rng}
\subsection{Randomness}
\begin{requirement}
\label{req:determinism:canonical-random-seeds}
Randomness \MUSTNOT{} affect canonical output unless the
random seed is an explicit canonical input.
Algorithms whose results enter canonical state and that use
pseudorandom generation (e.g., probabilistic constraint solvers,
randomized rounding) \MUST{} consume their seed as a declared
algorithm parameter that becomes part of the algorithm's
conformance configuration. Identical seed plus identical input
yields identical output.
Identifier minting (\texttt{ReplicaId} initialization) is the
only conforming use of platform-level randomness in this
specification, and even there the random output enters
canonical state only via the resulting identifiers.
\end{requirement}
\subsection{Parallelism}
\begin{requirement}
\label{req:determinism:parallel-baseline-equivalence}
Parallel implementations of canonical algorithms \MUST{}
produce results identical to a strict single-threaded baseline.
Race timing, work-stealing order, and thread count \MUSTNOT{}
affect canonical state.
Where parallel reductions are used (summation over many
contributions, parallel constraint evaluation), the merge step
\MUST{} be deterministic: ordered reduction over a canonical
ordering of contributions, not arbitrary tree-merge order.
Implementations \MAY{} use parallel execution as a performance
optimization. They \MAY NOT{} use it as a license for
nondeterminism.
\end{requirement}
\section{Compression and File Bytes}
\label{sec:det:compression}
\begin{requirement}
\label{req:determinism:compression-independent-identity}
Canonical content identity is the uncompressed payload bytes
with the domain-separated preimage
(Chapter~\ref{ch:format}). Compression algorithm choice and
compression level \MUSTNOT{} affect canonical identity.
Identical uncompressed content has identical
\texttt{ContentHash} regardless of how it was physically
compressed.
Canonical chunk payload encoding \MUST{} be deterministic: the
same in-memory canonical structure \MUST{} produce identical
uncompressed bytes across runs and across implementations,
within the same schema version. The Binary Format companion
specification defines the canonical encoding.
Compression bytes (the physical bytes stored on disk after
zstd or another algorithm is applied) are \emph{not} canonical
unless a profile explicitly requires bit-identical bundle
reproduction. Implementations \MAY{} recompress chunks (e.g.,
during compaction or repack) without altering canonical state.
Readers \MUST{} verify decompressed payload against the
declared content hash before treating the payload as
authoritative.
\end{requirement}
\section{Open Algorithm Hooks}
\label{sec:det:open}
Two algorithms remain open questions in the body of the
specification: tempo integration for arbitrary curves
and root-finding for \texttt{wallclock\_to\_musical}
(Chapter~\ref{ch:time}). These algorithms affect canonical
state. The spelling pre-pass (Chapter~\ref{ch:pitch}) and the
notational decomposition algorithm (Chapter~\ref{ch:time})
were resolved in Pass~12 to the profile-declared disposition
below, each with a ratified default identifier
(Requirements~\ref{req:pitch:spelling-algorithm}
and~\ref{req:time:decomposition-algorithm}).
\begin{requirement}
\label{req:determinism:canonical-algorithm-registration}
Any algorithm that affects canonical state \MUST{} satisfy one
of:
\begin{itemize}
\item It is specified normatively in this document or a
named companion specification. Conforming implementations
\MUST{} implement the specified algorithm.
\item It is profile-declared: the active conformance profile
names a specific algorithm by versioned identifier. Two
conforming implementations on the same profile use the
same algorithm. Profiles may differ in their algorithm
choices.
\item It is explicitly marked non-canonical or advisory: its
output appears in the score only in non-canonical layers
(caches, diagnostics, user-facing rendering hints) and
\MUSTNOT{} appear in canonical chunks.
\end{itemize}
Implementations \MUSTNOT{} introduce algorithms affecting
canonical state outside of these three categories. ``Vendor's
spelling preference'' or ``proprietary decomposition heuristic''
\MUSTNOT{} silently determine canonical content.
\end{requirement}
\section{Conformance Statement}
\label{sec:det:conformance}
\begin{requirement}
\label{req:determinism:conformance-disclosure}
Conforming implementations \MUST{} declare, as part of their
published conformance statement:
\begin{itemize}
\item Their platform and floating-point library, with the
libm version or vendored math library reference.
\item Their parallel execution strategy and the demonstrated
equivalence to single-threaded baseline.
\item Their handling of the \texttt{-0.0} canonicalization
and NaN/infinity rejection.
\item Their adherence to round-to-nearest-ties-to-even.
\item Their compliance with the canonical iteration orders
in Section~\ref{sec:det:ordering}.
\item Their NFC normalization implementation reference.
\item Their declared algorithms for any open-question area
(spelling, decomposition, tempo curves), with versioned
identifiers.
\end{itemize}
\end{requirement}
% ===========================================================================
\chapter{Canonical Byte-Layout Reference}
\label{app:bytes}
\providecommand{\bul}{\_\discretionary{}{}{}}% breakable underscore for long identifiers
\providecommand{\cwb}{\discretionary{}{}{}}% zero-width break for long camelCase identifiers
\section{Status of this reference}
This appendix consolidates, in one place, every byte layout pinned by
the Pass 11 ratification: the discriminant tables, derivation
preimages, and primitive encodings that the core, operations, and
bundle layers already implement and lock with golden-bytes tests. Its
purpose is to give the Binary Format companion
(Section~\ref{sec:format:binary}) a single point to import, rather than
re-deriving these layouts from three separate crates.
This reference is a \emph{consolidation, not a second source of truth.}
Every entry cites the requirement that defines it normatively; where
this appendix and a cited requirement appear to disagree, the cited
requirement governs. The appendix introduces no obligation beyond what
its citations already impose. The layouts that Pass 11 deliberately
left to the interchange companions are listed in
Section~\ref{sec:bytes:deferred} so the boundary is explicit.
\section{Shared encoding conventions}
\label{sec:bytes:conventions}
All composite canonical encodings follow the convention baseline that
the Binary Format companion inherits
(Requirement~\ref{req:format:codec-conventions}): little-endian
integers; a boolean as a single \texttt{0}/\texttt{1} byte; \texttt{u32}
little-endian counts and a length prefix on every variable-width leaf;
a tagged union as a single discriminant byte followed by its variant
payload; and free-text strings as length-prefixed UTF-8 that the codec
does \emph{not} NFC-fold (catalog identifiers are NFC-normalized at
construction, not in the codec). Certain hashing preimages deliberately
depart from the single-discriminant-byte convention where a wider or
order-bearing tag is required --- notably the \texttt{TypedObjectId}
16-bit big-endian discriminant; such departures are pinned at their
definitions and noted in the tables below.
\section{System-derived identifier function}
\label{sec:bytes:system-derived}
Several identifiers below are content-addressed rather than
counter-allocated. They share one function
(Section~\ref{sec:graph:system-derived}): the 64-bit counter of a
\texttt{ReplicaId::SYSTEM\_DERIVED}
(\texttt{0xffff\_ffff\_ffff\_ffff}) identifier is
$\mathrm{BE\_u64}(\mathrm{BLAKE3}(\texttt{domain\_tag} \Vert
\texttt{canonical\_inputs})[0..8])$. The domain tag is exactly eight
bytes. The reserved built-in tags \emph{for this derivation} are the
closed set \{\texttt{MUSCSVCE}, \texttt{MUSCSPCH}, \texttt{MUSCSANM}\}
--- the three system-derived-identifier tags only; the other reserved
domain tags catalogued in Section~\ref{sec:bytes:tags} feed plain
hashing preimages, not this counter. Tags introduced by registered
extensions \MUST{} begin with \texttt{MUSCS}, be exactly eight bytes,
and not collide with the reserved three.
\section{Discriminant tables, derivations, and encodings}
\label{sec:bytes:tables}
\subsection{Object-graph identity (canonical document state)}
\begin{longtable}{>{\raggedright\arraybackslash}p{3.5cm} >{\raggedright\arraybackslash}p{6.8cm} p{2.2cm}}
\toprule
\textbf{Layout} & \textbf{Canonical bytes / preimage} &
\textbf{Requirement} \\
\midrule
\endhead
\texttt{TypedObjectId} tag &
16-bit big-endian discriminant (\texttt{0}--\texttt{27}) $\Vert$
variant payload. \texttt{Registered} $= 27$ encodes
\texttt{reg}~(16 BE) $\Vert$ \texttt{raw}~(16 BE), 34 bytes total.
\texttt{ObjectKindRegistryId} is 128-bit &
\ref{req:graph:typed-object-id-discriminants} \\[2pt]
Promoted \texttt{VoiceId} &
System-derived under \texttt{MUSCSVCE} over a 64-byte preimage:
\texttt{staff\_instance} $\Vert$ \texttt{original\_voice} $\Vert$
\texttt{winning\_op} $\Vert$ \texttt{losing\_op}, each 16-byte
big-endian &
\S\ref{sec:graph:promoted-voices} \\[2pt]
System-derived \texttt{PitchId} &
System-derived under \texttt{MUSCSPCH} over the intrinsic identity
(scale position, acoustic realization); variable-width strings
length-prefixed and NFC-normalized at the boundary; the tuning
reference is always present (\texttt{Inherit} as a distinct presence
marker) &
\ref{req:graph:system-derived-pitch-id} \\[2pt]
\texttt{Integrity\cwb AnomalyId} &
System-derived under \texttt{MUSCSANM} over
\texttt{Integrity\cwb AnomalyKind::\cwb to\bul canonical\bul bytes()} &
\ref{req:graph:integrity-anomaly-id} \\[2pt]
\texttt{ObjectKind} discriminant &
Single byte: \texttt{Voice} $= 0$, \texttt{Pitch} $= 1$,
\texttt{Registered} $= 2$. Enters the \texttt{IntegrityAnomalyId}
preimage &
\ref{req:graph:object-kind-vocab} \\
\bottomrule
\end{longtable}
\subsection{Bundle layer (chunk store, manifest, blobs)}
\begin{longtable}{>{\raggedright\arraybackslash}p{3.5cm} >{\raggedright\arraybackslash}p{6.8cm} p{2.2cm}}
\toprule
\textbf{Layout} & \textbf{Canonical bytes / preimage} &
\textbf{Requirement} \\
\midrule
\endhead
Chunk content hash &
BLAKE3 of \texttt{domain\bul tag} $\Vert$ \texttt{ChunkKind} byte
$\Vert$ \texttt{SchemaVersion} (4) $\Vert$
\texttt{uncompressed\bul length} (u64 LE) $\Vert$ \texttt{payload};
tag \texttt{MUSCCHNK} for chunks, \texttt{MUSCMANI} for the manifest.
Compression is \emph{not} in the preimage &
\S\ref{sec:format:hashing} \\[2pt]
\texttt{ChunkKind} &
Single declaration-order byte, \texttt{0}--\texttt{8} (in the chunk
hash preimage, so stable) &
\ref{req:format:chunkkind-discriminants} \\[2pt]
\texttt{Compression\cwb Algorithm} &
Fixed two bytes: discriminant $\Vert$ parameter. \texttt{None} $=$
\texttt{[0,0]}, \texttt{Zstd\{level\}} $=$ \texttt{[1,level]},
\texttt{Reserved(v)} $=$ \texttt{[2,v]}. Not part of chunk identity &
\ref{req:format:chunkkind-discriminants} \\[2pt]
\texttt{SchemaVersion} &
\texttt{major}~(u16 LE) $\Vert$ \texttt{minor}~(u16 LE), four bytes &
\S\ref{sec:format:hashing} \\[2pt]
\texttt{BlobId} &
Bare BLAKE3 of \texttt{MUSCBLOB} $\Vert$
\texttt{uncompressed\bul payload}: the domain tag immediately
followed by payload, with no kind, schema, or length fields &
\ref{req:format:blob-hash-shape} \\[2pt]
\texttt{ManifestId} &
\texttt{trunc128} (leading 16 bytes) of BLAKE3 of \texttt{MUSCMNIF}
$\Vert$ \texttt{document\bul id} (16) $\Vert$ \texttt{generation}
(u64 LE) $\Vert$ \texttt{manifest\bul body}; the
\texttt{manifest\bul id} field is excluded from the body &
\ref{req:format:manifest-id} \\[2pt]
\texttt{ProfileId} &
Fixed 20 bytes: discriminant (u32 LE; \texttt{Full} $= 0$,
\texttt{ReadOnly} $= 1$, \texttt{Lite} $= 2$, \texttt{Custom} $= 3$)
$\Vert$ \texttt{ProfileRegistryId}~(16, zero unless \texttt{Custom}).
Load-bearing in superblock selection &
\ref{req:format:profileid-discriminants} \\
\bottomrule
\end{longtable}
\subsection{Operation layer (transaction metadata)}
\begin{longtable}{>{\raggedright\arraybackslash}p{3.5cm} >{\raggedright\arraybackslash}p{6.8cm} p{2.2cm}}
\toprule
\textbf{Layout} & \textbf{Canonical bytes} & \textbf{Requirement} \\
\midrule
\endhead
\texttt{Transaction\cwb Category} &
Single byte: \texttt{NoteEntry} $= 0$, \texttt{Structural} $= 1$,
\texttt{Layout} $= 2$, \texttt{Import} $= 3$, \texttt{Registered}
$= 4$ &
\ref{req:semops:transaction-category} \\[2pt]
\texttt{ResolutionAction} &
Single byte: \texttt{AcceptLoser} $= 0$, \texttt{KeepWinner} $= 1$,
\texttt{Override} $= 2$, \texttt{Reanchor} $= 3$, \texttt{Dismiss}
$= 4$, \texttt{Registered} $= 5$. Encoded into the operation content
hash &
\ref{req:semops:resolution-action-discriminants} \\
\bottomrule
\end{longtable}
\subsection{Primitive scalar encodings}
\begin{longtable}{>{\raggedright\arraybackslash}p{3.5cm} >{\raggedright\arraybackslash}p{6.8cm} p{2.2cm}}
\toprule
\textbf{Layout} & \textbf{Canonical bytes} & \textbf{Requirement} \\
\midrule
\endhead
\texttt{RationalTime} &
Sign byte (\texttt{0} zero, \texttt{1} positive, \texttt{2} negative)
$\Vert$ numerator magnitude (u32-LE length-prefixed, big-endian)
$\Vert$ denominator magnitude (u32-LE length-prefixed, big-endian).
Always stored reduced (lowest terms, positive denominator), so equal
rationals encode to equal bytes &
\ref{req:format:rationaltime-encoding} \\[2pt]
Wall-clock integers &
Fixed-width little-endian (matching \texttt{QuantizedCoord}) &
\ref{req:format:rationaltime-encoding} \\
\bottomrule
\end{longtable}
\section{Domain-tag registry}
\label{sec:bytes:tags}
Every reserved built-in eight-byte domain tag, in one place. The nine
\emph{canonical} tags ---
whose preimages produce identifiers and content hashes that are part of
the interoperable, durably persisted form every conforming
implementation must reproduce bit-identically --- are
listed first; \texttt{MUSCFNTM} and \texttt{MUSCLOID} are
\emph{non-canonical} (layout-only) and listed last, for completeness
(see Section~\ref{sec:bytes:deferred}). Registered extensions may
additionally mint \emph{system-derived} tags, which \MUST{} begin with
\texttt{MUSCS}, be exactly eight bytes, and not collide with a reserved
tag (Section~\ref{sec:bytes:system-derived}).
\begin{longtable}{>{\raggedright\arraybackslash}p{2.6cm} >{\raggedright\arraybackslash}p{10.4cm}}
\toprule
\textbf{Tag} & \textbf{Use} \\
\midrule
\endhead
\texttt{MUSCCHNK} & Chunk content-hash preimage
(\S\ref{sec:format:hashing}) \\
\texttt{MUSCMANI} & Manifest chunk content-hash preimage
(\S\ref{sec:format:hashing}) \\
\texttt{MUSCBLOB} & Blob identity, bare preimage
(Requirement~\ref{req:format:blob-hash-shape}) \\
\texttt{MUSCMNIF} & Manifest-id derivation
(Requirement~\ref{req:format:manifest-id}) \\
\texttt{MUSCCONF} & \texttt{ConflictId} derivation
(\S\ref{sec:semops:conflict-id}) \\
\texttt{MUSCENVH} & Operation-envelope hash (\texttt{EnvelopeHash})
(\S\ref{sec:semops:equivocation}) \\
\texttt{MUSCSVCE} & System-promoted voice id
(\S\ref{sec:graph:promoted-voices}) \\
\texttt{MUSCSPCH} & System-derived pitch id
(Requirement~\ref{req:graph:system-derived-pitch-id}) \\
\texttt{MUSCSANM} & Integrity-anomaly id
(Requirement~\ref{req:graph:integrity-anomaly-id}) \\
\texttt{MUSCFNTM} & Font-metrics hash for layout conformance ---
\emph{non-canonical} (\S\ref{sec:layoutir:catalog-identity}) \\
\texttt{MUSCLOID} & Layout-object id --- \emph{non-canonical},
wired by the reference implementation as of P12-I2
(Requirement~\ref{req:layoutir:object-id-derivation}) \\
\bottomrule
\end{longtable}
\section{Reference-implementation locks}
\label{sec:bytes:goldens}
Each ratified layout above is anchored by a test in the reference
implementation that fails deliberately if the layout drifts. Most are
\emph{golden-bytes} tests pinning the literal encoding; the two noted
exceptions (\texttt{BlobId} and \texttt{RationalTime}) are anchored by
round-trip and canonicalization tests, which catch a drift in decode or
normalized form but not every literal-byte change. These anchors make
the spec-to-code correspondence checkable.
\begin{longtable}{>{\raggedright\arraybackslash}p{3.7cm} >{\raggedright\arraybackslash}p{9.3cm}}
\toprule
\textbf{Layout} & \textbf{Anchoring test} \\
\midrule
\endhead
\texttt{TypedObjectId} &
{\footnotesize\texttt{epiphany-core/src/ids.rs}:
\texttt{typed\bul object\bul id\bul byte\bul form\bul is\bul locked}} \\
Promoted \texttt{VoiceId} &
{\footnotesize\texttt{epiphany-core/src/graph.rs}:
\texttt{promoted\bul voice\bul id\bul byte\bul form\bul is\bul locked}} \\
System \texttt{PitchId} &
{\footnotesize\texttt{epiphany-core/src/pitch.rs}:
\texttt{system\bul pitch\bul id\bul byte\bul form\bul is\bul locked}} \\
\texttt{Integrity\cwb AnomalyId} &
{\footnotesize\texttt{epiphany-ops/src/anomaly.rs}:
\texttt{integrity\bul anomaly\bul id\bul byte\bul form\bul is\bul locked}} \\
\texttt{ObjectKind} &
{\footnotesize\texttt{epiphany-ops/src/support.rs}:
\texttt{object\bul kind\bul discriminants\bul are\bul golden}} \\
\texttt{ChunkKind} &
{\footnotesize\texttt{epiphany-bundle/src/chunk.rs}:
\texttt{chunk\bul kind\bul discriminants\bul are\bul golden}} \\
\texttt{Compression\cwb Algorithm} &
{\footnotesize\texttt{epiphany-bundle/src/chunk.rs}:
\texttt{compression\bul algorithm\bul encoding\bul is\bul golden}} \\
\texttt{ProfileId} &
{\footnotesize\texttt{epiphany-bundle/src/superblock.rs}:
\texttt{profile\bul id\bul discriminants\bul are\bul golden}} \\
\texttt{ManifestId} &
{\footnotesize\texttt{epiphany-bundle/src/ids.rs}:
\texttt{manifest\bul id\bul is\bul content\bul derived\bul and\bul deterministic}} \\
\texttt{Transaction\cwb Category} &
{\footnotesize\texttt{epiphany-ops/src/payload.rs}:
\texttt{transaction\bul category\bul discriminants\bul are\bul golden}} \\
\texttt{ResolutionAction} &
{\footnotesize\texttt{epiphany-ops/src/conflict.rs}:
\texttt{resolution\bul action\bul discriminants\bul are\bul golden}} \\
\texttt{BlobId} &
{\footnotesize\texttt{epiphany-determinism/src/hash.rs}:
\texttt{ContentHash::of\bul blob} and its round-trip locks
\emph{(round-trip, not a literal-byte golden)}} \\
\texttt{RationalTime} &
{\footnotesize\texttt{epiphany-core/src/time.rs}:
\texttt{equal\bul rationals\bul encode\bul identically}
\emph{(canonical-equality lock, not a literal-byte golden)}} \\
\bottomrule
\end{longtable}
The \texttt{MUSCLOID} layout-object-id derivation
(Requirement~\ref{req:layoutir:object-id-derivation}), wired in P12-I2,
is anchored differently because it is \emph{non-canonical}: its ids
never enter document state or a content hash, so there is no durable
byte layout to golden-lock. It is instead pinned by a derivation
reference-lock test (\texttt{epiphany-layout-ir/src/provenance.rs}:
\texttt{stable\bul id\bul uses\bul the\bul ratified\bul muscloid\bul derivation}),
which fixes the tagged preimage; by the tag-spelling lock
(\texttt{epiphany-determinism/src/domain.rs}:
\texttt{exact\bul tag\bul spellings\bul match\bul spec}); and by the
renderer's provenance goldens, whose \texttt{data-prov} hex changes if
the derivation drifts.
\section{Layouts deferred to the companions}
\label{sec:bytes:deferred}
The following are intentionally \emph{not} ratified here; they remain
provisional pending the Binary Format companion and the Operation
Catalog, and are recorded so the companion authors know precisely which
layouts they own versus inherit:
\begin{itemize}
\item \texttt{OperationKindTag} / \texttt{OperationKind} discriminants
and the full per-operation payload encodings. The reference
implementation assigns provisional declaration-order discriminants
(\texttt{0}--\texttt{16}) locked only for mutual distinctness, not
to literal values; pinning the literal wire form is the
Operation Catalog's and Binary Format companion's job.
\item The full composite struct layouts --- whole-\texttt{Score},
operation envelopes, the manifest body's field order --- together
with schema-version wire evolution and varint details beyond the
convention baseline
(Requirement~\ref{req:format:codec-conventions}). The companion
inherits the baseline and formalizes these.
\end{itemize}
\chapter{Revision History}
\label{app:history}
\begin{longtable}{p{2cm} p{2.5cm} p{9cm}}
\toprule
\textbf{Date} & \textbf{Section} & \textbf{Change} \\
\midrule
\endhead
\today & All & Initial scaffold; chapter structure and front matter
established. \\
\today & Ch.~\ref{ch:pitch} & Pitch chapter written: layered pitch type,
scale position, acoustic realization, spelling subsystem with attachments,
precedence, pre-pass, and analytical layers. Chapter structure expanded:
pitch separated from time/tuning; semantic operations chapter added. \\
\today & Ch.~\ref{ch:time} & Time and duration chapter written: exact
rational time with inline-or-promoted representation, distinct position
and duration newtypes, wall-clock time as fixed-point nanoseconds, time
anchors, time signatures with explicit beat groups including irrational
meters, tempo maps with deterministic conversion, notational
decomposition pre-pass, tuplets as grouping objects, and three region
time models (metric, proportional, aleatoric). \\
\today & Ch.~\ref{ch:tuning} & Tuning systems and pitch spaces chapter
written: pitch spaces as analytical universes distinct from tuning
systems, position structures (chromatic, diatonic-over-chromatic, JI
lattice, registered), interval algebras, nominal and accidental
registries, SMuFL glyph references with versioning, tuning resolution
variants including adaptive tuning, score-level reference pitch,
hierarchical resolution rules, and the normative built-in catalog of
pitch spaces and tuning systems. \\
\today & Ch.~\ref{ch:graph} & Score graph chapter written: hybrid
tree-plus-cross-cutting topology, canvas-region-staff-voice-event
hierarchy with regions carrying time and content models, typed 128-bit
identifiers with replica-plus-counter generation, flat event arena,
complete event taxonomy (pitched, unpitched, rest, indeterminate,
trajectory, graphic, cue), cross-cutting registry partitioned by type,
graphic content with region-local coordinates and time bindings, parts
as projections separate from the canvas, analysis layers and views,
graph invariants, and required indexes. \\
\today & Ch.~\ref{ch:semops} & Semantic operations chapter written:
operation framework with dual command/delta layering, primitive and
compound operation distinction, transactions as atomic user units,
precondition classification (invariant vs. advisory) with strict
authoring and lenient replay modes, the normative re-anchoring rule
table, inverse-based undo with single-replica scope, semantic merge
semantics with five merge families, and representative operations
illustrating each major category. The full operation catalog is
deferred to a separate conformance specification. \\
\today & Ch.~\ref{ch:layout-ir} & Layout IR chapter written: pipeline
of four stages (LogicalLayoutIR, ConstrainedLayoutIR,
ResolvedLayoutIR, RenderIR-at-interface), shared provenance discipline,
spatial primitives in staff spaces with f32 precision, polymorphic
time axis trait with metric/proportional/aleatoric implementations,
composite logical objects flattened to glyph-level constrained
objects, spring-based horizontal and vertical layout, explicit
engraving decision recording, hard and soft override mechanism with
preservation rules, fine-grained dependency tracking with invalidation
rules, glyph catalog interface, and uniform region containers across
kinds. \\
\today & Ch.~\ref{ch:format} & File format chapter written: bundle
container structure with header, manifest, chunk store, and blob
store; custom schema-defined binary format with self-describing
extension envelope; content-addressed immutable chunks with manifest
as the only mutable index; multi-level chunking (score-level,
staff-region, locality-grouped cross-cutting, attachment chunks);
operation log as canonical source of truth with dotted version
vectors and hybrid logical clocks; snapshots as derivative caches;
schema versioning with lazy migration and forward compatibility;
format profiles (Full, ReadOnly, Lite); per-chunk zstd compression;
atomic write protocol; deterministic s-expression text projection;
streaming read guarantees. DRM and encryption explicitly excluded. \\
\today & Ch.~\ref{ch:solver} & Constraint-solver interface chapter
written: interface-and-contract specification rather than algorithm;
the solver trait with from-scratch and incremental modes; Cassowary-
style Required and Preferred constraint strengths; the constraint
family catalog (spring, collision, alignment, containment, break,
aesthetic, extension); the normative quality metric set (optical
spacing, collisions, page fill, casting-off, beam angle, slur
curvature, vertical balance, symbol density uniformity); Pareto-
bounded quality target with configurable slack and tie-breaking
weights for ambiguous cases; within-implementation byte-determinism
with cross-implementation Pareto equivalence; incremental solving
contract with propagation bounds; structured error reporting with
partial solutions on budget exhaustion; reference suite for
conformance; non-normative recommendation of Cassowary as a
starting algorithm. \\
\today & Chs.~\ref{ch:perf},~\ref{ch:extension} &
Performance Requirements chapter expanded from skeleton: reference
hardware profile (2026 desktop and tablet baselines), frame budgets
for interactive edits at p99, cold-open targets, memory budgets
separating core data from evictable caches, audio thread discipline
requirements crossing the core/audio boundary, constraint-solver
performance targets, file format performance targets, measurement
methodology, regression testing recommendations. Extension Points
chapter written from skeleton: consolidated catalog of every
extension point in the spec organized by chapter of origin
(notation grammar, tuning system, glyph, score graph, layout IR,
constraint solver, file format extensions), the extension registry
contract requiring versioning and identifier stability, plugin
runtime considerations, conformance interactions. \\
\today & Appendices & Glossary expanded with 50+ defined terms
cross-referenced to their originating chapters. Bibliography
populated with engraving treatises, music theory references,
tuning and microtonality sources, constraint-based layout
literature, CRDT references, and prior-art notation format
citations. \\
\today & All & Platform named \textit{Epiphany}. Typography refreshed
to TeX Gyre Pagella (body), Heros (sans), and Cursor (mono).
Color palette refined to deep teal, antique gold, warm parchment
neutrals, and deep crimson accents. Title page redesigned with
ornamental rules and an oldstyle classical feel. Status section,
introduction, and PDF metadata updated to reflect the platform's
name. \\
\today & Pass 1 revision (Chs.~\ref{ch:pitch},~\ref{ch:time},~\ref{ch:tuning},~\ref{ch:graph},~\ref{ch:semops}) &
Type-seam corrections in response to external review.
Introduced \texttt{IdentifiedPitch} wrapping every embedded pitch
with a stable \texttt{PitchId}; updated \texttt{PitchedEvent},
\texttt{TrajectoryEndpoint}, and \texttt{TrajectoryShape::Stepwise}
accordingly. Introduced \texttt{EventDuration} union covering
musical, wall-clock, and indeterminate cases, with concrete-only
\texttt{DurationBounds} avoiding recursive indeterminacy.
Aleatoric regions now declare an explicit
\texttt{AleatoricAnchoringDiscipline}. \texttt{PitchSpelling}
carries a stack of accidentals (\texttt{Vec<AccidentalId>}) with
normative additive semantics and duplicate-detection invariants,
replacing the prior \texttt{Option<AccidentalId>}.
\texttt{PitchSpacePosition} gains a \texttt{JiVector} variant for
first-class just-intonation lattice positions. The default CMN
pitch space is now correctly described as
\texttt{cmn-12} (diatonic-over-chromatic), not pure chromatic.
Chapter~\ref{ch:pitch}'s reference-pitch text rewritten to defer
to Chapter~\ref{ch:tuning}, resolving the prior ownership
contradiction. \texttt{MusicalPosition} and \texttt{MusicalDuration}
now derive \texttt{Clone}, not \texttt{Copy}, with a permitted
inline-or-pointer-tagged implementation noted as conforming.
\texttt{Tie::pitch\_pairing} now uses \texttt{Vec<(PitchId, PitchId)>}
instead of positional indices. The duplicate \texttt{AnalysisLayer}
definition in Chapter~\ref{ch:pitch} was removed in favor of the
authoritative definition in Chapter~\ref{ch:graph}. Graph
invariants expanded to cover identified pitches, tombstoning,
time-model agreement, and \texttt{PitchId}-pairing tie targets.
Representative operations (\texttt{InsertEvent},
\texttt{DeleteEvent}, \texttt{RespellPitch}, \texttt{Transpose})
updated for atomic ID minting, pitch tombstoning,
voice-promotion on concurrent insertion, and \texttt{PitchId}
preservation through transposition. Status downgraded to
``structural working draft.'' \\
\today & Pass 2 revision (Chs.~\ref{ch:time},~\ref{ch:graph},~\ref{ch:semops}) &
Graph and time invariants strengthened in response to external
review. \texttt{TimeAnchor} offsets generalized via the
\texttt{AnchorOffset} union (Musical, WallClock, Zero), with
normative agreement between offset variant and target region's
time model. \texttt{TempoSegment} restructured to carry explicit
\texttt{end: Option<TimeAnchor>}, \texttt{start\_tempo},
\texttt{end\_tempo: Option<Tempo>}, and \texttt{shape}; tempo
segments are required to be monotonic in musical time so that
wall-clock-to-musical conversion is single-valued. Polymeter
admitted natively: \texttt{StaffBasedContent} now carries
\texttt{Vec<StaffInstance>} (region-local), plus
\texttt{default\_metric\_grid: Option<MetricGrid>} and
\texttt{barline\_alignment\_groups: Vec<BarlineAlignmentGroup>}.
Measures and metric grids are per-staff-instance, not per-region.
Staff identity split into the global \texttt{Staff} (declared once
at score level) and the region-local \texttt{StaffInstance};
\texttt{Score} top-level structure expanded with
\texttt{staves: Vec<Staff>} and
\texttt{staff\_groups: Vec<StaffGroup>}.
\texttt{DeleteEvent} preconditions strengthened: target events
belonging to tuplets require explicit
\texttt{TupletCompensation} (\texttt{NotInTuplet},
\texttt{ReplaceWithRest}, \texttt{RewriteTuplets},
\texttt{CascadeDeleteTuplets}). Tie validation reframed from
universal immediate-adjacency to class-specific profiles:
\texttt{TieClass} (Standard, Editorial, CrossVoice,
LaissezVibrer, Registered) admits editorial, cross-voice, and
laissez-vibrer ties without relaxing standard-tie invariants.
System-promoted voices formalized: \texttt{VoiceOrigin} enum
(UserDeclared, Imported, SystemPromoted); promoted voices have
deterministically-derived \texttt{VoiceId}s, are first-class
graph objects, are visible to users, and are normalized only by
explicit user operation. Graph invariants expanded from 14 to 18
normative invariants reflecting staff-instance ontology,
per-staff measures, anchor-offset agreement, system-promoted
voice identity, and barline-alignment-group containment. New
identifier types: \texttt{StaffInstanceId},
\texttt{StaffGroupId}, \texttt{BarlineAlignmentGroupId},
\texttt{TimeSignatureId}. \\
\today & Pass 3 revision (Ch.~\ref{ch:semops},~\ref{ch:format}) &
Concurrent semantics rewritten from pairwise operation merge to
operation-set deterministic reduction. The replicated operation
set is now framed as a true CRDT (a grow-only set of envelopes);
the materialized score graph is its deterministic reduction, not
itself a CRDT. \texttt{OperationId} (replica + counter) separated
from \texttt{OperationStamp} (HLC + id); canonical reduction order
is causal-then-HLC-then-lexicographic. Causal contexts expressed as
dotted version vectors (\texttt{CausalContext}), not exhaustive
predecessor lists; exhaustive lists are explicitly forbidden as
canonical representation. \texttt{OperationEnvelope} introduced
with id, author, stamp, causal context, optional transaction,
payload. Four-phase operation lifecycle specified: Prepare,
Commit, Reduce, Report. Operation effects formalized:
\texttt{Applied}, \texttt{AppliedWithRepair}, \texttt{Conflicted},
\texttt{TombstonedTarget}, \texttt{NoOp\{reason\}}; every replica
reducing the same set records the same effects with the same
reasons. Conflict records made first-class graph objects in a
score-level \texttt{ConflictRegistry}: stable, addressable,
user-visible, resolvable by subsequent
\texttt{ResolveConflict} operations. Tombstones formalized with
explicit retention rules (pending replication, undo, conflict
resolution, attachment resolution). Re-anchoring rewritten as a
total deterministic function: ``nearest'' is now a strict
lexicographic minimum over (containment proximity, time distance,
direction preference, object id), with no implementation
discretion. Re-anchoring rule table updated for the new
staff-instance ontology and the deterministic ``nearest''
function. Transactions formalized as atomic replicated groups:
primitive members \MUSTNOT{} materialize as independently
visible intermediate states; failures conflict the whole
transaction. Undo redefined as a forward compensating operation
(\texttt{UndoTransaction}) with three policies
(\texttt{StrictInverse}, \texttt{BestEffort}, \texttt{Cascade});
bit-identical undo demoted to content equivalence. LWW reduction
restricted to explicitly-marked \texttt{LwwAdvisory} scalar
fields (metadata, view preferences, layout hints, system/page
break preferences, optional cross-cutting display hints);
structural musical fields \MUSTNOT{} default to LWW. Cache
invalidation rule: caches are acceleration only, never canonical;
invalidated on operation-set, causal-order, reduction-algorithm,
or profile change. Per-operation merge families renamed to
reduction rules and rewritten in the new model;
\texttt{ChangeRegionTimeModel} now correctly conflicts rather
than silently coercing incompatible contained events;
\texttt{SetUserSystemBreak} explicitly marked LwwAdvisory;
\texttt{RespellPitch} concurrent collisions preserve the losing
spelling in a structural-field-collision conflict record. New
identifier types: \texttt{ConflictId}, \texttt{TransactionId}.
Operation log restructured to store envelopes rather than
primitive operations with separate stored-sequence metadata. \\
\today & Pass 4 revision (Ch.~\ref{ch:format}) &
File-format restructured around Pass 3's operation-set
canonical-document model. The earlier ``single mutable manifest''
story replaced with a fixed 64-byte header plus two fixed-size
256-byte superblock slots; the superblocks are the only mutable
on-disk objects, and atomic commit is achieved by writing the
inactive superblock slot with generation $+ 1$. Active superblock
selected by highest valid generation. Generation gap greater than
one detected as integrity anomaly with read-only recovery mode.
Manifest reframed as a table of roots and declarations
(operation\_roots, retained\_snapshots, blob\_roots,
profile\_declarations, extension\_declarations, text\_projection
root, integrity\_root); user-facing metadata explicitly removed
from the manifest in favor of operation envelopes and reduced
state. BLAKE3 fixed as the single content-hashing algorithm,
replacing the prior ``BLAKE3 or SHA-256'' uncertainty. Hash
preimage domain-separated and explicitly excludes
\texttt{compression\_algorithm} to preserve deduplication across
compression choices. Operation-envelope blocks specified as the
canonical storage unit with a soft 1\,MiB uncompressed target
size and a default 64\,MiB upper bound per profile. Snapshots
separated into acceleration snapshots (caches, discardable) and
canonical-base snapshots (declared in
\texttt{retained\_snapshots}, required for pruning). Pruning
protocol specified: canonical state preserved as (base snapshot
$+$ post-frontier envelopes). Atomic write protocol specified
step-by-step with explicit durable-flush points; the durable
flush is platform-abstracted to cover \texttt{fsync},
\texttt{FlushFileBuffers}, and equivalents. Crash recovery rules
enumerated by failure point. Garbage collection specified as
conservative, optional, deferred; never on the commit critical
path. Edit barriers formalized as a structured first-class
concept (\texttt{EditBarrier} with scope, affected object kinds,
prohibited operation kinds, and condition), with an
unsafe-edit escape hatch that tombstones extension data rather
than silently corrupting it. Text projection reframed:
semantic round-trip required (envelopes, declarations, reduced
state); physical-layout fidelity (chunk offsets, compression
choices, cache chunks, garbage regions, superblock generation)
explicitly not required. Schema versioning, format profiles
(Full, ReadOnly, Lite, Custom), and compression sections updated
for the new architecture. Streaming-read cold-open procedure
rewritten to use snapshot $+$ envelope-stream model. \\
\today & Pass 5 revision (Ch.~\ref{ch:solver}) &
Solver conformance reframed from universal Pareto optimality to
reference-suite-based conformance with profile-specific
thresholds. The earlier ``no better Pareto layout exists''
obligation, which quantified over an undecidable space of
possible layouts, has been replaced with checkable, falsifiable
thresholds on a reference suite. Solver obligations split into
four classes: validity conformance (hard constraints, provenance,
unit system, invariants), determinism conformance, diagnostic
conformance (\texttt{SolveStatus}, \texttt{SolveReport}, warnings,
unsatisfied-constraint reporting, metric vectors), and quality
conformance (reference-suite thresholds). Three solver tiers
introduced (\texttt{Minimal}, \texttt{Standard}, \texttt{Advanced})
with declared obligations per tier; tiers are orthogonal to
file-format profiles. Deterministic budgets formalized
(\texttt{max\_iterations}, \texttt{max\_nodes},
\texttt{max\_constraint\_evaluations}); wall-clock time demoted
to advisory only. Wall-clock \MUSTNOT{} change the canonical
layout. Quality metrics normalized: \texttt{NormalizedMetric} is
a finite \texttt{f64} in $[0.0, 1.0]$ with $0.0$ best and $1.0$
worst; orientation and range fixed across all metrics including
extension metrics. Hard constraints explicitly separated from
quality metrics: a solver \MUSTNOT{} trade off a hard-constraint
violation for a better quality score. Within-implementation
determinism (byte-equal output for identical input + version)
separated from cross-implementation conformance (reference-suite
thresholds, not byte equality). \texttt{SolveReport} unified the
prior \texttt{Result<SolveOutput, SolverError>} return: every
invocation yields a report with a \texttt{SolveStatus}
(\texttt{Solved}, \texttt{SolvedWithWarnings},
\texttt{PartialBudgetExhausted}, \texttt{Unsatisfiable},
\texttt{InternalError}). \texttt{PartialBudgetExhausted}
preserves hard-constraint validity; budget exhaustion that
cannot satisfy hard constraints returns \texttt{Unsatisfiable}.
Six invalidation scopes specified (\texttt{ObjectLocal},
\texttt{MeasureLocal}, \texttt{SystemLocal}, \texttt{PageLocal},
\texttt{RegionLocal}, \texttt{WholeScore}); incremental solving
contract reframed as observational equivalence to scoped full
solving (solvers \MAY{} widen scopes conservatively but
\MAY NOT{} narrow them). Reference algorithm demoted from
``recommended starting point'' to explanatory and diagnostic
only; conforming implementations \MAY{} choose any algorithm.
Pass 4 rollback-language corrected: rollback is achieved by
retained manifests in the chunk store, not by retaining multiple
on-disk superblock generations (the fixed layout has only two
slots). \\
\today & Pass 6 revision (App.~\ref{app:determinism}) &
New Determinism Contract appendix consolidates reproducibility
obligations scattered across Chapters~\ref{ch:pitch}--\ref{ch:solver}
into a single normative reference. Five layers of determinism
distinguished: canonical score determinism (byte-equal
cross-implementation), canonical serialization determinism
(byte-equal chunk hashes and text projection), layout
determinism (byte-equal within implementation version),
conformance determinism (reference-suite-based, not byte-equal
cross-implementation), and non-canonical cache determinism
(caches may vary, MUST NOT affect canonical state). Canonical
floating-point values restricted to finite IEEE 754 binary64;
NaN and infinity prohibited from canonical chunks;
\texttt{-0.0} canonicalized to \texttt{+0.0} before storage and
hashing. Little-endian IEEE 754 binary64 serialization fixed.
IEEE 754 round-to-nearest, ties-to-even mandated for canonical
numerical algorithms; \texttt{-ffast-math} and equivalents
prohibited; reassociation, distribution, and implicit
fused-multiply-add forbidden where they could change canonical
output. Quantized layout coordinates introduced
(\texttt{QuantizedCoord} at $1/1024$ staff space) so canonical
\texttt{ResolvedLayoutIR} positions are stable across
implementations whose internal computations agree to within
half-quantum. Tolerance classes formalized
(\texttt{AcousticCents}, \texttt{LayoutCoordinate},
\texttt{QualityMetric}, \texttt{TempoIntegration},
\texttt{SolverResidual}) with governance category
(\texttt{Equality}, \texttt{Validation}, \texttt{Diagnostic});
ad-hoc epsilons prohibited; tolerances MUST NOT apply to
identity. Canonical iteration order specified for every
enumerable collection (identifiers lexicographic on canonical
byte form; operations causal-then-HLC-then-id; chunk references
by kind, hash, offset; conflicts by id; extensions by id then
version; strings byte-lexicographic on NFC UTF-8). Text
normalization fixed at UTF-8 NFC; locale prohibited from
affecting canonical parsing, serialization, sorting, decimal
formatting, or projection. Randomness MUST NOT enter canonical
state unless the seed is an explicit canonical input.
Parallelism permitted but MUST produce results identical to a
strict single-threaded baseline through deterministic ordered
reductions. Compression confirmed as non-canonical (identical
uncompressed content has identical hash regardless of
compression choice); recompression permitted without altering
canonical state. Open algorithm hooks (spelling pre-pass,
decomposition, tempo integration, root-finding) required to be
either normatively specified, profile-declared by versioned
identifier, or explicitly non-canonical/advisory; vendor
freedom over canonical algorithms prohibited. Conformance
statement enumerates the required disclosures implementations
must publish (libm, parallelism strategy, NFC implementation,
declared algorithms for open-question areas). Status section
updated to reference Appendix~\ref{app:determinism}. \\
\today & Pass 7 revision (editorial, all chapters) &
Editorial consistency pass: no architectural changes, only
cleanup. Resolved the contradiction between
Appendix~\ref{app:determinism}'s ``byte-equal canonical state
cross-implementation'' and the existence of open canonical
algorithms by adding an explicit conditional on the disposition
of Section~\ref{sec:det:open}. Stale section-number cross-
references replaced with symbolic labels (Section~2.4 $\to$
\texttt{sec:pitch:spelling}; Section~2.4.4 $\to$
\texttt{sec:pitch:precedence}; Section~5.9.3 $\to$
\texttt{sec:graph:timebinding}; Section~6.6 $\to$
\texttt{sec:semops:conflicts}; Section~6.8 $\to$
\texttt{sec:semops:undo}). Terminology normalized:
``merge family'' $\to$ ``reduction rule'' where the operation
framework was meant; ``Pareto-bounded quality model'' $\to$
reference-suite/normalized-metric language in extension points;
glossary entries for Operation log, Operation set, Operation
envelope, Canonical reduction, Pareto frontier (design target),
Staff, Staff instance, Conflict record, CRDT, Snapshot, and
Transaction rewritten or added. New Appendix~\ref{app:deferred}
(Intentionally Deferred Types and Specifications) catalogs
companion specifications (Binary Format, Text Projection,
Profile Conformance, Quality Metric Catalog, Reference Suite,
Performance Reference Suite, Operation Catalog, Reference
Algorithm), externally provided components (SMuFL, font metric
provider, audio engine, plugin runtime, collaboration
transport, user interface), open canonical algorithms, and
extension registry catalogs; distinguishes deliberately external
content from accidentally missing content. Status section
rewritten to reflect structural stabilization: substantive
review passes complete, architecture not under active redesign,
remaining blockers are companion specifications and open
canonical algorithms; the section enumerates what conformance
can be established now and what awaits companion delivery.
Revision history entries for Passes 1--7 read as the document's
architectural-stabilization log, consolidating the design
decisions taken in response to external review. \\
\today & Pass 8 revision (post-review correctness pass) &
Determinism correctness pass addressing review of the
Pass 7 output. \texttt{ConflictId} reframed as content-derived
via BLAKE3 truncation with domain tag \texttt{"MUSCCONF"},
fixing a determinism leak where each replica would have minted
its own conflict identifiers from local replica-counter
sequences; conflict records produced during reduction now have
identifiers that agree across replicas by construction.
\texttt{RepairRecord} defined (was referenced by
\texttt{OperationEffect::AppliedWithRepair} without
definition), with a \texttt{RepairKind} enumeration covering
\texttt{Reanchored}, \texttt{SpannerTruncated},
\texttt{Orphaned}, \texttt{CascadeDeleted},
\texttt{AttachmentTombstoned}, \texttt{VoicePromoted},
\texttt{TupletCompensated}, and \texttt{Registered}.
\texttt{TypedObjectId} defined as a tagged union over every
identifier kind in the score graph, with canonical-bytes
serialization for hashing/ordering/equality.
\texttt{OperationKind} defined as a tagged enumeration of
primitive operation variants, plus a \texttt{Registered}
variant for extension-defined primitives. System-derived
identifier namespace formalized:
\texttt{ReplicaId::SYSTEM\_DERIVED} reserved at
\texttt{0xffff\_ffff\_ffff\_ffff}, with the 64-bit counter
derived deterministically via BLAKE3 truncation; domain tags
for system identifiers (\texttt{"MUSCSVCE"} for
system-promoted voices, \texttt{"MUSCSPCH"} for system-derived
pitches) enumerated; user-authored replicas \MUSTNOT{} use
the reserved value. Envelope-acceptance rules added: a
well-formed envelope requires non-system replica id, finite
physical time, well-formed DVV, deserializable payload;
per-replica monotonicity enforced; clock-skew warnings allowed
but reordering for plausibility prohibited; authentication
deferred to collaboration transport. Canonical-base snapshot
semantics repaired: manifest split into single
\texttt{canonical\_base: Option<SnapshotRef>} plus
\texttt{acceleration\_snapshots: Vec<SnapshotRef>}; coverage
test specified as DVV-frontier membership, explicitly
forbidding HLC-stamp comparison for canonicality determination;
pruning replaces the canonical base atomically. Header CRC
moved to the last 4 bytes of the 64-byte fixed header,
resolving a circular-definition bug where the CRC field
appeared inside its own coverage range. Manifest chunks
\MUST{} be stored uncompressed in this format version,
resolving the bootstrap chicken-and-egg problem (a reader
needs to know how to decode the manifest before it has any
manifest information). \texttt{ChunkId} defined as a newtype
around \texttt{ContentHash}, clarifying the prior comment that
the hash and id were redundant. Three-layer identity model for
documents: \texttt{file\_uuid} (physical bundle, changes on
Save As), \texttt{document\_id} (logical work, persists across
Save As by default), optional \texttt{lineage\_id} (shared
ancestor for genealogy/version-control purposes); Save As
semantics specified normatively. \texttt{RetentionPolicy}
defined as a first-class concept with
\texttt{retain\_previous\_manifests},
\texttt{retain\_duration}, \texttt{retain\_named\_checkpoints};
rollback achieved via retained manifests in the chunk store,
not multiple on-disk superblock generations.
\texttt{BarrierCondition} defined (\texttt{Always},
\texttt{ObjectExists}, \texttt{ObjectHasExtensionData},
\texttt{All}, \texttt{Any}, \texttt{Not}, \texttt{Registered}),
making the edit-barrier model implementable for unknown
extensions. \texttt{GlyphCatalogIdentity} defined in
Chapter~\ref{ch:layout-ir}, required for any byte-equal-output
layout conformance claim; without it, layout output is
implementation-specific. \texttt{Box<dyn TimeAxis>} replaced
with \texttt{TimeAxisModel} enum (Metric, Proportional,
Aleatoric, Registered), giving the layout IR a canonical
representation for serialization, hashing, and conformance
comparison while still permitting internal trait-object
dispatch. Performance chapter restructured around an explicit
core/product/Performance-Reference-Suite obligation split:
core obligations bound the core's own work (reduction, layout,
file writes, snapshot acquisition), while end-to-end UI/audio
targets are product-layer obligations specified in companion
documents. Editorial sweeps: remaining ``merge semantics''
prose in Chapter~\ref{ch:intro}, the compound transpose
primitive, the \texttt{VoiceOrigin::SystemPromoted} comment,
and the extension-point design principles updated to
``reduction semantics'' or ``reduction rules''; tempo curve,
decomposition, and spelling pre-pass open questions updated
to reference Appendix~\ref{app:determinism}
Section~\ref{sec:det:open} dispositions. Glossary additions:
Edit barrier, Glyph catalog identity, Operation kind, Repair
record, Retention policy, Typed object identifier, Document
identifier (with cross-references to FileUuid and LineageId).
Reading Guide canonical-vs-non-canonical table updated to
reflect the canonical\_base / acceleration\_snapshots split. \\
\today & Pass 9 revision (correctness and consistency pass) &
Operation-envelope duplicate handling formalized into three
outcomes: \texttt{IdempotentDuplicate} (same id, byte-identical
envelope: silently dropped), \texttt{FirstAcceptance} (new id:
accepted after well-formedness check), and
\texttt{EnvelopeEquivocation} (same id, different canonical
bytes: divergent envelope rejected, integrity diagnostic
surfaced, bundle non-conforming if equivocation appears
on-disk). Well-formedness invariant
\texttt{envelope.stamp.id == envelope.id} made normative. HLC
monotonicity reframed as a property of the set of accepted
envelopes, not arrival order: out-of-order delivery is fine;
on detection of inconsistency, both envelopes are retained but
the offending replica's stream from the violating counter
onward is quarantined and the document is flagged with a
replica-equivocation anomaly. The free-form
\texttt{NoOpReason::PreconditionFailedUnderReduction { reason:
String }} replaced with a typed \texttt{PreconditionFailureReason}
enum, closing a determinism leak (free-form strings in
canonical state could otherwise differ across implementations).
Transaction descriptor ordering integrated into canonical
reduction via causal dependency: every primitive member of a
transaction \MUST{} causally depend on its
\texttt{DeclareTransaction} envelope; members lacking such a
dependency reduce to \texttt{NoOp\{TransactionConflict\}} with
a synthesized conflict record. The fixed-header
\texttt{file\_uuid} requirement reworded to match the Pass 8
identity model: fixed for the lifetime of a physical bundle,
preserved by filesystem byte-copy, freshly minted on Save As.
Manifest \texttt{integrity\_root} made \texttt{Option<ChunkRef>}
(it is an accelerator); new ``Canonical and Non-Canonical
Manifest Roots'' subsection partitions the manifest's fields
by their role in canonical state, with the rule that failed
verification of non-canonical chunks is rebuildable rather
than corruption. Cold-open procedure rewritten to reflect the
canonical-base / operation-envelope-block model (the prior
text retained pre-Pass-3 region-chunks language). Streaming-
read locatability list updated. System-derived counter
collisions: mandatory collision check during reduction;
collision becomes hard corruption via new
\texttt{ConflictKind::SystemIdentifierCollision} record naming
both colliding canonical-input sets. \texttt{GlyphCatalogIdentity}
\texttt{metrics\_hash} scope broadened: hash covers glyphs
referenced by the \texttt{ConstrainedLayoutIR} (solver inputs),
not just glyphs in the final \texttt{ResolvedLayoutIR}.
\texttt{CommitState} validation added to superblock selection:
non-\texttt{Committed} superblock is not valid for ordinary
selection. Schema-version fallback refined into three cases by
chunk role: canonical chunks with unsupported major schema
require read-only preservation mode (or refusal); non-canonical
chunks may be opaquely preserved; unknown canonical operation
payloads require extension support or open read-only without
materialization. New \texttt{OperationKindTag} (discriminator-
only enum) introduced for edit barriers, conformance reports,
and diagnostic surfaces; \texttt{EditBarrier} switched to
\texttt{prohibited\_operation\_kinds: Vec<OperationKindTag>}.
``Absolute musical positions \MUSTNOT{} appear in stored
data'' rule narrowed: stored \emph{references to external
time points} use \texttt{TimeAnchor}; events' \emph{own
positions} within their owning voice and region may be stored
as \texttt{EventPosition}. New Appendix~\ref{app:deferred}
Section~\ref{sec:deferred:support-types} catalogs the semantic
identity rules for \texttt{FileUuid}, \texttt{DocumentId},
\texttt{LineageId}, \texttt{ManifestId}, \texttt{SnapshotId},
\texttt{BlobId}, \texttt{AuthorId},
\texttt{ReductionAlgorithmVersion}, \texttt{SolverProfile},
\texttt{ProfileId}, \texttt{ContentHash}, \texttt{ChunkId},
\texttt{OperationId}, \texttt{TransactionId},
\texttt{ConflictId}, and the typed identifier family.
Editorial: \texttt{MUSTNOT} and \texttt{SHOULDNOT} macros
switched to \texttt{nobreakspace} so \texttt{pdftotext}
extracts ``MUST NOT'' as two tokens; Manifest, Operation log,
and Snapshot glossary entries updated to match the
\texttt{canonical\_base} / \texttt{acceleration\_snapshots}
manifest field names. \\
\today & Pass 10 revision (order-independence and integrity-
anomaly cleanup) &
\texttt{OperationId} equivocation reworked from a first-arrival
rule (which made canonical state arrival-order-dependent across
replicas observing the same envelopes in different orders) to an
order-independent \texttt{OperationSlot} model. The slot is
either \texttt{Single(OperationEnvelope)} or
\texttt{Equivocated{} \{ operation\_id, candidates:
BTreeSet<EnvelopeHash> \}}; observing a second distinct
canonical envelope under an \texttt{OperationId} transitions
the slot to \texttt{Equivocated} regardless of arrival order.
Equivocated slots produce no canonical reduction effect; both
candidates are retained for diagnostic recovery. Operations
causally depending on an equivocated id are held pending
resolution. Resolution is by explicit policy: transport-level
credential revocation, local user reconciliation, or a
profile-declared deterministic selection function. No
arrival-order default applies. HLC monotonicity-anomaly
handling sharpened: a new \texttt{AnomalousReplicaSegment}
type captures the offending replica, the first violating
counter, the reason, and the excluded envelope ids. Operations
from the violating counter onward are retained but
\emph{excluded from canonical reduction}; canonical state is
derived only from envelopes that satisfy the monotonicity
invariant. The earlier ``quarantined'' wording is now backed
by an explicit structural exclusion rule. System-derived ID
collisions moved out of \texttt{ConflictKind} and into a new
\texttt{IntegrityAnomaly} type with kinds
\texttt{SystemIdentifierCollision},
\texttt{OperationSlotEquivocated}, and
\texttt{ReplicaStreamQuarantined}. The distinction is that
conflict records are ordinary canonical-state facts (a
representable musical conflict between two valid edits) while
integrity anomalies indicate the structural assumptions
underlying canonical state have failed and require external
recovery; an integrity anomaly takes the document out of
ordinary canonical operation. Stale ``refined in a later
revision'' parenthetical removed from the \texttt{DeleteEvent}
reduction rule; the re-anchoring total order is part of the
current architecture and needs no further refinement. Typo
fixed in the superblock-selection requirement
(\texttt{MAY{}-be-valid} replaced with the prose ``invalid for
ordinary selection but may be inspected in diagnostic recovery
mode''). Glossary chapter references for ``Dotted version
vector'' and ``Hybrid logical clock'' updated to ``Chapters
6, 8'' with explicit ``defined operationally in
Chapter~\ref{ch:semops}; serialized in
Chapter~\ref{ch:format}'' wording, reflecting that the
Pass 3 architectural shift made causal context part of
operation semantics rather than only file storage. \\
\today & Pass 11 revision (spec ratification) &
Converted the v0 implementation's provisional, golden-locked byte
choices into ratified normative spec text; the architecture is
unchanged. \emph{Adopt-and-pin (bytes):} pinned the
\texttt{TypedObjectId} 16-bit big-endian discriminant table
(\texttt{0}--\texttt{27}, \texttt{Registered}~=~27; added the
\texttt{Tuplet}/\texttt{RepeatStructure}/\texttt{LyricLine}/%
\texttt{ChordSymbol}/\texttt{View} variants the implementation
carried); the promoted-voice (\texttt{MUSCSVCE}), system-pitch
(\texttt{MUSCSPCH}, tuning always part of identity), and
integrity-anomaly (\texttt{MUSCSANM}, promoted to a reserved
built-in tag) derivations; the \texttt{ChunkKind} /
\texttt{ProfileId} / \texttt{CompressionAlgorithm} discriminants;
the \texttt{ManifestId} preimage (with \texttt{manifest\_id}
excluded); and the \texttt{RationalTime}/scalar primitive layouts
plus the shared codec convention baseline the Binary Format
companion inherits. \emph{Decide-and-pin:} \texttt{TempoShape::Linear}
interpolates \emph{speed} (whole notes/second), not bpm; a
\texttt{StructuralFieldCollision} tags the \emph{winner}
\texttt{Conflicted}; lifted the $>$2-way voice-promotion
generalization (partial-overlap, lowest-id-survivor) to normative;
pinned the \texttt{TransactionCategory} and \texttt{ObjectKind} core
vocabularies; added \texttt{ResolutionAction::Dismiss} so the
\texttt{Dismissed} resolution state is reachable by an authored
operation; and specified the (non-canonical) \texttt{LayoutObjectId}
derivation with a \texttt{MUSCLOID} tag as the Track~A target (then
deferred to Track~A; subsequently wired by the reference
implementation in P12-I2). \emph{Fixes:} blob hashing
is the bare \texttt{"MUSCBLOB" || payload} (deleted the
contradictory ``identically to chunks'' phrasing); added the
equal-generation superblock rule (\texttt{DivergentSameGeneration}
on load-bearing-field divergence); defined \texttt{ProfileConstraints}
and placed the required \texttt{RetentionPolicy} in it with
first-declared multi-profile precedence; made the DVV zero-based
counter floor normative; reconciled the graph-invariant count to 19
(was 18 in QUICKSTART) and named the three construction-time MUSTs
(time-signature beat-group sum, ordering-DAG acyclicity, and
non-degenerate \texttt{TupletRatio}, now enforced at construction).
\emph{Consolidation:} added the Canonical Byte-Layout Reference
(Appendix~\ref{app:bytes}) gathering every ratified discriminant,
derivation preimage, and primitive encoding into one place for the
Binary Format companion to import, with a domain-tag registry and
golden-lock anchors; and closed two follow-through gaps where the
discriminant \emph{bytes} of \texttt{ObjectKind} (\texttt{Voice}~=~0,
\texttt{Pitch}~=~1, \texttt{Registered}~=~2; feeds the
\texttt{IntegrityAnomalyId} preimage) and \texttt{ResolutionAction}
(\texttt{AcceptLoser}~=~0~$\dots$~\texttt{Registered}~=~5; feeds the
operation content hash) were golden-locked in code but unpinned in
spec text.
\\
\today & Pass 12 tranche 1 (audit spec-alignment) &
Aligned spec text with dispositions the project had already made
and the reference implementation exercises; no byte-layout or
architecture change. The re-anchoring rule table's Slur and
Spanner rows now state the Operation Catalog's surviving-endpoint
semantics (re-anchor while $\geq 1$ endpoint survives,
cascade-delete only when none does; proximity-aware re-targeting
deferred). \texttt{TupletCompensation::RewriteTuplets} adopts the
catalog's id-only v1 payload (\texttt{TupletRewrite} removed):
graph-aware reduction refuses the variant rather than fabricate
rewritten values, and a value-carrying payload revision reopens
it. Operation-envelope blocks are pure envelope vectors (a
content-addressed chunk cannot embed its own id); the per-block
summary metadata
(\texttt{dvv\_summary}/\texttt{min\_stamp}/\texttt{max\_stamp})
relocated to the manifest's \texttt{operation\_block\_summaries}
map (opaque ops-supplied bytes, non-canonical, ChunkId-keyed),
and the order-blocks-by-\texttt{min\_stamp} \SHOULD{} was dropped
because the canonical manifest encoding sorts chunk references by
encoded form. The spelling and decomposition pre-passes are now
specified as producing \emph{canonical derived annotations} ---
deterministic functions of (materialized graph, profile,
versioned algorithm id), recomputed on materialization and never
stored --- replacing the stored-\texttt{Inferred}-attachment
model; the incremental re-run requirement is demoted from
\MUST{} to \MAY{} with caching and incrementality required to be
unobservable. Spelling-precedence ties now break by canonical
attachment order (attachments carry no creation timestamp). The
\texttt{ConflictRegistry} moved off the \texttt{Score} root into
canonical materialized state, matching Chapter~6.
\texttt{SolverTier} gains the non-conformance \texttt{Stub}
variant. Companion: Operation Catalog 0.2.0 $\rightarrow$ 0.3.0
(ModifyEvent materializes metric placement changes behind a
voice-occupancy placement precondition). Dispositions logged in
\texttt{spec/PASS12\_RATIFICATION\_LOG.md}; the Pass 12 batch
itself remains open.
\\
\today & Pass 12 tranche 1 addendum (Push-3 enablers) &
Two enabler ratifications for the machinery-wiring work.
\texttt{ResolveEquivocation}, previously a dangling prose
reference in the equivocation section, is now a defined
meta-operation: \texttt{OperationPayload} gains the variant here,
and the Operation Catalog (0.3.0 $\rightarrow$ 0.4.0) pins its
payload schema (\texttt{\{ target: OperationId, chosen:
EnvelopeHash \}}), the order-independent
earliest-resolve-governs promotion, idempotence, differing-resolve
meta-conflict, and precondition no-ops; the profile-declared
selection-policy path remains open (P12-K5). Engraving break
overrides became position-addressable:
\texttt{OverrideKind::SystemBreak}/\texttt{PageBreak} carry the
break's \texttt{TimeAnchor} (the \texttt{ScoreGraph} target names
the owning region; projected break overrides carry
\texttt{Internal} origin pending P11-C8 authorship surfacing),
closing the representational gap that blocked the logical-stage
override projection §Engraving Overrides requires.
\\
\today & Phase-3 first tranche (casting-off + catalog) &
Companion movements, no core-spec byte or architecture change. The
Operation Catalog (0.4.0 $\rightarrow$ 0.5.0) gains the Phase-3
schema-fill tranche --- \texttt{CreateStaff} (set-union global-staff
mint, plus a staff-liveness precondition on
\texttt{CreateStaffInstance}), \texttt{SetTimeSignature} and
\texttt{SetTempoSegment} (LWW structural overwrites beneath the
whole-grid \texttt{SetMetricGrid}), \texttt{SetStaffLayout} (LWW
advisory) --- and a rewritten \sectionsc{UndoTransaction}:
\texttt{StrictInverse}/\texttt{BestEffort} now perform
\emph{value restoration} for overwrite primitives via
canonical-order write chains (delete resurrection, Transpose
inversion, and Cascade closure stay deferred, P11-C8 narrowed).
The Binary Format companion (0.1.0 $\rightarrow$ 0.2.0) appends
the corresponding wire discriminants. This document's
\texttt{OperationKind} listing gains the four kinds. The create
score/canvas slots remain deliberately unavailable pending an
addressable root model (P12-K8). (Reversed in part by
\texttt{spec/RULING\_GENESIS\_PERSISTENCE.md}, ratified with the genesis
operation tranche: the root/canvas \emph{objects} stay unavailable as
ratified here, but the score's \emph{contents} are no longer outside the
operation set --- see \sectionsc{The Canvas}, \S\ref{sec:graph:canvas}.)
\\
\today & Schema major 1 (data-model expansion) &
Defines the two referenced-but-undefined types
\texttt{CanvasLayoutDefaults} (with \texttt{CanvasSize} /
\texttt{CanvasMargins}, staff-space page geometry, A4/8\,mm default;
closes P12-I7) and \texttt{PitchRange} (advisory pitch compass, used by
\texttt{Instrument.range} and \texttt{IndeterminacyHints}); adds
\texttt{Region.permits\_spanning\_slurs} (default false, P12-K7). Records
that schema major~1 is the first data-model expansion major: the
canonical-base \texttt{MaterializedState} embeds none of these values and
stays major~0, byte-identical, but \texttt{Region.permits\_spanning\_slurs}
reaches the \emph{canonical} \texttt{CreateRegion} operation payload, so a
major-0 reader opens a bundle carrying v1 \texttt{CreateRegion} ops
read-only. The wire form, the resolved-layout length-prefix unification
(the manifest-embedded barrier blobs stay \texttt{u64}), and the
byte-for-byte v0${\to}$v1 migration are ratified in the Binary Format
companion (0.2.0 $\rightarrow$ 0.3.0).
\\
\today & Schema major 1 Phase F (engrave ratify-only) &
Ratifies three engrave/layout-ir dispositions the implementation already made
(Pass 12 batch I8/I9/I10, now struck), with no byte-layout or architecture
change: the \emph{break-constraint satisfaction} predicate
(Requirement~\ref{req:layoutir:break-satisfaction} --- a \texttt{SystemBreakAt}
/ \texttt{PageBreakAt} is satisfied iff the final \texttt{ResolvedLayoutIR}
starts a system/page at that slot, a region-first slot trivially); \emph{break-override
attribution} threaded through a \texttt{ConstrainedLayoutIR.break\_origins}
sidecar, declining to widen the normalized constraint record
(Requirement~\ref{req:layoutir:break-origin-attribution}); and the
\emph{system-continuation} synthesis kind
\texttt{Registered(SYSTEM\_CONTINUATION\_SYNTHESIS)} with an
\texttt{(original,\,ordinal)} instance key
(Requirement~\ref{req:layoutir:continuation-synthesis}). Flags one open item:
which region governs a cross-region slur's spanning permission (implemented as
conservative AND, P12-K12).
\\
\today & Pass 12 G-ratification (the batch pass) &
Retires the open Pass-12 batch (28 rows; dispositions in
\texttt{PASS12\_RATIFICATION\_LOG.md}). Algorithm dispositions: the spelling
pre-pass and notational decomposition move to profile-declared-by-versioned-
identifier with ratified v1 defaults
(Requirements~\ref{req:pitch:spelling-algorithm}
and~\ref{req:time:decomposition-algorithm}; closes P12-H1/H3/H4/H5/C5);
decomposition precedence pinned fixed (P12-H6); authored annotations for
inference-ineligible targets surface
(Requirement~\ref{req:pitch:authored-uninferred}, P12-H7). Identity:
system-derived intrinsic content immutable under reduction (P12-K3). Genesis
ratified outside the operation set (P12-K8 --- reversed in part by
\texttt{spec/RULING\_GENESIS\_PERSISTENCE.md}, as annotated above: the
root/canvas objects stay unavailable, the score's contents no longer do).
Cross-region slur permission =
AND (P12-K12). Re-anchoring: cue cascade-on-any-source, Range truncate
defined, annotation orphaning sanctioned, \texttt{ReanchorReason::
SameCanvasNearer} appended (P12-C1/C2/C3/C4). Barriers: target-free and
opaque-operation matching (Requirement~\ref{req:format:barrier-matching},
P12-E4); unsafe-edit tombstone semantics
(Requirement~\ref{req:format:unsafe-tombstone}, P12-E5; encoding deferred to
the Binary Format companion). Solver: kind-determined strength
(Requirement~\ref{req:solver:kind-strength}, P12-I4), sub-conformant report
shape (Requirement~\ref{req:solver:subconformant-report}, P12-I5), the
Minimal-tier constraint floor
(Requirement~\ref{req:layoutir:constraint-floor}, P12-I6). The stale
\texttt{OperationKindTag} listing gains the eleven appended tags. Companion
movements: Operation Catalog 0.5.0~$\rightarrow$~0.6.0, Binary Format
0.3.0~$\rightarrow$~0.4.0.
\\
\today & Schema major 2 (data-model expansion, Phase A) &
Defines the leaf types Chapter~5's ratified shapes referenced but never
pinned: \texttt{SlurKind}, \texttt{CurveDirection},
\texttt{CurvatureOverride}, the shared \texttt{SpanStyle} record
(consolidating the formerly-sketched identical
\texttt{SlurStyle}/\texttt{TieStyle}/\texttt{SpannerStyle} triplet),
\texttt{SubBeam}, \texttt{BeamGeometryOverride}, the
\texttt{SpannerKind} payload types (\texttt{HairpinDirection},
\texttt{OctaveOffset}, \texttt{PedalKind}, \texttt{TextLineDefinition},
\texttt{BracketKind}) --- \texttt{SpannerKind}
gains a leading \texttt{Generic} variant as the honest migration default
--- \texttt{Volta}, \texttt{MetadataValue}, \texttt{Timestamp},
\texttt{SoundConfiguration}, \texttt{TranspositionInterval} (structural,
advisory until the Chapter~4 tuning catalog per the P12-K2 discipline),
\texttt{UnpitchedMember}, \texttt{SpaceUnit}, \texttt{LineStyle}, and
\texttt{StaffBracketKind}. Ratifies the clef/key content model into
Chapter~5 (\texttt{Clef}, \texttt{ClefShape}, \texttt{KeySignature},
\texttt{ClefChange}, \texttt{KeySignatureChange} --- the visible-slice
value types) and retires the \texttt{ClefId} identifier sketch:
\texttt{Staff.default\_clef} and \texttt{Instrument.default\_clef} embed
\texttt{Clef} values, and \texttt{Instrument.default\_staff\_lines} is
reconciled from \texttt{u8} to \texttt{StaffLineConfiguration}. Metadata
timestamps are pinned strictly authored
(Requirement~\ref{req:graph:metadata-timestamps}: nothing writes them
implicitly). Chapter~8 gains the schema-major-2 paragraph (canonical base
stays major~0; the filled values reach the snapshot and the seven embedding
operation payloads, \texttt{InsertRegion} transitively). Wire forms and the
total default-filling migration: Binary Format companion
0.4.0~$\rightarrow$~0.5.0.
\\
\today & Schema major 2 (repeat authoring, Phase D) &
The dedicated \texttt{CreateRepeatStructure}/%
\texttt{DeleteRepeatStructure} pair enters the operation set:
Chapter~6's \texttt{OperationKind} enum and Chapter~8's
\texttt{OperationKindTag} listing gain the two variants (repeats are not
\texttt{CrossCuttingValue} kinds on the wire --- the reconciliation
note follows the \texttt{CrossCuttingStructure} listing), and the
re-anchoring rule table gains the Repeat-structure~/ Anchor row
(re-anchor to the nearest surviving anchor across \emph{every}
event-referencing anchor site --- \texttt{start}/\texttt{end}, jump
targets, volta spans --- cascade-delete only when none survives).
Six-part schemas: Operation Catalog \sectionsc{Repeat Structures},
0.6.0~$\rightarrow$~0.7.0. Wire discriminants 28/29 and the
per-payload stamping (create born at v2; delete a minor kind append
at major~0): Binary Format companion 0.5.0~$\rightarrow$~0.6.0.
\\
\today & Schema major 2 (rendering, Phase F) &
The rendering consumers of the schema-major-2 data model are
ratified into Chapter~7. \texttt{ResolvedLayoutIR} gains the
non-glyph primitive vocabulary it has always carried in
implementation --- line \texttt{Stroke}s (staff lines, stems,
barlines, volta brackets) and cubic-B\'ezier \texttt{Curve}s
(slurs) --- as \ref{req:layoutir:resolved-primitives}
(provenance-traced, non-canonical). Two Minimal-tier rendering
floors join the constraint floor: \ref{req:layoutir:repeat-render}
(E1: repeat barlines at resolved boundaries, volta brackets with
ending numbers, honest non-drawing of unresolvable boundaries,
jump-kind marks and cross-region repeats deferred) and
\ref{req:layoutir:slur-curve} (E2: a slur renders as a cubic
B\'ezier honoring \texttt{CurvatureOverride}, honest non-drawing of
unresolvable or cross-staff spans, a non-\texttt{Solid} line style
surfaced not silently rendered solid). The curvature-computing
algorithm and the \texttt{RenderIR} encoding stay forward-referenced
out (\ref{sec:layoutir:forward}, \ref{sec:layoutir:render}). Layout
geometry is non-canonical, so no wire form and no companion version
moves --- an engrave-side ratify-as-implemented, mirroring the
schema-major-1 Phase~F precedent.
\\
\today & Layout IR (slur rendering completion, Push 3) &
The three slur refinements \ref{req:layoutir:slur-curve} deferred are
now landed and its requirement extended: an authored non-\texttt{Solid}
\texttt{SpanStyle} line renders faithfully (dashed / dotted --- the
\texttt{LineStyle} rides the \texttt{Curve}, whose listing gains the
field), a slur spanning a system break splits into per-system
sub-curves (de~Casteljau; first segment keeps the slur's provenance,
the rest synthesized continuations), and \texttt{slur\_shape\_penalty}
moves off its pinned $0.0$ to a real measurement (Quality Metric
Catalog \sectionsc{slur\_shape\_penalty} rationale refreshed; formula
unchanged, no version move). Layout geometry stays non-canonical --- no
wire form, no companion version moves.
\\
\midrule
\today & Layout IR (primitive band ownership, Push 3) &
Chapter 7 gains \ref{req:layoutir:primitive-band-ownership}: every
primitive the projection presents to the solver ---
\texttt{GlyphObject}, \texttt{Stroke}, \texttt{Curve} --- declares the
\texttt{VerticalBandId} that owns it, naming a band that exists, and a
vertical solver takes ownership from that declaration rather than
inferring it from geometry. Ratifies-as-implemented the contract a
vertical solver needs and an inference cannot supply: a slur's
endpoints are lifted clear of its own staff, so its owner is not
recoverable from where it is drawn. The \texttt{ConstrainedLayoutIR}
listing gains \texttt{strokes} / \texttt{curves} (present since staff
lines, never listed) and the \texttt{Stroke} / \texttt{Curve} listings
gain \texttt{vertical\_band}; a band now exists for every staff of a
region and for its margin, members or not.
\ref{req:layoutir:resolved-band-ownership} carries the declaration
through the resolved stage on strokes and curves (a
\texttt{ResolvedGlyph} keeps none --- its ownership is already baked
into its position). Band ownership is non-canonical attribution
metadata: it enters no content hash and no canonical encoding, so no
wire form and no companion version moves.
\\
\midrule
\today & The Fixed Header (container epoch) &
Chapter~8 gains \sectionsc{The Container Epoch} and
\ref{req:format:container-epoch}
(\texttt{spec/CONTRACT\_FORMAT\_EPOCH\_MAJOR1.md}).
\texttt{format\_major} becomes \tablenums{1} and \texttt{format\_minor}
restarts at \tablenums{0}. A reader classifies the major three ways ---
legacy \tablenums{0}, current \tablenums{1}, unsupported otherwise ---
and carries the classification as a value rather than re-deriving it by
comparison, so a legacy container is decoded deliberately instead of
refused.
The epoch exists because a canonical base is only meaningful when it has
been validated against a reduction authority, and nothing in a document
could previously attest that: the reduction-algorithm version is a bare
integer that a writer copies from the base's own self-report, so the one
check that would detect a stale base necessarily passes for a base that
was propagated conformingly. The header can attest it because the header
never changes after creation --- which makes the epoch a property of the
file's creation that no later commit can confer. A legacy container may
therefore open while it carries no base, \MUSTNOT{} acquire one, and is
refused outright if it already has one; a repack into a freshly created
major-1 container is the only path forward, and read-only degradation is
explicitly not offered, since a restricted view of unverifiable canonical
state is still unverifiable canonical state. Corruption keeps precedence
over every epoch rule in both epochs, so tampering is never reported as
staleness. \sectionsc{Schema Versioning} gains the corresponding
distinction between the schema axis and the container axis.
\\
\today & Ch.~\ref{ch:format} & \sectionsc{The Canonical Document Identity}
gains Requirement~\ref{req:format:reduction-authority}: an implementation
\MUST{} name the reduction semantics it implements and validate a canonical
base against \emph{that}, not against a value the base itself supplied.
Comparing the base's \texttt{reduction\_algorithm\_version} only against
the active superblock's is necessary but not sufficient, because a writer
that seeds the superblock from the base's own self-report makes the
comparison an identity --- so it holds for every conformingly written
document and detects only tampering. The requirement therefore introduces
the implementation's own semantics as a third participant, required at both
boundaries: a reader refuses a base that disagrees with the semantics it
implements, and a writer refuses to emit or replace one. A document with no
canonical base opens regardless, carrying no reduced state that could be
stale, and a commit that does not touch the base is not refused on this
ground. The refusal may not degrade to a read-only open nor be reported as
an integrity anomaly --- a stale base is not a restricted-but-correct view
but state computed under different rules --- and it stays distinguishable
from the malformed-document failure a base disagreeing with its own
superblock produces, since the two detect different faults. Rebuilding
during open is prohibited outright, because the envelopes a rebuild would
need may have been pruned.
\\
\bottomrule
\end{longtable}
\end{document}