Files
gmtool/README.md
T
slaguru666andClaude Opus 4.8 e79f643409 feat: native iPad wrapper — Capacitor iOS scaffold
Wrap the PWA in a thin native iOS shell (Capacitor 8):
- capacitor.config.json (webDir dist, appId uk.timevans.thedirector,
  iOS safe-area contentInset + theme background).
- @capacitor/{core,cli,ios} as devDependencies (web app keeps zero
  runtime deps); ios:add / ios:sync / ios:open / ios npm scripts.
- main.js skips service-worker registration under Capacitor
  (window.Capacitor.isNativePlatform) — assets are already local.
- iOS <meta> hints in index.html; .gitignore for native build output.
- docs/native-ipad-wrapper.md: full Xcode + CocoaPods build guide,
  incl. the keep-awake plugin note for the wake toggle in WKWebView.

The iOS project generation/build (npx cap add ios → Xcode) needs a Mac
with full Xcode + CocoaPods, which this env lacks; it is documented for
the user to run. tests/tools/capacitor-config.test.js guards the config.
211 tests pass; build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 23:55:20 +01:00

155 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
- **`<gm-shell>`** — 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.<namespace>.<key>`) + 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, CoC d100, BRP d100, VANITY d6, Panic & Glory, Dee Sanction) + registry |
| `src/npc/` | NPC generator (`generator.js`) + genre table packs (`packs/noir.js`) |
| `src/art/` | Pencil-art library search (`search.js`) + tagged manifest; online `generate.js` (configurable endpoint) + `library.js` (persisted, capped generated-art cache) |
| `src/clues/safety-net.js` | Clue safety-net engine (essential-gap + solvability) |
| `src/con/schedule.js` | Pure convention-schedule analysis (live / up-next / done / upcoming) |
| `src/components/con-hub.js` | `<con-hub>` — the convention landing screen; deep-links into a scenario |
| `src/scenarios/index.js` | Scenario registry (`getScenario(id)`) the hub deep-links through |
| `src/con/` | Convention data (`continuum-2026.js`; `example-con.js` is a fully-timed template) |
| `src/components/director-rail.js` | `<director-rail>` — the always-on pacing bar (🏠 🎲 👤 ✏️ 🔍 👥 ☕ chips; ☕ shows an on-break badge) |
| `src/components/dice-tray.js` | `<dice-tray>` — pack selector + die picker + roll + verdict |
| `src/components/npc-tray.js` · `art-tray.js` · `prop-viewer.js` · `clue-net.js` · `cast-tray.js` · `break-timer.js` · `parking-lot.js` | The tray tools (`cast-tray` = roster w/ tap-to-reveal secrets; `break-timer` pauses the clock so breaks never poison drift; `parking-lot` = timestamped thread capture) |
| `src/core/wake-lock.js` | Screen Wake Lock controller (keep the tablet awake; re-acquires on foreground; graceful no-op where unsupported) |
| `src/components/gm-shell.js` | `<gm-shell>` — root wiring + persistence |
| `src/scenarios/` | Scenario data files (six Continuum slots: `afterimage`, `day-one`, `vain-crown`, `silvery-moon`, `chopper`, `princes-bride`; `example-with-clues.js` is a template); `index.js` is the registry |
| `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.
### Generating a scenario from markdown
Instead of hand-writing the module, author a scenario in a structured markdown file and generate it:
```bash
npm run gen:scenario -- input.md src/scenarios/my-scenario.js # or omit the output path to print
```
The tool (`tools/scenario-md.js`, wrapped by `tools/md-to-scenario.mjs`) is a deterministic parser —
frontmatter → `meta`, and `## Timeline` / `## Clues` / `## Cast` lists → the arrays. See
[`tools/example-scenario.md`](tools/example-scenario.md) for the full format; in short:
```markdown
---
id: my-scenario
system: year-zero
players: 4
playMinutes: 210
---
## Timeline
- [0] The opening beat {hard} #a1-open ← [minutes] label, {hard}, optional #id
- [35] Interview | cut: skip the tea ritual ← | cut: … → cutHint
## Clues
- The door-cam face {essential} {act: Act One} | fallback: the monologue cards
## Cast
- MARY FLETCHER — Innkeeper; saw the watchers | secret: she saw Crowe sew
```
IDs are auto-slugged from the label/name when not pinned with `#id`. The output is validated against
`validateScenario` before it is written. It emits app data only, not prose — freeform con docs are
not parsed; you write the structured markdown. The `tools/` directory is dev-only (never bundled).
## 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** — dice rule-packs (CoC d100, VANITY d6-pool, Panic & Glory, Dee Sanction) + tray pack selector. ✅
4. **Slice 4** — the clue **safety-net** (essential-clue gap tracker + fallbacks). ✅
5. **Slice 5** — the convention **hub** (all slots, live "live now / up next / done", deep-links into each scenario). ✅ All **six** Continuum 2026 slots ported and scheduled with real times.
6. **Native iPad wrapper** — a Capacitor iOS shell around the PWA is scaffolded (config, scripts, integration). Generate + build it on a Mac with Xcode + CocoaPods: see [`docs/native-ipad-wrapper.md`](docs/native-ipad-wrapper.md). ✅ (config) / ⬜ (iOS build needs the toolchain)
**The full design is now built.** All §5 tray tools (dice with 6 packs, NPC, art with online Generate, clue-net, cast, break timer, parking-lot, wake-lock), the con hub, six ported Continuum scenarios, the markdown → scenario-data generator, and the native-wrapper scaffold.
### Online art "Generate"
The art tray can generate new pencil art at the table and cache it into the searchable library (offline
thereafter). It needs a **GM-configured** image-gen endpoint — nothing is hardcoded and no key is baked
in. Paste your endpoint URL into the tray's ⚙ field (persisted). The endpoint receives `POST { prompt }`
and must reply JSON with an image under `image` | `src` | `url` | `dataUrl` (a `data:` URL makes the
result offline-cacheable), plus optional `label` / `tags`. With no endpoint or no network the Generate
button is disabled and the library stays fully searchable — the network is never load-bearing.
## 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.*