The spec's domain-tag registry reserves a non-canonical MUSCLOID layout tag for
LayoutObjectId derivation (req:layoutir:object-id-derivation, Pass-11 item 2.6),
but it was never realized in code: layout-ir minted provisional, untagged ids and
synthesized objects borrowed MUSCCONF. This wires the ratified derivation.
- epiphany-determinism: add the reserved built-in DomainTag::LAYOUT_OBJECT_ID
(`MUSCLOID`), non-canonical/layout-namespace like FONT_METRICS (following the
SYSTEM_ANOMALY Pass-11 precedent of adding a reserved tag). The tag-enumeration
tests now derive from BUILTINS so they cannot drift; the spelling is locked and
from_bytes resolves it as a non-system builtin.
- layout-ir provenance.rs: all three LayoutObjectId derivations route through
MUSCLOID exactly per the requirement -- single keyed on source.canonical_bytes(),
multiply-manifested on (source, region), synthesized on (source, synthesis_kind,
instance_key); synthesized no longer borrows MUSCCONF. A reference-lock test
pins the derivation and proves it is genuinely domain-separated; another asserts
the three keying schemes do not collide (safe by the discriminant-led,
fixed-width canonical_bytes).
- layout-ir engraving.rs: EngravingDecisionId borrowed MUSCCONF for the same
reason; moved it onto MUSCLOID too, keeping its `engraving-decision` prefix so it
cannot alias a layout-object id within the namespace.
Layout ids are non-canonical (never document state, in no content hash), so this
changed id *values* but no durable or interchanged artifact: the only golden churn
is the data-prov hex in the four render goldens (every changed line is a data-prov;
geometry/structure byte-identical).
Spec/status sync: core_spec.tex descriptive notes (the requirement tail, the
domain-tag registry row, the registry intro, and the revision-history entry) now
say the reference implementation wires MUSCLOID as of P12-I2; MUSCLOID is moved out
of the "deferred to the companions" (not-ratified/provisional) list and given a
non-canonical anchor paragraph after the reference-implementation-locks table.
PASS12_BATCH.md marks P12-I2 resolved; PASS11_RATIFICATION_LOG.md keeps the
historical row with a "superseded by P12-I2" note; layout-ir/DECISIONS.md updates
the ratified-block note, the id bullet, and the open candidate (now RESOLVED).
Full gate green: build, fmt, clippy, 587 tests, conformance scale 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A render mode that references each glyph by its SMuFL codepoint via `<text>`,
drawn from an `@font-face`-embedded Bravura subset, alongside the default inline
`<path>` outlines. The SVG stays self-contained (the font travels in it) and the
text is selectable, at a larger file size.
- GlyphMode::EmbeddedFont: the SVG declares the font once via
<defs><style>@font-face{...}</style></defs>, then emits one
<text transform="translate(x y) scale(1 -1)" font-size="4" ...>&#xNNNN;</text>
per glyph. Geometry is consistent with PathOutline by construction: same
origin, a per-glyph counter-flip cancels the outer y-flip, and the SMuFL em is
four staff spaces. A new `text_count` stat; unbundled glyphs still fall through
to the visible bbox rect + diagnostic. The metadata comment declares which mode
produced the SVG (and, on the empty canvas too, via a shared `glyph_note`).
Path mode stays the byte-golden-locked, pixel-verified reference; the embedded
mode is structurally tested (well-formed XML — also under `xmllint`; one
@font-face; one <text>/codepoint per glyph; provenance preserved; determinism).
- The subset is a GENERATED artifact, not a vendored binary: a deterministic
base64 OTF emitted into src/font_subset_generated.rs by
`tools/extract_bravura_outlines.py --font-out` (the same SHA-pinned 1.392 font
the outlines come from). `recalcTimestamp=False` keeps the source font's fixed
head.modified so the bytes are reproducible across runs, not just within one.
- OFL compliance: the subset is a Modified Version, so its PRIMARY font name is
renamed off the Reserved Font Name "Bravura" to "EpiphanyBravuraSubset" in BOTH
naming structures an OTF carries — the SFNT `name` table AND the CFF (Name INDEX
+ top-dict FullName/FamilyName). The copyright/trademark/license records, which
name Bravura as attribution, are kept; the renderer references the renamed
family in @font-face and <text>. The generator reparses the saved bytes and
fails if the reserved name leaks into a primary record, and validates the cmap
covers every glyph.
- Machine-locked payload: the generator emits the decoded length and a BLAKE3-256
(the workspace's sole hash) of the font bytes; a render-svg test base64-decodes
the payload (no new runtime dep) and asserts the length, the OTTO signature, the
BLAKE3, and — parsing the SFNT name table and CFF Name INDEX — that neither
primary name is the reserved name. Adds an epiphany-determinism dev-dep.
The demo example gains `--glyph-mode=path|embedded`; lib/README/DECISIONS document
the two modes, the subset's OFL rename, and the regeneration command (--font-out +
the blake3 dependency). PathOutline goldens are byte-unchanged; the outlines stay
byte-identical. Full gate green: build, fmt, clippy, 585 tests, conformance scale 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`RenderOptions::emit_provenance = false` dropped every data-prov trace silently,
so the output was indistinguishable from an archival render even though it no
longer satisfies the renderer's "every element traces to its source" contract.
Now the SVG's metadata comment declares the provenance state, so suppression is
announced rather than silent:
- archival (default): "...; every glyph and stroke carries a data-prov trace
to its score-graph source"
- display-only (false): "...; provenance traces suppressed (display-only
output, not archival)"
A shared `provenance_note(emit_provenance)` helper feeds both the main render and
the empty-canvas path, so an empty trace-free layout is held to the same honesty
contract as a full one (neither can drift). The module doc and the render-svg
DECISIONS.md non-overreach rule now frame data-prov as the default archival
contract plus an explicit, declared display-only mode. Tests assert the suppressed
marker (full and empty layouts) and that the default render declares traces
present.
Goldens regenerated: the default (archival) render's metadata comment now carries
the new "every glyph and stroke carries a data-prov trace" clause, so the four
`.svg` goldens change by that one line (the snapshots, which omit the comment, do
not). Full gate green: build, fmt, clippy, 581 tests, conformance scale 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The layout metrics table claimed Bravura 1.38.0 while the renderer's outlines are
extracted from 1.392, so the advances/bboxes the engraver reserves space and
evaluates collisions from did not match the ink actually drawn (P12-I3). Both now
come from the SAME SHA-pinned bravura-1.392 font.
- BRAVURA_METRICS is regenerated from 1.392 by tools/extract_bravura_outlines.py
(the same font and script the outlines come from; the regenerated outlines are
byte-identical to the committed ones, and the extracted timeSig rows match the
values bundled earlier -- two integrity checks that the extraction is faithful).
Several old rows were not just stale but wrong: flag8thUp had its ink above the
origin when the glyph hangs below the stem tip; restWhole/restHalf and the
clefs had approximate or mis-signed bounds.
- The metric bbox is rounded OUTWARD from the outline bounds (floor the mins,
ceil the maxes) rather than to the nearest 1/1024, so the integer metric box
always CONTAINS the drawn outline -- the engraver evaluates collisions from
that box, and a containing box keeps a hard no-collision result honest on
paper. A new render-svg test (metric_bboxes_contain_the_drawn_outlines) proves
the containment for every bundled glyph, so a future re-extraction cannot
silently regress it.
- BRAVURA_VERSION is 1.38.0 -> the literal font version: name-table ID 5 reads
"Version 1.392" and head.fontRevision ~= 1.392 (a single decimal), recorded
verbatim as SemVer { major: 1, minor: 392, patch: 0 } so the identifier
round-trips to the font's own string. The canonical mapping rule is documented
on BRAVURA_VERSION; DECISIONS.md is updated to match.
The corrected metrics surfaced a coupled placement bug: the old barlineSingle
metric falsely centred the glyph (+/-2048), so the bottom-origin Bravura barline
(which runs 0..4 staff spaces UP from its origin) was anchored at staff-centre and
floated above the midline. It is now anchored at the staff bottom (yo), so a
barline connects the bottom and top staff lines.
Goldens regenerated (stub + engrave) for the corrected geometry. Full gate green:
build, fmt, clippy, 581 tests, conformance scale 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Turn the Score -> layout-IR -> SVG pipeline from placeholder glyphs into real
music notation, rendered through the stub solver and the real Engraver alike.
to_constrained (the spacing pass) now dispatches each layout object to the
notation primitive that represents it, on a column-based spring model:
- Pitch -> notehead at its clef-relative staff position; chord pitches share
one column slot. StaffInstance -> clef glyph. Staff -> five staff-line
strokes (the bottom line its anchor, four synthesized). Pitched Event ->
stem stroke; Measure -> barline glyph; rest Event -> rest glyph.
- Phase 3 ornaments as synthesized glyphs: a spelling's full accidental stack
left of its notehead, a key signature's clef-relative sharp/flat zigzag in
the lead, and a measure's numerator/denominator time-signature digit pair.
- A tied decomposition draws one notehead/stem/rest per component (offsets
honored, not collapsed). Active clef and key resolve by time, not vector
order. The lead area reserves clef + key-signature width.
- Coverage/surjection preserved so the round-trip holds: each laid-out object
is covered by exactly one exact-provenance primitive, and derived primitives
(staff lines, components, accidentals, key/time glyphs) are synthesized from
a laid-out source. Engraving-coverage gaps (missing spelling, unbundled
glyph) are surfaced as ConstrainedLayoutIR diagnostics, not silently
defaulted. A measure depends on the time signature it displays.
The Engraver re-spaces glyphs AND the strokes that track them through one
collision-aware coordinate map (per-slot left/right bearings, pairwise
advances), so stems / barlines / staff lines stay attached and a note's
accidental never overlaps the previous note. validate() now rejects empty
spring slots -- the contract that map relies on -- and to_constrained never
emits one (slots are realized by glyph occupancy).
Bundle the genuine Bravura outlines and metrics for time-signature digits 0-9
(regenerated from the SHA-pinned font via tools/extract_bravura_outlines.py),
replacing an inconsistent hand-written placeholder.
Regions tile left-to-right (no page casting-off yet). Goldens regenerated into
recognizable notation. Full gate green: build, fmt, clippy, 574 tests,
conformance scale 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lands the renderer-against-stub slice of Agent I's visible engraving work
(spec/PHASE2_QUICKSTART.md). Two new crates; prerequisites (G Pass 11, H
spelling/decomposition) are in place. Real engraving + Minimal-tier solver
follow next phase.
epiphany-render-svg (the deliverable this phase):
- Renders a ResolvedLayoutIR to well-formed SVG 1.1, drawing each glyph as a
GENUINE Bravura SMuFL outline <path>. Outlines are extracted reproducibly
from the official OFL Bravura.otf by a committed generator
(tools/extract_bravura_outlines.py, OFL.txt); the font is not vendored, only
the generated Rust (src/outlines_generated.rs). Staff-space/y-up coords with
one global y-flip wrapper; viewBox in staff spaces, px scale on the root.
- Non-overreach: every element traces to a ResolvedGlyph (data-prov) or a
declared wrapper; a glyph lacking an outline is surfaced as a diagnostic and
drawn as a fallback rect, never silently dropped.
- Hand-rolled xml::check_well_formed (no XML dep); acceptance tests cross-check
with system xmllint when present.
- examples/render_fixture.rs demo (fixture name -> SVG stdout, --solver=stub|real).
- Golden-locked machine acceptance snapshot + full-SVG golden for
ten_measure_single_staff and valid_score_rich; deterministic output.
epiphany-engrave (honest scaffold):
- Engraver: a deterministic horizontal-spacing pass (first axis of the planned
two-pass spring layout). Reports SolverTier::Stub — NOT Minimal — until it
evaluates the declared hard constraints, guarded by a regression test. The
demo's --solver=real exercises it end to end.
Honesty notes (recorded as Pass-12 candidates P12-I1..I3 in spec/PASS12_BATCH.md
and the crates' DECISIONS.md): the v0 to_logical/to_constrained pipeline is a
structural placeholder (arbitrary glyph per object, y=0), so stub output is not
yet recognizable notation and the QUICKSTART human visual gate is a next-phase
gate; MUSCLOID layout-id derivation stays unwired; bundled BRAVURA_METRICS are
approximations that disagree with the real outlines.
Gates: cargo fmt + clippy -D warnings clean; cargo test --workspace 504 passed,
0 failed, 0 ignored (criterion 6 layout round-trip still green).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>