通行証 · HALL PASS DOCS · PLAY

HALL PASS — Agent Handoff

Current continuation: September 8 session handoff supersedes the release status below. See also development checkpoint for the repository cleanup and preserved experiments. The remaining sections are history.

Soundtrack completion (2026-09-07): Read the current section of Music Engine. Eight beds now include the previously excluded Between Bells 5/6 as Annex/Stairwell. Longer first passes feed authored repeat beds; boss cues retain complete phrases. The recipe and shared catalog are scripts/music-arrangements.json and assets/music/catalog.json. npm run test:music covers real decoding and built-game handoffs. Do not run legacy loop-cut commands over these assets or regenerate the public sound library.

Roster follow-up (2026-09-07): Character select now uses explicit portrait taps, selected-card lift/brightness, a top-right stats button, real equipped skill/shikigami slots, mastery and permanent-build progress. Landscape fits all four cards between a fixed header/footer; Next stays visible. Slot and upgrade actions open the matching store section and refresh the card without changing the selected team. npm run test:roster verifies the built game's purchase/equip/two-person stage handoff; npm run test:menus covers 408 states. See the updated roster contract in docs/MECHANICS.md.

Earlier menus, effects and character session (2026-09-07): Read the full session handoff first for current UI, particle repairs, domain effects, hybrid heads, procedural outfits, verification, deployment state and uncommitted work to preserve. The latest gameplay release covered there is f899bf1. The environment notes below predate that session.

Complete environment campaign (2026-09-05): all ten stages now have an enhanced environment. This session completed Cafeteria (1 AM), Pool (2 AM), Detention (3 AM), Boiler Room (4 AM), Science Wing (5 AM), and Rooftop (6 AM to First Bell). environment-kits.js registers these six presentation modules with the shared world. Every original collider, start, clear rectangle, zone and simulation rule is preserved; the only map edits are eight small cosmetic decals per room, placed and saved through the real 3D editor. The showroom has a direct stage selector and three views per room, with current/enhanced comparisons. Gameplay uses the same world and its existing hour-driven lighting. No paid generation or new external asset dependency.

Science Wing: lab-environment.js adds six epoxy workstations with real sink apertures, taps, glassware, microscopes and small burner variants; detailed reagent storage, cold-storage cabinets and lab trolleys fit all eleven original footprints. The 5 AM shell has layered windows, gas services and genuinely recessed fume-hood/eyewash bays, with openings in both wall and tiled dado. Raycast tests prevent the shell from hiding the equipment again. Separate physical floor maps use shipped grain with guarded loading. Four/two local lights and a cached key shadow provide clinical night lighting; low effects hides lights/dust. Review stage=lab&shot=laboratory|benches|fumehood.

Rooftop: roof-environment.js builds gravel and concrete maintenance paths, detailed fan units, glazed skylights, vents and antenna, retaining all seven original colliders. The crossing floor patches form one non-overlapping partition and share continuous world-scaled texture coordinates. Parapets, service bulkhead and a neighboring water tank sit outside the play square. School facades descend to a common city foundation; streets and a forecourt anchor a full four-sided 3D skyline. Below-arena backdrop geometry is tagged separately from gameplay-level architecture and tested below floor level. A continuous sky shader replaces panorama panels. setHour(p,dawnMix) drives sky, city, street tones and quantized PMREM updates. Only Roof accepts dawn; progress reaching 1 alone cannot create sunrise. Low effects hides practical lights; static art remains. Roughly 56.4K desktop/24.5K mobile world triangles and two local lights; these are geometry counts, not measured device FPS. Review stage=roof&shot=rooftop|combat|first-bell. FIRST BELL deliberately previews dawn, faces the sun, and uses the same dawn light colors/intensities as gameplay; switching views restores blue hour. Ordinary Roof play begins at 6 AM and reaches sunrise only through the existing final-hour logic.

Validation: per-kit tests cover footprint confinement, perimeter geometry, resource reuse, scalar map color spaces, mobile detail and quality limits. Integration tests exercise all 24 new stage × graphics × device combinations, complete resource disposal, reflection/shadow restoration, unchanged map input, the actual Pool ground hole, non-overlapping Roof paths and Roof-only dawn. Deferred-load tests prevent abandoned room loads from adding decals or leaking textures; the legacy skylight halo is now owned too. All 161 Node tests, production and single-file builds pass. Browser review covers each new room, desktop combat, 390×844 showroom/touch views, First Bell and stage switching. Fixed the Boiler PMREM blur warning during browser review. Physical-phone FPS and thermal behavior remain unmeasured. All implementation and tests live in the repository; screenshots and ad-hoc audit outputs stay outside it.

Boiler Room environment pass (2026-09-05): the 4 AM room now has riveted cylindrical boilers with pressure gauges, fire doors, copper service lines and valve wheels, paired pipe manifolds, floor grates and a tool bin. All eleven authored obstacle footprints remain unchanged. Brick walls, clerestories, piers and steam services stay beyond the play boundary; pipe runs sit visibly in front of the masonry and clear the service doorway. Scarred concrete, damp roughness and footprint-aligned hazard paint share one floor material. Eight small wear decals were saved through the editor. Furnace glows, restrained steam, four/two local lights and a cached key shadow support the industrial palette. Low quality pauses steam and hides lights. Review stage=boiler&shot=plant|floor|furnaces. Desktop visual review and headless geometry, camera-fit, material-space, footprint and disposal checks pass. Mobile uses simpler machinery. No simulation changes or paid assets.

Shared world cleanup now guards every asynchronous texture callback: a late load after room disposal immediately disposes its texture and cannot append floor decals or touch detached materials. Disposal is idempotent. New deferred loader regression tests cover enhanced ground, legacy water, faces, skies and authored decals. The world hour hook explicitly passes dawn only to Roof.

Detention environment pass (2026-09-05): detention-environment.js replaces eight vault proxies with school desks and chairs facing the teaching wall. A teacher cabinet/board, rear storage and bench fit all eleven original colliders. Olive composition tile uses separate albedo/height/roughness with photo grain, border inlays and chair wear in one floor plane. Chalkboards, slatted blinds, radiators, Room 3-C signage and a 3 AM clock form the perimeter cutaway. Eight small editor-authored decals replace random floor clutter. Four desktop/two mobile local lights, optional reflections and cached shadows follow the shared quality controls. Review stage=detention with shot=classroom|desks|blackboard; overhead fixtures hide in gameplay/DESKS. All tests pass. Desktop browser views and headless camera-fit, complete resource disposal, footprint and mobile-quality audits pass. Collision, player start and simulation remain unchanged. No paid generation.

Pool environment pass (2026-09-05): pool-environment.js builds the 2 AM natatorium with a recessed 1.7-unit basin, animated physical water, submerged lane markings, lane ropes, coping and ladders within the original water footprint. groundHoles removes the base sheet beneath the basin. Movement/projectile behavior, all five solids, three starting blocks and the (0,16) player start are unchanged. Mosaic tile, wet borders and drain strips share one deck material; layered poolhouse glazing and columns stay outside the fighting square. Eight small deck decals were saved through the editor. Four/two local lights and reduced mobile geometry respect the shared quality controls. Showroom: stage=pool&shot=poolhouse|deck|water; DECK targets the actual player start. Headless construction, all prop footprints, floor maps, full architecture camera-fit, quality and complete resource-disposal audits pass. Browser review confirms the recessed basin and animated water. No paid assets or simulation changes; physical-device FPS remains unmeasured.

Cafeteria environment pass (2026-09-05): the 1 AM room now uses cafeteria-environment.js for cream/rust terrazzo, folding lunch tables with integrated benches, a shuttered stainless serving line, inward-facing vending cabinets, a tray trolley and sorting bins. All 12 collision footprints remain unchanged. Eight small floor details were authored through the real 3D editor. Four desktop/two mobile unshadowed lights, a cached key shadow and optional PMREM support the physical materials; low quality disables lights and dust. Overhead fixture models appear in lunchroom/serving and hide in gameplay. Showroom links: stage=cafeteria&shot=lunchroom|tables|serving; the stage control is now a direct selector. Current/enhanced comparison remains available.

environment-kits.js registers presentation-only stage modules. world.js composes their architecture, footprint-local props, authored floor materials, optional ground cutouts, reflection target and lighting hooks. Base and zone geometry subtract cutouts without touching simulation obstacles. Quality, review, hour and disposal hooks propagate through the shared world; special water/sky meshes can opt out of generic shadows. View._nightSky forwards the existing hour and Rooftop-only dawn mix. Module registration enables showroom stages incrementally. No simulation/config changes or paid generation. Headless camera-fit, finite geometry, footprint, color-space and complete GPU resource disposal checks pass for current/enhanced desktop/mobile builds. The full Node suite and local build pass; desktop browser review covers all three views. Physical-device FPS remains unmeasured.

