Record the text-width defect, and that the depth-4 seeds are fixed

The two seeds a wider scan found were one defect, older than the branch: a
text reported a width rounded to the step below the line it measured, so
the box that came back to it broke differently. Two tolerances were holding
that together and both are gone.
This commit is contained in:
iris-ai committed 2026-09-17 03:21:35 -04:00
1 parent f64d6a8d4d
commit faa4e98311
1 file changed
+66 -37
+66 -37
View File
@@ -10,21 +10,19 @@ stayed.
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`, head `split/18-position-chain`** is open in `/home/bob/repos/iris-pr18`, head
**`ffd79f3`**, pushed. It holds LAYOUT.md §2's position chain, `leftover`, **`c8beca5`**, pushed. It holds LAYOUT.md §2's position chain, `leftover`,
the `Holds` retained-layout contract, region nodes, built-in alignment and the `Holds` retained-layout contract, region nodes, built-in alignment and
size rules, fixed-point layout, a box in pixels threaded down the draw, and size rules, fixed-point layout, a box in pixels threaded down the draw, and
a report read as a fraction of the containing widget. No PR review was a report read as a fraction of the containing widget. No PR review was
present when checked on 2026-09-15. present when checked on 2026-09-15.
**The branch has a warm/cold divergence nothing on it explains yet**, found **Widen one fuzzer axis at a time, and record which.** Seeds **1121** and
2026-09-17 by widening the fuzzers rather than by any change: seeds **1121** **1839** at **depth 4** failed on `ea6dbae` and on every commit before it,
and **1839** at **depth 4** fail `shuffle-swap-for-three` and and nothing in the routine verification reached them: the fast oracle takes
`shuffle-all-but-first` respectively, and they fail the same way on ten seeds, the shrinker 400 at depth 5 and the long oracle 1000 at depth 6,
`ea6dbae`, before either of the day's commits. Nothing in the routine so a defect past seed 400 at depth 4 had nowhere to show. Both are fixed by
verification reaches them -- the fast oracle takes ten seeds, the shrinker `4bd8607`. The scan that found them, worth running again after any change
400 at depth 5 and the long oracle 1000 at depth 6, and these are past 400 to layout:
at a depth neither long run uses. Chasing them is first in "Next". The scan
that found them, which is worth keeping as a pattern:
```rust ```rust
// tests/scan.rs, deleted once it had done its job // tests/scan.rs, deleted once it had done its job
@@ -38,9 +36,10 @@ over_seeds((1..=2000).collect(), |seed| {
}); });
``` ```
262 s for 2000 seeds at depth 4 over all fifteen cases. `Rng::new` is 261 s for 2000 seeds at depth 4 over all fifteen cases, and clean at
`seed | 1`, so an even seed and the odd one above it are one tree: 1120 and `4bd8607`. `Rng::new` is `seed | 1`, so an even seed and the odd one above
1121 are the same counterexample, as are 1838 and 1839. it are one tree: 1120 and 1121 are the same counterexample, as are 1838 and
1839.
**Two ideas outrank everything else in this document** (Bryan, 2026-09-17). **Two ideas outrank everything else in this document** (Bryan, 2026-09-17).
First, a changed tree lays out exactly as if it had been drawn that way from First, a changed tree lays out exactly as if it had been drawn that way from
@@ -142,6 +141,38 @@ divergence needs a reader drawing while a size dependency two levels under
it is dirty, which no hand-built tree has reproduced yet, and the seeds at it is dirty, which no hand-built tree has reproduced yet, and the seeds at
depth 4 above fail for some other reason. depth 4 above fail for some other reason.
### A text is handed back a box its own line fits in (landed, `4bd8607`)
A wrapping text reported the width it used through `Px::from_f32`, which
takes the nearest step and is under the line the shaper measured half the
time. A parent that sizes itself from that report -- a stack taking a span's
width, the span taking its widest child's -- then hands the text back a box
its own longest line does not fit in, and a greedy break there is one line
longer. Warm kept the break it had; cold made the narrower one.
Two tolerances were holding that together and both are gone:
- `TextBuffer::shape` answered any width within `BREAK_EPSILON_PX = 0.05` of
the longest line from the break in hand. Fifty steps of the grid, and a
structural decision taken on a hair's breadth -- the thing the `Span`
boundary invariant below already forbids. It is `want >= layout.width()`
now, exactly.
- The `Holds` range the text declares started at the nearest step to its
longest line, so it admitted boxes that line does not fit in. It starts at
`Px::ceil_from_f32` of it now.
Neither was the fix. **The fix is the report**: `Size::from_px(
PxVec2::ceil_from_f32(tex.size))`, the step at or above what was measured,
so the box that comes back fits. With it in place either tolerance could
have stayed and the case passes; both are wrong on their own terms, so both
went. `Fixed::ceil_from_f32` is new and is the only rounding on the grid
that is not to the nearest step.
The general shape, and the third time this branch has hit it: **a value that
comes back as a box has to be rounded away from the measurement, not to the
nearest step.** Rounding to nearest is right for a value being carried;
it is wrong for a bound.
### `Span`'s leftover boundary is a third expression for the room ### `Span`'s leftover boundary is a third expression for the room
The decision uses a rounded division, `total.px.div(fixed)`, while the room The decision uses a rounded division, `total.px.div(fixed)`, while the room
@@ -446,19 +477,24 @@ above for what closing the rest would cost.
## Verification at the current head ## Verification at the current head
At `ffd79f3`: At `c8beca5`:
- `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- - `cargo fmt --all --check`, `cargo clippy --workspace --all-targets --
-D warnings`, `cargo test --workspace`: green, 89 suite tests, 18 core -D warnings`, `cargo test --workspace`: green, 90 suite tests, 19 core
unit tests, 11 generated cases. Only the long runs and the profiling rigs unit tests, 11 generated cases. Only the long runs and the profiling rigs
are ignored; no known defect is. are ignored; no known defect is.
- The release oracle at 100 seeds in 14.3 s, and **120 seeds in debug** in - The release oracle at 100 seeds in 14.3 s, and **120 seeds in debug** in
59 s -- the debug run exercises the `Holds` assertion in `draw_at`. 59 s -- the debug run exercises the `Holds` assertion in `draw_at`.
- All fifteen shrinker cases at 400 seeds of depth 5 in 56 s, and the - All fifteen shrinker cases at 400 seeds of depth 5 in 56 s, the oracle at
oracle at 1000 seeds of depth 6 in 142 s. 1000 seeds of depth 6 in 142 s, and **2000 seeds at depth 4 over all
- `view`, `minimal`, `random`, `tabs` and `text` byte-identical at fifteen cases** in 261 s.
1920x1200 against `ea6dbae`. `random` live-resized from 1920x1200 to - `view`, `minimal`, `random` and `tabs` byte-identical at 1920x1200
1280x800 is byte-identical to a cold 1280x800 render. against `ea6dbae`. `random` live-resized from 1920x1200 to 1280x800 is
byte-identical to a cold 1280x800 render. **`text` is a new picture**:
`4bd8607` moved its lower paragraph one pixel, the box being a step wider
and its left edge crossing the shader's snap, and `c8beca5` rewrote the
alignment panel, which had all three labels in the middle of a box the
width of the widest of them.
- Twenty-five rig work counters identical on `cold`, `repaint`, `scroll`, - Twenty-five rig work counters identical on `cold`, `repaint`, `scroll`,
`resize` and `size` across `0e0d4af`; `many` differs only in reuse `resize` and `size` across `0e0d4af`; `many` differs only in reuse
attempts. attempts.
@@ -632,22 +668,15 @@ The replay used for the reference check:
In order, from the review above and Bryan's steer (2026-09-17): In order, from the review above and Bryan's steer (2026-09-17):
1. **Seeds 1121 and 1839 at depth 4**, which fail on this head and on 1. **A fast test for `0e0d4af`**, the stale-answer guard, which went in
`ea6dbae` alike -- a warm/cold divergence older than either of the
day's commits and the only known correctness defect on the branch.
Reduce each with `SHRINK_SEED`/`SHRINK_DEPTH=4`/`SHRINK_CASE`, turn it
into a fast test, and add the seed to `generated.rs`'s `SEEDS` the way
86 and 20 are there. Then re-scan 2000 seeds at depth 4 before calling
it done.
2. **A fast test for `0e0d4af`**, the stale-answer guard, which went in
with only a fuzz seed behind it. The shape wanted is a widget drawing with only a fuzz seed behind it. The shape wanted is a widget drawing
while a size dependency two levels under it is dirty, where its while a size dependency two levels under it is dirty, where its
drawing is reusable at the measuring box and not at the placing one. drawing is reusable at the measuring box and not at the placing one.
3. Write `ActiveData::answer` in one place. 2. Write `ActiveData::answer` in one place.
4. Keep the `DrawInfo` on `ActiveData`; delete the copied fields and the 3. Keep the `DrawInfo` on `ActiveData`; delete the copied fields and the
reconstruction in `redraw`. reconstruction in `redraw`.
5. `Span`'s leftover boundary through `Holds::through`. 4. `Span`'s leftover boundary through `Holds::through`.
6. **Round on the CPU and snap to the nearest pixel in the shader**, as 5. **Round on the CPU and snap to the nearest pixel in the shader**, as
one change with one verification. Bryan approved the snap on 2026-09-17 one change with one verification. Bryan approved the snap on 2026-09-17
(rendering may change wherever it brings the screen closer to what the (rendering may change wherever it brings the screen closer to what the
user's code says: three equal sections of 1000 px need one of them user's code says: three equal sections of 1000 px need one of them
@@ -663,20 +692,20 @@ In order, from the review above and Bryan's steer (2026-09-17):
bounds move by half a `Rel` step); check with `nm` that `UiSpan::within` bounds move by half a `Rel` step); check with `nm` that `UiSpan::within`
still inlines; expect a couple of percent of instructions and re-run the still inlines; expect a couple of percent of instructions and re-run the
long fuzzers and the render set once for both. long fuzzers and the render set once for both.
7. The smaller items: the stale `f32` comment, the gap of an undrawn child, 6. The smaller items: the stale `f32` comment, the gap of an undrawn child,
confirm nested `leftover` weights, one zero-divisor fallback. Add to confirm nested `leftover` weights, one zero-divisor fallback. Add to
them: a span that overflows itself hands a child a box of negative them: a span that overflows itself hands a child a box of negative
length, which is ordinary now rather than a corner, and nothing states length, which is ordinary now rather than a corner, and nothing states
what a widget may assume about one. what a widget may assume about one.
8. `LazySpan`, the next LAYOUT.md §2 item. Region nodes cover the movable 7. `LazySpan`, the next LAYOUT.md §2 item. Region nodes cover the movable
subtree case; do not restore a separate child-placement API. subtree case; do not restore a separate child-placement API.
9. `SizeRule::{Min, Max, Clamp}`, restoring the `max_width`/`max_height` 8. `SizeRule::{Min, Max, Clamp}`, restoring the `max_width`/`max_height`
builders `8220a78` deleted. The clamp boundary is a hard layout decision builders `8220a78` deleted. The clamp boundary is a hard layout decision
with an exact `Holds` split at the crossover, both sides in `Px`. Still with an exact `Holds` split at the crossover, both sides in `Px`. Still
awaiting Bryan: whether a `Max` narrows the box the child draws in, or awaiting Bryan: whether a `Max` narrows the box the child draws in, or
only what the parent reports for it. only what the parent reports for it.
10. `Scroll` taking a direction rather than one axis. 9. `Scroll` taking a direction rather than one axis.
11. The measure/draw split, once the above is in. It deletes the two-ask 10. The measure/draw split, once the above is in. It deletes the two-ask
protocol, which is what `0e0d4af` had to put a guard around. protocol, which is what `0e0d4af` had to put a guard around.
`docs/LAYOUT.md` §4, §5 and the density section are stale: they name `docs/LAYOUT.md` §4, §5 and the density section are stale: they name