diff --git a/crates/epiphany-layout-ir/DECISIONS.md b/crates/epiphany-layout-ir/DECISIONS.md index 6518689..cbc8f7e 100644 --- a/crates/epiphany-layout-ir/DECISIONS.md +++ b/crates/epiphany-layout-ir/DECISIONS.md @@ -483,6 +483,13 @@ emission set (successive-notehead no-collision chains + per-glyph containment ## Repeat barlines and volta brackets (schema major 2, E1, 2026-07-07) +> **Ratified (Phase F, 2026-07-08):** these decisions are now the normative +> Minimal-tier floor `req:layoutir:repeat-render` in core spec Chapter 7 +> (§ResolvedLayoutIR); the non-glyph primitives the volta brackets use are +> `req:layoutir:resolved-primitives`. Layout geometry is non-canonical, so no +> wire/companion-version change (PASS12_RATIFICATION_LOG.md, schema-major-2 +> tranche). + The first repeat-structure ink (Chapter 5 `RepeatStructure` / `RepeatKind` / `Volta`, ratified by the major-2 Phase A). Rendering is spec-unconstrained (Ch7's `BarLine` payload is undefined and voltas have no layout variant), so @@ -559,6 +566,15 @@ these are E1 implementation decisions for the Phase-F ratification pass: ## Slur curves + the cubic-bézier curve primitive (schema major 2, E2, 2026-07-08) +> **Ratified (Phase F, 2026-07-08):** the slur rendering floor is +> `req:layoutir:slur-curve` and the `Curve`/`Stroke` primitive vocabulary is +> `req:layoutir:resolved-primitives` in core spec Chapter 7 (§ResolvedLayoutIR) +> — the struct listing now carries `strokes`/`curves` and the `Stroke`/`Curve` +> shapes, and the RenderIR provenance requirement was widened to name all three +> primitive kinds. The curvature *algorithm* and dash rendering stay +> forward-referenced out. Non-canonical, so no wire/companion-version change +> (PASS12_RATIFICATION_LOG.md, schema-major-2 tranche). + The third pipeline primitive kind. Primitives were two parallel flat Vecs (`glyphs`, `strokes`) at each of the three IR stages; a `Curve` (four control points + thickness/layer/style/provenance, mirroring `Stroke`) adds a third diff --git a/spec/PASS12_RATIFICATION_LOG.md b/spec/PASS12_RATIFICATION_LOG.md index adf36ad..c66e089 100644 --- a/spec/PASS12_RATIFICATION_LOG.md +++ b/spec/PASS12_RATIFICATION_LOG.md @@ -177,3 +177,34 @@ returned a successful empty derivation under the requested profile. Resolution: the **spec text stands**; the code moved — `derive_annotations` now returns `Result` and rejects unregistered ids up front (`PrePassError`). No spec change; core DECISIONS records the reasoning. + +## Schema-major-2 tranche (2026-07-08) — rendering (E1/E2), engrave ratify-only + +The schema-major-2 push's *rendering* consumers, ratified into core spec +Chapter 7. The data model (Phases A–C) and the repeat-authoring op pair (Phase +D) already landed with their own revision-history rows and companion-version +bumps (Operation Catalog 0.7.0, Binary Format 0.6.0); this tranche records +**Phase F**, which is layout-IR (Chapter 7) only. Delivered as the E1 +(`7a9bf42` + `6651ae5`), E2 (`81b7f42` + `28f210e`) commit series. Layout +geometry is non-canonical (no content hash, no wire form — see +`req:layoutir:object-id-derivation`), so this is a ratify-as-implemented with +**no byte-layout change and no companion bump**, mirroring the schema-major-1 +Phase-F precedent. + +| Item | Disposition | Spec locus | Consumer | +|---|---|---|---| +| Non-glyph resolved primitives | **adopt (ratify pre-existing + E2 reality)** — Ch7 gains Requirement `req:layoutir:resolved-primitives`: `ResolvedLayoutIR` carries, beyond glyphs, non-glyph line `Stroke`s (staff lines, stems, barlines, volta brackets — present since staff lines, never previously ratified) and cubic-Bézier `Curve`s (slurs, E2). Each carries provenance (the hit-testing basis) and is re-spaced by the solver like a glyph; both are non-canonical. The struct listing and the `Stroke`/`Curve` shapes are added; the RenderIR provenance requirement widened from "originating `ResolvedGlyph`" to "…`ResolvedGlyph`, `Stroke`, or `Curve`" | core_spec Ch7 §ResolvedLayoutIR (`req:layoutir:resolved-primitives`) | `epiphany-layout-ir` (`constrained.rs::{Stroke,Curve}`, `resolved.rs`); `epiphany-render-svg` | +| E1 repeat / volta rendering | **adopt** — Ch7 gains Requirement `req:layoutir:repeat-render`: a barline-drawing `RepeatStructure` renders a repeat barline at each resolved boundary (the precomposed sign, replacing a coinciding measure barline or standing alone; the dot pair beside a never-replaced final barline), each `Volta` a bracket with ending numbers; an unresolvable boundary draws no ink (traced anchor, honest placement); jump-kind (`DaCapo`/`DalSegno`) marks and cross-region repeats deferred. The glyph vocabulary/spacing stay engraving-algorithm concerns | core_spec Ch7 §ResolvedLayoutIR (`req:layoutir:repeat-render`); references Ch5 §Repeat Structures | `epiphany-layout-ir` (`logical.rs`, `constrained.rs`); `epiphany-engrave` (casting) | +| E2 slur rendering | **adopt** — Ch7 gains Requirement `req:layoutir:slur-curve`: a `Slur` renders as a cubic Bézier between its endpoint columns, honoring `CurvatureOverride` (direction, height); an endpoint that does not resolve to a column on a single staff of one region draws no curve (traced anchor); a non-`Solid` `SpanStyle` line MAY be deferred but MUST be surfaced (a layout diagnostic), never silently rendered solid. The curvature algorithm and dash rendering stay forward-referenced out | core_spec Ch7 §ResolvedLayoutIR (`req:layoutir:slur-curve`); references Ch5 §Slurs and Phrase Marks | `epiphany-layout-ir` (`logical.rs`, `constrained.rs`, `hittest.rs`); `epiphany-editor-core` | + +**Version movements.** None on the wire: core spec Chapter 7 gains three +`req:layoutir:*` requirements + the `Stroke`/`Curve` struct listings + a +revision-history row (Phase F). Operation Catalog stays 0.7.0, Binary Format +stays 0.6.0 — layout geometry is non-canonical. + +**Deferred (documented, not open candidates).** Dashed/dotted slur *rendering* +(surfaced as a diagnostic today), kind-differentiated slur appearance +(Phrase/Editorial), jump-kind repeat marks, cross-region and cross-system-break +curve splitting (de Casteljau), and the `slur_shape` quality metric moving off +`0.0`-by-construction — all Standard-tier / Push-3 work, named in the crate +DECISIONS, none a Pass-13 ambiguity. diff --git a/spec/core_spec.pdf b/spec/core_spec.pdf index b449cf3..3e91326 100644 Binary files a/spec/core_spec.pdf and b/spec/core_spec.pdf differ diff --git a/spec/core_spec.tex b/spec/core_spec.tex index f19e116..64395b8 100644 --- a/spec/core_spec.tex +++ b/spec/core_spec.tex @@ -5066,6 +5066,7 @@ pub struct CrossCuttingRegistry { \end{lstlisting} \subsection{Slurs and Phrase Marks} +\label{sec:graph:slurs} \begin{lstlisting}[language=Rust] pub struct Slur { @@ -5357,6 +5358,7 @@ pub enum MarkerKind { \end{lstlisting} \subsection{Repeat Structures} +\label{sec:graph:repeats} \begin{lstlisting}[language=Rust] pub struct RepeatStructure { @@ -9213,8 +9215,8 @@ pub enum BreakKind { \label{sec:layoutir:resolved} \texttt{ResolvedLayoutIR} is the output of the constraint solver: -every glyph has a definitive position. This is the IR consumed by -the renderer. +every glyph, stroke, and curve has a definitive position. This is the +IR consumed by the renderer. \begin{lstlisting}[language=Rust] pub struct ResolvedLayoutIR { @@ -9226,6 +9228,14 @@ pub struct ResolvedLayoutIR { /// Glyph-level objects with resolved positions. pub glyphs: Vec, + /// Non-glyph line primitives (staff lines, stems, barlines, + /// volta brackets, ...), positioned alongside the glyphs. + pub strokes: Vec, + + /// Cubic-Bezier curve primitives (slurs, ...), positioned + /// alongside the glyphs. + pub curves: Vec, + /// Engraving decisions, including any made by the constraint /// solver (e.g., soft system breaks placed by casting-off). pub engraving_decisions: Vec, @@ -9256,8 +9266,88 @@ pub struct ResolvedGlyph { pub style: GlyphStyle, pub layer: i32, } + +/// A line primitive the solver positions but the vertical-band +/// model does not contain (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, +} + +/// 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, +} \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: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{Minimal-tier slur rendering (ratified schema-major-2, + Phase~F).} 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) \MAY{} + be deferred to a higher tier, but \MUST{} be surfaced (a layout + diagnostic), never silently rendered solid. The curvature-computing + algorithm and the dash rendering are engraving-specification + concerns (\ref{sec:layoutir:forward}). +\end{requirement} + \section{RenderIR (Interface Only)} \label{sec:layoutir:render} @@ -9291,9 +9381,11 @@ pub struct RenderConfiguration { \begin{requirement} Implementations producing \texttt{RenderIR} \MUST{} preserve provenance from \texttt{ResolvedLayoutIR}: every renderer primitive - \MUST{} be traceable to its originating \texttt{ResolvedGlyph} (and - therefore to its score graph source). This is the basis of - hit-testing, selection, and back-reference navigation in the UI. + \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. @@ -9515,6 +9607,7 @@ pub struct GlyphCatalogIdentity { \end{requirement} \section{Forward References} +\label{sec:layoutir:forward} \begin{itemize} \item The constraint solver consuming \texttt{ConstrainedLayoutIR} @@ -15392,6 +15485,28 @@ layouts they own versus inherit: 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. + \\ \bottomrule \end{longtable}