← Back
Play

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 phaseId that 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

StreakMultiplier
0–11.0x
21.25x
31.5x
41.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 × 5 added 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 multiplier column always 1 (all bonuses baked into score)
  • 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.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

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:

  1. Divides the grid into zones (quadrants + edges + center)
  2. Places checkpoints with minimum spacing (~8 tiles apart), assigned to tiers and floors
  3. Places props with collision avoidance from a difficulty-appropriate prop pool
  4. Places hazard zones (rectangular patches, no overlap with spawn/exit/checkpoints)
  5. Places micro-events (avoid checkpoint proximity)
  6. Places stairs (multi-floor only, connecting same tile position across floors)
  7. Returns a complete SiteLevelConfig for the run

Level Scaling

LevelIDKindDifficultyGridFloorsCheckpointsTiersTime
Tutorial1tutorialEasy30×3014 (static)1600s
Level 12standardEasy35×35162480s
Level 23standardMedium45×45182420s
Level 34standardHard55×552103360s
Level 45standardExpert65×653123300s

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

IDPathwayTaskMini-Game Format
clash-correctionCAD/BIM/VDCBIM clash detection MCQMcqPlayfield
schedule-saverProject MgmtConstruction sequence orderingDragOrderPlayfield
hazard-scanTradesHazard pass/fail judgmentCardJudgePlayfield
gear-checkTradesPPE selectionTapSelectPlayfield
tool-selectElectricalTool identificationMultiSelectPlayfield
dispatch-callProject MgmtService call diagnosisDispatchPlayfield
measure-stationTradesPrecision measurementMeasurePlayfield
circuit-stationElectricalCircuit wiringCircuitPlayfield
connection-stationHVAC/PlumbingPipe routingConnectionPlayfield
precision-measureTradesMeasurement theory MCQMcqPlayfield
fault-finderElectricalElectrical code judgmentCardJudgePlayfield
flow-verifyHVAC/PlumbingPlumbing defect identificationTapSelectPlayfield
wire-checkElectricalNEC standards MCQMcqPlayfield
pipe-slopeHVAC/PlumbingDrain slope MCQMcqPlayfield
blueprint-readCAD/BIM/VDCBlueprint literacy MCQMcqPlayfield

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 PlaneGeometry per floor with MeshStandardMaterial (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/
    └── ...