From 1ef31bc9e700f6a65d18723dd1def7d690c0ac90 Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Sat, 19 Sep 2026 01:29:16 -0400 Subject: [PATCH] Measure step 5's span pin and say what blocks the sound version Re-measured the work counters at a888717 (unchanged from adbedaf), and tested the handoff's claim about a positive span pinning `far`: without the pin, seed 1's resize goes from 44 draws of 13 widgets to 20 of 9. The pin is worth removing, and what stops a sound removal is that no `Part` can say "from the cursor to the end of the box" without naming the box's length. --- docs/HANDOFF.md | 51 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 075fcbc..0be5167 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -254,7 +254,7 @@ Unchanged; see `docs/LAYOUT.md`. ## Implementation plan Work in `/home/bob/repos/iris-layout-experiment` on `wip/one-ask` from -`a30971e`. Make each step a warning-clean commit and run its named checks +`a888717`. Make each step a warning-clean commit and run its named checks before the next. If a step exposes a different mechanism, stop and update this handoff rather than papering over it. @@ -302,9 +302,11 @@ is worth a name. ### 5. Restore the expected retained cost Work counters, seed 1 and 13, depth 8, widget draws / distinct widgets, -beside `e44dea3` (#18) and `0ef87eb` (before the frame became a length): +beside `e44dea3` (#18) and `0ef87eb` (before the frame became a length). The +`a888717` column was re-measured after the step 1 review and is identical to +`adbedaf` at every phase, so the scroll fix cost nothing: -| seed 1 | e44dea3 | 0ef87eb | adbedaf | +| seed 1 | e44dea3 | 0ef87eb | a888717 | | --- | --- | --- | --- | | cold | 369/261 | 331/288 | 342/288 | | many | 157/95 | 110/92 | 118/95 | @@ -312,12 +314,21 @@ beside `e44dea3` (#18) and `0ef87eb` (before the frame became a length): | scroll | 2 | 1 | 1 | | resize | 13/13 | 40/15 | 44/13 | -| seed 13 | e44dea3 | 0ef87eb | adbedaf | +| seed 13 | e44dea3 | 0ef87eb | a888717 | | --- | --- | --- | --- | | cold | 1330/707 | 1179/982 | 1278/982 | | many | 524/159 | 424/364 | 429/366 | | resize | nothing | nothing | nothing | +The command is + +```sh +IRIS_SEED=1 IRIS_DEPTH=8 cargo test --release --features layout-diagnostics \ + --test layout_diagnostics -- --ignored --nocapture layout_cost +``` + +reading `widget draws` and `distinct widgets` from each phase's block. + `many`, `size` and `scroll` are better than #18 and within a few draws of `0ef87eb`; `cold` is 3% and 8% more than `0ef87eb` for the same distinct widgets, which is the frame pins making a widget answer again where it used @@ -348,7 +359,37 @@ Three mechanisms behind what is left, all understood: - **A positive-direction span with no shares pins `far`** it does not need, so a resize redraws it. Read `extent_len` only where a slot depends on it (shares, or `Sign::Neg`), and express the measuring room's far end without - the length. Measure `resize` at seed 1 before and after. + the length. + + **Measured at `a888717` (worker, 2026-09-19)**, by replacing `far` with a + deliberately unsound read that returns the length without pinning: seed 1's + `resize` goes from **44 draws of 13 distinct widgets to 20 of 9**, and the + median frame from 0.063 ms to 0.022 ms. So the pin is worth the step, and + it is the pin rather than the twice-asked multiplication -- the hottest + widgets are drawn 8, 8, 4, 4, 4, 4, 2, 2 times, and only 3 of 98 reuse + attempts are rejected by a frame length. A symbolic pin fails on a resize + because a symbolic length is not window-independent: a scroll's content box + is `rel 0, px content_len`, and `content_len` is measured against the + window. + + **What blocks the sound version.** The measuring room is + `Part::From(UiSpan::new(cursor, far))`, and no existing `Part` says "from + the cursor to the end of the box" without naming the box's length. A new + variant would -- an inset of `lead` and `trail` *window* lengths, which + also subsumes `Part::Of`'s pure-pixel use in `Pad` -- but `in_parent` needs + an arm for it, and the mapping is not a plain `Holds::through`: the inset + is a window length, so the child's box length changes with the window and + the offset cannot be written as a `Len` of the parent's box. + + There is a second, deeper question under it. Dropping the pin is only + sound if nothing else has to notice the span's box changing, and today + `in_parent`'s `_` arm converts a child's box range into a range on the + *window* through the child's box length -- which assumes that length is a + fixed expression of the window, which is exactly what `far` changing + violates. Putting that dependency on the parent's own box instead would + let the span stop reading `far` for soundness and read it only where a + slot depends on it, but it will also invalidate more elsewhere. Decide + that before writing the `Part` variant. - **A rule that is a fraction of the frame pins the frame**, which is what `cold` grew by. The answer for such an axis is a pure function of the rule and the frame, so a reuse could resolve it again from the record instead