← Engineering log
Games Autonomous Agents Proof of Work

The Engine Plays Quest: Warrior Dragon: A Milestone-Driven AI Playthrough with Per-Milestone Videos, Checkpoints and a Tray Icon

Proving every tile was reachable did not prove the game could be won. So we taught the robos-jrpg engine to play Quest: Warrior Dragon from King Lorik's throne room to the Dragonlord with real inputs only, cut the two-hour run into one video and one save checkpoint per milestone, added deadlock watchdogs on both sides of the IPC boundary, and put the whole thing in a GNOME tray icon so you can follow it without watching it.

The BFS proof of viability was the first verification layer for the Quest: Warrior Dragon port: a headless walk over every connected (map, col, row) state that shows every chest, door, staircase and the Dragonlord's altar is reachable from the throne room. It caught real routing bugs. It also said nothing about whether the game is beatable. Can a level-1 hero with 120 gold earn the 1500 for a Broad Sword before the Swamp Cave kills him? Does the pack ever fill up so that a quest chest has to be skipped? Does Charlock's basement let you out again after Erdrick's Sword? Those are economy and combat-formula questions, and the only honest test is to play.

Filmstrip: one frame from each of the 74 milestone videos of the winning run, with each clip's length
One frame from each per-milestone video of the winning run, with the milestone id and clip length. Chests take a second; digging up Erdrick's Token took 22 minutes of grinding first.

A brain that only wants the next milestone

PlayGameBrain.gd is a "play the game" mode of the in-engine AIEngine.gd. It has exactly the affordances a human has: move_up/down/left/right, ui_accept and cmd_menu, and the state the game exposes through its IPC web service (GameControlServer.gd, GET /api/v1/state). No teleports, no stat edits, no reading hidden RNG. What makes it finish the game is not cleverness but a registry: tools/gen_milestones.py derives data/v1/milestones.json, an ordered list of 70 objectives, from the chest, shop, quest and level-curve data: every weapon, armor and shield purchase, every chest, each quest item, a level gate before each dangerous zone, the Dragonlord and the ending. Each entry says where to stand, which command to use, what level and gold are needed, and a done_when predicate. The brain always seeks the next unfinished one.

  • Routing: RoutePlanner.gd plans multi-map routes over the real walkability grids (stairs, doors, bridges, the Rainbow Bridge); the brain walks them with the same stride inputs the demo AI uses.
  • Survival: 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 restocks at every item shop, MP conservation inside Charlock and HEALMORE-only healing against the Dragonlord.
  • NES rules: eight tool slots, six per stack, no dropping; a full pack skips a non-gold chest rather than cheating around it.
  • Death-adaptive grinding: every death outside a grind raises the current milestone's required level by one, so a run that gets unlucky becomes more careful instead of repeating the mistake.
Throne room: King Lorik tells how the Dragonlord stole the Ball of Light
Milestone #1. The run starts exactly where a new game starts.
Brecconary shop window with 112 gold
The brain works a real shop menu with ui_accept and the cursor keys.
Wolflord battle with the FIGHT / SPELL menu
A Wolflord: the battle policy has FIGHT and SPELL to choose from.
The ITEMS window in Erdrick's Cave: Torch x2, Leather Armor, Small Shield, Copper Sword
The ITEMS window. Herbs and torches stack to six; before that fix they filled all eight slots.

The visibility problem of a two-hour run

At time-scale 8 a full run takes about two hours and roughly 800 random battles. Nobody watches that, and a single two-hour MP4 is nearly useless when you want to know why milestone 42 took ten minutes. So the Python driver (tests/e2e/play_full_game.py) got a --stepped mode: it cuts the ffmpeg capture at every milestone boundary into videos/NN-<id>.mp4, asks the game to export_save_state into checkpoints/NN-<id>.json, and saves screenshots/NN-<id>.png. Because every milestone has a checkpoint, --only N loads checkpoint N-1 and replays just that milestone, so a fix to the Kol item shop can be verified in twenty seconds instead of ninety minutes. --from N resumes a run from a checkpoint to the ending.

Then the tray icon. Stepped and recorded runs now start tools/playthrough-dashboard, a small Electron app that reads the live.json the driver rewrites once a second. In the GNOME top bar it draws a 32-pixel progress ring (cyan running, green done, red crashed or stalled) whose context menu, rebuilt every three seconds, shows status, done/total milestones, level, HP, gold, battles, deaths, the current milestone and the brain's sub-objective, plus Open latest milestone video and Stop run (which sends stop_play_game over IPC). Click it and the dashboard window opens.

