Skip to content

Instantly share code, notes, and snippets.

@hajimehoshi
Last active May 6, 2026 14:52
Show Gist options
  • Select an option

  • Save hajimehoshi/a0f39858ac9b1ac6f5e9dcf26a688c35 to your computer and use it in GitHub Desktop.

Select an option

Save hajimehoshi/a0f39858ac9b1ac6f5e9dcf26a688c35 to your computer and use it in GitHub Desktop.
dump-ebitengine-screen
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

dump-ebitengine-screen skill

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.

When to use

  • 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).

When NOT to use

  • 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.

Recipe

1. Find the app's Draw method

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.

2. Add the dump hook

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 int to the receiver struct.
  • Add "image/png" and "os" to the import block (and "image" if not already there).
  • If Draw shadows screen (e.g. Guigui assigns to screen and keeps the original in origScreen), call ReadPixels on 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.

3. Optional — drive the app to the state to capture

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.

4. Build, run, capture

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.png

Use /tmp/<name>.png (not $TMPDIR/...) — the sandboxed shell on macOS doesn't always have $TMPDIR access for child processes.

5. View

Use the Read tool with the PNG path; it renders inline in the conversation.

6. Revert the patch

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.

Pitfalls

  • screencapture -l <window-id> plus osascript ... get id of window doesn'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 permitted lines in stderr are sandbox noise — they don't affect the dump.
  • ReadPixels returns RGBA, so image.NewRGBA(bounds) matches the buffer size; don't use image.NewNRGBA.
  • If the app draws into an offscreen buffer and copies it to screen at the end of Draw, dump the final image (the one drawn to screen), not the offscreen — otherwise you miss debug overlays and post-processing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment