diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 31ad512..4216070 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -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` 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` 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: