Record exact layout equality and measured reuse improvements

This commit is contained in:
iris-ai committed 2026-09-17 15:11:23 -04:00
1 parent 0080bcbcba
commit 8f1a5e0a46
2 files changed
+64 -44

No files matched your search

+60 -43
View File
@@ -376,19 +376,12 @@ boundaries through `through`, or make the grid floor everywhere.
### Where the residual comes from, and the snap
Both remaining steps are a symbolic region re-expressed by division rather
than recomputed the way a cold draw computes it: `AxisRemap::Scale` divides
to find a part's fraction of the old box, and `placed_box` scales a mixed
`Len` by the alignment. Neither touches the threaded pixel chain, so every
layout decision already agrees warm against cold; what differs is the
composed position the shader and hit testing see, by up to 0.002 px.
Closing `Scale` exactly is possible: keep each primitive's region in its
widget's own coordinates and recompose on a move with `within`, eight
multiplies and no division against the current four divisions and twelve
multiplies, exact by construction, sixteen bytes more per primitive. Closing
the alignment one means resolving alignment in pixels, which costs the
retained resize path. Neither is worth a thousandth of a pixel on its own.
The `e44dea3` baseline's two-step allowance came from inverse remapping and
alignment composed by different routes. The frame/extent continuation below
retains local widget frames as well as local primitive coordinates, recomposes
in the same order as a cold draw, and removes inverse remapping. Its oracle
requires exact pixel-region equality. That experiment is not yet the pinned
framework; the baseline's snap decision below still applies there.
Where it does matter is `snap_floor` in `prelude.wgsl`, which adds half a
layout step before flooring: that absorbs float error and not a layout
@@ -429,8 +422,8 @@ back.
The original proposal was implemented by Claude as `5fcace1` on
`wip/region-and-placement` in `/home/bob/repos/iris-pr18`. It kept the fraction
reference stable but failed five layout cases and three draw-count cases.
The correction is saved as `efb416b`, pushed to origin's
`wip/region-and-placement`, in the isolated checkout
The first correction is `efb416b`; the exact-recomposition continuation is
`2ed5503`, pushed to origin's `wip/region-and-placement`, in the isolated checkout
`/home/bob/repos/iris-layout-experiment`; **it is not on PR #18**. The original
checkout is unchanged. Keep the experiment's build directory separate: Cargo
can accept a different worktree's artifacts as fresh when a target directory
@@ -475,34 +468,62 @@ placeholder zero answer became reusable once it was drawn during placement.
and `adding_text_to_a_reverse_row_keeps_its_shared_height` reproduces the
zero-height failure with a small tree.
Ordinary verification currently passes: formatting, workspace clippy with
`layout-diagnostics`, all workspace tests (96 suite tests), and the fast
generated cases. New regressions also inspect primitive coordinates for mixed
frame/extent drawings, including a region node, and a content change after
moving an inherited extent. A `tabs` resize from 1920x1200 to 900x1200 matches
a cold 900x1200 screenshot with zero differing pixels. The release oracle
passes 2000 seeds at depth 4 and 1000 seeds at depth 6 over all fifteen cases.
The shrinker passes 400 seeds at depth 5, and the debug oracle passes 120
seeds at depth 4 with assertions enabled.
**Exact recomposition and reuse.** The continuation replaces `given_len` with
`given_region`: each widget retains its original frame in its parent's
coordinates. Moving a retained subtree replays those same local compositions,
and stops at region nodes. `RegionRemap`, `AxisRemap`, and inverse division
are gone. Fixed-pixel frames can now change size without forcing otherwise
valid descendants to draw again. This adds 16 bytes per active widget; local
primitive coordinates were already retained by `efb416b`.
The shared oracle now compares pixel regions with ordinary equality; there
is no `AGREE_STEPS` allowance. Text publishes the range of its retained line
breaks rather than narrowing that range to every later
requested width. A layout with only explicit line breaks or no breaks holds
at arbitrarily wider widths. New tests compare actual primitive and mask
geometry and assert reuse for fixed-frame resizing and widening unwrapped
text (including explicit newlines and empty content).
Ordinary verification passes: formatting, workspace clippy with
`layout-diagnostics`, workspace tests, and the fast generated cases. The
suite contains 98 tests. A `tabs` resize from 1920x1200
to 900x1200 matches a cold 900x1200 screenshot with zero differing pixels.
With exact equality, the final release oracle passes 2000 seeds at depth 4
and 1000 seeds at depth 6, the shrinker passes 400 seeds at depth 5, and the
debug oracle passes 120 seeds at depth 4 with assertions enabled. Every scan
runs all fifteen scenarios. The fixed-frame reuse regression was also run
against `efb416b`: it fails there with two child draws instead of one.
**Do not land this as a performance improvement.** The old stretching and
wrapper reuse tests pass, and one numeric-size test improves from three draws
to two. Cold work also improves against the broken `5fcace1` prototype: 604 to
535 widget draws and 15,296 to 10,384 primitive writes on seed 1, depth 8.
But against the last working `e44dea3`, the same rig does substantially more
nested layout work:
to two. The continuation reduces widget draws from `efb416b` by 6.9% cold,
9.0% with many dirty widgets, 7.6% for a size change, and 8.7% for resize.
Against the last working `e44dea3`, it still does substantially more nested
layout work (seed 1, depth 8):
| phase | widget draws before | prototype | primitive writes before | prototype |
| --- | ---: | ---: | ---: | ---: |
| cold | 369 | 535 | 9635 | 10384 |
| cold | 369 | 498 | 9635 | 9704 |
| repaint | 1 | 1 | 1 | 1 |
| many | 157 | 310 | 4721 | 6970 |
| size | 16 | 275 | 106 | 5980 |
| many | 157 | 282 | 4721 | 6398 |
| size | 16 | 254 | 106 | 5725 |
| scroll | 2 | 2 | 0 | 0 |
| resize | 13 | 328.5 | 0 | 6721.5 |
| resize | 13 | 300 | 0 | 6255 |
These are instrumented work counts, not elapsed-time speedups (50 frames per
phase except cold, which is one; 578 generated widgets, 191 finally active).
Uninstrumented release binaries, run directly with `perf stat -r 3 -e
instructions:u`, also improve over `efb416b`: 2000 size-change frames fall
from 14.3923B to 13.8914B instructions (3.5%); 2000 resize frames fall from
16.9756B to 16.1331B (5.0%). These whole-process totals include fixture setup;
reported run-to-run variation is at most 0.24%. The many-dirty workload falls
from 16.7962B to 16.0030B instructions (4.7%, 2000 frames). Repaint and scroll
keep their draw counts but incur small CPU increases: 0.8849B to 0.8913B
(+0.7%) and 2.4332B to 2.4479B (+0.6%) over 100,000 frames each. These are
CPU-work measurements, not phone frame times. Keeping measurement validity separate from final drawing validity
and deferring to an already dirty parent were also tried; neither improved
this rig's draw counts, so those changes were removed.
Raw placement reads in containers and repeated offer/placement evaluations
remain conservative. Retaining local coordinates also costs memory per
primitive. The experiment demonstrates selective reuse with one `Widget::draw`
@@ -591,17 +612,13 @@ cold reach every length by the same expression.
### What the fuzzers tolerate
`AGREE_STEPS` in `tests/scenario/mod.rs` is **2**, and both steps are
positions. One is a box centred in a fraction of its parent against the
same box centred in its own pixels, 0.001 px on a handful of seeds. The
other is `AxisRemap::Scale` re-expressing a part as a fraction of a box that
changed length; one step fails the 400-seed shrinker on `resize-size`,
seeds 384 and 162, by 0.002 px while passing the 100-seed oracle. Two of the
earlier sources were fixed rather than tolerated (`bdab558`): `Scroll` wrote
a box it had been given back out as its own length in pixels, and `Span`
placed each child a step from where the last ended rather than as the fixed
parts before it plus one rounded share. See "Where the residual comes from"
above for what closing the rest would cost.
The frame/extent continuation removes `AGREE_STEPS`: warm and cold pixel
regions must compare exactly. This is distinct from the equal-share test:
when a row's grid-step count is not divisible by the number of children,
individual share widths can differ while every rerun of that layout must
still agree exactly. The PR #18 baseline still allows two position steps;
its earlier failure at one step (`resize-size`, seeds 384 and 162 at depth 5)
is a useful regression target for the experimental recomposition.
## Retained-layout invariants
+4 -1
View File
@@ -871,7 +871,10 @@ replace that reference. A measurement also records the available extent, so
reusing it checks both inputs. Retained primitive and mask coordinates name
whether they follow the frame or the extent. Numeric extent reads constrain
layout validity; merely drawing in an extent does not require another widget
evaluation when it moves. `Widget::draw` remains the only layout body. The
evaluation when it moves. Widgets also retain their original local frames,
so reuse composes positions in the same order as a fresh draw instead of
recovering fractions by division. The experimental oracle requires exact
pixel-region equality. `Widget::draw` remains the only layout body. The
prototype has significant container-update costs and is not yet adopted into
the pinned framework.