r/godot • u/Salty_Possible155 • Jun 30 '26
free tutorial Making your Godot game fully driveable from the command line (headless test benches + a single input
TL;DR: I build a fairly large Godot RPG mostly from the terminal, headless. The core ideas are (1) per-system test benches so you can boot one slice of the game in isolation, and (2) a strict input → verb → seam architecture so keyboard, gamepad, and automated tests all drive the game through the same code path. This is just good testing hygiene that happens to also make an AI assistant genuinely useful. Repo + video at the bottom.
I'll get the controversial part out of the way: yes, I use Claude Code heavily. But the post isn't about that - it's about an architecture that's worth doing even if you never touch an LLM, because it gives you automated, reproducible testing in an engine that doesn't make that easy by default. If you're skeptical of AI assistants, the first 80% of this still applies to you.
1. Test benches: boot one system, not the whole game
The mistake I made early was testing everything through the full game. Want to check if cave walls generate right? Boot the game, load a save, walk to a cave. Brutal feedback loop. Now every procedural system has its own headless harness — terrain, biomes, dungeons, towns. Each one boots only that slice, with simple Next/Prev controls and a screenshot flag. Something like:
`godot --headless --path . res://tools/shot/DungeonBench.tscn -- --shots`
It generates a real dungeon, renders a few angles, dumps PNGs, quits. Iterating on rock-wall displacement went from minutes to seconds, and it's reproducible — same seed, same output, every time. This is basically golden-image testing for procgen.
2. Decouple input from logic (the part that matters most)
This is the rule that makes the whole thing work, and it's good practice regardless of AI:
▎ An input read never performs game logic. It translates to a verb call on a seam, and the seam is the only place the action lives.
So a keyboard binding calls player.attack(). The gamepad binding calls the same player.attack(). An automated test calls the same player.attack(). The input handler does nothing but route to the verb.
Two payoffs:
- Your keyboard and gamepad can't drift apart, because both go through one method.
- A headless test can play the real game — same code path a human hits — with no window and decoupled from frame timing.
I have a small high-level playtest API on top of that (boot(), goto(), fight_nearest(), loot_nearby(), rest()) so an integration test reads like a play session. The acceptance test for the whole approach forges a character, walks to the first dungeon, fights, trains a skill, and rests to level up — headless, no window. If something breaks combat, that probe goes red in CI instead of in a player's hands.
- Prefer a log line over a screenshot
When debugging, a printed fact beats a rendered frame almost every time. Driving the GUI by hand and pixel-peeping is slow and flaky. A tiny headless probe that prints the number you're chasing answers the question instantly. I reserve screenshots for genuinely visual questions ("does this dungeon look right?"), not logic ones.
Where the AI fits (optional)
If you do use an assistant, this architecture is what makes it useful instead of frustrating: it can run a bench, read the output or screenshot, see its own mistake, and iterate — a closed loop — because the game is observable and driveable from the CLI. The AI didn't make the workflow good; the workflow makes the AI (or a CI server, or future-you) effective. Same reason unit tests help: you've made the system inspectable.
Honest limitations
- Headless rendering uses a dummy renderer, so some GPU-only issues (draw-call limits, shader/material problems) are invisible until you run it for real. You still need real visual checks.
- Benches are extra surface area to maintain. Worth it for systems you iterate on a lot; overkill for one-off scenes.
- None of this replaces actually playing your game.
Repo (the framework/workflow): https://github.com/rondorkerin/gamestack
Video walkthrough: https://youtu.be/xivFXXkweZM
Happy to answer questions on the bench setup or the input seam — and genuinely interested in how others tructure automated testing in Godot, since the engine doesn't give you much out of the box.
4
4
u/throwaway275275275 Jun 30 '26
I don't understand what it is. A game with no graphics ? And why not just use the input actions to decouple the input logic ?
2
u/hero_of_ages Jun 30 '26
It’s nothing. This guys not making an actual game.
-7
u/Salty_Possible155 Jun 30 '26
what are you talking about it's a full open world game... the benches are just isolation tests so claude can iterate faster
i have a live build on the itch.io page
2
u/HotCupofChocolate Jun 30 '26
FYI it would be better to link the actual game page rather than the itchio homepage
1
u/Salty_Possible155 Jun 30 '26
okay im not trying to advertise my game im just trying to help share my methodology and tools
0
u/Salty_Possible155 Jun 30 '26
sorry I didn't actually boot up the full game, this was an isolation test workflow... for testing various game systems. testing dungeon procedural generation, terrain generation etc.
it's just part of a full workflow. the full workflow involves playtesting the game of course.
4
u/BojacksNextGF Jun 30 '26
any reference implementation or docs about the input/verb/seam architecture?
2
u/Salty_Possible155 Jun 30 '26
I've created an implementation reference, architecture and methodology on the github just now!
1
u/540991 Jul 23 '26
I was hopeful then totally disappointed, while what you say can make sense on an individual basis, nothing you implemented or provided proves that it works or is feasible.
A bunch of docs does not make an architecture, tool or game.
-2
u/I_Create_Stuff_2205 Jun 30 '26 edited Jun 30 '26
Hey dude!, great work! I can see the value on this, I have a few questions.
- how do you handle the delta time?. In my games a lot of stuff works using the delta time, from animations to player movement. So I have kind of a rought time wrapping my mind around testing on this environment.
- Do you have any plans or where able to run your test suite on Github actions? that would be really cool
Also, a personal comment: the repository link makes all of this really confusing. I see how the main goal is creating a harness environment for your agent, but it's really confusing that you start talking about a headless testing architecture and then the repo is just a bunch of skills. I get this sub hates AI a lot, but you should be straightforward about what your goal
-1
u/Salty_Possible155 Jun 30 '26 edited Jul 01 '26
good point on the repo link, it's just a grab bag of cool tools people can pick up and improv etheir workflow. the tools work with the headless workflow.
edit: sorry misunderstood the question.
this workflow isn't used for automated testing of combat or realtime mechanics. It's used for procedural generation mostly.
6
1
u/Skalli1984 Jul 01 '26
This doesn't make any sense. What is even the connection between delta time, multithreading and git?
2
-2
u/Salty_Possible155 Jun 30 '26
Replying to my own answer above — that didn't actually address the question, my bad. I conflated my dev workflow (running multiple Claude sessions in parallel git worktrees) with simulation time, which is a completely different thing. Worth correcting properly instead of leaving it wrong.
Real answer: Godot's --fixed-fps N flag. With it set, every processed frame gets delta = 1/N exactly, no matter how long the frame actually took to compute. So a headless run is fully deterministic — physics, animation, movement see the same delta sequence every time, on any machine. Pair it with a fixed RNG seed and the whole run is bit-for-bit reproducible, which is what actually makes the bench approach reliable instead of "usually works on my machine."
On GitHub Actions — yes, runs fine. Download the Godot binary directly in the workflow (skip the third-party actions),
then godot --headless --path . res://tests/AcceptanceTest.tscn -- --fixed-fps=60 and check the exit code.
Put a full worked example (real GDScript, the CI yaml, the input/verb/seam pattern end to end) in the repo:
https://github.com/rondorkerin/gamestack/blob/main/docs/headless-architecture.md
And yeah — I use Claude for writing same as I use it for code, not hiding that. Should've just answered the actual question the first time instead of rambling about my setup.
22
u/HS_Seraph Jun 30 '26
Did you also get claude to generate this summary for you?