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
valid weighted tiling layouts, derives leaf hierarchy metadata, mutates them
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:
@ -258,6 +261,7 @@ Tests:
- interruption restarts only affected phase groups
- reversing direction produces equivalent motion in reverse
- 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
toplevel peer position
- 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) {
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);
assert!(!overlaps(req.windows.iter().map(|window| window.from)));
assert!(!overlaps(req.windows.iter().map(|window| window.to)));
let plan = plan_no_overlap(&req).unwrap();
assert!(validate_plan_continuous(&req, &plan));
let first = plan_no_overlap_explained(&req).unwrap();
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 {
@ -1798,6 +1834,105 @@ mod tests {
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]
fn stack_extraction_creates_space_before_moving_child() {
let req = request(vec![