diff --git a/README.md b/README.md index 869bdb6..14c78ba 100644 --- a/README.md +++ b/README.md @@ -1 +1,102 @@ -# gmtool +# The Director + +An offline-first tablet app for running tabletop RPG sessions at conventions. + +Its permanent feature is the **Director Rail** — an always-on pacing co-pilot that keeps a +hard-clocked slot on time — sitting above a pluggable shell that hosts any game system and any +scenario. Built for face-to-face play: no server, no wifi, no install ceremony. Open it on the +tablet and it works. + +> Working name "The Director". Repo `gmtool`. First deployment: Continuum 2026 — but built as a +> reusable product for any convention, not one con's materials. + +## Status + +**Slice 1 — complete.** Proven end-to-end against the AFTERIMAGE pilot scenario: + +- **``** — root: owns the session clock, scenario, beat stamps, and persistence. +- **Director Rail** — session clock, "you are here" vs target with a live **drift** badge, next + hard trigger countdown, and one-tap **"Reached it"** stamping (no mental arithmetic mid-scene). +- **Dice engine** — one seedable core (any die: d4–d100, dF, pools, modifiers) plus pluggable + **system rule-packs** that interpret a roll. Ships with **Year-Zero** (6–9 = success, 10+ = two). +- **Scenario data model** — one structured file per scenario; the source of truth (see below). +- **Persistence** — namespaced `localStorage` with JSON export/import for backup and device swap. +- **Installable PWA** — a service worker precaches the shell so it launches offline. + +Deferred to later slices: NPC generator, art generator, the rest of the tray tools, more +rule-packs and scenarios, and the clue safety-net. See the roadmap below. + +## Quick start + +```bash +npm install +npm run dev # local dev server +npm test # Vitest suite (jsdom) +npm run build # static production build → dist/ +npm run preview # serve the production build +``` + +Requires Node ≥ 18. No runtime framework — the only dependencies are dev tools (Vite, Vitest, jsdom). + +## Architecture + +Vanilla ES modules and **Web Components** (light DOM) over a thin core of pure, deterministic +logic. All testable logic lives in `src/core` and `src/dice` and takes an injected `now` (ms) or +`rng` (`() => [0,1)`) — no `Date.now()` / `Math.random()` inside — so tests are fully deterministic. +Components are dumb renderers that emit events; the shell wires them together. + +| Unit | Responsibility | +|---|---| +| `src/core/store.js` | Namespaced `localStorage` (`gmd..`) + export/import | +| `src/core/clock.js` | Pure session clock: start / pause / resume / elapsed | +| `src/core/timeline.js` | Drift + next-hard-trigger analysis from the scenario timeline | +| `src/core/scenario.js` | Scenario validator | +| `src/core/format.js` | Elapsed + drift display formatting | +| `src/dice/roller.js` | Seedable dice core (pools, modifiers) | +| `src/dice/rulepacks/` | System interpreters (`year-zero.js`) + registry | +| `src/components/director-rail.js` | `` — the always-on pacing bar | +| `src/components/dice-tray.js` | `` — die picker + roll + verdict | +| `src/components/gm-shell.js` | `` — root wiring + persistence | +| `src/scenarios/` | Scenario data files (`afterimage.js`) | +| `public/` | `manifest.webmanifest`, `sw.js` (offline service worker) | + +## Scenario data model + +A scenario is one JS module — the single source of truth the Rail reads. The timeline is authored +in **relative minutes from Start** (con slots begin late; relative timing keeps drift honest). + +```js +export default { + meta: { id: 'afterimage', title: 'AFTERIMAGE', system: 'year-zero', players: 4, playMinutes: 210, slot: 'Fri · Slot 2' }, + timeline: [ + // targetMin = minutes after the GM taps Start + { id: 'a1-open', label: 'The pier — the case lands', targetMin: 0, hardTrigger: true }, + { id: 'a1-parlor', label: 'Parlor 88 interview', targetMin: 35, cutHint: 'Summarise the ledger; skip the tea ritual' }, + { id: 'a2-doorcam', label: 'Door-cam reveal', targetMin: 120, hardTrigger: true }, + // … + ], + clues: [], cast: [], props: [], // reserved for later slices +} +``` + +`meta.system` selects the dice rule-pack. `hardTrigger` marks immovable beats; `cutHint` is what to +compress if you reach a beat behind schedule. + +## Roadmap + +1. **Slice 1** — shell + Director Rail + data model + dice engine (Year-Zero). ✅ +2. **Slice 2** — NPC generator (offline genre tables) + art generator (offline pencil-art library). +3. **Slice 3** — more rule-packs + the remaining convention scenarios. +4. **Slice 4** — the clue **safety-net** (essential-clue gap tracker + fallbacks). +5. **Slice 5** — the convention **hub** (all slots, live "up next / live now"). +6. **Later** — markdown → scenario-data generator; native iPad wrapper. + +## Docs + +- Design: [`docs/superpowers/specs/2026-07-20-gm-director-design.md`](docs/superpowers/specs/2026-07-20-gm-director-design.md) +- Slice 1 plan: [`docs/superpowers/plans/2026-07-20-director-slice1.md`](docs/superpowers/plans/2026-07-20-director-slice1.md) + +--- + +*Personal convention-play tooling. System rule-references paraphrase their respective rulebooks for +private table use.*