The Safe Change Workflow

Editing a data/*.json file is safe as long as you run the checks afterward. The engine swallows data mistakes silently: a typo'd material grants nothing, an unknown recipe category renders nowhere, a missing balance key falls back to a stale hardcoded number. This loop catches those before they reach a player. Follow it every time.

The loop

  1. Edit the JSON. Change the number or field in the relevant data/*.json file. Save.
  2. Run the data validator. Expect the last line DATA OK. This catches broken references, unknown categories, BOMs, and missing balance keys.
  3. Run the smoke test for any gameplay-affecting change. Expect SMOKE PASS. This proves saves still round-trip and the offline simulation still holds its invariants over the real game code.
  4. Feel-check in the editor. Open the game and actually play the part you changed. The two automated gates prove the data is valid and the game still runs — only a human can tell you the change feels right.

Skipping step 4 is the most common mistake. A validator cannot tell you the Maw now eats too fast.

Step 2 — the data validator

Run after any data/*.json edit. It loads every data file and checks the cross-references the engine would otherwise ignore.

<godot console exe> --headless --path . --script res://tools/validate_data.gd

It passes when the final line is DATA OK (exit code 0). On failure it prints one VALIDATE: line per problem and ends with DATA FAIL: N error(s). What it checks:

Step 3 — the smoke test

Run for any change that affects gameplay (drain rates, the Maw, machines, offline behaviour, the economy). It boots the real autoload stack headless, mutates state across several managers, saves and reloads it, and runs a ten-minute offline simulation — asserting nothing broke.

printf '\n[autoload]\n\nZZSmokeTest="*res://tools/smoke_test.gd"\n' >> project.godot
<godot console exe> --headless --path .        # expect SMOKE PASS

It appends itself as the last autoload, runs, and prints SMOKE PASS (exit 0) or SMOKE FAIL: N assertion(s) failed. It uses the first free save slot and deletes it when done; if all slots are occupied it prints SMOKE SKIP rather than risk a real save.

Important — restoring project.godot afterward. The published instructions end with git checkout -- project.godot, but on this project's working copy that is unsafe: uncommitted tweaks are routinely kept in project.godot, and a hard checkout would discard them. Instead, copy the file aside before you append the autoload line and copy it back afterward:

cp project.godot project.godot.bak
printf '\n[autoload]\n\nZZSmokeTest="*res://tools/smoke_test.gd"\n' >> project.godot
<godot console exe> --headless --path .        # expect SMOKE PASS
cp project.godot.bak project.godot && rm project.godot.bak

Optional — the parse-check

If your change touched anything beyond plain data (or you just want the syntax gate the way CI runs it), the parse-check compiles every autoload and greps for script errors:

powershell -ExecutionPolicy Bypass -File tools\parse_check.ps1

Exit 0 means clean (no SCRIPT ERROR or Parse Error lines). Pure JSON edits do not need this, but it is harmless to run.

Finding the Godot console exe

The validator and smoke test need the Godot console executable — the plain GUI build detaches from the terminal and its output (the DATA OK / SMOKE PASS line you are checking for) is lost. The scripts default to the dev-machine location:

%USERPROFILE%\Downloads\Godot_v4.6.2-stable_win64.exe\Godot_v4.6.2-stable_win64_console.exe

Override it by setting the GODOT_CONSOLE environment variable to the full path of your Godot_*_console.exe.

Gotchas that will bite you

UTF-8 BOM

Some editors save JSON with a hidden "byte-order mark" at the very start of the file. Godot tolerates it, but strict JSON parsers (and the validator) reject it. If the validator reports a file "starts with a UTF-8 BOM", re-save it as plain UTF-8 without the mark.

_comment and other underscore keys

Many data files carry a "_comment" note at the top explaining the format. That is fine — the validator and the game skip any key starting with an underscore. The one exception is materials.json: a meta key there inflates the game's completion-percentage denominator, so the validator forbids it. Put explanatory notes for materials somewhere else, never as a top-level key in materials.json.

REQUIRED_BALANCE_KEYS — the anti-drift list

The validator holds a list (REQUIRED_BALANCE_KEYS in tools/validate_data.gd) of every balance.json key the game reads. If a key is missing from balance.json, the game silently runs on a hardcoded fallback that can drift out of sync — exactly the bug this list exists to catch. Two consequences for you:

Recipe categories

A recipe's category must be one the game renders: tools, structures, machines, components, processing, or village. Any other value and the recipe becomes invisible — it exists but shows up nowhere. The validator flags this.

The golden rule still applies

Do not "fix" a value by editing the matching constant in a .gd script. The JSON overrides the constant; editing the script leaves the JSON in charge and your change ignored. Tune the JSON, run the checks, play it.