Docs Decisions

Contents

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_*) through directory_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 classes GwSpec*, one class per file, snake_case files. Kits live in addons/gamewright/kits/<kit>/, each with a kit.json. Keep files small enough for an agent's context: split past ~600 lines.
  • Pure core, thin nodes. Simulation logic lives in RefCounted classes over packed arrays with no node access, so it's testable headless. Nodes only present.
  • Declared order. Systems declare after, provides and consumes; 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 Error or Failed to load script in the output, not just on exit codes. A test written for a bug must fail with the bug put back. tests/standing.txt lists 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 through tools/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 -s script 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.

From docs/adr/0010-engine-code-and-tests.md in the Gamewright repository.