Commit Graph

2 Commits

Author SHA1 Message Date
Levi Neuwirth 91a99b2093 Stop the conformance suite from authoring the frozen operation
req:opcat:transpose-frozen says a conforming implementation MUST NOT emit
Transpose from new authoring. testkit's editloop::sharpen -- the [7c] UI-seam
gate's editing action -- was emitting exactly that, so the conformance suite
itself would have violated the requirement the same push introduced. It now
authors TransposeInterval with (0, +1): the same alteration shift, the same
staff line, the same rendered result.

The random-kind corpora in generators.rs keep producing the frozen Transpose,
and should: it must reduce correctly forever, and a generator is now the only
thing that will ever produce one. The distinction is authoring versus replay.

This is not inert. TransposeInterval also writes the Propagated spelling
attachment the frozen operation never did, so the edited score genuinely
changes shape. the_sharpen_records_the_spelling_it_propagated pins that, and
was mutation-verified by removing graph_propagate_spelling.

Gate: fmt clean, clippy 0, 30 targets / 983 passed / 0 failed, docs 0 under
-D warnings, conformance 8/8, zero golden churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:50:22 -04:00
Levi Neuwirth bdab6dee1f testkit: an editing-loop vertical slice across the ops/layout/render seams
One iteration of an interactive edit, wired end to end so a missing connection
between the crates surfaces here, not in a GUI: render a score to its RenderIR,
resolve a click on a notehead to its graph pitch through the hit-test map, apply a
real operation (sharpen -- a +1-chromatic Transpose) by reducing it onto the score
graph, re-render, and confirm the selection survives the relayout.

  - run_edit_loop_with<S: ConstraintSolver>(base, solver) returns an EditLoopReport
    (selected_pitch, selection, graph_changed, selection_preserved, render_changed);
    run_edit_loop is the stub wrapper. render_with returns None for a
    diagnostic-only (non-renderable) solver report, so the loop never hit-tests or
    edits a layout the caller must not render.
  - The click resolves like a GUI's: aim at each real notehead's centre (a notehead
    glyph, not synthesized, pitch-backed) and select whatever the topmost hit there
    is, taking the first aim whose topmost hit is a pitch-backed glyph -- faithful
    to a click and robust to an occluding unison/chord notehead.
  - Selection survival rests on the MUSCLOID layout id, a function of the pitch's
    identity (PitchId), not its content: the sharpen changes the pitch's value but
    not its id, so its layout object keeps the same stable id and the cursor does
    not jump off the edited note. The prepass re-runs inside to_logical, so the
    edited pitch is re-spelled and its accidental reflects the new value (why the
    re-render reliably differs).

Tests drive the loop on valid_score_rich (one fixture and across 48 seeds, every
seed required), refuse a diagnostic-only solver layout, and -- in epiphany-engrave,
via its existing testkit dev-dep -- run it through the real Engraver across 16
seeds, proving the selection survives even as the Engraver re-spaces every glyph.
Full gate green: build, fmt, clippy, conformance scale 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:25:17 -04:00