Compare commits
301 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f37f9a747 | |||
| 386abf06b6 | |||
| a73f9fb951 | |||
| d29b01e398 | |||
| 6edc5f7972 | |||
| ae35eb1dfb | |||
| 4de86f4e58 | |||
| 5b123f9704 | |||
| d1bf438465 | |||
| d2ce990165 | |||
| 7d2a257e84 | |||
| 58eb60292f | |||
| 73c6d7d50d | |||
| d9cacdad12 | |||
| ab88ab722f | |||
| a30a9a2d29 | |||
| d217c3376b | |||
| 864e075b42 | |||
| 3fe5b32de2 | |||
| 72cb9f5be6 | |||
| f708c4cd2e | |||
| 193fc8b4b6 | |||
| c61760dafd | |||
| 18dd2ae49d | |||
| def0609383 | |||
| 19a1d20a97 | |||
| 49ebacfbfb | |||
| 68253fae41 | |||
| 2dabb73d3d | |||
| 4f1b3b4ff3 | |||
| 627c8d4eb9 | |||
| 0b801888f0 | |||
| a180b89ee6 | |||
| 3e66e31117 | |||
| 2c194cdd2e | |||
| feaf502e44 | |||
| 489499f5d2 | |||
| 39b996eb31 | |||
| 134c0aecb7 | |||
| b144dc1c18 | |||
| 69c720b86b | |||
| 1b57a25e5f | |||
| f6db7d74e2 | |||
| a1798aecb3 | |||
| 3b07f40f2d | |||
| 27416143e3 | |||
| a2a491bd5c | |||
| da7d66e1fd | |||
| 5faf4b4197 | |||
| bee0c7f223 | |||
| 216d29ae59 | |||
| e13cf1e4c7 | |||
| d20bdc4934 | |||
| 7c35090dbd | |||
| a766784ce8 | |||
| 63952912b5 | |||
| fd0b9e2749 | |||
| 777e51efeb | |||
| 1ad0c4de37 | |||
| 7a378afad3 | |||
| d52ec7e5a9 | |||
| 153833deec | |||
| b617885aa2 | |||
| 5d2e7e2aab | |||
| de77f76d48 | |||
| bdc704fe8e | |||
| bce7d11b66 | |||
| 8aa755da7a | |||
| 6d58b90856 | |||
| bafca5a936 | |||
| dcf3a8564c | |||
| bc862960a7 | |||
| 597ebcfbd4 | |||
| aa2d411b0c | |||
| 061e0dc677 | |||
| 9ef94af488 | |||
| 27b4a2c392 | |||
| d5feb07ff0 | |||
| 7dff4a1238 | |||
| a8cd66dcaa | |||
| 116746f838 | |||
| a388c02ab3 | |||
| 4b4162b7d2 | |||
| 4415faa1f1 | |||
| 4c5f08d772 | |||
| 51569af7b8 | |||
| d26c676edf | |||
| d3b4a55e71 | |||
| e212e4bbd5 | |||
| 39ec9feb0e | |||
| 4a43083178 | |||
| efcbf9e972 | |||
| f11ed67452 | |||
| 3e7edcb1b7 | |||
| b9c5d0c563 | |||
| ebdb72ce0d | |||
| 34c198ebfd | |||
| 564a455520 | |||
| c33d973f12 | |||
| 396e7e4ff0 | |||
| 2c2a90264d | |||
| e02d06b8a5 | |||
| 1901075e3a | |||
| e073fc375b | |||
| bff8a16290 | |||
| a3f611e227 | |||
| b578e68c2e | |||
| 7c691a8044 | |||
| f24704091a | |||
| e6bfcbe960 | |||
| 0fa7a82175 | |||
| 82dc47a296 | |||
| 970adf4853 | |||
| 07b09c22af | |||
| 0f6860f1ae | |||
| 6ae21a2427 | |||
| 29342d796c | |||
| 60e3c92511 | |||
| 02c1fb33d0 | |||
| ce5dc8ada0 | |||
| a2cff0567e | |||
| 8cfee1ac93 | |||
| 4c5e2ed945 | |||
| 345d49f485 | |||
| a6cc028848 | |||
| 52bb1b2915 | |||
| ade301389e | |||
| 47e50d9318 | |||
| c7df58099a | |||
| f7b4a07e41 | |||
| 89044a18ec | |||
| 95ca1bbfde | |||
| 093ffd726d | |||
| 4728690a11 | |||
| 343a122c06 | |||
| d5675fe82c | |||
| fcdbf7270c | |||
| 0b3d49e8d1 | |||
| 9bbed06ddc | |||
| ba50224e6e | |||
| 1a91b1d7ae | |||
| d9cf87d2d6 | |||
| d654565f87 | |||
| fb466a63cb | |||
| a75c3fd896 | |||
| 603e521714 | |||
| 49ef8f58b4 | |||
| 947025cbf5 | |||
| f2cecfbbc9 | |||
| 1b9ac5c996 | |||
| e45fdbf97d | |||
| 08b01715c0 | |||
| a2fc417be6 | |||
| 57498b9bb1 | |||
| b87a7e929c | |||
| ea23b4bb46 | |||
| 3881e38a6d | |||
| 7a72743e5c | |||
| 65651405b6 | |||
| 81cd935bba | |||
| fe989c9550 | |||
| e15fb18d6b | |||
| 0230795f55 | |||
| b1fca3a25b | |||
| 0a322acf88 | |||
| a397febd52 | |||
| c15cad2ab0 | |||
| 3eba38a80b | |||
| 011e7815a2 | |||
| 0992aacec6 | |||
| bfe184dea4 | |||
| 4ee13b0336 | |||
| fdd530a3e7 | |||
| 2b676d985d | |||
| b89eedd5be | |||
| d38ad242d6 | |||
| c2b16434fb | |||
| ab100c683f | |||
| b8cff43545 | |||
| 25e0f7e062 | |||
| ab3943eef3 | |||
| 4ebb5b8c25 | |||
| a6787a7ecb | |||
| d816e4b07e | |||
| 665d9f9702 | |||
| 0696ca2ae3 | |||
| d6d3d5b685 | |||
| 1c27d55e5a | |||
| fd558db034 | |||
| 054cb975da | |||
| fbe8c0c854 | |||
| 88b6db6166 | |||
| cf71148935 | |||
| 1b2241df49 | |||
| 2b08665508 | |||
| d7351e5f37 | |||
| 4f8355e934 | |||
| 417afdc1d5 | |||
| 235a38f67b | |||
| 6a412c7b00 | |||
| e9fb36f9dc | |||
| f54e71fc03 | |||
| 36180279b2 | |||
| 626dc47bbe | |||
| 6d178dc59e | |||
| b4a3545460 | |||
| 1f6d9659ed | |||
| a52d57ae6c | |||
| d0497ec42c | |||
| 4c42e11268 | |||
| f175ad6240 | |||
| a4383a7cec | |||
| d07ffc4662 | |||
| d17738eaf1 | |||
| 50fa94b3ad | |||
| 44f9d68ef1 | |||
| e4857135b1 | |||
| 1f6335092a | |||
| f035195b56 | |||
| b8e5c4d1a9 | |||
| 970253801a | |||
| 4e6582b543 | |||
| e4ee2d768b | |||
| 5e594c51f7 | |||
| 26ddbebe14 | |||
| 48c2b4f0cd | |||
| cf08062def | |||
| 4f25b33d3b | |||
| 072dec03b4 | |||
| 529c60adae | |||
| 246da0019a | |||
| 09a9471814 | |||
| 6e9318457a | |||
| 54a353de03 | |||
| 8b619bfc28 | |||
| 4faa226326 | |||
| dd66966507 | |||
| 5893afe42a | |||
| 1ead7ab3a7 | |||
| 047c58678b | |||
| ed9051b0dc | |||
| 08be6bee48 | |||
| ce0eb90321 | |||
| 96d7ec7fc0 | |||
| 9ab4b4a002 | |||
| d13dd0fda0 | |||
| fbedb90bca | |||
| cff7744ad9 | |||
| 6957b9e4f0 | |||
| 9dff245aab | |||
| 27951d13fd | |||
| cdd919c010 | |||
| cea2856fd0 | |||
| d245d6b460 | |||
| dba7aec6fa | |||
| d376d0ba6b | |||
| 242a3dcd37 | |||
| 225ac828df | |||
| 28f7db172c | |||
| 2063656f29 | |||
| 592cfa405f | |||
| 0a32cd1d21 | |||
| 4516cf4ec6 | |||
| c6f60d1ca7 | |||
| aa35e97cbb | |||
| 57c142c8ef | |||
| 4843bf1d75 | |||
| d02cf29a1d | |||
| 3b4c9c2529 | |||
| fdf03349cf | |||
| 439f9c1dad | |||
| 260bfea716 | |||
| b3a3f3557c | |||
| 621556b38c | |||
| 5c55f2c7f4 | |||
| bb4bccb175 | |||
| ae835e5008 | |||
| 1d3aa1c9f2 | |||
| 23cab2da5e | |||
| 44216f9395 | |||
| 7785a6c9d7 | |||
| 48f8de5ea5 | |||
| 73fc9ec677 | |||
| 90cd4d40cc | |||
| 5c68e4cd79 | |||
| 2b3ccaf67d | |||
| 122af7bd9a | |||
| ae34dc38ed | |||
| 1cecead474 | |||
| ceffedf684 | |||
| 43b64172ea | |||
| 700c088c48 | |||
| 490f9627cc | |||
| 8d197ba26b | |||
| eab552a09b | |||
| 2c3f0db65b | |||
| 91ebea8d99 | |||
| f7b968abe7 | |||
| 32d644b09d | |||
| 1b7813a5bb | |||
| 41f7b2ad19 |
+16
-52
@@ -15,12 +15,9 @@ export class SomeManager {
|
||||
return SomeManager._instance;
|
||||
}
|
||||
|
||||
private constructor() {
|
||||
// init logic
|
||||
}
|
||||
private constructor() {}
|
||||
|
||||
destroy(): void {
|
||||
// cleanup logic
|
||||
SomeManager._instance = null;
|
||||
}
|
||||
}
|
||||
@@ -28,43 +25,12 @@ export class SomeManager {
|
||||
|
||||
## Managers in this project
|
||||
|
||||
| Manager | File | Role |
|
||||
| -------------------- | ------------------------------------ | ----------------------------------------------------------------------------- |
|
||||
| `AudioManager` | `src/managers/AudioManager.ts` | Music and SFX playback. |
|
||||
| `InteractionManager` | `src/managers/InteractionManager.ts` | Focus, nearby, trigger, grab, and hand-grab interaction state. |
|
||||
| `GameManager` | target-state only | Future single source of truth for phase, zone, mission, input lock, dialogue. |
|
||||
| `CinematicManager` | target-state only | Future GSAP timeline orchestrator. |
|
||||
| `ZoneManager` | target-state only | Future zone entry/exit detection and LOD triggers. |
|
||||
| Manager | File | Role |
|
||||
| -------------------- | ------------------------------------ | -------------------------------------------------------------- |
|
||||
| `AudioManager` | `src/managers/AudioManager.ts` | Music and SFX playback. |
|
||||
| `InteractionManager` | `src/managers/InteractionManager.ts` | Focus, nearby, trigger, grab, and hand-grab interaction state. |
|
||||
|
||||
## 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
|
||||
export class GameManager {
|
||||
cinematic!: CinematicManager;
|
||||
audio!: AudioManager;
|
||||
zone!: ZoneManager;
|
||||
|
||||
private constructor() {
|
||||
this.cinematic = CinematicManager.getInstance();
|
||||
this.audio = AudioManager.getInstance();
|
||||
this.zone = ZoneManager.getInstance();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When a `GameManager` exists, components and hooks should access other managers through it:
|
||||
|
||||
```ts
|
||||
// Correct
|
||||
GameManager.getInstance().cinematic.play("intro");
|
||||
|
||||
// Wrong — never import sub-managers directly in components
|
||||
CinematicManager.getInstance().play("intro");
|
||||
```
|
||||
|
||||
## Target-State Subscribe Pattern
|
||||
## Subscribe Pattern
|
||||
|
||||
```ts
|
||||
private listeners = new Set<() => void>()
|
||||
@@ -79,28 +45,26 @@ private emit(): void {
|
||||
}
|
||||
```
|
||||
|
||||
In that target-state manager, every `set*()` method calls `this.emit()` to notify subscribers.
|
||||
Managers that expose state to React call `this.emit()` from every `set*()` method that changes subscribed state.
|
||||
|
||||
## Target-State React Bridge Hook
|
||||
## React Bridge Hook
|
||||
|
||||
```ts
|
||||
// hooks/useGameState.ts
|
||||
export function useGameState() {
|
||||
const game = GameManager.getInstance();
|
||||
const [state, setState] = useState(game.getState());
|
||||
// hooks/interaction/useInteraction.ts
|
||||
const manager = InteractionManager.getInstance();
|
||||
|
||||
useEffect(() => {
|
||||
return game.subscribe(() => setState({ ...game.getState() }));
|
||||
}, [game]);
|
||||
|
||||
return state;
|
||||
export function useInteraction(): InteractionSnapshot {
|
||||
return useSyncExternalStore(
|
||||
manager.subscribe.bind(manager),
|
||||
manager.getState.bind(manager),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- Do not add a `GameManager` unless the feature requires a real shared gameplay state owner.
|
||||
- Current managers may be imported directly until the target-state orchestrator exists.
|
||||
- Current managers may be imported directly.
|
||||
- Keep singleton managers limited to side-effect services or shared interaction state.
|
||||
- Always call `destroy()` on cleanup when a manager owns external resources.
|
||||
- Never create manager instances with `new` — always use `.getInstance()`.
|
||||
|
||||
@@ -23,3 +23,6 @@
|
||||
# Video (cinematics)
|
||||
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||
*.webm filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# ML models
|
||||
*.task filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
@@ -69,16 +69,21 @@ jobs:
|
||||
- name: 📥 Install
|
||||
run: npm ci
|
||||
|
||||
- name: 🧹 Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: 🎨 Format check
|
||||
run: npm run format:check
|
||||
|
||||
- name: 📦 Build
|
||||
run: npm run build
|
||||
|
||||
- name: 📏 Check bundle size
|
||||
run: |
|
||||
# Check generated app assets only; public/ model files are runtime assets copied to dist.
|
||||
SIZE=$(du -k dist/assets | cut -f1)
|
||||
# Check generated JS/CSS bundles only; public runtime assets are copied to dist/assets too.
|
||||
SIZE=$(node -e "const fs=require('fs');const path=require('path');function walk(dir){return fs.readdirSync(dir,{withFileTypes:true}).flatMap((entry)=>{const file=path.join(dir,entry.name);return entry.isDirectory()?walk(file):file;});}const bytes=walk('dist/assets').filter((file)=>/\.(js|css)$/.test(file)).reduce((sum,file)=>sum+fs.statSync(file).size,0);console.log(Math.ceil(bytes/1024));")
|
||||
echo "Bundle size: ${SIZE}KB"
|
||||
|
||||
# Threshold: 5000KB (configurable)
|
||||
THRESHOLD=5000
|
||||
|
||||
if [ "$SIZE" -gt "$THRESHOLD" ]; then
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
name: 🔁 Branch Promotions
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 6 * * 1,4" # Lundi et Jeudi à 6h UTC (design → develop)
|
||||
- cron: "0 6 * * 1" # Lundi à 6h UTC (develop → main)
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
promotion:
|
||||
description: "Which promotion to run"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- design-to-develop
|
||||
- develop-to-main
|
||||
- both
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: branch-promotions
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
design-to-develop:
|
||||
name: Open design → develop
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name == 'schedule') ||
|
||||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.promotion == 'design-to-develop' || github.event.inputs.promotion == 'both'))
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 🔁 Open promotion PR
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git fetch origin develop design
|
||||
|
||||
if git merge-base --is-ancestor origin/design origin/develop; then
|
||||
echo "No promotion needed: develop already contains design."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
existing_pr="$(gh pr list \
|
||||
--state open \
|
||||
--base develop \
|
||||
--head "$GITHUB_REPOSITORY_OWNER:design" \
|
||||
--json number \
|
||||
--jq '.[0].number // empty')"
|
||||
|
||||
if [ -n "$existing_pr" ]; then
|
||||
echo "Promotion PR already open: #$existing_pr."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
gh pr create \
|
||||
--base develop \
|
||||
--head design \
|
||||
--title "chore: merge design into develop" \
|
||||
--body "Automated promotion PR from \`design\` to \`develop\`."
|
||||
|
||||
develop-to-main:
|
||||
name: Open develop → main
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name == 'schedule' && github.event.schedule == '0 6 * * 1') ||
|
||||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.promotion == 'develop-to-main' || github.event.inputs.promotion == 'both'))
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 🔁 Open promotion PR
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git fetch origin main develop
|
||||
|
||||
if git merge-base --is-ancestor origin/develop origin/main; then
|
||||
echo "No promotion needed: main already contains develop."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
existing_pr="$(gh pr list \
|
||||
--state open \
|
||||
--base main \
|
||||
--head "$GITHUB_REPOSITORY_OWNER:develop" \
|
||||
--json number \
|
||||
--jq '.[0].number // empty')"
|
||||
|
||||
if [ -n "$existing_pr" ]; then
|
||||
echo "Promotion PR already open: #$existing_pr."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
gh pr create \
|
||||
--base main \
|
||||
--head develop \
|
||||
--title "chore: merge develop into main" \
|
||||
--body "Automated weekly promotion PR from \`develop\` to \`main\`."
|
||||
@@ -10,7 +10,7 @@ The current prototype puts the player in a repair-oriented world where they prog
|
||||
- Production map loaded from `public/map.json`
|
||||
- Progressive map/model/collision/stage loading overlay
|
||||
- Player controller with pointer lock, `ZQSD` movement, jump, octree collision, trigger input, and grab input
|
||||
- Reusable repair-game flow for `bike`, `pylone`, and `ferme`
|
||||
- Reusable repair-game flow for `ebike`, `pylon`, and `farm`
|
||||
- Repair case animation, exploded model scan, broken-part markers, grabbable replacements, snap-to-placeholder placement, install validation, reassembly, and completion
|
||||
- Shared interaction system for trigger and grab objects
|
||||
- Rapier physics for gameplay objects while the player keeps a Three.js octree collision controller
|
||||
@@ -18,19 +18,20 @@ The current prototype puts the player in a repair-oriented world where they prog
|
||||
- Category-based audio manager for music, SFX, and dialogue
|
||||
- Dialogue manifest, SRT subtitles, subtitle overlay, and dialogue queueing
|
||||
- Cinematic manifest with GSAP camera keyframes and optional dialogue cues
|
||||
- In-game settings menu for volumes, subtitles, subtitle language, and the currently staged repair-runtime toggle
|
||||
- In-game settings menu for volumes, subtitles, and subtitle language
|
||||
- Debug mode with `?debug`, lil-gui controls, game-state panel, hand-tracking panel, debug camera, physics playground, and R3F perf
|
||||
- `/editor` route for map transforms, SRT editing, dialogue manifest editing, cinematic manifest editing, preview, validation, export, and dev-server saves
|
||||
- `/docs` route that renders the repository documentation inside the app
|
||||
|
||||
## Routes
|
||||
|
||||
| Route | Purpose |
|
||||
| --------- | --------------------------------------------------- |
|
||||
| `/` | Playable 3D experience |
|
||||
| `/?debug` | Playable scene with debug GUI and overlays |
|
||||
| `/editor` | Local map, dialogue, subtitle, and cinematic editor |
|
||||
| `/docs` | In-app documentation index |
|
||||
| Route | Purpose |
|
||||
| ---------- | --------------------------------------------------- |
|
||||
| `/` | Playable 3D experience |
|
||||
| `/?debug` | Playable scene with debug GUI and overlays |
|
||||
| `/editor` | Local map, dialogue, subtitle, and cinematic editor |
|
||||
| `/gallery` | 3D model gallery for browsing project assets |
|
||||
| `/docs` | In-app documentation index |
|
||||
|
||||
## Tech Stack
|
||||
|
||||
@@ -98,6 +99,7 @@ Useful local URLs:
|
||||
```txt
|
||||
http://localhost:5173/?debug
|
||||
http://localhost:5173/editor
|
||||
http://localhost:5173/gallery
|
||||
http://localhost:5173/docs
|
||||
```
|
||||
|
||||
@@ -110,9 +112,15 @@ npm run format:check
|
||||
npm run build
|
||||
```
|
||||
|
||||
Regenerate runtime map data after editing `public/map_raw.json` that came from the hierachy node of the model Blocking.gltf:
|
||||
|
||||
```bash
|
||||
npm run map:transform
|
||||
```
|
||||
|
||||
## Optional Hand-Tracking Backend
|
||||
|
||||
The app can use browser-side MediaPipe, but the default debug source is the local backend.
|
||||
The app can use the local Python backend, but the default debug source is browser-side MediaPipe.
|
||||
|
||||
```bash
|
||||
python3.11 -m venv backend/.venv
|
||||
@@ -143,18 +151,20 @@ WS ws://localhost:8000/ws
|
||||
| `docs/technical/hand-tracking.md` | Webcam, backend/browser MediaPipe, glove, and gesture flow |
|
||||
| `docs/technical/zustand.md` | Game, settings, and subtitle stores |
|
||||
| `docs/technical/three-debugging.md` | DevTools workflow for stepping into Three.js internals |
|
||||
| `docs/technical/map-performance.md` | Map draw-call bottlenecks and optimization notes |
|
||||
| `docs/technical/map-lod.md` | Runtime map LOD presets, paths, and workflow |
|
||||
| `docs/technical/editor.md` | Editor implementation details |
|
||||
| `docs/technical/animation.md` | Animated, explodable, and reusable 3D model components |
|
||||
| `docs/user/features.md` | Implemented feature inventory |
|
||||
| `docs/user/main-feature.md` | User-facing repair-game walkthrough |
|
||||
| `docs/user/editor.md` | Editor user guide |
|
||||
| `docs/user/gallery.md` | Model gallery user guide |
|
||||
| `docs/code-review-preparation.md` | French code-review preparation support |
|
||||
|
||||
## Current Caveats
|
||||
|
||||
- This is still a prototype, not a complete game runtime.
|
||||
- The repair-runtime toggle is stored in settings and displayed in the UI, but the repair game currently still runs locally in React/Three.
|
||||
- `useRepairMovementLocked()` currently returns `false`, so the movement-lock rule and indicator are present but disabled on `develop`.
|
||||
- `useRepairMovementLocked()` locks player movement during focused repair steps and drives the repair movement indicator.
|
||||
- Production editor persistence does not exist. Save endpoints in `vite.config.ts` are local Vite dev-server helpers.
|
||||
- The player uses octree collision while gameplay objects use Rapier. Keep that boundary deliberate unless the whole player controller is migrated.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -13,7 +13,6 @@ class ClientConnection:
|
||||
websocket: WebSocket
|
||||
is_processing: bool = False
|
||||
last_frame_at: float = 0.0
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
class ConnectionManager:
|
||||
|
||||
Binary file not shown.
@@ -19,7 +19,7 @@ La-Fabrik est une expérience web 3D en React, Vite, Three.js et React Three Fib
|
||||
Le joueur est dans un monde 3D et avance dans une progression de réparation :
|
||||
|
||||
```txt
|
||||
intro -> bike -> pylone -> ferme -> outro
|
||||
intro -> ebike -> pylon -> farm -> outro
|
||||
```
|
||||
|
||||
Les trois piliers à connaître pour la review :
|
||||
@@ -62,7 +62,7 @@ HomePage
|
||||
-> World
|
||||
-> GameMap
|
||||
-> GameStageContent
|
||||
-> RepairGame bike/pylone/ferme
|
||||
-> RepairGame ebike/pylon/farm
|
||||
-> GameMusic
|
||||
-> GameDialogues
|
||||
-> Player
|
||||
@@ -121,7 +121,7 @@ Phrase à retenir :
|
||||
|
||||
Piège à connaître :
|
||||
|
||||
`useRepairMovementLocked()` retourne actuellement `false`. Le lock de mouvement est prévu dans le code et l'UI, mais il est désactivé sur `develop`.
|
||||
`useRepairMovementLocked()` lit maintenant l'étape de mission active et verrouille le déplacement pendant les phases de réparation qui doivent immobiliser le joueur.
|
||||
|
||||
### Interaction
|
||||
|
||||
@@ -324,7 +324,7 @@ Ouvrir dans cet ordre :
|
||||
`RepairGame` reçoit une mission :
|
||||
|
||||
```tsx
|
||||
<RepairGame mission="bike" position={[8, 0, -6]} />
|
||||
<RepairGame mission="ebike" position={[42.2399, 4.5484, 34.6468]} />
|
||||
```
|
||||
|
||||
Puis il vérifie :
|
||||
@@ -347,7 +347,7 @@ Les variations mission sont dans `repairMissions.ts` :
|
||||
### Pourquoi c'est bien
|
||||
|
||||
- Un seul flow réutilisable.
|
||||
- Moins de duplication entre `bike`, `pylone`, `ferme`.
|
||||
- Moins de duplication entre `ebike`, `pylon`, `farm`.
|
||||
- Les règles générales restent dans les composants.
|
||||
- Les variations restent dans la data.
|
||||
- Le debug panel peut tester les mêmes steps que le vrai jeu.
|
||||
@@ -471,9 +471,9 @@ Main states :
|
||||
|
||||
```txt
|
||||
intro
|
||||
bike
|
||||
pylone
|
||||
ferme
|
||||
ebike
|
||||
pylon
|
||||
farm
|
||||
outro
|
||||
```
|
||||
|
||||
@@ -509,12 +509,7 @@ Gère :
|
||||
- menu ouvert/fermé ;
|
||||
- volumes ;
|
||||
- sous-titres ;
|
||||
- langue ;
|
||||
- `repairRuntime`.
|
||||
|
||||
Piège :
|
||||
|
||||
`repairRuntime` est stocké et affiché, mais pas encore utilisé par `RepairGame`.
|
||||
- langue.
|
||||
|
||||
### Subtitle store
|
||||
|
||||
@@ -531,16 +526,15 @@ Phrase simple :
|
||||
|
||||
Si on te pose une question précise, réponds vrai.
|
||||
|
||||
| Sujet | Réponse honnête |
|
||||
| ------------------------- | ------------------------------------------------------------------------ |
|
||||
| Lock mouvement réparation | Le hook existe mais retourne `false`, donc pas actif actuellement. |
|
||||
| `repairRuntime` JS/Python | Le choix est stocké dans settings, mais pas consommé par le repair game. |
|
||||
| Old debug flags | `noMusic`, `noMap`, `noDialogues`, etc. ne sont plus branchés. |
|
||||
| Player physics | Le joueur n'est pas Rapier, il utilise capsule + octree. |
|
||||
| Collision map | L'octree vient de nodes dédiés, actuellement `terrain`. |
|
||||
| Editor save | Ce sont des endpoints Vite dev, pas une API de prod. |
|
||||
| Cinematics | `GameCinematics` est monté seulement pendant `outro` dans `World`. |
|
||||
| Hand tracking depth | Le `z` MediaPipe est relatif, pas une vraie profondeur monde stable. |
|
||||
| Sujet | Réponse honnête |
|
||||
| ------------------------- | -------------------------------------------------------------------- |
|
||||
| Lock mouvement réparation | Les étapes repair actives bloquent le déplacement via le hook dédié. |
|
||||
| Old debug flags | `noMusic`, `noMap`, `noDialogues`, etc. ne sont plus branchés. |
|
||||
| Player physics | Le joueur n'est pas Rapier, il utilise capsule + octree. |
|
||||
| Collision map | L'octree vient de nodes dédiés, actuellement `terrain`. |
|
||||
| Editor save | Ce sont des endpoints Vite dev, pas une API de prod. |
|
||||
| Cinematics | `GameCinematics` est monté seulement pendant `outro` dans `World`. |
|
||||
| Hand tracking depth | Le `z` MediaPipe est relatif, pas une vraie profondeur monde stable. |
|
||||
|
||||
## Si l'évaluateur ouvre un fichier au hasard
|
||||
|
||||
@@ -833,8 +827,6 @@ Pour réutiliser le même flow sur plusieurs missions et garder les variations d
|
||||
### Qu'est-ce qui est incomplet ?
|
||||
|
||||
- pas de vrai `GameManager` global ;
|
||||
- lock mouvement réparation désactivé ;
|
||||
- `repairRuntime` pas consommé ;
|
||||
- editor save uniquement en dev ;
|
||||
- hand tracking encore approximatif sur profondeur et smoothing.
|
||||
|
||||
@@ -869,8 +861,7 @@ Fichiers à avoir en tête :
|
||||
|
||||
Réponses pièges à réviser :
|
||||
|
||||
- lock mouvement repair désactivé actuellement ;
|
||||
- `repairRuntime` pas consommé ;
|
||||
- lock mouvement repair actif sur les étapes dédiées ;
|
||||
- player pas Rapier ;
|
||||
- save editor pas production ;
|
||||
- old boot flags non branchés.
|
||||
|
||||
@@ -46,13 +46,11 @@ It supports:
|
||||
The debug physics scene currently uses it to preview:
|
||||
|
||||
```txt
|
||||
public/models/electricienne_animated/model.gltf
|
||||
public/models/electricienne-animated/model.gltf
|
||||
```
|
||||
|
||||
with the `Dance` animation.
|
||||
|
||||
`src/hooks/animation/useCharacterAnimation.ts` is a hook-level alternative for components that need to own their group ref and animation controls directly.
|
||||
|
||||
## GLTF Reuse
|
||||
|
||||
Use `useClonedObject` when a GLTF scene is reused by a component instance. It memoizes `scene.clone(true)` and keeps clone creation out of render churn.
|
||||
|
||||
@@ -107,7 +107,7 @@ It owns:
|
||||
|
||||
- `mainState`
|
||||
- intro state
|
||||
- `bike`, `pylone`, and `ferme` mission state
|
||||
- `ebike`, `pylon`, and `farm` mission state
|
||||
- outro state
|
||||
- `isCinematicPlaying`
|
||||
- progression actions
|
||||
@@ -297,8 +297,7 @@ public/models/{name}/model.gltf
|
||||
- The repository is still a prototype.
|
||||
- There is no central production `GameManager`.
|
||||
- The repair game is implemented, but broader mission orchestration is still light.
|
||||
- `useRepairMovementLocked()` currently returns `false`, so repair movement lock is disabled even though the rule and UI component exist.
|
||||
- The repair-runtime setting is stored in settings but not consumed by the repair-game implementation.
|
||||
- `useRepairMovementLocked()` locks player movement during focused repair steps.
|
||||
- Player collision and Rapier gameplay physics are separate systems.
|
||||
- Editor persistence is local development tooling only.
|
||||
- Debug systems are still part of active scene composition and should remain easy to identify.
|
||||
|
||||
+25
-29
@@ -4,7 +4,7 @@ This document describes the map editor that exists in the current codebase.
|
||||
|
||||
## Purpose
|
||||
|
||||
The editor is a React route used to inspect and adjust the `public/map.json` scene data from inside the La-Fabrik app. It shares the same `MapNode` data format as the game scene and uses React Three Fiber for rendering.
|
||||
The editor is a React route used to inspect and adjust the current hierarchical `public/map.json` scene data from inside the La-Fabrik app. It exposes editable object nodes as a flat list for UI selection, while preserving and saving the full map tree.
|
||||
|
||||
## Routing
|
||||
|
||||
@@ -52,7 +52,7 @@ src/
|
||||
|
||||
## Responsibilities
|
||||
|
||||
`src/pages/editor/page.tsx` is the route-level composition component. It owns route-specific state such as selected object, hovered object, transform mode, selection lock, player-mode toggle, cinematic preview requests, and editor scene loading state.
|
||||
`src/pages/editor/page.tsx` is the route-level composition component. It owns route-specific state such as primary selected object, selected object indexes, hovered object, transform mode, selection lock, player-mode toggle, cinematic preview requests, and editor scene loading state.
|
||||
|
||||
`src/hooks/editor/useEditorSceneData.ts` loads the default map data and handles folder uploads.
|
||||
|
||||
@@ -60,7 +60,7 @@ src/
|
||||
|
||||
`src/components/editor/scene/EditorScene.tsx` composes the editor canvas scene, camera controls, lights, keyboard shortcuts, and `EditorMap`.
|
||||
|
||||
`src/components/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls.
|
||||
`src/components/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls. For multi-selection, it attaches `TransformControls` to a temporary group centered on the selected nodes, then decomposes the group delta back into each selected node transform.
|
||||
|
||||
`src/components/editor/EditorControls.tsx` renders the HTML control panel outside the canvas. The panel is organized into top-level `details` groups: `Editor`, `Cinematics`, `Dialogues`, and `SRT`.
|
||||
|
||||
@@ -72,7 +72,7 @@ src/
|
||||
|
||||
`src/controls/editor/FlyController.tsx` provides editor movement controls for player-style navigation.
|
||||
|
||||
`src/utils/map/loadMapSceneData.ts` is shared by the game map and editor. It loads `/map.json` and resolves available `public/models/{name}/model.glb` files first, then falls back to `public/models/{name}/model.gltf`.
|
||||
`src/utils/map/loadMapSceneData.ts` is shared by the game map and editor. It loads `/map.json`, validates the hierarchical payload, exposes editable nodes with their `sourcePath` back to the tree, and resolves available `public/models/{name}/model.glb` files first, then falls back to `public/models/{name}/model.gltf`.
|
||||
|
||||
`src/utils/editor/loadEditorScene.ts` contains editor-only upload handling for user-selected folders.
|
||||
|
||||
@@ -87,22 +87,13 @@ interface MapNode {
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
scale: [number, number, number];
|
||||
sourcePath?: number[];
|
||||
}
|
||||
```
|
||||
|
||||
`public/map.json` is expected to be a `MapNode[]`.
|
||||
`public/map.json` may be hierarchical. The editor keeps the hierarchy in `SceneData.mapTree` and stores editable entries in `SceneData.mapNodes` with a `sourcePath` back to the real tree node.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "pylone",
|
||||
"type": "Mesh",
|
||||
"position": [0, 5, 0],
|
||||
"rotation": [0, 1.57, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
```
|
||||
Group nodes use `role: "group"`; editable nodes keep `name`, `type`, `position`, `rotation`, and `scale`.
|
||||
|
||||
Each node `name` maps to a model folder:
|
||||
|
||||
@@ -122,20 +113,26 @@ If `model.glb` and `model.gltf` are both missing, the editor renders a fallback
|
||||
2. `useEditorSceneData` calls `loadMapSceneData()`.
|
||||
3. `loadMapSceneData()` loads `/map.json` and available model URLs.
|
||||
4. If `/map.json` is missing, the page displays a folder-upload flow.
|
||||
5. `EditorSceneLoadingTracker` uses drei `useProgress()` to update the fullscreen editor loading overlay while models load.
|
||||
5. The route-level loading overlay reports map JSON loading, then hands off to the editor scene once the map payload is ready.
|
||||
6. `EditorScene` renders the grid, lights, camera controls, and map nodes inside `Suspense`.
|
||||
7. `EditorControls` exposes transform mode, history actions, export, save, JSON preview, selection lock, and the cinematic/dialogue/SRT editors.
|
||||
7. `EditorControls` exposes transform mode, terrain snap, terrain-selection lock, add/delete node, precise scale inputs, history actions, camera focus/reset, export, save, JSON preview, selection lock, multi-selection status, and the cinematic/dialogue/SRT editors.
|
||||
|
||||
## Controls
|
||||
|
||||
- Click: select a node.
|
||||
- `Shift` + right click: add or remove a node from the multi-selection.
|
||||
- `Esc`: clear selection.
|
||||
- Click empty space: clear selection.
|
||||
- Selection lock button: prevent object clicks, empty-space clicks, and `Esc` from changing the current selection.
|
||||
- Selection lock button: prevent object clicks and `Esc` from changing the current selection.
|
||||
- Selection clear button: intentionally clear the current selection even when the lock is active.
|
||||
- `T`: translate mode.
|
||||
- `R`: rotate mode.
|
||||
- `S`: scale mode.
|
||||
- Snap terrain on move: enabled by default and applied while translating an object.
|
||||
- Multi-selection transforms use a temporary centered group and write the resulting position, rotation, and scale back to every selected map node.
|
||||
- Lock terrain: enabled by default so terrain remains visible but ignores selection clicks.
|
||||
- Camera action: centers on the selected object or resets to the editor home view.
|
||||
- Add node: creates a fallback cube under `blocking` using the requested model folder name.
|
||||
- Delete selected node: removes the editable node from the preserved map tree.
|
||||
- `Ctrl+Z` or `Cmd+Z`: undo.
|
||||
- `Ctrl+Y` or `Cmd+Y`: redo.
|
||||
- `WASD`, `ZQSD`, or arrow keys: move in player-controller mode.
|
||||
@@ -146,21 +143,20 @@ If `model.glb` and `model.gltf` are both missing, the editor renders a fallback
|
||||
|
||||
The editor supports two output paths:
|
||||
|
||||
- Export JSON downloads the current `MapNode[]` as `map.json`.
|
||||
- Save to Server posts the current `MapNode[]` to `/api/save-map`.
|
||||
- Export JSON downloads the current hierarchical map tree as `map.json`.
|
||||
- Save to Server posts the current hierarchical map tree to `/api/save-map`.
|
||||
|
||||
The dev-only `/api/save-map` endpoint is implemented by the Vite plugin in `vite.config.ts`. It writes to `public/map.json` and enforces a maximum payload size.
|
||||
The dev-only `/api/save-map` endpoint is implemented by the Vite plugin in `vite.config.ts`. It validates the payload through the shared map parser, writes to `public/map.json`, and enforces a maximum payload size.
|
||||
|
||||
## Editor Loading Overlay
|
||||
|
||||
The editor uses `SceneLoadingOverlay` like the runtime scene. `EditorSceneLoadingTracker` lives in `src/pages/editor/page.tsx` and reads drei `useProgress()` inside the canvas.
|
||||
The editor uses `SceneLoadingOverlay` like the runtime scene for the route-level map JSON loading phase.
|
||||
|
||||
The route tracks two loading phases:
|
||||
The route tracks the map JSON loading phase:
|
||||
|
||||
- map JSON loading through `useEditorSceneData()`
|
||||
- model loading through `useProgress()`
|
||||
|
||||
The overlay is rendered outside the canvas so it remains visible while the R3F scene mounts. The scene itself is wrapped in `Suspense` with a `null` fallback; the visual feedback is handled by the overlay instead of by the canvas fallback.
|
||||
The overlay is rendered outside the canvas so it remains visible while the editor route mounts. Model loading is left to R3F `Suspense` boundaries to avoid progress updates during model render.
|
||||
|
||||
## Panel Groups
|
||||
|
||||
@@ -192,9 +188,9 @@ The state is passed to:
|
||||
|
||||
- `EditorControls`, to render the lock/unlock button
|
||||
- `EditorScene`, to block `Esc` deselection when locked
|
||||
- `EditorMap`, to block object selection and empty-space deselection when locked
|
||||
- `EditorMap`, to block object selection when locked
|
||||
|
||||
The clear button calls `onClearSelection` directly from `EditorControls`. This is intentionally separate from scene click behavior so the user always has an explicit way to clear the selection.
|
||||
The clear button calls `onClearSelection` directly from `EditorControls`. Clicking empty canvas space does not clear the current selection; use `Esc` or the explicit clear button instead.
|
||||
|
||||
## Dialogue SRT Editing
|
||||
|
||||
|
||||
@@ -10,17 +10,23 @@ It is now also available to the production repair flow when a mission reaches a
|
||||
|
||||
## Runtime Flow
|
||||
|
||||
1. The browser captures webcam frames in `src/hooks/handTracking/useRemoteHandTracking.ts`.
|
||||
2. Frames are sent to the local Python backend over WebSocket.
|
||||
3. The backend runs MediaPipe hand landmark detection.
|
||||
4. The backend returns hand data including landmarks, handedness, score, center point, and `isFist`.
|
||||
5. React stores the latest snapshot in the hand tracking provider.
|
||||
6. `GrabbableObject` reads that snapshot each frame and uses fist state plus raycasting to grab objects.
|
||||
7. `HandTrackingGlove` reads the same snapshot and places the rigged `gant_l` and `gant_r` models on the detected hands when hand tracking is active.
|
||||
The frontend can run hand tracking with two interchangeable sources, selected from the debug source controller:
|
||||
|
||||
- **Browser JS** (`src/hooks/handTracking/useBrowserHandTracking.ts`) runs MediaPipe `hand_landmarker.task` directly in the browser via `@mediapipe/tasks-vision`. Default for debug.
|
||||
- **Backend** (`src/hooks/handTracking/useRemoteHandTracking.ts`) sends webcam frames as JPEG over WebSocket to a local Python process that runs MediaPipe and returns landmarks.
|
||||
|
||||
Both sources funnel into the same `HandTrackingContext` so all consumers see one shared snapshot:
|
||||
|
||||
1. The active source captures or receives landmarks.
|
||||
2. The hook applies an EMA smoothing pass on the landmarks before publishing the snapshot.
|
||||
3. `HandTrackingProvider` exposes that snapshot through React context.
|
||||
4. `GrabbableObject` reads the snapshot each frame and uses the fist state plus raycasting to grab objects.
|
||||
5. `HandTrackingGlove` reads the same snapshot and places a rigged glove on each detected hand.
|
||||
6. `HandTrackingVisualizer` paints an SVG wireframe overlay on top of the canvas.
|
||||
|
||||
## Activation Rules
|
||||
|
||||
Hand tracking is intentionally gated so the webcam and backend are not used all the time.
|
||||
Hand tracking is gated so the webcam and runtime are only spun up when actually needed.
|
||||
|
||||
The debug activation conditions are:
|
||||
|
||||
@@ -28,16 +34,26 @@ The debug activation conditions are:
|
||||
- scene mode is `physics`
|
||||
- the player is near an interaction, is holding an object, or is hand-holding an object
|
||||
|
||||
This keeps hand tracking active while the player is inside an interaction zone, even if the camera is not aimed directly at the object.
|
||||
|
||||
The production repair activation conditions are:
|
||||
|
||||
- active `mainState` is `bike`, `pylone`, or `ferme`
|
||||
- active `mainState` is `ebike`, `pylon`, or `farm`
|
||||
- the active mission step is `inspected`, `repairing`, `reassembling`, or `done`
|
||||
|
||||
This keeps the webcam off during `waiting`, `fragmented`, and `scanning`, then enables hand input only when the repair flow is expected to use hands.
|
||||
This keeps the webcam off during `waiting`, `fragmented`, and `scanning`.
|
||||
|
||||
In the current production repair flow, `inspected` uses a two-fists hold gesture to advance to `fragmented`. The hold must last one second and is independent from local object interaction distance once the mission is in the correct state. Keyboard input for the same transition is handled separately by the repair case trigger, so pressing `E` requires the case to be focused through the shared interaction system.
|
||||
### Linger
|
||||
|
||||
Once activation turns off (player walks back out of a trigger zone, or a mission step transitions away), the runtime stays alive for `HAND_TRACKING_LINGER_MS` (2000 ms) before being torn down. This gives MediaPipe enough time to finish initializing the webcam and load the model on a fresh entry — without the linger, a quick walk-through of a trigger zone never produces a detected hand.
|
||||
|
||||
## Provider Stability
|
||||
|
||||
`HandTrackingProvider` always renders the same JSX root (`HandTrackingRuntime`) and exposes `enabled` as a prop. Returning two different element types (`<HandTrackingContext value=IDLE>` vs `<ActiveHandTrackingProvider>`) used to be the historical shape and was the root cause of WebGL context loss: every `enabled` toggle forced React to remount the entire subtree, including the `<Canvas>`, which destroyed the WebGL renderer.
|
||||
|
||||
The two source hooks are therefore mounted in permanence with an `enabled` flag that they early-return on. No webcam or MediaPipe resources are created while `enabled` is false.
|
||||
|
||||
## StrictMode Resilience
|
||||
|
||||
In development, `<StrictMode>` mounts → unmounts → remounts each effect to surface non-idempotent code. The two source hooks delay their actual `start()` call by `HAND_TRACKING_RUNTIME_START_DELAY_MS` (80 ms) and clear the timer on cleanup, so a StrictMode double-mount or a rapid `nearby` flicker never reaches `getUserMedia` twice.
|
||||
|
||||
## Backend
|
||||
|
||||
@@ -52,7 +68,27 @@ The Python process uses MediaPipe and the local model file:
|
||||
backend/hand_landmarker.task
|
||||
```
|
||||
|
||||
The backend sends normalized hand coordinates and landmarks. The frontend treats the values as screen-space inputs, then maps them into world space with the active Three.js camera.
|
||||
The frontend sends JPEG frames at `HAND_TRACKING_FRAME_WIDTH × HAND_TRACKING_FRAME_HEIGHT` (320×240) to keep WebSocket bandwidth low. The backend sends normalized hand coordinates and landmarks.
|
||||
|
||||
## Browser MediaPipe
|
||||
|
||||
The browser path uses `hand_landmarker.task` (float16) downloaded from Google's MediaPipe model storage. The requested webcam resolution is **640×480** (`HAND_TRACKING_BROWSER_CAMERA_WIDTH/HEIGHT`), independent from the backend's 320×240. The float16 model is more sensitive than the backend Python model and needs the higher-resolution frame to detect hands reliably.
|
||||
|
||||
The MediaPipe delegate is currently `"GPU"`. CPU works too but is significantly slower; on a loaded scene the inference drops to ~5fps and the user feels noticeable lag during grab. MediaPipe creates its own WebGL context separate from Three.js, so there is no direct contention.
|
||||
|
||||
A singleton instance of `HandLandmarker` is cached in `src/lib/handTracking/browserHandTracking.ts`. `releaseBrowserHandLandmarker()` is called on cleanup and on WebGL context lost.
|
||||
|
||||
## Smoothing
|
||||
|
||||
MediaPipe at ~10 fps produces noticeable landmark jitter that, when fed raw into the scene, makes both the glove rig and any grabbed object tremble.
|
||||
|
||||
A simple exponential moving average is applied to every landmark before the snapshot is published:
|
||||
|
||||
```ts
|
||||
smoothed.x = previous.x * (1 - factor) + next.x * factor;
|
||||
```
|
||||
|
||||
The factor is `HAND_TRACKING_LANDMARK_SMOOTHING` (0.4). Hands are matched across frames by `handedness` so left/right don't bleed into each other.
|
||||
|
||||
## Frontend Data Shape
|
||||
|
||||
@@ -106,24 +142,36 @@ This is less expressive than true depth-aware hand movement, but it is more stab
|
||||
The current debug UI includes:
|
||||
|
||||
- `HandTrackingDebugPanel` inside `DebugOverlayLayout` for status, usage, loaded glove model, server state, hand count, and fist state
|
||||
- `HandTrackingVisualizer` for the SVG landmark wireframe fallback
|
||||
- `HandTrackingGlove` for the left-hand `gant_l` and right-hand `gant_r` models in the R3F scene
|
||||
- `HandTrackingVisualizer` for the SVG landmark overlay
|
||||
- `HandTrackingFallback` for the last-resort hand silhouette overlay
|
||||
- `HandTrackingGlove` for the per-hand rigged glove models in the R3F scene
|
||||
- `r3f-perf` for render performance
|
||||
- `lil-gui` for scene, camera, lighting, interaction, and grab controls
|
||||
|
||||
The hand tracking debug panel is a compact HTML grid outside the canvas. `Model loaded` displays the successfully loaded glove models. The SVG hand wireframe is only a fallback while models are loading or if a glove model fails to load.
|
||||
The SVG visualizer uses a "blueish hand" style: white connection lines between landmarks, cyan circles with a dark blue outline. The outline gets thicker when the hand is detected as a fist, so the user gets a visual confirmation of the grab gesture without having to look at the debug panel.
|
||||
|
||||
The fallback overlay (`HandTrackingFallback`) draws a simple open-hand or fist silhouette positioned on the detected wrist landmark. It only renders for a hand whose matching glove is in the `"error"` state in `useHandTrackingGloveStatus`. This guarantees the user always sees something on their hand even when the 3D glove model fails to load.
|
||||
|
||||
## Glove Models
|
||||
|
||||
The current glove MVP uses `public/models/gant_l/model.gltf` and `public/models/gant_r/model.gltf`, which contain GLTF skins and armatures. Each model is positioned, oriented, and scaled from palm landmarks, then each finger bone chain is rotated toward the matching MediaPipe landmark chain.
|
||||
`HandTrackingGlove` loads `public/models/gant_l/model.gltf` for both hands. The right hand applies `scale.x = -1` at the group level to mirror the mesh, so the thumb ends up on the correct side. Both hands therefore share the same rig and the same material.
|
||||
|
||||
The glove models are intentionally smaller than the raw SVG overlay so they do not dominate the camera view.
|
||||
The historical `public/models/gant_r/model.gltf` is kept as legacy but is not loaded by the frontend — its GLB embeds three skeletons (`Hand_l`, `Hand_l_pad`, `Hand_r`) plus a `galet` mesh, which made the finger rig unreliable.
|
||||
|
||||
The `gant_l` material is set to `alphaMode: OPAQUE` with `doubleSided: true`. The opaque mode prevents transparency sorting issues that made folded fingers disappear behind the palm; the double-sided flag covers the back faces revealed by the mirror scale on the right hand.
|
||||
|
||||
Two additional glove variants exist on disk:
|
||||
|
||||
- `public/models/gant_l_pad/model.gltf`
|
||||
- `public/models/gant_r_pad/model.gltf`
|
||||
|
||||
They are intended for future swap-by-state usage but are **not yet rigged**. They cannot be animated by MediaPipe landmarks in their current form — re-exporting them from Blender with the same armature structure as `gant_l` is a prerequisite.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- Production usage is currently limited to repair mission steps that explicitly need hands.
|
||||
- MediaPipe depth is relative and currently not used for stable object depth control.
|
||||
- The virtual hit zone is an approximation based on multiple raycasts, not a real 3D collider.
|
||||
- There is no smoothing layer for hand position or depth yet.
|
||||
- The SVG hand visualization is a fallback, not the primary display when glove models load correctly.
|
||||
- The right glove is a mirrored copy of `gant_l` rather than its own mesh; in the future a dedicated right-hand model would give a better visual.
|
||||
- The `_pad` glove variants are not rigged yet, so swap-by-state (normal ↔ pad) is not wired in.
|
||||
- Finger bone animation is an approximate landmark-to-bone mapping; it still needs calibration for per-model twist, offsets, and smoothing.
|
||||
|
||||
@@ -184,7 +184,7 @@ Input is ignored while:
|
||||
- the settings menu is open
|
||||
- a cinematic is playing
|
||||
|
||||
Movement lock is read separately from `useRepairMovementLocked`, but that hook currently returns `false` on this branch.
|
||||
Movement lock is read separately from `useRepairMovementLocked`, which locks the player during focused repair steps.
|
||||
|
||||
## UI Prompt
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
# Map LOD System
|
||||
|
||||
This document describes the runtime LOD system used by the production map.
|
||||
|
||||
## Goal
|
||||
|
||||
The map now supports two visual versions for selected models:
|
||||
|
||||
- the regular model in `public/models/<name>/`
|
||||
- the lighter model in `public/models/<name>-LOD/`
|
||||
|
||||
The runtime chooses between those paths from the active graphics preset. This keeps nearby objects visually richer while reducing the cost of distant objects.
|
||||
|
||||
## Graphics Presets
|
||||
|
||||
Presets are configured in:
|
||||
|
||||
```txt
|
||||
src/data/world/graphicsConfig.ts
|
||||
```
|
||||
|
||||
Current behavior:
|
||||
|
||||
| Preset | Chunk load distance | Fog | LOD behavior |
|
||||
| -------- | ------------------: | --- | ------------------------------------- |
|
||||
| `low` | 10m | On | Always use `*-LOD` models |
|
||||
| `medium` | 20m | On | Always use `*-LOD` models |
|
||||
| `high` | 35m | Off | Regular model up to 10m, then `*-LOD` |
|
||||
| `ultra` | 50m | Off | Regular model up to 20m, then `*-LOD` |
|
||||
|
||||
The unload distance stays slightly larger than the load distance to avoid rapid mount/unmount flickering when the player stands near a boundary.
|
||||
|
||||
## Runtime Selection
|
||||
|
||||
LOD path mapping lives in:
|
||||
|
||||
```txt
|
||||
src/data/world/mapLodConfig.ts
|
||||
```
|
||||
|
||||
The main selector is `selectMapModelPathByDistance()`. It receives:
|
||||
|
||||
- the current camera distance
|
||||
- the map model name
|
||||
- the regular model path
|
||||
- the active graphics preset
|
||||
|
||||
It returns either the regular path or the `*-LOD` path.
|
||||
|
||||
## Chunked Instanced Models
|
||||
|
||||
Repeated static assets are rendered through:
|
||||
|
||||
```txt
|
||||
src/world/map-instancing/MapInstancingSystem.tsx
|
||||
```
|
||||
|
||||
For each visible chunk, the system checks the nearest instance in that chunk. If the nearest instance is inside the high-detail threshold, the whole chunk uses the regular model. Otherwise, it uses the `*-LOD` model.
|
||||
|
||||
This is intentionally chunk-level LOD instead of per-instance LOD. It matches the existing chunk streaming architecture and avoids splitting every object into many tiny batches.
|
||||
|
||||
## Single And Generated Models
|
||||
|
||||
Single map nodes use:
|
||||
|
||||
```txt
|
||||
src/hooks/world/useMapLodModelPath.ts
|
||||
src/world/GameMap.tsx
|
||||
```
|
||||
|
||||
Some named map objects are rendered through dedicated generated components instead of the generic `GameMap` path. Those components must call `useMapLodModelPath()` directly.
|
||||
|
||||
Current dedicated generated components with LOD support:
|
||||
|
||||
```txt
|
||||
src/components/three/world/EcoleModel.tsx
|
||||
src/components/three/world/LaFabrikMapModel.tsx
|
||||
```
|
||||
|
||||
This matters for `lafabrik`: adding `public/models/lafabrik-LOD/` is not enough by itself. The component must also be connected to `useMapLodModelPath()`.
|
||||
|
||||
## Adding A New LOD Model
|
||||
|
||||
To add LOD support for a model:
|
||||
|
||||
1. Add the light model in `public/models/<name>-LOD/model.gltf`.
|
||||
2. Keep the regular model in `public/models/<name>/model.glb` or `public/models/<name>/model.gltf`.
|
||||
3. Add the mapping in `src/data/world/mapLodConfig.ts`.
|
||||
4. If the model uses a dedicated component, call `useMapLodModelPath()` in that component.
|
||||
5. Preload both paths when the component is dedicated and uses `useGLTF.preload()`.
|
||||
6. Verify the GLTF/GLB references: buffers, textures, opacity maps, and relative paths.
|
||||
|
||||
## Current LOD Models
|
||||
|
||||
The current explicit LOD mappings are:
|
||||
|
||||
```txt
|
||||
ebike
|
||||
eolienne
|
||||
pylone
|
||||
boiteimmeuble
|
||||
ecole
|
||||
immeuble1
|
||||
lafabrik
|
||||
maison1
|
||||
panneauaffichage
|
||||
talkie
|
||||
```
|
||||
|
||||
## Regression Risks
|
||||
|
||||
The most common failure modes are:
|
||||
|
||||
- the `*-LOD` folder exists but is missing from `mapLodConfig.ts`
|
||||
- a dedicated generated component keeps a hardcoded model path
|
||||
- GLTF references point to textures that were renamed during export
|
||||
- a model is added to LOD config but does not spawn through `GameMap` or `MapInstancingSystem`
|
||||
|
||||
Before committing model changes, validate both the regular and LOD folders for missing GLTF refs.
|
||||
@@ -0,0 +1,268 @@
|
||||
# Map Performance Notes
|
||||
|
||||
This document tracks the current map-rendering performance pass.
|
||||
|
||||
## Current Runtime Path
|
||||
|
||||
- `public/map.json` is the source of map transforms.
|
||||
- `src/world/GameMap.tsx` renders regular visual map nodes.
|
||||
- `src/world/vegetation/VegetationSystem.tsx` already instances dense vegetation.
|
||||
- `src/world/map-instancing/MapInstancingSystem.tsx` instances selected repeated static map assets.
|
||||
- `src/world/GameMapCollision.tsx` keeps terrain collision separate for the player octree.
|
||||
|
||||
## Draw-Call Bottlenecks Found
|
||||
|
||||
The first performance bottleneck was draw calls. Some assets were exported as many small GLTF primitives even when they used only a few materials.
|
||||
|
||||
| Model | Instances | Meshes / primitives | Notes |
|
||||
| ---------------- | --------: | ------------------: | ------------------------------------------------------------------------------------ |
|
||||
| `generateur` | 3 | 3152 | Worst draw-call offender. Needs asset-side mesh merging. |
|
||||
| `lafabrik` | 4 | 474 | High primitive count; current HD GLB has embedded geometry and no external textures. |
|
||||
| `ecole` | 1 | 107 | One material but many primitives; should be merged. |
|
||||
| `fermeverticale` | 3 | 1 | Geometry is fine; textures are large for the visible complexity. |
|
||||
|
||||
`generateur` was especially expensive because three visible instances could multiply thousands of primitives into thousands of draw calls. Instancing reduces repeated instance cost, but the source asset still needs a cleaner export.
|
||||
|
||||
## Runtime Merge Pass
|
||||
|
||||
`InstancedMapAsset` now groups source meshes by material and compatible geometry attributes before creating `THREE.InstancedMesh` objects. This reduces the runtime draw groups even when the source GLTF is exported as many small meshes.
|
||||
|
||||
Estimated source primitive count versus runtime merged groups:
|
||||
|
||||
| Model | Source primitives | Runtime merged groups |
|
||||
| ------------ | ----------------: | --------------------: |
|
||||
| `generateur` | 3152 | 8 |
|
||||
| `ecole` | 107 | 2 |
|
||||
| `eolienne` | 118 | 8 |
|
||||
| `lafabrik` | 474 | ~77 |
|
||||
|
||||
This is a code-side safety net, not a replacement for clean asset exports. Clean GLB exports with merged meshes and fewer textures remain the preferred long-term path.
|
||||
|
||||
## Current Triangle Bottleneck
|
||||
|
||||
After the runtime merge pass, draw calls can drop dramatically, but FPS can still stay low because the scene now remains triangle-bound. A debug capture after the merge showed roughly:
|
||||
|
||||
```txt
|
||||
138 draw calls
|
||||
~69.6M triangles
|
||||
~10 FPS
|
||||
```
|
||||
|
||||
That means the renderer is no longer mostly blocked by draw-call submission. It is mostly drawing too many visible triangles.
|
||||
|
||||
Estimated triangle contribution from `map.json` instance counts:
|
||||
|
||||
| Model | Instances | Triangles each | Estimated total triangles |
|
||||
| ------------------- | --------: | -------------: | ------------------------: |
|
||||
| `buisson` | 646 | 37 500 | ~24.2M |
|
||||
| `champdesoja` | 1181 | 16 268 | ~19.2M |
|
||||
| `arbre` | 291 | 38 906 | ~11.3M |
|
||||
| `champdeble` | 1307 | 6 260 | ~8.2M |
|
||||
| `champsdetournesol` | 1163 | 3 264 | ~3.8M |
|
||||
| `sapin` | 93 | 23 972 | ~2.2M |
|
||||
|
||||
These vegetation and crop assets account for almost all of the current `~69M` triangle count. By comparison, the previously suspicious static buildings are much smaller in triangle cost:
|
||||
|
||||
| Model | Estimated total triangles |
|
||||
| ---------------- | ------------------------: |
|
||||
| `generateur` | ~123k |
|
||||
| `lafabrik` | ~124k |
|
||||
| `ecole` | ~5k |
|
||||
| `fermeverticale` | ~1k |
|
||||
|
||||
`InstancedMesh` reduces draw calls, but it does not reduce triangle count. If 646 bushes each contain 37 500 triangles, the GPU still has to draw about 24 million bush triangles when those instances are visible.
|
||||
|
||||
## Debug Performance Controls
|
||||
|
||||
The debug-only performance folder can isolate model families when `?debug` is enabled.
|
||||
|
||||
Proposed controls:
|
||||
|
||||
```txt
|
||||
Performance / Map
|
||||
- vegetation
|
||||
- crops
|
||||
- trees
|
||||
- buildings
|
||||
- landmarks
|
||||
- props
|
||||
- terrain
|
||||
- sky
|
||||
```
|
||||
|
||||
Useful per-model toggles:
|
||||
|
||||
```txt
|
||||
buisson
|
||||
arbre
|
||||
sapin
|
||||
champdeble
|
||||
champdesoja
|
||||
champsdetournesol
|
||||
fermeverticale
|
||||
lafabrik
|
||||
immeuble1
|
||||
eolienne
|
||||
pylone
|
||||
```
|
||||
|
||||
The purpose is diagnostic, not final gameplay behavior. The expected workflow is:
|
||||
|
||||
1. Open `/?debug` with R3F perf enabled.
|
||||
2. Disable one family or model type.
|
||||
3. Watch `triangles`, `calls`, and FPS.
|
||||
4. Identify which model groups need LOD, density reduction, or asset re-export.
|
||||
|
||||
Recommended implementation files:
|
||||
|
||||
```txt
|
||||
src/managers/stores/useMapPerformanceStore.ts
|
||||
src/hooks/debug/useMapPerformanceDebug.ts
|
||||
src/world/vegetation/VegetationSystem.tsx
|
||||
src/world/map-instancing/MapInstancingSystem.tsx
|
||||
src/world/GameMap.tsx
|
||||
```
|
||||
|
||||
The store should stay runtime/debug-only. It should not change persisted production map data.
|
||||
|
||||
## Triangle-Reduction Follow-Up
|
||||
|
||||
Once the expensive model families are isolated, the real triangle fixes are:
|
||||
|
||||
1. Lower-poly vegetation and crop exports.
|
||||
2. LOD variants for trees, bushes, and crop fields.
|
||||
3. Distance-based culling for vegetation/crop instances.
|
||||
4. Chunked instancing so Three.js can frustum-cull groups instead of one huge global `InstancedMesh`.
|
||||
5. Billboard/impostor versions for far vegetation.
|
||||
|
||||
Chunked instancing is especially important. A single `InstancedMesh` containing every bush has one global bounding sphere. If that bounding sphere is visible, Three.js may keep the whole batch visible. Splitting instances into grid chunks allows entire offscreen chunks to be skipped.
|
||||
|
||||
## Player-Only Vegetation Streaming
|
||||
|
||||
The first distance-streaming pass is intentionally limited to vegetation and crop instances:
|
||||
|
||||
- `arbre`
|
||||
- `sapin`
|
||||
- `buisson`
|
||||
- `champdeble`
|
||||
- `champdesoja`
|
||||
- `champsdetournesol`
|
||||
|
||||
The behavior is configured in:
|
||||
|
||||
```txt
|
||||
src/data/world/fogConfig.ts
|
||||
```
|
||||
|
||||
Current runtime values:
|
||||
|
||||
```txt
|
||||
chunkSize: 35
|
||||
low load/unload radius: 10m / 18m
|
||||
medium load/unload radius: 20m / 30m
|
||||
high load/unload radius: 35m / 45m
|
||||
ultra load/unload radius: 50m / 65m
|
||||
updateInterval: 250ms
|
||||
fog near: 30
|
||||
fog far: 45
|
||||
```
|
||||
|
||||
The streaming and fog are scoped to the production game scene with the player camera only:
|
||||
|
||||
```txt
|
||||
sceneMode === "game" && cameraMode === "player"
|
||||
```
|
||||
|
||||
This matters for debugging. In debug camera mode there is no fog and no distance streaming, so the developer can inspect the full map freely. In player mode, chunks mount and unmount around the camera to reduce visible triangle count while fog hides vegetation pop-in.
|
||||
|
||||
Chunk cleanup is handled through React unmounting. `VegetationSystem` removes chunks from the tree, and `InstancedVegetation` removes its `THREE.InstancedMesh` objects from the group while disposing the locally created merged geometries/material clones in its own cleanup path.
|
||||
|
||||
## Runtime Texture Filtering
|
||||
|
||||
Loaded GLTF textures are normalized in code through:
|
||||
|
||||
```txt
|
||||
src/utils/three/optimizeGLTFScene.ts
|
||||
```
|
||||
|
||||
The runtime pass applies conservative texture filtering:
|
||||
|
||||
1. Cap anisotropy to a small value.
|
||||
2. Enable mipmap generation for regular PNG/JPG/WebP textures.
|
||||
3. Use trilinear mipmap filtering for minification.
|
||||
4. Keep existing opacity/alpha material mapping intact.
|
||||
|
||||
This mirrors the intent of the designer upload pipeline without rewriting model files at runtime. The sibling `upload-GLTF` project already has the stronger asset-side path: Blender GLB export with Draco, texture resizing, KTX2 generation with mipmaps, WebP fallback, and GLTF JSON URI/extension rewriting for `KHR_texture_basisu`.
|
||||
|
||||
Runtime texture filtering improves distant texture stability and GPU sampling behavior, but it does not reduce mesh triangle count. Triangle reduction still comes from streaming, distance unloading, or optimized source assets.
|
||||
|
||||
## Terrain-Snapped Map Placement
|
||||
|
||||
Map object heights are corrected at runtime through:
|
||||
|
||||
```txt
|
||||
src/hooks/three/useTerrainHeight.ts
|
||||
```
|
||||
|
||||
The terrain raycast is not done every frame. The terrain mesh list is built from the cached terrain GLTF, then each model or instance computes its snapped `y` when it is mounted or when its instance data changes.
|
||||
|
||||
Applied paths:
|
||||
|
||||
1. Regular `GameMap` model instances.
|
||||
2. Generated static map models.
|
||||
3. Instanced static map assets.
|
||||
4. Vegetation and crop chunks.
|
||||
|
||||
Only the `y` coordinate is replaced. `x`, `z`, and rotation stay from `map.json`. Runtime scale is also normalized when a static map node has a non-uniform scale, which prevents exported values like `[1, 2, 1]` from stretching or shrinking a map model unexpectedly.
|
||||
|
||||
## Current Code-Side Optimization
|
||||
|
||||
Repeated static assets are configured in:
|
||||
|
||||
```txt
|
||||
src/world/map-instancing/mapInstancingConfig.ts
|
||||
```
|
||||
|
||||
Those names are excluded from the regular `GameMap` clone path, then rendered by `MapInstancingSystem` with merged `THREE.InstancedMesh` batches.
|
||||
|
||||
This keeps the existing map authoring format while reducing repeated draw calls for selected assets.
|
||||
|
||||
## Generated R3F Model Path
|
||||
|
||||
Unique static map assets can use explicit R3F components instead of the generic cloned GLTF path. This follows the same intent as `gltfjsx`: expose the model as a React component, then keep control over mesh/material setup in code.
|
||||
|
||||
Current generated map-model entry point:
|
||||
|
||||
```txt
|
||||
src/world/map-generated/GeneratedMapNodeInstance.tsx
|
||||
```
|
||||
|
||||
Current generated model component:
|
||||
|
||||
```txt
|
||||
src/components/three/world/EcoleModel.tsx
|
||||
src/components/three/world/LafabrikModel.tsx
|
||||
src/components/three/world/FermeVerticaleModel.tsx
|
||||
src/components/three/world/GenerateurModel.tsx
|
||||
```
|
||||
|
||||
`ecole`, `lafabrik`, `fermeverticale`, and `generateur` use this path. Their components share the same merged static model renderer, which groups compatible geometry by material before mounting meshes.
|
||||
|
||||
This path should be used selectively. It improves control and can remove clone overhead, but it does not reduce source triangle count by itself.
|
||||
|
||||
## Asset-Side Follow-Up
|
||||
|
||||
Design/export should prioritize:
|
||||
|
||||
1. Produce lower-poly `buisson`, `arbre`, `sapin`, and crop assets.
|
||||
2. Add LOD or billboard variants for far vegetation.
|
||||
3. Merge `generateur` meshes from 3152 primitives to a small number of material groups.
|
||||
4. Keep `lafabrik` exports texture-light, and merge repeated material primitives where possible.
|
||||
5. Merge `ecole` primitives because it uses a single material.
|
||||
6. Prefer runtime `.glb` or compressed runtime textures when the pipeline supports it.
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- Do not instance `terrain` for player collision without validating `Octree.fromGraphNode` support.
|
||||
- Do not replace repair-game models with optimized map models unless repair node names are preserved.
|
||||
- Dispose only GPU resources created locally. Do not dispose textures or geometries owned by `useGLTF`'s cache.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Mission Flow
|
||||
|
||||
This document describes the mission intro and mission 2 prototype flow after it was merged into the current architecture.
|
||||
|
||||
## Source Of Truth
|
||||
|
||||
Mission flow state lives in the global game store:
|
||||
|
||||
```txt
|
||||
src/managers/stores/useGameStore.ts
|
||||
```
|
||||
|
||||
The store owns the `missionFlow` slice:
|
||||
|
||||
```ts
|
||||
missionFlow: {
|
||||
activityCity: boolean;
|
||||
playerName: string;
|
||||
canMove: boolean;
|
||||
dialogMessage: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
This keeps global gameplay state in Zustand instead of splitting it across a separate mission store or a gameplay manager.
|
||||
|
||||
## Managers Boundary
|
||||
|
||||
Managers stay responsible for local runtime services:
|
||||
|
||||
- `AudioManager` owns audio elements, audio pools, music playback, category volume, and stereo pan.
|
||||
- `InteractionManager` owns transient focused/nearby/held interaction handles.
|
||||
|
||||
Mission progression is not owned by a manager. Components update the store through explicit actions such as `setIntroStep`, `setCanMove`, `showDialog`, and `hideDialog`.
|
||||
|
||||
## Runtime Components
|
||||
|
||||
- `src/components/game/GameFlow.tsx` reacts to intro state and triggers one-off side effects such as intro audio and movement unlocks.
|
||||
- `src/components/zone/ZoneDetection.tsx` reads the camera position and moves the flow to a target step when the player enters a configured zone.
|
||||
- `src/world/GameStageContent.tsx` mounts repair games and their mission-start triggers.
|
||||
- `src/pages/page.tsx` mounts mission HTML overlays: `IntroUI`, `DialogMessage`, and subtitles.
|
||||
- `src/world/player/PlayerController.tsx` reads `missionFlow.canMove` as an additional movement lock.
|
||||
|
||||
## Step Sequence
|
||||
|
||||
The prototype currently uses these steps:
|
||||
|
||||
```ts
|
||||
"intro" |
|
||||
"start-intro" |
|
||||
"naming" |
|
||||
"bienvenue" |
|
||||
"star-move" |
|
||||
"mission2" |
|
||||
"searching" |
|
||||
"helped" |
|
||||
"manipulation" |
|
||||
"outOfFabrik";
|
||||
```
|
||||
|
||||
These steps are mission-flow prototype states. They do not replace `mainState` or the repair mission step machine used by `RepairGame`.
|
||||
|
||||
## Zone Configuration
|
||||
|
||||
Zone triggers live in:
|
||||
|
||||
```txt
|
||||
src/data/zones.ts
|
||||
```
|
||||
|
||||
Each zone has an id, position, radius, height, and `targetStep`. `ZoneDetection` marks a zone as triggered after the first activation so the same zone does not replay its transition every frame.
|
||||
|
||||
## Rules
|
||||
|
||||
- Keep mission flow state in `useGameStore.missionFlow`.
|
||||
- Do not reintroduce `GameStepManager` for global state transitions.
|
||||
- Do not create a second Zustand store for mission flow unless the state becomes independent from game progression.
|
||||
- Keep side effects such as audio playback in components or service managers, but keep the state transition itself in the store.
|
||||
- Keep per-frame values such as camera position and zone distance checks out of Zustand.
|
||||
@@ -8,11 +8,11 @@ The repair game is the current core gameplay loop. It gives three missions the s
|
||||
|
||||
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 |
|
||||
| Mission | Object | Role |
|
||||
| ------- | ------------- | --------------------------------------------- |
|
||||
| `ebike` | E-bike | Repair a damaged cooling core |
|
||||
| `pylon` | Power pylon | Restore relay/panel-like broken parts |
|
||||
| `farm` | Vertical farm | Stabilize irrigation/sensor-like broken parts |
|
||||
|
||||
## Main Files
|
||||
|
||||
@@ -79,7 +79,7 @@ src/managers/stores/useGameStore.ts
|
||||
- `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.
|
||||
The important architectural choice is that reusable repair components do not call `setEbikeState`, `setPylonState`, or `setFarmState` directly. They use generic mission actions so the same component can run for all three missions.
|
||||
|
||||
## Data-Driven Mission Config
|
||||
|
||||
@@ -159,7 +159,7 @@ The repair case appears near the mission object. The player can:
|
||||
|
||||
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.
|
||||
`useRepairMovementLocked()` locks player movement during focused repair steps and drives the repair movement indicator.
|
||||
|
||||
### Fragmented
|
||||
|
||||
@@ -324,9 +324,9 @@ 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]} />
|
||||
<RepairGame mission="ebike" position={[42.2399, 4.5484, 34.6468]} />
|
||||
<RepairGame mission="pylon" position={[64, 0, -66]} />
|
||||
<RepairGame mission="farm" position={[-24, 0, 42]} />
|
||||
```
|
||||
|
||||
Only the repair game whose `mission` matches `useGameStore().mainState` renders active content.
|
||||
|
||||
@@ -32,6 +32,8 @@ The loading progress in `HomePage` is monotonic:
|
||||
|
||||
This prevents the overlay from jumping backward when nested loaders finish in a slightly different order.
|
||||
|
||||
After the initial map boot is complete, late loading signals no longer reopen the full-screen loading overlay. Instead, `HomePage` shows the compact `AppLoadingIndicator` while the game remains visible. This is reserved for explicit runtime reload signals such as graphics preset changes, repair-state transitions, or late world loading events; chunk streaming intentionally does not drive this indicator.
|
||||
|
||||
## World Composition
|
||||
|
||||
`src/world/World.tsx` is the main scene composer.
|
||||
@@ -74,12 +76,31 @@ It tracks:
|
||||
- `showGameStage`: true when the map is ready enough to mount gameplay content
|
||||
- `gameplayReady`: true when map, stage, and octree are all ready
|
||||
|
||||
The final game-scene readiness condition is:
|
||||
The game-scene readiness condition is:
|
||||
|
||||
```ts
|
||||
showGameStage && gameStageLoaded && octree !== null;
|
||||
```
|
||||
|
||||
Shadows are configured once when `Lighting` mounts (renderer `shadowMap.enabled`, sun
|
||||
`shadow.autoUpdate = true`, bias and frustum from `SHADOW_CONFIG` in
|
||||
`src/data/world/lightingConfig.ts`). The shadow map then refreshes every frame and
|
||||
follows the player camera through the sun's `target`. The earlier `SceneShadowWarmup`
|
||||
step has been removed — the visible loading overlay no longer waits for a forced
|
||||
shadow refresh because `autoUpdate` covers steady-state rendering.
|
||||
|
||||
### Avoiding global scene remounts
|
||||
|
||||
Heavy stage components (`GameStageContent`, `Player`, dialogues) load assets via
|
||||
`useGLTF`/`useTexture` without preload (e.g. `EbikeSpeedometer` calls `useTexture`
|
||||
when the bike mounts). To prevent any late suspension from bubbling up to the
|
||||
root `<Suspense>` boundary in `src/pages/page.tsx` and unmounting the entire
|
||||
world (which would trigger a redundant octree rebuild and shadow re-config), the
|
||||
game stage block and the spawn-player block are wrapped in their own
|
||||
`<Suspense fallback={null}>` boundaries inside `src/world/World.tsx`. Any new
|
||||
sibling that suspends late should be added inside one of these boundaries or get
|
||||
its own.
|
||||
|
||||
The debug physics scene is ready when:
|
||||
|
||||
```ts
|
||||
|
||||
@@ -20,3 +20,63 @@ If DevTools still opens a bundled file, stop the dev server, clear Vite's cached
|
||||
rm -rf node_modules/.vite
|
||||
npm run dev:three-debug
|
||||
```
|
||||
|
||||
## Visual debug toggles
|
||||
|
||||
The `Debug` folder of the runtime debug GUI exposes inspection toggles backed by
|
||||
`src/managers/stores/useDebugVisualsStore.ts`:
|
||||
|
||||
- **Show Player Model** — renders the main character GLTF in front of the
|
||||
current camera (`src/components/debug/DebugPlayerModel.tsx`). The model is
|
||||
positioned in camera-local space so it stays visible regardless of pitch.
|
||||
- **Show Octree** — overlays the collision octree as colored line segments,
|
||||
one wireframe per spatial cell (`src/components/debug/DebugOctreeVisualization.tsx`).
|
||||
Cells are colored by depth. Use it to inspect collision precision around
|
||||
doorways or passages.
|
||||
- **Octree Max Depth** — caps how deep the octree visualization recurses
|
||||
(default 6). Increase to see leaf-level subdivisions; decrease to keep the
|
||||
scene readable when the tree is large.
|
||||
|
||||
The octree visualization reads the live `Octree` instance from `World`. The
|
||||
mesh uses `depthTest: false` and a high `renderOrder`, so cells stay visible
|
||||
through opaque geometry.
|
||||
|
||||
## Shadow rendering intermittence
|
||||
|
||||
Shadows occasionally failed to render on initial load and could disappear
|
||||
mid-session even though the `Lighting` configuration ran to completion. The
|
||||
fix has two layers:
|
||||
|
||||
### Per-frame refresh (steady state)
|
||||
|
||||
The sun follows the camera, so its world matrix is dirty every frame. With
|
||||
`shadow.autoUpdate` alone, three.js can skip the shadow map re-render on a
|
||||
frame where the matrix update has happened but the renderer's internal dirty
|
||||
tracking does not pick it up. To prevent that, `Lighting.useFrame` sets
|
||||
`sun.shadow.needsUpdate = true` after the per-frame matrix updates. Shadow
|
||||
config is centralized in `src/data/world/lightingConfig.ts` (`bias=0`,
|
||||
`normalBias=0`, `cameraSize=95`).
|
||||
|
||||
### Mount-time shadow map reallocation (`useShadowMapWarmup`)
|
||||
|
||||
The merged static map and other GLTFs mount imperatively after `Lighting`,
|
||||
so the shadow render target ends up linked to a renderer state that pre-dates
|
||||
the final scene. Materials compiled at that point bake a "no shadow map"
|
||||
permutation into their shader program and silently fail to render shadows
|
||||
until a WebGL context-restore cycle (the kind triggered by Chrome DevTools
|
||||
in `?debug` runs) reallocates everything.
|
||||
|
||||
`src/hooks/three/useShadowMapWarmup.ts` replays that cycle programmatically
|
||||
without the cost of a full context loss. It runs a `useFrame` watchdog that
|
||||
samples the scene mesh count every 6 frames; once the count has been stable
|
||||
for ~1 s (or after a 5 s safety cap), it:
|
||||
|
||||
1. Disposes the directional light shadow map and nulls it. three.js
|
||||
reallocates the render target on the next render at the configured
|
||||
`mapSize`.
|
||||
2. Marks every material's `needsUpdate = true`, forcing a shader recompile
|
||||
that rebinds every program to the freshly created shadow sampler.
|
||||
3. Forces a single shadow pass and invalidates the renderer.
|
||||
|
||||
The watchdog runs once per mount and adds a single traversal every 6 frames
|
||||
during the warmup window, after which it self-terminates.
|
||||
|
||||
+23
-30
@@ -28,11 +28,11 @@ They are under `src/managers/stores/` because they are shared runtime state, not
|
||||
|
||||
## Store Responsibilities
|
||||
|
||||
| Store | Responsibility |
|
||||
| ------------------ | ----------------------------------------------------------------- |
|
||||
| `useGameStore` | Durable game progression, mission steps, cinematic input lock |
|
||||
| `useSettingsStore` | Menu visibility, volumes, subtitle options, repair-runtime toggle |
|
||||
| `useSubtitleStore` | Currently displayed subtitle cue |
|
||||
| Store | Responsibility |
|
||||
| ------------------ | ------------------------------------------------------------- |
|
||||
| `useGameStore` | Durable game progression, mission steps, cinematic input lock |
|
||||
| `useSettingsStore` | Menu visibility, volumes, and subtitle options |
|
||||
| `useSubtitleStore` | Currently displayed subtitle cue |
|
||||
|
||||
## Managers vs Stores
|
||||
|
||||
@@ -65,18 +65,18 @@ Main states:
|
||||
| Main state | Role |
|
||||
| ---------- | ------------------------------- |
|
||||
| `intro` | Onboarding and opening sequence |
|
||||
| `bike` | E-bike repair sequence |
|
||||
| `pylone` | Power pylon repair sequence |
|
||||
| `ferme` | Vertical farm repair sequence |
|
||||
| `ebike` | E-bike repair sequence |
|
||||
| `pylon` | Power pylon repair sequence |
|
||||
| `farm` | Vertical farm repair sequence |
|
||||
| `outro` | Ending sequence |
|
||||
|
||||
Other important state:
|
||||
|
||||
- `isCinematicPlaying`
|
||||
- `intro`
|
||||
- `bike`
|
||||
- `pylone`
|
||||
- `ferme`
|
||||
- `ebike`
|
||||
- `pylon`
|
||||
- `farm`
|
||||
- `outro`
|
||||
|
||||
Mission steps:
|
||||
@@ -125,28 +125,28 @@ For development and debug tooling, direct setters also exist:
|
||||
```ts
|
||||
const setMainState = useGameStore((state) => state.setMainState);
|
||||
|
||||
setMainState("bike");
|
||||
setMainState("ebike");
|
||||
```
|
||||
|
||||
Direct setters are useful for debug panels, but production gameplay should prefer business actions such as:
|
||||
|
||||
- `advanceGameState`
|
||||
- `completeBike`
|
||||
- `completePylone`
|
||||
- `completeFerme`
|
||||
- `completeEbike`
|
||||
- `completePylon`
|
||||
- `completeFarm`
|
||||
- `completeMission`
|
||||
|
||||
Mission gameplay that can target `bike`, `pylone`, or `ferme` should prefer generic mission actions:
|
||||
Mission gameplay that can target `ebike`, `pylon`, or `farm` should prefer generic mission actions:
|
||||
|
||||
```ts
|
||||
const setMissionStep = useGameStore((state) => state.setMissionStep);
|
||||
const completeMission = useGameStore((state) => state.completeMission);
|
||||
|
||||
setMissionStep("bike", "inspected");
|
||||
completeMission("bike");
|
||||
setMissionStep("ebike", "inspected");
|
||||
completeMission("ebike");
|
||||
```
|
||||
|
||||
This keeps reusable gameplay components such as `RepairGame` from duplicating mission-specific branches like `setBikeState`, `setPyloneState`, and `setFermeState`.
|
||||
This keeps reusable gameplay components such as `RepairGame` from duplicating mission-specific branches like `setEbikeState`, `setPylonState`, and `setFarmState`.
|
||||
|
||||
## Settings Store
|
||||
|
||||
@@ -160,7 +160,6 @@ State:
|
||||
- `dialogueVolume`
|
||||
- `subtitlesEnabled`
|
||||
- `subtitleLanguage`
|
||||
- `repairRuntime`
|
||||
|
||||
Audio setters clamp values between `0` and `1`, then call:
|
||||
|
||||
@@ -170,8 +169,6 @@ AudioManager.getInstance().setCategoryVolume(category, nextVolume);
|
||||
|
||||
This keeps UI state and browser audio state synchronized.
|
||||
|
||||
Current caveat: `repairRuntime` is stored and displayed in the settings menu, but the repair game does not consume it yet. Treat it as a staged architecture hook rather than an active runtime switch.
|
||||
|
||||
## Subtitle Store
|
||||
|
||||
`useSubtitleStore` is intentionally tiny.
|
||||
@@ -191,9 +188,9 @@ State/actions:
|
||||
Current production repair placement:
|
||||
|
||||
```tsx
|
||||
<RepairGame mission="bike" position={[8, 0, -6]} />
|
||||
<RepairGame mission="pylone" position={[64, 0, -66]} />
|
||||
<RepairGame mission="ferme" position={[-24, 0, 42]} />
|
||||
<RepairGame mission="ebike" position={[42.2399, 4.5484, 34.6468]} />
|
||||
<RepairGame mission="pylon" position={[64, 0, -66]} />
|
||||
<RepairGame mission="farm" position={[-24, 0, 42]} />
|
||||
```
|
||||
|
||||
`RepairGame` reads the active mission step from the store and writes transitions through generic actions such as `setMissionStep` and `completeMission`.
|
||||
@@ -222,13 +219,11 @@ Current overlays:
|
||||
- `GameStateDebugPanel`: compact debug UI for viewing and switching main/sub states
|
||||
- `Crosshair`: player aiming helper
|
||||
- `InteractPrompt`: interaction prompt
|
||||
- `RepairMovementLockIndicator`: indicator intended for repair movement lock
|
||||
- `RepairMovementLockIndicator`: indicator shown while repair steps lock movement
|
||||
- `HandTrackingVisualizer`: hand tracking SVG fallback/debug visualization
|
||||
- `Subtitles`: active dialogue subtitle overlay
|
||||
- `GameSettingsMenu`: options menu and settings controls
|
||||
|
||||
Current caveat: `useRepairMovementLocked()` returns `false` immediately on the current branch, so the movement-lock rule and indicator exist but are disabled at runtime.
|
||||
|
||||
## Regression Rules
|
||||
|
||||
- Do not store per-frame values in Zustand.
|
||||
@@ -241,6 +236,4 @@ Current caveat: `useRepairMovementLocked()` returns `false` immediately on the c
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Decide whether `repairRuntime` should be removed, implemented, or clearly labeled as experimental.
|
||||
- Re-enable or remove the repair movement-lock rule depending on desired gameplay.
|
||||
- Move broader mission orchestration into a clearer layer if intro, mission, dialogue, and cinematic branching grows.
|
||||
|
||||
+43
-14
@@ -6,7 +6,7 @@ The map editor is available at `/editor`. It is a browser-based tool for editing
|
||||
|
||||
Use the editor when you need to:
|
||||
|
||||
- move, rotate, or scale objects from `public/map.json`
|
||||
- move, rotate, scale, add, or delete objects from `public/map.json`
|
||||
- inspect the raw JSON generated by the editor
|
||||
- preview and edit cinematics from `public/cinematics.json`
|
||||
- create, preview, and validate dialogue entries from `public/sounds/dialogue/dialogues.json`
|
||||
@@ -14,13 +14,13 @@ Use the editor when you need to:
|
||||
|
||||
The map editor reads the same map data as the runtime scene:
|
||||
|
||||
- `public/map.json` contains the object list.
|
||||
- `public/map.json` contains the current hierarchical runtime map.
|
||||
- `public/models/{name}/model.glb` contains the matching 3D model for each object name. `model.gltf` is still supported as a fallback during migration.
|
||||
- Missing models are displayed as gray fallback cubes, so incomplete maps remain editable.
|
||||
|
||||
## Map Node Format
|
||||
|
||||
Each entry in `public/map.json` represents one object:
|
||||
`public/map.json` is hierarchical. Group nodes such as `Scene`, `blocking`, `vegetation`, or `agriculture` organize the map. Editable object nodes still use the same transform fields:
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | ------------------------------------------------- |
|
||||
@@ -45,18 +45,34 @@ Only the `Editor` group is open by default. Open the other groups when you need
|
||||
|
||||
1. Open `/editor` in the local app.
|
||||
2. Click an object in the scene to select it.
|
||||
3. Choose a transform mode: translate, rotate, or scale.
|
||||
4. Drag the transform gizmo in the 3D view.
|
||||
5. Check the JSON inspector if you need exact values.
|
||||
6. Use undo or redo if the transform is not correct.
|
||||
7. Export the JSON or save it to the dev server.
|
||||
3. Use `Shift + right click` on other objects to add or remove them from the current multi-selection.
|
||||
4. Choose a transform mode: translate, rotate, or scale.
|
||||
5. Drag the transform gizmo in the 3D view. With multiple objects selected, the gizmo transforms the selected group and writes each object transform back to `map.json`.
|
||||
6. Keep `Snap terrain on move` enabled when placing objects on the terrain.
|
||||
7. Use `Center on object` or `Reset camera` from the `View` section when navigating large maps.
|
||||
8. Adjust scale numerically from the `Selection` section if the gizmo is not precise enough.
|
||||
9. Check the JSON inspector if you need exact values.
|
||||
10. Use undo or redo if the transform is not correct.
|
||||
11. Export the JSON or save it to the dev server.
|
||||
|
||||
## Adding And Deleting Nodes
|
||||
|
||||
Use `Add Node` to create a new editable object under the `blocking` group.
|
||||
|
||||
- The new object starts as a fallback cube at `[0, 0, 0]`.
|
||||
- Name it with the model folder name you want, for example `maison1`.
|
||||
- If `public/models/{name}/model.glb` or `model.gltf` exists, saving and reloading will display the matching model.
|
||||
- If no matching model exists, the node stays editable as a gray cube.
|
||||
|
||||
Use the trash button in `Selection` to delete the selected node from the map tree.
|
||||
|
||||
## Controls
|
||||
|
||||
| Action | Input |
|
||||
| -------------------- | -------------------------- |
|
||||
| Select object | Click object |
|
||||
| Deselect | `Esc` or click empty space |
|
||||
| Toggle multi-select | `Shift` + right click |
|
||||
| Deselect | `Esc` |
|
||||
| Lock selection | `Lock` button in Selection |
|
||||
| Clear selection | `X` button in Selection |
|
||||
| Translate mode | `T` |
|
||||
@@ -73,14 +89,25 @@ Only the `Editor` group is open by default. Open the other groups when you need
|
||||
The `Selection` section shows the selected object name and its index in `public/map.json`.
|
||||
|
||||
- Click an object to select it.
|
||||
- Click empty space or press `Esc` to clear the selection.
|
||||
- Use `Shift + right click` on objects to add or remove them from a multi-selection.
|
||||
- When several objects are selected, the gizmo appears on the selection group and applies translate, rotate, or scale to each selected node.
|
||||
- Press `Esc` to clear the selection.
|
||||
- Use the `X` button to clear the selection explicitly.
|
||||
- Use the `Lock` button to protect the current selection while editing.
|
||||
- Use the scale fields to edit X/Y/Z scale precisely.
|
||||
- Use the trash button to remove the selected object.
|
||||
|
||||
## Terrain Snapping
|
||||
|
||||
`Snap terrain on move` is enabled by default. When you move an object, the editor samples the terrain height at the object's X/Z position and updates its Y position.
|
||||
|
||||
This is intended for map objects that should sit on the ground. Disable it when you intentionally need a floating object.
|
||||
|
||||
`Lock terrain` is also enabled by default. The terrain stays visible, but terrain clicks are ignored so normal objects remain easier to select. Disable it only when you need to select or transform the terrain node itself.
|
||||
|
||||
When selection is locked:
|
||||
|
||||
- clicking another object does not change the selection
|
||||
- clicking empty space does not clear the selection
|
||||
- pressing `Esc` does not clear the selection
|
||||
- the `X` button still clears the selection intentionally
|
||||
|
||||
@@ -88,9 +115,11 @@ When selection is locked:
|
||||
|
||||
The `Lock view` action switches the editor into a movement mode closer to the runtime player camera. Use it to navigate larger scenes while keeping the transform tools available.
|
||||
|
||||
The camera action switches between `Center on object` and `Reset camera`. Selecting an object also focuses the camera on that object automatically.
|
||||
|
||||
## JSON Inspector
|
||||
|
||||
The `JSON` section shows the raw map data that will be exported or saved:
|
||||
The `JSON` section shows the editable node data:
|
||||
|
||||
- When no object is selected, it shows the full map node list.
|
||||
- When an object is selected, it highlights the JSON lines for that object.
|
||||
@@ -101,11 +130,11 @@ Use it to verify exact numeric transform values before saving or exporting. The
|
||||
|
||||
### Export JSON
|
||||
|
||||
`Export JSON` downloads the current map node list as `map.json`. Use this when you want to manually replace `public/map.json`.
|
||||
`Export JSON` downloads the current hierarchical map tree as `map.json`. Use this when you want to manually replace `public/map.json`.
|
||||
|
||||
### Save To Server
|
||||
|
||||
`Save to server` is available only during local development. It writes the edited map back to `public/map.json` through the Vite dev-server endpoint.
|
||||
`Save to server` is available only during local development. It writes the edited hierarchical map back to `public/map.json` through the Vite dev-server endpoint.
|
||||
|
||||
The button is hidden in production builds because production persistence is not implemented.
|
||||
|
||||
|
||||
+11
-12
@@ -37,7 +37,7 @@ This document lists the user-visible and developer-facing features implemented i
|
||||
- Input lock while the settings menu is open
|
||||
- Input lock while a cinematic is playing
|
||||
- Octree collision against dedicated map collision nodes, currently scoped to the `terrain` node
|
||||
- Repair movement-lock hook and indicator exist, but the hook currently returns `false`, so movement is not locked during repair on the current branch
|
||||
- Repair movement lock during focused repair steps, with a matching UI indicator
|
||||
|
||||
## Physics And Collision
|
||||
|
||||
@@ -63,12 +63,12 @@ This document lists the user-visible and developer-facing features implemented i
|
||||
|
||||
## Repair Gameplay
|
||||
|
||||
- Reusable `RepairGame` mounted for `bike`, `pylone`, and `ferme`
|
||||
- Reusable `RepairGame` mounted for `ebike`, `pylon`, and `farm`
|
||||
- Mission progression driven by Zustand and shared `MissionStep` types
|
||||
- Production repair positions:
|
||||
- `bike` at `[8, 0, -6]`
|
||||
- `pylone` at `[64, 0, -66]`
|
||||
- `ferme` at `[-24, 0, 42]`
|
||||
- `ebike` at `[42.2399, 4.5484, 34.6468]`
|
||||
- `pylon` at `[64, 0, -66]`
|
||||
- `farm` at `[-24, 0, 42]`
|
||||
- Debug physics repair playground zones for all three missions
|
||||
- Data-driven mission config in `src/data/gameplay/repairMissions.ts`
|
||||
- Mission flow: `locked -> waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done`
|
||||
@@ -80,9 +80,9 @@ This document lists the user-visible and developer-facing features implemented i
|
||||
- Fragmentation through repair-case trigger or two-fists hand gesture
|
||||
- Exploded model visualization through `ExplodableModel`
|
||||
- Scan visual that steps through exploded parts
|
||||
- Broken-part detection by configured `nodeName`, with fallback to first scanned parts
|
||||
- Broken-part detection by configured `nodeName`, with diagnostics when configured parts are missing
|
||||
- Persistent broken-part highlight and broken-part prompt after discovery
|
||||
- Grabbable replacement part choices, including decoys
|
||||
- Grabbable replacement part choices, including distractor parts
|
||||
- Grabbable broken parts that must be deposited back into the case
|
||||
- Snap-to-placeholder placement
|
||||
- Correct-part, wrong-part, and stored-part visual feedback
|
||||
@@ -95,7 +95,7 @@ This document lists the user-visible and developer-facing features implemented i
|
||||
## Game Progression Store
|
||||
|
||||
- Zustand `useGameStore` for durable gameplay progression
|
||||
- Main states: `intro`, `bike`, `pylone`, `ferme`, `outro`
|
||||
- Main states: `intro`, `ebike`, `pylon`, `farm`, `outro`
|
||||
- Per-mission repair step state
|
||||
- Per-mission completion flags
|
||||
- Generic mission helpers: `setMissionStep`, `completeMission`, `advanceGameState`, `rewindGameState`, `resetGame`
|
||||
@@ -108,12 +108,11 @@ This document lists the user-visible and developer-facing features implemented i
|
||||
- Music, SFX, and dialogue volume sliders
|
||||
- Subtitle visibility toggle
|
||||
- Subtitle language choice between French and English
|
||||
- Repair-runtime choice between JavaScript and Python modes stored in settings
|
||||
- Quit action that clears browser-accessible cookies and returns to `/`
|
||||
- Crosshair overlay
|
||||
- Interaction prompt
|
||||
- Subtitle overlay
|
||||
- Repair movement-lock indicator component, currently inactive because the lock hook returns `false`
|
||||
- Repair movement-lock indicator
|
||||
- Debug overlay layout
|
||||
- Scene loading overlay
|
||||
|
||||
@@ -192,7 +191,7 @@ This document lists the user-visible and developer-facing features implemented i
|
||||
- Debug game-state panel
|
||||
- Debug hand-tracking panel
|
||||
- Physics test scene with floor, grabbable object, trigger object, repair zones, and animated model preview
|
||||
- Animated `electricienne_animated` model preview restored in the debug physics scene
|
||||
- Animated `electricienne-animated` model preview restored in the debug physics scene
|
||||
|
||||
## Map And Content Editor
|
||||
|
||||
@@ -230,7 +229,7 @@ This document lists the user-visible and developer-facing features implemented i
|
||||
- Technical docs for architecture, scene runtime, repair game, interaction, editor, audio, hand tracking, Zustand, Three debugging, animation, and target architecture
|
||||
- User docs for implemented features, main feature, editor usage, and code-review preparation
|
||||
|
||||
## Not Implemented Or Incomplete
|
||||
## Known Gaps
|
||||
|
||||
- Complete production mission manager/orchestrator
|
||||
- Full mission HUD or minimap
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Galerie des modèles
|
||||
|
||||
La galerie est disponible sur `/gallery`. Elle permet de parcourir les modèles 3D présents dans `public/models/` sans lancer la boucle de gameplay principale.
|
||||
|
||||
## Objectif
|
||||
|
||||
Cette page sert à remercier et valoriser le travail des designers du projet La Fabrik. Chaque modèle est affiché dans un canvas dédié, avec la même skybox et le même lighting que l'expérience principale.
|
||||
|
||||
## Utilisation
|
||||
|
||||
1. Ouvrir `/gallery`.
|
||||
2. Utiliser les flèches en bas de l'écran pour passer au modèle précédent ou suivant.
|
||||
3. Tourner autour du modèle avec la souris ou le doigt.
|
||||
4. Utiliser le bouton de réglages à droite pour ouvrir ou fermer le panneau lumière.
|
||||
5. Lire le diagnostic texture discret pour savoir si le modèle chargé semble correct côté textures.
|
||||
|
||||
## Fonctionnement
|
||||
|
||||
- La liste des modèles est déclarée dans `src/data/galleryModels.ts`.
|
||||
- Le viewer utilise `@react-three/fiber` et `@react-three/drei`.
|
||||
- `OrbitControls` permet de manipuler la caméra autour du modèle.
|
||||
- `Bounds` et `Center` recadrent automatiquement le modèle actif.
|
||||
- `SkyModel` réutilise la skybox du jeu, avec un matériau non éclairé uniquement dans la galerie pour éviter que certaines faces deviennent noires avec une caméra orbitale libre.
|
||||
- Les lumières reprennent les valeurs par défaut du jeu, puis peuvent être ajustées dans le panneau latéral.
|
||||
- `OrbitControls` autorise une orbite verticale complète pour inspecter le dessous des modèles.
|
||||
- Le viewer désactive les normal maps dans la preview pour limiter les coutures visibles sur certains exports découpés en plusieurs meshes.
|
||||
- Les animations GLTF présentes dans un modèle sont lancées automatiquement.
|
||||
- Un diagnostic simple inspecte les matériaux chargés pour signaler les textures absentes ou non exploitables.
|
||||
|
||||
## Ajouter un modèle
|
||||
|
||||
1. Ajouter le dossier du modèle dans `public/models/{nom}`.
|
||||
2. Vérifier que le modèle possède un fichier chargeable, par exemple `model.gltf`, `model.glb` ou un nom explicite comme `potager.gltf`.
|
||||
3. Ajouter une entrée dans `src/data/galleryModels.ts` avec un `id`, un `name` et un `path`.
|
||||
|
||||
Exemple :
|
||||
|
||||
```ts
|
||||
{ id: "nouveau-modele", name: "Nouveau modèle", path: "/models/nouveau-modele/model.gltf" }
|
||||
```
|
||||
|
||||
## Limites connues
|
||||
|
||||
- Le navigateur ne liste pas automatiquement les dossiers de `public/models/`, donc la liste reste déclarative.
|
||||
- Les modèles très lourds peuvent prendre du temps à charger.
|
||||
- La galerie est un viewer simple : elle ne remplace pas les outils d'inspection avancée comme Blender ou le viewer d'upload.
|
||||
+10
-10
@@ -8,7 +8,7 @@ The main feature is a reusable repair flow mounted in the production game scene.
|
||||
|
||||
The current user flow is:
|
||||
|
||||
1. Enter a mission state such as `bike`, `pylone`, or `ferme`.
|
||||
1. Enter a mission state such as `ebike`, `pylon`, or `farm`.
|
||||
2. Move close to the active repair object in the game scene.
|
||||
3. Aim at the object and press the interaction key when prompted.
|
||||
4. The mission step moves from `waiting` to `inspected`.
|
||||
@@ -21,7 +21,7 @@ The current user flow is:
|
||||
11. Move each scanned broken part into a compatible placeholder so the damaged parts are stored in the case.
|
||||
12. Press `E` on the green install target to move to `reassembling`. Wrong parts turn the target red and cannot finish the repair.
|
||||
13. The exploded object animates back into its assembled form with completion particles, then moves to `done`.
|
||||
14. Press `E` on the completion target. The repair case closes, returns to the ground, disappears, then `completeMission` moves to the next mission or to `outro` after `ferme`.
|
||||
14. Press `E` on the completion target. The repair case closes, returns to the ground, disappears, then `completeMission` moves to the next mission or to `outro` after `farm`.
|
||||
|
||||
## Why It Matters
|
||||
|
||||
@@ -33,17 +33,17 @@ For implementation details, see `docs/technical/repair-game.md`.
|
||||
|
||||
In `waiting`, the active mission renders its repair object and the `interagir.webm` prompt in the game scene. The interaction uses the shared focus/raycast interaction system, so the player still gets the normal `E` prompt.
|
||||
|
||||
When the player inspects the object, `RepairGame` writes `inspected` through the generic mission store action. The repair case then appears from the mission config with a small pop animation. When the player is close enough, the case model floats upward and rotates gently to signal interactivity. The codebase also contains a shared repair movement-lock hook and HTML indicator, but `useRepairMovementLocked()` currently returns `false`, so movement remains available during the repair flow on the current branch.
|
||||
When the player inspects the object, `RepairGame` writes `inspected` through the generic mission store action. The repair case then appears from the mission config with a small pop animation. When the player is close enough, the case model floats upward and rotates gently to signal interactivity. The shared repair movement-lock hook and HTML indicator keep movement disabled during active repair steps.
|
||||
|
||||
In `inspected`, `RepairGame` can also move to `fragmented`. Keyboard input goes through the shared focus/raycast interaction system on the repair case, so the player must be close enough and aim at the case before pressing `E`. The hand-tracking path still uses a two-fists hold gesture and is state-based, so it does not depend on being inside a local object interaction radius.
|
||||
|
||||
In `fragmented`, the repair object is rendered with `ExplodableModel`, then automatically advances to `scanning`. In `scanning`, the exploded model remains visible, a blue scan visual moves from part to part, and a red halo/wire marker plus the configured broken UI video stay attached to configured broken parts after the scanner reaches them. The scan can match a specific `nodeName` when mission data provides one, otherwise it falls back to the first scanned parts as placeholder broken parts. In `repairing`, the case opens in a larger focused transform, `RepairCaseModel` traverses the case GLTF for empty nodes named `placeholder_*`, several grabbable replacement parts appear on those placeholder positions, and releasing a part near a placeholder snaps it into place with a short GSAP animation. Scanned broken parts are also rendered as grabbable objects and must be deposited into a compatible placeholder before the final install target validates. If `brokenParts[].placeholderName` is configured, that broken part snaps only to the matching placeholder; otherwise it can use any available placeholder. If the current case asset has no placeholder nodes, the flow keeps using fallback focus positions. Replacement parts show green or red placement feedback after snapping, broken parts show stored feedback after deposit, and the install target gives a short blocked feedback if the player tries to validate too early. The install target only validates when the configured correct replacement part is placed and all scanned broken parts have been deposited. In `reassembling`, the exploded model animates back into its assembled position with green completion particles before the flow moves to `done`. In `done`, the repaired object remains visible with a completion target; validating closes the repair case first, then plays the case exit animation before advancing the global mission progression.
|
||||
In `fragmented`, the repair object is rendered with `ExplodableModel`, then automatically advances to `scanning`. In `scanning`, the exploded model remains visible, a blue scan visual moves from part to part, and a red halo/wire marker plus the configured broken UI video stay attached to configured broken parts after the scanner reaches them. The scan matches configured broken parts by `nodeName` and reports diagnostics when a configured node is missing. In `repairing`, the case opens in a larger focused transform, `RepairCaseModel` traverses the case GLTF for empty nodes named `placeholder_*`, several grabbable replacement parts appear on those slot positions, and releasing a part near a slot snaps it into place with a short GSAP animation. Scanned broken parts are also rendered as grabbable objects and must be deposited into a compatible slot before the final install target validates. If `brokenParts[].caseSlotName` is configured, that broken part snaps only to the matching slot; otherwise it can use any available slot. If the current case asset has no slot nodes, the flow keeps using fallback focus positions and logs the fallback. Replacement parts show green or red placement feedback after snapping, broken parts show stored feedback after deposit, and the install target gives a short blocked feedback if the player tries to validate too early. The install target only validates when the configured correct replacement part is placed and all scanned broken parts have been deposited. In `reassembling`, the exploded model animates back into its assembled position with green completion particles before the flow moves to `done`. In `done`, the repaired object remains visible with a completion target; validating closes the repair case first, then plays the case exit animation before advancing the global mission progression.
|
||||
|
||||
The mission config now carries the mission-specific variations. `bike` repairs one cooling core, `pylone` scans and stores both the lamp relay and a damaged panel with slower scan/reassembly timing, and `ferme` scans and stores an irrigation pump plus humidity sensor with faster scan/reassembly timing.
|
||||
The mission config now carries the mission-specific variations. `ebike` repairs one cooling core, `pylon` scans and stores both the lamp relay and a damaged panel with slower scan/reassembly timing, and `farm` scans and stores an irrigation pump plus humidity sensor with faster scan/reassembly timing.
|
||||
|
||||
## Key Files
|
||||
|
||||
- `src/world/GameStageContent.tsx` mounts production `RepairGame` instances for `bike`, `pylone`, and `ferme`.
|
||||
- `src/world/GameStageContent.tsx` mounts production `RepairGame` instances for `ebike`, `pylon`, and `farm`.
|
||||
- `src/components/three/gameplay/RepairCompletionStep.tsx` renders the final repaired object, completion target, case exit animation, and mission UI prompt.
|
||||
- `src/components/three/gameplay/RepairGame.tsx` composes the reusable production repair flow.
|
||||
- `src/components/three/gameplay/RepairBrokenPartHighlight.tsx` renders the red halo and wire marker around detected broken parts during scanning.
|
||||
@@ -56,16 +56,16 @@ The mission config now carries the mission-specific variations. `bike` repairs o
|
||||
- `src/components/three/gameplay/RepairPromptVideo.tsx` renders `.webm` prompts inside the 3D scene.
|
||||
- `src/components/three/gameplay/RepairScanSequence.tsx` keeps the exploded model visible and advances the scan from part to part.
|
||||
- `src/components/three/gameplay/RepairScanVisual.tsx` renders the scan halo and scan line around the active part.
|
||||
- `src/components/ui/RepairMovementLockIndicator.tsx` renders the HTML indicator intended for repair movement lock.
|
||||
- `src/components/ui/RepairMovementLockIndicator.tsx` renders the HTML repair movement-lock indicator.
|
||||
- `src/hooks/gameplay/useRepairFragmentationInput.ts` handles the `inspected -> fragmented` two-fists input and can optionally bind keyboard input for non-trigger flows.
|
||||
- `src/hooks/gameplay/useRepairMissionStep.ts` reads the active mission step from the game store.
|
||||
- `src/hooks/gameplay/useRepairMovementLocked.ts` exposes the shared repair movement-lock rule used by the player controller and UI indicator, but currently returns `false`.
|
||||
- `src/hooks/gameplay/useRepairMovementLocked.ts` exposes the shared repair movement-lock rule used by the player controller and UI indicator.
|
||||
- `src/hooks/handTracking/useBothFistsHold.ts` detects the reusable two-fists hold gesture.
|
||||
- `src/components/three/gameplay/RepairCaseModel.tsx` renders and animates the case model, and exposes `placeholder_*` transforms when the GLTF provides them.
|
||||
- `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 flow timing constants.
|
||||
- `src/data/gameplay/repairMissions.ts` stores reusable repair mission config for `bike`, `pylone`, and `ferme`.
|
||||
- `src/data/gameplay/repairMissions.ts` stores reusable repair mission config for `ebike`, `pylon`, and `farm`.
|
||||
- `src/managers/stores/useGameStore.ts` stores mission progression state and generic mission step helpers.
|
||||
- `src/types/gameplay/repairMission.ts` contains shared repair mission ids, mission steps, and guards used by the store, data config, debug UI, and gameplay components.
|
||||
|
||||
@@ -73,7 +73,7 @@ The mission config now carries the mission-specific variations. `bike` repairs o
|
||||
|
||||
The production repair flow currently requires:
|
||||
|
||||
- the active `mainState` to be one of `bike`, `pylone`, or `ferme`
|
||||
- the active `mainState` to be one of `ebike`, `pylon`, or `farm`
|
||||
- `GameStageContent` mounted inside the game scene Rapier `Physics` boundary
|
||||
- model assets available under `public/models/`
|
||||
- sound assets available under `public/sounds/`
|
||||
|
||||
Generated
+1
@@ -22,6 +22,7 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"three": "0.182.0",
|
||||
"three-stdlib": "^2.36.1",
|
||||
"zustand": "^5.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"map:transform": "node scripts/transformMap.cjs",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc -b"
|
||||
},
|
||||
@@ -32,6 +33,7 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"three": "0.182.0",
|
||||
"three-stdlib": "^2.36.1",
|
||||
"zustand": "^5.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
Binary file not shown.
+40324
-4587
File diff suppressed because it is too large
Load Diff
+35051
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user