Repository navigation
feat: Add annotated and size limited snapshots to flame_cli - #4138
Merged
Merged
Conversation
spydon
added this pull request to stack #4135
October 24, 2026 09:50
spydon
removed this pull request from stack #4135
October 24, 2026 10:30
spydon
added this pull request to stack #4140
October 24, 2026 10:30
spydon
force-pushed
the
feat/cli-annotated-snapshot
branch
from
October 25, 2026 09:47
f1479a7 to
ef8c3f9
Compare
spydon
force-pushed
the
feat/cli-annotated-snapshot
branch
4 times, most recently
from
October 25, 2026 19:37
6a49254 to
8ab8195
Compare
Adds a `flame wait` command to `flame_cli` that blocks until a condition holds in the running game, or until a timeout is reached, so that scripts and AI agents don't need sleep loops and guessed timings. For example, tap a button and then wait until the game over overlay is shown: ```shell flame input tap 400,300 flame wait overlay GameOver ``` The conditions are subcommands, like `overlay show` and `input tap`: - `wait component <type pattern>`: a component whose type matches the regular expression exists, matched case insensitively like `tree --filter`. `--id` matches a single component, `--count` waits for an exact number of matches, `--at-least` for a minimum number, and `--gone` until nothing matches, which covers waiting until a component is removed (`flame wait component --id 123 --gone`). `--where "position.x > 100"` compares a numeric attribute from the same data that `tree` and `inspect` return (`position.x`, `position.y`, `size.x`, `size.y`, `scale.x`, `scale.y`, `angle` and `priority`), and can be given several times. - `wait overlay <name>`: the overlay is shown, or hidden with `--hidden`. An unregistered overlay fails right away with `65`. - `wait paused` and `wait resumed`: the game loop is paused or running. All of them take `--timeout` (10 seconds by default, `0` checks once and works as an assertion), `--interval` (0.2 seconds by default) and `--json`. When the condition holds, the command prints what matched, for example the matching components with their ids and attributes, and exits with `0`. When the timeout is reached first it says what it found instead, for example `Timed out after 10 seconds waiting for a component with a type matching enemy, found 0.`, and exits with `75` (`ExitCode.tempFail`), which is added to the documented exit codes. A game that doesn't answer a check in time, for example because it is stopped at a breakpoint, also fails with `75` instead of blocking the command past its timeout. The command polls the existing `getComponentTree`, `getOverlays` and `getPaused` service extensions over one connection, so nothing changes on the game side. The clock is injectable through a `WaitClock` passed to `FlameCommandRunner`, so the tests are fast and deterministic. To support this: - `FlameCommand` takes an optional `connect`, and the runner passes its connector to the wait command, so that commands talking to a game can be tested with a fake connection. - `FlameConnection.call` now reports a closed connection, which is what happens when the game stops while a command is waiting, as `The game is no longer running` with exit code `69`, instead of the generic `70` error. `FlameConnection.withService` creates a connection from an existing `VmService`, which the tests use. - `findComponentNodes` in `component_tree.dart` returns the matching nodes of the tree. The command is documented in the CLI docs and the readme.
spydon
force-pushed
the
feat/cli-annotated-snapshot
branch
from
October 25, 2026 19:38
8ab8195 to
c47b4da
Compare
spydon
enabled stacked auto-merge (squash)
October 25, 2026 19:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds three options to
flame snapshot, so that AI models can match what they see in a snapshot with the component tree and get images that stay within their size limits:--annotate(-a) draws the outline of every visiblePositionComponenton the image, labeled with its id (the same id thatflame treeprints) and its type, and prints the annotated components asPlayer (id: 220731871) box 368,268,64,64, with the box in the pixel coordinates of the image, before the path of the image. Text only consumers get the same mapping that way.--filter(-f) is a case insensitive regular expression on the type, likeflame tree --filter, so that only the matching components are annotated. It needs--annotate.--max-size(-m) limits the longest side of the image to that many pixels. The game lowers the pixel ratio before rendering instead of resizing the image afterwards, so the aspect ratio is kept, nothing is cropped and the image stays sharp. When the ratio was lowered the command prints the size and the pixel ratio that was used, so that pixel coordinates can be converted back to canvas coordinates for the input commands.All three work for the whole game,
--world,--rectand--component, at any--pixel-ratio.Drawing on the game side
The annotations are computed and drawn on the game side, while the snapshot is rendered, in the new
packages/flame/lib/src/devtools/snapshot_annotations.dart. ThegetGameSnapshotandgetComponentSnapshotextensions take the newannotate,annotationFilterandmaxSizeparameters, and returnpixelRatioand, when annotating,annotationswith theid,nameandboxof each component.The game is the only place that knows all the transforms involved: the transforms of every position component up the tree, the viewfinder (position, zoom, angle), the viewport (position, anchor and the scale of a
FixedResolutionViewport), and the snapshot mode itself (canvas, world rect or component bounds, and the pixel ratio). Doing it on the CLI side would mean sending all of that over and duplicating the camera math in a second place that has to be kept in sync. On the game side the outline of a component is found by mapping its four corners up the tree withpositionOf,Viewport.localToGlobalandCameraComponent.localToGlobal, which are the same functions that the camera uses, so the outlines line up with the rendering by construction. A world is mapped through the camera that renders it, the backdrop of a camera through its viewport, and the box of a world, viewport or backdrop component is checked against that viewport, so components that the viewport clips away are left out. Components that are hidden withHasVisibility, that have no area or that are outside of the image are left out too, and the outlines are drawn as the rotated quad of the component, while the printed box is its axis aligned bounding box.The annotations are drawn in image pixels after the game is rendered, so the labels have the same legible size at every pixel ratio. Every outline has a dark border under a colored line, every label has a solid colored background with dark text, and a label is placed at the first of a few positions around its box that doesn't cover the labels placed before it, or at the one that covers them the least.
Older Flame versions
An older game ignores the unknown parameters, so the CLI checks that the response has
annotationswhen--annotatewas passed andpixelRatiowhen--max-sizewas passed. Otherwise it fails withThe running game does not support --annotate, update Flame to the latest version.and exit code 69, like a missing extension, before anything is written.Testing
The geometry is tested directly instead of through goldens, so the tests don't depend on font rendering: through a zoomed and moved camera, a rotated camera (compared against the pixels of the rendered snapshot), a
FixedResolutionViewport, viewport components, a camera backdrop (compared against the rendered pixels), nested and scaled components, viewport clipping,--worldwith a rect and a pixel ratio, single components, and the filtering rules. The maximum size is tested to never be exceeded through rounding. On the CLI side the options that are sent to the game, the output, the usage errors and the error for an older game are tested with a fake connection.I also verified it end to end with a small game on
flutter run -d flutter-testerwith a rotated and zoomed camera, a fixed resolution viewport, a nested child and a viewport component, and checked that the outlines in the produced PNGs line up for the whole game,--max-size,--worldand--rect --pixel-ratio 2.The options are documented in the CLI docs, the service extensions in the debug docs, and there is an example in the readme.
Checklist
docsand added dartdoc comments with///.examplesordocs.Breaking Change?
Related Issues
Part of #4131