# Splatter Tennis — session rules 1. **Read `PROJECT.md` first** — it is the only planning document: how Oliver's feedback is handled, every invariant, every settled feel decision, what is open. Do not re-derive or contradict it. 2. **`node test.js` must pass before every commit.** It is also a Dockerfile build stage, so a red suite blocks the deploy — but run it locally first. New state-machine behavior gets a test. 3. **Mode transitions live in `beats.js` only** (duel/horde/boss/changeover). One mode owns the court; every `start*` refuses while another mode runs. A new mode starts there and refuses there. 4. **Every push deploys** (Coolify → splatter-tennis.wuwi.one). Bump `main.js?v=N` in index.html on every push or phones keep the old build. New top-level files must be added to the Dockerfile COPY. 5. **Zero dependencies, no build step, no framework.** This is a settled decision — solve it with what is already in the repo. The Capacitor shell (`package.json`, `ios/`) is the ONE exception and it is outside `src/`: **no package JS ships.** Game code calls native plugins through the injected bridge — `Capacitor.nativePromise(name, method, opts)`, see `src/haptics.js` — never an `import` of a package (a bare specifier 404s on the web) and never `Capacitor.Plugins.*` (empty without @capacitor/core, so it fails SILENTLY). `npm run ios` builds the app.