Record #18's three agreed changes, the measurement, and the wrapping-text divergence the random-tree rig found
This commit is contained in:
1 parent
5959d58e44
commit
19b9ca7413
1 file changed
+91
-36
@@ -9,40 +9,76 @@ Canonical `main` is **`ca2b4b2`** (#17, the headless rig). Sixteen slices are
|
|||||||
in; #16's size work and #17's rig both merged on 2026-09-14.
|
in; #16's size work and #17's rig both merged on 2026-09-14.
|
||||||
|
|
||||||
**#18 `split/18-position-chain`** is open, worktree `/home/bob/repos/iris-pr18`,
|
**#18 `split/18-position-chain`** is open, worktree `/home/bob/repos/iris-pr18`,
|
||||||
head `db1751f`, five commits. LAYOUT.md §2's O(1) subtree movement, plus the
|
head `86a7e8d`, eight commits. LAYOUT.md §2's O(1) subtree movement, the
|
||||||
`Remap` retirement the owner asked for on top of it.
|
`Remap` retirement, and then the three changes the owner chose on 2026-09-14:
|
||||||
|
the slot carries a box, slots are opt-in, and a widget's region is held in the
|
||||||
|
coordinates of the slot it draws in.
|
||||||
|
|
||||||
- Every active widget owns a slot in `UiRenderState::moves` -- a translation in
|
- **A slot holds a box, not a translation** (`1f9dc48`), given in the
|
||||||
physical pixels and the slot it is relative to. A primitive instance and a
|
coordinates of the slot it names, and `prelude.wgsl` composes the chain with
|
||||||
mask each name one, and `prelude.wgsl` walks the chain and adds the
|
`within` instead of adding a delta. A translation is the special case where
|
||||||
accumulated delta. A mask resolves its own chain rather than the drawn
|
the box has its parent's relative extent. Measured against the translate
|
||||||
primitive's, so a stationary viewport can clip content moving inside it.
|
slot on the same binary: +0.5-0.8% at depth 1-4, +9.6% at 8, +32.2% at 64.
|
||||||
- `try_reuse`'s translation case writes one slot instead of remapping a
|
Free where opt-in slots put the chain. The identity is `UiRegion::FULL`, not
|
||||||
subtree: 100 primitive region writes to 0 on a span of 20 rows five
|
zero -- a zeroed entry collapses its subtree to a point.
|
||||||
primitives deep.
|
- **`Painter::place` is how a container asks for a slot** (`d989691`): it
|
||||||
- `window_region` walks the same chain on the CPU, so hit testing and anyone
|
draws a child it decides the box of and may decide again. `Span`, `Aligned`
|
||||||
asking in window pixels agree with the shader. `Moves::resolve` stops at
|
and `Scroll` use it; everything else shares its nearest ancestor's slot,
|
||||||
`CHAIN_LIMIT` as the shader does, and debug-asserts that it reached the end.
|
which `tests/layout.rs` pins at one deep with four widgets in between.
|
||||||
- `Vec2` is `repr(align(8))`, WGSL's alignment for a `vec2<f32>`, so a GPU
|
Placing a child again is one entry to write, moved or resized alike.
|
||||||
struct holding one is laid out the way its shader reads it without saying so
|
- **Nothing is inverted any more.** `UiRegion::stretch`, `stretchable` and
|
||||||
itself. `GlyphPrimitive` no longer states its own alignment; both it and
|
`UiScalar::stretch` are gone, with the case they could not express: a
|
||||||
`MoveOffset` keep a manual `unsafe impl Pod`, because the trailing padding
|
40-tall row now stretches on its other axis, which `stretchable` refused.
|
||||||
that alignment introduces is what `derive(Pod)` refuses. **No manual padding
|
- **`redraws_under` is the CPU walk that remains**, and it is a question
|
||||||
fields** -- the owner rejected one on 2026-09-14.
|
rather than a marking: before keeping a drawing, ask whether anything under
|
||||||
- `Remap` is gone, with `UiScalar::outside`, `UiSpan::outside` and
|
it would have to be drawn again for the new length, and give up the reuse if
|
||||||
`LerpUtil::lerp_inv`. A translation never needed an inversion: shifting a box
|
so. Marking instead does not terminate -- the mark escalates to the
|
||||||
shifts everything composed into it by the same amount, since
|
descendant's size reader, which re-places the child, which marks it again.
|
||||||
`lerp(s + d, e + d, t) == lerp(s, e, t) + d` on both channels, whatever the
|
A part of a box with no relative extent holds its contents as offsets, and
|
||||||
box's relative extent. Only a change of length needs each part's fraction
|
composing into it leaves none either, so the walk stops where a length did
|
||||||
recovered, which is `UiRegion::stretch` behind `UiRegion::stretchable`. The
|
not change: an 80-wide child of a widened row is never asked.
|
||||||
decision is made once before the walk and neither relocation method
|
- **`Span`, `Pad`, `Stack`, `Offset`, `Aligned`, `SetSize` and `LayerOffset`
|
||||||
branches, which is how the owner asked for it.
|
say `Scale`.** Each places in fractions and offsets of its own box and none
|
||||||
|
reads its pixel length. `Scroll` and `MaxSize` do read pixels and stay
|
||||||
|
`Redraw`. Measured as not the cause of the divergence below: taking every
|
||||||
|
one of these claims back out leaves the failing set identical.
|
||||||
|
- **`OnResize::Scale` keeps its name.** The owner rejected `Stretch` on
|
||||||
|
2026-09-14: stretch has an opposite and scale does not, and the answer is
|
||||||
|
per axis so the axis is already established where it is read.
|
||||||
|
|
||||||
**Two invariants everything here rests on.** `resolved = region +
|
**The open defect, and it is older than this branch.** `tests/generated.rs`
|
||||||
resolve(slot)`, so anything that rewrites a region owes that slot a zero --
|
grows a seeded random tree and checks that laying it out again lands where
|
||||||
`stretch` does it for the subtree it rewrites, `draw_inner` for the widget it
|
growing it cold does. It does not always. Every case seen is a **wrapping text
|
||||||
draws. And a stretch is only expressible out of a box with a relative extent;
|
on a span's own axis**: the span measures the child in the whole box, the
|
||||||
a fixed length holds its parts as offsets from its start and keeps no fraction.
|
child shapes to that width and reports the width it used, the span places it
|
||||||
|
in exactly that width, which is a length change, so it shapes again and its
|
||||||
|
longest line is shorter than the box it was just given. Where it settles
|
||||||
|
depends on how many passes it has had, and a warm tree has had a different
|
||||||
|
number from a cold one -- so layout is not a function of the state, which it
|
||||||
|
has to be. A span whose axis is not the wrap axis is stable, which is every
|
||||||
|
real column of text, and why nothing had run into it.
|
||||||
|
|
||||||
|
seeds 1-30, three checks each db1751f (before) 86a7e8d (now)
|
||||||
|
diverging 7 of 90 30 of 90
|
||||||
|
|
||||||
|
The same defect either way: a placed child reaches the second shaping more
|
||||||
|
often now. It wants fixing where the two draws meet, LAYOUT.md §4, rather than
|
||||||
|
anywhere in the chain -- and **it should be fixed before #18 merges**, since
|
||||||
|
tripling how often a tree lands somewhere unrepeatable is not something to
|
||||||
|
land even on a defect that was already there.
|
||||||
|
`a_wrapping_child_of_a_row_settles_somewhere_else_each_time` is the ignored
|
||||||
|
reproduction; the six seeds the live tests use are ones that agree.
|
||||||
|
|
||||||
|
**Rigs added.** `iris::random` grows the tree (spans in every direction,
|
||||||
|
stacks, rects with varying opacity, text wrapping and overflowing, a declared
|
||||||
|
size over half of it, stopping at a depth), `examples/random.rs` draws one --
|
||||||
|
`IRIS_SEED` and `IRIS_DEPTH` pick it -- and `tests/generated.rs` compares warm
|
||||||
|
against cold under a size change, a resize, and both. It found the
|
||||||
|
non-terminating marking above immediately.
|
||||||
|
|
||||||
|
**Not verified yet:** the headless render shots and the live resize under
|
||||||
|
sway. The owner asked for those last, after the CPU side is right, and the CPU
|
||||||
|
side is not right until the divergence is.
|
||||||
|
|
||||||
### The plan after #18, agreed with the owner on 2026-09-14
|
### The plan after #18, agreed with the owner on 2026-09-14
|
||||||
|
|
||||||
@@ -91,10 +127,29 @@ the row grows. The CPU work on a resize becomes asking `on_resize` down the
|
|||||||
widgets and redrawing the ones that say so -- one ask for a transcript row
|
widgets and redrawing the ones that say so -- one ask for a transcript row
|
||||||
against a few hundred glyph rewrites. It replaces #18's chain rather than
|
against a few hundred glyph rewrites. It replaces #18's chain rather than
|
||||||
extending it, and it contradicts LAYOUT.md §2's line that slots carry
|
extending it, and it contradicts LAYOUT.md §2's line that slots carry
|
||||||
translation only. Before choosing it, run `chain_cost` with a region per slot
|
translation only -- a line an agent wrote into the design on 2026-09-04, not
|
||||||
in place of a `Vec2`: the per-level cost was measured as the dependent load
|
something the owner asked for. Her own statement of the requirement was "if it
|
||||||
rather than the arithmetic, so the extra lerps should be free, but that is an
|
needs to be moved then that can be done after the fact efficiently, or resized
|
||||||
assumption until the rig says so.
|
just done after as well".
|
||||||
|
|
||||||
|
**Measured, 2026-09-14.** `MoveOffset` widened from a `Vec2` to a `UiRegion`
|
||||||
|
(36 bytes) and `resolve_move` composing with `within` instead of adding
|
||||||
|
deltas, against the same binary's translate slots: depth 1 78.5 us against
|
||||||
|
78.0, depth 2 78.7 against 78.3, depth 4 79.6 against 79.0, then depth 8 89.8
|
||||||
|
against 81.9, depth 16 128.6 against 111.3, depth 64 331.2 against 250.6. So a
|
||||||
|
box slot is free at the 2-4 depth opt-in slots produce (+0.5-0.8%) and costs
|
||||||
|
10-30% past depth 8, where the chain is already expensive. All 42 tests pass
|
||||||
|
against the composing shader and `tabs` at 1920x1200 is byte-identical,
|
||||||
|
because composing through a translation box is the same map as adding its
|
||||||
|
delta. The experiment is `stash@{0}` in `/home/bob/repos/iris-pr18`.
|
||||||
|
|
||||||
|
The slot has to carry the whole box rather than a scale and an offset: a
|
||||||
|
pixel-space affine map scales everything under it, including a child that must
|
||||||
|
keep its pixel length, and the `rel`/`abs` pair is exactly what distinguishes
|
||||||
|
the two. What a box slot does not buy is a shorter CPU walk. Asking each
|
||||||
|
widget whether its drawing depends on the length that changed is the same walk
|
||||||
|
either way; what the box removes is the region rewrite and the upload at the
|
||||||
|
widgets that answer `Scale`, and the inversion at the ones that do not.
|
||||||
|
|
||||||
What is settled either way: recalculating rather than repositioning is in, as
|
What is settled either way: recalculating rather than repositioning is in, as
|
||||||
the `Remap` retirement above. It costs the per-axis carry: a box that changed
|
the `Remap` retirement above. It costs the per-axis carry: a box that changed
|
||||||
|
|||||||
Reference in new issue
Block a user