Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MbtUPJ24P6vc6tE1x6RS3m
127 lines
6.5 KiB
Markdown
127 lines
6.5 KiB
Markdown
# game-kit
|
|
|
|
The shared infrastructure of Wiz-War and Waving Hands, extracted so the next
|
|
game starts with all of it in place: the hall, room codes and tables, the
|
|
Peanut Gallery (and the host's leave for it to talk), table talk, rematches,
|
|
seats that travel between devices by phrase and come back by the keeper's
|
|
link when a browser loses them, the keeper's bell, append-only ledgers
|
|
replayed through a deterministic engine, the rules revision, the reports
|
|
desk, the deploy script with its determinism gate, nightly rollup and
|
|
backup, Sentry with Slack alerts, and the visitors, pulse and reports
|
|
skills for Claude.
|
|
|
|
The game itself is one file. `src/lib/game/index.ts` implements `GameSpec`
|
|
(see `src/lib/game/spec.ts`); the server, the client store, the hall and
|
|
the ops tools never look past it. The template ships with a demo game,
|
|
High Card, so everything runs before you have written a line.
|
|
|
|
Beyond the required methods, a game opts into what it needs through
|
|
optional hooks, and without one the kit behaves as if it were not there:
|
|
`validate` (refuse a move with a reason), `seatsFor` (fewer seats for some
|
|
options), `settled` (drafts a player may replace until final), `rankable`
|
|
(a secret ranking before the deal, read by `create`), `botPress` and
|
|
`parting` (bots that talk, remember by notes and read the talk in
|
|
`botInput`), and `tally` / `tallyBegun` (the game's own lines in the tally).
|
|
`CONVENTIONS.md` says what each does.
|
|
|
|
## Start a game
|
|
|
|
./new-game.sh <slug> "<Name>" [port] [domain]
|
|
|
|
That copies `template/` to `~/projects/<slug>`, fills the name, port and
|
|
domain in, initialises git, installs dependencies, and prints the steps
|
|
that remain: write the game, fill in the hall and rules and guide, create
|
|
the droplet and the Sentry project, deploy. `CONVENTIONS.md` is copied to
|
|
the new project as `docs/conventions.md`; it is the house style.
|
|
|
|
Run the demo locally from a new project:
|
|
|
|
npm run dev:all # server and site together; Ctrl-C stops both
|
|
npm run server # the game server on its port
|
|
npm run dev -- --host
|
|
|
|
## What is where
|
|
|
|
template/
|
|
src/lib/game/spec.ts the contract a game implements
|
|
src/lib/game/index.ts the demo game; replace it
|
|
src/lib/net/ client.ts (calls, held seats, socket), room.svelte.ts (the room store), talk.ts, view.ts
|
|
src/lib/components/ Hall, Lobby, TableTalk, ReportSlip, Board (demo; replace it)
|
|
src/routes/ the hall, /join/[code], /rules, /guide
|
|
server/src/ index (routes), rooms (game-agnostic), store (ledger), reports (the desk), tally, ratelimit, and their tests
|
|
deploy/ deploy, setup, Caddy, systemd, cron, gate + replay, backup, rollup, pulse, visitors, Slack alert, reports tools
|
|
.claude/skills/ <slug>-visitors, <slug>-pulse, <slug>-reports
|
|
|
|
Placeholders: `__SLUG__`, `__NAME__`, `__PORT__`, `__DOMAIN__` are filled by
|
|
`new-game.sh`; `__IP__`, `__SENTRY_DSN__`, `__SENTRY_PROJECT_ID__`,
|
|
and `__SENTRY_SLACK_INTEGRATION__` wait until those things exist.
|
|
|
|
## What the kit is, and is not
|
|
|
|
The model is copy and diverge. A new game starts as a copy of the template
|
|
and then goes its own way; the two games that came before it are not
|
|
instances of the template and were not rewritten to be. The kit holds the
|
|
reference copy of the shared parts and the conventions they follow, so a
|
|
third game starts from the best current version of everything.
|
|
|
|
Drift is meant to be visible, not prevented:
|
|
|
|
./drift.sh ../waving-hands # how far each shared file has wandered
|
|
|
|
`drift-map/<slug>.txt` pairs files when a game's layout differs from the
|
|
template's. When a game grows something every game should have (the
|
|
gallery and the reports desk were both born in one game and ported to the
|
|
other), bring it back here, with placeholders, so the next game inherits
|
|
it, and read the drift before assuming the kit has it.
|
|
|
|
The contract is simultaneous rounds: every seat that needs input submits,
|
|
then the round resolves; turn-at-a-time games fit as the case where one
|
|
seat needs input at a time. A game of single commands with out-of-turn
|
|
interruptions, Wiz-War's shape, does not fit and keeps its own server.
|
|
|
|
## The hall-wide notice
|
|
|
|
Every game on the shared host shows the same notice at the top of its
|
|
pages when there is one: maintenance, a new game, anything the whole
|
|
family should hear. The keeper sets it on the host,
|
|
|
|
hall-announce "Kestrel's Hall is down for five minutes of maintenance" --at "2026-09-30 11:00 UTC"
|
|
hall-announce --clear
|
|
|
|
which writes /var/lib/hall/announcement.json; each game's site serves it at
|
|
`/hall/announcement.json` (a `handle_path` in its Caddy site file), and
|
|
`Announcement.svelte`, in the template's layout, shows it with the time in
|
|
each reader's own clock. A reader may dismiss one notice; the next still
|
|
shows. `hall-announce` is installed by `gamehall/deploy.sh`.
|
|
|
|
## The family
|
|
|
|
`family.json` lists every game of Kestrel's Hall: slug, name, address,
|
|
one-line pitch, player count and origin, plus `hall`, the address of the
|
|
family's landing page. Each game ships a copy as `static/family.json` (the
|
|
deploy script takes the kit's latest when the kit is beside the game) and
|
|
its about panel lists the others under *More games in Kestrel's Hall*, the
|
|
heading linking to the family's landing page. Add a game here, then
|
|
deploy the games.
|
|
|
|
That landing page, https://gamehall.kestrelsnest.social, is one static page
|
|
in `gamehall/site/` with a card per game, written by hand. It is served by
|
|
Caddy on the shared host beside the games (`gamehall/gamehall.caddy`) and
|
|
published with `gamehall/deploy.sh 159.203.115.252`. A new game is not in
|
|
the family until it has its card there too.
|
|
|
|
Its visitors are counted from Caddy's log by `gamehall/ops/` (installed by
|
|
the same deploy): `gamehall-visitors.sh` on the host for today, and a
|
|
nightly rollup line in /var/lib/gamehall/rollup.jsonl. The skill
|
|
`gamehall-visitors` reads them. A new game on the shared host is added to
|
|
`GAMES` in `gamehall/ops/traffic.py` so the digest counts the people the
|
|
hall sends it.
|
|
|
|
The hall also keeps the night on the shared host: `hall-nightly.sh rollup`
|
|
at 00:12 UTC and `hall-nightly.sh backup` at 07:23 UTC (`gamehall.cron`)
|
|
run the hall's own rollup and every game's rollup and backup found in
|
|
/usr/local/bin, and check in with Sentry Crons once for each job, monitors
|
|
`hall-rollup` and `hall-backup` in the `gamehall` project, from the URLs in
|
|
/root/.hall-sentry-cron-rollup and /root/.hall-sentry-cron-backup. A game's
|
|
own cron file and check-ins are for a box of its own.
|