Grow images in the generated trees

`Image` is the only widget in the repository whose size hint is a length in
pixels -- everything else hints a share, or nothing -- so it is the only one
that exercises a rule beside a hint, a box a widget knows before it is drawn,
and the answer the commit before this one changed. The generated trees had
none, which is why nothing there could reach that case.

`Kind::Image` is a fifth leaf, drawn one time in five, and it steps to a plain
rect when the shrinker reduces it: a picture measures nothing either, but its
length is its own, so the leaf that takes whatever it is given is the simpler
one. The picture is a 64x64 checkerboard of purple and black in 8 px cells,
committed at `src/assets/checkerboard.png` beside the generator that draws it
-- the way `examples/tabs` keeps its own -- and included rather than opened, so
that growing a tree does not depend on a working directory and one seed is one
tree whatever anything else does.

One upload per tree, however many images it grows: a `TextureHandle` is a
counted reference, so the first image in a tree uploads the checkerboard and
every one after it clones the handle. Measured: seed 1 at depth 4 grows 13
images and holds 1 texture, seed 6 grows none and holds none, and
`a_tree_of_images_uploads_one_texture` asserts it. `Image::new` is what a
caller holding a handle needs, since `image` uploads what it is given.

A seed names a tree only while the generator draws the same things in the same
order, so every seed now grows a different tree. The seed list in
`generated.rs` says so: 20 and 86 no longer grow the trees whose defects they
once caught, and both of those live on as shrunk fixtures in `unsettled.rs`,
which are trees rather than numbers. The seeds those fixtures name are
similarly historical, and their file says that too.

Format, clippy with and without layout-diagnostics, and the suite (135 + 19 +
13 + 4) are clean. The cold dump is a new baseline of 34,571 boxes over the 400
depth-5 trees, since the trees themselves changed; all three seed scans pass
over the new ones -- 400 at depth 5 in 62.79s, 1,000 at depth 6 in 160.20s,
2,000 at depth 4 in 299.58s -- which is what actually checks that images lay
out warm the way they do cold.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-20 04:50:31 -04:00
1 parent b295c8b97a
commit 2dba90bd0f
6 files changed
+72 -10

No files matched your search

+20 -3
View File
@@ -5,7 +5,9 @@
//! and the oracle another. And reducing a plan has to end, or a shrinker
//! searching for the smallest counterexample never returns.
use iris::random::{Edits, Kind, Plan, Rng, SpanEdit, plan};
use iris::harness::Harness;
use iris::prelude::*;
use iris::random::{Edits, Kind, Plan, Rng, SpanEdit, grow, plan};
use std::collections::HashMap;
fn some_edits(seed: u64, of: &Plan) -> Edits {
@@ -67,8 +69,6 @@ fn some_edits(seed: u64, of: &Plan) -> Edits {
}
}
use iris::prelude::*;
/// The two routes to an edited tree are one tree. `plan` resolves edits out
/// of the random stream as it draws; `edited` puts them on a tree that
/// already exists, which is the only route a shrunk plan has, since no seed
@@ -137,3 +137,20 @@ fn reducing_a_plan_all_the_way_ends() {
);
}
}
/// Every image in a tree is the same picture, and a handle is a reference to
/// the texture rather than a copy of it, so one upload and one slot serve all
/// of them however many a tree grows -- and the trees are grown in hundreds.
#[test]
fn a_tree_of_images_uploads_one_texture() {
let mut images = 0;
let mut tree = plan(1, 4, &Edits::default());
tree.walk_mut(&mut |p| images += (p.kind == Kind::Image) as usize);
assert!(images > 1, "a tree of {images} images tests nothing");
let mut h = Harness::new((900, 1200));
let (root, _) = grow(&mut h.rsc, 1, 4, &Edits::default());
h.state.root = Some(root);
h.frame();
assert_eq!(h.rsc.ui().textures.count(), 1);
}
+6
View File
@@ -9,6 +9,12 @@
//! reached through a region node's own entry rather than through the offer
//! that node was given. The last is a wrapping text handed back the width
//! it measured, rounded to a step below the line it measured there.
//!
//! Each says which seed it was shrunk from, of the generator as it stood when
//! it was found. Those numbers no longer grow those trees -- a seed names one
//! only while the generator draws the same things in the same order, and the
//! leaves have grown an image since -- so what is written out below is the
//! record of the case, and the seed is where it came from.
use std::collections::HashSet;
+8 -4
View File
@@ -25,10 +25,14 @@ fn depth() -> usize {
env("IRIS_GENERATED_DEPTH", 4)
}
/// The seeds the ordinary tests take. Eight that have never failed; 86,
/// which a `Scroll` fixed point once settled differently on; and 20, which
/// caught a locally redrawn widget being placed twice in the box its parent
/// had already placed it in.
/// The seeds the ordinary tests take: a corpus rather than a set of
/// regression cases, since a seed names a tree only for as long as the
/// generator draws the same things in the same order. Adding images to the
/// leaves moved every one of them, so 20 and 86 -- which once caught a widget
/// placed twice in a box its parent had already placed it in, and a `Scroll`
/// fixed point settling differently -- no longer grow those trees. Both
/// defects are pinned by the shrunk fixtures in `cases/unsettled.rs`, which
/// are trees rather than numbers.
const SEEDS: [u64; 10] = [1, 2, 3, 5, 8, 10, 13, 20, 86, 98];
fn check(seed: u64, depth: usize, case: Case) {