Second Floor environment checkpoint (2026-09-05): the approved fourth-room pass is implemented in src/halls-architecture.js, src/halls-props.js, and src/halls-surfaces.js. SECOND FLOOR remains the MIDNIGHT room; the wall clock reads 12:00. Sage ceramic wainscot, painted block, numbered classroom doors/glazed transoms, notice boards and wall utilities surround the locker lanes. A north stairwell vestibule at x=22 recedes beyond the playable boundary; the south wall is a low cutaway. Detailed locker banks and furniture preserve all nine original collider positions/dimensions, the 18×14 clear center and the 21.6-unit central lane. The floor uses pale worn 2-unit composition tiles with photographed grain from the shipped lino.webp; muted sage checker borders, lane inlays and the north entry mat share one floor plane. Fluorescent fixture rows at x=-20/0/20, z=-23/0/23 use four unshadowed spots on desktop/two on mobile, one cached key shadow and a PMREM reflection environment. Fixture models appear only in CORRIDORS/STAIRWELL review views; gameplay and LANES keep overhead sightlines clear. Low effects hides local lights, shadows and dust. Review /showroom.html?stage=halls&shot=corridors&graphics=enhanced, also lanes and stairwell; the showroom cycle is CAST → YARD → GYM → LIBRARY → HALLS. Eight small floor details were authored and saved through the real 3D editor. Geometry, camera-fit, floor and resource-disposal audits passed. All 130 tests and production/single-file builds pass. Browser review covered moving combat, 390×844 low-effects touch, comparison and the full showroom stage cycle; checked logs contain no warnings or errors. Physical-phone FPS is unmeasured. No paid generation or new external asset dependency.

Library environment checkpoint (2026-09-05): the approved third-room pass is implemented in src/library-architecture.js, src/library-props.js, and src/library-surfaces.js. THE LIBRARY remains the 11 PM room. Walnut and brass wall panels, recessed moonlit windows, a circulation alcove/card catalog and an 11 PM clock surround the existing stack aisles; the south cutaway keeps the gameplay camera clear. Detailed bookshelves, reading benches, book trolleys and a refreshment cabinet preserve all 14 original colliders and their positions. Eight small floor details were saved through the real 3D editor. The existing parquet image repeats continuously in non-mirrored 8-unit tiles beneath an aged varnish wash; green runners, trim and roughness share the same floor material plane. Banker lamps use four unshadowed point lights on desktop/two on mobile, with perimeter dust and one cached key shadow. The showroom cycles CAST → YARD → GYM → LIBRARY; review /showroom.html?stage=library&shot=reading-room&graphics=enhanced, also aisles and catalog. AISLES matches the real indoor gameplay camera; architectural views include the full north alcove. Current/enhanced comparison remains. Headless construction, camera-fit, quality and disposal checks passed. All 127 tests and production/single-file builds pass. Browser QA covered moving combat, 390×844 low-effects touch, comparison and room switching; no new runtime warnings/errors. Physical-phone FPS is unmeasured. No new external asset or paid-generation dependency.

Gym environment checkpoint (2026-09-05): the approved second-room pass is implemented in gym-surfaces.js, gym-props.js, and gym-architecture.js. Maple/varnish with navy court paint, detailed blue retractable bleachers, padded glass-backboard hoops, stitched mats/vaults and ball storage, masonry and windows, amber scoreboard, warm high bays and cool moonlight. All 13 collision objects and bleacher escape routes retain their original geometry; only six cosmetic decal placements were authored through the editor. Ceiling fixture models hide during combat, with lighting active, to preserve visibility. Review /showroom.html?stage=gym&shot=arena&graphics=enhanced (also court and bleachers). No paid generation or new external asset dependencies. All 124 tests and production/ single-file builds pass; desktop combat, portrait low-effects and environment switching were checked in browser. Physical phone FPS unmeasured.

