diff --git a/docs/IRIS_EXTRACTION_HANDOFF.md b/docs/IRIS_EXTRACTION_HANDOFF.md index f00ba3d..7e868ff 100644 --- a/docs/IRIS_EXTRACTION_HANDOFF.md +++ b/docs/IRIS_EXTRACTION_HANDOFF.md @@ -9,7 +9,7 @@ Canonical `main` is **`ca2b4b2`** (#17, the headless rig). Sixteen slices are in; #16's size work and #17's rig both merged on 2026-09-14. **#18 `split/18-position-chain`** is open, worktree `/home/bob/repos/iris-pr18`, -head `86a7e8d`, eight commits. LAYOUT.md §2's O(1) subtree movement, the +head `2272634`, ten commits. LAYOUT.md §2's O(1) subtree movement, the `Remap` retirement, and then the three changes the owner chose on 2026-09-14: the slot carries a box, slots are opt-in, and a widget's region is held in the coordinates of the slot it draws in. @@ -46,28 +46,50 @@ coordinates of the slot it draws in. 2026-09-14: stretch has an opposite and scale does not, and the answer is per axis so the axis is already established where it is read. -**The open defect, and it is older than this branch.** `tests/generated.rs` -grows a seeded random tree and checks that laying it out again lands where -growing it cold does. It does not always. Every case seen is a **wrapping text -on a span's own axis**: the span measures the child in the whole box, the -child shapes to that width and reports the width it used, the span places it -in exactly that width, which is a length change, so it shapes again and its -longest line is shorter than the box it was just given. Where it settles -depends on how many passes it has had, and a warm tree has had a different -number from a cold one -- so layout is not a function of the state, which it -has to be. A span whose axis is not the wrap axis is stable, which is every -real column of text, and why nothing had run into it. +**The divergence the random trees found, and what it was.** A warm tree did +not always land where a cold build does -- 30 of 90 cases, against 7 on +`db1751f`. Two causes, both fixed in `b0f9f04`, and both about deciding +whether a drawing is still valid: - seeds 1-30, three checks each db1751f (before) 86a7e8d (now) - diverging 7 of 90 30 of 90 +- **A region is a fraction of a slot's box, so an unchanged region is not an + unchanged box.** `try_reuse` compared regions, and a child drawn at + `UiRegion::FULL` of a slot whose box had just halved compared equal to + itself and was reused without being descended into, leaving a wrapping text + shaped for a width it no longer had. `ActiveData::px` now holds the pixel + size of the box the widget drew against and the comparison is against that + -- the question that was being asked all along, and right through a slot + change and an output resize alike. +- **A size the parent learnt by drawing the child is an answer for that box + only.** The walk skipped a child whose own box was a fixed width, correctly + -- but that width was what the child reported when the span drew it in the + span's box, and the span's box had changed. A child whose size the widget + read is now redrawn unless it declares an exact `size_hint` for the changed + axis, which is the one case the parent did not draw it to find out. -The same defect either way: a placed child reaches the second shaping more -often now. It wants fixing where the two draws meet, LAYOUT.md §4, rather than -anywhere in the chain -- and **it should be fixed before #18 merges**, since -tripling how often a tree lands somewhere unrepeatable is not something to -land even on a defect that was already there. -`a_wrapping_child_of_a_row_settles_somewhere_else_each_time` is the ignored -reproduction; the six seeds the live tests use are ones that agree. +90 of 90 now agree, and the ignored sweep agrees over 300 checks on 100 +seeds. The cost of the second one: a size-reading container gives up its reuse +when its box changes length, which is every span, so `OnResize::Scale` earns +its keep on moves and on subtrees whose sizes nobody read rather than on every +stretch. `replace_cost` still measures the case the chain was built for. + +**Render verification, done last as the owner asked.** `view`, `minimal` and +`text` are byte-identical to `upstream/main` at 1920x1200. **`tabs` is not**: +1,283 pixels of 2.3M (0.06%), two one-pixel-wide panel edges shifted by a +pixel, at x=1056 and x=1337. Composing a position through the chain on the GPU +associates the arithmetic differently from collapsing it on the CPU, so a +value that used to land exactly on an integer now falls the other side of the +shader's `floor`. The CPU and the GPU still agree with each other -- both walk +the chain bottom-up -- so hit testing matches what is drawn; what changed is +only the comparison against the old code. Matching it exactly would mean +composing root-down in the shader, which needs the chain collected into an +array first. **Worth the owner's call before merging**, since byte-identical +against `upstream/main` has been the bar for every slice so far. + +A live resize does land where a cold start does, byte for byte, on `tabs` and +`text` -- the check that caught both of #16's defects. `run-headless.sh +--resize WxH@Hz` is that check now. Run one at a time: the rig reuses a single +compositor and a single output, so two at once resize each other's window and +screenshot the wrong thing. **Rigs added.** `iris::random` grows the tree (spans in every direction, stacks, rects with varying opacity, text wrapping and overflowing, a declared @@ -76,10 +98,6 @@ size over half of it, stopping at a depth), `examples/random.rs` draws one -- against cold under a size change, a resize, and both. It found the non-terminating marking above immediately. -**Not verified yet:** the headless render shots and the live resize under -sway. The owner asked for those last, after the CPU side is right, and the CPU -side is not right until the divergence is. - ### The plan after #18, agreed with the owner on 2026-09-14 Three changes, done together as one slice on top of #18, because each decides