From 150245071fc1208f2b10d9f139c96f2ada75c04e Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Sat, 19 Sep 2026 00:35:42 -0400 Subject: [PATCH] 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. --- docs/HANDOFF.md | 88 ++++++++++++++++++++++++++++------------------ docs/LAYOUT_LOG.md | 30 +++++++++++++++- 2 files changed, 83 insertions(+), 35 deletions(-) diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 90afa0d..191c89d 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -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 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 plan with the one-ask protocol below, and `1512d84` and `23523ee` make the 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 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 is pinned as a named test. -Not done: the renders and the `tabs` replay, the retained-cost work, the -renames, and a pre-submit review of the three commits as one diff. +The renders and the replay are done and recorded in `docs/LAYOUT_LOG.md`: +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 `wip/step3-experiment` (one commit over `4328eac`) and as @@ -244,7 +250,7 @@ Unchanged; see `docs/LAYOUT.md`. ## Implementation plan 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 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 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` -as one diff against `4328eac`. `1512d84` rewrote much of what the first two did to -`painter.rs` and `render_state.rs` and was reviewed as it was written, but -the three have never been read as one change, and the parts of the one-ask -protocol it did not touch -- `place_at`, the twice-asked deferral, `Span`'s -two passes -- were written as a probe and reviewed only by their tests. +Run the pre-submit review over the five commits as one diff against +`4328eac`. `1512d84` rewrote much of what the first two did to `painter.rs` +and `render_state.rs` and was reviewed as it was written, but the five have +never been read as one change, and the parts of the one-ask protocol it did +not touch -- `place_at`, the twice-asked deferral, `Span`'s two passes -- +were written as a probe and reviewed only by their tests. ### 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 -"Frames, decided boxes and padding"; what implementing it corrected in the -plan -- three of them wrong layout that would have shipped -- is in +**Done at `1512d84` and `23523ee`.** The settled rule is in `docs/LAYOUT.md` +under "Frames, decided boxes and padding"; what implementing it corrected in +the plan -- four of them wrong layout that would have shipped -- is in `docs/LAYOUT_LOG.md`. ### 3. Render and replay -Read the installed graphics skill and confirm the renderer. Render `view`, -`minimal`, `random`, `tabs` and `text` at 1920x1200 against `34cafb6` and -`e44dea3`, replay `tabs`, and compare a live resize of `random` with a cold -render at the same size (commands under **Full verification** below). A -text placed by re-expression rather than a second draw is the change most -likely to show here; inspect every intentional difference and record it. +**Done at `adbedaf`**, against `e44dea3` (#18); what each render showed is +in `docs/LAYOUT_LOG.md`. `view` has no counterpart in the baseline, so it +was rendered but not compared. Run the set again after any further change +here -- the commands are under **Full verification** below, and a text +placed by re-expression rather than by a second draw is what shows first. ### 4. Rename and delete @@ -293,26 +298,36 @@ Suite, oracle. ### 5. Restore the expected retained cost -Work counters at `3091fb8`, seed 1 and 13, depth 8, widget draws / distinct -widgets, beside `e44dea3` (#18) and `49cec82` (the branch head before this): +Work counters, seed 1 and 13, depth 8, widget draws / distinct widgets, +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 | -| many | 157/95 | 187/119 | 110/92 | +| cold | 369/261 | 331/288 | 342/288 | +| many | 157/95 | 110/92 | 118/95 | | size | 16/12 | 3/3 | 3/3 | | 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 | -| many | 524/159 | 1091/423 | 424/364 | -| resize | nothing | 2215/510 | nothing | +| cold | 1330/707 | 1179/982 | 1278/982 | +| many | 524/159 | 424/364 | 429/366 | +| resize | nothing | nothing | nothing | -`many` and `size` are better than #18 at seed 1 and `many` draws fewer times -at seed 13, but it touches twice as many distinct widgets there, and `resize` -at seed 1 draws 40 times where #18 drew 13. Two mechanisms, both understood: +`many`, `size` and `scroll` are better than #18 and within a few draws of +`0ef87eb`; `cold` is 3% and 8% more than `0ef87eb` for the same distinct +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 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 (shares, or `Sign::Neg`), and express the measuring room's far end without 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 work agrees. diff --git a/docs/LAYOUT_LOG.md b/docs/LAYOUT_LOG.md index b1ebbb2..a5535ce 100644 --- a/docs/LAYOUT_LOG.md +++ b/docs/LAYOUT_LOG.md @@ -75,13 +75,21 @@ after the step lands. - **The clipping debug assert compared the reported size with the frame** 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 tree's, and the divergence now lists, for the failing widget and every 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 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 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 @@ -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 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 (`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