Site Audit
Status: Active development (dynamic overhaul)
Overview
The player acts as a Lead Inspector navigating a 3D isometric job site. There are 5 levels: a static tutorial (id=1, displayed as "Level 0") plus 4 procedurally generated standard levels (one per difficulty: Easy / Medium / Hard / Expert, ids 2-5, displayed as Levels 1-4). Standard runs are procedurally generated — checkpoint positions, content, props, hazard zones, and micro-events are randomized from pools and layout templates. The player walks to checkpoints in any order within unlocked tiers, completes a diagnostic or quick-fix task at each, and signs off the work order. One continuous timer runs the entire audit; it never pauses between checkpoints.
Core Mechanics
Free-Roam Navigation
- Isometric 3D scene built with Three.js (orthographic camera, tile-based movement)
- WASD / arrow keys for movement, click-to-move, scroll to zoom
- Base speed: 10 tiles/sec (up from 6) — inspector moves briskly
- Avatar walks between checkpoints with collision against stations and props
- Virtual joystick on mobile (
sm:hidden)
Continuous Timer
- A single countdown starts after the 3-2-1 countdown and runs through both walking (
roaming) and task completion (checkpointActive) - Timer only freezes during the pause menu
- Time allotments scale by difficulty (300–480s)
- Time running out ends the run; the player is scored on whatever they completed
Checkpoints
- Each checkpoint maps to a trade pathway and a
phaseIdthat loads a mini-game component - Content is drawn from randomized pools — questions, configs, and cards differ every run
- The player walks near a checkpoint (colored ring) and presses Space or taps to open it
- Completed checkpoints show a green ring and checkmark on the map
- When all checkpoints are signed off, the exit door unlocks; walking to it ends the run
Tiered Checkpoints
- Checkpoints are assigned to tiers (2 tiers on Easy/Medium, 3 tiers on Hard/Expert)
- Tier 1 stations are open from the start
- When the player completes a configurable percentage of the current tier, the next tier unlocks
- Unlock thresholds are per-level (e.g., Easy = 50%, Expert = 30%)
- Locked stations show a grey ring + lock icon in the 3D scene
- Minimap color-codes by tier: tier 1 = cyan, tier 2 = orange, tier 3 = purple, locked = grey
Multi-Floor Navigation
- Easy/Medium levels have 1 floor; Hard has 2 floors; Expert has 3 floors
- Stairs tiles connect floors — walking onto a stairs tile transitions the player to the target floor
- Only the current floor renders (floor groups show/hide)
- Minimap shows a floor indicator (F1 / F2 / F3) and only current-floor objects
- Collision, checkpoint proximity, and micro-event detection are all floor-aware
Hazard Zones
- Rectangular regions on the grid where the player moves at half speed (5 tiles/sec)
- Narratively: water spills, gravel patches, debris fields
- Rendered as colored semi-transparent floor patches (blue for water, brown for gravel, grey for debris)
- Positions randomized per run; no overlap with spawn, exit, or checkpoint tiles
Micro-Events
- Hidden interactables scattered across the map (no minimap marker)
- Grant bonus points or bonus time when collected
- Proximity cue: faint particle shimmer appears when the player is within 3-4 tiles
- Collected on contact — plays SFX, removes from scene, adds to store
- Reward attentive exploration without requiring memorization
Streak System
| Streak | Multiplier |
|---|---|
| 0–1 | 1.0x |
| 2 | 1.25x |
| 3 | 1.5x |
| 4 | 1.75x |
| 5+ | 2.0x |
- Builds when checkpoint accuracy >= 70% (streak + 1)
- Decays when accuracy is 50–70% (streak - 1, minimum 0)
- Breaks when accuracy < 50% (streak resets to 0)
- Multiplier applies to the checkpoint score before adding to the run total
- Store tracks peak streak (highest streak reached during the run) for results display
Par Time
- Each checkpoint has a par time based on difficulty
- Par time is shown inside the checkpoint modal as elapsed vs par
- Under par: green display (e.g., "12s / 30s par")
- Over par: red display counting up (e.g., "+5s over par")
- No time bonus is awarded for checkpoints completed over par
- The global run timer continues ticking during checkpoints — par is informational + scoring only
Scoring
- Each checkpoint: max 1000 points (scored by each mini-game playfield independently)
- Checkpoint score multiplied by current streak multiplier
- Time bonus:
remainingSeconds × 5added when all checkpoints complete - Speed bonus: 100 points for completing a checkpoint in < 60% of par time
- Micro-event bonus: accumulated bonus points from collected micro-events
- Platform
multipliercolumn always1(all bonuses baked intoscore) - Stars/grades scale by checkpoint count and difficulty
Content Pool System
Content is not hardcoded per level. Each checkpoint type has a pool file under content/pools/ with items tagged by difficulty:
interface PoolItem<T> {
maxDifficulty: Difficulty;
data: T;
}
Each run, the phase adapter calls pickN(pool, count, difficulty) to draw randomized content. Easy pools include Easy-only items; Expert pools include everything.
Pool files map 1:1 to checkpoint types:
clash-correction.ts,schedule-saver.ts,hazard-scan.ts,gear-check.ts,tool-select.tsdispatch-call.ts,measure-station.ts,circuit-station.ts,connection-station.tsprecision-measure.ts,fault-finder.ts,flow-verify.ts,wire-check.ts,pipe-slope.ts,blueprint-read.ts
Content can be pulled from other games in the monorepo (circuit-breaker, flow-state, trades-build-off, pipe-runner, task-master-relay) and wrapped in PoolItem format.
Dynamic Layout
Each run is assembled by the layout generator (lib/layout-generator.ts) from a LevelTemplate:
interface LevelTemplate {
id: number;
difficulty: Difficulty;
grid: { width: number; height: number };
floors: number;
totalTimeSeconds: number;
checkpointCounts: Record<number, number>; // tier → count
tierUnlockThreshold: number; // 0-1, fraction to unlock next tier
hazardDensity: number; // 0-1
microEventCount: number;
checkpointPhaseIds: string[]; // which checkpoint types to include
// ... display fields (title, description, briefing, etc.)
}
The generator:
- Divides the grid into zones (quadrants + edges + center)
- Places checkpoints with minimum spacing (~8 tiles apart), assigned to tiers and floors
- Places props with collision avoidance from a difficulty-appropriate prop pool
- Places hazard zones (rectangular patches, no overlap with spawn/exit/checkpoints)
- Places micro-events (avoid checkpoint proximity)
- Places stairs (multi-floor only, connecting same tile position across floors)
- Returns a complete
SiteLevelConfigfor the run
Level Scaling
| Level | ID | Kind | Difficulty | Grid | Floors | Checkpoints | Tiers | Time |
|---|---|---|---|---|---|---|---|---|
| Tutorial | 1 | tutorial | Easy | 30×30 | 1 | 4 (static) | 1 | 600s |
| Level 1 | 2 | standard | Easy | 35×35 | 1 | 6 | 2 | 480s |
| Level 2 | 3 | standard | Medium | 45×45 | 1 | 8 | 2 | 420s |
| Level 3 | 4 | standard | Hard | 55×55 | 2 | 10 | 3 | 360s |
| Level 4 | 5 | standard | Expert | 65×65 | 3 | 12 | 3 | 300s |
The tutorial has static checkpoint positions and no hazard zones, micro-events, or tiers — it teaches core mechanics only. Standard levels are procedurally generated each run.
Per-Difficulty Theming
Each difficulty has a distinct visual identity:
- Floor color: green-tinted (Easy) → grey (Medium) → blue-grey (Hard) → dark (Expert)
- Ambient light tint: warm (Easy) → neutral (Medium) → cool (Hard) → red-tinted (Expert)
- BGM track: per-difficulty (see Level Scaling table)
- Prop set: outdoor/green props (Easy) → industrial/dark props (Expert)
Level Configuration (Runtime)
interface SiteLevelConfig {
id: number;
title: string;
description: string;
briefing?: string;
difficulty: Difficulty;
totalTimeSeconds: number;
floors: number;
grid: { width: number; height: number };
spawn: { tileX: number; tileY: number; floor: number };
checkpoints: CheckpointConfig[];
exitDoor: ExitDoorConfig;
props: LevelProp[];
hazardZones: HazardZone[];
microEvents: MicroEvent[];
stairs: StairsTile[];
tierUnlockThreshold: number;
kind?: "tutorial" | "standard";
// ... display fields
}
Checkpoint Types
| ID | Pathway | Task | Mini-Game Format |
|---|---|---|---|
clash-correction | CAD/BIM/VDC | BIM clash detection MCQ | McqPlayfield |
schedule-saver | Project Mgmt | Construction sequence ordering | DragOrderPlayfield |
hazard-scan | Trades | Hazard pass/fail judgment | CardJudgePlayfield |
gear-check | Trades | PPE selection | TapSelectPlayfield |
tool-select | Electrical | Tool identification | MultiSelectPlayfield |
dispatch-call | Project Mgmt | Service call diagnosis | DispatchPlayfield |
measure-station | Trades | Precision measurement | MeasurePlayfield |
circuit-station | Electrical | Circuit wiring | CircuitPlayfield |
connection-station | HVAC/Plumbing | Pipe routing | ConnectionPlayfield |
precision-measure | Trades | Measurement theory MCQ | McqPlayfield |
fault-finder | Electrical | Electrical code judgment | CardJudgePlayfield |
flow-verify | HVAC/Plumbing | Plumbing defect identification | TapSelectPlayfield |
wire-check | Electrical | NEC standards MCQ | McqPlayfield |
pipe-slope | HVAC/Plumbing | Drain slope MCQ | McqPlayfield |
blueprint-read | CAD/BIM/VDC | Blueprint literacy MCQ | McqPlayfield |
Phase Machine
menu → loading → levelIntro → countdown → roaming ⇄ checkpointActive → checkpointComplete → roaming
↕ ↕ ↕
paused paused paused
↓ ↓ ↓
results results results
Floor transitions happen within roaming (no separate phase).
Floor Rendering
- Floor plane: single
PlaneGeometryper floor withMeshStandardMaterial(per-difficulty color) - Optional grid lines via
LineSegments - Hazard zones: semi-transparent overlay planes on affected tiles
- No GLB cloning for floor tiles — performance-first approach
File Map
games/site-audit/
├── index.ts # barrel export
├── store.ts # Zustand FSM + streak + tiers + micro-events
├── types.ts # GamePhase, CheckpointConfig, SiteLevelConfig, HazardZone, MicroEvent, StairsTile
├── config.ts # timing, scoring, streak thresholds, difficulty themes
├── scoring.ts # streak helpers, run total, accuracy
├── image-gen.config.ts # stub
├── components/ # site-canvas, minimap, HUD, banner, phase-runner, virtual-joystick
├── hooks/ # game-actions, game-audio, input, movement, three-app
├── lib/ # scene-builder, iso-camera, collision, assets, layout-generator, station-markers
├── content/
│ ├── levels.ts # tutorial level + 4 LevelTemplate definitions + generateLayout caller
│ ├── image-prompts.ts # prompt text and style constants
│ └── pools/ # per-checkpoint-type content pools
│ ├── index.ts # barrel: re-exports all pools + pickN
│ ├── utils.ts # pickN, filterByDifficulty, seeded shuffle
│ ├── clash-correction.ts
│ ├── schedule-saver.ts
│ ├── hazard-scan.ts
│ ├── gear-check.ts
│ ├── tool-select.ts
│ ├── dispatch-call.ts
│ ├── measure-station.ts
│ ├── circuit-station.ts
│ ├── connection-station.ts
│ ├── precision-measure.ts
│ ├── fault-finder.ts
│ ├── flow-verify.ts
│ ├── wire-check.ts
│ ├── pipe-slope.ts
│ └── blueprint-read.ts
└── phases/ # checkpoint mini-game adapters (draw from pools)
├── clash-correction/
├── schedule-saver/
├── hazard-scan/
├── gear-check/
├── tool-select/
├── dispatch-call/
├── measure-station/
├── circuit-station/
├── connection-station/
└── ...