通行証 · HALL PASS DOCS · PLAY

HALL PASS — read this before you touch anything

Three.js survivors-like. Students hunt curses in a school from 9 PM to First Bell at 7 AM. Live at https://hallpass.cc. Studio: Very Bad Games. Canon guard: ten rooms, one hour each; a run is one hour and its bell is just the top of the next hour. Only the tenth room ends at First Bell with a sunrise. Never write daybreak, sun, or First Bell into a mid-night hour.

Chris's tasks and feedback are on the Hall Pass board (private Trello in the free "Very Bad" workspace, beside Salty's: https://trello.com/b/eNxiGuGk/hall-pass). Hand-driven sessions read it at the start and whenever they run out of their own work: npm run relay (status). A card in 📥 To do is Chris's ask and comes before self-chosen work. Never act on 💡 Ideas & Backlog (the first list): read it only to see where Chris is heading. Every command is npm run relay -- <cmd>: take <id> when you pick one up, comment <id> "…" as you go, attachments <id> <dir> to save the card's screenshots, done <id> "<commit, live link>" shot.png once it's verified and shipped (a card you filed goes to ✅ Done (Claude)); 👍 Approved is Chris's, never move a card there. ask <id> "<recommendation first>" for anything that needs Chris's yes (money, anything outward, a real design fork); file "<title>" "<body>" for your own work if it runs long. The client is tools/relay/relay.mjs, a copy of Salty's: keep the two in step. The tuner, playtest and registrar loops stay in their lanes and don't take cards; the registrar's decision cards stay in npm run idea -- --cards.

This file is loaded into every agent session automatically. Keep it short. Depth lives in docs/MECHANICS.md (systems), HANDOFF.md (project state) and docs/SESSION-LOG.md (dated session history, spend, vendor facts, gotchas). If you change a system, update docs/MECHANICS.md in the same commit. Before touching music loops read docs/MUSIC-ENGINE.md first: the looping primitive is NOT the problem and four rounds were lost to assuming it was. Ask the graph before reading the tree: graphify-out/graph.json maps every module, function and doc (1,226 nodes). graphify query "<question>" answers architecture questions for a fraction of the tokens of a grep-and-read crawl; GRAPH_REPORT.md lists the god nodes and communities. Rebuild after big changes with /graphify G:\Skunkworks\hallpass --update.

Browser rigs start chrome-headless-shell, never full Chrome (no channel: 'chrome', no temp profile): a full browser with a new profile locked Chris's Windows account on Ghost (2026-10-13). Table and the one exception in docs/BROWSER-RIGS.md.

Everything an agent needs travels in this repo. Skills live in .claude/skills/, the dev-server config in .claude/launch.json, session history in docs/SESSION-LOG.md. Nothing load-bearing may live only in a machine-local ~/.claude — if a session learns something durable, write it here in that same session. Only .claude/settings.local.json (per-machine permissions) stays untracked.

src/config.js belongs to the tuning loop. Do not edit it in a hand-driven session without checking npm run tuner first — it says whether a robot is mid-landing. Everything else in src/ and all assets are yours; the tuner touches that one file and nothing else, so staying off it makes a collision impossible. playtest/** belongs to the playtest loop. Full map: playtest/OWNERSHIP.md.

The playtest lab. Two agents (Claude and Codex) measure this game under one set of rules: playtest/PROTOCOL.md. Read it before running or judging any experiment. scripts/playtest-runner.mjs is the stable interface and owns the metric definitions; each agent writes its own harness on top (scripts/insight.mjs is Claude's). Shared seed banks make both agents measure the same deterministic nights. Records are append-only manifests in _hallpass-archive/experiments/; conclusions in _hallpass-archive/reports/INSIGHTS.md. Claude's loop is /hallpass-playtest under /loop, from the repo root. The registrar (/hallpass-triage, cheap model) judges everything on the board that is unapproved or parked on a human: it approves tuning with the lever named, approves harness bugs, rejects the superseded, and escalates only design as one-question decision cards. Chris answers cards (npm run idea -- --cards, --decide=<id>:<letter>); nothing else reaches him.

Rooms are edited in 3D, not by hand — docs/LEVEL-EDITOR.md. npm run dev plus npm run editor, then /level-editor.html: drag props, stickers and floor patches, save straight into src/maps.js. It renders with the real buildWorld(), so what you drag is what ships. Dev-only, gated on !prod. MAPS moved to src/maps.js — config.js re-exports it, so imports are unchanged, but the tuner keeps config.js and the editor owns maps.js.

Directed gameplay footage is a solved problem — docs/CINECAM.md. Shots live in cine/*.json, the rig in scripts/cine{,-client}.mjs|js. It is dev-only and never enters the bundle. Read it before hand-recording anything: takes are reproducible only because four specific leaks are plugged, and cine.verify() is how you find out you broke one.

Heavy artifacts never enter this repo. Raw run JSON, screenshots, video and browser profiles go to ../_hallpass-archive/, which is its OWN private repo (MisterSands/hallpass-archive) with its own README — so telemetry is retained and versioned without riding along on every game commit. All of it regenerates from (seed, commit) because the sim is deterministic. Bulk artifacts in git already cost this repo 1.25GB of history and broke pushes once — never git add -A here.

Non-negotiables

  1. npm test must stay green. The suite includes determinism lockstep pairs and a snapshot round-trip — they are the canaries for the whole sim. If they fail, you changed rng consumption, and a saved run will no longer replay.
  2. The sim is deterministic and the view is not. src/sim.js owns logic and consumes mulberry32 in a fixed order. src/view.js owns rendering and may use Math.random() freely. Never call Math.random() in sim. Never put game state in view.
  3. Difficulty comes from enemy HP and speed, never spawn counts. More spawns make a survivors-like EASIER — spawns are the XP economy. This was measured: Night II at spawnMult 1.15 hit dawn at the median.
  4. Deploy only from main, only from dist/: npm run ship (= npm run build:prod, then npx netlify-cli deploy --prod --dir dist --site 84baf5f8-bf39-4a6f-8439-93b4e22ec37c, then node scripts/cf-purge.mjs, which purges Cloudflare's edge once it is in front and is a harmless warning until then — docs/CLOUDFLARE-PLAN.md). Standing rule (Chris, 2026-09-01): ship straight to production after every landed game change — no draft URLs, no asking — until there are live players (expected a few weeks out). Revisit then; draft URLs (netlify deploy with no --prod) come back for experiments. APK rule (Chris, 2026-09-09): do not rebuild or publish a new APK unless Chris explicitly requests one for a major release. Web changes still ship.
  5. Never regenerate the public sound library on build. scripts/sound-library.py runs on demand only, or it silently re-cuts reference takes that collaborators already downloaded.
  6. No text baked into generated video. Titles and names are composited in post so a clip can be re-cut in any language. Only diegetic text on props.

Tripwires that have actually bitten

Each of these cost real time. They are not hypothetical.

How to prove a change works here

The embedded browser pane does not composite. It cannot screenshot, does not run rAF, and freezes CSS transitions. So:

Conventions

HALL PASS by Very Bad Games · Solo Developer Chris Sands · All documentation