Skills/godot-headless-workflow/SKILL.md

12 KiB
Raw Blame History

name description license compatibility metadata
godot-headless-workflow 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. MIT
godot-4.2
godot-4.3
godot-4.4
godot-4.5
godot-4.6
godot-4.7
author version tags created
vl4dt 0.1.0
godot
headless
ci
ai-agents
testing
export
revenuecat
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

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\<engine-version>\
Linux ~/.local/share/godot/export_templates/<engine-version>/
macOS ~/Library/Application Support/Godot/export_templates/<engine-version>/

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)

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

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. Tests live in res://tests/; suites extend GdUnitTestSuite. Run from the project root:

# 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:

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:

# 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)
godot --headless --path . -s res://tools/smoke_test.gd

Copy-paste verification gate (put this in every game repo)

#!/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:

# 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).

# 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:

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(
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/
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