Record that the shader, not the grid, decides screen pixels
`d21a215` answers the API question: one widget keeps one length per axis, and `Wrapper` -- what `WidgetPtr` already was -- is the second widget the second length needs. The `i64` composition item is rewritten around a measurement that changes what it is for. `prelude.wgsl` decodes the raw counts into `f32`, walks the move chain in floats, and lands every edge on a whole pixel with `snap_floor`. Checked in the render rather than read off the source: there is no partially covered column anywhere along `tabs`'s band of rounded rects, so every box edge is hard, and the two `pad(10)` gaps are exactly ten pixels on both sides of the truncating multiply. The screen invariant is therefore already as good as integers allow, and exact composition cannot improve it. What it can still buy is layout's own decisions -- the `Span` leftover boundary, the queued clamp crossover, `Holds` validity, warm-against-cold agreement -- with `Holds::through`'s two-route allowance as the test of whether it worked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
82c006cd59
commit
b0c13b85a9
1 file changed
+48
-23
+48
-23
@@ -8,7 +8,7 @@ log.
|
||||
|
||||
Canonical Iris `main` is **`ca2b4b2`** (#17, the headless rig). **#18
|
||||
`split/18-position-chain`** is open in `/home/bob/repos/iris-pr18`; its local
|
||||
head is **`e166e00`**, seventy-nine commits, pushed. Built-in alignment is
|
||||
head is **`d21a215`**, eighty commits, pushed. Built-in alignment is
|
||||
complete there; see "Built-in alignment" below for the retained-layout
|
||||
details. No PR reviews were present when checked on 2026-09-15.
|
||||
|
||||
@@ -216,7 +216,8 @@ against `394d514`. The measurements are under "Performance".
|
||||
`1d397c57b9914a2e596fa907029bc6aab4629e4d74deb0715e81325c703bcdb3`.
|
||||
The run used the Venus adapter backed by the host RX 7900 XT.
|
||||
- `minimal`, `text` and `view` render byte-identical at 1920x1200 across
|
||||
`8220a78`. `tabs` differs only in the widget count it prints about itself,
|
||||
`8220a78`. `tabs` differed only in the widget count it printed about
|
||||
itself,
|
||||
which is two wrapper types smaller -- so it is no longer a byte-identical
|
||||
reference and the generated oracle is the check that matters.
|
||||
**That last claim was then left to cover `d3b0ebf` as well, and it does
|
||||
@@ -661,14 +662,13 @@ Chaining them therefore means something different now, and two of it showed:
|
||||
to sit at the top of its row and filled it. Fixed in `2bc6bdf`; both match
|
||||
`main` again.
|
||||
- **`.width()` overwrites what `.sized()` set on the same axis**, so
|
||||
`tabs`'s `rrect.sized((100, 100)).center().width(leftover(2))` is a
|
||||
`tabs`'s `rrect.sized((100, 100)).center().width(leftover(2))` was a
|
||||
100-tall bar across two shares where `main` draws a 100x100 square centred
|
||||
in them. **Open, and an API question rather than a bug**: one widget
|
||||
carries one length per axis, so "a small thing centred in a big box" now
|
||||
needs a container to hold the big box -- `(thing.sized(..).center(),)
|
||||
.stack().width(leftover(2))` is the idiom, and it is what the old builders
|
||||
were inserting silently. The alternatives are to update the example, or to
|
||||
give a widget an outer length as well as its own. Awaiting Bryan.
|
||||
in them. **Answered by `d21a215`** (Bryan, 2026-09-16): one widget keeps
|
||||
one length per axis, and the second length needs a second widget that does
|
||||
as little as possible. `WidgetPtr` already was that widget and is now
|
||||
`Wrapper`, with `.wrapper()` to make one -- `Wrapper` and not `Wrap`, and
|
||||
`.wrapper()` and not `.wrapped()`, so neither reads as the text setting.
|
||||
|
||||
`text` still differs from `main` in its `aligned` panel, and that one is a
|
||||
decision rather than a defect: `9d8415d` deleted `OrthoSize`, so a Y span
|
||||
@@ -954,26 +954,51 @@ Queued from this work, in order:
|
||||
since `a8898aa`. Still awaiting Bryan: whether a `Max` narrows the box the
|
||||
child draws in, or only what the parent reports for it.
|
||||
|
||||
- **Compose in `i64` and narrow only when storing** (Bryan, 2026-09-16). The
|
||||
range inside a single operation is already protected -- `Fixed::mul`
|
||||
- **Compose in `i64` and narrow only when storing** (Bryan, 2026-09-16).
|
||||
Chosen for the share spread; **what it buys is not that**, found on
|
||||
2026-09-16 and worth reading before starting.
|
||||
|
||||
The range inside a single operation is already protected -- `Fixed::mul`
|
||||
widens to `i64` for the product, `Holds::through` does all of its
|
||||
arithmetic in `i64` and narrows once at the end, and `div_toward` takes
|
||||
`i64`. What is not protected is *precision across a chain*:
|
||||
`UiSpan::within` composes a box through its parent with four multiplies,
|
||||
and every one of them comes back to the `i32` grid before the next starts,
|
||||
which is exactly the "step per level of nesting" in "Fixed point" and what
|
||||
forces `Holds::through`'s allowance for two routes. A `Wide<SHIFT>` held
|
||||
through a composition and narrowed when it is written to `ActiveData` or a
|
||||
primitive would round once instead of once a level, at no cost in stored
|
||||
size, since storage stays `i32`.
|
||||
`UiSpan::within` composes a box through its parent with four multiplies and
|
||||
every one comes back to the `i32` grid before the next starts, which is the
|
||||
"step per level of nesting" in "Fixed point" and what forces
|
||||
`Holds::through`'s two-route allowance. A `Wide<SHIFT>` held through a
|
||||
composition and narrowed when it is written to `ActiveData` or a primitive
|
||||
would round once instead of once a level, at no cost in stored size.
|
||||
|
||||
**But it cannot make equal shares the same number of screen pixels,
|
||||
because the CPU's grid is not what puts them on the screen.**
|
||||
`prelude.wgsl` decodes the raw `rel`/`px` counts into `f32`, walks the move
|
||||
chain in floats -- with the comment saying so, "what has to hold is that
|
||||
this agrees with itself frame to frame, not that it matches the CPU to the
|
||||
last bit" -- and then lands each edge with
|
||||
`snap_floor(rel * dim) + snap_floor(px)`, which is a whole pixel.
|
||||
**Measured: every box edge in the `tabs` render is a hard edge**, no
|
||||
partially covered column anywhere along the band, and the two `pad(10)`
|
||||
gaps are exactly ten pixels on both sides of the truncation change. So the
|
||||
screen invariant is already as good as integers allow: a length in pixels
|
||||
is that many pixels, and equal shares differ by at most one whole pixel
|
||||
because three equal integers cannot sum to 1000. Layout's own one-or-two
|
||||
step spread is below what the shader can express, and shows only where it
|
||||
pushes a value across the `floor` -- which is what moved `tabs`'s corner
|
||||
arcs by a pixel at `08c9d5a`.
|
||||
|
||||
What is left to buy is **layout's own decisions**, which is not nothing:
|
||||
the `Span` leftover boundary and the clamp crossover are structural
|
||||
decisions taken on a pixel comparison, `Holds` validity is a pixel
|
||||
interval, and warm-against-cold agreement is the thing every rig here
|
||||
measures. The success test is `Holds::through`'s two-route allowance
|
||||
shrinking from three half steps toward zero, in debug where the assertion
|
||||
is live.
|
||||
|
||||
Two things to decide before starting: how many fractional bits the
|
||||
intermediate keeps, since chained multiplies accumulate them and an `i64`
|
||||
runs out too; and whether it helps `UiSpan::within` stay inlined, which is
|
||||
the thing that actually moves cycles there -- a `Wide` that makes the body
|
||||
bigger loses on the axis the truncation change won on. The success test is
|
||||
narrowing `Holds::through`'s two-route allowance back to a step, which is a
|
||||
check the generated oracle can answer in debug.
|
||||
runs out too; and whether it keeps `UiSpan::within` inside the inliner,
|
||||
which is what actually moves cycles there -- a `Wide` that makes the body
|
||||
bigger loses on the axis the truncating multiply won on.
|
||||
|
||||
Other queued work, in dependency order:
|
||||
|
||||
|
||||
Reference in new issue
Block a user