Files
La-Fabrik/docs/technical/repair-game.md
T
Tom Boullay c09818aa67
🔍 Lint / 🪄 Check lint (push) Has been cancelled
🔍 Lint / 🎨 Check format (push) Has been cancelled
🔍 Lint / 🔎 Typecheck (push) Has been cancelled
🔍 Lint / 🏗 Build (push) Has been cancelled
📊 Quality / 🔒 Security Audit (push) Has been cancelled
📊 Quality / 📋 Dependency Freshness (push) Has been cancelled
📊 Quality / 📦 Bundle Size (push) Has been cancelled
docs: audit app architecture and refresh feature documentation
2026-05-12 11:46:11 +02:00

366 lines
10 KiB
Markdown

# Repair Game Technical Notes
This document explains the implementation of the reusable repair-game flow.
## Purpose
The repair game is the current core gameplay loop. It gives three missions the same interaction structure while allowing mission-specific assets, broken parts, replacement choices, prompts, and timing to live in data.
Implemented missions:
| Mission | Object | Role |
| -------- | ------------- | --------------------------------------------- |
| `bike` | E-bike | Repair a damaged cooling core |
| `pylone` | Power pylon | Restore relay/panel-like broken parts |
| `ferme` | Vertical farm | Stabilize irrigation/sensor-like broken parts |
## Main Files
| File | Responsibility |
| ---------------------------------------------- | ------------------------------------------------- |
| `src/components/three/gameplay/RepairGame.tsx` | Orchestrates the repair step machine |
| `src/data/gameplay/repairMissions.ts` | Mission-specific data |
| `src/types/gameplay/repairMission.ts` | Mission ids, step ids, guards |
| `src/managers/stores/useGameStore.ts` | Global progression and mission transitions |
| `src/world/GameStageContent.tsx` | Production placement of the three repair missions |
| `src/world/debug/TestMap.tsx` | Debug repair playground placement |
## State Machine
Repair mission steps are defined in:
```txt
src/types/gameplay/repairMission.ts
```
```txt
locked -> waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done
```
The practical flow is:
```mermaid
stateDiagram-v2
[*] --> locked
locked --> waiting: mission unlocked
waiting --> inspected: inspect mission object
inspected --> fragmented: repair-case trigger or two-fists hold
fragmented --> scanning: fragmentation timer
scanning --> repairing: scan sequence complete
repairing --> reassembling: install target validates
reassembling --> done: reassembly timer
done --> [*]: completion target calls completeMission
```
There is no dedicated finite-state-machine library. The state machine is intentionally lightweight and distributed across:
- `MissionStep` union types
- Zustand transition helpers
- conditional rendering in `RepairGame`
- callbacks passed to step components
For the current prototype, this is readable and low overhead. If mission rules become much more branched, a centralized mission orchestrator or FSM library would become more useful.
## Integration With Zustand
The durable state lives in:
```txt
src/managers/stores/useGameStore.ts
```
`RepairGame` reads:
- `mainState`
- current step for its mission
`RepairGame` writes:
- `setMissionStep(mission, nextStep)`
- `completeMission(mission)`
The important architectural choice is that reusable repair components do not call `setBikeState`, `setPyloneState`, or `setFermeState` directly. They use generic mission actions so the same component can run for all three missions.
## Data-Driven Mission Config
Mission variation lives in:
```txt
src/data/gameplay/repairMissions.ts
```
Each mission config defines:
- `id`
- `label`
- `description`
- `modelPath`
- optional `modelScale`
- `stageUiPath`
- `interactUiPath`
- `brokenUiPath`
- repair case transform
- optional scan/reassembly timings
- `requiredReplacementPartId`
- `brokenParts`
- `replacementParts`
The main benefit is that `RepairGame` stays generic. A mission can change broken nodes, replacement choices, or prompt videos without changing the orchestration component.
The tradeoff is that the config can grow complex. If one future mission needs very different rules, create a mission-specific component instead of forcing every exception into the shared config.
## Orchestration Component
`RepairGame.tsx` is a step router.
It:
1. receives a `mission` id and transform props
2. gets `config = REPAIR_MISSIONS[mission]`
3. subscribes to the active `mainState`
4. subscribes to the current mission step
5. preloads mission assets
6. mounts the component for the active step
7. stores local runtime state needed between steps
Local runtime state:
- `casePlaceholders`: placeholder transforms emitted by the repair case GLTF
- `scannedBrokenParts`: output of the scan sequence used by the repair step
Those values are local because they are transient scene/runtime details. They do not need to persist globally in Zustand.
## Step Components
### Waiting
File:
```txt
src/components/three/gameplay/RepairInspectionObject.tsx
```
The mission object is rendered with a 3D prompt video and wrapped in an interaction trigger. Pressing `E` while focused moves the mission to `inspected`.
### Inspected
Files:
```txt
src/components/three/gameplay/RepairMissionCase.tsx
src/components/three/gameplay/RepairCaseModel.tsx
src/hooks/gameplay/useRepairFragmentationInput.ts
```
The repair case appears near the mission object. The player can:
- aim at the case and press `E`
- hold both fists closed for one second when hand tracking is active
Both paths move to `fragmented`.
Important current detail: `useRepairMovementLocked()` currently returns `false`, so the movement-lock rule and indicator are present but disabled in the current branch.
### Fragmented
File:
```txt
src/components/three/models/ExplodableModel.tsx
```
The mission object is shown split apart. A timer then moves the mission to `scanning`.
The default delay comes from:
```txt
REPAIR_FRAGMENTATION_SEQUENCE_SECONDS
```
### Scanning
File:
```txt
src/components/three/gameplay/RepairScanSequence.tsx
```
The scan sequence:
- keeps the exploded model visible
- receives model parts from `ExplodableModel`
- advances an active part index over time
- renders `RepairScanVisual` on the active part
- reveals broken-part highlights when configured broken parts have been reached
- returns `RepairScannedBrokenPart[]` when done
Broken-part lookup first tries `brokenParts[].nodeName`. If no configured node matches, it falls back to the first available exploded parts. This fallback is useful while GLTF node names are still unstable, but precise `nodeName` config is safer for production.
### Repairing
File:
```txt
src/components/three/gameplay/RepairRepairingStep.tsx
```
This is the densest gameplay step.
It renders:
- install target
- placeholder markers
- grabbable replacement parts
- grabbable broken parts to store
- placement feedback
- ready-to-install prompt
Important local state:
- `placedPartIds`: replacement parts that snapped near a placeholder
- `depositedBrokenPartIds`: broken parts stored in the case
- `showBlockedInstallFeedback`: temporary visual feedback when install is attempted too early
Validation:
```txt
correct replacement part placed
AND every scanned broken part deposited
```
Only then does the install target call `onRepair()` and move to `reassembling`.
### Reassembling
File:
```txt
src/components/three/gameplay/RepairReassemblyStep.tsx
```
The exploded model animates back into assembled form and completion particles play. A timer then moves the mission to `done`.
Mission configs can override the default reassembly duration.
### Done
File:
```txt
src/components/three/gameplay/RepairCompletionStep.tsx
```
The repaired object remains visible. The player validates the completion target, then:
1. the repair case closes
2. the case plays its exit animation
3. `completeMission(mission)` advances the global game progression
## Repair Case Details
The case model implementation lives in:
```txt
src/components/three/gameplay/RepairCaseModel.tsx
```
It handles:
- GLTF loading through `useLoggedGLTF`
- clone creation through `useClonedObject`
- pop-in animation
- lid open/close animation
- open/close SFX through `AudioManager`
- proximity-based floating
- small rotation wobble
- exit animation
- placeholder discovery
Placeholder discovery is data-friendly:
```txt
placeholder_*
```
Any GLTF node whose name starts with that prefix is exported to the repair step as a placement target. This lets artists move placeholder transforms in the model file without hard-coding every placement point in TypeScript.
## Interaction Dependencies
The repair game depends on the shared interaction layer:
- `RepairInspectionObject` uses `InteractableObject`
- `RepairMissionCase` uses `TriggerObject`
- `RepairRepairingStep` uses `GrabbableObject` and `TriggerObject`
- completion uses `TriggerObject`
This keeps the repair game from owning raw keyboard or mouse listeners for every object. The player controller handles input, and interaction components decide what is focused.
## Hand Tracking Dependencies
Hand tracking participates in two places:
- `useRepairFragmentationInput` uses `useBothFistsHold`
- `GrabbableObject` can be `handControlled`
`HandTrackingProvider` enables tracking during the repair steps that are expected to use hands:
```txt
inspected
repairing
reassembling
done
```
This avoids keeping the webcam active for the whole game scene.
## Runtime Placement
Production placement lives in:
```txt
src/world/GameStageContent.tsx
```
Current positions:
```tsx
<RepairGame mission="bike" position={[8, 0, -6]} />
<RepairGame mission="pylone" position={[64, 0, -66]} />
<RepairGame mission="ferme" position={[-24, 0, 42]} />
```
Only the repair game whose `mission` matches `useGameStore().mainState` renders active content.
## Debug Placement
Debug placement lives in:
```txt
src/world/debug/TestMap.tsx
```
The debug scene mounts repair playground zones for all missions. Use `?debug`, switch to the physics scene in lil-gui, then use the game-state debug panel to activate the mission you want to test.
## Why This Is A Good Review Focus
This feature shows several important frontend/game architecture skills:
- state-driven scene composition
- data-driven feature variation
- React state for step-local runtime values
- Zustand for durable game progression
- R3F component boundaries
- Rapier object interaction
- hand tracking integration
- audio feedback
- GLTF traversal
- graceful asset fallbacks
## Known Limitations
- Movement lock is currently disabled by an early `return false` in `useRepairMovementLocked`.
- The repair-game runtime setting in the options menu is stored but not consumed by `RepairGame`.
- Broken-part scan fallback can produce incorrect matches if GLTF node names are missing.
- Mission progression is still prototype-level and not owned by a central `GameManager`.
- The same repair flow covers all missions. Very different future missions may need dedicated components.