Record the two reuse defects the random trees found, their fixes, and the one-pixel tabs difference left for Iris to call

This commit is contained in:
iris-ai committed 2026-09-14 13:31:46 -04:00
1 parent 19b9ca7413
commit e442481a30
1 file changed
+43 -25
+43 -25
View File
@@ -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