ADR-0010: Engine code standards and test tooling
- Status: Proposed
- Date: 2026-09-30
Context
Reelwright's ADR-0010 settled typed GDScript, gdUnit4 and a script-error scan on Godot 4.7.2. unspent-ortho adds lessons from 206k lines written by parallel agents in 16 days (research/unspent-ortho-study.md §5–6):
- Tests "passed" while a script error stopped them halfway.
- Untyped operators crashed on worker threads.
- Quitting with pool tasks running hung CI for an hour.
- Numeric load order hid dependencies between systems.
- Files grew to thousands of lines.
- Parallel runs starved the box of memory.
- Tours rotted on CI, which has no GPU.
C# can't export to the web in 4.7 (confirmed in the docs), and Godot has no script sandbox.
Decision
- Typed GDScript only, warnings as errors (
untyped_declaration,unsafe_*) throughdirectory_rules{"res://addons/gamewright": 1, "res://addons": 0}. No C#. A GDExtension is allowed only for a measured hot kernel, and only if it ships wasm builds. - Names.
class_name Gw*, generated classesGwSpec*, one class per file, snake_case files. Kits live inaddons/gamewright/kits/<kit>/, each with akit.json. Keep files small enough for an agent's context: split past ~600 lines. - Pure core, thin nodes. Simulation logic lives in
RefCountedclasses over packed arrays with no node access, so it's testable headless. Nodes only present. - Declared order. Systems declare
after,providesandconsumes; the player sorts them. A system never looks another up by node name. - Threads only through an engine job helper: typed kernels over packed arrays, with a mandatory drain before quit.
- Tests. gdUnit4 6.2.1, vendored. Exit code 101 (orphans or warnings) fails. The runner loads
every script first and fails on any
SCRIPT ERROR,Parse ErrororFailed to load scriptin the output, not just on exit codes. A test written for a bug must fail with the bug put back.tests/standing.txtlists known failures with a cause, so a gate reports only new and fixed ones. - Every Godot run has a hard timeout that kills its process group, imports first (with a
stamp), uses a per-checkout
user://directory, and runs heavy jobs throughtools/heavy.sh, whose slots are separate from other projects'. - One hidden-input check,
tools/check-hidden-inputs.sh, is the only place its rules live. The core's test calls it. - The CLI entrypoint is a scene (
res://cli/cli.tscn), because a-sscript can't see autoloads. It reads user args, takes absolute paths only, and prints one JSON object as its last stdout line.
Consequences
- The first engine commits carry scaffolding (vendored gdUnit4, tools, CI). Every later commit benefits.
- Tours and perf runs need a scheduled local sweep, because CI has no GPU.