54 lines
3.1 KiB
Markdown
54 lines
3.1 KiB
Markdown
# NOT TONIGHT — instructions for Claude sessions in this repo
|
|
|
|
Papers, Please-style bouncer sim. Browser game: Phaser 3 + TypeScript (strict) +
|
|
Vite + Vitest. You are probably an **Opus 4.8 lane executor** — this repo is built
|
|
by parallel "lanes" coordinated through files, reviewed by Fable.
|
|
|
|
## Before writing any code
|
|
1. Read `LANEHANDOVER.md` — find the latest REVIEW block and any block for your
|
|
lane. Review instructions override lane files.
|
|
2. Read your lane file in `lanes/` (John/Fable will tell you which lane you are;
|
|
if not stated, ask — do not guess).
|
|
3. Skim `docs/GAME_DESIGN.md`, `docs/BUILD_PLAN.md`, and read `docs/CONTRACTS.md`
|
|
fully. Contracts are law.
|
|
|
|
## Hard rules
|
|
- Work ONLY on your lane branch (`lane/<name>`), only in your owned directories
|
|
(BUILD_PLAN.md §2) + your tests + LANEHANDOVER.md. **Never merge or push main.**
|
|
- Parallel lanes on one machine: use a git worktree
|
|
(`git worktree add ../not-tonight-<lane> lane/<lane>`), don't switch branches in
|
|
a shared checkout.
|
|
- Don't modify `docs/CONTRACTS.md`, `src/core/`, or `src/data/types.ts` after
|
|
Phase 0 — file a CONTRACT CHANGE REQUEST in the handover instead, stub locally
|
|
with `// TODO(contract):`, keep working.
|
|
- No `Math.random()` — SeededRNG streams only. No cross-lane scene imports.
|
|
Game logic in pure tested functions (`src/rules/`, per-lane logic modules);
|
|
scenes stay thin.
|
|
- End EVERY session: `npm run lint && npm run build && npm test` clean, append a
|
|
SESSION block to `LANEHANDOVER.md` (template in that file), commit, push your
|
|
branch. An unpushed session didn't happen.
|
|
- Remote is Gitea: `ssh://git@100.71.119.27:222/monster/not-tonight.git`.
|
|
|
|
## Dev notes (Phase 0 landed 2026-07-19)
|
|
- Gate: `npm run lint && npm run build && npm test` — all three must pass before
|
|
any push. Dev server: `npm run dev` (port 5199, config in `.claude/launch.json`).
|
|
- Key APIs (read the source, it's short): `core/EventBus` (`on` returns an
|
|
unsubscribe fn), `core/SeededRNG` (`rng.stream(name)` — NEVER `Math.random`,
|
|
eslint blocks it), `core/GameClock` (feed real ms via `update(delta)` from your
|
|
scene; `ageOn(dob, nightDate)` is the ID math), `core/meters` (emit
|
|
`meters:delta`, never mutate NightState), `patrons/generator`
|
|
(`generatePatron(ctx, clockMin, override?)`), `patrons/dollPlan` (pure,
|
|
testable) + `patrons/doll` (`renderDoll(scene, patron, pose)` → texture key),
|
|
`rules/drunk` (`drunkStage`). `StubBeatClock` needs an owning scene calling
|
|
`update(delta)` each frame.
|
|
- **Verifying in Claude's browser preview pane:** the pane tab reports
|
|
`document.hidden = true`, so Chrome suspends rAF and the Phaser loop freezes at
|
|
frame 0. Drive frames manually via the exposed debug handle:
|
|
`const b = performance.now(); for (let i = 1; i <= 900; i++) window.game.loop.step(b + i * 16.67)`
|
|
then screenshot/inspect. In a normal browser the game runs fine.
|
|
|
|
## Tone guard
|
|
The game is Aussie hospitality satire — affectionate, funny, with a moral undertow
|
|
(design doc §4.3). If content you're writing reads as mean-spirited rather than
|
|
absurd, soften toward absurd.
|