ResolvedLayoutIR carried a page/system tree and three flat primitive arrays with nothing joining them, so a canvas wanting to re-tessellate one damaged system had to infer ownership spatially — ambiguous exactly where it matters, at cross-system slurs and boundary-straddling strokes. The partition was never missing, though: casting-off computes it (system_of_slot, stroke_system, curve_system) and the fold into ResolvedLayoutIR dropped it. This publishes it instead of inferring it, so the answer is exact. ResolvedSystem gains PrimitiveIndices — u32 index lists into the layout's flat glyphs/strokes/curves — and ResolvedLayoutIR gains an unowned bucket of the same shape. No primitive is split, merged, reordered, or renumbered; the flat arrays are exactly what they were. The partition is total and disjoint: for each array, every system's list plus the unowned bucket covers 0..len exactly once, tested directly rather than assumed from construction. Unowned is a first-class bucket with a real producer. The stub solver resolves no per-system geometry — one default-rect system per region — so it publishes every primitive unowned rather than fabricating an attribution it never computed. Cross-system primitives need no special case: casting-off already splits a spanning stroke or curve into per-system segments carrying SYSTEM_CONTINUATION_SYNTHESIS provenance, and each segment is owned by the system it was split into, not by the source's. Ownership is excluded from the canonical encoding, for the reason vertical_band already is: it draws nothing, so two layouts differing only in it are the same rendered layout and hash alike. Encoding it would make the fingerprint more fragile than the rendering it fingerprints — a casting-off refactor that re-partitions without moving a pixel would become a byte-level break for something no renderer and no conformance claim can observe. Verified against main: all eight reference fixtures produce byte-identical canonical layouts, and all five GUI goldens are untouched. One derivation, structurally. The quality census consumed its own copy of the glyph-to-system rule, and so did vertical_raw's system_of_glyph closure; both now read the published glyph_system. That left CastLayout::system_of_slot with no consumer outside the casting pass, so it is removed from the published struct — a future consumer cannot grow a third copy of the rule, because the raw material is no longer there. The per-glyph answer travels; the derivation does not. Six mutations, each killed and coordinator-re-verified independently: dropping an index from a system's list, coercing unowned onto system 0, an off-by-one system index, publishing a constant attribution, attributing a continuation to its source's system, and encoding ownership into the canonical bytes. The fourth is the one that proves the single-derivation claim rather than asserting it — it kills three quality tests, two of which survived it before vertical_raw was migrated. Gate: 34 suites / 1341 tests / 0 failed, conformance 9/9 with golden-gate and 8/8 without, requirement labels 6/6 at 212/282/282, clippy 0, five goldens byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| Cargo.toml | ||
| DECISIONS.md | ||
| README.md | ||
README.md
epiphany-engrave
Agent I's engraving constraint solver (spec Chapter 9): turns a
ConstrainedLayoutIR into a ResolvedLayoutIR with real geometry. It is the
production-side replacement for epiphany-layout-ir's interface-only
StubSolver; the two live in separate crates so the spec's core/product boundary
stays sharp.
Status: Minimal tier, with casting-off
Engraverruns a deterministic horizontal spacing pass (the first axis of the two-pass spring layout): each spring slot is placed left-to-right by a collision-aware advance derived from real glyph bearings.- A casting-off pass (Phase 3's layout track) then breaks the spaced line
into systems at measure boundaries (greedy first-fit against a
PageGeometry— default A4 portrait at an 8 mm staff), stacks systems vertically at the vertical-band model's inter-system gap, assigns pages by content height, and populates the realResolvedPage/ResolvedSystemtree. Every position is baked into a single y-up world frame (pages stacked vertically), so the SVG renderer and hit-testing consume the flat glyph/stroke lists unchanged. - The IR's declared constraints are evaluated — geometric families against
the pre-casting spaced frame, break constraints against the final break
structure (hard breaks are always honoured; a pathological soft break is
skipped and recorded as an
IrOverridedecision). Chosen breaks are recorded asEngravingDecisions withSynthesisKind::EngravedBreaktargets, attributed to the user override that requested them when one did. - It reports
SolverTier::Minimal: hard constraints (break family included) satisfied, no optimality claim — the quality-metric vector stays the honest all-worst placeholder until the Quality Metric Catalog lands.
Deferred: the vertical soft-spring solve, per-system justification/stretch,
optimal break search, widow/orphan control, and casting-off caching. See
DECISIONS.md.
use epiphany_engrave::Engraver;
use epiphany_layout_ir::{ConstraintSolver, SolverConfig};
let report = Engraver::default().solve(&constrained_ir, &SolverConfig::default());
assert!(report.satisfied_hard_constraints);
let resolved = report.layout; // real pages/systems; hand to epiphany-render-svg