Files
hypercross 275ec8531d docs: add agent workflow and reference docs
AGENTS.md defines the session loop; docs/oracle.md and docs/games.md
cover table mechanics and the game package contract.
2026-09-13 16:41:26 +08:00

3.7 KiB

Game packages

Each game is a self-contained package under games/<id>/. It owns everything that is true about the game regardless of who is playing it: the rules, the spark tables, the character options, and any tooling the game needs. Campaigns own the play state and reference a game by id.

Layout

games/<id>/
  package.json        @solo/game-<id>, type: module
  index.js            the manifest (default export)
  game.md             authoritative rules the agent reads each session
  rules/              longer rulebook chapters, read on demand
  tables/*.csv        spark tables and complication tables
  characters/         backgrounds, playbooks, starting options
  scripts/            tool scripts and CLI commands

package.json should depend on @solo/oracle if the game's scripts need dice or table parsing:

{
  "name": "@solo/game-my-game",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "main": "index.js",
  "exports": { ".": "./index.js" },
  "dependencies": { "@solo/oracle": "workspace:*" }
}

The manifest

index.js default-exports the manifest. Every field is optional; the id always comes from the directory name.

export default {
  id: 'my-game',
  title: 'My Game',
  description: 'One line about the game.',
  rules: 'game.md',
  commands: {
    'my-command': {
      description: 'What it does.',
      run(args, ctx) { /* ... */ },
    },
  },
};

Commands

Each entry in commands becomes a journal <name> subcommand, available whenever a campaign running this game is in context. The handler receives:

  • args — { positional, flags }, already parsed.
  • ctx — { root, campaignsDir, gamesDir, campaignDir, campaign, game } plus ctx.findTable(name) and ctx.helpers (rollDie, rollExpr, oracle, parseTable, rollTable, tableRange, findTable).

Handlers may be async. Print to console.log; throwing exits non-zero with journal: <message>.

Commands are the recommended way to ship game tooling, because the agent can reach them through the one journal entry point. Standalone executables go in scripts/ for one-off jobs and are run with node.

What lives where

Content Location Reached by
Rules summary game.md the agent, every session
Rulebook chapters rules/*.md the agent, on demand
Spark/complication tables tables/*.csv journal roll <name>
Character backgrounds characters/ game commands, or the agent
Game tooling index.js, scripts/ journal <command>, node

Table lookup

journal roll <name> searches the campaign's tables/ first, then the game's. That lets a campaign override a game table without copying the rest. Tables the game ships are not copied into campaigns.

Validating

journal check --game <id> validates the package as well as its tables:

  • errors — overlapping table ranges, CSV parse failures, invalid or missing package.json, missing game.
  • warnings — range gaps, package name that is not @solo/game-<id>, missing "type": "module", missing index.js/tables//characters/, missing rules file.

It exits non-zero on errors. journal games additionally surfaces a manifest that fails to load as ERROR. Test each table with journal roll <table> --game <id> and each command with journal <command> --game <id>.

journal check --game my-game
journal game my-game

Creating a game

  1. Copy games/generic/ to games/<id>/.
  2. Rename the package to @solo/game-<id> and set id in index.js.
  3. Replace game.md, the tables, and characters/ with the game's content.
  4. Run journal check --game <id>, journal games, and journal game <id>.
  5. Start a campaign with journal new-campaign <name> --game <id>.