Vanilla ES modules + Three.js (r160). Development: open index.html or npm run dev (Vite). Production: npm run build bundles into dist/; GitHub Pages uses BASE_PATH=/Stroll/.
/
├── index.html Single page: shells for HUD, overlays, import map
├── css/
│ ├── tokens.css Design tokens (colors, fonts, z-index)
│ ├── base.css Reset, shell UI, mobile, keyframes
│ ├── hud.css HUD, compass, toasts
│ ├── overlays.css Pause, journal, loading, modes, minigames
│ ├── reduced-motion.css prefers-reduced-motion overrides
│ └── style.css Entry (@imports partials in order)
├── docs/
│ ├── ARCHITECTURE.md This file
│ └── PROJECT_PLAN.md Roadmap and definition of done
└── js/
├── main.js Boot, scene/renderer/composer, wiring
├── game-loop.js Per-frame tick (modes: cinematic, pause, photo, play)
├── input.js Keyboard / pointer bindings (photo, journal, mood, games)
├── game-state.js Session flags + stats builders (HUD / achievements / pause)
├── spawn-utils.js Walkable placement for pickups
├── player-input.js canPlayerAct / canPlayerMove guards
├── quality.js low / medium / high graphics presets
├── events.js Lightweight pub/sub (pickup, etc.)
├── focus-trap.js Keyboard focus for pause / journal modals
├── accessibility.js prefers-reduced-motion helper
├── config.js Tunables — prefer changing values here vs scattered literals
├── lighting.js Sun, shadows, fog, sky, ground, day/night cycle
├── city.js Procedural city + collision helpers (`buildings`, `isInsideBuilding`)
├── controls.js Input (keyboard, pointer lock, mobile joystick), resize helper
├── audio.js User-gated AudioContext — wind/birds/lofi; calls `ambient.js` when enabled
├── ambient.js Extra pads/bells/ocean (runs only after sound is on + graph exists)
├── lofi.js Procedural lofi beats
├── collectibles.js Orbs/crystals/stars + HUD **score**
├── enhanced-collectibles.js Gems, artifacts, notes, mystery boxes (bonuses fed into score via `addScore`)
├── challenges.js Waypoints, journal data, achievements
├── npcs.js Pedestrians
├── traffic.js Cars
├── dog.js Companion
├── wildlife.js Birds / butterflies
├── particles.js Leaves / fireflies
├── weather.js Rain / lightning / fog integration
├── interactive.js Flowers (right-click) + ambient interactives
├── weapon.js FPS pistol overlay + pickups
├── photomode.js Orbit cam + screenshots
├── meditation.js Breathing / camera soften
├── minigames.js Simple overlay games (`G`)
├── cinematic.js Opening flythrough
└── hud.js Compass, HUD numbers, pause, journal, toasts
startWhenReady()— Runsinit()afterDOMContentLoadedor immediately if the document is already parsed (fixes ES-module timing).init()— try/catch;initCore()bails early if WebGL/composer/scene failed.- World build — city → NPCs → collectibles → … → controls + UI bindings.
- First frame — loading screen hides, cinematic starts (optional skip). Gameplay branch of
animate()runs after cinematic. - Loop —
game-loop.tick()— movement → NPCs/day-night/particles/traffic/dog → weather → minigames → collectibles → enhanced → challenges → HUD → composer render → optional weapon pass.
| Concern | Source |
|---|---|
| Pickup totals for HUD / “Completionist” | collectibles.js counts + enhanced-collectibles.js counts (summed in main.js) |
| Score HUD | collectibles.js internal score (base pickups + addScore() for enhanced) |
| Achievements | challenges.js — stats from game-state.buildChallengeStats() |
| Pause menu stats | hud.updatePauseStats via setPauseStatsProvider in main |
| Pointer / movement | controls.js — pointer lock is desktop-only |
| Ambient layered audio | Created only inside audio.js enableSound() — never pass null context into ambient.startAmbient |
Browsers block AudioContext until a user gesture. Flow:
- On load: no
AudioContextyet —ambientmust not allocate nodes. - User clicks 🔇/
sound-toggle:audio.jscreates context +masterGain, builds soundscape, thenstartAmbient(ctx, masterGain). - Other systems (weather SFX, enhanced pickup pings) safely no-op when
getAudioContext()is null.
EffectComposer + bloom + FXAA. FXAA resolution uniforms must follow window size → updateFxaaResolution() chained with resizeWeapon() from controls.setupResize.
- Testing: Add Vitest + jsdom smoke tests that import modules with mocked
THREECanvas, or Playwright screenshot tests for regressions. - Build: Optional Vite to bundle/version assets and tree-shake; keep import-map build for fallback.
- CSS: Split
style.cssintotokens.css(variables) + partials imported via@importif file size grows further. - Types: Incremental
// @ts-checkwith JSDoc onmain.jsand game state objects. - Feature flags: Optional
config.FEATURE_WEAPONetc. if the “peaceful walk” SKU should hide shooting.
- Magic numbers: Prefer
config.js. - New systems: Export
create*+update*; registerupdate*exactly once insideanimate()in the appropriate mode branch (cinematic / paused / photo / meditation / play). - DOM: HUD IDs are the contract between
index.htmlandhud.js— keep them stable or update both.