atagen

monolith

public
Star0
Watch0
b75c2f9a2eee739adce9a2269a7aa24abd490aef
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git

Blame: crates/planner/src/tests.rs

b563d2da1c80atagen2026-10-16 08:25 UTC1
use crate::geom::Rect;
b563d2da1c80atagen2026-10-16 08:25 UTC2
use crate::plan::{Config, Plan, Strategy, Style, plan, plan_canonical};
b563d2da1c80atagen2026-10-16 08:25 UTC3
use crate::{plan_blocks, validate};
b563d2da1c80atagen2026-10-16 08:25 UTC4
use proptest::prelude::*;
b563d2da1c80atagen2026-10-16 08:25 UTC5
b563d2da1c80atagen2026-10-16 08:25 UTC6
const G: f64 = 10.0;
b563d2da1c80atagen2026-10-16 08:25 UTC7
b563d2da1c80atagen2026-10-16 08:25 UTC8
fn region() -> Rect {
b563d2da1c80atagen2026-10-16 08:25 UTC9
    Rect::new(G, G, 1180.0, 680.0)
b563d2da1c80atagen2026-10-16 08:25 UTC10
}
b563d2da1c80atagen2026-10-16 08:25 UTC11
b563d2da1c80atagen2026-10-16 08:25 UTC12
fn split(rect: Rect, ratios: &[f64], horizontal: bool) -> Vec<Rect> {
b563d2da1c80atagen2026-10-16 08:25 UTC13
    let total: f64 = ratios.iter().sum();
b563d2da1c80atagen2026-10-16 08:25 UTC14
    let len = if horizontal { rect.w } else { rect.h };
b563d2da1c80atagen2026-10-16 08:25 UTC15
    let avail = len - G * (ratios.len() - 1) as f64;
b563d2da1c80atagen2026-10-16 08:25 UTC16
    let mut pos = if horizontal { rect.x } else { rect.y };
b563d2da1c80atagen2026-10-16 08:25 UTC17
    ratios
b563d2da1c80atagen2026-10-16 08:25 UTC18
        .iter()
b563d2da1c80atagen2026-10-16 08:25 UTC19
        .map(|r| {
b563d2da1c80atagen2026-10-16 08:25 UTC20
            let size = avail * r / total;
b563d2da1c80atagen2026-10-16 08:25 UTC21
            let out = if horizontal {
b563d2da1c80atagen2026-10-16 08:25 UTC22
                Rect::new(pos, rect.y, size, rect.h)
b563d2da1c80atagen2026-10-16 08:25 UTC23
            } else {
b563d2da1c80atagen2026-10-16 08:25 UTC24
                Rect::new(rect.x, pos, rect.w, size)
b563d2da1c80atagen2026-10-16 08:25 UTC25
            };
b563d2da1c80atagen2026-10-16 08:25 UTC26
            pos += size + G;
b563d2da1c80atagen2026-10-16 08:25 UTC27
            out
b563d2da1c80atagen2026-10-16 08:25 UTC28
        })
b563d2da1c80atagen2026-10-16 08:25 UTC29
        .collect()
b563d2da1c80atagen2026-10-16 08:25 UTC30
}
b563d2da1c80atagen2026-10-16 08:25 UTC31
b563d2da1c80atagen2026-10-16 08:25 UTC32
fn cols(rect: Rect, n: usize) -> Vec<Rect> {
b563d2da1c80atagen2026-10-16 08:25 UTC33
    split(rect, &vec![1.0; n], true)
b563d2da1c80atagen2026-10-16 08:25 UTC34
}
b563d2da1c80atagen2026-10-16 08:25 UTC35
b563d2da1c80atagen2026-10-16 08:25 UTC36
fn rows(rect: Rect, n: usize) -> Vec<Rect> {
b563d2da1c80atagen2026-10-16 08:25 UTC37
    split(rect, &vec![1.0; n], false)
b563d2da1c80atagen2026-10-16 08:25 UTC38
}
b563d2da1c80atagen2026-10-16 08:25 UTC39
b563d2da1c80atagen2026-10-16 08:25 UTC40
fn cfg(style: Style) -> Config {
b563d2da1c80atagen2026-10-16 08:25 UTC41
    Config {
b563d2da1c80atagen2026-10-16 08:25 UTC42
        style,
b563d2da1c80atagen2026-10-16 08:25 UTC43
        ..Config::default()
b563d2da1c80atagen2026-10-16 08:25 UTC44
    }
b563d2da1c80atagen2026-10-16 08:25 UTC45
}
b563d2da1c80atagen2026-10-16 08:25 UTC46
b563d2da1c80atagen2026-10-16 08:25 UTC47
fn assert_clean(p: &Plan, c: &Config) {
b563d2da1c80atagen2026-10-16 08:25 UTC48
    let v = validate(p, c);
b563d2da1c80atagen2026-10-16 08:25 UTC49
    assert!(v.is_empty(), "violations {v:?} in {p:?}");
b563d2da1c80atagen2026-10-16 08:25 UTC50
    assert!(p.phases() <= 4, "{} phases", p.phases());
b563d2da1c80atagen2026-10-16 08:25 UTC51
    assert!(
b563d2da1c80atagen2026-10-16 08:25 UTC52
        p.groups.iter().all(|g| g.strategy != Strategy::Pinch),
b563d2da1c80atagen2026-10-16 08:25 UTC53
        "fell back to pinch: {p:?}"
b563d2da1c80atagen2026-10-16 08:25 UTC54
    );
b563d2da1c80atagen2026-10-16 08:25 UTC55
}
b563d2da1c80atagen2026-10-16 08:25 UTC56
b563d2da1c80atagen2026-10-16 08:25 UTC57
fn strategy(p: &Plan) -> Strategy {
b563d2da1c80atagen2026-10-16 08:25 UTC58
    p.groups
b563d2da1c80atagen2026-10-16 08:25 UTC59
        .iter()
b563d2da1c80atagen2026-10-16 08:25 UTC60
        .map(|g| g.strategy)
b563d2da1c80atagen2026-10-16 08:25 UTC61
        .find(|s| *s != Strategy::Unchanged)
b563d2da1c80atagen2026-10-16 08:25 UTC62
        .unwrap_or(Strategy::Unchanged)
b563d2da1c80atagen2026-10-16 08:25 UTC63
}
b563d2da1c80atagen2026-10-16 08:25 UTC64
b563d2da1c80atagen2026-10-16 08:25 UTC65
#[test]
b563d2da1c80atagen2026-10-16 08:25 UTC66
fn full_reversal_needs_one_lane_per_window() {
b563d2da1c80atagen2026-10-16 08:25 UTC67
    let c = cfg(Style::Orthogonal);
b563d2da1c80atagen2026-10-16 08:25 UTC68
    let a = cols(region(), 4);
b563d2da1c80atagen2026-10-16 08:25 UTC69
    let b: Vec<Rect> = (0..4).map(|i| a[3 - i]).collect();
b563d2da1c80atagen2026-10-16 08:25 UTC70
    let p = plan(&a, &b, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC71
    assert_clean(&p, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC72
    assert_eq!(strategy(&p), Strategy::Lanes { lanes: 4 });
b563d2da1c80atagen2026-10-16 08:25 UTC73
}
b563d2da1c80atagen2026-10-16 08:25 UTC74
b563d2da1c80atagen2026-10-16 08:25 UTC75
#[test]
b563d2da1c80atagen2026-10-16 08:25 UTC76
fn growing_both_axes_depends_on_style() {
b563d2da1c80atagen2026-10-16 08:25 UTC77
    let grid = |wide: bool| {
b563d2da1c80atagen2026-10-16 08:25 UTC78
        let r = if wide { [7.0, 3.0] } else { [1.0, 1.0] };
b563d2da1c80atagen2026-10-16 08:25 UTC79
        let rs = split(region(), &r, false);
b563d2da1c80atagen2026-10-16 08:25 UTC80
        let mut out = split(rs[0], &r, true);
b563d2da1c80atagen2026-10-16 08:25 UTC81
        out.extend(split(rs[1], &[1.0, 1.0], true));
b563d2da1c80atagen2026-10-16 08:25 UTC82
        out
b563d2da1c80atagen2026-10-16 08:25 UTC83
    };
b563d2da1c80atagen2026-10-16 08:25 UTC84
    let o = cfg(Style::Orthogonal);
b563d2da1c80atagen2026-10-16 08:25 UTC85
    let d = cfg(Style::Direct);
b563d2da1c80atagen2026-10-16 08:25 UTC86
    let po = plan(&grid(false), &grid(true), &o);
b563d2da1c80atagen2026-10-16 08:25 UTC87
    let pd = plan(&grid(false), &grid(true), &d);
b563d2da1c80atagen2026-10-16 08:25 UTC88
    assert_clean(&po, &o);
b563d2da1c80atagen2026-10-16 08:25 UTC89
    assert_clean(&pd, &d);
b563d2da1c80atagen2026-10-16 08:25 UTC90
    assert_eq!((po.phases(), pd.phases()), (2, 1));
b563d2da1c80atagen2026-10-16 08:25 UTC91
}
b563d2da1c80atagen2026-10-16 08:25 UTC92
b563d2da1c80atagen2026-10-16 08:25 UTC93
#[test]
b563d2da1c80atagen2026-10-16 08:25 UTC94
fn insert_and_split_rewinds() {
b563d2da1c80atagen2026-10-16 08:25 UTC95
    let c = cfg(Style::Orthogonal);
b563d2da1c80atagen2026-10-16 08:25 UTC96
    let layout = |on: bool| {
b563d2da1c80atagen2026-10-16 08:25 UTC97
        let n = if on { 4 } else { 3 };
b563d2da1c80atagen2026-10-16 08:25 UTC98
        let ratios: Vec<f64> = (0..n)
b563d2da1c80atagen2026-10-16 08:25 UTC99
            .map(|i| if i == 0 && on { 2.0 } else { 1.0 })
b563d2da1c80atagen2026-10-16 08:25 UTC100
            .collect();
b563d2da1c80atagen2026-10-16 08:25 UTC101
        let cs = split(region(), &ratios, true);
b563d2da1c80atagen2026-10-16 08:25 UTC102
        let (a, mut b, c3) = (cs[0], cs[1], cs[n - 1]);
b563d2da1c80atagen2026-10-16 08:25 UTC103
        let d = if on {
b563d2da1c80atagen2026-10-16 08:25 UTC104
            cs[2]
b563d2da1c80atagen2026-10-16 08:25 UTC105
        } else {
b563d2da1c80atagen2026-10-16 08:25 UTC106
            Rect::new((b.right() + c3.x) / 2.0, b.y, 0.0, b.h)
b563d2da1c80atagen2026-10-16 08:25 UTC107
        };
b563d2da1c80atagen2026-10-16 08:25 UTC108
        let e = if on {
b563d2da1c80atagen2026-10-16 08:25 UTC109
            let s = rows(b, 2);
b563d2da1c80atagen2026-10-16 08:25 UTC110
            b = s[0];
b563d2da1c80atagen2026-10-16 08:25 UTC111
            s[1]
b563d2da1c80atagen2026-10-16 08:25 UTC112
        } else {
b563d2da1c80atagen2026-10-16 08:25 UTC113
            Rect::new(b.x, b.bottom(), b.w, 0.0)
b563d2da1c80atagen2026-10-16 08:25 UTC114
        };
b563d2da1c80atagen2026-10-16 08:25 UTC115
        vec![a, b, c3, d, e]
b563d2da1c80atagen2026-10-16 08:25 UTC116
    };
b563d2da1c80atagen2026-10-16 08:25 UTC117
    let on = plan(&layout(false), &layout(true), &c);
b563d2da1c80atagen2026-10-16 08:25 UTC118
    let off = plan(&layout(true), &layout(false), &c);
b563d2da1c80atagen2026-10-16 08:25 UTC119
    assert_clean(&on, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC120
    assert_clean(&off, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC121
    assert!(on.groups.iter().all(|g| !g.opens_hole));
b563d2da1c80atagen2026-10-16 08:25 UTC122
    let mut rewound = off.keyframes.clone();
b563d2da1c80atagen2026-10-16 08:25 UTC123
    rewound.reverse();
b563d2da1c80atagen2026-10-16 08:25 UTC124
    assert_eq!(on.keyframes, rewound);
b563d2da1c80atagen2026-10-16 08:25 UTC125
}
b563d2da1c80atagen2026-10-16 08:25 UTC126
b563d2da1c80atagen2026-10-16 08:25 UTC127
#[test]
b563d2da1c80atagen2026-10-16 08:25 UTC128
fn tag_rows_swap_rigidly_with_two_lanes() {
b563d2da1c80atagen2026-10-16 08:25 UTC129
    let c = cfg(Style::Orthogonal);
b563d2da1c80atagen2026-10-16 08:25 UTC130
    let layout = |first_top: bool| {
b563d2da1c80atagen2026-10-16 08:25 UTC131
        let rs = rows(region(), 2);
b563d2da1c80atagen2026-10-16 08:25 UTC132
        let (t1, t2) = if first_top {
b563d2da1c80atagen2026-10-16 08:25 UTC133
            (rs[0], rs[1])
b563d2da1c80atagen2026-10-16 08:25 UTC134
        } else {
b563d2da1c80atagen2026-10-16 08:25 UTC135
            (rs[1], rs[0])
b563d2da1c80atagen2026-10-16 08:25 UTC136
        };
b563d2da1c80atagen2026-10-16 08:25 UTC137
        let mut out = cols(t1, 3);
b563d2da1c80atagen2026-10-16 08:25 UTC138
        out.extend(cols(t2, 2));
b563d2da1c80atagen2026-10-16 08:25 UTC139
        out
b563d2da1c80atagen2026-10-16 08:25 UTC140
    };
b563d2da1c80atagen2026-10-16 08:25 UTC141
    let blocks = vec![vec![0, 1, 2], vec![3, 4]];
b563d2da1c80atagen2026-10-16 08:25 UTC142
    let p = plan_blocks(&layout(true), &layout(false), &blocks, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC143
    assert_clean(&p, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC144
    assert_eq!(strategy(&p), Strategy::Lanes { lanes: 2 });
b563d2da1c80atagen2026-10-16 08:25 UTC145
}
b563d2da1c80atagen2026-10-16 08:25 UTC146
b563d2da1c80atagen2026-10-16 08:25 UTC147
#[test]
b563d2da1c80atagen2026-10-16 08:25 UTC148
fn simultaneous_reversals_on_both_axes_use_the_grid() {
b563d2da1c80atagen2026-10-16 08:25 UTC149
    let c = cfg(Style::Orthogonal);
b563d2da1c80atagen2026-10-16 08:25 UTC150
    let halves = cols(region(), 2);
b563d2da1c80atagen2026-10-16 08:25 UTC151
    let (l, r) = (rows(halves[0], 2), rows(halves[1], 2));
b563d2da1c80atagen2026-10-16 08:25 UTC152
    let from = vec![l[0], r[1], r[0], l[1]];
b563d2da1c80atagen2026-10-16 08:25 UTC153
    let left3 = rows(halves[0], 3);
b563d2da1c80atagen2026-10-16 08:25 UTC154
    let to = vec![left3[0], left3[1], left3[2], halves[1]];
b563d2da1c80atagen2026-10-16 08:25 UTC155
    let p = plan(&from, &to, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC156
    assert_clean(&p, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC157
    assert!(matches!(strategy(&p), Strategy::Grid { .. }));
b563d2da1c80atagen2026-10-16 08:25 UTC158
}
b563d2da1c80atagen2026-10-16 08:25 UTC159
b563d2da1c80atagen2026-10-16 08:25 UTC160
struct Rng(u64);
b563d2da1c80atagen2026-10-16 08:25 UTC161
b563d2da1c80atagen2026-10-16 08:25 UTC162
impl Rng {
b563d2da1c80atagen2026-10-16 08:25 UTC163
    fn next(&mut self) -> u64 {
b563d2da1c80atagen2026-10-16 08:25 UTC164
        self.0 ^= self.0 << 13;
b563d2da1c80atagen2026-10-16 08:25 UTC165
        self.0 ^= self.0 >> 7;
b563d2da1c80atagen2026-10-16 08:25 UTC166
        self.0 ^= self.0 << 17;
b563d2da1c80atagen2026-10-16 08:25 UTC167
        self.0
b563d2da1c80atagen2026-10-16 08:25 UTC168
    }
b563d2da1c80atagen2026-10-16 08:25 UTC169
b563d2da1c80atagen2026-10-16 08:25 UTC170
    fn below(&mut self, n: usize) -> usize {
b563d2da1c80atagen2026-10-16 08:25 UTC171
        (self.next() % n as u64) as usize
b563d2da1c80atagen2026-10-16 08:25 UTC172
    }
b563d2da1c80atagen2026-10-16 08:25 UTC173
b563d2da1c80atagen2026-10-16 08:25 UTC174
    fn unit(&mut self) -> f64 {
b563d2da1c80atagen2026-10-16 08:25 UTC175
        (self.next() >> 11) as f64 / (1u64 << 53) as f64
b563d2da1c80atagen2026-10-16 08:25 UTC176
    }
b563d2da1c80atagen2026-10-16 08:25 UTC177
}
b563d2da1c80atagen2026-10-16 08:25 UTC178
b563d2da1c80atagen2026-10-16 08:25 UTC179
fn random_layout(rng: &mut Rng, n: usize) -> Vec<Rect> {
b563d2da1c80atagen2026-10-16 08:25 UTC180
    let mut ids: Vec<usize> = (0..n).collect();
b563d2da1c80atagen2026-10-16 08:25 UTC181
    for i in (1..n).rev() {
b563d2da1c80atagen2026-10-16 08:25 UTC182
        ids.swap(i, rng.below(i + 1));
b563d2da1c80atagen2026-10-16 08:25 UTC183
    }
b563d2da1c80atagen2026-10-16 08:25 UTC184
    let mut out = vec![Rect::default(); n];
b563d2da1c80atagen2026-10-16 08:25 UTC185
    let horizontal = rng.below(2) == 0;
b563d2da1c80atagen2026-10-16 08:25 UTC186
    build(rng, &ids, region(), horizontal, &mut out);
b563d2da1c80atagen2026-10-16 08:25 UTC187
    out
b563d2da1c80atagen2026-10-16 08:25 UTC188
}
b563d2da1c80atagen2026-10-16 08:25 UTC189
b563d2da1c80atagen2026-10-16 08:25 UTC190
fn build(rng: &mut Rng, ids: &[usize], rect: Rect, horizontal: bool, out: &mut [Rect]) {
b563d2da1c80atagen2026-10-16 08:25 UTC191
    if let [only] = ids {
b563d2da1c80atagen2026-10-16 08:25 UTC192
        out[*only] = rect;
b563d2da1c80atagen2026-10-16 08:25 UTC193
        return;
b563d2da1c80atagen2026-10-16 08:25 UTC194
    }
b563d2da1c80atagen2026-10-16 08:25 UTC195
    let k = ids.len().min(2 + rng.below(2));
b563d2da1c80atagen2026-10-16 08:25 UTC196
    let mut cuts = Vec::with_capacity(k);
b563d2da1c80atagen2026-10-16 08:25 UTC197
    let mut rest = ids;
b563d2da1c80atagen2026-10-16 08:25 UTC198
    for i in 0..k {
b563d2da1c80atagen2026-10-16 08:25 UTC199
        let take = if i == k - 1 {
b563d2da1c80atagen2026-10-16 08:25 UTC200
            rest.len()
b563d2da1c80atagen2026-10-16 08:25 UTC201
        } else {
b563d2da1c80atagen2026-10-16 08:25 UTC202
            1 + rng.below(rest.len() - (k - 1 - i))
b563d2da1c80atagen2026-10-16 08:25 UTC203
        };
b563d2da1c80atagen2026-10-16 08:25 UTC204
        let (head, tail) = rest.split_at(take);
b563d2da1c80atagen2026-10-16 08:25 UTC205
        cuts.push(head);
b563d2da1c80atagen2026-10-16 08:25 UTC206
        rest = tail;
b563d2da1c80atagen2026-10-16 08:25 UTC207
    }
b563d2da1c80atagen2026-10-16 08:25 UTC208
    let ratios: Vec<f64> = cuts.iter().map(|_| 0.5 + rng.unit()).collect();
b563d2da1c80atagen2026-10-16 08:25 UTC209
    for (part, ids) in split(rect, &ratios, horizontal).into_iter().zip(cuts) {
b563d2da1c80atagen2026-10-16 08:25 UTC210
        build(rng, ids, part, !horizontal, out);
b563d2da1c80atagen2026-10-16 08:25 UTC211
    }
b563d2da1c80atagen2026-10-16 08:25 UTC212
}
b563d2da1c80atagen2026-10-16 08:25 UTC213
b563d2da1c80atagen2026-10-16 08:25 UTC214
proptest! {
b563d2da1c80atagen2026-10-16 08:25 UTC215
    #![proptest_config(ProptestConfig::with_cases(512))]
b563d2da1c80atagen2026-10-16 08:25 UTC216
b563d2da1c80atagen2026-10-16 08:25 UTC217
    #[test]
b563d2da1c80atagen2026-10-16 08:25 UTC218
    fn random_transitions_plan_cleanly(seed in 1u64.., n in 2usize..9, direct: bool) {
b563d2da1c80atagen2026-10-16 08:25 UTC219
        let mut rng = Rng(seed);
b563d2da1c80atagen2026-10-16 08:25 UTC220
        let c = cfg(if direct { Style::Direct } else { Style::Orthogonal });
b563d2da1c80atagen2026-10-16 08:25 UTC221
        let (a, b) = (random_layout(&mut rng, n), random_layout(&mut rng, n));
b563d2da1c80atagen2026-10-16 08:25 UTC222
        let p = plan(&a, &b, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC223
        assert_clean(&p, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC224
        prop_assert_eq!(&p.keyframes[0], &a);
b563d2da1c80atagen2026-10-16 08:25 UTC225
        prop_assert_eq!(p.keyframes.last().unwrap(), &b);
b563d2da1c80atagen2026-10-16 08:25 UTC226
    }
b563d2da1c80atagen2026-10-16 08:25 UTC227
b563d2da1c80atagen2026-10-16 08:25 UTC228
    #[test]
b563d2da1c80atagen2026-10-16 08:25 UTC229
    fn planning_back_rewinds(seed in 1u64.., n in 2usize..9) {
b563d2da1c80atagen2026-10-16 08:25 UTC230
        let mut rng = Rng(seed);
b563d2da1c80atagen2026-10-16 08:25 UTC231
        let c = cfg(Style::Orthogonal);
b563d2da1c80atagen2026-10-16 08:25 UTC232
        let (a, b) = (random_layout(&mut rng, n), random_layout(&mut rng, n));
b563d2da1c80atagen2026-10-16 08:25 UTC233
        let mut back = plan_canonical(&b, &a, &c).keys;
b563d2da1c80atagen2026-10-16 08:25 UTC234
        back.reverse();
b563d2da1c80atagen2026-10-16 08:25 UTC235
        prop_assert_eq!(plan_canonical(&a, &b, &c).keys, back);
b563d2da1c80atagen2026-10-16 08:25 UTC236
    }
b563d2da1c80atagen2026-10-16 08:25 UTC237
b563d2da1c80atagen2026-10-16 08:25 UTC238
    #[test]
b563d2da1c80atagen2026-10-16 08:25 UTC239
    fn interrupts_replan_cleanly(seed in 1u64.., n in 2usize..9, at in 0.0f64..1.0, s in 0.0f64..1.0) {
b563d2da1c80atagen2026-10-16 08:25 UTC240
        let mut rng = Rng(seed);
b563d2da1c80atagen2026-10-16 08:25 UTC241
        let c = cfg(Style::Orthogonal);
b563d2da1c80atagen2026-10-16 08:25 UTC242
        let (a, b, d) = (random_layout(&mut rng, n), random_layout(&mut rng, n), random_layout(&mut rng, n));
b563d2da1c80atagen2026-10-16 08:25 UTC243
        let first = plan(&a, &b, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC244
        if first.phases() == 0 {
b563d2da1c80atagen2026-10-16 08:25 UTC245
            return Ok(());
b563d2da1c80atagen2026-10-16 08:25 UTC246
        }
b563d2da1c80atagen2026-10-16 08:25 UTC247
        let phase = ((at * first.phases() as f64) as usize).min(first.phases() - 1);
b563d2da1c80atagen2026-10-16 08:25 UTC248
        let mid = first.sample(phase, s);
b563d2da1c80atagen2026-10-16 08:25 UTC249
        let second = plan(&mid, &d, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC250
        assert_clean(&second, &c);
b563d2da1c80atagen2026-10-16 08:25 UTC251
        prop_assert_eq!(&second.keyframes[0], &mid);
b563d2da1c80atagen2026-10-16 08:25 UTC252
    }
b563d2da1c80atagen2026-10-16 08:25 UTC253
}
1a10e81ae5c1atagen2026-10-17 13:31 UTC254
1a10e81ae5c1atagen2026-10-17 13:31 UTC255
/// A hidden window is a zero-size seed. One sitting inside a visible window
1a10e81ae5c1atagen2026-10-17 13:31 UTC256
/// used to join its group and make every strategy fail, so the plan pinched.
1a10e81ae5c1atagen2026-10-17 13:31 UTC257
#[test]
1a10e81ae5c1atagen2026-10-17 13:31 UTC258
fn hidden_seeds_stay_out_of_the_plan() {
1a10e81ae5c1atagen2026-10-17 13:31 UTC259
    let c = cfg(Style::Orthogonal);
1a10e81ae5c1atagen2026-10-17 13:31 UTC260
    let a = cols(region(), 2);
1a10e81ae5c1atagen2026-10-17 13:31 UTC261
    let seed = |r: Rect| Rect::new(r.center_x(), r.center_y(), 0.0, 0.0);
1a10e81ae5c1atagen2026-10-17 13:31 UTC262
    let from = vec![a[0], a[1], seed(a[0])];
1a10e81ae5c1atagen2026-10-17 13:31 UTC263
    let to = vec![a[1], a[0], seed(a[1])];
1a10e81ae5c1atagen2026-10-17 13:31 UTC264
    let p = plan(&from, &to, &c);
1a10e81ae5c1atagen2026-10-17 13:31 UTC265
    assert_clean(&p, &c);
1a10e81ae5c1atagen2026-10-17 13:31 UTC266
    assert!(p.groups.iter().all(|g| !g.members.contains(&2)));
1a10e81ae5c1atagen2026-10-17 13:31 UTC267
    assert!(p.keyframes.iter().all(|k| k[2] == to[2]));
1a10e81ae5c1atagen2026-10-17 13:31 UTC268
}