`Remap` existed to invert a composition, and a translation never needed
one: shifting a box shifts everything composed into it by the same
amount, because `lerp(s + d, e + d, t) == lerp(s, e, t) + d` on both
channels. That holds whether or not the box has a relative extent, so
the carry branch was answering a question it did not have to ask.
So the decision is made once, before the walk, and neither relocation
method branches. A translation is already one slot write. A change of
length calls `UiRegion::stretch`, which re-expresses each part at its
own fraction of the new box and needs `stretchable` -- a fixed length
holds its parts as offsets from its start and keeps no fraction to
stretch by.
`Remap`, `UiScalar::outside`, `UiSpan::outside` and `LerpUtil::lerp_inv`
are all gone with it. Nothing inverts a lerp any more: the one division
is done against a denominator `stretchable` already established is not
zero.
What it gives up is the per-axis carry, so a box that changed length on
one axis and not the other is redrawn where it used to be remapped.
Counted: six of `tabs`'s fourteen relocations and five of `text`'s
sixteen, and one extra redraw per frame on `replace_cost`'s 200 rows --
354,310,889 instructions against 354,272,387, which is noise.
Checked: fmt, clippy and 42 tests. `tabs` (with the image replay),
`view`, `minimal` and `text` all still render byte-identical to
`upstream/main`.
The plumbing for O(1) subtree movement (LAYOUT.md §2), with every slot
still at zero, so this changes no pixels and the next commit can change
behaviour against a known-good picture.
Every active widget owns a slot in `UiData::moves`: a translation in
physical pixels and the slot it is relative to. A primitive instance and
a mask each name one, and `prelude.wgsl` walks the chain and adds the
accumulated delta. A mask resolves its own chain rather than the drawn
primitive's, so a stationary viewport can clip content that moves inside
it. `CHAIN_LIMIT` is stated on both sides; it bounds a malformed cycle
rather than any real tree.
A slot outlives any one `ActiveData`, because a redraw replaces that
while the widget's children go on pointing at the slot, so it lives in
`UiRenderState::moves` keyed by widget and is retired when the widget
stops being drawn. `MoveIdx` is its own type rather than another
`Id<u32>`: it sits beside `MaskIdx` in an instance and the two must not
be swappable.
`Vec2` is now `repr(align(8))`, which is WGSL's alignment for a
`vec2<f32>`, so a GPU struct holding one is laid out the way its shader
reads it without saying so itself -- `GlyphPrimitive` no longer states
its own alignment, and `MoveOffset` never has to. Both keep a manual
`unsafe impl Pod`, since the trailing padding that alignment introduces
is what `derive(Pod)` refuses. `WindowUniform` holds the `Vec2` its
shader has always called `dim` rather than two loose floats, which was
the last place the two sides described the same bytes differently.
Checked: fmt, clippy and 40 tests. `tabs` (with the image replay),
`view` and `minimal` render byte-identical to `upstream/main`, and
`text` is unchanged.