Pass 13 reopens; P13-I1: complete the ConstrainedLayoutIR listing
Three parked candidates accumulated while the Standard-tier solver track closed and the notation-quality pass landed, which is the threshold at which the house rule opens a batch pass. PASS13_CANDIDATES.md reopens as Batch 2 with P13-I1 (this), P13-I2 (Staff::default_clef never consulted), P13-I3 (the notehead stem anchors). P13-I1 was filed as "two elided fields". Checking before writing turned up a third, and it is the one that matters: `diagnostics: Vec<LayoutDiagnostic>` appears NOWHERE in core_spec, though it is how the projection's honesty rule manifests. So Chapter 7's listing gains break_origins, diagnostics, and catalog; BreakOrigin and LayoutDiagnostic gain their shapes. And it gains req:layoutir:coverage-diagnostics, ratified as implemented: an object the projection cannot engrave faithfully -- a pitch with no resolved spelling, a glyph the bound catalog does not carry -- MUST be recorded as a LayoutDiagnostic AND still placed, as a fallback notehead or a zero-extent traced anchor. It MUST NOT silently substitute a plausible shape, and MUST NOT drop the object. Both halves matter: dropping it breaks the round-trip surjection, so a hit-test can no longer find what the author wrote; guessing produces a score that looks engraved and is wrong, with nothing in the IR to say so. Locked by an_unengravable_object_is_recorded_and_still_placed, which drives a percussion clef (no bundled glyph) and asserts all three halves: the diagnostic names the object, no glyph stands in for it, and a zero-extent anchor keeps its provenance addressable. Spec rebuilds clean, no undefined references, new label registered. No code change beyond the test; zero golden churn. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
24b6a34db9
commit
b7dab0fd44
|
|
@ -682,7 +682,7 @@ strokes and curves.
|
|||
cross-staff slur is not drawn at this tier (`staff.is_some()` guards the curve;
|
||||
it engraves to an anchor stroke), so a drawn curve always names a staff band.
|
||||
|
||||
## Parked: the ConstrainedLayoutIR listing is still abridged (2026-07-09)
|
||||
## RESOLVED (P13-I1): the ConstrainedLayoutIR listing was abridged (2026-07-09)
|
||||
|
||||
Chapter 7's `ConstrainedLayoutIR` listing gained `strokes` / `curves` when
|
||||
`req:layoutir:primitive-band-ownership` landed (that requirement depends on
|
||||
|
|
@ -698,6 +698,18 @@ Neither blocks an implementation the way a missing `strokes`/`curves` did: both
|
|||
are governed by requirement text elsewhere, so a conformant implementer is not
|
||||
left guessing.
|
||||
|
||||
**Resolved as P13-I1.** Filing it turned up a *third* elided field, and the
|
||||
substantive one: `diagnostics: Vec<LayoutDiagnostic>` appeared **nowhere** in
|
||||
core_spec, though it is how the projection's honesty rule manifests. The listing
|
||||
now carries all three, `BreakOrigin` and `LayoutDiagnostic` gain their shapes, and
|
||||
Chapter 7 gains `req:layoutir:coverage-diagnostics`: an object the projection
|
||||
cannot engrave faithfully MUST be recorded *and* still placed — a fallback
|
||||
notehead or a zero-extent traced anchor — never silently substituted, never
|
||||
dropped. Dropping it breaks the round-trip surjection (a hit-test can no longer
|
||||
find what the author wrote); guessing produces a score that looks engraved and is
|
||||
wrong, with nothing in the IR to say so. Ratified as implemented; locked by
|
||||
`an_unengravable_object_is_recorded_and_still_placed`.
|
||||
|
||||
## Parked: `Staff::default_clef` is never consulted (2026-07-09)
|
||||
|
||||
`to_constrained` takes a staff instance's active clef from its `clef_sequence`
|
||||
|
|
|
|||
|
|
@ -4808,6 +4808,61 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
/// `req:layoutir:coverage-diagnostics`: an object the projection cannot
|
||||
/// engrave faithfully is **recorded and still placed** — never guessed at,
|
||||
/// never dropped. A percussion clef has no bundled glyph, so the staff
|
||||
/// instance engraves to a zero-extent traced anchor that keeps its
|
||||
/// provenance (a hit-test can still find it) while an `UnbundledGlyph`
|
||||
/// diagnostic names the gap.
|
||||
#[test]
|
||||
fn an_unengravable_object_is_recorded_and_still_placed() {
|
||||
let (mut score, _) = repeat_ready_score(47);
|
||||
let instance_id = score.canvas.regions[0].staff_instances()[0].id;
|
||||
score.canvas.regions[0]
|
||||
.content
|
||||
.staff_instances_mut()
|
||||
.expect("staff-based")[0]
|
||||
.clef_sequence
|
||||
.push(epiphany_core::ClefChange {
|
||||
anchor: TimeAnchor::WallClock {
|
||||
time: epiphany_core::WallClockTime(0),
|
||||
},
|
||||
clef: epiphany_core::Clef {
|
||||
shape: epiphany_core::ClefShape::Percussion,
|
||||
line: 3,
|
||||
octave_shift: 0,
|
||||
},
|
||||
});
|
||||
let c = to_constrained(&to_logical(&score));
|
||||
let source = TypedObjectId::StaffInstance(instance_id);
|
||||
|
||||
// Recorded: the gap names the object and the glyph it wanted.
|
||||
let diagnostic = c
|
||||
.diagnostics
|
||||
.iter()
|
||||
.find(|d| d.source == source)
|
||||
.expect("the unbundled clef is surfaced, not hidden");
|
||||
assert!(
|
||||
matches!(diagnostic.kind, LayoutDiagnosticKind::UnbundledGlyph(_)),
|
||||
"and says why: {:?}",
|
||||
diagnostic.kind
|
||||
);
|
||||
|
||||
// Not guessed: no glyph stands in for the clef.
|
||||
assert!(
|
||||
!c.glyphs.iter().any(|g| g.provenance.source == source),
|
||||
"no plausible substitute is drawn"
|
||||
);
|
||||
// Not dropped: a traced anchor keeps its provenance addressable.
|
||||
let anchor = c
|
||||
.strokes
|
||||
.iter()
|
||||
.find(|st| st.provenance.source == source)
|
||||
.expect("the object is still placed, as a traced anchor");
|
||||
assert_eq!(anchor.from, anchor.to, "a zero-extent anchor draws no ink");
|
||||
assert_eq!(anchor.thickness.0, 0.0);
|
||||
}
|
||||
|
||||
/// A stem points AWAY from the middle line — up for a head below it, down
|
||||
/// for a head above it or on it — and attaches on the side it points: an
|
||||
/// up-stem at the head's right edge, a down-stem at its left. A stem on a
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ per the house rule (a batch pass opens at ≥3 candidates; this file opened
|
|||
when P13-D1/D2 joined P13-K1). Each entry names the owning DECISIONS record;
|
||||
this file is the index, not the analysis.
|
||||
|
||||
**Batch CLOSED (2026-07-08).** All four candidates are resolved (worked down in
|
||||
order): P13-D3 and P13-K1 by the user's ratified calls ("fix the mint only" /
|
||||
"reject the introduction"), P13-D1 and P13-D2 as correctness fixes with
|
||||
convergence-locked / execute-then-fix regressions. No open Pass-13 candidates
|
||||
remain; a future ≥3-candidate batch reopens the pass.
|
||||
## Batch 1 — CLOSED (2026-07-08)
|
||||
|
||||
All four candidates are resolved (worked down in order): P13-D3 and P13-K1 by
|
||||
the user's ratified calls ("fix the mint only" / "reject the introduction"),
|
||||
P13-D1 and P13-D2 as correctness fixes with convergence-locked /
|
||||
execute-then-fix regressions.
|
||||
|
||||
| Id | One-line statement | Filed in | Status |
|
||||
|---|---|---|---|
|
||||
|
|
@ -17,3 +18,18 @@ remain; a future ≥3-candidate batch reopens the pass.
|
|||
| P13-D1 | Undo-driven event tombstones run graph-side re-anchor/cascade but never ledger-side `reanchor_for_tombstone`: structures leave the graph while staying `Live`, no `RepairRecord` — Ch6's same-step recording MUST is unmet for undo-driven tombstones (pre-existing class: slurs/spanners; repeats now too) | `crates/epiphany-ops/DECISIONS.md` (Schema major 2, Phase D) | **resolved** (Pass 13: `tombstone_undo_targets` runs the ledger re-anchor per event target; liveness guard; convergence-locked) |
|
||||
| P13-D2 | Cue-cascade recursion re-anchors against the triggering event before its tombstone lands in `objects`: a structure anchored on {X, cue-of-X} can record `Reanchored{to: X}` then `CascadeDeleted` in one effect (contradictory repair trail; plausible by code trace, unexecuted) | `crates/epiphany-ops/DECISIONS.md` (Schema major 2, Phase D) | **resolved** (Pass 13: `delete_event` tombstones before the graph delete, matching `cascade_cue`/undo; repro executed then fixed) |
|
||||
| P13-D3 | `CreateCrossCutting` validates only event endpoints (`CrossCuttingValue::endpoints()`), so a SPANNER anchored to a missing region/measure mints dangling past `anchor_target_exists`; and non-event referent tombstones (`DeleteRegion` under a region-anchored spanner/repeat) re-anchor nothing — "every referenced endpoint is live" is events-only as implemented | `crates/epiphany-ops/DECISIONS.md` (Phase D follow-up) | **resolved** (Pass 13: mint fixed via `anchor_object_refs`; non-event referent re-anchoring ratified events-only, user "fix the mint only") |
|
||||
|
||||
## Batch 2 — OPEN (2026-07-09)
|
||||
|
||||
Three candidates accumulated while the Standard-tier solver track closed and the
|
||||
notation-quality pass (stems, slurs) landed. All three were **parked** as they
|
||||
were found, each in `crates/epiphany-layout-ir/DECISIONS.md`, and reaching three
|
||||
reopens the pass per the house rule. None is a live incorrectness in shipped
|
||||
output; each is a place where the code, the spec, and the data disagree about
|
||||
what is true.
|
||||
|
||||
| Id | One-line statement | Filed in | Status |
|
||||
|---|---|---|---|
|
||||
| P13-I1 | Chapter 7's `ConstrainedLayoutIR` listing elides **three** fields the code carries: `break_origins: Vec<BreakOrigin>` (named by `req:layoutir:break-origin-attribution`, its own shape unlisted), `catalog: GlyphCatalogIdentity` (its type specified, the field unlisted), and `diagnostics: Vec<LayoutDiagnostic>` — which appears **nowhere** in core_spec, though it is how the projection's honesty rule manifests: an unspellable pitch or an unbundled glyph is placed as a fallback *and recorded*, never silently guessed | `crates/epiphany-layout-ir/DECISIONS.md` ("the ConstrainedLayoutIR listing is still abridged") | **resolved** (Pass 13: listing gains all three fields; `BreakOrigin` and `LayoutDiagnostic` shapes added; new `req:layoutir:coverage-diagnostics` ratifies as-implemented that an unengravable object is recorded AND still placed — never guessed, never dropped) |
|
||||
| P13-I2 | `Staff::default_clef` is never consulted: `to_constrained` takes the active clef from the staff instance's `clef_sequence` and falls back to `Clef::default()` (treble), so a bass-clef staff that declares its clef only on the `Staff` engraves as treble. The field is decorative in the projection — is it the fallback, or should it not exist? | `crates/epiphany-layout-ir/DECISIONS.md` ("`Staff::default_clef` is never consulted") | **open** |
|
||||
| P13-I3 | `BRAVURA_METRICS`' `NOTEHEAD_ANCHORS` are hand-written, unconsumed, and doubly suspect: they name `stemUpNW`/`stemDownSE` — the corners a normal notehead's stems do *not* attach to, and a pair Bravura's `noteheadBlack` does not define — 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). They enter only `metrics_hash`, so any correction moves the `GlyphCatalogIdentity` every conformance claim declares. The font is not vendored, so the values cannot be verified in-tree | `crates/epiphany-layout-ir/DECISIONS.md` ("the notehead stem anchors are unusable as written") | **open** |
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -9138,6 +9138,27 @@ pub enum VerticalBandKind {
|
|||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\begin{requirement}
|
||||
\label{req:layoutir:coverage-diagnostics}
|
||||
\textbf{Engraving-coverage gaps are surfaced, not guessed.} A projection
|
||||
that cannot engrave an object faithfully --- a pitch with no resolved
|
||||
spelling, a glyph the bound catalog
|
||||
(\ref{sec:layoutir:catalog}) does not carry --- \MUST{} record a
|
||||
\texttt{LayoutDiagnostic} naming the score-graph object and the kind of
|
||||
gap, and \MUST{} still place the object: a fallback notehead on the clef
|
||||
reference line, or a zero-extent traced anchor that keeps its provenance.
|
||||
It \MUSTNOT{} silently substitute a plausible shape, and it \MUSTNOT{}
|
||||
drop the object.
|
||||
|
||||
Both halves matter. Dropping the object breaks the round-trip surjection
|
||||
(\ref{sec:layoutir:provenance}) --- a hit-test can no longer find it, and
|
||||
an editor can no longer select what the author wrote. Guessing a shape
|
||||
produces a score that looks engraved and is wrong, with nothing in the IR
|
||||
to say so. A diagnostic is not an error: the layout is still
|
||||
\emph{renderable}, and a conformance claim is unaffected. It is the record
|
||||
that the engraving is incomplete at a named place.
|
||||
\end{requirement}
|
||||
|
||||
\begin{requirement}
|
||||
\label{req:layoutir:primitive-band-ownership}
|
||||
\textbf{Primitive band ownership is declared, not inferred.} Every
|
||||
|
|
@ -9207,8 +9228,43 @@ pub struct ConstrainedLayoutIR {
|
|||
/// Additional constraints not captured by spring parameters.
|
||||
pub constraints: Vec<LayoutConstraint>,
|
||||
|
||||
/// Which user break override each projected break constraint came
|
||||
/// from, so a solver can cite it in the decision it records.
|
||||
pub break_origins: Vec<BreakOrigin>,
|
||||
|
||||
/// Engraving decisions, carried forward from the logical stage.
|
||||
pub engraving_decisions: Vec<EngravingDecision>,
|
||||
|
||||
/// Engraving-coverage gaps, surfaced rather than hidden.
|
||||
pub diagnostics: Vec<LayoutDiagnostic>,
|
||||
|
||||
/// The glyph catalog this IR's metrics were drawn from.
|
||||
pub catalog: GlyphCatalogIdentity,
|
||||
}
|
||||
|
||||
/// A projected break constraint's user-override attribution.
|
||||
pub struct BreakOrigin {
|
||||
pub slot: SpringSlotId,
|
||||
pub class: BreakClass,
|
||||
pub override_id: EngravingOverrideId,
|
||||
}
|
||||
|
||||
/// An object the projection could not engrave faithfully.
|
||||
pub struct LayoutDiagnostic {
|
||||
pub source: TypedObjectId,
|
||||
pub kind: LayoutDiagnosticKind,
|
||||
}
|
||||
|
||||
pub enum LayoutDiagnosticKind {
|
||||
/// A pitch reached the constrained pass with no resolved (or
|
||||
/// non-CMN) spelling; its notehead is placed on the clef
|
||||
/// reference line, but its true staff position is unknown.
|
||||
MissingSpelling,
|
||||
|
||||
/// A glyph the bound catalog does not carry; the object is
|
||||
/// carried as a traced anchor rather than drawn at a guessed
|
||||
/// shape.
|
||||
UnbundledGlyph(GlyphReference),
|
||||
}
|
||||
|
||||
pub enum LayoutConstraint {
|
||||
|
|
|
|||
Loading…
Reference in New Issue