Kaiju AI Driver
You control a running kaiju game by taking screenshots and injecting
mouse/keyboard input through a localhost HTTP server compiled into the game. You
talk to it with curl. No authentication; it binds 127.0.0.1 only.
Preconditions
The game must be running and built with the ai_driver tag:
go run -tags ai_driver . # or: go build -tags ai_driver -o game . && ./game
On startup it logs AI Driver started addr=127.0.0.1:7777. Default port is
7777; override with the AI_DRIVER_PORT env var.
Confirm it is up before doing anything else:
curl -s http://127.0.0.1:7777/v1/health
If this fails to connect, the game is not running or was built without
-tags ai_driver. Ask the user to launch it that way; do not try to start it
yourself unless asked.
Coordinate & timing contract (do not violate)
- Coordinates are SCREENSHOT PIXELS, top-left origin (+x right, +y down).
Read a pixel straight off the screenshot you just looked at and send that exact
pixel. The server converts to the engine's logical points for you — never
multiply or divide coordinates yourself, even on a Retina display.
- Input takes effect over frames, not milliseconds. A click is a press then a
release on a later frame; typing is one character per frame. After acting, let
the frame settle so the screenshot reflects your input: pass
settle_frames
(POST) or ?settle=N (screenshot). settle_frames: 2 is a good default.
- Coordinates outside the framebuffer are rejected with
400 invalid_coordinate.
If the window may have resized, re-check /v1/state and re-screenshot.
Core loop
Learn the geometry (once, and again after any resize):
curl -s http://127.0.0.1:7777/v1/state
Returns window (logical size), framebuffer (screenshot pixel size),
scale (e.g. 2.0 on Retina), focused, and frame. The screenshot will be
framebuffer pixels; send coordinates in that space.
See the game — capture a PNG to a temp file, then Read it:
curl -s "http://127.0.0.1:7777/v1/screenshot?settle=1" --output /tmp/kaiju-shot.png
Then use the Read tool on /tmp/kaiju-shot.png to view the frame.
Reason about what to do and note the target pixel from the image.
Act and re-screenshot in one call — POST input with
return_screenshot:true and write the resulting frame straight to a file:
curl -s -X POST http://127.0.0.1:7777/v1/input \
-H 'Content-Type: application/json' \
-d '{"settle_frames":2,"return_screenshot":true,
"actions":[{"type":"mouse_click","button":"left","x":1280,"y":980}]}' \
--output /tmp/kaiju-after.png
Then Read /tmp/kaiju-after.png to verify the result.
Repeat until the task is done.
Resizing the window
Resize the game window (dimensions are logical points, the same space as
/v1/state's window). Rendering pauses for a couple of frames while the window
resizes and the swap chain rebuilds — the last frame is held on screen, so it's a
brief freeze, not a flicker. The call waits for the new size to become stable
before it returns, so the reported geometry (and any screenshot) already reflect
the resize; you don't need to poll, but re-read /v1/state if you cached sizes.
curl -s -X POST http://127.0.0.1:7777/v1/resize \
-H 'Content-Type: application/json' \
-d '{"width":1600,"height":900}'
It returns {"ok":true,"frame":...,"window":{...},"framebuffer":{...},"scale":{...}}
with the actual resulting size (the OS may clamp to screen or minimum bounds, so
the height you get back can be smaller than requested). Add "return_screenshot":true
(with --output FILE) to get the post-resize frame in one call, or
"settle_frames":N to wait extra frames before the read-back.
Closing the game
When you are finished, shut the game down gracefully through the API instead of
killing the process:
curl -s -X POST http://127.0.0.1:7777/v1/quit
It responds {"ok":true,"message":"host is shutting down"} and the game closes
its window and exits on its own a couple of frames later. Do not use pkill.
POST /v1/input reference
Body fields:
coordinate_space: "framebuffer" (default; screenshot pixels) or "window"
(logical points). Use the default.
settle_frames: frames to advance after the actions (default applied as-is;
use 1-2 so a returned/next screenshot reflects the input).
return_screenshot: if true, the response body is the resulting PNG (use
--output FILE); otherwise it is JSON {ok, frame_after, actions_run, warnings}.
actions: array, run in order, each spanning one or more frames:
| type |
fields |
effect |
mouse_move |
x, y |
move cursor |
mouse_down |
button, x?, y? |
press and hold |
mouse_up |
button, x?, y? |
release |
mouse_click |
button, x?, y?, hold_frames? |
press then release |
scroll |
dx, dy, x?, y? |
scroll wheel |
key_down |
key |
hold a key |
key_up |
key |
release a key |
key_press |
key, hold_frames? |
tap a key |
type_text |
text |
type a string (US-QWERTY) |
wait_frames |
frames |
idle N frames |
button: left, middle, right, x1, x2.
key: symbolic names, case-insensitive — Return/Enter, Escape/Esc,
Space, Tab, Backspace, Delete, Left/Right/Up/Down, A..Z,
0..9, F1..F12, Home, End, PageUp, PageDown, modifiers
(Shift, Ctrl, Alt, Cmd).
Examples:
# Type into a focused field, then submit
curl -s -X POST http://127.0.0.1:7777/v1/input -H 'Content-Type: application/json' \
-d '{"settle_frames":1,"actions":[{"type":"type_text","text":"Player1"},
{"type":"key_press","key":"Return"}]}'
# Press Escape and capture the result
curl -s -X POST http://127.0.0.1:7777/v1/input -H 'Content-Type: application/json' \
-d '{"settle_frames":2,"return_screenshot":true,
"actions":[{"type":"key_press","key":"Escape"}]}' --output /tmp/kaiju-after.png
Caveats
type_text is best-effort and assumes a US-QWERTY layout; symbols outside that
layout and IME input are not handled. For anything non-alphanumeric, prefer
explicit key_press actions. Unmapped characters are skipped and reported in
the JSON warnings field.
- If
/v1/state shows focused:false, injected input may be ignored — ask the
user to click the game window once to focus it.
- The screenshot is the last presented frame, so always use a
settle of 1-2
when you need to see the effect of input you just sent.
- The server only moves the in-game cursor/keyboard — it cannot drive other apps
or the OS, so its blast radius is the game window alone.
Debugging
curl -s http://127.0.0.1:7777/v1/help # list endpoints
curl -s http://127.0.0.1:7777/v1/state # geometry + focus + frame
1---2name: kaijuengine-aidriver3description: Drive an already-running Kaiju game through its built-in AI Driver HTTP server (localhost, enabled by the `ai_driver` build tag): capture screenshots of the game window and inject mouse/keyboard input (click, type, scroll, key presses), loop screenshot then reason then act then re-screenshot, and quit the game gracefully. Use this whenever the user wants to look at, inspect, interact with, control, or visually verify the running kaiju game — for example "screenshot the game", "click the start button", "type into the name field", "is the menu rendering correctly", or "check my UI change in the running app" — even if they don't mention ai_driver or curl. Prefer this over the generic run/verify skills for a kaiju game that exposes the AI Driver server, since it talks to the live process via curl rather than launching or rebuilding it. Requires the game built with the `ai_driver` tag.4---56# Kaiju AI Driver78You control a running kaiju game by taking screenshots and injecting9mouse/keyboard input through a localhost HTTP server compiled into the game. You10talk to it with `curl`. No authentication; it binds `127.0.0.1` only.1112## Preconditions1314The game must be running and built with the `ai_driver` tag:1516```17go run -tags ai_driver . # or: go build -tags ai_driver -o game . && ./game18```1920On startup it logs `AI Driver started addr=127.0.0.1:7777`. Default port is21`7777`; override with the `AI_DRIVER_PORT` env var.2223Confirm it is up before doing anything else:2425```26curl -s http://127.0.0.1:7777/v1/health27```2829If this fails to connect, the game is not running or was built without30`-tags ai_driver`. Ask the user to launch it that way; do not try to start it31yourself unless asked.3233## Coordinate & timing contract (do not violate)3435- **Coordinates are SCREENSHOT PIXELS, top-left origin (+x right, +y down).**36 Read a pixel straight off the screenshot you just looked at and send that exact37 pixel. The server converts to the engine's logical points for you — never38 multiply or divide coordinates yourself, even on a Retina display.39- **Input takes effect over frames, not milliseconds.** A click is a press then a40 release on a later frame; typing is one character per frame. After acting, let41 the frame settle so the screenshot reflects your input: pass `settle_frames`42 (POST) or `?settle=N` (screenshot). `settle_frames: 2` is a good default.43- Coordinates outside the framebuffer are rejected with `400 invalid_coordinate`.44 If the window may have resized, re-check `/v1/state` and re-screenshot.4546## Core loop47481. **Learn the geometry** (once, and again after any resize):4950 ```51 curl -s http://127.0.0.1:7777/v1/state52 ```5354 Returns `window` (logical size), `framebuffer` (screenshot pixel size),55 `scale` (e.g. 2.0 on Retina), `focused`, and `frame`. The screenshot will be56 `framebuffer` pixels; send coordinates in that space.57582. **See the game** — capture a PNG to a temp file, then Read it:5960 ```61 curl -s "http://127.0.0.1:7777/v1/screenshot?settle=1" --output /tmp/kaiju-shot.png62 ```6364 Then use the Read tool on `/tmp/kaiju-shot.png` to view the frame.65663. **Reason** about what to do and note the target pixel from the image.67684. **Act and re-screenshot in one call** — POST input with69 `return_screenshot:true` and write the resulting frame straight to a file:7071 ```72 curl -s -X POST http://127.0.0.1:7777/v1/input \73 -H 'Content-Type: application/json' \74 -d '{"settle_frames":2,"return_screenshot":true,75 "actions":[{"type":"mouse_click","button":"left","x":1280,"y":980}]}' \76 --output /tmp/kaiju-after.png77 ```7879 Then Read `/tmp/kaiju-after.png` to verify the result.80815. Repeat until the task is done.8283## Resizing the window8485Resize the game window (dimensions are logical points, the same space as86`/v1/state`'s `window`). Rendering pauses for a couple of frames while the window87resizes and the swap chain rebuilds — the last frame is held on screen, so it's a88brief freeze, not a flicker. The call waits for the new size to become stable89before it returns, so the reported geometry (and any screenshot) already reflect90the resize; you don't need to poll, but re-read `/v1/state` if you cached sizes.9192```93curl -s -X POST http://127.0.0.1:7777/v1/resize \94 -H 'Content-Type: application/json' \95 -d '{"width":1600,"height":900}'96```9798It returns `{"ok":true,"frame":...,"window":{...},"framebuffer":{...},"scale":{...}}`99with the *actual* resulting size (the OS may clamp to screen or minimum bounds, so100the height you get back can be smaller than requested). Add `"return_screenshot":true`101(with `--output FILE`) to get the post-resize frame in one call, or102`"settle_frames":N` to wait extra frames before the read-back.103104## Closing the game105106When you are finished, shut the game down gracefully through the API instead of107killing the process:108109```110curl -s -X POST http://127.0.0.1:7777/v1/quit111```112113It responds `{"ok":true,"message":"host is shutting down"}` and the game closes114its window and exits on its own a couple of frames later. Do not use `pkill`.115116## POST /v1/input reference117118Body fields:119120- `coordinate_space`: `"framebuffer"` (default; screenshot pixels) or `"window"`121 (logical points). Use the default.122- `settle_frames`: frames to advance after the actions (default applied as-is;123 use 1-2 so a returned/next screenshot reflects the input).124- `return_screenshot`: if `true`, the response body is the resulting PNG (use125 `--output FILE`); otherwise it is JSON `{ok, frame_after, actions_run, warnings}`.126- `actions`: array, run in order, each spanning one or more frames:127128 | type | fields | effect |129 |---------------|---------------------------------|--------|130 | `mouse_move` | `x`, `y` | move cursor |131 | `mouse_down` | `button`, `x?`, `y?` | press and hold |132 | `mouse_up` | `button`, `x?`, `y?` | release |133 | `mouse_click` | `button`, `x?`, `y?`, `hold_frames?` | press then release |134 | `scroll` | `dx`, `dy`, `x?`, `y?` | scroll wheel |135 | `key_down` | `key` | hold a key |136 | `key_up` | `key` | release a key |137 | `key_press` | `key`, `hold_frames?` | tap a key |138 | `type_text` | `text` | type a string (US-QWERTY) |139 | `wait_frames` | `frames` | idle N frames |140141- `button`: `left`, `middle`, `right`, `x1`, `x2`.142- `key`: symbolic names, case-insensitive — `Return`/`Enter`, `Escape`/`Esc`,143 `Space`, `Tab`, `Backspace`, `Delete`, `Left`/`Right`/`Up`/`Down`, `A`..`Z`,144 `0`..`9`, `F1`..`F12`, `Home`, `End`, `PageUp`, `PageDown`, modifiers145 (`Shift`, `Ctrl`, `Alt`, `Cmd`).146147Examples:148149```150# Type into a focused field, then submit151curl -s -X POST http://127.0.0.1:7777/v1/input -H 'Content-Type: application/json' \152 -d '{"settle_frames":1,"actions":[{"type":"type_text","text":"Player1"},153 {"type":"key_press","key":"Return"}]}'154155# Press Escape and capture the result156curl -s -X POST http://127.0.0.1:7777/v1/input -H 'Content-Type: application/json' \157 -d '{"settle_frames":2,"return_screenshot":true,158 "actions":[{"type":"key_press","key":"Escape"}]}' --output /tmp/kaiju-after.png159```160161## Caveats162163- `type_text` is best-effort and assumes a US-QWERTY layout; symbols outside that164 layout and IME input are not handled. For anything non-alphanumeric, prefer165 explicit `key_press` actions. Unmapped characters are skipped and reported in166 the JSON `warnings` field.167- If `/v1/state` shows `focused:false`, injected input may be ignored — ask the168 user to click the game window once to focus it.169- The screenshot is the last *presented* frame, so always use a `settle` of 1-2170 when you need to see the effect of input you just sent.171- The server only moves the in-game cursor/keyboard — it cannot drive other apps172 or the OS, so its blast radius is the game window alone.173174## Debugging175176```177curl -s http://127.0.0.1:7777/v1/help # list endpoints178curl -s http://127.0.0.1:7777/v1/state # geometry + focus + frame179```