Yard environment checkpoint (2026-09-05, Mac): recovered the September 4 storm-render work from the existing commits and built a new 3D campus and detailed furniture on top. src/yard-campus.js, src/yard-props.js, and src/yard-sky.js own the architecture, cached props and continuous sky. Review /showroom.html?stage=yard&shot=entrance for the school, shot=campus for the whole environment and shot=court for actual combat framing. Enhanced is the default. The follow-up reorganized the Yard through the 3D editor: entrance shelter/paired benches, wider court, separate track, south spectator apron and east equipment pad. All 13 solid kinds/dimensions remain, with new positions and orientations; 32×20 clear center and ≥7-unit corridors remain. ground-layout.js partitions overlapping ground surfaces to remove z-fighting without depth-offset tricks. Court keys now face inward. The editor has an object selector and reliable inspector blur commits. All 121 tests and production/single-file builds pass; desktop gameplay, portrait low-effects, mobile showroom geometry, Gym and Yard→Cast restoration were visually checked. Real-phone FPS remains to be measured. ?nofx=1 now survives saved settings. The new art is generated by source code with no external generation spend or missing machine-local art dependency. Full notes: docs/SESSION-LOG.md.

Project root: G:\Skunkworks\hallpass Status (2026-09-03): Ten-room playable build with the club, electives, shikigami, authored geometry and the Edge economy. npm test and production + single-file builds are green. The first-run lesson, exact resume/one-tap return flow and Rooftop-only First Bell records are live in source; the balance lab now separates world/policy RNG and validates promoted evidence. Physical-phone and external-player calibration remain required.

