← Back
Play

Fix-It-First

A timed relay race through 3 cross-trade stations. Players choose between two phase groupings at the level-select — Diagnosis (probe + repair existing systems) or Build (wire/install from scratch) — then pick a difficulty (Easy/Medium/Hard/Expert). Each run plays Frame Up → Circuit → Pipe & Duct under a shared countdown clock. Heavily modeled on Site Sprint Relay; reuses the Circuit and Connection mini-game playfields and introduces a new Frame Build mini-game.

Game Flow

Menu → /levels (2 phase sections × 4 difficulties) → Level Intro → Countdown → Station Intro → Playing → Station Complete → [next station or Results]

Same state machine as Site Sprint Relay. See store.ts.

Modes

ModeSlugFrame pool filterCircuit pool filterConnection pool filterConnection levelKind
Diagnosisdiagnosistags.includes("diagnose")tags.includes("diagnose")tags.includes("diagnostic")"diagnostic"
Build & Installregulartags.includes("build")tags.includes("build") || tags.includes("match-fix") (excluding practice)!tags.includes("diagnostic")"standard"

Routes: /fix-it-first/play/[mode]/[difficulty] (e.g. /fix-it-first/play/diagnosis/easy).

Stations

#StationPhase IDShared Mini-GameNotes
1Frame Upframe-stationFrameBuildPlayfieldpickN from frame pool filtered by mode. Build = pick four right-length planks for the dock; Diagnose = inspect the pre-built frame and swap mismatched planks.
2Circuitcircuit-stationCircuitPlayfieldpickN from circuit pool filtered by mode.
3Pipe & Ductflow-stationConnectionPlayfieldpickN from connection pool filtered by mode; passes levelKind.

Per-station + total time budgets live in config.ts.

Pool additions

To make Easy & Medium playable in Diagnosis mode, the following items were added to the shared pools (and benefit Circuit Breaker / Flow State as well):

The new shared Frame Build mini-game lives at lib/mini-games/frame-build/ with its own content pool covering both build and diagnose items at every difficulty.

Frame Build mini-game

A drag-and-place mini-game representing the in-person framing activity (slot wooden planks into a dock to square the work area). One config defines:

  • 4 slots (top, bottom, left, right) each with a requiredLengthInches.
  • An inventory of PlankDef objects (mix of correct planks + decoys close in length).
  • Optional prePlaced array used by diagnose mode to seed the slots; entries with isFault: true carry a faultReason shown when the player taps to inspect.

Build mode auto-completes when the player has placed a correct plank in every slot. Diagnose mode is the same playfield but starts with the prePlaced planks (some faulty) — the player taps a placed plank to inspect, removes the bad one, and drags a replacement from the tray. Wrong-length placement attempts increment wrongAttempts, which feeds into accuracy.

Visuals are pure CSS (warm wood gradients + repeating-linear-gradient grain overlay) so the game ships without art. Real wood textures can be generated later via bun generate:images:fix-it-first (see games/fix-it-first/image-gen.config.ts) — the playfield resolves them via the assetBasePath prop.

Blueprints

All three stations share a unified blueprint pattern. Each pool item carries an optional blueprintImageId referring to a generated WebP under /mini-games/{slug}/{blueprintImageId}.webp (blueprints belong to the mini-game, not the host game, so every consumer reads the same asset). The toolbar exposes a Blueprint button that opens a modal showing the image alongside a context summary (cut list for frame, parts list / schematic / grid outline for flow, multimeter log for circuit). The frame station also shows an inline blueprint preview at the top of the playfield.

StationPool fieldImage lives atPrompts authored in
Frame UpFrameBuildConfig.blueprintImageId/mini-games/frame-build/bp-frame-*.webplib/mini-games/frame-build/content/blueprint-prompts.ts (FRAME_BLUEPRINT_PROMPTS)
CircuitCircuitChallengeConfig.blueprintImageId/mini-games/circuit/bp-*.webplib/mini-games/circuit/content/blueprint-prompts.ts (BLUEPRINT_PROMPTS + describeCircuitBlueprint())
Pipe & DuctConnectionConfig.blueprintImageId/mini-games/connection/bp-*.webpderived per-pool-item via describeConnectionBlueprint() in lib/mini-games/connection/content/blueprint-prompts.ts

The drafting style is unified via BLUEPRINT_STYLE in lib/images/prompts.ts; each domain extends it with trade-specific symbol guidance (NEC electrical, ANSI woodworking dimensions, IPC plumbing / ASHRAE HVAC). All blueprint prompts use the PRO_MODEL (Gemini pro image model) for crisp text + line work. Generate via bun generate:images:circuit, bun generate:images:connection, bun generate:images:frame-build.

Generation is on-demand: bun generate:images:fix-it-first, bun generate:images:flow-state, bun generate:images:circuit-breaker. Until assets exist, the modals hide the image and show the inline detail / placeholder.

Scoring

Identical formulas to Site Sprint Relay (per-station score + time bonus + difficulty multiplier). See scoring.ts and grade thresholds tuned for 3-station max in config.ts.

Persistence

Best runs are stored under one slot per (mode, difficulty) pair (8 total slots) keyed ${mode}_${difficulty} in runs. getCumulativeBest() sums all slots for an aggregate best. See hooks/use-game-actions.ts.