Quest: Warrior Dragon · Autonomous Agent Governance
AI Playthrough
The robos-jrpg engine plays the whole game — King Lorik's throne room to the Dragonlord — with nothing a human player does not have: real controller inputs and what is on the screen. Every milestone becomes a video, a save checkpoint and a screenshot, and a GNOME tray icon lets you follow a two-hour run without watching it.
Why
Reachable is not the same as beatable
The BFS proof of viability walks every connected (map, col, row) state and proves every chest, stair, door and the Dragonlord's altar can be reached. It says nothing about whether a hero with the gold, levels, spells and herbs the game actually hands out can get there alive, afford the Broad Sword, survive Charlock's basement, or beat the Dragonlord's second form. Those are economy, combat-formula and inventory questions, and the only honest way to answer them is to play the game.
So the game plays itself. PlayGameBrain.gd is a "play the game" mode of the in-engine AIEngine.gd: it presses move_up/down/left/right, ui_accept and cmd_menu, and reads state through the same HTTP IPC web service the E2E tests use (GameControlServer.gd, GET /api/v1/state). No teleports, no stat edits, no peeking at hidden RNG. If it cannot finish, the game has a bug.




Architecture
Driver, IPC web service, brain, dashboard
Four pieces, three of which only talk HTTP and JSON. The Python driver is "the user": it boots Godot on an Xvfb display, enables the mode with one IPC command and from then on only observes. The brain lives inside the engine and is the only thing that presses buttons. The dashboard reads a live.json the driver rewrites once a second and asks the game for a screenshot every 1.5 s.
flowchart LR
subgraph Driver["tests/e2e/play_full_game.py (Python)"]
D1[boot Godot on Xvfb]
D2[start_play_game]
D3[poll /api/v1/state + get_milestones]
D4[assert invariants
stall-timeout watchdog]
D5[ffmpeg per-milestone video
export_save_state checkpoint
screenshot]
D6[live.json every 1 s
playthrough.json]
end
subgraph Godot["Godot 4 · games/quest-warrior-dragon"]
W["GameControlServer.gd
GET /api/v1/state
POST /api/v1/command"]
B["PlayGameBrain.gd
(AIEngine play-the-game mode)"]
R["RoutePlanner.gd"]
M["data/v1/milestones.json
70 ordered objectives"]
G["Game scenes · CombatEngine · PlayerState"]
B -->|real inputs| G
G -->|state| B
B --> R
M --> B
W <--> B
W <--> G
end
subgraph Dash["RobOS Game Playthrough app (packages/game-playthrough)"]
T["GNOME tray icon
progress ring + menu"]
V["Dashboard window
live state · screenshots · milestone cards"]
end
D2 -->|POST command| W
D3 -->|HTTP| W
D6 -->|reports/playthrough/live.json| V
D5 -->|videos/NN-id.mp4
checkpoints/NN-id.json| V
V -->|capture_screenshot / stop_play_game| W
V --> T
Milestone registry
tools/gen_milestones.py derives data/v1/milestones.json from the chest, shop, quest and level-curve registries: every weapon, armor and shield purchase, every chest, each quest item, level gates before dangerous zones, the Dragonlord and the ending. Each entry says where to stand, which command to use, the min_level and gold needed, and a done_when predicate the brain checks against game state.
The brain
PlayGameBrain.gd always seeks the next unfinished milestone. RoutePlanner.gd plans the multi-map route (stairs, doors, bridges, the Rainbow Bridge) over the real walkability grids; the brain walks it one tile at a time with the same stride inputs the demo AI uses.
Survival policy
Kill-before-die battle maths from combat_formulas.json; zone-aware heal thresholds (survive two worst-case hits from the local encounter pool); tiered HEAL / herb / HEALMORE; inn trips only when out of heal MP and herbs; herb restock from every item shop; MP conservation inside Charlock and HEALMORE-only healing against the Dragonlord. NES pack rules hold: eight tool slots, six per stack, no dropping.
Deadlock watchdog (brain)
A wall-clock progress watchdog fires after 90 s with no change in milestone, battles, level, gold, map, tile, herbs or HP. It drops the current objective and plan, blacklists an unreachable supply shop, and after three stalls on one milestone skips it if optional or fails the run.
Deadlock watchdog (driver)
--stall-timeout (default 600 s) ends a run whose done-count, battles, level, gold and EXP stop changing with status stalled, a screenshots/stall.png and the tail of the brain's event log, so a hung run never burns the night.
Invariants
The driver asserts things a real player could never do: the hero is never standing on water or mountains, HP is never negative, and no input loop repeats forever. Violations are recorded in playthrough.json and shown in the dashboard.
The driver
One video and one checkpoint per milestone
A single two-hour recording is nearly useless for review. In --stepped mode the driver cuts the ffmpeg capture at every milestone boundary into reports/playthrough/videos/NN-<id>.mp4, asks the game to export_save_state into checkpoints/NN-<id>.json and takes screenshots/NN-<id>.png. Trivial milestones (a chest two tiles away) are one-second clips; level gates and expensive purchases are the five-minute grinds. Because every milestone has a checkpoint, --only N loads checkpoints/(N-1)-*.json with load_save_state and replays just that milestone, so a fix can be re-verified in seconds instead of two hours.
cd games/quest-warrior-dragon
python3 tests/e2e/play_full_game.py # whole game, headless, ~2 h at the default time scale
python3 tests/e2e/play_full_game.py --until 18 # stop after milestone 18 (Copper Sword)
DW_HEADED=1 python3 tests/e2e/play_full_game.py # watch it on the desktop
python3 tests/e2e/play_full_game.py --record # + playthrough.mp4 and a 12x time-lapse
python3 tests/e2e/play_full_game.py --stepped # one video + checkpoint + screenshot per milestone
python3 tests/e2e/play_full_game.py --stepped --only 42 # replay milestone 42 from checkpoints/41-*.json
python3 tests/e2e/play_full_game.py --stepped --from 42 # resume from checkpoint 41 to the ending
python3 tests/e2e/play_full_game.py --stall-timeout 300 # driver-side deadlock detector (default 600 s)
python3 tests/e2e/play_full_game.py --dashboard off # stepped/recorded runs start the tray + dashboard by default
# Regenerate the milestone registry after changing shops, chests or quests
python3 tools/gen_milestones.py
| Artifact | Path under tests/e2e/reports/playthrough/ | Written |
|---|---|---|
| Run report | playthrough.json | Status, battles, deaths, elapsed, violations, per-milestone timings; at the end of the run |
| Live feed | live.json | Once a second: status, done count, stats, battle, objective, recent AI inputs, event log, milestone list |
| Milestone video | videos/NN-<id>.mp4 | At each milestone boundary in --stepped mode (_current.mp4 while recording) |
| Checkpoint | checkpoints/NN-<id>.json | Full save state via export_save_state: level, HP/MP, gold, equipment, items, opened chests, unlocked doors, quest flags, map and tile |
| Screenshot | screenshots/NN-<id>.png, stall.png, final.png | At each milestone, on a stall, and when the run ends |

13-buy-copper_sword: the brain navigates a real shop menu.
15-chest-garinham-1f-garc1 (17 min): grinding Drakees before Garinham's first chest.
10-level-3: the reward text the brain waits through with ui_accept.
42-return-gwaelin: HURT picked from the battle spell list against a Druin.Following a run
The tray icon and the live dashboard
A two-hour autonomous run has a visibility problem: you want to know at a glance whether it is still alive, how far along it is, and what it is doing, without a terminal scrolling JSON. The RobOS Game Playthrough app (packages/game-playthrough) is a small Electron app that stepped and recorded runs start automatically (--dashboard on|off|auto). It stays open after the run; quit it from the tray menu.

live.json with the menu the app builds.Tray icon
A 32-px progress ring drawn on a canvas and pushed to the tray whenever status or done-count changes: cyan while running, green when done, grey while waiting, red on a crash or stall. Its context menu, rebuilt every 3 s, shows:
- One summary line: status · done/total milestones · level · HP · gold · battles · deaths
- The current milestone (
▶ #47 Buy Magic Armor at Rimuldar) and the brain's current objective ("grind for level 12 before …") - Show dashboard, Open latest milestone video, Open videos folder, Open reports folder
- Stop run (sends
stop_play_gameover IPC; enabled only while the driver is alive) and Quit dashboard
Click the icon to toggle the window; the tooltip carries the same summary line.
Live view
Header
Status pill (RUNNING, DONE, STALLED, CRASHED), elapsed time and time scale, a progress bar over the 74 milestones, and stat chips: level, HP, MP, gold, EXP, ATK/DEF, battles, deaths, scene and tile. The "Now:" line names the current milestone and the brain's sub-objective.
Live game
A screenshot refreshed every 1.5 s through capture_screenshot (it writes live.png in the reports folder), with the scene and map key beside the heading.
Battle
Current monster and its HP, monster ATK/DEF, hero HP/MP and whether STOPSPELL landed, straight from /api/v1/state's battle block.
Equipment & pack
Weapon, armor, shield and the full pack with stacks collapsed (Herb ×5), so you can see the NES eight-slot rule and herb restocks working.
AI inputs
The last ~20 inputs the brain pressed, including stride targets (move_up(stride:9->100,16)) and the current stride target tile.
Brain event log
The brain's own event log (L11 cast: hurt vs scorpion (hp 44/59 mp 12), field heals, inn trips, watchdog stalls, skipped milestones) plus any invariant violations the driver recorded.
Milestone cards
The left column lists all 70 milestones with their type and whether a checkpoint exists; "follow run" keeps the current one in view. Every finished milestone is a clickable card that plays its video, shows the save checkpoint taken right after it (location, level, EXP, gold, HP/MP, ATK/DEF, equipment) and the milestone screenshot. Reveal video opens the file in the file manager; Replay command copies the exact play_full_game.py --stepped --only N command line.




broad_sword equipped with 746 gold left.# Run the dashboard on its own against any reports folder / game port
cd packages/game-playthrough && npm install
npx electron --no-sandbox . --reports ../../games/quest-warrior-dragon/tests/e2e/reports/playthrough --port 19260
# Debug aids for headless agents
npx electron --no-sandbox . --snapshot out.png # write a PNG of the window after 5 s
npx electron --no-sandbox . --select 44 --snapshot out.png # open milestone 44's card first
What it found
Every bug the brain hit was a game bug
The rule in games/quest-warrior-dragon/AGENTS.md is "fix the game, not the brain", and it held. Teaching the AI to finish the game surfaced defects that neither the BFS audit nor the chained Cucumber checkpoints could see, because they only show up when a hero with the real economy and real stats tries to progress:
- Charlock 1F exit gate unwalkable — reachable on the BFS grid, but the AI could never leave the castle after Erdrick's Sword.
- Kol NPC blocking the item-shop doorway — a wandering villager parked in a one-tile door and deadlocked the herb restock; the watchdog caught it, the fix capped shop retries and moved the NPC.
- Consumables not stacking — herbs filled all eight tool slots, so chests with quest items were skipped under the NES "no dropping" rule.
- Missing level-up curves and incorrect flee factors and spell resistances — the hero either could not reach a level gate or kept losing to monsters that should have run.
- Boss fights not syncing the pack, duplicate chests, a Cantlin item shop with no stock, and Wings doing nothing on the overworld (the brain uses them to fly home from Charlock when its MP commit bar is short).
- Magic Armor tie rule — Magic Armor and Erdrick's Armor compared equal, so the brain kept re-buying; fixed in the equipment comparison, together with a death-adaptive grind policy (each death outside grinding raises the current milestone's required level by one).




Gallery
Milestone screenshots from a live run
Each image is the screenshots/NN-<id>.png the driver takes the moment a milestone's done_when predicate turns true. Click any image to enlarge. Every image on this page comes from one uninterrupted run that cleared all 74 milestones, and the milestone numbers in the captions are that run's.
























Battles, from the videos












Results
What a passing run looks like
With the game data fixed, the brain clears all 74 milestones and beats the Dragonlord at level 21 in a single Charlock expedition. The run that produced every image on this page took 2 h 34 min at time-scale 8, with 2,621 battles and 6 deaths, and you can watch all of it on YouTube. The playthrough.json report, the per-milestone videos and checkpoints are the proof of work; the dashboard is how you read it.