Every stem in the engine pointed up, on the notehead's right, at a constant
octave. A C6 three ledger lines above the staff grew an upward stem shooting past
everything -- visible in the two_staff and three_staff renders. It is also why the
slurs are wrong: an Auto slur is placed OPPOSITE the stems, so no correct side
could be chosen while every stem pointed the same way. Stems are therefore a
prerequisite for the slur fix, not merely adjacent to it.
- Direction: away from the middle line, decided by the head furthest from it,
ties going down (the convention for a note on the line, and for a chord that
straddles it evenly).
- Attachment: the side it points -- an up-stem at the lowest head's right edge,
a down-stem at the highest head's left. Read from the head's own bounding box
rather than the rounded NOTEHEAD_STEM_X: for noteheadBlack those are 1.1807
and 0, and 1.1807 is Bravura's real stemUpSE (1.18). The old 1.15 was a
rounding -- it accounts for the whole of ten_measure's churn, where every stem
moved right by 0.031 and nothing else moved at all.
- Length: an octave from the outer head, but drawn out to the middle line when
the note lies beyond it, so no stem dangles in the ledger field.
No version moves. This is the PROJECTION changing, not a solver: to_constrained
emits different geometry from the same graph, so ENGRAVER_VERSION's promise (same
input => same output) is untouched. Every golden churns, stub and engrave alike,
because stems are constrained-stage geometry.
Locked by a_stem_points_away_from_the_middle_line_and_reaches_it, which re-pitches
a generated score across four octaves -- the corpus generator writes only low
notes, so nothing in the suite had ever exercised a down-stem. Mutation-verified:
restoring always-up plus fixed-octave fails it.
Parked, found on the way: BRAVURA_METRICS' notehead anchors are unusable as
written. They name stemUpNW/stemDownSE -- the corners an up- and down-stem never
attach to -- and their x of 1180 reads like 1.18 staff spaces written in
thousandths rather than the table's 1/1024 units (1.18 sp = 1208). Nothing
consumes them except metrics_hash, so correcting them would move the
GlyphCatalogIdentity every conformance claim declares. That makes three parked
candidates; the house rule opens a batch pass at three.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The third pipeline primitive kind, review-hardened (5 verified findings fixed
pre-commit). A `Curve` (four control points, mirroring `Stroke`) threads
through all three IR stages, the canonical-encode fingerprint (a 5th u32 count
prefix; width-lock 4→5), the round-trip provenance chains and count identity,
`to_render`, the stub solver, engrave remap + casting, SVG path emission, and
hit-testing.
Slurs draw as one cubic bézier per slur carrying the slur's exact provenance
(no synthesis). LayoutContent::Slur resolves each endpoint event to a Note
column at to_logical (SlurEndpoint At/Unresolved — E1's honest-placement
discipline); a symmetric arc whose apex sits `height` from the endpoint line;
curvature_override direction+height honored, style.line (dashed) deferred to
Push 3. Honest non-drawing (traced anchor kept) for an unresolved endpoint, a
non-left-to-right span, or a cross-staff slur (no single staff — would float
at yo=0). Authored height/thickness sanitized to defaults when non-positive
(a negative thickness would else fail validation and blank the layout).
Hit-test: HitShape::Curve (Copy) flattens the cubic to a 16-segment capsule
inside contains/intersects (one region per curve); a slur click resolves to
Slur generically (no editor arm; edit ops refuse it). render-svg: stroked
unfilled <path C> after strokes/before glyphs + curve_count + content-hull
bounds. engrave: HorizontalRemap::curves; casting curve_system = Rigid-to-
start-system (no de Casteljau split — deferred; break-spanning slur draws
whole in its start system, kept for the source surjection); ENGRAVER_VERSION
4→5; slur_shape_penalty now 0.0 by construction (Minimal draws the ideal arc).
Existing SVG goldens changed only in the provenance-note comment (curves now
enumerated) — geometry byte-identical; 6 snapshots gained curve_count; new
ten_measure_with_slurs goldens. 929 tests, conformance 8/8.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first repeat-structure ink, review-hardened (19 verified findings fixed
pre-commit). Zero golden churn for repeat-free scores; four new goldens for
the ten_measure_with_repeats fixture.
layout-ir: repeats project LayoutContent::Repeat resolved at to_logical
(RepeatPlacement::At/RegionEnd/Unresolved — honest, no origin fallback;
zero-offsets judged by value; bare wall-clock boundaries draw no ink); deps
now from anchor_sites(). Constrained: coinciding measure barlines morph into
precomposed repeatLeft/Right/RightLeft (name-only, exact provenance kept);
standalone signs synthesize under a semantic (site, staff) instance key;
end repeats at the region close draw repeatDots beside a final barline (or
the full sign on continuing staves); start marks there draw nothing;
end-facing signs reserve their left reach via column overhang; time
signatures clear morphed signs; volta brackets = 3 strokes above the top
staff + timeSig digit numerals, kind-independent.
engrave: ENGRAVER_VERSION 3 -> 4; casting classifies barline columns via
is_barline_glyph AND direct Measure source, so standalone signs are never
phantom break candidates or measure records; criterion-6 round-trip covers
the repeat fixture.
render-svg: repeatLeft/Right/RightLeft/Dots outlines + metrics + font subset
regenerated from the SHA-pinned Bravura (fontTools 4.63.0); GlyphClass::Repeat.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>