From 038f6a3832c1b2878411a89fc8b626365cfb240f Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Mon, 7 Sep 2026 12:39:53 -0400 Subject: [PATCH] docs: the test rig's layers 1 and 2, with their commands and their limits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RUST.md's "Three test layers" section rewritten in place with what was built: the `cargo test -p transcript-fixture` command and the five assertions with the mutation that fails each, the `run-headless.sh --phone [--replay …]` commands and the 15s/18s they take, and a paragraph on what still cannot be answered below layer 3 (anything about pixels, any frame time, anything JNI). Also the two traps that cost time -- `swaymsg seat - cursor` reaching nothing on a compositor with no input devices, and a leftover window tiling beside the new one so a screenshot looks like a duplicated-primitive bug. IRIS.md gains the public surface: `iris::harness`, `TouchScript`, `List::fling_velocity`, the fling's clock, and the desktop backend's move to physical-pixel layout with `content_scale`/`IRIS_SCALE`. Co-Authored-By: Claude Fable 5.1 --- AGENTS.md | 14 +++++++ docs/RUST.md | 114 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 95 insertions(+), 33 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3481aa3..20786bf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -283,6 +283,20 @@ Each exists because something was invisible without it. checkout's own emulator, taps "Run benchmark" by label, and prints the report -- written so the P0 build/install/tap/read-report cycle stops being retyped by hand each time (docs/RUST.md's P0 box). +- **iris's three test layers** (docs/RUST.md's "Three test layers" has + the commands and what each cannot answer): test at the cheapest one + that can answer the question. `cargo test -p transcript-fixture` runs + the real transcript screen over the bench fixture with **no window, no + compositor and no GPU** (`iris::harness`), on a clock the test owns and + a gesture replayed from a `t_ms action x y` file under + `iris/transcript-fixture/touch/` -- which is how the batched 120Hz + flick a finger actually makes is testable at all, since a `ui-trace` + swipe is many evenly-spaced events. `iris/run-headless.sh phone --phone + --shot …` opens the same screen in a window at the phone's own size and + density for looking at, and `--replay FILE` drives the same recording + into it. The emulator is for JNI, the IME, insets, the surface + lifecycle and one verification run before a build goes to the phone -- + not for iterating on layout. ### Driving the UI diff --git a/docs/RUST.md b/docs/RUST.md index f6445d1..75874df 100644 --- a/docs/RUST.md +++ b/docs/RUST.md @@ -48,7 +48,7 @@ closes it. In order; two builders at a time. Each is ticked here by the agent that closes it. -- [ ] Test rig, layers 1 and 2 ("Three test layers" below). Running. +- [x] Test rig, layers 1 and 2 ("Three test layers" below), landed 2026-09-07. - [ ] Fling parity with Compose, and the phone's keyboard push-up, with insets shown in the diagnostics overlay. Running, in a worktree. - [ ] Rows at the transcript's top edge: culled too early in one state, @@ -93,7 +93,7 @@ The bench client (`android-app/src/bench_client.rs`, ~1000 lines) is the first thing to look at moving, since a desktop bench on the same fixture is layer 2 of the test rig below. -### Three test layers, cheapest first (decided 2026-09-07, rig not yet built) +### Three test layers, cheapest first (decided 2026-09-07; layers 1 and 2 built the same day) Iris's suggestion, adopted and layered: test at the cheapest layer that can answer the question, and go up only when it cannot. The emulator @@ -101,43 +101,91 @@ costs minutes a cycle; the desktop window seconds; the headless harness runs inside `cargo test`. 1. **Headless, in-process, no compositor and no GPU -- the default.** - `iris/src/layout_tests.rs` already builds a tree over `UiRenderState` - with no window, `sense.rs`'s gesture tests feed fabricated - `CursorState`s with their own times, and `List::tick_fling` is - driven by hand. Extend that into one harness that opens - `transcript-ui`'s screen on `app/bench-fixture` (as `bench_client.rs` - does on Android, no server), at the phone's logical size and - `content_scale` from `docs/bench/iris-phone-v2-2026-09-06.md` (2.55, - 120Hz -- read, never typed from memory), ticks frames, and feeds a - **replayed touch stream** from a trivial file of `(t_ms, action, x, - y)` lines with `cursor.time` taken from the file. That is what the - emulator cannot do at all: the batched 120Hz flick from the phone - report becomes a deterministic test asserting on scroll offset and on - the `iris drag release:` velocity. Anything about layout, scroll - position, selection, focus or fold state is answered here, with - assertions rather than eyes. Nothing renders; a widget's placed - rectangle is the evidence. + `iris::harness` (`iris/src/harness.rs`), plus the fixture crate it + opens. `Harness::new(size, density)` builds an `Rsc`, a + `UiRenderState` and a state whose `FocusHost`/`OpenUrl` *record* what + the platform was asked for; `frame(t_ms)`/`frames_until(..)` run + frames on a clock the test owns, and `replay(&TouchScript)` feeds a + recorded gesture one sample at a time exactly as + `IrisViewPeer::on_touch_event` replays Android's historical samples. + The recordings are plain `t_ms action x y` files under + `iris/transcript-fixture/touch/`, and `flick-120hz.touch` is the + phone's own shape: DOWN, four samples 4ms apart, UP, 20ms in total. + + cd iris && cargo test -p transcript-fixture + + runs in about a second and asserts (a) the flick releases with a real + velocity (`List::fling_velocity`, which only `Released(Some(v))` + fills), (b) the list travels and settles inside the AOSP spline's own + `FlingCalculator::duration`, (c) a tap moves nothing and opens no + link, (d) a long-press-then-drag leaves selected text and does not + pan, and (e) the composer clears a simulated 1000px IME inset + (`Composer::set_bottom_inset`). Each was confirmed to fail without + its subject rather than assumed: dropping `animate(id)` from + `Selection::drag` -- the phone's own "fling does nothing" defect -- + and starting the fling curve at the wall clock each fail only the + flick test; flinging on `Tapped` fails only the tap test; a 5s + `LONG_PRESS` fails only the selection test; a `set_bottom_inset` that + ignores its argument fails only the composer test. + + **What still cannot be answered below layer 3**: nothing renders + here, so anything about pixels -- glyph rasterisation, the atlas, + stale or duplicated primitives, colour, the surface lifecycle, the + renderer rebuild -- is invisible to layer 1 and only *looked at* in + layer 2. Frame *times* are not measurable at either: layer 1 does no + GPU work at all and layer 2 runs a debug build on this VM's virtio + GPU, so a number from either is not the phone's. Anything JNI (the + IME, real insets, the clipboard, battery) is layer 3 by construction: + layer 1 records that the platform was asked and layer 2 has no + Android platform to ask. + 2. **A phone-shaped desktop window under headless sway -- for looking.** - `iris/run-headless.sh` already runs a winit binary under a private - sway and screenshots it with `grim`. Add a `--phone` mode (output and - window at the phone's size and scale, with the scale reaching iris - the way Android's does so dp layout runs at that density) and - touch-shaped mouse input: a left-button drag pans and flings through - `DragArbiter`, long-press selects, no hover. One input path, not a - parallel one (`CODE_RULES`). Colour, spacing, text and anything a - person has to see is answered here. `swaymsg seat - cursor - move/press/release` drives it when a gesture is needed on screen. + + cd iris && ./run-headless.sh phone --phone --shot /tmp/p.png -- -p transcript-fixture + + About 15 seconds warm. `--phone` sets the private sway output to + 1080x2424@120Hz and exports `IRIS_SCALE=2.55`, which reaches iris the + way `DisplayMetrics.density` does on Android + (`iris::default::content_scale`) -- the desktop backend now lays out + in physical pixels with a density instead of dividing into a separate + logical space, so both platforms run one path. `transcript-fixture`'s + `phone` example opens the same screen from the same bytes as layer 1 + and the Android bench. + + A gesture on screen uses the *same recordings*: + + ./run-headless.sh phone --phone --replay transcript-fixture/touch/flick-120hz.touch \ + --shot /tmp/p.png -- -p transcript-fixture + + writes `/tmp/p-before.png` and `/tmp/p.png` either side of the flick; + looked at 2026-09-07, the list moved back about seven turns of the + fixture and settled. + + **`swaymsg seat - cursor` cannot drive it, and that cost an hour.** + This compositor runs the headless backend with no input devices + (`WLR_LIBINPUT_NO_DEVICES=1`, `LIBSEAT_BACKEND=noop`): the cursor + commands all report `success` and nothing whatever reaches the + client, with `swaymsg -t get_seats` showing `capabilities: 0` as the + only sign. wlroots 0.19 dropped `WLR_HEADLESS_INPUTS`, and ydotool's + uinput device would be ignored by a compositor that is not reading + libinput. `iris/rig-input`'s `replay-touch` uses the + **virtual-pointer protocol** instead, which is a client protocol and + needs neither devices nor root, and it parses `iris::harness`'s own + `TouchScript`. Two traps inside it, both found by printing winit's + events: a button sent in the same frame as the motion that first puts + the pointer over the window is dropped (the client sees the enter, + the moves and the *release*, never the press), so the pointer is + positioned and left to settle 200ms first; and a leftover window from + an earlier manual run **tiles beside the new one**, halving the width + and producing a screenshot that looks exactly like a duplicated- + primitive rendering bug -- `swaymsg -t get_tree` and `pgrep -af + examples/phone` are the check. + 3. **The Android emulator -- platform plumbing and the final pass.** JNI, IME, insets, surface lifecycle, the renderer rebuild, and one verification run before a build goes to the phone. Not for iterating on layout. -Pass condition for the rig: `cargo test` runs a fixture-backed headless -transcript screen with a replayed flick and asserts a nonzero release -velocity and a moved scroll offset; one command opens the same screen in -a phone-shaped window and screenshots it. Record the commands here when -it lands. - ### The 22:16 phone report, worked 2026-09-06/07 Iris's four items are listed in docs/IRIS_TODO.md's "From the phone,