# Game packages Each game is a self-contained package under `games//`. 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// package.json @solo/game-, 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: ```json { "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. ```js 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 ` 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: `. 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 ` | | Character backgrounds | `characters/` | game commands, or the agent | | Game tooling | `index.js`, `scripts/` | `journal `, `node` | ## Table lookup `journal roll ` 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 ` 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-`, 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 --game ` and each command with `journal --game `. ``` journal check --game my-game journal game my-game ``` ## Creating a game 1. Copy `games/generic/` to `games//`. 2. Rename the package to `@solo/game-` and set `id` in `index.js`. 3. Replace `game.md`, the tables, and `characters/` with the game's content. 4. Run `journal check --game `, `journal games`, and `journal game `. 5. Start a campaign with `journal new-campaign --game `.