diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 5d21de1..d4b59b7 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -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 **`cb955f1`**, fifty-five commits. Built-in alignment is complete there; +head is **`bdab558`**, fifty-seven 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. @@ -72,13 +72,14 @@ rewrite: `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 `cb955f1` +## Verification at `bdab558` - `cargo fmt --all --check` - `cargo build --workspace --all-features` - `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- `cargo test --workspace --all-features`: 102 passed, 10 ignored -- The release generated cold-layout oracle passed 100 seeds in 75 s. +- `cargo test --workspace --all-features`: 103 passed, 10 ignored +- The release generated cold-layout oracle passed 100 seeds in 9.4 s, and a + shrinker case at 300 seeds in 3.5 s: both run a thread per core but one. - The release shrinker passed **all five** cases -- `resize`, `repaint`, `resize-repaint`, `reorder`, `size-change` -- at 300 seeds of depth 5: 26,001 widgets per case, largest tree 331. It passed 1000 seeds of depth 6 @@ -137,6 +138,13 @@ tighter figures. ## Retained-layout invariants +- A span is as long across itself as its longest child, unless a rule beside + it says how long it is -- and then it does not read its children there at + all, since the answer is not wanted and reading one is what makes its size + depend on it. `OrthoSize` was that second case written twice and is gone + (`9d8415d`); `Painter::ruled` is how a container asks which it is in, and + the only thing a widget may learn about a rule over it. + - A region node holds a whole `UiRegion` in its parent node's coordinates. `UiRegion::FULL` is the identity. Widgets opt in with `.region_node()` or `Widgets::set_region_node`; ordinary widgets share the nearest ancestor @@ -315,12 +323,24 @@ pixels and `Holds`. Decided with Bryan on 2026-09-15. rounding, so it scales with the step: at `1/64` that was 0.016 px, enough to move a box, and at `1/1024` it is a thousandth of a pixel. Range is +/-2.1M px and conversion to `f32` is exact to 16,384 px. -- **What the fuzzers ask for is a step per operation.** The shrinker's five - cases agree within one (`resize` exactly), the oracle's two-operation cases - within two. Closing that needs one way of asking how long a box is: today a - chain composed down through `within` and a length measured against the - window are two, and they round separately. That is the next real step in - precision and it is bigger than any of these four commits. +- **What the fuzzers ask for is a step per level of nesting**, which is two + for these trees. Traced on 2026-09-16 to the same box reached two ways, + each rounding where the other does not -- not accumulation, and not one + place. Two of them are fixed in `bdab558`: + - `Scroll` wrote a box it had been given back out as its own length in + pixels. Centring a part in `rel 1` lands a step from centring it in + `px 900`, because `a(x - y)` and `ax - ay` do not round alike. Content + that fills the viewport unscrolled is handed back as it came, and the + `repaint` and `resize-repaint` cases became exact. + - `Span` placed each child a step from where the last ended, carrying every + share's rounding along the row. A position is now the fixed parts before + it, exact, plus one rounded share. Two hundred equal shares of a 1000 px + row ended at 999.999 and now end at 1000. + What is left is a box centred in a fraction of its parent against the same + box centred in its own pixels, one step per level between them. Closing it + means alignment resolved in pixels everywhere -- which costs the retained + resize path, since it is the fractional form that re-centres a subtree + without redrawing it. Not worth it at a thousandth of a pixel. - `Holds::through` inverts `px + rel * box`, which rounds, so the answer is an interval even for a single length. It maps the half step either side, plus one more for the two ways above; inverting the length alone gives a @@ -376,6 +396,11 @@ targets went from 14.3 s to 7.7 s and `target/` from 45 GB to 13 GB. Pick a module out with `cargo test --test suite layout::`. The fuzzers and the `*_cost` measurements are still their own targets. +The fuzzers take a thread per core but one (`9d8415d`), since a seed grows, +lays out and drops its tree alone: the oracle's hundred seeds went from 68 s +to 9.4 s and a shrinker case at 300 seeds from 18 s to 3.5 s. A failing seed +still shrinks and panics on its own thread. + Run the long generated oracle only after ordinary tests pass: ```sh @@ -433,15 +458,6 @@ the same `Holds` contract and box chain. Queued from this work, in order: -- 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 - today. That also removes the span's own instance of the compounding above. - **Paused 2026-09-16 for a decision**: a widget deliberately cannot see its - own rule ("the widget under a rule never learns of it"), so either that - changes or the span reports something else on the orthogonal axis -- which - is what every span reports, and a column sized to its widest item is what - `Children` is for. - `Scroll` should take a direction rather than one axis: vertical, horizontal, or both. Reporting `LEFTOVER` on both axes is already the right shape for it. - Restore `max_width`/`max_height` as `SizeRule::{Min, Max, Clamp}`. `8220a78`