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

116 lines
3.7 KiB
Markdown

# 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:
```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 <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>`.