chore: align repo health checks and docs

This commit is contained in:
Tom Boullay
2026-04-30 13:51:39 +02:00
parent c5b672cdb5
commit 9998fb65f8
8 changed files with 73 additions and 141 deletions
+12 -10
View File
@@ -36,7 +36,9 @@ export class SomeManager {
| `CinematicManager` | target-state only | Future GSAP timeline orchestrator. | | `CinematicManager` | target-state only | Future GSAP timeline orchestrator. |
| `ZoneManager` | target-state only | Future zone entry/exit detection and LOD triggers. | | `ZoneManager` | target-state only | Future zone entry/exit detection and LOD triggers. |
## GameManager is the orchestrator ## Target-State GameManager
`GameManager` does not exist in the current implementation. The following pattern is target-state guidance only and should not be applied until the manager exists in code.
```ts ```ts
export class GameManager { export class GameManager {
@@ -52,7 +54,7 @@ export class GameManager {
} }
``` ```
Components and hooks access other managers **through GameManager only**: When a `GameManager` exists, components and hooks should access other managers through it:
```ts ```ts
// Correct // Correct
@@ -62,7 +64,7 @@ GameManager.getInstance().cinematic.play("intro");
CinematicManager.getInstance().play("intro"); CinematicManager.getInstance().play("intro");
``` ```
## Subscribe pattern (GameManager only) ## Target-State Subscribe Pattern
```ts ```ts
private listeners = new Set<() => void>() private listeners = new Set<() => void>()
@@ -77,9 +79,9 @@ private emit(): void {
} }
``` ```
Every `set*()` method calls `this.emit()` to notify subscribers. In that target-state manager, every `set*()` method calls `this.emit()` to notify subscribers.
## React bridge hook ## Target-State React Bridge Hook
```ts ```ts
// hooks/useGameState.ts // hooks/useGameState.ts
@@ -97,8 +99,8 @@ export function useGameState() {
## Rules ## Rules
- Max 4 managers total - Do not add a `GameManager` unless the feature requires a real shared gameplay state owner.
- Only `GameManager` holds durable state with `subscribe()` - Current managers may be imported directly until the target-state orchestrator exists.
- Other managers are side-effect handlers — they do not store persistent state - Keep singleton managers limited to side-effect services or shared interaction state.
- Always call `destroy()` on cleanup (App unmount) - Always call `destroy()` on cleanup when a manager owns external resources.
- Never create manager instances with `new` — always use `.getInstance()` - Never create manager instances with `new` — always use `.getInstance()`.
-15
View File
@@ -66,21 +66,6 @@ import { RigidBody, CuboidCollider } from "@react-three/rapier";
- `type="dynamic"` for movable objects - `type="dynamic"` for movable objects
- Player uses `type="dynamic"` with `lockRotations` - Player uses `type="dynamic"` with `lockRotations`
## Postprocessing
```tsx
import { EffectComposer, Bloom, Vignette } from "@react-three/postprocessing";
<EffectComposer>
<Bloom intensity={0.5} luminanceThreshold={0.9} />
<Vignette offset={0.3} darkness={0.5} />
</EffectComposer>;
```
- Always wrap in `<EffectComposer>`
- Keep effects minimal for performance
- Disable heavy effects on low-end devices via Debug panel
## What NOT to do ## What NOT to do
- Do not use `new THREE.Scene()` or `new THREE.WebGLRenderer()` — R3F handles this - Do not use `new THREE.Scene()` or `new THREE.WebGLRenderer()` — R3F handles this
+17 -28
View File
@@ -56,58 +56,47 @@ la-fabrik/
│ ├── debug/ # Debug-only test scene │ ├── debug/ # Debug-only test scene
│ │ └── TestMap.tsx │ │ └── TestMap.tsx
│ └── player/ │ └── player/
│ ├── FPSController.tsx # PointerLockControls + Rapier movement │ ├── Player.tsx # Player rig composition
── Crosshair.tsx ── PlayerCamera.tsx # Player camera mount
│ └── PlayerController.tsx # Pointer lock movement and inputs
├── components/ ├── components/
│ ├── three/ # Shared R3F components by domain │ ├── three/ # Shared R3F components by domain
│ │ ├── gameplay/repairGame/ # Core repair gameplay prototype │ │ ├── gameplay/ # Core repair gameplay prototype
│ │ ├── interaction/ # Trigger, grab, focus wrappers │ │ ├── interaction/ # Trigger, grab, focus wrappers
│ │ ├── models/ # GLTF model components │ │ ├── models/ # GLTF model components
│ │ └── world/ # Environment-specific 3D objects │ │ └── world/ # Environment-specific 3D objects
│ └── ui/ # HTML overlays — outside Canvas │ └── ui/ # HTML overlays — outside Canvas
│ ├── NarrativeOverlay.tsx # Floating dialogues │ ├── Crosshair.tsx
│ ├── MissionHUD.tsx # Current objective │ ├── HandTrackingOverlay.tsx
│ ├── MapHUD.tsx # Minimap │ ├── HandTrackingVisualizer.tsx
── CinematicBars.tsx # GSAP black bars ── InteractPrompt.tsx
│ └── LoadingScreen.tsx # Asset progress
├── managers/ # Current singleton-style services ├── managers/ # Current singleton-style services
│ ├── AudioManager.ts # Music and SFX playback │ ├── AudioManager.ts # Music and SFX playback
│ └── InteractionManager.ts # Focus, nearby, grab state │ └── InteractionManager.ts # Focus, nearby, grab state
├── hooks/ # React hooks — thin wrappers on managers ├── hooks/ # React hooks by domain
│ ├── useGameState.ts # Subscribes to GameManager │ ├── debug/ # Debug state and GUI folders
│ ├── useZoneDetection.ts │ ├── docs/ # Docs language context access
│ ├── useInteraction.ts │ ├── editor/ # Editor loading and history
│ ├── useCinematic.ts │ ├── gameplay/ # Repair gameplay helpers
│ ├── useAudio.ts │ ├── handTracking/ # Webcam/WebSocket hand tracking
── useLOD.ts ── interaction/ # Interaction manager subscriptions
│ └── three/ # Three.js/R3F helpers
├── data/ ├── data/
│ ├── interaction/ # Interaction tuning │ ├── interaction/ # Interaction tuning
│ ├── player/ # Player tuning │ ├── player/ # Player tuning
│ ├── repairGame/ # Repair gameplay static config │ ├── gameplay/ # Repair gameplay static config
│ └── world/ # Environment and lighting config │ └── world/ # Environment and lighting config
├── shaders/
│ └── hologram/
│ ├── vertex.glsl
│ └── fragment.glsl
├── utils/ ├── utils/
│ ├── core/ # Logger and generic utilities │ ├── core/ # Logger and generic utilities
│ ├── debug/ # Dev-only tools and scene inspection │ ├── debug/ # Dev-only tools and scene inspection
│ ├── editor/ # Editor-only parsing utilities │ ├── editor/ # Editor-only parsing utilities
│ ├── map/ # Map loading and validation │ ├── map/ # Map loading and validation
│ └── three/ # Three.js helpers │ └── three/ # Three.js helpers
├── hooks/
│ └── debug/
│ ├── useCameraMode.ts
│ ├── useDebugFolder.ts
│ ├── useDebugStore.ts
│ └── useSceneMode.ts
├── App.tsx # Canvas bootstrap ├── App.tsx # Canvas bootstrap
└── main.tsx └── main.tsx
``` ```
+1 -1
View File
@@ -46,7 +46,7 @@ This document describes the code that exists today in the repository.
- `src/components/three/models/` contains reusable model helpers such as `ExplodableModel`. - `src/components/three/models/` contains reusable model helpers such as `ExplodableModel`.
- `src/components/three/interaction/` contains reusable interaction wrappers such as `InteractableObject`, `TriggerObject`, and `GrabbableObject`. - `src/components/three/interaction/` contains reusable interaction wrappers such as `InteractableObject`, `TriggerObject`, and `GrabbableObject`.
- `src/components/three/gameplay/repairGame/` contains the current core repair gameplay prototype: the repair case, repair game zone, and module slots. - `src/components/three/gameplay/` contains the current core repair gameplay prototype: the repair case, repair game zone, and module slots.
- `src/components/three/world/` contains reusable world/environment objects such as `SkyModel`. - `src/components/three/world/` contains reusable world/environment objects such as `SkyModel`.
## Editor System ## Editor System
+4 -2
View File
@@ -34,11 +34,13 @@ src/
│ ├── useEditorHistory.ts │ ├── useEditorHistory.ts
│ └── useEditorSceneData.ts │ └── useEditorSceneData.ts
├── types/ ├── types/
│ └── editor.ts │ └── editor/
│ └── editor.ts
└── utils/ └── utils/
├── editor/ ├── editor/
│ └── loadEditorScene.ts │ └── loadEditorScene.ts
└── loadMapSceneData.ts └── map/
└── loadMapSceneData.ts
``` ```
## Responsibilities ## Responsibilities
+38 -37
View File
@@ -1,51 +1,52 @@
# Main Feature # Main Feature
This document explains La-Fabrik's debug hand-tracking feature: grabbing and moving 3D objects with a webcam. This document explains the current repair-game prototype in La-Fabrik.
## What It Does ## What It Does
In debug mode, the player can use their webcam to control object grabbing in the physics scene. The main feature is a repair interaction sandbox mounted in the debug physics scene. It lets the player approach a repair case, open it, and interact with module slots that can show selectable models and exploded-model states.
The intended user flow is: The current user flow is:
1. Open the app with `?debug`. 1. Open the app with `?debug`.
2. Switch the scene to `Physics` in the debug panel. 2. Switch the scene to `Physics` in the debug panel.
3. Move close to a grabbable object. 3. Move close to the repair case.
4. Show a hand to the camera. 4. Press the interaction key when prompted.
5. Close the hand into a fist near the object. 5. Watch the case open or close with sound feedback.
6. Move the hand to move the object. 6. Interact with repair module slots to cycle/select repair models.
7. Open the hand to release the object.
## Why It Matters ## Why It Matters
This feature tests whether La-Fabrik interactions can feel more physical and embodied than a classic mouse or keyboard interaction. This feature validates the core repair fantasy before a full mission system exists. It tests whether repair objects, physical proximity, model selection, audio feedback, and exploded model visualization can work together in the 3D scene.
For the final experience, this can support low-tech repair gestures, object manipulation, and more expressive interaction sequences.
## Current Behavior ## Current Behavior
The feature works with one or more detected hands. A hand is considered active for grabbing when the backend detects a closed fist. The repair case reacts to player proximity. When the player is close enough, it floats upward and rotates gently to signal interactivity. When the player moves away, it returns to its resting transform.
When the fist starts close enough to a grabbable object, the object attaches to the hand target. The object then follows the hand center in screen space and also reacts to relative hand depth. Interacting with the case toggles its open state. The lid animation is handled with GSAP because it is a discrete interaction animation, not a continuous per-frame loop.
Moving the hand left, right, up, or down moves the object in that direction. Moving the hand closer or farther from the camera changes the object's distance from the camera. Repair module slots are configured from static gameplay data. They render selectable repair models and can use exploded model visualization to show parts separated from their original positions.
## Key Files
- `src/world/debug/TestMap.tsx` mounts the repair-game prototype in the debug physics scene.
- `src/components/three/gameplay/RepairGameZone.tsx` composes the repair-game zone.
- `src/components/three/gameplay/RepairCaseObject.tsx` connects the repair case to trigger interaction and audio.
- `src/components/three/gameplay/RepairCaseModel.tsx` renders and animates the case model.
- `src/components/three/gameplay/RepairModuleSlot.tsx` renders repair slots and model selection behavior.
- `src/components/three/models/ExplodableModel.tsx` renders selectable models with split/exploded visualization.
- `src/data/gameplay/repairCaseConfig.ts` stores repair case model, sound, and animation constants.
- `src/data/gameplay/repairGameConfig.ts` stores repair zone and slot positions.
- `src/data/gameplay/repairGameModelCatalog.ts` stores selectable repair models.
## Debug Requirements ## Debug Requirements
Hand tracking requires: The repair-game prototype currently requires:
- Chrome or another browser that allows `getUserMedia()` reliably
- the local Python backend running
- the local MediaPipe model file available in `backend/hand_landmarker.task`
- the app opened with `?debug` - the app opened with `?debug`
- the debug scene set to `Physics` - the debug scene set to `Physics`
- model assets available under `public/models/`
Backend command: - sound assets available under `public/sounds/`
```bash
source backend/.venv/bin/activate
python -m backend.main
```
Frontend command: Frontend command:
@@ -59,21 +60,21 @@ Debug URL:
http://localhost:5173/?debug http://localhost:5173/?debug
``` ```
## On-Screen Feedback ## Related Hand Tracking
The debug build shows several helpers: Hand tracking is a separate debug interaction layer. It can move grabbable physics objects with webcam input, but it is not yet integrated into the repair-game mission flow.
- a hand tracking status panel For hand tracking, run the Python backend separately:
- a hand landmark wireframe
- the `lil-gui` debug panel
- the `r3f-perf` performance panel
- optional interaction spheres
The wireframe turns yellow when the detected hand is a fist. ```bash
source backend/.venv/bin/activate
python -m backend.main
```
## Current Limitations ## Current Limitations
- It is enabled only in the debug physics scene. - It is mounted only in the debug physics scene.
- The SVG hand wireframe is a debug visualization, not final gameplay UI. - There is no mission progression system yet.
- Depth movement depends on relative webcam tracking and may need tuning. - There is no central `GameManager` or Zustand store in this branch.
- The system is not integrated into mission gameplay. - Hand tracking is available as debug interaction input, not as final repair gameplay.
- The repair-game content is configured statically in `src/data/gameplay/`.
-46
View File
@@ -10,7 +10,6 @@
"dependencies": { "dependencies": {
"@react-three/drei": "^10.7.7", "@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.0", "@react-three/fiber": "^9.6.0",
"@react-three/postprocessing": "^3.0.4",
"@react-three/rapier": "^2.2.0", "@react-three/rapier": "^2.2.0",
"@tanstack/react-router": "^1.168.25", "@tanstack/react-router": "^1.168.25",
"gsap": "^3.15.0", "gsap": "^3.15.0",
@@ -763,32 +762,6 @@
} }
} }
}, },
"node_modules/@react-three/postprocessing": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@react-three/postprocessing/-/postprocessing-3.0.4.tgz",
"integrity": "sha512-e4+F5xtudDYvhxx3y0NtWXpZbwvQ0x1zdOXWTbXMK6fFLVDd4qucN90YaaStanZGS4Bd5siQm0lGL/5ogf8iDQ==",
"license": "MIT",
"dependencies": {
"maath": "^0.6.0",
"n8ao": "^1.9.4",
"postprocessing": "^6.36.6"
},
"peerDependencies": {
"@react-three/fiber": "^9.0.0",
"react": "^19.0",
"three": ">= 0.156.0"
}
},
"node_modules/@react-three/postprocessing/node_modules/maath": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/maath/-/maath-0.6.0.tgz",
"integrity": "sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==",
"license": "MIT",
"peerDependencies": {
"@types/three": ">=0.144.0",
"three": ">=0.144.0"
}
},
"node_modules/@react-three/rapier": { "node_modules/@react-three/rapier": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@react-three/rapier/-/rapier-2.2.0.tgz", "resolved": "https://registry.npmjs.org/@react-three/rapier/-/rapier-2.2.0.tgz",
@@ -4190,16 +4163,6 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/n8ao": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/n8ao/-/n8ao-1.10.1.tgz",
"integrity": "sha512-hhI1pC+BfOZBV1KMwynBrVlIm8wqLxj/abAWhF2nZ0qQKyzTSQa1QtLVS2veRiuoBQXojxobcnp0oe+PUoxf/w==",
"license": "ISC",
"peerDependencies": {
"postprocessing": ">=6.30.0",
"three": ">=0.137"
}
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -4389,15 +4352,6 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/postprocessing": {
"version": "6.39.1",
"resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.39.1.tgz",
"integrity": "sha512-R2dG2zy+BAx3USl5EHw+PvnrlbT5PKnZVp3se0HCR0pWH8WQdh742yNG4YWOsq6c0bFpffk0Gd2RqPeoP/wKng==",
"license": "Zlib",
"peerDependencies": {
"three": ">= 0.168.0 < 0.185.0"
}
},
"node_modules/potpack": { "node_modules/potpack": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
+1 -2
View File
@@ -14,12 +14,11 @@
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check .", "format:check": "prettier --check .",
"preview": "vite preview", "preview": "vite preview",
"typecheck": "tsc --noEmit" "typecheck": "tsc -b"
}, },
"dependencies": { "dependencies": {
"@react-three/drei": "^10.7.7", "@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.0", "@react-three/fiber": "^9.6.0",
"@react-three/postprocessing": "^3.0.4",
"@react-three/rapier": "^2.2.0", "@react-three/rapier": "^2.2.0",
"@tanstack/react-router": "^1.168.25", "@tanstack/react-router": "^1.168.25",
"gsap": "^3.15.0", "gsap": "^3.15.0",