From 0cdda1713f982b295a9d809da220692680aa8701 Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Thu, 17 Sep 2026 05:13:30 -0400 Subject: [PATCH] Record the span boundary landing and what the guard experiment showed --- docs/HANDOFF.md | 60 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index c9023b3..c729746 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -10,7 +10,7 @@ stayed. Canonical Iris `main` is **`ca2b4b2`** (#17, the headless rig). **#18 `split/18-position-chain`** is open in `/home/bob/repos/iris-pr18`, head -**`a92c6ac`**, pushed. It holds LAYOUT.md §2's position chain, `leftover`, +**`25e456e`**, pushed. It holds LAYOUT.md §2's position chain, `leftover`, 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 a report read as a fraction of the containing widget. No PR review was @@ -207,15 +207,45 @@ loop { ``` Bryan's, 2026-09-17, and the right answer where `0e0d4af` was a check: -"then that entire category of issue can't even occur". `dirty_size_under` -stays in `draw_inner` anyway, because `update` draws the root for a resize -*before* `redraw_updates` runs at all and the ordering does not cover that -entry. Whether it is still load-bearing is untested -- the experiment that -said it was not had a failed edit in it and measured nothing. +"then that entire category of issue can't even occur". + +**No fuzzer can tell whether `0e0d4af`'s guard still does anything.** +Dropping `dirty_size_under` from it passes the suite, the shrinker at 400 +seeds of depth 5, the oracle at 1000 of depth 6 and 2000 seeds at depth 4. +It stays because `update` draws the root for a resize *before* +`redraw_updates` runs at all, which the ordering does not reach -- a hole +that is reasoned rather than measured, so either find the case or delete +the guard, but do not leave it on a hunch forever. Drawn widgets, widget draws and primitive writes are unchanged on every rig phase; `many` pays 51 queue pops for 27 and 1059 depth reads for 410. +### A span's leftover boundary is its own inverse (landed, `53b00c6`) + +The decision used a rounded division where the room the children get is a +floored multiply, so the boundary and the drawing it guarded were two +expressions for one length. `room` is that length as a `Len`, `room.to_px` +is the multiply, and `Holds::through` is its exact preimage: + +```rust +let room = Len::rel_max() - Len::from_parts(total.rel, total.px); +let mut shares = false; +if total.leftover > Weight::ZERO { + shares = room.to_px(painter.px_len(axis)) > Px::ZERO; + let holds = match shares { + true => Holds::from(Px::STEP..=Px::MAX), + false => Holds::from(Px::MIN..=Px::ZERO), + }; + painter.holds(axis, holds.through(room)); +} +``` + +The three branches were the sign of `1 - rel`, which `through` reads +already. Forty lines became twelve and one `div` left layout. The general +rule stands and is now demonstrated: **derive a boundary through the +inverse of the expression that draws, never by a second expression for the +same length.** + ### Two branches parked, both real, neither ready **`wip/stack-fraction-twice`.** A stack sized by a child that reports a @@ -561,7 +591,7 @@ above for what closing the rest would cost. ## Verification at the current head -At `a92c6ac`: +At `25e456e`: - `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace`: green, 90 suite tests, 19 core @@ -571,7 +601,8 @@ At `a92c6ac`: 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, the oracle at 1000 seeds of depth 6 in 142 s, and **2000 seeds at depth 4 over all - fifteen cases** in 261 s. + fifteen cases** in 262 s. The last is not routine and should be: it is + the only run that has ever found anything past seed 400. - `view`, `minimal`, `random` and `tabs` byte-identical at 1920x1200 against `ea6dbae`. `random` live-resized from 1920x1200 to 1280x800 is byte-identical to a cold 1280x800 render. **`text` is a new picture**: @@ -769,8 +800,7 @@ In order, from the review above and Bryan's steer (2026-09-17): 4. Write `ActiveData::answer` in one place. 5. Keep the `DrawInfo` on `ActiveData`; delete the copied fields and the reconstruction in `redraw`. -6. `Span`'s leftover boundary through `Holds::through`. -7. **Round on the CPU and snap to the nearest pixel in the shader**, as +6. **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 (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 @@ -786,20 +816,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` still inlines; expect a couple of percent of instructions and re-run the long fuzzers and the render set once for both. -8. The smaller items: the stale `f32` comment, the gap of an undrawn child, +7. The smaller items: the stale `f32` comment, the gap of an undrawn child, confirm nested `leftover` weights, one zero-divisor fallback. Add to 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 what a widget may assume about one. -9. `LazySpan`, the next LAYOUT.md §2 item. Region nodes cover the movable +8. `LazySpan`, the next LAYOUT.md §2 item. Region nodes cover the movable subtree case; do not restore a separate child-placement API. -10. `SizeRule::{Min, Max, Clamp}`, restoring the `max_width`/`max_height` +9. `SizeRule::{Min, Max, Clamp}`, restoring the `max_width`/`max_height` builders `8220a78` deleted. The clamp boundary is a hard layout decision 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 only what the parent reports for it. -11. `Scroll` taking a direction rather than one axis. -12. The measure/draw split, once the above is in. It deletes the two-ask +10. `Scroll` taking a direction rather than one axis. +11. 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. `docs/LAYOUT.md` §4, §5 and the density section are stale: they name