Docs Decisions

Contents

ADR-0003: The expressivity ladder, and where code may live

  • Status: Proposed
  • Date: 2026-09-30

Context

"Any game any user wants" needs more than parameters. Every game description language we surveyed runs out in the same three places: conditions and formulas, sequencing over time, and genuinely new mechanics (research/generative-stack.md §2.2). Each language answers the last one with code. The question is who writes that code, and where it runs.

ADR-0002 keeps code out of published games. That alone would cap Gamewright at whatever the engine already knows.

Decision

Games climb a ladder, and each rung is tried before the next.

Rung What Who writes it Where it runs
L0 Parameters numbers, enums, id refs and tags in documents Director, people everywhere
L1 Expressions typed, pure Expr strings for conditions, magnitudes and weights (a Python/GDScript subset, with whitelisted functions) Director, people everywhere
L2 Graphs flat rule sets, state machines, behavior trees, timelines, dialogue and quests, interpreted by the engine Director, people everywhere
L3 Kits typed GDScript in the engine repo: components, actions, events, Expr functions, generator passes, BT tasks. Each kit registers its vocabulary with prompt-quality descriptions coding agents and people, reviewed, with tests everywhere, once released
L3′ Local kits the same kit interface, inside a Game Pack's kits/ folder the owner's own coding agent only on the owner's machine, in dev and desktop builds that opt in with a trust flag
L4 Sandboxed scripts per-game Lua via a wasm-capable GDExtension — deferred

Rules:

  1. Expressions are ours, not Godot's Expression class. That class can call any method on the objects passed to it. It's untyped, and its errors surface only at runtime. The core parses and typechecks each Expr once at bake and compiles it to a postfix array. A small GDScript stack VM evaluates it with a step budget. One grammar, one parser.
  2. Graphs are bounded by construction. each queries carry a max, event chains stop at a depth limit, and each tick has an action budget. Rules never wait; waiting belongs in a timeline or a state machine.
  3. Vocabulary is generated from kits. Schema enums and union branches for components, actions, events, functions, passes and tasks come from kit registrations. The Director's tools can never offer what the runtime can't do.
  4. A vocabulary gap is a document. When the Director can't express a request with rungs L0–L2, it files a KitRequest: the need, examples, a proposed registration. In the owner's workspace a coding agent can build it as a local kit at once. Promoting it to an engine kit is a normal reviewed change. The rate of KitRequests is the engine's roadmap signal.
  5. Published games carry no code. The platform and the shared player reject a bundle that depends on local kits. A local kit reaches other players only by becoming an engine kit.

Alternatives considered

  • Per-game GDScript written by the AI. Unbounded, but it can't be sandboxed, it can't be pointer-edited or locked, and "compiles" isn't "works": in the Mage study compile rate was anti-correlated with functional correctness. Kept only as local kits, where the owner accepts the risk on their own machine.
  • A pure interpreter with no expressions. Every formula would become an enum. GAS, GDevelop and Construct all grew an expression layer.
  • A Turing-complete JSON or visual language. It needs recursion (breaking ADR-0005), it carries the same safety surface as code, and Godot's VisualScript history is against it.
  • Sandboxed Lua now. A second language for the model, GDExtension on the web, and a new sandbox to audit. Revisit only if kits demonstrably can't keep up.

Consequences

  • The engine grows by kits. The fourth genre template should need mostly packs, not new kits; if it doesn't, the vocabulary is at the wrong level.
  • Kits need prompt-quality registrations, tests and versioning. The kit registry is a contract between the engine, the schemas and the Director.
  • Local kits give the owner "anything goes" on their own machine without forking the engine.

From docs/adr/0003-expressivity-ladder.md in the Gamewright repository.