Take two roundings out of where a box comes from

Traced what was left of the warm-against-cold difference after fixed point.
It is not accumulation and not one place: it is the same box reached two
ways, and each way rounds where the other does not.

`Scroll` was writing a box it had been given back out as its own length in
pixels. That is the same box in another form, and centring a part in `rel 1`
lands a step from centring it in `px 900`, because halving a difference is
not halving each part of it. Content that fills the viewport and has not been
scrolled is now handed back as it came, which makes the shrinker's `repaint`
and `resize-repaint` cases agree exactly rather than within a step.

`Span` placed each child a step from where the last one ended, so the
rounding of every share was carried along the row. A position is now the
fixed parts before it -- a sum, exact -- plus one rounded share of the room.
Measured: two hundred equal shares of a 1000 px row ended at 999.999 and now
end at 1000, and `tests/cases/layout.rs` pins it at 2, 3, 7, 64 and 200.

What is left is a step per level of nesting between the two ways, which is
what the fuzzers now allow: four of the five shrinker cases pass at one step
and the fifth is five spans deep. Closing it needs one way of asking where a
box is, which is a bigger change than this.

Checked: fmt, clippy, 103 tests, all five shrinker cases at 300 seeds, 100
generated seeds, five examples byte-identical at 1920x1200.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-16 03:42:09 -04:00
1 parent 9d8415d65f
commit bdab55824f
6 files changed
+90 -25

No files matched your search

+37
View File
@@ -285,6 +285,43 @@ fn an_uneven_nesting_still_gives_every_share_the_same_length() {
}
}
/// However many ways a row is divided, the shares add up to the row: each
/// one is the fixed parts before it plus a share of the room, rather than a
/// step from where the last one ended, so the roundings do not accumulate
/// along it. Chained, two hundred of them ended a step short of the edge.
#[test]
fn a_row_of_equal_shares_fills_it_exactly() {
for n in [2usize, 3, 7, 64, 200] {
let mut h = Harness::new((1000, 100));
let mut ids = Vec::new();
let mut kids: Vec<StrongWidget> = Vec::new();
for _ in 0..n {
let kid = rect(Color::RED).add(&mut h.rsc);
ids.push(kid.id());
kids.push(kid.add_strong(&mut h.rsc));
}
let span = Span {
children: kids,
dir: Dir::RIGHT,
gap: Px::ZERO,
}
.add(&mut h.rsc);
h.set_root(span);
h.frame();
for (i, id) in ids.iter().enumerate() {
let at = h.region(id).expect("a share drew nothing").top_left.x;
let want = Px::from_f32(1000.0 * (i as f32) / (n as f32));
assert!(
(at - want).abs() <= Px::STEP,
"{n} shares: the {i}th starts at {at:?}, not {want:?}"
);
}
let end = h.region(ids.last().unwrap()).unwrap().bot_right.x;
assert_eq!(end, Px::from_int(1000), "{n} shares do not reach the edge");
}
}
/// Where the shader puts an edge: the two parts of a scalar are floored
/// apart, so a fraction and a pixel offset snap independently, and each is
/// taken to the boundary it composes to within half a step of. Kept in step
+5 -6
View File
@@ -31,12 +31,11 @@ fn env<T: std::str::FromStr>(name: &str, fallback: T) -> T {
}
const SEEDS: [u64; 9] = [1, 2, 3, 5, 8, 10, 13, 86, 98];
/// The same box, to a step of the grid per operation. Warm and cold reach a
/// coordinate by different arithmetic: a move lands on the same number now,
/// and a length composed one way against the same length measured another can
/// land one step out. These cases apply two operations in turn, so they allow
/// two steps -- a thousandth of a pixel each, where this was a twentieth of
/// one before any of it was on a grid.
/// The same box, to a step of the grid per level of nesting between the two
/// ways of reaching it. A move, a repaint and a row of shares land on the
/// same number now; what is left is a box centred in a fraction of its parent
/// against the same box centred in its own pixels. A step is a thousandth of
/// a pixel, where this was a twentieth of one before any of it was on a grid.
const AGREE_STEPS: i32 = 2;
fn same_region(got: Option<PixelRegion>, want: Option<PixelRegion>) -> bool {
+3 -3
View File
@@ -51,9 +51,9 @@ const WORDS: &[&str] = &[
const ONE_LINE: &str = "one line, overflowing whatever it is given";
const OUTER: (f32, f32) = (1920.0, 1200.0);
/// Steps of the grid two ways of reaching a box may differ by. See
/// `docs/HANDOFF.md`'s "Fixed point" in `ai-app-2` for where the last of
/// them is.
/// Steps of the grid two ways of reaching a box may differ by: one per level
/// of nesting between them, and these trees are five deep. See
/// `docs/HANDOFF.md`'s "Fixed point" in `ai-app-2` for what is left.
const AGREE_STEPS: i32 = 2;
const INNER: (f32, f32) = (640.0, 900.0);