--- name: godot-headless-workflow description: "AI-agent development loop for Godot 4.x without opening the editor: headless validation (--check-only), project import, gdUnit4 testing, safe hand-editing of text scenes (.tscn), CLI export pipeline for Android/iOS, mobile checklist, and RevenueCat plugin integration. Use when building or modifying a Godot game as a coding agent, running Godot in CI, validating GDScript changes, exporting mobile builds, or integrating in-app purchases." license: MIT compatibility: - godot-4.2 - godot-4.3 - godot-4.4 - godot-4.5 - godot-4.6 - godot-4.7 metadata: author: vl4dt version: 0.1.0 tags: - godot - headless - ci - ai-agents - testing - export - revenuecat created: 2026-08-24 --- # Godot Headless Workflow (Agent Development Loop) The complete edit→validate→test→run→export loop for Godot projects **without ever opening the editor**. Every change must pass the headless gate before you claim it works. ## Golden Rules 1. **Never trust an edit you haven't validated headlessly.** A script that looks right but was never parsed is not done. 2. **GDScript only** unless the project already uses C# (see `godot-csharp-patterns`). Pin the engine version in the project's `AGENTS.md` (e.g., `Godot 4.7.2 stable, GDScript`). 3. **Scenes are text** (`.tscn`/`.tres`) — editable by hand *with care*, or better, generated by tool scripts. See §Text Asset Safety. 4. **Never touch `.godot/`** — it's a regenerable cache. 5. **The editor GUI is optional.** Use it (or the package's MCP bridge) only for visual layout iteration; logic, tests, and builds are all CLI work. ## Prerequisites ```bash godot --version # e.g. 4.7.stable.official — confirm once per machine/session ``` The Godot binary must be on PATH **in the shell you run commands from** (Git Bash on Windows, native bash on Linux). Export templates are needed only for §CLI Export: | Host | Template install path | |---|---| | Windows | `%APPDATA%\Godot\export_templates\\` | | Linux | `~/.local/share/godot/export_templates//` | | macOS | `~/Library/Application Support/Godot/export_templates//` | ## Cross-Platform (Windows ↔ Ubuntu hosts) All Godot CLI flags, gdUnit4 runners (`runtest.cmd` / `runtest.sh`), `res://` paths, and export behavior are **identical across hosts** — projects stay fully portable. Watch only: 1. **Line endings kill `.sh` scripts**: if `verify.sh` is checked out with CRLF on Linux it fails with `$'\r': command not found`. Add to every game repo's `.gitattributes`: `*.sh text eol=lf` (and commit it from day one). 2. **Runner extension differs by host** — `runtest.cmd` on Windows, `runtest.sh` elsewhere; detect or parameterize instead of hardcoding one. 3. **Don't mix toolchain worlds**: run Godot + Android SDK/adb **natively** per OS. Building inside WSL while your emulator/adb runs on Windows causes device-detection pain. On Ubuntu, USB debugging needs udev rules for your phone vendor before `adb devices` lists it. 4. **Signing env vars are the same** on both hosts — just use absolute paths appropriate per filesystem in `GODOT_ANDROID_KEYSTORE_PATH`. ## The Core Loop ``` edit (.gd / .tscn / project settings) → full import (after adding/removing assets or scenes) → parse check (every changed script) → unit tests (gdUnit4, see references/testing.md) → smoke run (boot the game headlessly for N frames) → commit (only when everything above exits 0) → export (when feature-complete; see references/exporting.md) ``` ### Step 1 — Full import (required after asset/scene changes) ```bash godot --headless --path . --import ``` Imports all resources, generates `.godot/` cache, resolves UIDs, and reports load errors. Run this before anything else in a fresh clone, and whenever you added textures/scenes/fonts. Exit code ≠ 0 means broken resources — fix before continuing. ### Step 2 — Parse-check changed scripts ```bash godot --headless --check-only --script res://scripts/player.gd ``` Syntax + basic type checking without executing. Fast enough to run per-edit. Note: it does NOT catch broken node paths, missing autoloads, or runtime type errors — those need steps 3–4. ### Step 3 — Unit tests (gdUnit4) Standardize every project on [gdUnit4](https://github.com/MikeSchulze/gdUnit4). Tests live in `res://tests/`; suites extend `GdUnitTestSuite`. Run from the project root: ```bash # Windows addons/gdUnit4/runtest.cmd -a res://tests --continue # Linux/macOS ./addons/gdUnit4/runtest.sh -a res://tests --continue ``` Non-zero exit = failures (CI-safe). HTML/XML reports land in `res://reports/`. Full patterns — assertions, async tests, parametrized cases — in `references/testing.md`. ### Step 4 — Smoke run Boot the real game headlessly and let it tick a bounded number of frames: ```bash godot --headless --path . --quit-after 300 # ~300 frames of main scene ``` Runtime errors (bad autoload order, missing nodes, signal typos) print here and fail loudly. For deeper checks (instantiate specific scenes, assert state), use a SceneTree driver script: ```gdscript # tools/smoke_test.gd — instantiate target scenes, tick frames, assert they survive extends SceneTree func _init() -> void: call_deferred("_run") func _run() -> void: var targets := ["res://scenes/main.tscn"] var errors := 0 for path in targets: var ps: PackedScene = load(path) if ps == null: push_error("FAILED TO LOAD: " + path) errors += 1 continue var node := ps.instantiate() root.add_child(node) await process_frame await process_frame node.queue_free() await process_frame print("SMOKE RESULT: ", "FAIL" if errors > 0 else "PASS") quit(1 if errors > 0 else 0) ``` ```bash godot --headless --path . -s res://tools/smoke_test.gd ``` ### Copy-paste verification gate (put this in every game repo) ```bash #!/usr/bin/env bash # verify.sh — the agent gate. Run after EVERY batch of edits. All steps must exit 0. set -e godot --headless --path . --import for f in $(git diff --name-only HEAD | grep '\.gd$'); do godot --headless --check-only --script "res://${f#*/}" || exit 1 done addons/gdUnit4/runtest.cmd -a res://tests --continue # Linux/macOS: runtest.sh godot --headless --path . --quit-after 300 echo "ALL CHECKS PASSED" ``` ## Text Asset Safety (.tscn / .tres) Scenes and resources are plain text — readable, diffable, mergeable. Rules: | Do | Don't | |---|---| | Read `.tscn` to learn structure before editing scripts | Invent `uid://` values | | Edit node properties, positions, exported values in place | Delete or rename `.uid` sidecar files | | Add `[node]` blocks mirroring existing syntax exactly | Reference resources by UID you haven't seen | | Prefer generating scenes via a tool script (below) | Hand-write complex nested scenes from scratch | | Let Godot regenerate paths after moving files (`--import`) | Edit anything under `.godot/` | Since Godot 4.4, scripts carry `.uid` sidecars and `ext_resource` entries may point at `uid://…`. If both `uid` and `path` exist, either resolving is fine — **keep the `path` attribute when hand-editing** and never fabricate UIDs. When structural changes get risky (new node trees, wiring signals between nodes), generate the scene programmatically instead: ```gdscript # tools/build_scene.gd — run: godot --headless --path . -s res://tools/build_scene.gd extends SceneTree func _init() -> void: call_deferred("_build") func _build() -> void: var root_node := Node2D.new() root_node.name = "Level1" var sprite := Sprite2D.new() sprite.name = "Player" sprite.position = Vector2(640, 360) root_node.add_child(sprite) var packed := PackedScene.new() packed.pack(root_node) ResourceSaver.save(packed, "res://scenes/levels/level_1.tscn") print("SCENE SAVED") quit(0) ``` This is deterministic, reviewable, and can't corrupt unrelated scene sections. ## CLI Export One-time setup: create presets (editor UI or by writing `export_presets.cfg` — see `references/exporting.md` for working Android/iOS preset skeletons). ```bash # Windows/Linux desktop godot --headless --path . --export-release "Windows Desktop" builds/win/game.exe # Android (AAB for Play Store; APK for sideload/Galaxy Store) godot --headless --path . --export-release "Android" builds/android/game.aab # iOS — produces an Xcode project FOLDER, archived later on macOS godot --headless --path . --export-release "iOS" builds/ios/ ``` Release Android exports read signing config from env vars: ```bash export GODOT_ANDROID_KEYSTORE_PATH=/path/to/release.keystore export GODOT_ANDROID_KEYSTORE_USER=alias export GODOT_ANDROID_KEYSTORE_PASSWORD=secret ``` Details, debugging exports (`--export-debug`), and the store-submission checklist live in `references/exporting.md`. ## Mobile Quick Checklist - Stretch mode `canvas_items` + portrait/landscape locked in `project.godot` - Touch input: enable "Emulate Mouse From Touch"; consider Godot 4.7's `VirtualJoystick` - Respect notches/safe areas (get visible rect from viewport, anchor UI inside it) - Budget: mid-range phone should hold 60 fps — profile with `godot-performance` skill before shipping - Min versions when using the RevenueCat plugin below: iOS 15+, Android SDK 24+ ## In-App Purchases (RevenueCat) Use the community **Godotx RevenueCat** plugin (MIT; wraps the official RC iOS/Android SDKs; built against Godot 4.7-stable). Condensed flow — full playbook in `references/revenuecat.md`: 1. Install via AssetLib ("Godotx RevenueCat") → gives `addons/`, `ios/plugins/`, `android/plugins/` 2. Enable the plugin **per export preset**; Android additionally needs **Gradle Build ON** 3. Create the app + IAP products in Play Console / App Store Connect first, mirror them in the RevenueCat dashboard, note the platform-specific public API keys 4. Init at startup with the platform key → fetch offerings → paywall → purchase → entitlement check 5. Test a real sandbox purchase on device **before** building game systems on top of it ⚠️ Verify exact method names against the addon's own README/API reference at integration time — don't guess API surface (see failure modes below). ## Agent Failure Modes (catch these in review) | Failure mode | Symptom | Gate | |---|---|---| | **Godot 3 API bleed** | `onready var`, `yield(`, `.instance()`, `tool` alone, `export var`, string-form `connect("x", self, "y")` | `grep -rnE "onready \|yield\(|\.instance\(\)|^tool\b|^export \|connect\(\"" scripts/` → must be empty | | **Unity API bleed (C# projects)** | `UnityEngine`, `MonoBehaviour`, `GetComponent` | Only relevant in .NET projects; prefer GDScript to avoid entirely | | **Invented UIDs / broken refs** | Load errors at import | Run `--import` gate after every scene edit | | **Autoload order bugs** | Singleton accessed before init in `_init()` | Move access to `_ready()`; smoke run catches it | | **Wrong callback** | Game logic in `_process()` that needs physics ticks | Use `_physics_process()` for movement/forces (`godot-physics` skill) | | **Signal leaks** | Connections to freed nodes; crashes on scene change | Connect in `_ready()`, disconnect or use `CONNECT_ONE_SHOT`; review with `godot-code-review` skill | | **Guessed external APIs** | Hallucinated plugin/engine methods | Only cite APIs found in the project's addons/docs or verified via `--check-only` passing | ## Editor vs Headless — When To Use Which | Task | Tool | |---|---| | Script edits, tests, exports, CI, refactors | **Headless CLI (this skill)** | | Visual layout polish, animation curves, theme tweaking | Editor GUI (human) or the package's `mcp-bridge/` MCP server for agent-driven editor control | | Import cache issues | Always `--import` headless, never hand-fix `.godot/` | ## Related Skills | Need | Skill | |---|---| | Scaffold the project | `godot-project-setup` | | GDScript style & architecture decisions | `godot-gdscript-patterns`, `godot-brainstorming` | | Review before merging features | `godot-code-review` | | Diagnose failing smoke runs | `godot-debugging` | | Ship fast on phones | `godot-performance` | | Input handling, save systems, audio, shaders | `godot-input`, `godot-save-systems`, `godot-audio`, `godot-shaders-vfx` | ## References - `references/testing.md` — gdUnit4 deep dive: suites, assertions, async, CI integration - `references/exporting.md` — export presets, Android/iOS pipelines, store submission checklist - `references/revenuecat.md` — RevenueCat plugin integration playbook for mobile games