epiphany/crates/epiphany-render-svg/tests
Levi Neuwirth 24b6a34db9 Slurs: side from the stems, endpoints on the notes, apex clear of both
The rendered slurs were wrong in three independent ways, all visible in the
two-staff and three-staff goldens.

  1. Side. SlurDirection::Auto always arced above. The single-voice rule is
     OPPOSITE the stems -- all stems up puts the slur under the noteheads, all
     down puts it over them, and a mixed-stem span (which has no notehead side)
     goes above. Every Auto slur over a stem-up passage was drawn through its own
     stems. This is why stem direction had to land first: with every stem pointing
     up, "opposite the stems" means nothing.

  2. Endpoints. They sat at staff_top + gap -- a constant offset from the STAFF,
     not from the notes -- so a slur between two C6s hung below its own noteheads
     and crossed their ledger lines. They now sit a gap outside the endpoint
     column's ink, at the notehead's centre. Where the stem points the same way as
     the slur, that ink includes the stem, so the endpoint clears the stem tip.

  3. Clearance. The apex was span-proportional and blind, so a note between the
     endpoints poked straight through the arc. ColumnInk -- per staff, per column:
     top, bottom, stem direction, notehead centre -- is the obstacle field. The
     control points sit on the chord at thirds, so x is exactly linear in t and
     the arc's departure from the chord is 3*lift*t*(1-t); a column at t needing d
     more clearance forces an apex of at least d/(4*t*(1-t)).

An authored height is a floor, not a ceiling: clearance may raise it, so obeying
an author cannot draw a slur through a note. An authored direction still wins.

Obstacles are measured at the notehead CENTRE, the same x the endpoints use. The
first cut used the raw column x, which skews t and silently over-lifts: the
two-staff slur cleared its C6 by 4.05 spaces where 3.5 was needed. The clearance
test now asserts an upper bound as well as a lower one.

SLUR_INSET is gone. Endpoints at the notehead centres are what its 0.6-space
"tuck" approximated for the start point -- and got wrong for the end, where it
tucked a full notehead width to the LEFT of the final note.

Four mutations verified: always-above, staff-relative endpoints, no clearance
pass, and obstacles at the column x. The staff-relative-endpoint mutation PASSED
at first -- the tests asserted only "above the staff" / "below the staff", which a
staff-relative endpoint satisfies by construction. The exact-endpoint assertion
exists because that mutation survived.

Projection change, so no version moves; goldens churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 13:58:56 -04:00
..
golden Slurs: side from the stems, endpoints on the notes, apex clear of both 2026-07-09 13:58:56 -04:00
acceptance.rs Close the inter-staff residual risk: a three-staff cascade fixture 2026-07-09 10:20:35 -04:00