Record the step 5 result: hints skip the room ask, and the far pin is not the cost

Measured at a888717 and at the new f6242aa on wip/hint-first, with the
inset experiment recorded as tested and rejected. The plan is now review,
render and land.
This commit is contained in:
iris-ai committed 2026-09-19 02:24:09 -04:00
1 parent 1ef31bc9e7
commit e4fada298d
2 files changed
+242 -124

No files matched your search

+72
View File
@@ -7,6 +7,78 @@ must outlive it (settled design, the measurement method) is already in
`docs/LAYOUT.md`, and the current plan is in `docs/HANDOFF.md`. Commit ids
are in `/home/bob/repos/iris-layout-experiment` unless said otherwise.
## What the step 5 measurements found (planner, 2026-09-19)
Re-measured `a888717` first: every number in the handoff's table reproduced
exactly (342/288, 118/95, 3/3, 1, 44/13 at seed 1; 1278/982, 429/366 at
seed 13), so the table was trusted. What the counters said beyond it:
- **Most redraws never reached the reuse check.** `many` at seed 1 made 118
draws and only 78 reuse attempts, 4 of them rejected; the other draws
came from `retained_answer` failing before `try_reuse` ran. Mechanism: a
share child is asked in the room and then in its slot, one record holds
one answer, and each ask overwrites it -- so the room ask finds the slot
answer (pinned to the slot's length) and the slot ask finds the room
answer, and each draws. Every ancestor redraw pays it twice per share
child, multiplied down nested shares.
- **#18 avoided the room ask where a hint or rule gave the length.** Its
`Span` called `painter.known_len` first; `3091fb8` dropped that with the
offer machinery. Restored at `f6242aa` as `Painter::size_hint`, which now
resolves a fraction against the asking widget's frame (it returned the raw
rule before, which no caller had noticed because its one caller read
`px`) and pins that frame where it did. `Scroll`, `Masked`, a `Stack`
without a sizing child and the fixture's `Branch` gained `LEFTOVER`
hints. A hinted child is asked once, in its slot; a hinted share with no
room is never asked and is undrawn. Cost (draws / distinct, depth 8):
seed 1 cold 264/232, many 41/41, size 3/3, scroll 1, resize 36/13; seed
13 cold 758/627, many 16/16, resize nothing. `many` is at its floor:
every draw is a marked widget or the parent one deferred to. Cold layout
of 400 trees at depth 5 is byte-identical to `a888717` (the new
`tests/layout_dump.rs`), and the three long fuzzers agree.
- **What is left is reported shares.** With hints in, the hottest widget
in seed 1's resize frame is a `Span` drawn 8 times, two more drawn 4 and a
`Branch` drawn 4: a span whose children report `leftover` has no hint, is
asked in the room and in its slot, and both draw because the room drawing
divided the room (`far` read, pinned to that length). Under the root's
resize redraw that is 2 per level of nesting. It is also the whole of
cold's draws over its distinct count.
The design that removes it, not implemented: keep two answers on the
record, the room answer from a measuring ask and the slot answer from the
placing ask, with the drawing belonging to the placing ask. A measuring
ask (`Painter::measure`, a new call the parent makes explicitly, so
"which ask is the measurement" is stated rather than inferred) reuses the
room answer when its holds contain the room and touches no drawing;
otherwise it draws in the room and records the answer as the room's. The
placing ask is `widget_at` as now. A local redraw of a twice-asked child
re-asks both questions, marks the parent if either answer moved, and puts
the drawing back otherwise -- which also retires `re_asked`'s deferral.
The cost is a second `Option<(Size, LayoutHolds)>` on `ActiveData` and a
measuring path through `draw_inner` that skips `try_reuse` and `relocate`.
This is the one-record-two-questions bookkeeping the earlier plans died
of, made explicit at the call site instead of recovered from the ask; it
is worth doing only once an app screen shows reported shares nested
under a resize, and that screen should be measured first.
- **Dropping the span's `far` pin does nothing** (`wip/inset` at `5b181bc`,
pushed, one commit over `f6242aa`): `Part::Of` replaced by
`Part::Inset { lead, trail }` in window lengths, an exact `in_parent` arm
for a pixel inset (the range moved by the pixels, `Holds::longer_by`) and
a pinned own length plus a window range for one with a fraction, `Pad`
speaking it, and `Span` reading `extent_len` only with shares or in the
negative direction. Suite, oracle and clippy green; counters identical to
`f6242aa` at every phase of both seeds. Why: a symbolic pin survives a
resize because every ask box is symbolically stable across one -- the
viewport a scroll asks its content in is `rel 1`, a slot is a sum of
pixel answers, a stack's sized part is an answer -- so the pin only fails
where an answer changed, which is a real relayout. The worker's "44 to
20" at `a888717` was a deliberately unsound read, and what it saved was
share spans reusing the drawing that divided the room in the slot that
is narrower: wrong layout, not spared work. Not to be repeated; the
branch is evidence.
- **The frame pin on a rule that is a fraction costs nothing** to speak of:
`reuse outside: a frame length` is 3 of 264 cold draws at seed 1 and 1 of
758 at seed 13. Dropped from the plan.
## What making the frame a length found (worker, 2026-09-19)
Step 2 of the handoff, implemented over `0ef87eb` in