Docs Guides

Contents

Game Packs and documents

In Gamewright a game is a Game Pack: a folder of small JSON documents, plus any media they name. It holds no code, no Godot scenes and no scripts. One player runs every game, on the desktop and in the browser (ADR-0002). Because a game is only data, it can be checked before anyone plays it, run safely from anywhere, shipped without its own copy of the engine, and given a new look without touching how it plays.

A pack is a folder

Here is part of Tideline, the first game made this way:

tideline/
  game.json                        the game: its template, theme, style, world and player
  brief.json                       the brief: its pitch, pillars and core loop
  archetypes/sentry.archetype.json an Archetype: a kind of entity, here the machine
  brains/sentry.sm.json            a StateMachine: how the sentry patrols, notices and hunts
  items/driftwood.item.json        an Item
  recipes/campfire.recipe.json     a Recipe: three driftwood and two stones make a campfire
  rules/tideline.rules.json        a RuleSet: on an event, where a condition holds, do actions
  worlds/tideline.worldgen.json    a WorldGen: how the coast is generated, pass by pass
  models/sentry.model.json         a Model: a recipe the engine builds the sentry from
  sounds/hit.sound.json            a Sound: a recipe the engine synthesizes
  ui/hud.screen.json               a Screen: the HUD
  playtests/proof.playtest.json    a Playtest: a scenario every change must keep green
  .gamewright/                     what the tools make: bundles, reports, proposals

A file's name says what it is: <folder>/<slug>.<kind>.json, so driftwood.item.json is an Item wherever it sits. The folders are yours to arrange. The game's manifest, game.json, and its brief, brief.json, sit at the top. .gamewright/ holds only what the tools make, so keep it out of git: put .gamewright/ in the pack's .gitignore.

A document

Every document starts the same way: its kind, the schemaVersion, an id, a name for people, and meta (its status, where it came from, notes). The rest is the kind's own:

{
  "kind": "Item",
  "schemaVersion": "1.0",
  "id": "item.driftwood",
  "name": "Driftwood",
  "meta": {
    "status": "draft",
    "provenance": {
      "createdAt": "2026-09-30T00:00:00Z",
      "createdBy": "user",
      "license": "CC0-1.0"
    }
  },
  "description": "A grey length of sea-worn wood. Burns well once dry.",
  "bulk": 2,
  "stack": 10,
  "tags": ["fuel"]
}

Ids say what a thing is, with its kind first: item.driftwood, arch.sentry, rcp.campfire. Documents name each other by id, and a change to a list names its entry by id too, never by its place in the list, so an edit never lands on the wrong thing. Document kinds lists every kind, where it lives and its fields.

The template underneath

game.json pins a template: "template": {"id": "tpl.action_survival", "version": "1"}. The template brings the kits the game runs, the controls, the camera, the screens and its starting documents, so a game on it is playable from its first step. The game's own documents then replace the template's, one by one, by id.

What documents can say comes from the engine's kits: the components an entity can have, the actions a rule takes, the events it hears, the functions an expression calls. Conditions and formulas are expressions in a small language like Python's: self.health < 30, clamp(tune.alarm_radius * 2, 4, 20). When a game needs something no kit offers, the answer is a new kit of reviewed engine code that every game can use, never code inside one game.

Schemas are the contract

Every kind has a JSON Schema in schemas/v1/. The core checks every document against it, and the engine's own types are generated from the same schemas (ADR-0005). The rules that make documents easy to write and hard to get wrong:

  • Closed objects. A field the schema doesn't know is an error, with a suggestion when it is close to one it does.
  • Flat lists. A tree is a list whose entries name their parent, never objects nested deep.
  • Units in names. timeMin, heightM, windupMs: a number says what it measures.
  • Values in fields, logic in expressions. Numbers and ids go in fields; conditions and formulas go in expressions.

The core writes, the engine plays

Only the core ever writes a document, and always in one canonical form, so a change shows as a small diff and the same content always hashes the same. The Studio reads documents but never writes them. Check the form, or hash a document, with the gamewright command line that came with the Studio (installing says where it is):

gamewright fmt --check ~/Documents/Gamewright/Tideline
gamewright hash ~/Documents/Gamewright/Tideline/items/driftwood.item.json

Bake turns a pack into a bundle: the template and the game resolved into one set of documents, expressions compiled, everything hashed, in .gamewright/build/<hash>/. The same pack always bakes to the same bytes. The player loads a bundle, checks every hash, and refuses anything that is not a known kind of document or an allowed kind of media: it never loads a script from a game.

Locks and history

A document whose meta.status is locked is fixed; meta.locks fixes parts of one, by item and pointer, such as "keep the sentry's body, change its brain". The Director treats locked parts as given, and a change to one is refused. Lock from a terminal with changes lock, or ask the Director's deciding tools in Claude Code.

A pack is meant to be its own git repository. When it is, every change you approve is a commit with a Gamewright-Change: line naming the change, and changes undo applies its inverse.

From docs/guides/game-packs.md in the Gamewright repository.