Autoplay
The QA harness: a script that plays a mission through, tick by tick, against the real game, and a trace of what happened.
Location
| Header | src/GalaxyEggbert/Game/Autoplay.hpp (its top comment is the authoritative description of the script language) |
|---|---|
| Sources | src/GalaxyEggbert/Game/Autoplay.cpp; host side GalaxyEggbertGame::EnableAutoplay() in src/GalaxyEggbertCNA/GalaxyEggbertGame.cpp and StepAutoplay() in src/GalaxyEggbertCNA/GalaxyEggbertGameMissions.cpp |
| Namespace | GalaxyEggbert::Game |
| Engine dependency | None. It includes only GalaxyEggbert/Def/GamePhase.hpp and the standard library, so VerifyAutoplay tests it without CNA. |
Purpose
The game's Play path is deterministic at a fixed 1/60 s tick, so a playthrough written as a script
reproduces exactly, run after run. Autoplay parses such a script and, once per tick, is
given a snapshot of the game's state (AutoplayState) and returns the keys to hold
(AutoplayKeys). It steers Blupi to points, waits for lifts, crates, patrols and hazard
cycles, types cheat words, checks expectations and writes a trace. A command that runs out of
ticks, or Blupi dying, ends the run as a failure. As of 2026-11-10 there is a script for every
mission (tools/autoplay/worldNNN.txt), a chain per theme world from its hub, and
tools/autoplay/campaign.txt, which plays a new game through the whole campaign.
Collaborators
Uses
Def::GamePhase(thephasecondition takes a phase name).
Used by
- GalaxyEggbertGame:
StepAutoplay()builds theAutoplayStatefrom BlupiController, InteractionSystem and WorldRuntime (lifts, enemies and projectiles found through the object registry's semantic kinds) and turns the returned keys into a keyboard state: arrows, Left Ctrl (jump), Space (action), Left Shift (crouch), Right Shift (look up) and the letter keys. tools/autoplay.sh,tools/autoplay_all.sh,tools/play_pass.sh,tools/record_demo.sh(a demo can be recorded from a script).
Key members
A selection of the public API declared in src/GalaxyEggbert/Game/Autoplay.hpp.
| Member | Signature | What it does |
|---|---|---|
Load | bool Load(const std::string& path, std::string& error) | Reads and parses a script file; includes resolve against the file's own directory. On failure error names the file and line. |
Parse | bool Parse(std::string_view text, std::string& error) | Parses script text; includes resolve against the current directory. |
Step | AutoplayKeys Step(const AutoplayState& state) | One tick: the keys to hold now, given the state after the previous tick. |
Finished / Passed | bool Finished() const noexceptbool Passed() const noexcept | The script has ended; it ended with no failure. |
TakeLog | std::string TakeLog() | The trace lines written since the last call (each prefixed t=<tick>). |
WantsShot | bool WantsShot() const noexcept | Whether the frame of this tick should be saved (shots N). |
AutoplayState | struct AutoplayState | Tick, position (raw grid), yaw, ground/dead/ghost, animation, phase, mission, counters (treasures, eggs, lives, keys, dynamite), vehicle, cycle positions (blitz, temp, crush), switches pressed, saws turning, exit reached, and the lists of lifts, crates and enemies. |
AutoplayKeys | struct AutoplayKeys { bool up, down, left, right, jump, action, crouch, lookUp; char letter; } | What to hold this tick. |
Script language
One command per line; # starts a comment. Positions are raw grid coordinates (as in
.vwr files and --spawn; a cell's centre is at its integer value); yaw in degrees,
0 facing -Z and 90 facing +X. Commands that wait default to at most 1800 ticks (max N).
| Command | Meaning |
|---|---|
wait N | No keys for N ticks. |
hold N KEY... | Hold keys for N ticks: up down left right jump action crouch lookup. |
type WORD | Type lowercase letters, each followed by a release tick (cheat words). |
face DEG | Turn on the spot until facing DEG. |
go X Z [jump D] [tol T] [max N] [until COND] [with KEY]... | Turn towards (X, Z) and walk until within T (default 0.25) or until COND; with jump D jump once when D or less from the point; with KEY holds another key too. |
fly X Y Z ... | A go that also holds height Y (helicopter, overcraft), done within 0.3 of the height. |
until COND [max N] | No keys until COND holds. |
lift X Y Z, crate X Y Z [tol T] | Wait until a lift platform would carry Blupi at that point, or a crate stands there (default tolerance 0.05). |
clear X Y Z R [lane x|z], near X Y Z R [lane x|z] | Wait until no enemy, or an enemy, is within R: time a pass by a patrol. |
expect COND | Record a pass or a failure. |
log TEXT, trace N, shots N | Write to the trace; log the position every tick for N ticks (otherwise every 60); save the next N frames. |
mortal | From here on a death is written to the trace instead of failing the run. |
end, include FILE | Stop; insert another script (path relative to this script, nesting at most 8 deep). |
A condition is FIELD OP VALUE with OP one of = != < <= > >=. Fields:
x y z yaw tick mtick mission treasures eggs lives keys dynamite vehicle ground dead ghost anim
exit blitz temp switches saws crush phase. tick counts from the script's start,
mtick from the current mission's start (use it in anything that can run inside a chain).
vehicle is 0 none, 1 helicopter, 2 jeep, 3 tank, 4 skateboard, 5 overcraft.
phase takes a name: None First Wait Init Play Pause Lost Win Trial MainSetup PlaySetup
Resume Ranking Editor.
vehicle, but
the field list in the header comment of Autoplay.hpp does not mention it (as of 2026-11-10).
The mortal command is in the header but not in the handbook's summary table.How it is used
The start of tools/autoplay/world011.txt, a real script:
# Mission 011 played through (plans/briefs/world011.md).
wait 30
expect mission = 11
# A: walk to the pillar, jump onto it for treasure 1.
go 65 56
go 65 54 jump 1.2
until treasures = 1 max 120
...
lift 58 10 34 max 400
go 58 34 tol 0.15
until y >= 12.95 max 300
The game runs a script with GalaxyEggbert --mission N --autoplay <script> (add
--spawn x,y,z[,yaw] to start elsewhere, --galaxy to play the selected player's
galaxy). It writes autoplay_trace.txt in the working directory, draws only one frame in
30 unless a shot is wanted, and exits with code 0 when the script passed, 1 when it failed. By
default it runs as fast as the machine allows; the result is the same tick for tick.
| Setting | Effect |
|---|---|
AUTOPLAY_REALTIME=1 | Pace at 60 Hz (for watching); results do not change. |
AUTOPLAY_SHOTS=N | Save a frame every N ticks into autoplay_shots/. |
GALAXY_RESUME_SELFTEST=<ticks> | Exercise the resume replay at that tick and fail the run if the state differs. |
The wrapper scripts:
tools/autoplay.sh <build-dir> <script> <mission> [game arguments]: runs the game under its own Xvfb display from a run directory (AUTOPLAY_RUN=<dir>to choose it) with symlinked assets, a freshsavedata.txtandGALAXY_USER_DIRset to that directory; silent unlessQA_SOUND=1;AUTOPLAY_WORLDS=<dir>substitutes theworlds3ddirectory. Prints the trace and returns the game's exit code.tools/autoplay_all.sh <build-dir> [worldNNN ...]: every mission script (or the named ones), one line per mission, exit code 1 if any failed. A script whose first line saysBLOCKEDis listed and skipped.tools/play_pass.shruns them in parallel lanes and lays kept frames out as contact sheets.
Rules and invariants
- Keep it engine-independent: no CNA or cna-engine include in
Autoplay.{hpp,cpp}. - Scripts depend on gameplay timing (20 Hz tick tables of lifts, crates and creatures). A gameplay change can break a script with no compile error; if one fails after an engine change, first check the mechanic against the reference, and give a real defect a test before "fixing" the script.
- Use
mtickor a cycle field (temp,blitz,crush), nevertick, for timing inside a chain.
Tests
VerifyAutoplay(tools/VerifyAutoplay.cpp): parses scripts and plays their commands against a simple stand-in Blupi (turning 135 degrees/s, walking 2.5 cells/s): steering, jumps, timeouts and deaths, expectations, lanes. The same executable also holds the ResumeLog format checks.- The playthroughs themselves (
tools/autoplay_all.sh) are run by hand after gameplay or world changes; they need a display and are not in ctest. The last recorded full pass of all 78 scripts is from 2026-11-07 (seedocs/handbook/04-testing.md).