GNOME top bar with the Game Playthrough tray icon and its context menu after the winning run
The tray icon and its menu after the winning run: 74 / 74 milestones, level 21, 2,621 battles, 6 deaths.
Game Playthrough live view after the winning run: 74 / 74 milestones and the closing credits
The live view at the end of the winning run. Left: all 74 milestones with their type and whether a checkpoint exists. Right: the game screen, battle, equipment and pack, AI inputs and the brain's event log.

Every finished milestone in the list is a card. Click it and the dashboard plays that milestone's video, shows the checkpoint saved right after it (location, level, EXP, gold, HP/MP, ATK/DEF, equipment), the milestone screenshot, and a Replay command button that copies the exact --stepped --only N command line. Reviewing a run became: skim the filmstrip, click the milestone that looks slow, watch sixty seconds, read the checkpoint.

Milestone card #1 Hear King Lorik's quest with its video and the checkpoint: ThroneRoom, level 1
#1 Hear King Lorik's quest: a four-second clip and the first checkpoint.
Milestone card #31 Grind + Reach level 10 with its video and checkpoint: WorldMap (6,60), level 10, 617 gold
#31 Reach level 10 (STOPSPELL): 143 seconds on the overworld, checkpoint at 617 gold.
Milestone card #41 Rescue Princess Gwaelin with a Wolf battle frame and checkpoint: SwampCave (3,22), level 11
#41 Rescue Princess Gwaelin: the Swamp Cave clip and its checkpoint.
Milestone card #39 Grind + Buy Broad Sword at Rimuldar with its video and checkpoint: broad_sword, 746 gold
#39 Buy Broad Sword: the checkpoint confirms broad_sword equipped with 746 gold left.

Two watchdogs, one on each side of the wire

Autonomous runs do not fail loudly; they stall. The brain got a wall-clock progress watchdog: 90 seconds with no change in milestone, battle count, level, gold, map, tile, herbs or HP and it drops the current objective and plan, blacklists an unreachable supply shop, and after three stalls on the same milestone skips it if optional or fails the run. The driver got --stall-timeout (600 s by default): when the done-count, battles, level, gold and EXP all stop moving, it ends the run with status stalled, a stall.png and the tail of the brain's event log. The first thing the pair caught was a wandering villager in Kol who parked himself in the one-tile item-shop doorway and deadlocked the herb restock forever.

Kol village with the command menu open at a chest
Kol. The NPC that blocked the item-shop door now wanders elsewhere, and shop retries are capped.
Battle spell list with HURT selected against a Druin
HURT picked from the battle spell list. The battle policy casts when a spell is cheaper than the sword rounds it saves.

Every bug it hit was a game bug

The rule in the game's AGENTS.md is "fix the game, not the brain", and it held. The playthrough found the Charlock 1F exit gate unwalkable (reachable on the BFS grid, impassable in play), boss fights not syncing the pack, duplicate chests, missing level-up curves, incorrect enemy flee factors and spell resistances, a Cantlin item shop with no stock, Wings that did nothing on the overworld, consumables that refused to stack, and a Magic Armor / Erdrick's Armor tie that made the brain re-buy armor it already outclassed. None of these were visible to the BFS audit or to the chained Cucumber checkpoints, because they only appear when a hero with the real economy and real stats tries to progress.

Erdrick's Cave B2 in darkness with a one-tile torch radius
Erdrick's Cave in the dark. The brain buys torches before every dark dungeon until it knows RADIANT.
Swamp Cave: Princess Gwaelin asks to be taken back to her father
Gwaelin's cell at level 11.
Rimuldar: Thou dost find the Wings!
Wings from Rimuldar, the brain's way home from Charlock when its MP commit bar is short.
Northern shrine, the Staff of Rain chest
Staff of Rain in the northern shrine.

Results, and how to run it

With the game data fixed, the brain clears all 70 milestones and beats the Dragonlord at level 21–22 in one or two Charlock expeditions with at most a single death; the screenshots in this post are from a later run that cleared all 74 milestones with 2,621 battles and 6 deaths. The playthrough.json report, the per-milestone videos and the checkpoints are the proof of work. To try it: python3 games/quest-warrior-dragon/tests/e2e/play_full_game.py --stepped runs the whole game headless with the tray icon and dashboard; --until 18 stops after the Copper Sword, --only 42 replays one milestone, DW_HEADED=1 shows the game on your desktop. The dashboard also runs on its own against any reports folder (npx electron --no-sandbox . --reports ... --port ...) and has --snapshot out.png and --select N flags so headless agents can screenshot it, which is how the dashboard images in this post were taken.

Desert overworld while grinding for level 15
The desert on the way to level 15.
King Lorik: Thou hast rescued my daughter Gwaelin! I am forever grateful.
Gwaelin returned to King Lorik.