Godot expert (technical director and agent protocol)
Target: Godot 4.7.2.stable (standard build, no C#), macOS Apple Silicon, Metal by default.
Expert Godot work is a loop run against evidence: set the budget, build one stage, measure it, look at it, fix it, advance. An agent without a mouse gets there by picking the right channel, passing only on a machine-readable result, and never trusting one signal alone. This skill is that protocol, the shared toolkit every scenario-godot-* skill calls (
), and the map of the team. If a sibling skill named here is missing from your available skills, ask the user to install it (
npx skills add scenario-labs/skills --skill <name>
); unattended, proceed from tool schemas and flag the gap.
Status (2026-10-02): every toolkit function below ran in Godot 4.7.2 here (23 live tests, 17 offline, all pass;
).
Stance (the expert delta)
- Headless output is the truth; the editor's Output panel is not. MCP servers that scrape it "report errors for lines no longer on disk", and it does not list every project error (Fennara, 2vSYP7GyA5U [00:02:51, 00:06:20]). PixelLab's agent runs the Godot binary itself and reads the console (THwZYWuOdZI [00:13:45]). Pass on the envelope plus a log scan, never on "it exited": a script with a parse error exits 0, with or without , and so does a script path Godot cannot find (version deltas, verified; L6).
- One writer per project. A GUI editor holding a project keeps its own copy of the open scenes, so its next save can overwrite a headless edit [added]; merged files can fail to parse (Lilith Duncan, CAJ_iIedx_I [00:01:36]). The toolkit refuses a project a GUI Godot has open; work through that editor (MCP) or ask the user to close it. Never close a Godot or Blender process you did not start [added].
- Milliseconds against a budget, on the target. Measure before optimizing (Dan Does Dev, s2C2RO_WMh0 [00:07:12]); profile on the real device class, thermals included (Ian Bolton, WrjaUNAXYqk [00:09:35]). Headless frame time is loop time, not game cost; windowed frame time on this Mac is pinned to the display refresh; judge and (observed).
- Look AND check. Every frame goes through and a contact sheet you open. Headless draws nothing, so captures need a windowed run (observed). After AI asset import, check facing, animation direction, tile seams and camera jitter by eye (PixelLab, THwZYWuOdZI).
- Tests are the contract. Assert on signals and public results, not internals (Godotneers, CreugthdgJ0 [01:08:36]; Butch Wesley, ImqhHLlPfZg [00:16:59]; Mike Schulze, x6GdTgIAFiU [00:24:19]); bound every await (2 s Godotneers, 5 s Schulze); prove the test bites by emptying the function and watching it go red (Wesley [00:13:40]).
- Design for a mouseless driver. Split body and brain so a test brain can steer a character (Godotneers [00:18:14]). Headless has no mouse device (Wesley [00:48:33]), yet GUI input still tests headless:
get_viewport().push_input(event, true)
reaches Controls, while never reaches a MOUSE_FILTER_STOP Control (scenario-godot-gameplay, scenario-godot-ui, observed); pass so positions are logical, not window pixels. Levels hold a spawn point, not the player (Godotneers [00:56:56]).
- Edits persist only with and a save. alone is invisible in the Scene dock and lost on save (Godot docs, Running code in the editor); script edits bypass Undo unless routed through (Queble, nW7YtSSJzbQ [00:04:41]).
- Be honest about visual editors. VisualShader, AnimationTree, the TileSet and theme editors, and the particle inspector are visual; the agent writes , / text, or builds the resource in code, and names the GUI path for a human. Pick an MCP by feedback fidelity, not tool count (Fennara [00:09:22]).
Execution channels
| Channel | Use for | Cost (measured here) | Rules |
|---|
| Headless job | logic, data, audits, scene building, imports, tests, exports | 0.2 s for a small job; about 2 s | default channel; watchdog ends a hung job (exit 124) |
| Windowed job , , | anything that must render: screenshots, sequences, GPU time | about 1 s for 4 views warm; first capture in a fresh project 10.8 s, a 5 s profile 29.6 s | 160x90 window, SubViewport at the requested size, focus handed back (2 restores per run observed); 2 |
| Headless editor | EditorInterface: open, edit and save scenes; EditorFileSystem | 1.1 s | wait 2 frames before editor calls; without EditorInterface calls fail (observed) |
| GUI editor + MCP (hybridindie ) | the user's open editor: live tree, selection, undoable edits | stdio probe 0.9 s, 23 tools exposed by default | only when the user has the editor open; project-local only () |
| Running game + MCP (tugcantopaloglu) | drive a running game (input, eval) | not run here | clone, , a game autoload on port 9090 |
reports what is possible now. Prefer the headless channels; add an MCP for live scene inspection.
Establish first
Ask once, then run: target platforms and lowest device; frame budget (default 60 fps desktop, 30 fps mobile); 2D or 3D; renderer (Forward+ desktop 3D, Mobile for demanding phones, Compatibility for web and widest reach, after Ray Hayes, KWhVVMpihsc [00:06:03]); art style and pixel art or not; whether the user's editor has the project open; the deliverable. Then
and
gd_run.audit(P, "project")
, and write the renderer, physics engine (Jolt for 3D), stretch mode, main scene and autoloads into the plan.
Workflow (the expert loop)
- Brief in numbers. Budgets per platform, resolution, scale, style. GATE: every later check has a number to compare with.
- Project.
gd_env.base_project("3d"|"2d", dest)
(APFS clone, 0.01 s, its own folder) or , which writes the project-manager defaults a hand-written lacks. GATE: has no flags.
- Baseline. ,
audit(P, "scene", scene=...)
, captures from fixed views, a profile if performance matters. GATE: zero parse and script errors; flags fixed or listed; sheet opened.
- Build one stage with small idempotent jobs (get-or-create, set, explicit save). Route domain work to its owner (below).
- Measure. Audit, tests (), plus , or the export result. GATE: numbers against the brief.
- Look AND check. Same views, , contact sheet opened, judged with the domain skill's critique. GATE: no flag that sets false, the change visible ( changed_fraction above 0).
- Fix before advancing, one change at a time; proves parity or gain.
- Deliver with evidence. Each step with its toolkit call, then Verified (what ran, result file, number or frame) and Assumed (not run, defaults). Never call a scene, shader, level or build done without numbers and a frame you looked at.
Shared toolkit (, the contract for every scenario-godot-* skill)
import sys; sys.path.insert(0, "<scenario-godot-expert>/scripts")
. System python3, standard library (Pillow optional).
Every
,
,
,
and
call, with defaults and return values:
section 1. Most used:
,
,
,
,
,
.
Every
run returns
,
,
(the job's dictionary),
,
,
,
,
,
,
,
,
,
. Output lands in
(carries
). Concurrency:
(6) Godot processes,
(2), one per project, all through
;
when a GUI editor holds the project.
A domain job is a GDScript file in the project (AgentKit,
, is copied to
):
gdscript
extends "res://addons/agentkit/agent_job.gd"
func run() -> Dictionary:
var scene := await load_scene("res://main.tscn", 2) # in the tree, 2 frames waited
note("children", scene.get_child_count())
return {"ok": true, "speed": arg("speed", 4.0)} # arg() typed by its default
Or a method on any RefCounted script,
run_script(P, "res://tools/x.gd:build", args)
(it receives the job). Errors logged during the job flip
to false unless
is set. A script error aborts a typed
, which then returns
: an empty result always fails (
), even with
, so return at least
.
reports
for every file it wrote (
for the named one) and treats an iOS
Xcode project as the
. Captures emulate the project stretch (keep, keep_width, keep_height, expand, ignore, integer scale, scale factor, viewport mode) with black bars, matching the engine on 15 cases (L20); in your own job read a
SubViewport with
. Helpers:
,
,
,
,
,
,
,
,
; libraries
(
, presets),
(
). Domain code goes in
and
scripts/agentkit/<domain>/
.
Numbers
| Value | Relative to | Source |
|---|
| 16.67 / 33.33 ms | frame budget at 60 / 30 fps | arithmetic |
| 8.33 ms | windowed floor on this 120 Hz display, even with | observed |
| 6.9 ms | headless frame pacing: the low-processor sleep (low_processor_usage_mode_sleep_usec
6900), whatever the work; benchmark with and time the work | observed; scenario-godot-gameplay, scenario-godot-ui |
| 1.4 to 1.8 ms / 0 | GPU p95 of the fx test scene at 1080p on Vulkan (four runs) / on Metal | observed |
| 0.2 s, 10.2 s | small job; a hung job stopped by the watchdog (timeout 20 s) | observed |
| 60.8 / 75.0 / 41.4 MB | macOS universal zip / Linux x86_64 (binary plus .pck) / Web (9 files) export of Base3D | observed, L9 |
| 430 MB | iOS Xcode project (), mostly the engine xcframework | observed, L9 |
Quality gates
- Measurable: true with zero parse, script and shader errors; ; JUnit and ; audit flags fixed or justified; ok (p95 under target, hitch ratio at most 1%); export exit 0, artifact size recorded, lists the expected files.
- Visual: every capture passes ( false on all_black, all_white, uniform, transparent) and was opened in a contact sheet; before and after from the same views.
Common mistakes
| Mistake | What it looks like | Fix |
|---|
| trusting exit codes | parse error, exit 0 | / read the log |
| on code using autoloads | "Identifier not found: GameState" | (in-process load); files it under |
| hand-written | GodotPhysics3D, stretch disabled, UI does not scale | , or set the keys |
| in a fresh project | "Identifier not found" | first |
| autoload named like its | 'Class "GameState" hides an autoload singleton.' (GDScript analyzer) | name the autoload differently () or drop |
| capture or headless | 0 frames drawn; crashes | windowed |
| GPU timing on Metal | 0 | profile_scene(driver="vulkan")
|
| export output inside the project | build imported and packed next time | outside, or (auto) |
| macOS export | "ETC2 ASTC" or "Invalid bundle identifier" | ; sets a bundle id |
| clones sharing | saves, input maps and the shader cache of other agents' clones show up (every clone was named Base3D) | isolates by default; gd_env.isolate_user_dir(P)
for other scratch projects |
| per-frame | the same value on every CSV row (the monitor refreshes about once per second; 0 under ) | column of , or sentinel nodes |
Team routing and handoffs
| Skill | Route here when the brief mentions |
|---|
| scenario-godot-architecture | project structure, scenes vs nodes, signals, Resources, autoloads, composition, typing, save data |
| scenario-godot-2d | sprites, TileMapLayer, TileSet, platformer or top-down feel, Camera2D, pixel-perfect, 2D lights, parallax |
| scenario-godot-3d-world | CharacterBody3D, blockout, CSG, GridMap, terrain, GLB import, Jolt, procedural meshes |
| scenario-godot-rendering-lighting | renderer choice, SDFGI, VoxelGI, LightmapGI, environment, fog, shadows, post, Compositor |
| scenario-godot-shaders | , VisualShader substitutes, global uniforms, screen reading, compute, stencil |
| scenario-godot-vfx | GPUParticles, trails, sub-emitters, particle collision, impacts, juice |
| scenario-godot-animation | AnimationPlayer, AnimationTree, tweens, skeletons, IK, retargeting, root motion, cutscenes |
| scenario-godot-ui | Control layout, containers, themes, menus, HUD, focus and gamepad, localization, scaling on phones |
| scenario-godot-gameplay | state machines, inventory, dialogue, save and load, navigation, AI, procedural generation, input |
| scenario-godot-audio | buses, effects, spatial audio, adaptive music, SFX variation |
| scenario-godot-multiplayer | high-level multiplayer, RPCs, authority, spawner and synchronizer, lobbies, dedicated servers |
| scenario-godot-pipeline-automation | CLI and CI, editor plugins, GUT and gdUnit4, import plugins, GDExtension, MCP workflows |
| scenario-godot-performance-export | profiler, monitors, stutter, mobile, web, desktop and Steam export, signing |
A cross-cutting item has one owner: shader stutter and web threading go to scenario-godot-performance-export, naming the second skill in the brief. Every handoff packet carries: project path and channel, the brief in numbers, what was done with its toolkit calls, evidence (result JSON path, contact sheet and flags, CSV with
, JUnit totals, export sizes), Verified, Assumed, open issues, and a fence (what the receiver may touch). The receiver reproduces one piece of evidence before building on it. Sister teams: blender-expert (asset fixes before GLB export), unreal-expert, unity-expert, maya-expert, zbrush-expert.
Scenario assets into Godot
| Asset | Generated with | Lands in |
|---|
| sprites, props, icons, tilesets, UI pieces | scenario-game-assets | scenario-godot-2d, scenario-godot-ui |
| animation frames, sprite sheets, VFX loops | scenario-sprite-animation, scenario-sprite-pipeline | scenario-godot-2d (SpriteFrames), scenario-godot-vfx (flipbooks) |
| parallax planes | scenario-parallax-stage | scenario-godot-2d (Parallax2D) |
| GLB meshes | scenario-3d | scenario-godot-3d-world (import), scenario-godot-animation (rigs) |
| PBR textures, tileable materials | scenario-textures | scenario-godot-rendering-lighting, scenario-godot-shaders |
| skyboxes, 360 panoramas | scenario-skyboxes | scenario-godot-rendering-lighting (sky) |
| music, SFX, ambience, voice | scenario-audio, scenario-elevenlabs | scenario-godot-audio |
Check the asset before import (scenario-quality-gate) and again after it, in a capture from the game camera judged by the receiving skill, which also audits GLB scale, pivot and triangle count.
Godot 4.7 traps and macOS notes
Read
before the first job: Godot 3 names that pass
,
hangs, unknown flags ignored,
waiting on stdin, what headless cannot do (MultiMesh writes, GPUParticles),
rewriting
, import crashes, deprecated APIs; then the macOS binary, template and
paths, window focus, and Metal profiling.
References
references/expert-notes.md
: expert claims by source with timestamps, and traps observed while building the toolkit.
- : the toolkit API in full, job-writing rules, and each procedure with its live test and result.
- : the rubric for judging an agent's Godot output and a handoff.
- : editor menus and settings for the same tasks.
- : every source with credentials, URLs and best timestamps.