epiphany/crates/epiphany-testkit/src/layout_stub.rs

1627 lines
58 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! The layout round-trip harness (v0 acceptance criterion 6 — Chapter 7's IR
//! contract):
//!
//! > A score graph → LogicalLayoutIR → stub-solved ResolvedLayoutIR → RenderIR
//! > interface call completes without panic and without losing provenance
//! > back-references.
//!
//! **Agent E (`epiphany-layout-ir`) has landed.** This module was formerly a
//! faithful in-tree *stub* of the layout IR; per the QUICKSTART re-point note it
//! now drives the **real** crate, re-exporting its IR types behind the same
//! [`round_trip`] signature the acceptance suite already calls. The
//! provenance-preservation contract the harness asserts is implemented and
//! tested inside `epiphany-layout-ir` itself; the testkit keeps deterministic
//! generators for E's public types (Agent F's "generators for every public type
//! in A through E" mandate) and exercises the real `round_trip` on the testkit's
//! hand-off fixtures.
//!
//! The module name is retained so the harness entry point stays
//! `layout_stub::round_trip`.
// Re-export the real layout IR: the four stages, `TimeAxisModel`, the provenance
// back-references, the glyph-catalog identity, the engraving-decision and
// vertical-band models, the edit-barrier types, and the stub solver.
pub use epiphany_layout_ir::*;
use crate::rng::Rng;
// --- Deterministic generators for Agent E's public types (Agent F mandate) ---
/// A generated layout-object identifier value.
pub fn gen_layout_object_id(rng: &mut Rng) -> LayoutObjectId {
LayoutObjectId(((rng.next_u64() as u128) << 64) | rng.next_u64() as u128)
}
/// A synthesis kind (every variant, including the registered form).
pub fn gen_synthesis_kind(rng: &mut Rng) -> SynthesisKind {
match rng.below(7) {
0 => SynthesisKind::CancellationAccidental,
1 => SynthesisKind::KeySignatureNatural,
2 => SynthesisKind::GeneratedRest,
3 => SynthesisKind::EngravedBreak,
4 => SynthesisKind::MultimeasureRest,
5 => SynthesisKind::Cautionary,
_ => SynthesisKind::Registered(SynthesisRegistryId(
((rng.next_u64() as u128) << 64) | rng.next_u64() as u128,
)),
}
}
/// A time-axis model (every variant of the tagged enum).
pub fn gen_time_axis_model(rng: &mut Rng) -> TimeAxisModel {
match rng.below(4) {
0 => TimeAxisModel::Metric(MetricTimeAxis::default()),
1 => TimeAxisModel::Proportional(ProportionalTimeAxis {
duration_ns: rng.range(0, 1 << 40) as i64,
space_per_second: gen_staff_space(rng),
placements: vec![],
}),
2 => TimeAxisModel::Aleatoric(AleatoricTimeAxis::default()),
_ => TimeAxisModel::Registered(
TimeAxisRegistryId(((rng.next_u64() as u128) << 64) | rng.next_u64() as u128),
SerializedRegisteredAxis(vec![rng.next_u64() as u8]),
),
}
}
/// A provenance record with a random source and a few dependency
/// back-references, **internally consistent**: a synthesized provenance carries
/// `Some(kind)` *and* the matching `synthesized_layout_id`; a projected one
/// carries `None` *and* `stable_layout_id`. (It never sets a synthesis kind while
/// keeping the plain source-only id.)
pub fn gen_provenance(rng: &mut Rng) -> Provenance {
let source = crate::generators::typed_object_id(rng);
let mut dependencies = Vec::new();
for _ in 0..rng.range_usize(0, 3) {
dependencies.push(crate::generators::typed_object_id(rng));
}
if rng.boolean() {
let kind = gen_synthesis_kind(rng);
Provenance::synthesized(
source,
kind,
SynthesisInstanceKey(((rng.next_u64() as u128) << 64) | rng.next_u64() as u128),
dependencies,
)
} else {
Provenance::projected(source, dependencies)
}
}
/// A bundled SMuFL glyph name.
fn gen_glyph_name(rng: &mut Rng) -> &'static str {
BRAVURA_METRICS[rng.below(BRAVURA_METRICS.len() as u64) as usize]
.name
.as_ref()
}
/// A solver status (every variant).
pub fn gen_solve_status(rng: &mut Rng) -> SolveStatus {
*rng.choose(&[
SolveStatus::Solved,
SolveStatus::SolvedWithWarnings,
SolveStatus::PartialBudgetExhausted,
SolveStatus::Unsatisfiable,
SolveStatus::InternalError,
])
}
/// A glyph-catalog identity (the bundled Bravura identity).
pub fn gen_glyph_catalog_identity(_rng: &mut Rng) -> GlyphCatalogIdentity {
GlyphCatalogIdentity::default()
}
/// A 2-D staff-space point.
pub fn gen_point(rng: &mut Rng) -> Point {
// Spread points over a few hundred staff spaces at sub-grid resolution.
let coord = |r: &mut Rng| (r.range(0, 400_000) as f32) / 1000.0;
Point::new(coord(rng), coord(rng))
}
/// A logical engraving-content payload — every [`LayoutContent`] variant, so the
/// generator/fuzz surface exercises the enriched payloads, not just structural.
pub fn gen_layout_content(rng: &mut Rng) -> LayoutContent {
fn time(rng: &mut Rng) -> TimePoint {
if rng.boolean() {
TimePoint::Musical(epiphany_core::MusicalPosition(
epiphany_core::RationalTime::new(rng.range(0, 16) as i64, 4).expect("nonzero"),
))
} else {
TimePoint::WallClock(epiphany_core::WallClockTime(rng.range(0, 10_000) as i64))
}
}
fn duration(numerator: i64, denominator: i64) -> epiphany_core::MusicalDuration {
epiphany_core::MusicalDuration(
epiphany_core::RationalTime::new(numerator, denominator).expect("nonzero"),
)
}
fn component(rng: &mut Rng, offset: epiphany_core::MusicalDuration) -> PlacedComponent {
PlacedComponent {
offset,
component: epiphany_core::NotatedComponent {
base_value: *rng.choose(&[
epiphany_core::NoteValue::Whole,
epiphany_core::NoteValue::Half,
epiphany_core::NoteValue::Quarter,
epiphany_core::NoteValue::Eighth,
epiphany_core::NoteValue::Sixteenth,
]),
dots: rng.range(0, 4) as u8,
tuplet: None,
tied_to_next: rng.boolean(),
},
tuplet: None,
}
}
fn components(rng: &mut Rng) -> Vec<PlacedComponent> {
let mut out = vec![component(rng, epiphany_core::MusicalDuration::zero())];
if rng.boolean() {
out.push(component(rng, duration(1, 4)));
}
out
}
fn clefs(rng: &mut Rng) -> Vec<PlacedClef> {
if rng.boolean() {
vec![
PlacedClef {
time: time(rng),
clef: epiphany_core::Clef::treble(),
},
PlacedClef {
time: time(rng),
clef: epiphany_core::Clef::bass(),
},
]
} else {
Vec::new()
}
}
fn keys(rng: &mut Rng) -> Vec<PlacedKeySignature> {
if rng.boolean() {
vec![PlacedKeySignature {
time: time(rng),
key: epiphany_core::KeySignature::new(rng.range(0, 14) as i8 - 7)
.expect("generator stays in -7..=7"),
}]
} else {
Vec::new()
}
}
fn spelling(rng: &mut Rng) -> Option<epiphany_core::PitchSpelling> {
rng.boolean().then(|| {
let nominal = *rng.choose(&[
epiphany_core::CmnNominal::C,
epiphany_core::CmnNominal::D,
epiphany_core::CmnNominal::E,
epiphany_core::CmnNominal::F,
epiphany_core::CmnNominal::G,
epiphany_core::CmnNominal::A,
epiphany_core::CmnNominal::B,
]);
epiphany_core::PitchSpelling::cmn(nominal, rng.range(2, 7) as i8)
})
}
fn barline(rng: &mut Rng) -> BarlineKind {
match rng.below(3) {
0 => BarlineKind::Interior,
1 => BarlineKind::RegionEnd,
_ => BarlineKind::Final,
}
}
fn placement(rng: &mut Rng) -> RepeatPlacement {
match rng.below(3) {
0 => RepeatPlacement::At(time(rng)),
1 => RepeatPlacement::RegionEnd,
_ => RepeatPlacement::Unresolved,
}
}
match rng.below(6) {
0 => LayoutContent::Structural,
1 => LayoutContent::Staff(StaffContent {
default_clef: epiphany_core::Clef::default(),
clefs: clefs(rng),
keys: keys(rng),
}),
2 => LayoutContent::Note(NoteContent {
position: time(rng),
components: components(rng),
pitches: vec![NotePitch {
pitch: epiphany_core::PitchId::new(epiphany_core::ReplicaId(7), rng.next_u64()),
spelling: spelling(rng),
}],
}),
3 => LayoutContent::Rest(RestContent {
position: time(rng),
components: components(rng),
staff_position: rng
.boolean()
.then(|| epiphany_core::StaffPosition(rng.range(0, 9) as i16)),
}),
4 => LayoutContent::Measure(MeasureContent {
start: time(rng),
barline: barline(rng),
time_signature: rng.boolean().then(|| TimeSignatureContent {
numerator: rng.range(1, 13) as u16,
denominator: *rng.choose(&[2, 4, 8, 16]),
}),
}),
_ => LayoutContent::Repeat(RepeatContent {
barlines: rng.boolean(),
start: placement(rng),
end: placement(rng),
voltas: (0..rng.below(3))
.map(|index| VoltaContent {
endings: vec![index as u32 + 1],
start: placement(rng),
end: placement(rng),
})
.collect(),
}),
}
}
/// A logical layout object (provenance + an optional owning staff + content).
pub fn gen_layout_object(rng: &mut Rng) -> LayoutObject {
LayoutObject::from_projection_with_content(
gen_provenance(rng),
rng.boolean().then(|| crate::generators::staff_id(rng)),
gen_layout_content(rng),
)
}
/// A constrained glyph object (provenance + glyph + baseline anchor + band).
pub fn gen_glyph_object(rng: &mut Rng) -> GlyphObject {
let glyph_name = gen_glyph_name(rng);
GlyphObject {
provenance: gen_provenance(rng),
glyph: GlyphReference::borrowed(glyph_name),
horizontal_slot: gen_spring_slot_id(rng),
baseline: gen_point(rng),
vertical_band: gen_vertical_band_id(rng),
bounding_box: metrics(glyph_name)
.expect("generator chooses a bundled glyph")
.bounding_box(),
anchor: gen_point(rng),
layer: rng.range(0, 8) as i32,
style: GlyphStyle {
rgba: rng.next_u64() as u32,
},
}
}
/// A resolved glyph (provenance + glyph + definitive position).
pub fn gen_resolved_glyph(rng: &mut Rng) -> ResolvedGlyph {
ResolvedGlyph {
provenance: gen_provenance(rng),
glyph: GlyphReference::borrowed(gen_glyph_name(rng)),
position: gen_point(rng),
transform: None,
bounding_box: gen_glyph_bounding_box(rng),
style: GlyphStyle {
rgba: rng.next_u64() as u32,
},
layer: rng.range(0, 8) as i32,
}
}
/// A logical layout region (provenance + time axis + a few objects).
pub fn gen_layout_region(rng: &mut Rng) -> LayoutRegion {
let n = rng.range_usize(0, 4);
let region = crate::generators::region_id(rng);
let objects: Vec<LayoutObject> = (0..n).map(|_| gen_layout_object(rng)).collect();
let staves = objects.iter().filter_map(LayoutObject::staff).collect();
LayoutRegion {
provenance: Provenance::projected(epiphany_core::TypedObjectId::Region(region), vec![]),
coordinate_system: LocalCoordinateSystem::default(),
time_axis: gen_time_axis_model(rng),
vertical_extent: VerticalExtent { staves },
objects,
}
}
/// A logical layout IR (a few regions and engraving decisions).
pub fn gen_logical_layout_ir(rng: &mut Rng) -> LogicalLayoutIR {
let n = rng.range_usize(0, 3);
let d = rng.range_usize(0, 3);
LogicalLayoutIR {
source: ScoreVersion::default(),
regions: (0..n).map(|_| gen_layout_region(rng)).collect(),
engraving_decisions: (0..d).map(|_| gen_engraving_decision(rng)).collect(),
overrides: vec![],
cross_region: vec![],
}
}
/// A non-glyph line primitive (a staff line / stem / barline), so the
/// generator/fuzz surface exercises strokes alongside glyphs.
pub fn gen_stroke(rng: &mut Rng, band: VerticalBandId) -> Stroke {
Stroke {
provenance: gen_provenance(rng),
from: gen_point(rng),
to: gen_point(rng),
thickness: gen_staff_space(rng),
layer: rng.range(0, 8) as i32 - 4,
style: GlyphStyle {
rgba: rng.next_u64() as u32,
},
// A stroke names a band that exists; validation rejects a dangling one.
vertical_band: band,
}
}
/// A cubic-bézier curve primitive with generated provenance and geometry.
pub fn gen_curve(rng: &mut Rng, band: VerticalBandId) -> Curve {
Curve {
provenance: gen_provenance(rng),
p0: gen_point(rng),
p1: gen_point(rng),
p2: gen_point(rng),
p3: gen_point(rng),
thickness: gen_staff_space(rng),
layer: rng.range(0, 8) as i32 - 4,
style: GlyphStyle {
rgba: rng.next_u64() as u32,
},
line: *rng.choose(&[
epiphany_core::LineStyle::Solid,
epiphany_core::LineStyle::Dashed,
epiphany_core::LineStyle::Dotted,
]),
// A curve names a band that exists; validation rejects a dangling one.
vertical_band: band,
}
}
/// A constrained layout IR whose catalog hash covers exactly its glyph metrics
/// (so the real stub solver accepts it as well-formed), with an **internally
/// consistent** vertical band: every glyph names the band, and the band's
/// members are exactly those glyphs.
pub fn gen_constrained_layout_ir(rng: &mut Rng) -> ConstrainedLayoutIR {
let n = rng.range_usize(0, 8);
let mut glyphs: Vec<GlyphObject> = (0..n).map(|_| gen_glyph_object(rng)).collect();
// One band owning exactly these glyphs; point every glyph at it.
let band = VerticalBand::staff(
crate::generators::staff_id(rng),
glyphs.iter().map(|g| g.id()).collect(),
);
for g in &mut glyphs {
g.vertical_band = band.id;
}
let horizontal_slots: Vec<SpringSlot> = glyphs
.iter_mut()
.enumerate()
.map(|(index, glyph)| {
let id = SpringSlotId(glyph.id().0);
glyph.horizontal_slot = id;
SpringSlot {
id,
time: TimePoint::WallClock(epiphany_core::WallClockTime(index as i64)),
min_width: StaffSpace(1.0),
preferred_width: StaffSpace(1.5),
max_width: None,
stretch_factor: 1.0,
compress_factor: 1.0,
members: vec![glyph.id()],
}
})
.collect();
let metrics_hash = metrics_hash_for(glyphs.iter().map(|g| g.glyph.as_str()));
let decisions = rng.range_usize(0, 3);
ConstrainedLayoutIR {
source: ScoreVersion::default(),
regions: vec![],
horizontal_slots,
glyphs,
strokes: (0..rng.range_usize(0, 3))
.map(|_| gen_stroke(rng, band.id))
.collect(),
curves: (0..rng.range_usize(0, 3))
.map(|_| gen_curve(rng, band.id))
.collect(),
vertical_bands: vec![band],
constraints: vec![],
break_origins: vec![],
engraving_decisions: (0..decisions)
.map(|_| gen_engraving_decision(rng))
.collect(),
diagnostics: Vec::new(),
catalog: GlyphCatalogIdentity {
metrics_hash,
..GlyphCatalogIdentity::default()
},
}
}
/// A resolved layout IR produced by the real stub-solver interface.
pub fn gen_resolved_layout_ir(rng: &mut Rng) -> ResolvedLayoutIR {
StubSolver
.solve(&gen_constrained_layout_ir(rng), &SolverConfig::default())
.layout
}
pub fn gen_resolved_staff(rng: &mut Rng) -> ResolvedStaff {
let staff = crate::generators::staff_id(rng);
ResolvedStaff {
provenance: Provenance::projected(epiphany_core::TypedObjectId::Staff(staff), vec![]),
staff,
bounding_box: gen_rect(rng),
}
}
pub fn gen_resolved_measure(rng: &mut Rng) -> ResolvedMeasure {
let measure = crate::generators::measure_id(rng);
ResolvedMeasure {
provenance: Provenance::projected(epiphany_core::TypedObjectId::Measure(measure), vec![]),
measure,
bounding_box: gen_rect(rng),
}
}
pub fn gen_resolved_system(rng: &mut Rng) -> ResolvedSystem {
ResolvedSystem {
provenance: gen_provenance(rng),
bounding_box: gen_rect(rng),
staves: (0..rng.range_usize(0, 2))
.map(|_| gen_resolved_staff(rng))
.collect(),
measures: (0..rng.range_usize(0, 2))
.map(|_| gen_resolved_measure(rng))
.collect(),
// Not tied to any real flat array here (this generator builds an
// isolated `ResolvedSystem`, not a whole `ResolvedLayoutIR`), so a
// random index list would be equally fake; left empty rather than
// fabricated.
primitives: Default::default(),
}
}
pub fn gen_resolved_page(rng: &mut Rng) -> ResolvedPage {
ResolvedPage {
provenance: gen_provenance(rng),
number: rng.range(1, 500) as u32,
size: gen_size2d(rng),
margins: gen_margins(rng),
systems: (0..rng.range_usize(0, 2))
.map(|_| gen_resolved_system(rng))
.collect(),
free_objects: (0..rng.range_usize(0, 2))
.map(|_| gen_glyph_object_id(rng))
.collect(),
}
}
/// A render primitive with generated provenance, glyph, and geometry.
pub fn gen_render_primitive(rng: &mut Rng) -> RenderPrimitive {
RenderPrimitive {
provenance: gen_provenance(rng),
glyph: GlyphReference::borrowed(gen_glyph_name(rng)),
position: gen_point(rng),
transform: None,
bounding_box: gen_glyph_bounding_box(rng),
style: GlyphStyle {
rgba: rng.next_u64() as u32,
},
layer: rng.range(0, 8) as i32,
}
}
/// A RenderIR generated through the resolved-to-render projection.
pub fn gen_render_ir(rng: &mut Rng) -> RenderIR {
to_render(&gen_resolved_layout_ir(rng))
}
/// A complete solver report generated through the solver interface.
pub fn gen_solve_report(rng: &mut Rng) -> SolveReport {
StubSolver.solve(&gen_constrained_layout_ir(rng), &SolverConfig::default())
}
/// A self-consistent round-trip report value.
pub fn gen_round_trip_report(rng: &mut Rng) -> RoundTripReport {
let render = gen_render_ir(rng);
let recovered_sources = render
.primitives
.iter()
.map(|primitive| primitive.provenance.source)
.chain(render.strokes.iter().map(|stroke| stroke.provenance.source))
.chain(render.curves.iter().map(|curve| curve.provenance.source))
.collect();
let total = render.primitives.len() + render.strokes.len() + render.curves.len();
RoundTripReport {
status: SolveStatus::Solved,
logical_objects: total,
glyphs: render.primitives.len(),
render_strokes: render.strokes.len(),
render_curves: render.curves.len(),
render_primitives: total,
recovered_sources,
}
}
// --- Generators for the rest of E's public surface (Agent F mandate) ---
/// A staff-space measurement.
pub fn gen_staff_space(rng: &mut Rng) -> StaffSpace {
StaffSpace((rng.range(0, 400_000) as f32) / 1000.0)
}
/// A 2-D staff-space size.
pub fn gen_size2d(rng: &mut Rng) -> Size2D {
Size2D {
width: gen_staff_space(rng),
height: gen_staff_space(rng),
}
}
/// A staff-space bounding box.
pub fn gen_bounding_box(rng: &mut Rng) -> BoundingBox {
let c = |r: &mut Rng| (r.range(0, 8000) as f32) / 1000.0;
BoundingBox::new(c(rng), c(rng), c(rng), c(rng))
}
pub fn gen_rect(rng: &mut Rng) -> Rect {
Rect {
origin: gen_point(rng),
size: gen_size2d(rng),
}
}
pub fn gen_transform_2d(rng: &mut Rng) -> Transform2D {
let mut transform = Transform2D::default();
transform.matrix[0][2] = gen_staff_space(rng).0;
transform.matrix[1][2] = gen_staff_space(rng).0;
transform
}
pub fn gen_margins(rng: &mut Rng) -> Margins {
Margins {
top: gen_staff_space(rng),
right: gen_staff_space(rng),
bottom: gen_staff_space(rng),
left: gen_staff_space(rng),
}
}
pub fn gen_score_version(rng: &mut Rng) -> ScoreVersion {
let mut bytes = [0; 32];
for chunk in bytes.chunks_exact_mut(8) {
chunk.copy_from_slice(&rng.next_u64().to_le_bytes());
}
ScoreVersion(bytes)
}
pub fn gen_local_coordinate_system(rng: &mut Rng) -> LocalCoordinateSystem {
LocalCoordinateSystem {
transform: gen_transform_2d(rng),
}
}
pub fn gen_vertical_extent(rng: &mut Rng) -> VerticalExtent {
VerticalExtent {
staves: (0..rng.range_usize(0, 3))
.map(|_| crate::generators::staff_id(rng))
.collect(),
}
}
pub fn gen_cross_region_object(rng: &mut Rng) -> CrossRegionObject {
CrossRegionObject {
provenance: gen_provenance(rng),
regions: (0..rng.range_usize(1, 3))
.map(|_| crate::generators::region_id(rng))
.collect(),
staff: rng.boolean().then(|| crate::generators::staff_id(rng)),
}
}
pub fn gen_dependency_index(rng: &mut Rng) -> DependencyIndex {
let mut index = DependencyIndex::default();
for _ in 0..rng.range_usize(0, 4) {
index.insert(&gen_provenance(rng));
}
index
}
pub fn gen_layout_cache(rng: &mut Rng) -> LayoutCache {
LayoutCache {
dependencies: gen_dependency_index(rng),
logical: Default::default(),
constrained: Default::default(),
resolved: Default::default(),
fine_cache: FineLayoutCache::default(),
}
}
pub fn gen_system_id(rng: &mut Rng) -> SystemId {
SystemId(((rng.next_u64() as u128) << 64) | rng.next_u64() as u128)
}
pub fn gen_logical_region_cache(rng: &mut Rng) -> LogicalRegionCache {
LogicalRegionCache {
objects: (0..rng.range_usize(0, 3))
.map(|_| gen_layout_object_id(rng))
.collect(),
}
}
pub fn gen_constrained_region_cache(rng: &mut Rng) -> ConstrainedRegionCache {
ConstrainedRegionCache {
objects: (0..rng.range_usize(0, 3))
.map(|_| gen_layout_object_id(rng))
.collect(),
}
}
pub fn gen_resolved_system_cache(rng: &mut Rng) -> ResolvedSystemCache {
ResolvedSystemCache {
objects: (0..rng.range_usize(0, 3))
.map(|_| gen_layout_object_id(rng))
.collect(),
}
}
pub fn gen_fine_layout_cache(rng: &mut Rng) -> FineLayoutCache {
FineLayoutCache {
objects: (0..rng.range_usize(0, 3))
.map(|_| gen_layout_object_id(rng))
.collect(),
}
}
/// A glyph-object identifier value.
pub fn gen_glyph_object_id(rng: &mut Rng) -> GlyphObjectId {
GlyphObjectId(((rng.next_u64() as u128) << 64) | rng.next_u64() as u128)
}
pub fn gen_synthesis_instance_key(rng: &mut Rng) -> SynthesisInstanceKey {
SynthesisInstanceKey(((rng.next_u64() as u128) << 64) | rng.next_u64() as u128)
}
pub fn gen_glyph_style(rng: &mut Rng) -> GlyphStyle {
GlyphStyle {
rgba: rng.next_u64() as u32,
}
}
pub fn gen_time_point(rng: &mut Rng) -> TimePoint {
if rng.boolean() {
TimePoint::Musical(epiphany_core::MusicalPosition(
epiphany_core::RationalTime::new(rng.range(0, 16) as i64, 4)
.expect("positive denominator"),
))
} else {
TimePoint::WallClock(epiphany_core::WallClockTime(rng.next_u64() as i64))
}
}
pub fn gen_time_range(rng: &mut Rng) -> TimeRange {
if rng.boolean() {
let start = epiphany_core::MusicalPosition(
epiphany_core::RationalTime::new(rng.range(0, 8) as i64, 4)
.expect("positive denominator"),
);
TimeRange::Musical {
start: start.clone(),
end: start
+ epiphany_core::MusicalDuration(
epiphany_core::RationalTime::new(1, 4).expect("positive denominator"),
),
}
} else {
let start = epiphany_core::WallClockTime(rng.range(0, 10_000) as i64);
TimeRange::WallClock {
start,
end: epiphany_core::WallClockTime(start.0 + 1_000),
}
}
}
pub fn gen_spring_slot(rng: &mut Rng) -> SpringSlot {
SpringSlot {
id: gen_spring_slot_id(rng),
time: gen_time_point(rng),
min_width: StaffSpace(1.0),
preferred_width: StaffSpace(2.0),
max_width: rng.boolean().then_some(StaffSpace(3.0)),
stretch_factor: 1.0,
compress_factor: 1.0,
members: (0..rng.range_usize(0, 3))
.map(|_| gen_glyph_object_id(rng))
.collect(),
}
}
pub fn gen_constrained_layout_region(rng: &mut Rng) -> ConstrainedLayoutRegion {
let region = crate::generators::region_id(rng);
ConstrainedLayoutRegion {
provenance: Provenance::projected(epiphany_core::TypedObjectId::Region(region), vec![]),
glyphs: (0..rng.range_usize(0, 3))
.map(|_| gen_glyph_object_id(rng))
.collect(),
time_axis: gen_time_axis_model(rng),
}
}
pub fn gen_layout_constraint(rng: &mut Rng) -> LayoutConstraint {
match rng.below(6) {
0 => LayoutConstraint::NoCollision {
a: gen_glyph_object_id(rng),
b: gen_glyph_object_id(rng),
},
1 => LayoutConstraint::Align {
a: gen_glyph_object_id(rng),
b: gen_glyph_object_id(rng),
axis: if rng.boolean() {
Axis::Horizontal
} else {
Axis::Vertical
},
},
2 => LayoutConstraint::PositionWithin {
glyph: gen_glyph_object_id(rng),
region: gen_rect(rng),
},
3 => LayoutConstraint::SystemBreakAt {
slot: gen_spring_slot_id(rng),
kind: if rng.boolean() {
BreakKind::Hard
} else {
BreakKind::Soft
},
},
4 => LayoutConstraint::PageBreakAt {
slot: gen_spring_slot_id(rng),
kind: if rng.boolean() {
BreakKind::Hard
} else {
BreakKind::Soft
},
},
_ => LayoutConstraint::Registered(
ConstraintRegistryId(rng.next_u64() as u128),
ConstraintParameters(vec![rng.next_u64() as u8]),
),
}
}
/// A constraint strength (both variants).
pub fn gen_constraint_strength(rng: &mut Rng) -> ConstraintStrength {
if rng.boolean() {
ConstraintStrength::Required
} else {
ConstraintStrength::Preferred {
weight: (rng.range(1, 1_000) as f64) / 100.0,
}
}
}
/// A bundled glyph metric (drawn from the in-tree Bravura table).
pub fn gen_glyph_metric(rng: &mut Rng) -> GlyphMetric {
BRAVURA_METRICS[rng.below(BRAVURA_METRICS.len() as u64) as usize].clone()
}
/// A SMuFL version. `minor_centi` is fraction-normalized (P13-S12): a small
/// integer sampled directly (as the previous generator did) would denote
/// 1.00-1.05 as hundredths, versions SMuFL never released. Instead sample the
/// minor digit string from real SMuFL minor releases and normalize it through
/// [`SmuflVersion::from_decimal`], the same constructor production code uses,
/// so the generator can never emit a value the type's own invariant forbids.
pub fn gen_smufl_version(rng: &mut Rng) -> SmuflVersion {
const MINOR_RELEASES: [&str; 5] = ["12", "18", "20", "3", "4"];
let major = rng.range(1, 2) as u16;
let minor_digits = MINOR_RELEASES[rng.below(MINOR_RELEASES.len() as u64) as usize];
SmuflVersion::from_decimal(major, minor_digits).expect("a real SMuFL minor release")
}
/// A font identifier (the bundled font).
pub fn gen_font_id(rng: &mut Rng) -> FontId {
if rng.boolean() {
FontId::BRAVURA
} else {
FontId::owned(format!("runtime-font-{}", rng.next_u64()))
}
}
/// A decision source (every variant).
pub fn gen_decision_source(rng: &mut Rng) -> DecisionSource {
match rng.below(3) {
0 => DecisionSource::Automatic,
1 => DecisionSource::UserOverride(EngravingOverrideId(rng.next_u64() as u128)),
_ => DecisionSource::IrOverride,
}
}
/// An engraving-decision kind (a representative subset).
pub fn gen_engraving_decision_kind(rng: &mut Rng) -> EngravingDecisionKind {
match rng.below(5) {
0 => EngravingDecisionKind::StemDirection(if rng.boolean() {
StemDirection::Up
} else {
StemDirection::Down
}),
1 => EngravingDecisionKind::LedgerLineCount(rng.range(0, 5) as u8),
2 => EngravingDecisionKind::SystemBreak,
3 => EngravingDecisionKind::PageBreak,
_ => EngravingDecisionKind::Registered(EngravingDecisionRegistryId(rng.next_u64() as u128)),
}
}
/// An engraving-decision record with a content-derived id.
pub fn gen_engraving_decision(rng: &mut Rng) -> EngravingDecision {
EngravingDecision::with_source(
gen_layout_object_id(rng),
gen_engraving_decision_kind(rng),
gen_decision_source(rng),
)
}
pub fn gen_override_target(rng: &mut Rng) -> OverrideTarget {
if rng.boolean() {
OverrideTarget::ScoreGraph(crate::generators::typed_object_id(rng))
} else {
OverrideTarget::IrSynthesized(gen_layout_object_id(rng))
}
}
/// A break anchor for a generated break override (a representative subset: a
/// wall-clock instant or an event anchor without offset).
pub fn gen_break_anchor(rng: &mut Rng) -> epiphany_core::TimeAnchor {
if rng.boolean() {
epiphany_core::TimeAnchor::WallClock {
time: epiphany_core::WallClockTime(rng.range(0, 10_000) as i64),
}
} else {
epiphany_core::TimeAnchor::Event {
id: epiphany_core::EventId::from_raw(rng.next_u64() as u128),
offset: epiphany_core::AnchorOffset::Zero,
}
}
}
pub fn gen_override_kind(rng: &mut Rng) -> OverrideKind {
match rng.below(9) {
0 => OverrideKind::StemDirection(if rng.boolean() {
epiphany_core::StemDirection::Up
} else {
epiphany_core::StemDirection::Down
}),
1 => OverrideKind::AccidentalParenthesized(rng.boolean()),
2 => OverrideKind::AccidentalVisible(rng.boolean()),
3 => OverrideKind::SystemBreak {
anchor: gen_break_anchor(rng),
},
4 => OverrideKind::PageBreak {
anchor: gen_break_anchor(rng),
},
5 => OverrideKind::HiddenObject,
6 => OverrideKind::CustomPosition(gen_point(rng)),
7 => OverrideKind::LedgerLineSuppression,
_ => OverrideKind::Registered(rng.next_u64() as u128),
}
}
pub fn gen_engraving_override(rng: &mut Rng) -> EngravingOverride {
EngravingOverride {
id: EngravingOverrideId(rng.next_u64() as u128),
target: gen_override_target(rng),
kind: gen_override_kind(rng),
priority: if rng.boolean() {
OverridePriority::Hard
} else {
OverridePriority::Soft
},
origin: match rng.below(4) {
0 => OverrideOrigin::User {
author: AuthorId(rng.next_u64() as u128),
timestamp: Timestamp(rng.next_u64() as i64),
},
1 => OverrideOrigin::Import {
format: ForeignFormatId(rng.next_u64() as u128),
},
2 => OverrideOrigin::Plugin {
plugin: PluginId(rng.next_u64() as u128),
},
_ => OverrideOrigin::Internal,
},
}
}
/// A vertical-band kind (every variant).
pub fn gen_vertical_band_kind(rng: &mut Rng) -> VerticalBandKind {
match rng.below(4) {
0 => VerticalBandKind::Staff(crate::generators::staff_id(rng)),
1 => VerticalBandKind::InterStaffGap,
2 => VerticalBandKind::InterSystemGap,
_ => VerticalBandKind::MarginBand,
}
}
/// A vertical band (every constructor / kind exercised).
pub fn gen_vertical_band(rng: &mut Rng) -> VerticalBand {
let members: Vec<GlyphObjectId> = (0..rng.range_usize(0, 3))
.map(|_| gen_glyph_object_id(rng))
.collect();
match rng.below(4) {
0 => VerticalBand::staff(crate::generators::staff_id(rng), members),
1 => VerticalBand::margin(LayoutObjectId(rng.next_u64() as u128), members),
2 => VerticalBand::inter_staff_gap(gen_vertical_band_id(rng)),
_ => VerticalBand::inter_system_gap(gen_vertical_band_id(rng)),
}
}
/// An operation-kind tag (every variant Agent C's type provides, including the
/// registered form).
///
/// Genesis tranche G2a (row 29): **derived**, not hand-maintained. The prior
/// hand-written `match` had already gone stale twice — it stopped at 23 until
/// the Phase-3 tranche and again omitted the repeat pair — because nothing
/// forces a hand-written arm list to move when the vocabulary grows, even
/// though this generator's own doc promises *every* variant. Built-ins are
/// drawn uniformly from `OperationKindTag::PAYLOAD_FREE`
/// (`operation_kind_tag_vocabulary!`'s single source of truth, `payload.rs`) —
/// `Registered` is excluded from `PAYLOAD_FREE` by design (it carries a
/// payload, `payload.rs:469`) and is appended here explicitly. Every future
/// built-in append then follows structurally, with no edit to this function;
/// see `OperationKind::discriminant()`'s in-crate precedent
/// (`payload.rs:2052`, `all_tags`).
pub fn gen_operation_kind_tag(rng: &mut Rng) -> OperationKindTag {
// +1 slot for `Registered`, drawn last.
let index = rng.below(OperationKindTag::PAYLOAD_FREE.len() as u64 + 1) as usize;
match OperationKindTag::PAYLOAD_FREE.get(index) {
Some(tag) => *tag,
None => OperationKindTag::Registered(epiphany_ops::OperationKindRegistryId(
rng.next_u64() as u128
)),
}
}
/// An object kind (the kind of a generated score-graph object).
pub fn gen_object_kind(rng: &mut Rng) -> ObjectKind {
ObjectKind::of(&crate::generators::typed_object_id(rng))
}
/// A pitch-space id drawn from the built-in catalog.
fn gen_pitch_space_id(rng: &mut Rng) -> epiphany_core::PitchSpaceId {
let names = ["cmn-12", "edo-31", "ji-5limit"];
epiphany_core::PitchSpaceId::new(names[rng.below(names.len() as u64) as usize])
}
/// A barrier scope (every variant).
pub fn gen_barrier_scope(rng: &mut Rng) -> BarrierScope {
match rng.below(8) {
0 => BarrierScope::WholeScore,
1 => BarrierScope::Region(crate::generators::region_id(rng)),
2 => BarrierScope::StaffInstance(crate::generators::staff_instance_id(rng)),
3 => BarrierScope::AnalysisLayer(crate::generators::analysis_layer_id(rng)),
4 => BarrierScope::ObjectSet(
(0..rng.range_usize(0, 3))
.map(|_| crate::generators::typed_object_id(rng))
.collect(),
),
5 => BarrierScope::PitchSpace(gen_pitch_space_id(rng)),
6 => BarrierScope::TuningContext,
_ => BarrierScope::Registered(BarrierScopeRegistryId(rng.next_u64() as u128)),
}
}
/// A barrier condition (bounded recursion depth; every variant).
pub fn gen_barrier_condition(rng: &mut Rng) -> BarrierCondition {
match rng.below(7) {
0 => BarrierCondition::Always,
1 => BarrierCondition::ObjectExists(crate::generators::typed_object_id(rng)),
2 => BarrierCondition::ObjectHasExtensionData {
object: crate::generators::typed_object_id(rng),
extension: ExtensionRef(rng.next_u64() as u128),
},
3 => BarrierCondition::All(vec![BarrierCondition::Always]),
4 => BarrierCondition::Any(vec![
BarrierCondition::Always,
BarrierCondition::ObjectExists(crate::generators::typed_object_id(rng)),
]),
5 => BarrierCondition::Not(Box::new(BarrierCondition::Always)),
_ => BarrierCondition::Registered(BarrierConditionRegistryId(rng.next_u64() as u128)),
}
}
/// An edit barrier keyed on operation-kind tags.
pub fn gen_edit_barrier(rng: &mut Rng) -> EditBarrier {
EditBarrier {
scope: gen_barrier_scope(rng),
affected_object_kinds: (0..rng.range_usize(0, 3))
.map(|_| gen_object_kind(rng))
.collect(),
prohibited_operation_kinds: (0..rng.range_usize(0, 3))
.map(|_| gen_operation_kind_tag(rng))
.collect(),
condition: gen_barrier_condition(rng),
}
}
/// An edit context (an object's structural location, every field exercised).
pub fn gen_edit_context(rng: &mut Rng) -> EditContext {
EditContext {
region: rng.boolean().then(|| crate::generators::region_id(rng)),
staff_instance: rng
.boolean()
.then(|| crate::generators::staff_instance_id(rng)),
analysis_layer: rng
.boolean()
.then(|| crate::generators::analysis_layer_id(rng)),
pitch_space: rng.boolean().then(|| gen_pitch_space_id(rng)),
}
}
/// A solver tier (every variant).
pub fn gen_solver_tier(rng: &mut Rng) -> SolverTier {
*rng.choose(&[
SolverTier::Stub,
SolverTier::Minimal,
SolverTier::Standard,
SolverTier::Advanced,
])
}
/// A solver version.
pub fn gen_solver_version(rng: &mut Rng) -> SolverVersion {
SolverVersion(rng.next_u64() as u32)
}
/// A deterministic solver budget.
pub fn gen_solver_budget(rng: &mut Rng) -> SolverBudget {
SolverBudget {
max_iterations: rng.next_u64(),
max_nodes: rng.next_u64(),
max_constraint_evaluations: rng.next_u64(),
advisory_wall_time_ms: rng.boolean().then(|| rng.range(0, 1000)),
}
}
/// A consumed-budget record.
pub fn gen_solver_budget_used(rng: &mut Rng) -> SolverBudgetUsed {
SolverBudgetUsed {
iterations: rng.next_u64(),
nodes: rng.next_u64(),
constraint_evaluations: rng.next_u64(),
wall_time_ms: rng.next_u64(),
}
}
/// A solver state.
pub fn gen_solver_state(rng: &mut Rng) -> SolverState {
SolverState {
solver_version: rng.boolean().then(|| gen_solver_version(rng)),
resolved_glyphs: rng.range_usize(0, 32),
}
}
/// A solver profile (every variant).
pub fn gen_solver_profile(rng: &mut Rng) -> SolverProfile {
*rng.choose(&[
SolverProfile::Draft,
SolverProfile::Standard,
SolverProfile::Publication,
])
}
/// Tie-breaking weights.
pub fn gen_tie_breaking_weights(rng: &mut Rng) -> TieBreakingWeights {
let w = |r: &mut Rng| (r.range(0, 4000) as f64) / 1000.0;
TieBreakingWeights {
collision: w(rng),
spacing: w(rng),
slur_shape: w(rng),
beam_slope: w(rng),
vertical_density: w(rng),
system_break: w(rng),
page_fill: w(rng),
casting_off: w(rng),
symbol_density: w(rng),
}
}
/// A solver configuration (profile + budget + tie-breaking).
pub fn gen_solver_config(rng: &mut Rng) -> SolverConfig {
SolverConfig {
profile: gen_solver_profile(rng),
budget: gen_solver_budget(rng),
tie_breaking: gen_tie_breaking_weights(rng),
}
}
/// A normalized quality metric in `[0, 1]`.
pub fn gen_normalized_metric(rng: &mut Rng) -> NormalizedMetric {
NormalizedMetric::new((rng.range(0, 1000) as f64) / 1000.0)
}
/// A constraint id.
pub fn gen_constraint_id(rng: &mut Rng) -> ConstraintId {
ConstraintId(rng.next_u64() as u128)
}
/// A spring-slot id.
pub fn gen_spring_slot_id(rng: &mut Rng) -> SpringSlotId {
SpringSlotId(rng.next_u64() as u128)
}
/// A quality-metric kind (every variant).
pub fn gen_quality_metric_kind(rng: &mut Rng) -> QualityMetricKind {
*rng.choose(&[
QualityMetricKind::Collision,
QualityMetricKind::Spacing,
QualityMetricKind::SlurShape,
QualityMetricKind::BeamSlope,
QualityMetricKind::VerticalDensity,
QualityMetricKind::SystemBreak,
QualityMetricKind::PageFill,
QualityMetricKind::CastingOff,
QualityMetricKind::SymbolDensity,
])
}
/// An extension-warning id.
pub fn gen_extension_warning_id(rng: &mut Rng) -> ExtensionWarningId {
ExtensionWarningId(rng.next_u64() as u128)
}
/// A solver warning (every kind).
pub fn gen_solver_warning(rng: &mut Rng) -> SolverWarning {
let kind = match rng.below(4) {
0 => SolverWarningKind::LargeSoftConstraintViolation {
constraint: gen_constraint_id(rng),
magnitude: (rng.range(0, 10_000) as f64) / 1000.0,
},
1 => SolverWarningKind::UnusualLayoutDecision("stub".to_string()),
2 => SolverWarningKind::QualityFloorApproached {
metric: gen_quality_metric_kind(rng),
},
_ => SolverWarningKind::ExtensionWarning(gen_extension_warning_id(rng)),
};
SolverWarning {
kind,
affected_objects: (0..rng.range_usize(0, 2))
.map(|_| crate::generators::typed_object_id(rng))
.collect(),
message: "generated".to_string(),
}
}
/// An extension-metric id.
pub fn gen_extension_metric_id(rng: &mut Rng) -> ExtensionMetricId {
ExtensionMetricId(rng.next_u64() as u128)
}
/// An extension-contributed quality metric.
pub fn gen_extension_metric(rng: &mut Rng) -> ExtensionMetric {
ExtensionMetric {
metric_id: gen_extension_metric_id(rng),
value: gen_normalized_metric(rng),
}
}
/// A (non-default) quality metric vector.
pub fn gen_quality_metric_vector(rng: &mut Rng) -> QualityMetricVector {
QualityMetricVector {
collision_penalty: gen_normalized_metric(rng),
spacing_distortion: gen_normalized_metric(rng),
slur_shape_penalty: gen_normalized_metric(rng),
beam_slope_penalty: gen_normalized_metric(rng),
vertical_density_penalty: gen_normalized_metric(rng),
system_break_penalty: gen_normalized_metric(rng),
page_fill_efficiency: gen_normalized_metric(rng),
casting_off_quality: gen_normalized_metric(rng),
symbol_density_uniformity: gen_normalized_metric(rng),
extension_metrics: (0..rng.range_usize(0, 2))
.map(|_| gen_extension_metric(rng))
.collect(),
}
}
/// A render target (every variant).
pub fn gen_render_target(rng: &mut Rng) -> RenderTarget {
*rng.choose(&[
RenderTarget::Pdf,
RenderTarget::Svg,
RenderTarget::Screen,
RenderTarget::Print,
])
}
pub fn gen_color_configuration(rng: &mut Rng) -> ColorConfiguration {
ColorConfiguration {
color_space: *rng.choose(&[
ColorSpace::Srgb,
ColorSpace::DisplayP3,
ColorSpace::Cmyk,
ColorSpace::Grayscale,
]),
embed_profile: rng.boolean(),
}
}
pub fn gen_rasterization_configuration(rng: &mut Rng) -> RasterizationConfiguration {
RasterizationConfiguration {
antialias: rng.boolean(),
dpi: rng.range(72, 1201) as u32,
}
}
/// A render configuration.
pub fn gen_render_configuration(rng: &mut Rng) -> RenderConfiguration {
RenderConfiguration {
target: gen_render_target(rng),
color: gen_color_configuration(rng),
rasterization: gen_rasterization_configuration(rng),
}
}
/// An invalidation scope (every variant).
pub fn gen_invalidation_scope(rng: &mut Rng) -> InvalidationScope {
*rng.choose(&[
InvalidationScope::ObjectLocal,
InvalidationScope::MeasureLocal,
InvalidationScope::SystemLocal,
InvalidationScope::PageLocal,
InvalidationScope::RegionLocal,
InvalidationScope::WholeScore,
])
}
/// An invalidation set over generated slots, bands, constraints, and glyphs.
pub fn gen_invalidation_set(rng: &mut Rng) -> InvalidationSet {
let n = |r: &mut Rng| r.range_usize(0, 3);
InvalidationSet {
scope: gen_invalidation_scope(rng),
slots: (0..n(rng)).map(|_| gen_spring_slot_id(rng)).collect(),
bands: (0..n(rng)).map(|_| gen_vertical_band_id(rng)).collect(),
constraints: (0..n(rng)).map(|_| gen_constraint_id(rng)).collect(),
glyphs: (0..n(rng)).map(|_| gen_glyph_object_id(rng)).collect(),
}
}
// --- Remaining public-type generators (Agent F mandate completeness) ---
/// A render-scale context.
pub fn gen_scale_context(rng: &mut Rng) -> ScaleContext {
ScaleContext {
points_per_staff_space: 4.0 + (rng.range(0, 6000) as f32) / 1000.0,
}
}
/// A glyph anchor.
pub fn gen_glyph_anchor(rng: &mut Rng) -> GlyphAnchor {
let names = ["stemUpNW", "stemDownSE", "cutOutNE"];
GlyphAnchor {
name: std::borrow::Cow::Borrowed(names[rng.below(names.len() as u64) as usize]),
x: rng.range(0, 2048) as i32,
y: rng.range(0, 2048) as i32,
}
}
/// A vertical-band id.
pub fn gen_vertical_band_id(rng: &mut Rng) -> VerticalBandId {
VerticalBandId(((rng.next_u64() as u128) << 64) | rng.next_u64() as u128)
}
/// A time-axis kind (every variant).
pub fn gen_time_axis_kind(rng: &mut Rng) -> TimeAxisKind {
match rng.below(4) {
0 => TimeAxisKind::Metric,
1 => TimeAxisKind::Proportional,
2 => TimeAxisKind::Aleatoric,
_ => TimeAxisKind::Registered(TimeAxisRegistryId(rng.next_u64() as u128)),
}
}
/// An engraving-decision id (content-derived from a generated decision).
pub fn gen_engraving_decision_id(rng: &mut Rng) -> EngravingDecisionId {
gen_engraving_decision(rng).id
}
/// A Sem-ver font version.
pub fn gen_sem_ver(rng: &mut Rng) -> SemVer {
SemVer::new(
rng.range(0, 4) as u32,
rng.range(0, 400) as u32,
rng.range(0, 9) as u32,
)
}
/// Glyph render data availability.
pub fn gen_glyph_render_data(rng: &mut Rng) -> GlyphRenderData {
GlyphRenderData {
outline: if rng.boolean() {
vec![gen_path_command(rng), PathCommand::Close]
} else {
vec![]
},
bitmap: rng.boolean().then(|| gen_glyph_bitmap(rng)),
}
}
pub fn gen_path_command(rng: &mut Rng) -> PathCommand {
match rng.below(4) {
0 => PathCommand::MoveTo(gen_point(rng)),
1 => PathCommand::LineTo(gen_point(rng)),
2 => PathCommand::CurveTo {
control1: gen_point(rng),
control2: gen_point(rng),
to: gen_point(rng),
},
_ => PathCommand::Close,
}
}
pub fn gen_glyph_bitmap(rng: &mut Rng) -> GlyphBitmap {
GlyphBitmap {
width: 1,
height: 1,
rgba8: vec![rng.next_u64() as u8; 4],
}
}
/// A staff-space bounding box drawn from a bundled glyph.
pub fn gen_glyph_bounding_box(rng: &mut Rng) -> BoundingBox {
gen_glyph_metric(rng).bounding_box()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fixtures;
use epiphany_core::TypedObjectId;
use epiphany_determinism::CanonicalEncode;
/// (s10, row 29) `gen_operation_kind_tag`'s draw must cover exactly
/// `PAYLOAD_FREE` `{Registered}` — **not** merely the appended
/// discriminants 30..=34, which would pass even if the `Registered`
/// append (structurally different from every built-in: it is the one
/// variant `PAYLOAD_FREE` excludes by design, `payload.rs:469`) were
/// deleted from the generator entirely.
#[test]
fn gen_operation_kind_tag_covers_payload_free_and_registered() {
let mut rng = Rng::new(0x6E_57_A9);
let mut saw_registered = false;
let mut seen_builtins = std::collections::BTreeSet::new();
for _ in 0..5000 {
match gen_operation_kind_tag(&mut rng) {
OperationKindTag::Registered(_) => saw_registered = true,
other => {
seen_builtins.insert(other);
}
}
}
assert!(saw_registered, "Registered never drawn in 5000 samples");
let expected: std::collections::BTreeSet<_> =
OperationKindTag::PAYLOAD_FREE.iter().copied().collect();
assert_eq!(
seen_builtins, expected,
"the draw's built-in coverage must be exactly PAYLOAD_FREE"
);
}
#[test]
fn edit_barrier_blob_codec_round_trips_generated_barriers() {
// Property gate for the provisional manifest-blob byte form
// (`ExtensionDeclaration.edit_barriers` / `.affected_object_kinds`):
// any generated barrier set encodes to a blob that decodes and
// re-encodes byte-identically, and each decoded barrier's own canonical
// bytes round-trip through the single-barrier decoder.
let mut rng = Rng::new(0xBA22_1E2C_0DEC);
for _ in 0..256 {
let barriers: Vec<EditBarrier> = (0..rng.range_usize(0, 4))
.map(|_| gen_edit_barrier(&mut rng))
.collect();
let blob = encode_edit_barriers(&barriers);
let decoded = decode_edit_barriers(&blob).expect("a canonical blob decodes");
assert_eq!(
encode_edit_barriers(&decoded),
blob,
"decode → re-encode is byte-identical"
);
for barrier in &decoded {
let bytes = barrier.to_canonical_bytes();
assert_eq!(
EditBarrier::decode_canonical_bytes(&bytes).as_ref(),
Ok(barrier)
);
}
let kinds: Vec<ObjectKind> = (0..rng.range_usize(0, 6))
.map(|_| gen_object_kind(&mut rng))
.collect();
let kind_blob = encode_affected_object_kinds(&kinds);
let decoded_kinds =
decode_affected_object_kinds(&kind_blob).expect("a canonical blob decodes");
assert_eq!(encode_affected_object_kinds(&decoded_kinds), kind_blob);
}
}
#[test]
fn ir_generators_are_deterministic_and_well_formed() {
let mut a = Rng::new(13);
let mut b = Rng::new(13);
// Deterministic from the seed.
assert_eq!(gen_logical_layout_ir(&mut a), gen_logical_layout_ir(&mut b));
let generated = gen_logical_layout_ir(&mut Rng::new(14));
let constrained = try_to_constrained(&generated)
.expect("generated logical IR must be structurally transformable");
// The spacing stage emits real constraints, which the stub does not
// evaluate: the solve is renderable either way, and satisfaction is
// claimed exactly when the problem is constraint-free.
let report = StubSolver.solve(&constrained, &SolverConfig::default());
assert!(report.status.is_renderable());
assert_eq!(
report.satisfied_hard_constraints,
constrained.constraints.is_empty()
);
// Provenance ids are consistent with their synthesis: a projected one
// (no synthesis) carries the source-only id; a synthesized one does not.
// Every generated constrained IR is accepted by the real stub solver.
let mut rng = Rng::new(99);
for _ in 0..64 {
let p = gen_provenance(&mut rng);
match p.synthesis {
None => assert_eq!(p.stable_id, stable_layout_id(&p.source)),
Some(_) => assert_ne!(p.stable_id, stable_layout_id(&p.source)),
}
let ci = gen_constrained_layout_ir(&mut rng);
assert_eq!(
StubSolver.solve(&ci, &SolverConfig::default()).status,
SolveStatus::Solved
);
let _ = gen_glyph_object(&mut rng);
let _ = gen_resolved_glyph(&mut rng);
let _ = gen_time_axis_model(&mut rng);
let _ = gen_synthesis_kind(&mut rng);
let _ = gen_solve_status(&mut rng);
let _ = gen_glyph_catalog_identity(&mut rng);
let _ = gen_layout_object_id(&mut rng);
let _ = gen_resolved_layout_ir(&mut rng);
let _ = gen_render_primitive(&mut rng);
let _ = gen_render_ir(&mut rng);
let _ = gen_solve_report(&mut rng);
let _ = gen_round_trip_report(&mut rng);
// The rest of E's public surface.
let _ = gen_staff_space(&mut rng);
let _ = gen_size2d(&mut rng);
let _ = gen_bounding_box(&mut rng);
let _ = gen_glyph_object_id(&mut rng);
let _ = gen_glyph_metric(&mut rng);
let _ = gen_smufl_version(&mut rng);
let _ = gen_font_id(&mut rng);
let _ = gen_engraving_decision(&mut rng);
let _ = gen_vertical_band(&mut rng);
let _ = gen_vertical_band_kind(&mut rng);
let barrier = gen_edit_barrier(&mut rng);
// The barrier canonically encodes (Appendix D), and prohibition is
// well-defined against a generated edit context.
let _ = barrier.to_canonical_bytes();
let _ = barrier.prohibits_edit(
gen_operation_kind_tag(&mut rng),
&crate::generators::typed_object_id(&mut rng),
&gen_edit_context(&mut rng),
&AlwaysLiveOracle,
);
let _ = gen_solver_config(&mut rng);
let _ = gen_solver_tier(&mut rng);
let _ = gen_solver_version(&mut rng);
let _ = gen_invalidation_set(&mut rng);
// The remaining public surface.
let _ = gen_solver_budget_used(&mut rng);
let _ = gen_solver_state(&mut rng);
let _ = gen_solver_profile(&mut rng);
let _ = gen_tie_breaking_weights(&mut rng);
let _ = gen_normalized_metric(&mut rng);
let _ = gen_constraint_id(&mut rng);
let _ = gen_spring_slot_id(&mut rng);
let _ = gen_solver_warning(&mut rng);
let _ = gen_scale_context(&mut rng);
let _ = gen_glyph_anchor(&mut rng);
let _ = gen_vertical_band_id(&mut rng);
let _ = gen_time_axis_kind(&mut rng);
let _ = gen_engraving_decision_id(&mut rng);
let _ = gen_sem_ver(&mut rng);
let _ = gen_glyph_render_data(&mut rng);
let _ = gen_glyph_bounding_box(&mut rng);
let _ = gen_decision_source(&mut rng);
let _ = gen_engraving_decision_kind(&mut rng);
let _ = gen_object_kind(&mut rng);
let _ = gen_barrier_scope(&mut rng);
let _ = gen_barrier_condition(&mut rng);
let _ = gen_invalidation_scope(&mut rng);
let _ = gen_size2d(&mut rng);
let _ = gen_layout_object(&mut rng);
let _ = gen_layout_region(&mut rng);
let _ = gen_font_id(&mut rng);
let _ = gen_smufl_version(&mut rng);
let _ = gen_quality_metric_kind(&mut rng);
let _ = gen_extension_warning_id(&mut rng);
let _ = gen_extension_metric(&mut rng);
let _ = gen_extension_metric_id(&mut rng);
let _ = gen_quality_metric_vector(&mut rng);
let _ = gen_render_target(&mut rng);
let _ = gen_render_configuration(&mut rng);
let _ = gen_glyph_render_data(&mut rng);
let _ = gen_vertical_band(&mut rng);
let _ = gen_rect(&mut rng);
let _ = gen_transform_2d(&mut rng);
let _ = gen_margins(&mut rng);
let _ = gen_score_version(&mut rng);
let _ = gen_local_coordinate_system(&mut rng);
let _ = gen_vertical_extent(&mut rng);
let _ = gen_cross_region_object(&mut rng);
let _ = gen_synthesis_instance_key(&mut rng);
let _ = gen_glyph_style(&mut rng);
let _ = gen_time_point(&mut rng);
let _ = gen_time_range(&mut rng);
let _ = gen_spring_slot(&mut rng);
let _ = gen_constrained_layout_region(&mut rng);
let _ = gen_layout_constraint(&mut rng);
let _ = gen_constraint_strength(&mut rng);
let _ = gen_engraving_override(&mut rng);
let _ = gen_resolved_staff(&mut rng);
let _ = gen_resolved_measure(&mut rng);
let _ = gen_resolved_system(&mut rng);
let _ = gen_resolved_page(&mut rng);
let _ = gen_dependency_index(&mut rng);
let _ = gen_layout_cache(&mut rng);
let _ = gen_system_id(&mut rng);
let _ = gen_logical_region_cache(&mut rng);
let _ = gen_constrained_region_cache(&mut rng);
let _ = gen_resolved_system_cache(&mut rng);
let _ = gen_fine_layout_cache(&mut rng);
let _ = gen_color_configuration(&mut rng);
let _ = gen_rasterization_configuration(&mut rng);
let _ = gen_path_command(&mut rng);
let _ = gen_glyph_bitmap(&mut rng);
}
}
#[test]
fn solver_rejects_a_catalog_hash_that_misses_its_glyphs() {
let mut rng = Rng::new(8);
let mut input = gen_constrained_layout_ir(&mut rng);
// Ensure there is at least one glyph to consult.
if input.glyphs.is_empty() {
input = gen_constrained_layout_ir(&mut Rng::new(3));
}
assert_eq!(
StubSolver.solve(&input, &SolverConfig::default()).status,
SolveStatus::Solved
);
input.catalog.metrics_hash[0] ^= 1;
assert_eq!(
StubSolver.solve(&input, &SolverConfig::default()).status,
SolveStatus::InternalError,
"solver must reject a catalog hash that does not cover consulted metrics"
);
}
#[test]
fn ten_measure_single_staff_round_trips() {
// The QUICKSTART's headline case for Agent E's hand-off: a real
// 10-measure single-staff score, driven through the real crate.
let score = fixtures::ten_measure_single_staff(0xA11CE);
let report = round_trip(&score);
assert!(report.glyphs > 0);
assert_eq!(
report.render_primitives,
report.glyphs + report.render_strokes
);
let measures = report
.recovered_sources
.iter()
.filter(|s| matches!(s, TypedObjectId::Measure(_)))
.count();
assert_eq!(measures, 10, "all ten measures must be laid out");
assert!(report
.recovered_sources
.iter()
.any(|s| matches!(s, TypedObjectId::Tie(_))));
assert!(report
.recovered_sources
.iter()
.any(|s| matches!(s, TypedObjectId::ChordSymbol(_))));
}
}