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:
- The night is winnable. (Superseded 2026-08-31: a run is now ONE HOUR of the night — the room sets the hour, and only the tenth room ends at First Bell; see MECHANICS "Canon". The rest of this bullet describes the original single-night model.) A run is 9 PM → 6 AM over 12 real minutes (
NIGHTin config). The HUD shows the clock; the sky palette shifts through the night; at 6:00 AM the yard is wiped in a golden dawn burst, a DAWN BONUS is banked, the win screen offers AFTER HOURS (endless continuation of the same run, clock shows6:00 AM +mm:ss), and the save recordswins/clearedBy(roster cards show ★ NIGHT CLEARED). "ONE HOUR TO FIRST BELL" banner at 5 AM; big surges at 645 s / 685 s. Balance check: at bot skill 0.8 with zero perks, Yui cleared the night 1 run in 6; others peak 8–10 min — dawn is earned, perks close the gap. - Signature bosses.
BOSSESin config maps each name to a kit, implemented inSim._bossAI: THE JANITOR sweeps a rotating mop-beam (dodge the line), HALL MONITOR hunts with a searchlight (caught = wisp reinforcements + speed), POP QUIZ strafes firing spiral volleys, THE SUBSTITUTE vanish→appear→slam, EXAM SEASON drops timed detonation zones, DETENTION closes a shrinking cage ring. Every kit also summons once at 60% HP. Hazards (sim.zones,sim.cages) render in the view; the sweep beam is an animated strip infx.sweeps.
Session 2 later shipped the build-depth layer:
- Evolutions (
EVOLUTIONSin config): golden cards offered when technique ranks qualify, one of each per night, behaviour not stats — Perfect Flash (Black Flash crits detonate 60% splash), Storm Circuit (talismans arc Chain Static), Second Heart (one revive at half HP with a knockback burst), Encore (every 4th cast repeats), Ghost Step (Flash Step leaves a burning trail), Afterglow (+60% dmg / +30% speed for 8 s after a Domain). Implemented as flags onsim.stats; behaviours live in_damage(splash/afterglow),_weapon(encore),_player(ghost trail),_die(revive),_domainFields(storm circuit). - Per-student Domains (
DOMAINS+character.domainKit): every kit opens with a wipe pulse, then — Iron Cell (Ren): 6 s of invulnerability, curses dragged into an 8.5 u cage, then it crushes shut; Silk Cathedral (Yui): 5 s where enemies and bullets run at 25% and her fire rate doubles; Blackout Grid (Kaito): the full 26 u wipe plus 6 s of bolts hunting survivors; Paper Storm (Sora): 24 homing charms and 4 s of near-double move speed.
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)
- Original IP only. "Loosely inspired by" shonen sorcery anime: cursed energy, Domain Expansion analogue, Binding Vow, Black Flash-style crits. No borrowed names, characters or art. Mechanics homage to Boneyard, zero asset/name reuse.
- Three.js r185 + esbuild, no TypeScript, no framework, nothing fetched from external hosts at runtime. Geometry is primitives; world textures are drawn on canvas at boot; audio is synthesised WebAudio. Session 2 relaxed the no-assets rule: licensed Envato Elements graphics are bundled locally (see Licensed assets) — sound assets are the agreed next step.
- Target 60 fps on a mid phone;
InstancedMeshhordes; pooled everything. - Single static page deploy (
dist/index.html+dist/bundle.js), plusdist/hallpass.htmlwhich inlines the bundle and opens from a double-click (file://).
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
- Hit-stop on crits, big kills and bosses; slow-motion on Domain activation and death; camera shake scaled by event.
- Flash Step dash (Shift / RMB / gamepad LB-LT / tap the fire stick on phone): i-frames, afterimage ghosts, two charges with sequential recharge, two techniques and a perk that extend it.
- Crowd separation steering so hordes spread into a swarm and surround instead of stacking into one blob; spawns biased toward the direction you're running so kiting into open space gets cut off.
- Telegraphed attacks: brutes wind up, draw a strip and charge; spitters glow then fire a two-shot spread; bosses alternate a 14-bullet radial burst and a telegraphed charge, and summon a grub ring at 60% HP.
- Scripted surges (SWARM, WISP TIDE, HUSK PARADE…) with banners, then random surges every 80 s.
- Auto-aim with target lock hysteresis (no more shots scattering between near-equal threats); mouse aim is an exact ground-plane raycast.
- Floating damage numbers (pooled DOM), kill-streak counter, level-up gem vacuum, Domain-ready pulse, "DOMAIN NOT CHARGED" toast.
- Procedural soundtrack (kick/clap/hats/bass/arp/pad at 126 BPM) whose energy follows run intensity, with a phrygian boss mode and ducking under the Domain.
Look
- Pitched perspective camera (~60°), not flat top-down: figures read as people, rows of curses have depth, fog fades the far yard.
- Cel-shaded (
MeshToonMaterial) bodies with inverted-hull ink outlines; per-type curse silhouettes (grub, shambling husk, hovering wisp, spitter with a mouth, fisted brute, crowned boss); per-student rigs with distinct hair, blazer colour, collar, and technique prop. - Real bloom: custom HDR half-float pipeline in
src/post.js(bright pass → two blur passes at ¼ res → ACES + sRGB + vignette composite). Auto-disables below 45 fps for 3.5 s and drops DPR. Manga speed-lines on crits, dashes, boss entrances, Domain and death are drawn in the same pass. - Licensed hand-drawn anime FX flipbooks for every hit, kill, slash, bolt, streak and the Domain (see Licensed assets).
- A place, not a void: painted basketball court, running lanes, crosswalk, hopscotch, manholes, chain-link fence with posts and rails, 12 lamp posts with light pools and cones, a fogged skyline with ~500 lit windows, drifting petals.
- Title screen runs an attract mode: a bot-driven match plays out under the logo while the camera orbits.
Engineering
src/sim.jshas zero Three/DOM dependencies → runs in Node.scripts/balance.mjsplays hundreds of seeded bot runs at two skill levels, which is how the difficulty curve was tuned (see below) instead of guessing.- Seedable PRNG → deterministic runs (tested).
- Adaptive quality, resize safety net, save migration that tolerates garbage, settings persisted.
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
- Desktop 1280×720, phone portrait 375×812 and landscape 812×375: HUD rects don't collide, level-up cards fit, roster scrolls horizontally on phones.
- Keyboard (Enter/arrows/1-2-3 in menus, WASD, Shift, Space, Esc, M), mouse aim, simulated touch sticks, tap-to-dash, on-screen Domain button — all drive the sim correctly.
- Flow: title → roster → run → chained level-ups → pause (settings persist) → death screen → shop (buy perk, unlock student) → retry → quit;
localStorageround-trips. - Stress: 260 curses on screen — sim 0.2 ms + view sync 0.2 ms per frame; GPU-bound at ~7.5 ms/frame at 1280×720 with bloom on the dev machine (≈130 fps equivalent).
npm test12/12,npm run build:prodclean.
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
- User: Chris, creative director. Judges by feel and look. Communicate in outcomes.
- Windows 11; Bash tool = Git Bash. Never launch anything that pops a console window.
npm run devis fine from the in-app preview; a dev esbuild server for Curfew may already be holding port 8000 — HALL PASS uses 8010. - No git repo initialised here (init one if you commit; don't push).
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).
- Capacitor 8 Android shell (
android/,capacitor.config.json, appIdcom.verybad.hallpass— changed 2026-08-29 fromcom.sandsgames.hallpassto match the Very Bad Games studio; appId is permanent on Play once uploaded, never change it after the first upload).sensorLandscapelock, immersive-sticky fullscreen + keep-screen-on in MainActivity, Android back button routed to pause/menu-back viasrc/native.js(bridge globals only — web bundle byte-identical). Toolchain is user-local atG:/Skunkworks/tools/{jdk21,android-sdk}(no admin); build:cd android && JAVA_HOME=G:/Skunkworks/tools/jdk21 ./gradlew assembleDebug. Debug APK deployed at https://hallpass-night.netlify.app/hallpass-playtest.apk. - Field reports — FIELD REPORT button (title + pause) → Netlify Forms
(
feedbackform; site settingignore_html_formsflipped to false via API — submissions in Netlify dashboard → Forms). Auto-attaches build id (__BUILD__esbuild define, version+sha), UA, screen, run context. Offline queue in localStorage, flushed on next boot. iOS shell is NOT set up (needs a Mac or cloud build service; Capacitor iOS cannot be added on Windows). - Tester guide at /testers.html (committed, copied by build).
- Monetization scaffold, dark:
MONETIZATION.enabled=falsein config,save.outfits/save.supporter+ migration,src/iap.jsstub shaped like @revenuecat/purchases-capacitor. Nothing imports it yet by design. - Chris's homework (only he can): Apple Developer ($99), Google Play ($25, then 14-day closed test with 12+ testers), AdMob account, shipaton.com registration.