Schema major 1 Phase E: resolved-layout length prefixes u64 -> u32
Unifies the resolved-layout's 13 length/count prefixes to u32 (schema major 1), matching the core codec's put_len; the manifest-embedded barrier blobs stay u64 (regime (b), canonical). The resolved layout is an encode-only, non-persisted determinism fingerprint, so there is no migrate-on-read and no bundle LayoutCache machinery (that would be ahead of a producer) — a cross-major layout cache is regenerated, never decoded. - resolved.rs: push_u64 length helper -> push_len (u32 LE, debug_assert n <= u32::MAX). Data fields (rgba, layer, page.number, smufl_version) untouched. - Byte-shape lock: count_prefixes_are_u32_width_locked asserts an empty layout's four counts occupy 4x4 bytes after the 32-byte ScoreVersion (catalog length recomputed independently), so a revert to u64 fails (verified: 128 vs 112). Zero golden churn (every existing resolved test is self-comparison). Full gate green (workspace tests, clippy -D warnings, fmt, rustdoc -D warnings). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
This commit is contained in:
parent
4598f30ddd
commit
64de92605f
|
|
@ -114,18 +114,18 @@ impl ResolvedLayoutIR {
|
||||||
impl CanonicalEncode for ResolvedLayoutIR {
|
impl CanonicalEncode for ResolvedLayoutIR {
|
||||||
fn encode_canonical(&self, out: &mut Vec<u8>) {
|
fn encode_canonical(&self, out: &mut Vec<u8>) {
|
||||||
out.extend_from_slice(&self.source.0);
|
out.extend_from_slice(&self.source.0);
|
||||||
push_u64(out, self.pages.len() as u64);
|
push_len(out, self.pages.len());
|
||||||
for page in &self.pages {
|
for page in &self.pages {
|
||||||
encode_page(out, page);
|
encode_page(out, page);
|
||||||
}
|
}
|
||||||
push_u64(out, self.glyphs.len() as u64);
|
push_len(out, self.glyphs.len());
|
||||||
for glyph in &self.glyphs {
|
for glyph in &self.glyphs {
|
||||||
encode_provenance(out, &glyph.provenance);
|
encode_provenance(out, &glyph.provenance);
|
||||||
// The glyph reference itself (so swapping two glyphs' symbols, even
|
// The glyph reference itself (so swapping two glyphs' symbols, even
|
||||||
// with the consulted-name set unchanged, changes the canonical bytes
|
// with the consulted-name set unchanged, changes the canonical bytes
|
||||||
// — the encoding is injective in glyph identity).
|
// — the encoding is injective in glyph identity).
|
||||||
let name = glyph.glyph.as_str().as_bytes();
|
let name = glyph.glyph.as_str().as_bytes();
|
||||||
push_u64(out, name.len() as u64);
|
push_len(out, name.len());
|
||||||
out.extend_from_slice(name);
|
out.extend_from_slice(name);
|
||||||
let (qx, qy) = quantize(glyph.position);
|
let (qx, qy) = quantize(glyph.position);
|
||||||
qx.encode_canonical(out);
|
qx.encode_canonical(out);
|
||||||
|
|
@ -145,7 +145,7 @@ impl CanonicalEncode for ResolvedLayoutIR {
|
||||||
out.extend_from_slice(&glyph.style.rgba.to_le_bytes());
|
out.extend_from_slice(&glyph.style.rgba.to_le_bytes());
|
||||||
out.extend_from_slice(&glyph.layer.to_le_bytes());
|
out.extend_from_slice(&glyph.layer.to_le_bytes());
|
||||||
}
|
}
|
||||||
push_u64(out, self.strokes.len() as u64);
|
push_len(out, self.strokes.len());
|
||||||
for stroke in &self.strokes {
|
for stroke in &self.strokes {
|
||||||
encode_provenance(out, &stroke.provenance);
|
encode_provenance(out, &stroke.provenance);
|
||||||
let (fx, fy) = quantize(stroke.from);
|
let (fx, fy) = quantize(stroke.from);
|
||||||
|
|
@ -158,7 +158,7 @@ impl CanonicalEncode for ResolvedLayoutIR {
|
||||||
out.extend_from_slice(&stroke.style.rgba.to_le_bytes());
|
out.extend_from_slice(&stroke.style.rgba.to_le_bytes());
|
||||||
out.extend_from_slice(&stroke.layer.to_le_bytes());
|
out.extend_from_slice(&stroke.layer.to_le_bytes());
|
||||||
}
|
}
|
||||||
push_u64(out, self.engraving_decisions.len() as u64);
|
push_len(out, self.engraving_decisions.len());
|
||||||
for decision in &self.engraving_decisions {
|
for decision in &self.engraving_decisions {
|
||||||
encode_decision(out, decision);
|
encode_decision(out, decision);
|
||||||
}
|
}
|
||||||
|
|
@ -179,24 +179,24 @@ fn encode_page(out: &mut Vec<u8>, page: &ResolvedPage) {
|
||||||
] {
|
] {
|
||||||
encode_staff_space(out, margin);
|
encode_staff_space(out, margin);
|
||||||
}
|
}
|
||||||
push_u64(out, page.systems.len() as u64);
|
push_len(out, page.systems.len());
|
||||||
for system in &page.systems {
|
for system in &page.systems {
|
||||||
encode_provenance(out, &system.provenance);
|
encode_provenance(out, &system.provenance);
|
||||||
encode_rect(out, system.bounding_box);
|
encode_rect(out, system.bounding_box);
|
||||||
push_u64(out, system.staves.len() as u64);
|
push_len(out, system.staves.len());
|
||||||
for staff in &system.staves {
|
for staff in &system.staves {
|
||||||
encode_provenance(out, &staff.provenance);
|
encode_provenance(out, &staff.provenance);
|
||||||
out.extend_from_slice(&staff.staff.canonical_bytes());
|
out.extend_from_slice(&staff.staff.canonical_bytes());
|
||||||
encode_rect(out, staff.bounding_box);
|
encode_rect(out, staff.bounding_box);
|
||||||
}
|
}
|
||||||
push_u64(out, system.measures.len() as u64);
|
push_len(out, system.measures.len());
|
||||||
for measure in &system.measures {
|
for measure in &system.measures {
|
||||||
encode_provenance(out, &measure.provenance);
|
encode_provenance(out, &measure.provenance);
|
||||||
out.extend_from_slice(&measure.measure.canonical_bytes());
|
out.extend_from_slice(&measure.measure.canonical_bytes());
|
||||||
encode_rect(out, measure.bounding_box);
|
encode_rect(out, measure.bounding_box);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
push_u64(out, page.free_objects.len() as u64);
|
push_len(out, page.free_objects.len());
|
||||||
for object in &page.free_objects {
|
for object in &page.free_objects {
|
||||||
push_u128(out, object.0);
|
push_u128(out, object.0);
|
||||||
}
|
}
|
||||||
|
|
@ -229,8 +229,15 @@ fn encode_f32(out: &mut Vec<u8>, value: f32) {
|
||||||
.encode_canonical(out);
|
.encode_canonical(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_u64(out: &mut Vec<u8>, v: u64) {
|
/// Appends a `u32` little-endian length/count prefix (schema major 1: the
|
||||||
out.extend_from_slice(&v.to_le_bytes());
|
/// resolved-layout unifies its length prefixes to `u32`, matching the core
|
||||||
|
/// codec's `put_len`; no resolved-layout count nears 4 GB). The resolved layout
|
||||||
|
/// is a non-canonical, encode-only determinism fingerprint (Appendix D
|
||||||
|
/// §"Quantized Layout Coordinates"), so this width change has no persisted-format
|
||||||
|
/// migration — a cross-major layout cache is regenerated, never decoded.
|
||||||
|
fn push_len(out: &mut Vec<u8>, n: usize) {
|
||||||
|
debug_assert!(n <= u32::MAX as usize, "resolved-layout length exceeds u32");
|
||||||
|
out.extend_from_slice(&(n as u32).to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_u128(out: &mut Vec<u8>, v: u128) {
|
fn push_u128(out: &mut Vec<u8>, v: u128) {
|
||||||
|
|
@ -249,7 +256,7 @@ fn quantize(p: Point) -> (QuantizedCoord, QuantizedCoord) {
|
||||||
/// Length-prefixes an id's canonical bytes (self-delimiting).
|
/// Length-prefixes an id's canonical bytes (self-delimiting).
|
||||||
fn encode_source(out: &mut Vec<u8>, source: &TypedObjectId) {
|
fn encode_source(out: &mut Vec<u8>, source: &TypedObjectId) {
|
||||||
let bytes = source.to_canonical_bytes();
|
let bytes = source.to_canonical_bytes();
|
||||||
push_u64(out, bytes.len() as u64);
|
push_len(out, bytes.len());
|
||||||
out.extend_from_slice(&bytes);
|
out.extend_from_slice(&bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,9 +278,9 @@ fn encode_provenance(out: &mut Vec<u8>, p: &Provenance) {
|
||||||
.collect();
|
.collect();
|
||||||
deps.sort();
|
deps.sort();
|
||||||
deps.dedup();
|
deps.dedup();
|
||||||
push_u64(out, deps.len() as u64);
|
push_len(out, deps.len());
|
||||||
for bytes in deps {
|
for bytes in deps {
|
||||||
push_u64(out, bytes.len() as u64);
|
push_len(out, bytes.len());
|
||||||
out.extend_from_slice(&bytes);
|
out.extend_from_slice(&bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -326,7 +333,7 @@ fn encode_catalog(out: &mut Vec<u8>, c: &GlyphCatalogIdentity) {
|
||||||
out.extend_from_slice(&c.smufl_version.major.to_le_bytes());
|
out.extend_from_slice(&c.smufl_version.major.to_le_bytes());
|
||||||
out.extend_from_slice(&c.smufl_version.minor.to_le_bytes());
|
out.extend_from_slice(&c.smufl_version.minor.to_le_bytes());
|
||||||
let font = c.font_id.0.as_bytes();
|
let font = c.font_id.0.as_bytes();
|
||||||
push_u64(out, font.len() as u64);
|
push_len(out, font.len());
|
||||||
out.extend_from_slice(font);
|
out.extend_from_slice(font);
|
||||||
match c.font_version {
|
match c.font_version {
|
||||||
None => out.push(0),
|
None => out.push(0),
|
||||||
|
|
@ -392,6 +399,34 @@ mod tests {
|
||||||
assert_ne!(a, moved.canonical_bytes());
|
assert_ne!(a, moved.canonical_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_prefixes_are_u32_width_locked() {
|
||||||
|
// Schema major 1 unifies the resolved-layout length/count prefixes to
|
||||||
|
// u32 (Binary Format companion §"Schema Major 1"). This locks the byte
|
||||||
|
// shape so a revert to the old u64 prefixes fails: an empty layout
|
||||||
|
// encodes its four counts — pages, glyphs, strokes, engraving_decisions
|
||||||
|
// — as u32 zeros (16 bytes) right after the 32-byte ScoreVersion source,
|
||||||
|
// then the catalog. Under u64 that region would be 32 bytes, shifting the
|
||||||
|
// catalog and lengthening the output by 16.
|
||||||
|
let bytes = ir(vec![], vec![]).canonical_bytes();
|
||||||
|
let source_len = ScoreVersion::default().0.len();
|
||||||
|
assert_eq!(source_len, 32, "ScoreVersion source is 32 bytes");
|
||||||
|
// The first count prefix (pages) is a 4-byte u32 zero — not 8 bytes.
|
||||||
|
assert_eq!(&bytes[source_len..source_len + 4], &0u32.to_le_bytes());
|
||||||
|
// The four count prefixes occupy exactly 4 × 4 bytes; then the catalog,
|
||||||
|
// whose length we recompute independently (no magic number).
|
||||||
|
let catalog_len = {
|
||||||
|
let mut c = Vec::new();
|
||||||
|
encode_catalog(&mut c, &GlyphCatalogIdentity::default());
|
||||||
|
c.len()
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
bytes.len(),
|
||||||
|
source_len + 4 * 4 + catalog_len,
|
||||||
|
"four u32 count prefixes (16 bytes), not u64 (32 bytes)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn canonical_bytes_capture_engraving_decisions_and_catalog() {
|
fn canonical_bytes_capture_engraving_decisions_and_catalog() {
|
||||||
let base = ir(vec![glyph(1, 1.0)], vec![]);
|
let base = ir(vec![glyph(1, 1.0)], vec![]);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue