The performance section reported a cost the fixture had invented. `Branch`
picks which subtree to draw from a measured length, so seed 1 draws 88
widgets at `5ed9e87` and 115 at `bd6de71` -- the ms table was comparing
three and a half times the work. Measured on one tree, fixed point costs
about a fifth to a quarter.
Also corrects where the cost is. Cycles say the `i64` division is 0.00% of
`apply_scalar` and the multiply 1.5%; it is `saturating_add`, five
instructions with no `i32` vector form. Cycle counts are steady here to
0.23% -- it is wall time that varies 2x, not the counters -- and IPC is
unchanged across fixed point, so this is instruction count and not stalls.
Records the four wins taken (-7.4% cycles, -12% instructions), the four
things tried that made it worse, and the one lever left that needs a
decision rather than a measurement.
Also: the `Len`/`LayoutLen` split and `has_exact_size`; that `Scroll`
reports `LEFTOVER` rather than its content's size, which this said wrongly
in one place and rightly in another; and that the clipping `debug_assert`
holds mask-setters only, which is why a `LazySpan` does not trip it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`OrthoSize` was deleted in `9d8415d` and the handoff still described it as
restored; the surviving rule is `Painter::ruled`, and the circularity lesson
is restated without the enum's names. `HOLDS_EPSILON_PX` went with
`39e4ca2`, the clipping `debug_assert` the alignment section asked for landed
as `95fb4f9` and is narrower than it asked, the glyph conversion queued under
"Next" landed as `11c55bc`, and `LAYOUT.md`'s stale sections are §4, §5 and
density rather than §2.
Also records why the grid costs 1.5x what floats did, from the disassembly
rather than from theory: `RegionRemap::apply_span` is 352 instructions with
two 64-bit `idiv`s in it, because a fixed-point rounding is code where a
float's is hardware, integer division has no vector form, and saturation
breaks the pairing the `f32` `Vec2` had.
And the finding that answers the queued clamp item's open half: a `Max` may
not take a `leftover`, because a cap must read the report a rule otherwise
makes moot, and a share puts the division into the same equation -- the
multiple-fixed-point failure seed 13 already punished once.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three more roundings and divisions taken out after measuring each, one
optimisation tried and reverted with its number kept so it is not tried
again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three times slower in layout, and two thirds of that was a stacked child
redrawing twice a frame rather than the arithmetic. The counters are what
found it, once they could say "another layer" at all.
The performance table is rewritten around the fixed-point commits and in
milliseconds a frame, since the fixture it used has changed and the old
instruction counts cannot be compared across it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
§2 described `Painter::place`, `move_offsets`, `resolved_region` and
`set_instance`; three of those four names no longer exist. What shipped is
the opt-in region node, a box rather than a translation, and remapping for
everything that did not opt in.
§3 described `Widget::on_resize` and its `Scale`/`Redraw`/`Translate`
answers, which are gone: the `Holds` interval says the same thing per drawing
rather than per widget type, and says how far. The 0.05 px comparison it
quoted is equality on the grid now.
The trait in §1 has two methods rather than three, and the line numbers it
cited have all moved; they are dropped rather than corrected, since the names
are enough to find.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Traced rather than guessed: the same box reached two ways, each rounding
where the other does not. Two of the three are fixed, the third is priced --
it costs the retained resize path, at a thousandth of a pixel.
`OrthoSize` is deleted rather than paused; the decision it was waiting on was
that a span asks whether a rule already answers for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four commits on #18 put layout on a grid: `Fixed<SHIFT>`, then positions,
lengths, and the last of the pixels with `Holds`. The invariants that were
about float rounding are rewritten rather than annotated -- the 0.05 px
comparison is equality now, a move is a translation rather than a
re-expression, and the span boundary needs no margin.
What is left open is written down where the next session will look for it:
the remaining step of imprecision is one rounding between two ways of asking
how long a box is, and `OrthoSize` is paused on a decision rather than
started.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The shrinker's `reorder` case is no longer red: its two seeds were a span
deciding whether anything was left over on a box length its own answer had
produced, which comes back a few bits off. `5ed9e87` in `/home/bob/repos/
iris-pr18` moves that boundary by `HOLDS_EPSILON_PX` of room instead of
softening the comparison, so the validity range stays exact where generated
seed 16 needs it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three attempts at "draw the widget, then move its drawing to where its
alignment says" failed, and the notes are worth more than the attempts: the
move is a change of coordinate frame, and no split of the stored state
carries it. Moving a widget's region with its drawing has a later local
redraw ask a differently rounded question, which crosses the one boundary
`Span` needs exact. Leaving the region alone lets the placed box accumulate,
because `try_reuse` returns a clean subtree's size without walking into it.
Both are measured.
Also recorded: that a container reporting a child's size while handing it a
bigger box places that content twice, which is what the align override is
for; that a widget must not report more than it occupies, which is why
`Scroll` now fills both axes; and Bryan's decision that alignment is one
fraction per axis rather than four directions, with the middle as the
default because the edges assume a direction.
The alignment work is in the working tree at `8220a78` and not committed.
Its one red case, and the four things queued behind it, are listed.
The measurement is the part worth keeping: adding an offset to a moved
subtree's stored regions drifts linearly and without bound, so the fraction
path is there for accuracy, not generality. `tests/drift.rs` on the Iris
branch pins it.
Built-in alignment and size moves onto #18 rather than waiting for it, with
ownership agreed -- properties on the `region_node` mechanism, a declared
length winning per axis, and a size rule that carries min/max/clamp so a
clamp is resolved where the box is decided instead of by a widget reading
its own pixels.
Rewrites the opening as state rather than a chronicle of the last few
commits, and gives the parked rule its own section: what the owner asked
for in her words, the half that is written and works with the code in
it, the four ways of asking that were measured and what each did, the
seed that shows it most clearly, and the design question underneath --
which is the same one holding OrthoSize. Also folds the declared-length
rule into the invariants, brings the vocabulary to leftover, says the
suite is eight seconds and must stay that way, and writes out the image
replay rather than pointing at a file in /tmp.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only rel doubles: apply_rest turns rest into the whole box and px
coincides. Aligned and Scroll honour a hint the same way Span does, so
all three double it. Also corrects the note about a fractional hairline,
which fails because a fraction is a fraction of its own box rather than
through any rounding, and records that a span whose shares run out walks
its children backwards instead of overflowing forward.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The text reference render is not byte-identical after all: a declared
rel or rest length is applied by the span and then again inside SetSize,
so .width(rel(0.5)) draws its child at a quarter. Also replaces the
example that measured one-pixel lines with what the tests now say, since
a span short of room overlaps its fixed lengths rather than collapsing
them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tabs difference does not reproduce at f61e893 against upstream/main
built in its own worktree, so nothing is waiting on a decision. Records
the instruction counts, frame times and memory taken today rather than
mid-branch, and what a one-pixel line and a one-pixel gap do under the
chain, which is the question the pixel difference was really about.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A fresh session had no way to learn the handoff existed: AGENTS.md named
only docs/PLAN.md, and the filename tied the document to one piece of work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both gestures, since two widths in turn flatters a store and a drag does
not, and the memory beside the time: the question that decided the shape
of it was whether it is a fixed cost or one per text widget.
Bring LAYOUT.md's §1 and §2 to what shipped. A widget returns its size
rather than recording it, a slot carries a box rather than a
translation, and the per-axis answer is `OnResize` rather than
`is_size_independent` -- and the constraint the position chain replaced
was an agent's, which the text now says.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The random generator grew during the work, so the seed that names a
1061-widget tree in the pre-#16 rows names a 144-widget one now. Every
"still N times the old resize" here was measured across that change and
says nothing; a hand-written fixture that compiles on both revisions
says the old code is 2.2x cheaper and puts a paragraph outside the
output, for two reasons it records as deliberate.
Record what a resize frame is actually spent on -- 63% text, 0.9%
draw_inner -- and what removing the single-entry shaping and placement
caches measures at.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The random tree at depth 7 measured against the two revisions before it: the
GPU pass is a tenth of a millisecond everywhere, and every difference is the
CPU laying out. A repaint of one leaf costs 1313 draws and 6.5 ms where the
pre-#16 code cost one draw, because `redraw` escalates to the top size reader
before knowing whether the size changed at all. Drawing first and escalating
only on a different size takes that to one draw and takes a resize below the
pre-#16 number, but misplaces four widgets in the resize case, so it is the
owner's to call.
Also records the measure-by-drawing multiplier -- 1.3x the drawn widgets at
depth 4, 5.2x at depth 8 -- and that scrolling is now part of the random
trees.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
And the open question beside it: a slot that carries a box rather than a
translation, which would make a move and a stretch of a Scale subtree
one write each. To be measured with chain_cost before it is chosen.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>