ai-behavior-trees-util…
Build a production behavior-tree runtime (Blackboard, action/condition leaves,…
Run GDScript test suites from the command line with `godot --headless`, using a SceneTree/MainLoop runner script that exits non-zero on failure so CI can gate merges. Use when a Godot project needs unit tests without opening the editor, when wiring a CI job (GitHub Actions or
$ npx -y skills add gamedev-skills/awesome-gamedev-agent-skills --skill godot-gdscript-headless-testing --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/godot-gdscript-headless-testingContext preview
The summary Claude sees to decide when to auto-load this skill.
Run GDScript test suites from the command line with `godot --headless`, using a SceneTree/MainLoop runner script that exits non-zero on failure so CI can gate merges. Use when a Godot project needs unit tests without opening the editor, when wiring a CI job (GitHub Actions or
name: godot-gdscript-headless-testing description: > Run GDScript test suites from the command line with `godot --headless`, using a SceneTree/MainLoop runner script that exits non-zero on failure so CI can gate merges. Use when a Godot project needs unit tests without opening the editor, when wiring a CI job (GitHub Actions or similar) that must fail the build on a failing `.gd` test, or when a `godot --headless` invocation hangs, opens a window, or exits 0 despite failed assertions.
Run GDScript tests from the command line, without the editor GUI, and get a real process exit code CI can act on. Targets **Godot 4.7** headless CLI.
verify GDScript logic (pure functions, resource loading, autoload state) from a terminal or CI pipeline.
exits 0 despite failing assertions.
**When *not* to use:** GDScript syntax or language features themselves → `godot-gdscript`; export/build pipeline and platform templates → `godot-export` (its own `--headless` use case, producing a binary, not running tests).
1. **Confirm the binary resolves headless.** Godot 4.x ships `--headless` built in (no export template needed); run `godot --headless --version` and confirm it prints a version string, not a GUI window. 2. **On a fresh checkout, import before running tests.** `.godot/` is normally not committed, so a clean checkout has no import cache: `class_name` types fail to resolve (`Identifier "X" not declared in the current scope`) and imported assets fail to load (`No loader found for resource: res://...`). Run `godot --headless --path <project_dir> --import` once first, in CI and locally. 3. **Write the runner as a `SceneTree` script, not a `Node` scene.** A `SceneTree` script's `_initialize()` runs once before any frame — enough for pure-logic tests and no `.tscn` required to launch. 4. **Track pass/fail counts yourself and call `quit(<code>)` explicitly. Do not use bare `assert()` to fail a test.** Godot does not turn the process exit code non-zero on `push_error()` by itself — the runner must count failures and call `quit(1)`. Worse, a failed `assert()` inside `_initialize()` (official/debug build) prints `SCRIPT ERROR: Assertion failed` and **stops execution before `quit()` runs**, so the process never exits and CI hangs until its own timeout. Use an `assert_eq()`-style helper that records the failure and keeps going. 5. **Invoke with `godot --headless --path <project_dir> --script res://<runner>.gd`** and read the **process exit code**, not just stdout, from the shell or CI step. `--script` accepts both a `res://`-relative path and an absolute filesystem path (e.g. a runner outside the project folder); either works. 6. **Redirect stdout and stderr to files when scripting the invocation from a wrapper shell** (PowerShell, some CI runners). `push_error()` output goes to stderr and can be dropped or reordered when only stdout is captured live. 7. **Add a step timeout in CI.** Even with the `assert()` pitfall avoided, an `await` that never resolves (Pattern #2) hangs the runner forever; a `timeout-minutes` on the CI step is a backstop CI-side, not a substitute for backing every `await` with a timeout node.
# res://test_runner.gd — run with:
# godot --headless --path . --script res://test_runner.gd
extends SceneTree
var passed := 0
var failed := 0
func _initialize() -> void:
test_add()
print("Results: %d passed, %d failed" % [passed, failed])
quit(1 if failed > 0 else 0) # non-zero exit fails the CI step
func assert_eq(actual, expected, label: String) -> void:
if actual == expected:
passed += 1
else:
failed += 1
push_error("FAIL %s: expected %s, got %s" % [label, expected, actual])
func test_add() -> void:
assert_eq(2 + 2, 4, "test_add")Verified against Godot 4.7.2: `godot --headless --path . --script res://test_runner.gd` prints `Results: N passed, M failed` to stdout, routes `push_error` lines to stderr, and returns process exit code `0` when `failed == 0`, `1` otherwise.
extends SceneTree
var passed := 0
var failed := 0
func _initialize() -> void:
await run_tests()
print("Results: %d passed, %d failed" % [passed, failed])
quit(1 if failed > 0 else 0) # track and report failures here too
func assert_eq(actual, expected, label: String) -> void:
if actual == expected:
passed += 1
else:
failed += 1
push_error("FAIL %s: expected %s, got %s" % [label, expected, actual])
func run_tests() -> void:
# `root` is not inside the tree yet during _initialize(): a Timer started now
# errors ("not inside the tree") and its `timeout` never fires. Wait one frame.
await process_frame
var timer_node := Timer.new()
timer_node.one_shot = true # default Timer restarts after timeout
root.add_child(timer_node)
timer_node.start(0.1)
await timer_node.timeout
# assertions here can rely on the node having been in the tree for a frame
assert_eq(timer_node.is_stopped(), true, "timer_fires_once")
timer_node.queue_free()`_initialize()` may `await`, which is what makes this pattern work for anything that needs a node to actually enter the tree, a timer to fire, or a signal to emit — none of which happen before the engine has processed at least one frame. Use the same `passed`/`failed` counter and `assert_eq()` helper as Pattern #1; a version of this pattern that always calls `quit(0)` can never fail a build.
<img src="docs/assets/awesome-gamedev-agent-skills-banner.png" width="820" alt="awesome-gamedev-agent-skills — game-dev skills for AI coding agents.
Repo: gamedev-skills/awesome-gamedev-agent-skills
Build a production behavior-tree runtime (Blackboard, action/condition leaves,…
Implement game audio practice — bus/mixer architecture and gain in decibels, ducking…
Build game cameras that feel good — 2D follow with a deadzone, look-ahead, smoothing, and…
Plan, generate, source, normalize, and validate cohesive visual game assets. Use for art…
Build branching dialogue and narrative — a node/choice graph with conditions, variables, and…
Design NPC and enemy decision-making with finite state machines, behavior trees, steering…