Commit Graph
9 Commits
Author SHA1 Message Date
irisandClaude Opus 5 43f3786bed Validate the serial cache by AVD name, not by the serial still existing
Emulator serials are ports, and ports get recycled. The cache checked only
that its cached serial was still in `adb devices`, which stays true after
the AVD that was on it exits and a different one takes the port -- so the
entry never expires and every call resolves to somebody else's emulator.

Measured on 2026-09-04 with ai-app.serial and ai-app-2.serial both holding
emulator-5554: from ~/repos/ai-app-2, `emu up` reported "'ai-app-2' is
already running (emulator-5554)" and handed back a device that
`adb -s emulator-5554 emu avd name` calls ai-app. Every adb, ui-trace and
emu call from that checkout went to the other session's emulator, and the
right AVD could not be started at all because the tool believed it was up.
That is the exact fan-out this repo exists to prevent, and nothing says so.

Asking the cached serial its name is one round trip against one device, not
the per-device sweep the cache exists to avoid, and a mismatch drops the
entry so the slow path can answer. It goes in avd_serial because `adb`,
`ui-trace` and both of `emu`'s uses -- resolving a serial, and deciding
whether to start one -- already come through it; a check in only one of
them would be the same bug with a smaller blast radius.

Found from ai-app-2; ai-app's session confirmed its own work had landed on
the right device and asked for the fix here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 18:01:17 -04:00
irisandClaude Opus 5 b678bf3ccd Press things by name in ui-trace, not by coordinate
`--do "tap 'Save'"` finds whatever currently carries that label -- the text,
or the padded control around it, preferring the clickable one -- resolves its
box from the accessibility tree at the moment of the gesture, and presses its
centre. It is the name the control already has for assistive technology, so
there is nothing extra to keep in step with it.

`tap X Y` still works and is now the exception. A coordinate is a position
measured once by hand: anything that moves the control makes the tap land on
whatever now sits there, and the script then reports a number that was never
measured, which reads exactly like a result rather than like a failure.
ai-app's two benchmark scripts pressed a header button at `tap 723 205` and
that button has now moved; Iris asked on 2026-09-03 that the fix be in the
tool rather than a habit each script remembers.

Two things make the failure loud, which is the whole point. A label that is
not on screen ends the recording with `# error` and a non-zero exit, and
`record` now prints the error lines rather than the head of a trace that can
be thousands of frames long. And the sampling loop waits for the action
thread before exiting -- a script whose last step outlasted the recording
used to have its outcome discarded, including that error.

The label is looked for over three seconds rather than once: the
accessibility connection has no window at all for the first frames after it
is made, and a control revealed by the previous step arrives a frame or two
later. The wait is bounded and the failure is still loud.

Exercised against a running emulator: a tap by label, a tap that finds
nothing, and a failure that lands after the recording's own duration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 23:52:43 -04:00
iris 69a22c54e7 Stop Gradle installing on every checkout's emulator at once
The adb wrapper can only aim a call that goes through it, and Gradle's
Android tasks do not: installDebug, uninstallDebug and connectedAndroidTest
ask the adb server for every attached device and act on all of them. One
session ran installDebug with two emulators up and replaced the app on both,
which Gradle reported as success and the other session read as its own build
never landing.

`emu check` answers the question the wrapper already answers for adb --
would a command that reaches every attached device reach a stranger's
emulator from here -- and install.sh links a Gradle init script into
~/.gradle/init.d so every build on this machine asks it, including checkouts
nobody has adopted it in.

It refuses narrowly: a lone emulator, a physical phone and a caller who
named a device are all fine. ANDROID_SERIAL naming another checkout's
emulator is not, because that is what a serial exported into a long-lived
shell decays into once an emulator restarts and another's takes the port.
2026-08-31 15:40:58 -04:00
irisandClaude Opus 5 1da445ae32 Re-measure the GPU numbers with an emu list that can be trusted
The table in the README compared our own app on software against the stock
Settings app on the GPU, and labelled both as Settings. The memory saving
beside it -- 1.1 GB -- came from `emu list` before 0984336 fixed its prefix
match, so it was one emulator's resident size reported against another's.
Both numbers were written down as recorded guidance, which is the worst
place for a figure nobody can reproduce.

Taken again on one AVD, same gesture, same app on both sides. Settings
scrolling its own list: 57.4% janky frames and 18 slow UI-thread frames on
software, 2.3% and 0 on the GPU; legacy jank 100% against 5.7%. Memory
twenty seconds after boot: 3537 MB against 2918 MB, so about 620 MB rather
than 1.1 GB.

The conclusion is stronger than the version that was wrong -- the stock
Settings app missed every frame's deadline on the rasteriser -- and the
correction is noted in place rather than quietly swapped, since the old
figure has been quoted between sessions today.

