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
`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
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
rounding tolerance. Its `Holds` range must use the same exact divided
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
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 build --workspace --all-features`
- `cargo clippy --workspace --all-targets --all-features -- -D warnings`
- `cargo test --workspace --all-features`: 88 passed, 10 ignored
- The release generated cold-layout oracle passed 100 seeds in 65.5 s.
- The release shrinker passed both `repaint` and `resize` at 300 seeds each:
26,001 widgets per case, largest tree 331 widgets.
- `cargo test --workspace --all-features`: 89 passed, 10 ignored
- The release generated cold-layout oracle passed 100 seeds in 68.5 s.
- The release shrinker passed **all five** cases -- `resize`, `repaint`,
`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.
- The seeded `random` example, live-resized from 1920x1200 to 1280x800, is
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
remains valid from its longest produced line through the width at which it
was made, and `TextView` reports that interval through `Painter::holds`.
- `Span`'s decision to distribute `leftover` is a pixel question. Its validity
interval is split exactly at the length where fixed parts fill the box;
pure `leftover` children are undrawn on the no-space side. Exact/open ranges
are for hard widget decisions; the ordinary tolerant ranges remain for
accumulated coordinate rounding.
- `Span`'s decision to distribute `leftover` is a pixel question. The room to
divide is `len * fixed - total.px`, and under `HOLDS_EPSILON_PX` of it is
none; pure `leftover` children are undrawn on the no-room side. The validity
interval is split exactly at that moved boundary. Exact/open ranges are for
hard widget decisions; the ordinary tolerant ranges remain for accumulated
coordinate rounding.
- `Scroll` reports its content's first measured size. Its drawing can survive
container-length changes only over the interval in which clamping and its
current offset do not change.
@@ -199,9 +205,7 @@ tighter figures.
## Built-in alignment
Committed as `d3b0ebf` in `/home/bob/repos/iris-pr18`. The shrinker's
`reorder` case remains red at its base `8220a78`, so that failure is
pre-existing and unrelated.
Committed as `d3b0ebf` in `/home/bob/repos/iris-pr18`.
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
@@ -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
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
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,
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
working tree and described below.
anyway). The size half landed as `8220a78`, the alignment half as `d3b0ebf`.
Do not restore `OnResize::Translate`; retained translation is now expressed by
the same `Holds` contract and box chain.
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
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
@@ -369,10 +400,10 @@ Queued from this work, in order:
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
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
crossover, the way generated seed 16 taught for `Span`.
- The shrinker's `reorder` case, which is red at `8220a78` and was not
introduced by any of this.
decision: its `Holds` range must be exact and split at the crossover, the
way generated seed 16 taught for `Span` -- and the crossover itself has to
sit off the length a box structurally lands on, the way `5ed9e87` taught.
See "The leftover boundary".
Other queued work, in dependency order: