epiphany/crates/epiphany-render-svg
Levi Neuwirth af297881e2 Agent I-3: criterion-6 round-trip and golden lock on the real Engraver
Criterion 6 (the Chapter 7 layout round-trip) and the render goldens previously
exercised only the verbatim StubSolver, so a regression in the real Engraver's
geometry could land unseen. I-3 drives both through the Engraver.

  - round_trip_with<S: ConstraintSolver> factors the solver-agnostic provenance
    contract out of round_trip (now a one-line stub wrapper): coverage, the
    complete Provenance surviving constrained -> resolved -> render, the source
    surjection, and no duplicate stable ids hold for *any* conformant solver. The
    Stub tier's verbatim-geometry clause is gated behind solver.tier() == Stub;
    every other tier re-spaces. The status gate accepts any renderable status
    (Solved / SolvedWithWarnings / PartialBudgetExhausted), not exactly Solved, so
    the helper matches its "arbitrary conformant solver" contract while still
    rejecting the diagnostic-only statuses that carry no authoritative layout.
  - criterion_six_round_trips_through_the_engravers_respacing (epiphany-engrave)
    runs the full graph -> logical -> constrained -> *engraved* -> render round
    trip over the criterion-6 hand-off fixtures -- ten_measure_single_staff (the
    measured fixture) and valid_score_rich (cross-cutting tuplet/tie/spanner),
    plus valid_score for breadth -- and asserts the whole provenance contract
    survives the Engraver's re-spacing. A non-vacuity check confirms the Engraver
    genuinely moved geometry, so provenance is preserved *through* a real geometry
    change -- the statement the verbatim stub can never make. This adds an
    epiphany-testkit dev-dep (no cycle: testkit does not depend on this crate).
  - The render-svg engraver acceptance test is upgraded from invariant-only to
    byte-locked: new .engrave.snapshot.txt / .engrave.svg goldens for both
    fixtures capture the Engraver's re-spaced output (e.g. ten_measure view_box
    width 82.26 vs the stub's 88.88, same glyph/stroke/class counts), so an
    Engraver geometry regression is caught at the byte level. A companion test
    asserts the engrave goldens genuinely differ from the stub goldens, catching
    the degeneracy where the Engraver echoes the stub (which would otherwise pass
    both golden checks independently).

Also corrects the epiphany-engrave package description, which still claimed it
reports SolverTier::Stub until it earns Minimal (it earned Minimal in I-2).

Full gate green: build, fmt, clippy, 580 tests, conformance scale 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:58:31 -04:00
..
examples Phase 2 (Agent I): visible-slice scaffold — Bravura SVG renderer vs stub 2026-06-23 20:01:40 -04:00
src Agent I Phase 2-3: engrave recognizable notation from Score to SVG 2026-06-26 20:46:33 -04:00
tests Agent I-3: criterion-6 round-trip and golden lock on the real Engraver 2026-06-26 21:58:31 -04:00
tools Agent I Phase 2-3: engrave recognizable notation from Score to SVG 2026-06-26 20:46:33 -04:00
Cargo.toml Phase 2 (Agent I): visible-slice scaffold — Bravura SVG renderer vs stub 2026-06-23 20:01:40 -04:00
DECISIONS.md Phase 2 (Agent I): visible-slice scaffold — Bravura SVG renderer vs stub 2026-06-23 20:01:40 -04:00
README.md Phase 2 (Agent I): visible-slice scaffold — Bravura SVG renderer vs stub 2026-06-23 20:01:40 -04:00

README.md

epiphany-render-svg

Agent I's SVG renderer behind the Epiphany RenderIR interface (spec Chapter 7): turns a ResolvedLayoutIR into well-formed SVG 1.1, drawing each glyph as a genuine Bravura SMuFL outline <path>. It is the visible end of the v0 Score → layout IR pipeline.

Status: renderer against the stub solver

This phase builds and golden-locks the renderer against the stub solver's output (the QUICKSTART development pattern), before the real engraving solver and the score→real-notation engraving pass land. The stub returns the IR geometry verbatim — a structural projection (each object becomes one arbitrary glyph in a row), not yet recognizable notation — so what is proven here is renderer correctness and faithfulness: real outlines, provenance preserved, output XML-valid and deterministic. The renderer consumes any solver's output, so the picture improves with no renderer change once epiphany-engrave lands.

Demo

# Render a fixture to SVG (stub solver by default):
cargo run -p epiphany-render-svg --example render_fixture -- \
    ten_measure_single_staff > out.svg

# Drive Agent I's engrave solver instead, to bisect renderer-vs-solver:
cargo run -p epiphany-render-svg --example render_fixture -- \
    ten_measure_single_staff --solver=real > out.svg

Fixtures: ten_measure_single_staff, valid_score_rich, valid_score. Stats and diagnostics go to stderr; the SVG goes to stdout.

Library

use epiphany_render_svg::{render, RenderOptions};

let out = render(&resolved_layout_ir, &RenderOptions::default());
assert!(out.is_well_formed());
println!("{}", out.svg);

render is pure and deterministic. RenderOptions controls SVG-encoding choices only (display scale, margin, provenance attributes) — nothing that changes engraving.

Bundled outlines

The glyph outlines in src/outlines_generated.rs are extracted from the official OFL Bravura.otf by tools/extract_bravura_outlines.py. The font is not vendored; only the generated Rust is committed. Bravura is © Steinberg Media Technologies GmbH under the SIL Open Font License 1.1 (tools/OFL.txt); the extracted outlines are redistributed under the same license. To regenerate:

cd crates/epiphany-render-svg/tools
python3 -m venv .venv && . .venv/bin/activate && pip install fonttools
python3 extract_bravura_outlines.py > ../src/outlines_generated.rs