Also records which jank column to read, from the peer session's measurement
of a change whose modern figure barely moved while its legacy figure fell by
five sixths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:26:55 -04:00
irisandClaude Opus 5 098433681e Match an AVD name as a whole argument, never as a prefix
`emu up` kills a stray process for its own AVD first, with `pkill -f
"[e]mulator.*-avd $avd"`. An AVD name is a prefix of every longer one, so
`-avd ai-app` matched `-avd ai-app-2`: starting one checkout's emulator
silently killed another checkout's. That is the exact interference this tool
exists to prevent, and from the other side it looked like an emulator dying
on its own -- twice on 2026-08-31 before the pattern was the suspect, with
nothing in its log but a graceful shutdown nobody had asked for.

`emu list` had the same hazard in its `index($0, want)`, where the cost is
quieter: with both up it reported ai-app-2's resident size against ai-app,
which is a number that looks entirely plausible.

`running_avds` and `avd_serial` were already exact matches, which is what
made this hard to see -- the rule was right in two places out of four.

Verified with both emulators running: the ai-app pattern now matches only
ai-app, and `emu list` reports the two sizes separately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:17:13 -04:00
irisandClaude Opus 5 1d71730b56 Render on the GPU with no display, through Xwayland
This VM sees the host's 7900 XT through virtio-gpu and renderD128 is
readable without the video group, so the software rasteriser was never
necessary. What was in the way is that the emulator's `-gpu host` renderer
speaks GLX: with no DISPLAY it cannot open a context at all
("GlxEnginegetDefaultDisplay: Failed to open display 0") and silently falls
back.

So `emu up` now starts a headless sway whose only job is to provide an
Xwayland, and Xwayland's GLX runs on the render node. The guest's GLES
reports `virgl (AMD Radeon RX 7900 XT)` afterwards. One compositor per
machine, on a named socket so a second `emu up` reuses it; no seat, no DRM
master, no card node.

Scrolling a list in the stock Settings app: 25.5% janky frames and 8 slow
UI-thread frames on software, 3.3% and 0 on the GPU. An emulator also costs
1.1 GB less resident (2.9 GB against 3.9 GB), which is real headroom on a
machine that only takes two.

The reason this is worth the machinery is measurement rather than speed.
Before it, frame timings from in here said nothing about an app at all --
ai-app scrolled *better* than Settings, because both were bounded by the
rasteriser rather than by anything either was doing.

Host Vulkan is off in `GPU_HOST_FEATURES`, and that one is a bug rather than
a preference: gfxstream asks Venus for a memory type for its ColorBuffers
that Venus does not offer, and the emulator dies before adb sees it. GLES is
unaffected. Retry it whenever mesa moves; the failure is loud.

A missing sway, or one that will not start, says so and falls back to
software exactly as before -- verified by moving the config aside.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 20:29:02 -04:00
irisandClaude Opus 5 fe97956632 Take ui-trace to the adb beside it
ui-trace found adb on PATH, and this machine's ambient PATH puts the SDK's
own platform-tools ahead of ~/.local/bin -- so unless a project's
android-env.sh had been sourced into that same shell, ui-trace drove the
device through the unwrapped adb and failed with "more than one device" as
soon as a second emulator was up. The wrapper installed beside it is the one
that knows which emulator this checkout means, so it is now the first
candidate rather than a coincidence of PATH order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 13:12:44 -04:00
irisandClaude Opus 5 e379edfc1b Aim at the right emulator, and measure the right process
Two corrections from driving these against a live machine. A bare adb call
made from a checkout whose own emulator is down was still handed through to
whatever single device was attached -- which is another session's emulator,
installing over the app they are looking at and taking their foreground, and
looking like it worked. It now refuses and says which serials belong to whom.
A physical phone belongs to no checkout and is left alone, which is why this
asks for AVD names rather than counting devices.

`emu list` was reporting 6 MB for a 3.8 GB emulator: it matched on a -port
that is not in the process's command line at all, and the line it did match
was the shell running the ps.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 13:09:46 -04:00
irisandClaude Opus 5 5d77599f3f Collect the emulator tooling into one repo
ui-trace, the adb wrapper and its device recorder were loose files under
~/.local, and each Android checkout carried its own copy of the same
emulator boot sequence. This puts them together, with an install script that
symlinks them back so editing the repo is editing what runs.

Two things are new rather than moved. `emu` is the emulator lifecycle --
name, serial, list, up, down -- keyed on the AVD named after the enclosing
checkout, which is the rule that lets several sessions work here at once;
and `adb` now fills in `-s` from that same rule, because with two emulators
attached a bare `adb shell pm list packages` comes back empty rather than
failing, which reads as the app being uninstalled rather than the question
being ambiguous.

`emu up` refuses when the machine has no room. On 2026-08-30 an emulator
started with 2.8 GB available invoked the OOM killer, and what it took was
not the emulator that had just started: it walked the user slice and killed
pipewire, dbus-broker and another session's emulator first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 13:06:17 -04:00