Record the declared-length fix, and park the rest-with-no-leftover rule

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-15 13:52:04 -04:00
1 parent 324f0f02ef
commit a128648bd2
1 file changed
+44 -2
+44 -2
View File
@@ -26,8 +26,32 @@ reference renders and the resize render are byte-identical across all of
it, and the hundred-seed sweep passes. `/home/bob/repos/ai-app-2` is on it, and the hundred-seed sweep passes. `/home/bob/repos/ai-app-2` is on
`rustify`, worktree clean. `rustify`, worktree clean.
**A declared `rel` length is applied twice, and it is the one thing to fix **Fixed, 2026-09-15, by `de9ddc0`: a declared length is resolved where the
before #18 lands.** `.width(rel(0.5))` in a 400-wide span puts the `SetSize` at widget is drawn.** `Painter` takes a child's `size_hint` in its own box before
drawing it there -- which is what a fraction of a length means, and is the
identity for a caller that already reserved the space -- and `SetSize::draw`
keeps none of it. `rest` is deliberately not resolved there: a part of what is
left over is only a length to the widget dividing one, so it passes up in the
size, the way it already passes up out of a span. A declared length is then
part of the box its parent decided, so a change to one redraws the parent; the
lengths resolved into a box are kept beside it in `ActiveData::declared` and
compared. Assuming instead that any dirty widget which declares a length needs
its parent costs 17% of a frame that dirties 130 of 260 widgets and buys
nothing. All five reference renders, the resize render and the image replay are
byte-identical to `upstream/main`, and the 100-seed sweep passes.
`9644971` is the other half of the cost. Whether the inliner took
`DrawLayers::write` into `Painter::glyphs` turned out to depend on unrelated
code in `iris-core`: the resolution above pushed it out, and a call per glyph
cost 12% of a resize frame with every counter -- widget draws, primitive
writes, text renders -- unchanged. `#[inline]` says it directly. The
random-tree rig is 12.59B instructions per 500 resize frames where it was
13.25B before either change, and the `revision_cost` fixture is 1.286 ms
against 1.289.
The defect it fixed, for the record:
**A declared `rel` length used to be applied twice.** `.width(rel(0.5))` in a 400-wide span puts the `SetSize` at
0..200 and its child at 0..100: the span sizes the box from the hint, and 0..200 and its child at 0..100: the span sizes the box from the hint, and
`c596bf1` then measures the child in the declared length again *inside* that `c596bf1` then measures the child in the declared length again *inside* that
box. `px` is unaffected, since 200 px of a box already 200 px wide is the whole box. `px` is unaffected, since 200 px of a box already 200 px wide is the whole
@@ -45,6 +69,24 @@ its box is its own declared length or the whole of what its parent had.
Minimal repro: `(inner.width(rel(0.5)), rect()).span(Dir::RIGHT)` at 400 wide, Minimal repro: `(inner.width(rel(0.5)), rect()).span(Dir::RIGHT)` at 400 wide,
asking where `inner` landed. asking where `inner` landed.
**Parked, and wanted: a `rest` with nothing left over should not draw.** The
owner asked for it on 2026-09-15 -- "if it's 0 wide for rest then it shouldn't
draw in the first place", with `rel` and `px` continuing to overflow forward as
they do now, and only a length that is *nothing but* `rest` skipped. Written
and reverted: `Span` needs `painter.px_len(axis)` to know whether anything is
left over, since `rel(0.5)` beside 300 px is full at 600 and overfull at 400
and the difference is not expressible in fractions, and that makes a span's
drawing depend on its own box. Inside a `Scroll`, whose box comes from the
content, seed 13 of `adding_and_removing_span_children` then lands warm and
cold on different answers: the same span holds the same region in both, and
`ActiveData::px` says it drew against 63 px warm and 900 cold, so the leftover
question is answered in two different boxes. `Painter::undraw` -- drop a child
drawn only to measure it, so it is not one of this widget's children this
frame -- is the other half and worked. `Branch` in `iris::random` already makes
a pixel-dependent choice about which child to draw and the fuzzer is happy with
it, so the shape is supported; what is not settled is which box a span should
ask in.
**The `tabs` divergence does not reproduce, so nothing waits on the owner.** **The `tabs` divergence does not reproduce, so nothing waits on the owner.**
Re-measured 2026-09-15 at `f61e893` against `upstream/main` built in its own Re-measured 2026-09-15 at `f61e893` against `upstream/main` built in its own
worktree: `tabs` at 1920x1200 is byte-identical, and so are a cold 900x1200 and worktree: `tabs` at 1920x1200 is byte-identical, and so are a cold 900x1200 and