Record the renders and the counters for the frame-as-length step

Step 3 is done: against #18 the render set is byte-identical but for two
pixels of antialiasing in random and one padded block in text moving a
pixel, and a live resize of random matches a cold render at that size
exactly. The cost table is re-measured at adbedaf, where the fuzzer's own
Branch was found pinning the window and hiding what the change costs.
This commit is contained in:
iris-ai committed 2026-09-19 00:35:42 -04:00
1 parent 36ce66554b
commit 150245071f
2 files changed
+83 -35

No files matched your search

+54 -34
View File
@@ -14,12 +14,12 @@ detached comparison checkout is `/home/bob/repos/iris-layout-baseline`. It is
the reviewed baseline this work must preserve or improve. the reviewed baseline this work must preserve or improve.
The continuation is `/home/bob/repos/iris-layout-experiment`, now on branch The continuation is `/home/bob/repos/iris-layout-experiment`, now on branch
**`wip/one-ask`** at **`23523ee`**, four commits over `4328eac` (the head of **`wip/one-ask`** at **`adbedaf`**, five commits over `4328eac` (the head of
`wip/transparent-frames`, which is unchanged). It replaces the old step 3 `wip/transparent-frames`, which is unchanged). It replaces the old step 3
plan with the one-ask protocol below, and `1512d84` and `23523ee` make the plan with the one-ask protocol below, and `1512d84` and `23523ee` make the
frame a length of the window. It passes every check: frame a length of the window. It passes every check:
| check at `23523ee` | result | | check at `adbedaf` | result |
| --- | --- | | --- | --- |
| `cargo fmt --all --check`, clippy `-D warnings`, with and without `layout-diagnostics` | clean | | `cargo fmt --all --check`, clippy `-D warnings`, with and without `layout-diagnostics` | clean |
| `cargo test --workspace` (debug) | 122 suite, 20 core, 11 generated, all green | | `cargo test --workspace` (debug) | 122 suite, 20 core, 11 generated, all green |
@@ -35,8 +35,14 @@ over two pads relocating the column under them instead of dividing it again,
and a resize leaving a short scroll's window-tall content where it was. Each and a resize leaving a short scroll's window-tall content where it was. Each
is pinned as a named test. is pinned as a named test.
Not done: the renders and the `tabs` replay, the retained-cost work, the The renders and the replay are done and recorded in `docs/LAYOUT_LOG.md`:
renames, and a pre-submit review of the three commits as one diff. against #18, `minimal` and `tabs` are byte-identical (before and after the
reference gesture), `random` differs in two pixels of glyph antialiasing,
`text` moves one padded block one pixel, and a live resize of `random`
matches a cold render at that size byte for byte.
Not done: the retained-cost work, the renames, and a pre-submit review of
the five commits as one diff.
The worker's older step 3/4 experiment is preserved as branch The worker's older step 3/4 experiment is preserved as branch
`wip/step3-experiment` (one commit over `4328eac`) and as `wip/step3-experiment` (one commit over `4328eac`) and as
@@ -244,7 +250,7 @@ Unchanged; see `docs/LAYOUT.md`.
## Implementation plan ## Implementation plan
Work in `/home/bob/repos/iris-layout-experiment` on `wip/one-ask` from Work in `/home/bob/repos/iris-layout-experiment` on `wip/one-ask` from
`23523ee`. Make each step a warning-clean commit and run its named checks `adbedaf`. 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 before the next. If a step exposes a different mechanism, stop and update
this handoff rather than papering over it. this handoff rather than papering over it.
@@ -254,30 +260,29 @@ frame, ask, box and size warm against cold), pinned as a named test in
`tests/cases/unsettled.rs`, then fixed under the two rules above. Do not add `tests/cases/unsettled.rs`, then fixed under the two rules above. Do not add
a second draw back. a second draw back.
### 1. Review the four commits ### 1. Review the five commits
Run the pre-submit review over `3091fb8`, `0ef87eb`, `1512d84` and `23523ee` Run the pre-submit review over the five commits as one diff against
as one diff against `4328eac`. `1512d84` rewrote much of what the first two did to `4328eac`. `1512d84` rewrote much of what the first two did to `painter.rs`
`painter.rs` and `render_state.rs` and was reviewed as it was written, but and `render_state.rs` and was reviewed as it was written, but the five have
the three have never been read as one change, and the parts of the one-ask never been read as one change, and the parts of the one-ask protocol it did
protocol it did not touch -- `place_at`, the twice-asked deferral, `Span`'s not touch -- `place_at`, the twice-asked deferral, `Span`'s two passes --
two passes -- were written as a probe and reviewed only by their tests. were written as a probe and reviewed only by their tests.
### 2. Make the frame a length and the box a region ### 2. Make the frame a length and the box a region
**Done at `1512d84` and `23523ee`.** The settled rule is in `docs/LAYOUT.md` under **Done at `1512d84` and `23523ee`.** The settled rule is in `docs/LAYOUT.md`
"Frames, decided boxes and padding"; what implementing it corrected in the under "Frames, decided boxes and padding"; what implementing it corrected in
plan -- three of them wrong layout that would have shipped -- is in the plan -- four of them wrong layout that would have shipped -- is in
`docs/LAYOUT_LOG.md`. `docs/LAYOUT_LOG.md`.
### 3. Render and replay ### 3. Render and replay
Read the installed graphics skill and confirm the renderer. Render `view`, **Done at `adbedaf`**, against `e44dea3` (#18); what each render showed is
`minimal`, `random`, `tabs` and `text` at 1920x1200 against `34cafb6` and in `docs/LAYOUT_LOG.md`. `view` has no counterpart in the baseline, so it
`e44dea3`, replay `tabs`, and compare a live resize of `random` with a cold was rendered but not compared. Run the set again after any further change
render at the same size (commands under **Full verification** below). A here -- the commands are under **Full verification** below, and a text
text placed by re-expression rather than a second draw is the change most placed by re-expression rather than by a second draw is what shows first.
likely to show here; inspect every intentional difference and record it.
### 4. Rename and delete ### 4. Rename and delete
@@ -293,26 +298,36 @@ Suite, oracle.
### 5. Restore the expected retained cost ### 5. Restore the expected retained cost
Work counters at `3091fb8`, seed 1 and 13, depth 8, widget draws / distinct Work counters, seed 1 and 13, depth 8, widget draws / distinct widgets,
widgets, beside `e44dea3` (#18) and `49cec82` (the branch head before this): beside `e44dea3` (#18) and `0ef87eb` (before the frame became a length):
| seed 1 | e44dea3 | 49cec82 | 0ef87eb | | seed 1 | e44dea3 | 0ef87eb | adbedaf |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| cold | 369/261 | 516/288 | 331/288 | | cold | 369/261 | 331/288 | 342/288 |
| many | 157/95 | 187/119 | 110/92 | | many | 157/95 | 110/92 | 118/95 |
| size | 16/12 | 3/3 | 3/3 | | size | 16/12 | 3/3 | 3/3 |
| scroll | 2 | 1 | 1 | | scroll | 2 | 1 | 1 |
| resize | 13/13 | 24/76 | 40/15 | | resize | 13/13 | 40/15 | 44/13 |
| seed 13 | e44dea3 | 49cec82 | 0ef87eb | | seed 13 | e44dea3 | 0ef87eb | adbedaf |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| cold | 1330/707 | 2940/982 | 1179/982 | | cold | 1330/707 | 1179/982 | 1278/982 |
| many | 524/159 | 1091/423 | 424/364 | | many | 524/159 | 424/364 | 429/366 |
| resize | nothing | 2215/510 | nothing | | resize | nothing | nothing | nothing |
`many` and `size` are better than #18 at seed 1 and `many` draws fewer times `many`, `size` and `scroll` are better than #18 and within a few draws of
at seed 13, but it touches twice as many distinct widgets there, and `resize` `0ef87eb`; `cold` is 3% and 8% more than `0ef87eb` for the same distinct
at seed 1 draws 40 times where #18 drew 13. Two mechanisms, both understood: widgets, which is the frame pins making a widget answer again where it used
to be reused on a box that happened to match. `resize` at seed 1 still draws
44 times where #18 drew 13.
Measuring this is what found `adbedaf`: the fuzzer's own `Branch` pinned the
window rather than saying which side of its threshold it was on, which put
seed 1's resize at 131 and seed 13's at 828. A fixture that redraws
everything on a resize cannot tell a change that reuses well from one that
does not, so check the fixture before believing a regression.
Three mechanisms behind what is left, all understood:
- **A share child is asked twice per span draw** -- in the measuring room - **A share child is asked twice per span draw** -- in the measuring room
with the frame forwarded, then in its slot with the frame narrowed. Each with the frame forwarded, then in its slot with the frame narrowed. Each
@@ -331,6 +346,11 @@ at seed 1 draws 40 times where #18 drew 13. Two mechanisms, both understood:
so a resize redraws it. Read `extent_len` only where a slot depends on it 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 (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. Measure `resize` at seed 1 before and after.
- **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
of redrawing -- `placed_extent` already takes `declared`. Worth trying
before anything subtler; the pin stays for the axes that read the frame.
Report every phase at both seeds, work counters first, medians only when the Report every phase at both seeds, work counters first, medians only when the
work agrees. work agrees.
+29 -1
View File
@@ -75,13 +75,21 @@ after the step lands.
- **The clipping debug assert compared the reported size with the frame** - **The clipping debug assert compared the reported size with the frame**
rather than with the box it is a claim about. rather than with the box it is a claim about.
Two rig changes came out of it, both kept: Three rig changes came out of it, all kept:
- The shrinker prints the *shrunk* tree's divergence rather than the grown - The shrinker prints the *shrunk* tree's divergence rather than the grown
tree's, and the divergence now lists, for the failing widget and every tree's, and the divergence now lists, for the failing widget and every
ancestor, warm and cold `frame`, `ask`, `box` and `size`. Reading that ancestor, warm and cold `frame`, `ask`, `box` and `size`. Reading that
table is what found the box-arm defect; reconstructing a 20-widget plan by table is what found the box-arm defect; reconstructing a 20-widget plan by
hand would not have. hand would not have.
- The fuzzer's `Branch` states the range it branched on rather than pinning
the window it read the measurement against (`adbedaf`). It is the only
widget in the fixture that reads a length in pixels and chooses a subtree
from it, so with a pin every one of them redrew on every resize: seed 1's
resize was 131 widget draws and seed 13's 828, against 44 and nothing once
it says the range. That is a fixture that cannot tell a change that reuses
well from one that does not, and it looked exactly like a regression in
the change being measured.
- A `size-resize` case: a size change and *then* a resize. Every other case - A `size-resize` case: a size change and *then* a resize. Every other case
changes something and compares at once or resizes first, so an answer kept changes something and compares at once or resizes first, so an answer kept
as a fraction of the wrong length agreed at the window it was made at and as a fraction of the wrong length agreed at the window it was made at and
@@ -93,6 +101,26 @@ generated, the 400-seed depth-5 shrinker over all sixteen cases, the
1000-seed depth-6 oracle, and the 2000-seed depth-4 scan over all sixteen 1000-seed depth-6 oracle, and the 2000-seed depth-4 scan over all sixteen
cases. cases.
What the renders showed, at 1920x1200 against `e44dea3` (#18), with the
examples identical in both checkouts and Vulkan confirmed (the example asks
for `Backends::PRIMARY` and would have aborted on the GL fallback):
- `minimal` and `tabs` are byte-identical, and so is `tabs` after the
reference replay -- which changes 94% of the screen, so it is a real
comparison and not two pictures of nothing.
- `random` differs in two pixels, each by one grey level: glyph
antialiasing, nothing deleted.
- `text` differs only in its third block, the `rel(0.5)` padded paragraph in
a row: the block is the same width (945 px) and wraps the same lines, and
sits one pixel further right. Its box is 948 px, so the drawing is centred
with a 1.5 px remainder and the two expressions round that half-pixel the
other way. The first two blocks, which have no declared width, are
identical -- padding coming off the frame does not move a text that wraps
at the box it was given.
- A live resize of `random` to 900x1200 is byte-identical to a cold render
at 900x1200, which is the warm/cold rule checked in pixels rather than in
regions.
One thing is unexplained and worth knowing about: seed 30 at depth 5 One thing is unexplained and worth knowing about: seed 30 at depth 5
(`shuffle-all-but-first`) failed once, with a span keeping an answer of (`shuffle-all-but-first`) failed once, with a span keeping an answer of
`1 rel` for an axis whose frame had become `78 px`, and then stopped failing `1 rel` for an axis whose frame had become `78 px`, and then stopped failing