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

- Every JSON file parses and has no UTF-8 BOM (see the gotcha below).
- Recipe inputs and outputs point at real materials/tools/machines, amounts are above zero, and the
  `category` is one the game actually renders.
- Reward codes are keyed by a real SHA-256 hash and grant only things that exist.
- Building costs, discovery layers, and food-trade materials all reference real ids.
- Every required `balance.json` key is present (this is the anti-drift check — see
  `REQUIRED_BALANCE_KEYS` below).
- The apothecary brewing schema is internally sane.

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

- **Do not delete a balance key** to "turn something off". Removing it fails the validator and, if
  it somehow shipped, would hand control back to a stale constant. Set the value, do not remove the
  key.
- **If you are adding a brand-new balance key** that a script starts reading, add its name to
  `REQUIRED_BALANCE_KEYS` too, or the validator cannot protect it. (This is the one time a data
  change also touches a `tools/` script.)

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

## Related
- [Balance Docs Overview](/docs/underroot/balance-docs-overview)
- [Where Balance Lives](/docs/underroot/where-balance-lives)
- [The Economy Model](/docs/underroot/the-economy-model)
- [The Levers That Matter Most](/docs/underroot/the-levers-that-matter-most)