Ratify primitive band ownership in Chapter 7; scope canonical_bytes' doc

Review follow-up. Both findings were about documentation lagging a contract the
code now enforces, and the first is the one that matters: band ownership is the
solver-facing correctness contract, so it belongs in the spec, not only in a
crate's DECISIONS.md.

Chapter 7's ratified Stroke/Curve listings gain `vertical_band`, and a new
requirement (req:layoutir:primitive-band-ownership) states the rule as a MUST:
every primitive declares its owning band, the projection MUST name a band that
exists, and a vertical solver MUST take ownership from that declaration and MUST
NOT infer it from geometry. The rationale is recorded because the requirement
looks like a convenience until you see why it isn't: a stem shares its column
with the staff above, and a slur's endpoints are lifted clear of its own staff
into the zone where the nearest notehead belongs to the neighbour. A solver that
guesses from proximity tears such primitives off their notes when it renegotiates
the staff gaps -- which is exactly what happened, twice. The requirement also
records that only glyphs are band *members*, and that the reference is
non-canonical attribution metadata.

Stroke's stale "the vertical-band model does not contain" gloss is dropped.

canonical_bytes' doc claimed to encode "the full resolved layout". It encodes the
rendering fingerprint and deliberately excludes attribution metadata. Stating
that precisely required checking rather than paraphrasing: ResolvedGlyph drops
its band on the way out of the constrained stage, while Stroke and Curve -- whose
types are shared with that stage -- carry vertical_band through but never encode
it. Two layouts differing only in band ownership draw identically and hash alike.

Spec rebuilds clean: no LaTeX errors, no undefined references, new label
registered. Gate green, no golden churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-07-09 10:50:10 -04:00
parent efaebb9acd
commit fc411ea7ac
3 changed files with 55 additions and 4 deletions

View File

@ -101,8 +101,18 @@ pub struct ResolvedLayoutIR {
impl ResolvedLayoutIR {
/// The canonical serialized output (Appendix D §"Quantized Layout
/// Coordinates"): the full resolved layout, with glyph positions quantized
/// to the `1/1024` grid. Equivalent to [`CanonicalEncode::to_canonical_bytes`].
/// Coordinates"): the layout's *rendering fingerprint*, with glyph positions
/// quantized to the `1/1024` grid. Equivalent to
/// [`CanonicalEncode::to_canonical_bytes`].
///
/// It encodes what a conformant renderer draws and what a conformance claim
/// compares — every primitive's provenance, geometry, style, and layer — and
/// **excludes non-canonical layout-attribution metadata**. Concretely:
/// [`ResolvedGlyph`] drops its band on the way out of the constrained stage,
/// while [`Stroke`] and [`Curve`] (whose types are shared with that stage)
/// carry `vertical_band` through but do not encode it. Band ownership tells a
/// vertical solver which staff owns a primitive; it draws nothing, so two
/// layouts differing only in it are the same rendered layout and hash alike.
///
/// Two solves whose internal f32 computations agree to better than `1/2048`
/// staff space at every coordinate produce identical bytes; two layouts that

Binary file not shown.

View File

@ -9267,8 +9267,8 @@ pub struct ResolvedGlyph {
pub layer: i32,
}
/// A line primitive the solver positions but the vertical-band
/// model does not contain (a staff line, stem, barline, or bracket).
/// A line primitive the solver positions (a staff line, stem,
/// barline, or bracket).
pub struct Stroke {
pub provenance: Provenance,
pub from: Point2D,
@ -9276,6 +9276,13 @@ pub struct Stroke {
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
@ -9293,6 +9300,11 @@ pub struct Curve {
/// 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}
@ -9313,6 +9325,35 @@ pub struct Curve {
itself is out of scope (\ref{sec:layoutir:render}).
\end{requirement}
\begin{requirement}
\label{req:layoutir:primitive-band-ownership}
\textbf{Primitive band ownership is declared, not inferred.} Every
primitive --- glyph, stroke, and curve alike --- \MUST{} declare the
\texttt{VerticalBandId} of the band that owns it, and the projection
\MUST{} name a band that exists 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 staff gaps, tear it off the notes it belongs to.
Only glyphs are \emph{members} of a band
(\texttt{VerticalBand::members}), which is what realizes the
horizontal spring solve; a stroke's or curve's band reference is
one-way. Like the primitives themselves, the reference is
non-canonical layout-attribution metadata: it enters no content hash
and no canonical encoding.
\end{requirement}
\begin{requirement}
\label{req:layoutir:repeat-render}
\textbf{Minimal-tier repeat and volta rendering (ratified