Store plumbing (2026-09-02): RevenueCat + AdMob + OneSignal wired behind isNative() (src/services.js holds the ids; every service is dark until its placeholder is replaced). Rewarded revive (THE CHARTER'S FAVOR), a capped between-hours interstitial, supporter + fragment packs, a 9 PM push. The Android debug APK builds here; ios/ exists and builds on the Mac. Dashboard checklist: docs/STORE-PLUMBING.md. The HallPassAdTracker bridge (plugins/hallpass-ad-tracker) makes the ads count as RevenueCat Ads — Android compiled here, Swift twin for the Mac. The app shell ships from dist-app/ (53 MB, npm run build:app); real icons from resources/; store paperwork drafted in store/; privacy policy live at hallpass.cc/privacy. Three bets shipped 2026-09-02 evening: INSURANCE (pre-run Second Heart per room, 40 + 20/hour in fragments or an ad; rooms now pay bonusMult on every gem), THE FAVOR (post-death: +60 s on the bell, 30% haul dropped), REROLL (two free a day, then fragments or ad) — INSURANCE/FAVOR/REROLL in config.

Direction (set by Chris, session 2): the goal is no longer parity with Curfew — it is to beat Solomon's Boneyard. Graphics came first (licensed FX + font, below); then session 2 built the first two pieces of the design spine:

Session 2 later shipped the build-depth layer:

Higgsfield image pipeline (validated 2026-08-24, 8 credits total). nano_banana_pro at 2 credits/image, ~20 s, 2048². Three smoke tests, all now shipping in the game: ground texture (measured: tiles within its own noise floor, centre-vs-corner brightness delta 2.4/255 — no baked lighting), a 4×4 technique icon sheet (16/16 correct first try, sliced by scripts/bake-icons.py and wired into the level-up and shop cards), and a decal sheet. The one real gotcha: asking for a transparent background makes the model paint a checkerboard; ask for flat chroma magenta instead and it keys perfectly — recipe and both prompt rules are in ASSET-BRIEF.md. Seedance video preflights at 45 credits per 5 s 1080p clip, so cinematics stay a separate decision. Drop new art in assets-src/incoming/.

Controls are now true twin-stick (Boneyard fidelity, set by Chris 2026-08-24). Fixed stick bases in the bottom corners on touch — left moves, right fires in the direction it points, fully decoupled (walk left while shooting right). No stick held = no shots, which restores the tactical choice of running dry. Desktop: mouse aims, hold LMB to fire. Gamepad right stick likewise. An AUTO-FIRE setting (default off) restores continuous fire with auto-aim for accessibility. sim.firing gates _weapon(); the cooldown is held at the ready while not firing so the first shot on press is instant rather than a banked burst. Tap-to-dash on the right half is gone — it conflicted with firing; use the STEP button.

The yard is a real stage now (2026-08-24). MAP in config.js holds two things: zones (rectangles of a named ground material — court, concrete apron, cinder track, grass field, over a base asphalt sheet) and obstacles (13 pieces of solid schoolyard furniture: planters, dumpster, benches, bike rack, shed, low wall, two hoop posts). Sim._clearObstacles() pushes any circle out of the boxes — verified exact: walking east into the shed stops at 23.23, precisely the face minus the player radius. _blocked() makes tall obstacles stop projectiles both ways, so cover is real and symmetric. Best bit: an enemy in charge state that hits scenery is staggered into recover — baiting a brute or a boss charge into a planter is a genuine tactic (verified: a charge died flush against the south wall and never reached the bait). Nothing sits inside the centre court (|x|<16, |z|<10) so the main fight stays readable, and gaps are >=7 units so a 1.7-radius boss can never wedge. ~30 scattered decals break up texture tiling. Keep those invariants if you edit the map.

Art now shipping (all generated through the Higgsfield MCP, 28 credits total): 5 tiling ground materials, 12 keyed decals, 28 technique/perk icons, 4 student portraits (roster key visuals). scripts/bake-art.py does ground/decals/portraits, scripts/bake-icons.py slices icon sheets; both read assets-src/incoming/ and write assets/. Materials are colour-graded into the night palette at runtime via NIGHT_GRADE in world.js — the generator lights them neutrally, and ungraded they read as a collage.

Dev-server gotcha, fixed: scripts/build.mjs --serve used to read index.html once at startup, so every rebuild rewrote dist/ with stale HTML and CSS edits silently never appeared. It now re-reads the shell inside writeHtml() and watches index.html + assets/. If a style change ever seems ignored, check that first.

Five stages (2026-08-24). MAPS in config.js now holds five arenas; MAP remains an alias for the first. Each map carries size (square half-extent, max 40 — the sim grid is fixed at the yard's size), env (yard/gym/halls/roof/pool — picks the dressing and the View.ENV_TINT colour grade), spawnMult/bonusMult (small maps spawn slower and pay more at dawn), base + zones (ground materials), clear, decals, optional start (pool spawns you on the south deck) and optional props (drawn like obstacles but non-colliding — used for the pool's starting blocks after flush obstacle pairs proved they can hand the player back and forth; the player push-out now also runs a second pass when the first one moved anybody).

THE YARD (free) · THE GYM (◆150, bleachers are tall cover, warm sodium grade) · SECOND FLOOR (◆300, two 38-unit locker banks make three lanes; lockers/vending faces are photographic textures tiled at ~1 unit per door via facePlane(..., repeatX)) · THE ROOFTOP (◆500, smallest and hardest, city panorama from assets/sky/skyline.webp) · THE POOL (◆800, the water is an obstacle that blocks movement but never projectiles — shoot across what curses must walk around).

Stage select sits between PICK A STUDENT and the run; thumbs are real engine captures in assets/stages/. Save gained stages/stageBest/stageCleared (same SAVE_KEY, migrated). ?map=<id> + ?bot=1 bypasses unlocks for testing. node scripts/balance.mjs 6 --map=<id> sweeps one stage; all five hold the band (novice ~3–5 min medians, competent 5–8 with occasional dawn wins; roof is the spiciest). XP gems are now the generated essence-wisp sprites (assets/sprites/), octahedra remain as fallback. Structured floor textures (planks/tile grids) never wrap — they use MirroredRepeatWrapping, see MIRROR in world.js.

Boss splashes + UI pass (2026-08-24, late). All nine special-grade curses have generated splash art (assets/bosses/*.webp, slug + one-line tag on each BOSSES entry). On a boss spawn ui.bossSplash() slides a full-width cinematic strip in from the right — art right, name over the black left third, red rules top and bottom — and falls back to the old text banner if the art is missing. ui.hideSplash() runs on quit.

The DOM UI got its identity pass: slab-underlined headings with a red slash, ghost watermark words behind each menu (STUDENTS / STAGES / SHOP / EXPELLED / FIRST BELL), notched cards with accent spines + staggered entrance + hover lift, caution-stripe locked states, button shine sweeps, chipped/plated HUD (name plate, segmented HP, clock slab, boss name plate, frag chip), expanding-rule banners, skewed toasts.

DOM screenshots without a compositing pane: the embedded pane taints any foreignObject canvas, so window.__domShot(name) (defined in the drive harness notes) serializes body+styles+data-URI'd assets into an SVG, POSTs it to scripts/shot-server.mjs ({name, svg} now supported), and real headless Chrome rasterizes it: chrome --headless=new --hide-scrollbars --virtual-time-budget=2600 --screenshot=out.png --window-size=1280,720 file.svg. The virtual-time budget matters — entrance animations are at opacity 0 without it.

Tonight's Run + cut-ins + share card (2026-08-24, latest). The spine is in: dailyInfo() in config hashes the local date into a seed, a stage and one of six DAILY_MODIFIERS (rule twists passed to Sim as mods — damage/hp/spawn/fragment multipliers, flat-HP, sealed domain). Everyone playing the same day gets the same night; determinism is unit-tested. Title screen shows TONIGHT'S RUN with the date, stage, twist and your best; the run skips stage select, grants the nightly stage even if unlocked=false, and banks save.daily {date,best,cleared}.

Domains now fire a portrait-consistent cut-in (generated FROM each portrait via image reference): ui.domainCutin() reuses the splash strip mirrored — art left, copy right, accent in the student's colour (--sc). Boss splashes are unchanged.

game.shareCard(won) composes a 1200×630 PNG on a canvas (stage thumb backdrop, wordmark, survived time, stage, student in their colour, TONIGHT'S RUN chip, site URL, portrait right) and Web-Shares it on mobile / downloads it elsewhere. SHARE sits on both end screens. The win screen now paints assets/ui/dawn.webp behind a scrim.

Yui's needles and Sora's charms render as the generated sprites when the pack is present (primitives remain the fallback): needle webps were auto-rotated to point +x in the bake (principal-axis), view lays them flat with atan2(-vz, vx); charms just spin so orientation never mattered. Kaito's orbs and Ren's palm stay procedural on purpose.

Sound pass (2026-08-24, Envato). Licensed via the new app.envato.com (auto-licenses on download; audio did not visibly consume plan credits): PremiumBeat "Night Drive Synthwave" (night theme) + "Devil On The Run" (boss theme — same author, so they sit in one mix), a 3-take School Bell, and a city-crickets night ambience. scripts/bake-audio.py transcodes masters (git-ignored under assets-src/incoming/audio/) into assets/music/{night,boss,title}.mp3 + assets/sfx/{bell,ambience}.mp3, loudnorm'd.

audio.js gained a streaming layer: _loadTracks() decodes at the first user gesture; startMusic streams the night loop, setBoss crossfades night<->boss (~1s), the title screen plays crickets + the quiet 61s cut (titleAmbience, wired in showTitle), and win() rings the REAL bell instead of the synth chime. The procedural engine is intact and remains the fallback whenever the mp3s are absent — the single-file build deliberately does not inline music, so hallpass.html stays procedural. Loop seams are trimmed with loopStart/loopEnd (encoder padding).

Repo now on GitHub: https://github.com/MisterSands/hallpass (private). Deploys can also go through the local Netlify CLI (authed as Chris): npx netlify-cli deploy --prod --dir .netlify-stage --site 84baf5f8-... — useful when the Netlify MCP 503s.

Category parity + review harvest (2026-08-25). research/steam-complaints.md holds 4,370 weighted negative Steam reviews across seven category titles; research/RISK-TRACKER.md maps the complaint league (balance-rng > grind > bugs/save-loss > content-drought > difficulty-walls) to HALL PASS mitigations — re-audit it after every major system. Raw JSONL is git-ignored; refetch with python scripts/harvest-reviews.py fetch (use filter=recent — filter=all starves pagination).

Shipped against it: run resume (Sim.snapshot()/Sim.restore() — rng state now lives in sim.rngState so streams serialize; autosave every 5s + on pause to hallpass.resume.v1, cleared on death/win/quit; exact-time RESUME on the title, <24h), music/SFX sliders (sound.setVolumes, settings.musicVol/sfxVol), one-thumb mode (settings.onethumb → forces autofire, hides the aim stick, body.onethumb CSS), first-session hints (game.hint(id, text), save.hints, suppressed after 5 runs), Assignments (16 one-time quests in config, 3/day via activeAssignments off the daily hash, evaluated in _commitRun, paid in fragments, chips on the title + award lines on end screens), Night II/III (STAGE_TIERS presets through the daily-mods pipeline — no extra spawns: more spawns = more XP = EASIER, the classic survivors trap; HP/speed carry the difficulty; per-tier bests keyed id:tier; balance.mjs --tier=2). 26 node tests; snapshot round-trip is bit-identical for 10 simulated seconds — keep that test green through any sim change.

Remaining (all optional polish): yard obstacles with pathing, challenges/daily seed/share card, one-thumb mode (largely covered already: auto-aim + tap-right-to-dash means the game is playable with one thumb). Then sound (Envato SFX/music) — agreed next. No Higgsfield spend without Chris's say-so.

Public deploy: https://hallpass-night.netlify.app — Netlify project hallpass-night on Chris's team (Web Royal / csands-gtziix8). Redeploy: npm run build:prod, copy dist/ to .netlify-stage/ with a netlify.toml (publish = "."), then run the npx command that the Netlify MCP deploy-site operation returns, inside .netlify-stage/ (never the repo root — assets-src/ is 3.3 GB).

What this is

A from-scratch successor to the brief in G:\Skunkworks\curfew\HANDOFF.md — top-down twin-stick wave survival (Solomon's Boneyard homage) re-skinned as original anime high-schoolers exorcising curses after dark. Same brief (Three.js, plain ES modules + esbuild, phone/desktop/gamepad, same four students, six curse types, XP techniques, fragment shop) — different title, different codebase, built to beat Curfew on feel and look.

Working title HALL PASS: the permission slip to be out after dark — the conceptual flip of "curfew". Chris has not signed off on the name. The kicker line on the title screen uses 通行証 ("pass / permit") as decoration only.

Locked decisions carried over (do not relitigate)

Licensed assets (Envato Elements, Chris's subscription)

Asset Source Where Use
2D Cartoon FX (Dyomin) — 180 ProRes-4444 clips, 3K/30fps Envato Elements stock video, licensed 2026-08-23 assets-src/cartoon-fx/ (1.4 GB, not redistributable, git-ignored) → baked to assets/fx/atlas-{4096,2048}.webp + atlas.json Every in-game effect: hits, crits, kill splats, Ren's slashes, Chain Static bolts, Flash Step streaks, spawn puffs, Kaito's lightning-sphere orbs, Domain rings/rays/swirl, level-up, boss entrances, death
CT Headstone (sideswork) — heavy condensed sans, Regular + Oblique Envato Elements fonts, licensed 2026-08-23 assets/fonts/ (TTF, inlined into the single-file build) --display face: logo, headings, HUD timer, damage numbers, buttons, the gate sign

Pipeline: python scripts/bake-fx.py (needs ffmpeg + Pillow, both on this machine) picks 40 clips (list at the top of the script), samples them at 15 fps ("on twos"), trims blank lead-in frames, crops to the alpha bounding box, converts to luminance+alpha and packs 128-px cells into one 4096² atlas (64-px / 2048² for phones). Gotcha: about a third of the pack's clips have an opaque black background instead of alpha (meant for screen blending) — the script detects those (lum-as-alpha in its log) and uses luminance as coverage. src/sprites.js renders the atlas as one instanced quad pool with a per-instance tint; bright cores stay white (uWhite), so a single white clip serves every student colour. Ground sprites are oriented by a direction vector (used to stretch lightning between two curses); air sprites are camera-facing billboards.

Scouted and rejected: Envato's 3D category (300K–700K-polygon "3D illustration" models — wrong style and far too heavy without a decimation pipeline) and fantasy RPG icon packs (off-theme). The yard is dressed with primitives instead (src/world.js: school facade with lit windows and a clock, KUROHANE HIGH gate sign, bleachers, parked school bus), all outside the play area so the sim is untouched. Manga concentration lines live in the post shader (post.speedLines) — no asset needed.

How to run

npm install          # three + esbuild (already installed)
npm run dev          # esbuild watch + server on http://127.0.0.1:8010 (PORT env overrides)
npm test             # node --test: save migration, config sanity, sim determinism, bot smoke run
npm run balance      # headless bot sweep, prints survival-time percentiles per student
npm run build:prod   # minified dist/ (bundle ≈1 MB) + dist-single/hallpass.html (file:// build, never deployed)
npm run ship         # build:prod + netlify deploy --prod + Cloudflare purge (docs/CLOUDFLARE-PLAN.md)

Dev-only URL params: ?bot=1&char=ren (the bot plays), ?stress=260 (spawns 260 curses at t=0), ?drive=1 (timer-driven loop + window.__shot(name) capture hook that POSTs JPEGs to node scripts/shot-server.mjs — used to see the game from a headless browser pane), ?nofx=1 (bloom off). window.__HALLPASS__ exposes game/view/sim/save for poking.

Where it beats Curfew (the benchmark notes)

Feel

Look

Engineering

Architecture

File Role
src/config.js All data: arena, students, curses, boss names, surges, DIFFICULTY curve, PLAYER (dash/domain), techniques, perks. Source of truth.
src/sim.js Pure simulation. Sim.step(dt, input) advances everything; emits sim.events for the view/controller. Fixed grid spatial hash (4-unit cells).
src/bot.js Stand-in player with a skill dial (0.25 novice … 0.8 competent). Used by balance runs and the attract mode.
src/view.js Three.js presentation: camera, instanced pools (body/outline/eyes per type, shadows, shots, gems, charms), player rig, ghosts, handleEvent() → FX.
src/actors.js Toon materials, per-type merged curse geometry + outlines, the student rig builder, afterimage ghosts.
src/world.js Canvas-drawn floor/fence textures, lamps, skyline, petals, lights.
src/fx.js Shard + glow particle pools, rings, telegraph strips, palm-slash sector, chain arcs, DOM damage numbers, Domain disc.
src/post.js Bloom compositor + manga speed-lines.
src/sprites.js Tintable flipbook sprite pool over the baked FX atlas.
scripts/bake-fx.py Clip selection + atlas bake (ffmpeg + Pillow).
src/input.js Touch twin-sticks + on-screen buttons, keyboard/mouse, gamepad, menu-nav queues.
src/audio.js Sound: SFX synth + look-ahead-scheduled procedural music.
src/ui.js DOM HUD + title/roster/shop/level-up/pause/death screens, focus navigation, banners/toasts.
src/game.js Controller/state machine: attract mode, run loop, hit-stop/slow-mo, events → audio/UI, save commits, adaptive quality.
src/save.js hallpass.save.v1 load/migrate/persist + perk → stat bonuses.
src/main.js Boot, device detection, resize, rAF loop, dev harness.
index.html Shell + all CSS (anime/neon-noir: condensed display type, skewed hard-shadow buttons, scanline/vignette overlay).
scripts/build.mjs esbuild build/serve + single-file writer. scripts/balance.mjs sweep. scripts/shot-server.mjs dev capture sink.
test/*.test.js 12 node tests.

Coordinate convention: camera sits at +Z looking toward −Z, so screen-up is world −Z; input vectors are screen-space (+y down) and map 1:1 onto world (x, z). sim.spawnRadius is set from the view's frustum each frame (capped at 24).

Balance (headless bot, 8 seeded runs per student)

novice (0.25) p50 competent (0.8) p50
Ren 3:05 7:02
Yui 4:11 10:48
Kaito 3:26 4:52
Sora 3:17 5:42

First-run fragments ≈ 100–140 → 2–3 cheap perk ranks. Every run ends: after 7 min enemy HP goes exponential (doubles every 100 s) and enemy speed climbs 12 % per 30 s, so even a maxed build is caught by ~13–15 min. XP curve is 8 + level^1.7 × 4, which lands level ~20 around minute 6. Re-run npm run balance after any change to DIFFICULTY, weapons or upgrades.

Verified

Still worth checking on physical hardware

Real multi-touch stick feel and thermal behaviour on a phone over 10 minutes; an actual gamepad (mapping is standard: left stick move, right stick aim, X/LB/RB/LT dash, Y/RT domain, Start pause, A/B confirm/back); whether bloom needs to default off on low-end Android (the auto-fallback should catch it); and the 3–6 min first-run target against a human rather than the bot.

Ideas not built (deliberately out of scope for v1)

PWA manifest/service worker; weapon evolutions; per-student unique Domain behaviours (all four currently share the same wipe with different colour/name); leaderboards; an options screen beyond the four toggles.

Constraints & user context


2026-08-25 — Shipaton phase 1: native shell + playtest infrastructure

Store-track work for RevenueCat Shipaton 2026 (deadline Sept 30, 11:45pm PDT). Sequencing decision: playtest first, monetize dark — the store plumbing and the playtest program are the same work (Play's mandatory 14-day closed test IS the playtest window).

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