| name | dump-ebitengine-screen |
|---|---|
| description | Use this skill to capture a screenshot of a running Ebitengine app (Guigui, raw Ebitengine, anything that implements ebiten.Game) for visual debugging — for example to verify a layout or rendering fix without asking the user to run the app and paste a screenshot. Works on macOS where screencapture / AppleScript / EBITENGINE_SCREENSHOT_KEY + keystroke-injection tricks tend to fail because the example window doesn't take focus when launched from a sandboxed shell. Use whenever the user shares a screenshot showing a UI bug and you need to reproduce or verify it. |
| allowed-tools | Read, Edit, Write, Bash |
Patch the app's Draw to call ReadPixels on the screen image, write a
PNG to a path from an env var, and exit. This avoids macOS focus /
window-id problems with screencapture and the
EBITENGINE_SCREENSHOT_KEY keystroke flow.
- The user reports a visual bug ("items not rendered", "thumb in wrong position", "text overlapping") and you've made a candidate fix; you want to verify it without round-tripping a screenshot through the user.
- You need to inspect an intermediate state that takes a few ticks to settle (animations, scroll-jumps, layout convergence).
- The user just wants a thumbnail of their app — they can run it themselves.
- The behavior depends on user input (clicks, drags) — this skill captures a static frame after N ticks of the app running idle.
For a typical Ebitengine app it's a method on the ebiten.Game impl:
func (g *Game) Draw(screen *ebiten.Image) { ... }For Guigui specifically it's func (a *app) Draw(screen *ebiten.Image)
in app.go at the repo root, and the screen is shadowed — capture the
original screen (often kept as origScreen), not the offscreen
buffer.
Insert at the very end of Draw, after all existing rendering:
if path := os.Getenv("EBITEN_DUMP_SCREEN"); path != "" {
g.dumpDrawCount++
if g.dumpDrawCount >= 30 {
bounds := screen.Bounds()
img := image.NewRGBA(bounds)
screen.ReadPixels(img.Pix)
if f, err := os.Create(path); err == nil {
png.Encode(f, img)
f.Close()
}
os.Exit(0)
}
}Also:
- Add
dumpDrawCount intto the receiver struct. - Add
"image/png"and"os"to the import block (and"image"if not already there). - If
Drawshadowsscreen(e.g. Guigui assigns toscreenand keeps the original inorigScreen), callReadPixelson the original — it's the image actually presented to the window.
The 30-tick delay matters: scroll-jumps, animations, lazy measure / layout convergence all need a few ticks to settle. Bumping it is safe; lowering it usually captures the wrong frame.
If the bug only reproduces at a specific state, drive it from a place
that runs every tick (Build, Update):
r.table.Widget().JumpToItemByIndex(len(r.tableItems) - 1)If it requires user input (click, drag), this skill won't help.
go build -o /tmp/ebiten_target ./path/to/main/
EBITEN_DUMP_SCREEN=/tmp/screen.png /tmp/ebiten_target 2>&1 &
PID=$!
sleep 3
kill $PID 2>/dev/null
wait 2>/dev/null
ls -la /tmp/screen.pngUse /tmp/<name>.png (not $TMPDIR/...) — the sandboxed shell on macOS
doesn't always have $TMPDIR access for child processes.
Use the Read tool with the PNG path; it renders inline in the conversation.
When done, undo the edits. The patch is small and self-contained so the revert is clean. Don't leave the dump hook in committed code.
screencapture -l <window-id>plusosascript ... get id of windowdoesn't return Ebitengine's window id reliably. Don't waste time on it.EBITENGINE_SCREENSHOT_KEY=q+osascript ... keystroke "q"requires the example window to have focus. From a sandboxed shell it usually doesn't.nice(5) failed: operation not permittedlines in stderr are sandbox noise — they don't affect the dump.ReadPixelsreturns RGBA, soimage.NewRGBA(bounds)matches the buffer size; don't useimage.NewNRGBA.- If the app draws into an offscreen buffer and copies it to
screenat the end ofDraw, dump the final image (the one drawn toscreen), not the offscreen — otherwise you miss debug overlays and post-processing.