Record the leftover boundary, and close the reorder case

The shrinker's `reorder` case is no longer red: its two seeds were a span
deciding whether anything was left over on a box length its own answer had
produced, which comes back a few bits off. `5ed9e87` in `/home/bob/repos/
iris-pr18` moves that boundary by `HOLDS_EPSILON_PX` of room instead of
softening the comparison, so the validity range stays exact where generated
seed 16 needs it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-15 23:49:28 -04:00
1 parent 76334cd5aa
commit 1a73aba1cd
1 file changed
+53 -22
+53 -22
View File
@@ -8,7 +8,7 @@ log.
Canonical Iris `main` is **`ca2b4b2`** (#17, the headless rig). **#18 Canonical Iris `main` is **`ca2b4b2`** (#17, the headless rig). **#18
`split/18-position-chain`** is open in `/home/bob/repos/iris-pr18`; its local `split/18-position-chain`** is open in `/home/bob/repos/iris-pr18`; its local
head is **`d3b0ebf`**, fifty commits. Built-in alignment is complete there; head is **`5ed9e87`**, fifty-one commits. Built-in alignment is complete there;
see "Built-in alignment" below for the retained-layout details. No PR reviews see "Built-in alignment" below for the retained-layout details. No PR reviews
were present when checked on 2026-09-15. were present when checked on 2026-09-15.
@@ -65,20 +65,25 @@ rewrite:
- `Span`'s leftover/no-leftover split is a strict layout decision, not a - `Span`'s leftover/no-leftover split is a strict layout decision, not a
rounding tolerance. Its `Holds` range must use the same exact divided rounding tolerance. Its `Holds` range must use the same exact divided
boundary as drawing; a tolerant endpoint retained zero-height children at boundary as drawing; a tolerant endpoint retained zero-height children at
the boundary in generated seed 16. the boundary in generated seed 16. `5ed9e87` moves that boundary rather
than softening it -- see "The leftover boundary" below.
`core/src/ui/holds.rs`, the retained tests, and the generated cold-layout oracle `core/src/ui/holds.rs`, the retained tests, and the generated cold-layout oracle
pin those rules. Seeds 10 and 86 are now in the ordinary generated set. pin those rules. Seeds 10 and 86 are now in the ordinary generated set.
## Verification at `d3b0ebf` ## Verification at `5ed9e87`
- `cargo fmt --all --check` - `cargo fmt --all --check`
- `cargo build --workspace --all-features` - `cargo build --workspace --all-features`
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` - `cargo clippy --workspace --all-targets --all-features -- -D warnings`
- `cargo test --workspace --all-features`: 88 passed, 10 ignored - `cargo test --workspace --all-features`: 89 passed, 10 ignored
- The release generated cold-layout oracle passed 100 seeds in 65.5 s. - The release generated cold-layout oracle passed 100 seeds in 68.5 s.
- The release shrinker passed both `repaint` and `resize` at 300 seeds each: - The release shrinker passed **all five** cases -- `resize`, `repaint`,
26,001 widgets per case, largest tree 331 widgets. `resize-repaint`, `reorder`, `size-change` -- at 1000 seeds of depth 6:
159,024 widgets per case, largest tree 587 widgets. At the ordinary 300
seeds of depth 5 it is 26,001 widgets per case, largest tree 331.
- `tabs`, `view`, `minimal`, `text` and `random` render byte-identical at
1920x1200 against the same worktree without `5ed9e87`.
- `tests/drift.rs` passed its 20,000-move exactness check in release mode. - `tests/drift.rs` passed its 20,000-move exactness check in release mode.
- The seeded `random` example, live-resized from 1920x1200 to 1280x800, is - The seeded `random` example, live-resized from 1920x1200 to 1280x800, is
byte-identical to a cold 1280x800 render. Both PNGs hash to byte-identical to a cold 1280x800 render. Both PNGs hash to
@@ -175,11 +180,12 @@ tighter figures.
- Text shaping is retained separately from line breaking. A greedy line break - Text shaping is retained separately from line breaking. A greedy line break
remains valid from its longest produced line through the width at which it remains valid from its longest produced line through the width at which it
was made, and `TextView` reports that interval through `Painter::holds`. was made, and `TextView` reports that interval through `Painter::holds`.
- `Span`'s decision to distribute `leftover` is a pixel question. Its validity - `Span`'s decision to distribute `leftover` is a pixel question. The room to
interval is split exactly at the length where fixed parts fill the box; divide is `len * fixed - total.px`, and under `HOLDS_EPSILON_PX` of it is
pure `leftover` children are undrawn on the no-space side. Exact/open ranges none; pure `leftover` children are undrawn on the no-room side. The validity
are for hard widget decisions; the ordinary tolerant ranges remain for interval is split exactly at that moved boundary. Exact/open ranges are for
accumulated coordinate rounding. hard widget decisions; the ordinary tolerant ranges remain for accumulated
coordinate rounding.
- `Scroll` reports its content's first measured size. Its drawing can survive - `Scroll` reports its content's first measured size. Its drawing can survive
container-length changes only over the interval in which clamping and its container-length changes only over the interval in which clamping and its
current offset do not change. current offset do not change.
@@ -199,9 +205,7 @@ tighter figures.
## Built-in alignment ## Built-in alignment
Committed as `d3b0ebf` in `/home/bob/repos/iris-pr18`. The shrinker's Committed as `d3b0ebf` in `/home/bob/repos/iris-pr18`.
`reorder` case remains red at its base `8220a78`, so that failure is
pre-existing and unrelated.
What is in it: `align` is a widget property beside `region_node` and the size What is in it: `align` is a widget property beside `region_node` and the size
rule, `Aligned` is deleted, `.align()`/`.center()` set the property, and the rule, `Aligned` is deleted, `.align()`/`.center()` set the property, and the
@@ -288,6 +292,35 @@ length the box takes and the drawing holds for that length alone.
A `debug_assert` that a reported non-leftover size does not exceed the box it A `debug_assert` that a reported non-leftover size does not exceed the box it
drew in would have caught both immediately, and is still worth adding. drew in would have caught both immediately, and is still worth adding.
## The leftover boundary
Fixed in `5ed9e87`, which closed the shrinker's `reorder` case. Neither of its
two red seeds was about reordering.
A span's box in pixels, compared with what its fixed and relative children
fill, is the same number whenever the parent sized that box from the span's own
answer -- and the box comes back through the chain a few bits off. So
`0.00003 px` decided whether a `leftover`-only child existed: warm rounded
under and left it undrawn, cold rounded over and drew it at zero length. Both
layouts are stable and the pixels are identical either way, which is why only
the warm-against-cold oracle could see it.
**A structural decision may not be taken where boxes structurally land.** The
fix is not a tolerant comparison -- that is what generated seed 16 punished --
but a boundary moved by `HOLDS_EPSILON_PX` of room, with the validity range
split exactly at the moved boundary. What it gives up is a share of under a
twentieth of a pixel. `tests/unsettled.rs`'s
`a_box_that_only_rounds_past_its_fixed_children_leaves_nothing_over` is the
six-widget regression, shrunk from 266; it needs the span above the one that
divides, because without a box composed through it both trees round the same
way.
`Scroll`'s `content_len <= container_len` sits on the same coincidence but is
continuous there -- it chooses between two `Holds` ranges that both contain the
current length, so a rounding difference costs a redraw rather than a
different layout. Checked while fixing this; nothing else reads a box in
pixels to decide something structural.
## Rigs and reproduction ## Rigs and reproduction
Ordinary framework verification: Ordinary framework verification:
@@ -349,15 +382,13 @@ child-placement API. `docs/LAYOUT.md` §2 is stale: it still describes
**Built-in alignment and size goes on #18 rather than after it** (Bryan, **Built-in alignment and size goes on #18 rather than after it** (Bryan,
2026-09-15: #18 is unreviewed and already large enough that most lines get read 2026-09-15: #18 is unreviewed and already large enough that most lines get read
anyway). The size half landed as `8220a78`; the alignment half is in the anyway). The size half landed as `8220a78`, the alignment half as `d3b0ebf`.
working tree and described below.
Do not restore `OnResize::Translate`; retained translation is now expressed by Do not restore `OnResize::Translate`; retained translation is now expressed by
the same `Holds` contract and box chain. the same `Holds` contract and box chain.
Queued from this work, in order: Queued from this work, in order:
- The shrinker's `repaint` case, via the replacement placement design above.
- Delete `OrthoSize`. It is redundant now that size is built in: a span should - Delete `OrthoSize`. It is redundant now that size is built in: a span should
read its own rule on the orthogonal axis and, where that is fixed, skip read its own rule on the orthogonal axis and, where that is fixed, skip
reading its children's orthogonal sizes entirely, which is what `Full` does reading its children's orthogonal sizes entirely, which is what `Full` does
@@ -369,10 +400,10 @@ Queued from this work, in order:
clamp resolved where the box is decided also fixes `MaxSize`'s reading of clamp resolved where the box is decided also fixes `MaxSize`'s reading of
`px_size`, which pinned its interval to one exact box on both axes and `px_size`, which pinned its interval to one exact box on both axes and
redrew its whole subtree on any resize. The clamp boundary is a hard layout redrew its whole subtree on any resize. The clamp boundary is a hard layout
decision, not a tolerance: its `Holds` range must be exact and split at the decision: its `Holds` range must be exact and split at the crossover, the
crossover, the way generated seed 16 taught for `Span`. way generated seed 16 taught for `Span` -- and the crossover itself has to
- The shrinker's `reorder` case, which is red at `8220a78` and was not sit off the length a box structurally lands on, the way `5ed9e87` taught.
introduced by any of this. See "The leftover boundary".
Other queued work, in dependency order: Other queued work, in dependency order: