Godot Data-Driven Config Skill
What this skill does
Turns hardcoded const / magic numbers inside .gd files into a clean Resource-based config layer that designers can edit via .tres or the Godot Inspector. Also generates a CLI validator (bash tools/validate_data.sh) so a human or CI can check "did I break the config table?" in one command.
When to invoke
Trigger on intents like:
- "Create a WeaponData / EnemyData / LevelData / SkillData for this project"
- "Move these hardcoded numbers into a config table"
- "I want designers to tune damage/hp/speed without touching code"
- "Add a new weapon/enemy type through data"
- "Build an AI-native Godot config workflow"
Canonical file layout (ALWAYS use this)
res://data/
├── specs/ # JSON specs, source of truth (git-tracked)
│ ├── weapon.spec.json
│ └── ...
├── resources/
│ ├── <name>_data.gd # class_name <Name>Data extends Resource
│ └── data_manager.gd # Autoload (AUTOGENERATED — do not hand-edit AUTOGEN regions)
├── <name>s/ # plural dir, one .tres per entry
│ └── default_<name>.tres
tools/
├── validate_data.gd # Godot-side CLI validator (autogen)
└── validate_data.sh # bash wrapper (exit code fix-up)
project.godot # [autoload] DataManager="*res://data/resources/data_manager.gd"
Rationale: plural-dir + id field makes DirAccess scanning trivial. Specs under data/specs/*.spec.json are the source of truth; regenerating data_manager.gd / validate_data.gd is always a pure function of that directory, so nothing can drift.
Required workflow
Step 1 — Discover & propose
- Read target
.gdfiles; collect hardcoded constants and literals. - Ask the user which category (weapon/player/enemy/level/skill/item/…). Each category becomes one
<Name>Dataclass. - Produce a field mapping table (source constant → new field → range). Present it, wait for OK (max 1 round of confirmation).
Step 2 — Write spec JSON under data/specs/
Every category is described by exactly one JSON file matching schemas/field_spec.schema.json. Commit these to git — they are the source of truth and diff well.
Minimal valid spec:
{
"name": "weapon",
"default_id": "default_pistol",
"fields": [
{ "key": "id", "type": "StringName", "default": "&\"\"", "group": "Identity" },
{ "key": "damage", "type": "int", "default": 10, "range": [0, 9999, 1] }
],
"validators": ["damage >= 0"]
}
Step 3 — Run scaffold.py once per new/updated category
python3 ~/.codebuddy/skills/godot-data-driven-config/scripts/scaffold.py \
--project-root <abs Godot project path> \
--spec <abs path to spec>.spec.json
The script:
- copies the spec into
res://data/specs/(creates source-of-truth file) - writes
res://data/resources/<name>_data.gd(first time only; re-run with--force-classto overwrite) - writes
res://data/<name>s/default_<name>.tres(first time only;--force-tresto overwrite) - rebuilds
res://data/resources/data_manager.gdfrom alldata/specs/*.spec.json— you never lose a previously-added category - rebuilds
res://tools/validate_data.gd(duck-typed againstResource, so cold-start without.godot/cache still works) - writes
res://tools/validate_data.shwrapper - patches
[autoload]inproject.godotidempotently
Step 4 — Migrate consumer .gd files
For each file with hardcoded values:
- Add at top of class:
@export var <name>_data: <Name>Data - In
_ready():if <name>_data == null: <name>_data = DataManager.get_<name>() - Replace literal(s):
JUMP_SPEED→<name>_data.jump_speedetc. - Keep purely-technical consts unchanged (collision masks, shader paths, blend-tree parameter strings).
- DO NOT edit
.tscnunless necessary — theDataManagerfallback covers unbound cases.
Step 5 — Validate
bash tools/validate_data.sh
# exit 0 = OK
# exit 1 = validation failed (stdout/stderr has reasons)
# exit 2 = environment issue
On cold clone (no .godot/ dir), the wrapper auto-refreshes the class cache once via godot --headless --editor --quit — designers/CI need no special knowledge.
Step 6 — Document
Append to PROJECT_OVERVIEW.md or README.md:
- The mapping table from Step 1
- Designer workflow: "edit
.tres→ runbash tools/validate_data.sh→ F5". - Validator command.
Hard rules
- One Resource class per category, never a god-class.
- Numeric fields MUST have
@export_range. - Every
<Name>DataMUST have anid: StringNamefield; filename stem is used as fallback id. DataManageris ALWAYS an Autoload namedDataManager.data_manager.gdreturnsResource(not<Name>Data) from getters; callers cast withas WeaponDataif they want static typing. This keeps cold-start safe.- DO NOT mutate a Resource at runtime (shared reference). To modify:
var copy = data.duplicate(); copy.x = .... - DO NOT try to
MultiplayerSynchronizer-sync a Resource. Instead, guarantee the same.tresexists on all peers (git-managed). - Validator never uses
exit_codebecause Godot bug #88055 drops it under--script. Use theVALIDATION_RESULT=OK|FAIL:nstdout marker (the generatedvalidate_data.shdoes this). - DO NOT hand-edit the
# region *_AUTOGENblocks;scaffold.pyrewrites them on every run.
Cold-start / CI notes
- Godot's
global_script_class_cache.cfgis created by the editor. For headless/CI, the first run needsgodot --headless --editor --quitto populate it (the generated wrapper does this automatically when.godot/is missing). - Validator is intentionally written to NOT depend on
class_nameresolution (usesResourcebase +res.get("field")duck-typing), so even if the cache is stale it still works.
Anti-patterns to avoid
- ❌ Using CSV/JSON/YAML loaders at runtime — Godot's native
.tresis already text, git-friendly, type-safe, and Inspector-editable. - ❌ Putting everything in one god-resource — harder to diff, no grouping, single-file merge conflicts.
- ❌ Hardcoding defaults in the Resource script AND in the
.tresat different values — keep.tresauthoritative; script defaults only act as fallback when a consumer never binds a resource. - ❌ Reading
.treswithFileAccess/JSON.parse— always useload()/preload().
Smoke-test before declaring done
Run scripts/selftest.sh (in this skill) which:
- Creates a fresh temp Godot project.
- Runs
scaffold.pywith every example spec. - Refreshes class cache + runs the validator.
- Injects a bad value and confirms the validator now exits 1.
- Cleans up.
bash ~/.codebuddy/skills/godot-data-driven-config/scripts/selftest.sh
Deliverables checklist
When you finish, confirm ALL of:
-
res://data/specs/<name>.spec.jsonexists and is committed -
res://data/resources/<name>_data.gdwithclass_name+ typed@exportfields -
res://data/<name>s/default_<name>.treswith sensible defaults -
res://data/resources/data_manager.gdexists and references the new category -
project.godothasDataManagerin[autoload] - Consumer
.gdreference<name>_data.xxx(no more magic numbers) -
bash tools/validate_data.sh→ exit 0 -
PROJECT_OVERVIEW.md/README.mdupdated
Files in this skill
SKILL.md this file
QUICKREF.md one-page cheat sheet for the AI
schemas/field_spec.schema.json JSON schema for designer-facing spec
templates/
data_class.gd.tmpl Resource class template
data_resource.tres.tmpl .tres template
data_manager.gd.tmpl Autoload template (uses Resource base)
validate_data.gd.tmpl CLI validator template (duck-typed)
validate_data.sh.tmpl bash wrapper (exit-code fix-up)
scripts/
scaffold.py end-to-end generator
selftest.sh self-test / regression check
examples/
weapon_spec.json
enemy_spec.json