1
0
Fork 0
forked from wry/wry

Enumerate deterministic split-tree plans

This commit is contained in:
atagen 2026-05-21 21:13:48 +10:00
parent 511e188d16
commit 332a7468f6
2 changed files with 141 additions and 2 deletions

View file

@ -248,6 +248,9 @@ Current pure planner status:
- Planner tests now include a deterministic split-tree generator. It builds - Planner tests now include a deterministic split-tree generator. It builds
valid weighted tiling layouts, derives leaf hierarchy metadata, mutates them valid weighted tiling layouts, derives leaf hierarchy metadata, mutates them
through supported transitions, and runs the real planner plus exact validator. through supported transitions, and runs the real planner plus exact validator.
- The generated tests also include a bounded corpus of supported split-tree
transitions across both axes and directions. Each case is planned twice and
compared exactly to catch nondeterministic planner output.
Tests: Tests:
@ -258,6 +261,7 @@ Tests:
- interruption restarts only affected phase groups - interruption restarts only affected phase groups
- reversing direction produces equivalent motion in reverse - reversing direction produces equivalent motion in reverse
- accepted and rejected plans expose deterministic strategy explanations - accepted and rejected plans expose deterministic strategy explanations
- bounded generated split-tree corpus produces identical plans on repeated runs
- child waits for parent/container-space phases when moving upward into a - child waits for parent/container-space phases when moving upward into a
toplevel peer position toplevel peer position
- mono-mode tab switches do not animate, while entering/exiting mono can animate - mono-mode tab switches do not animate, while entering/exiting mono can animate

View file

@ -1473,11 +1473,47 @@ mod tests {
} }
fn assert_generated_case_plans(old: &TestTree, new: &TestTree, bounds: Rect) { fn assert_generated_case_plans(old: &TestTree, new: &TestTree, bounds: Rect) {
assert_generated_case_plans_deterministically(old, new, bounds);
}
fn assert_generated_case_plans_deterministically(
old: &TestTree,
new: &TestTree,
bounds: Rect,
) -> MultiphasePlanned {
let req = generated_request(old, new, bounds); let req = generated_request(old, new, bounds);
assert!(!overlaps(req.windows.iter().map(|window| window.from))); assert!(!overlaps(req.windows.iter().map(|window| window.from)));
assert!(!overlaps(req.windows.iter().map(|window| window.to))); assert!(!overlaps(req.windows.iter().map(|window| window.to)));
let plan = plan_no_overlap(&req).unwrap(); let first = plan_no_overlap_explained(&req).unwrap();
assert!(validate_plan_continuous(&req, &plan)); let second = plan_no_overlap_explained(&req).unwrap();
assert_eq!(first, second);
assert_eq!(first.plan.phases.len(), first.explanation.phases.len());
assert_eq!(
first.explanation.validation,
ValidationExplanation::passed()
);
for phase in &first.explanation.phases {
assert!(phase.nodes.windows(2).all(|pair| pair[0].0 < pair[1].0));
}
assert!(validate_plan_continuous(&req, &first.plan));
first
}
fn bounds_for_axis(axis: PhaseAxis) -> Rect {
match axis {
PhaseAxis::Horizontal => rect(0, 0, 400, 100),
PhaseAxis::Vertical => rect(0, 0, 100, 400),
}
}
fn push_generated_case_bidirectional(
cases: &mut Vec<(TestTree, TestTree, Rect)>,
old: TestTree,
new: TestTree,
bounds: Rect,
) {
cases.push((old.clone(), new.clone(), bounds));
cases.push((new, old, bounds));
} }
fn request(windows: Vec<MultiphaseWindow>) -> MultiphaseRequest { fn request(windows: Vec<MultiphaseWindow>) -> MultiphaseRequest {
@ -1798,6 +1834,105 @@ mod tests {
assert_generated_case_plans(&vertical_new, &vertical_old, rect(0, 0, 100, 400)); assert_generated_case_plans(&vertical_new, &vertical_old, rect(0, 0, 100, 400));
} }
#[test]
fn bounded_generated_supported_split_tree_corpus_is_deterministic() {
let mut cases = vec![];
for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] {
let child_axis = axis.other();
let bounds = bounds_for_axis(axis);
push_generated_case_bidirectional(
&mut cases,
split(10, axis, &[1, 1], vec![leaf(1), leaf(2)]),
split(10, axis, &[1, 1], vec![leaf(2), leaf(1)]),
bounds,
);
push_generated_case_bidirectional(
&mut cases,
split(10, axis, &[1, 1, 1], vec![leaf(1), leaf(2), leaf(3)]),
split(10, axis, &[1, 2, 1], vec![leaf(1), leaf(2), leaf(3)]),
bounds,
);
push_generated_case_bidirectional(
&mut cases,
split(
10,
axis,
&[1, 1],
vec![
leaf(1),
split(11, child_axis, &[1, 1], vec![leaf(2), leaf(3)]),
],
),
split(10, axis, &[1, 2, 1], vec![leaf(1), leaf(2), leaf(3)]),
bounds,
);
push_generated_case_bidirectional(
&mut cases,
split(
10,
axis,
&[1, 1],
vec![
split(11, child_axis, &[1, 1], vec![leaf(1), leaf(2)]),
leaf(3),
],
),
split(10, axis, &[1, 2, 1], vec![leaf(1), leaf(2), leaf(3)]),
bounds,
);
push_generated_case_bidirectional(
&mut cases,
split(
10,
axis,
&[1, 1],
vec![
leaf(1),
split(11, child_axis, &[1, 1], vec![leaf(2), leaf(3)]),
],
),
split(
10,
axis,
&[1, 3],
vec![
leaf(1),
split(11, child_axis, &[3, 1], vec![leaf(2), leaf(3)]),
],
),
bounds,
);
push_generated_case_bidirectional(
&mut cases,
split(
10,
axis,
&[1, 1],
vec![
split(11, child_axis, &[1, 1], vec![leaf(1), leaf(2)]),
leaf(3),
],
),
split(
10,
axis,
&[3, 1],
vec![
split(11, child_axis, &[1, 3], vec![leaf(1), leaf(2)]),
leaf(3),
],
),
bounds,
);
}
assert_eq!(cases.len(), 24);
for (old, new, bounds) in cases {
assert_generated_case_plans_deterministically(&old, &new, bounds);
}
}
#[test] #[test]
fn stack_extraction_creates_space_before_moving_child() { fn stack_extraction_creates_space_before_moving_child() {
let req = request(vec![ let req = request(vec![