SMLib history: a split line between two pieces of one face comes from nothing

An edge SMLib reports as new was given the origin "where its two faces meet", each side what
that face came from. A split line lies between two pieces of one face, so both sides are that
face and the edge read EdgeOrigin::Meet { top, top }: a face meeting itself, which no two faces
did. Such an edge now has no origin, as the history's EdgeProv documents for a new edge between
faces new to the operation (the doc now names the split line too).

New test: a_split_line_comes_from_nothing (one face split in two, its new edge has no origin).
modify_history_tests::split_a_face checks this at its last line; it still fails earlier, on its
counts ([3, 2, 2, 0, 0] for [5, 0, 2, 0, 0]) on develop's SMLib pin, until SMLib batch 3's
history fix (03bbc88) is pinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-30 08:44:56 -04:00
committed by Irakli Kourtis
parent 738f336e12
commit 9243b6550d
3 changed files with 16 additions and 3 deletions
+2 -1
View File
@@ -119,7 +119,8 @@ pub enum EdgeOrigin {
}
/// A result edge: the result faces it separates and what it came from (empty when it is new
/// and lies between faces new to the operation).
/// and lies between faces new to the operation, or between two pieces of one face: a split
/// line).
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct EdgeProv {
pub faces: Vec<usize>,
+3 -2
View File
@@ -178,10 +178,11 @@ impl Raw {
}
};
let origins = if e.origins.is_empty() {
// New: where its two faces meet, each side what that face came from.
// New: where its two faces meet, each side what that face came from. Between two
// pieces of one face (a split line) nothing met: it comes from nothing.
let side = |f: usize| faces.get(f).and_then(|p| Side::new(p.origins.iter().copied()));
match bounds.as_slice() {
[a, b] => side(*a).zip(side(*b)).map(|(a, b)| EdgeOrigin::meet(a, b)).into_iter().collect(),
[a, b] => side(*a).zip(side(*b)).filter(|(a, b)| a != b).map(|(a, b)| EdgeOrigin::meet(a, b)).into_iter().collect(),
_ => Vec::new(),
}
} else {
+11
View File
@@ -55,6 +55,17 @@ fn new_edges_are_where_their_faces_meet() {
h.check_counts((3, 3), &[(3, 8), (5, 4)]).unwrap();
}
/// A face split in two (a split line across operand 0's face 0): the new edge lies between two
/// pieces of one face, where nothing met, so it comes from nothing.
#[test]
fn a_split_line_comes_from_nothing() {
let raw = Raw::decode(&[1, 2, 1, 2, 1, 0, 0, 2, 1, 0, 0, 2, 0, 1, 0]).unwrap();
let h = raw.history(&[Order::default()], &Order::default()).unwrap();
assert_eq!(h.faces.iter().map(|f| (f.origins.clone(), f.change)).collect::<Vec<_>>(), vec![(vec![face(0, 0)], Change::Split); 2]);
assert_eq!(h.edges[0].faces, vec![0, 1]);
assert!(h.edges[0].origins.is_empty(), "{:?}", h.edges[0].origins);
}
#[test]
fn renumbers_into_canonical_order() {
let raw = Raw::decode(RECORDS).unwrap();