Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9cf87d2d6 | |||
| d654565f87 | |||
| 947025cbf5 | |||
| f2cecfbbc9 | |||
| 1b9ac5c996 | |||
| e45fdbf97d | |||
| 08b01715c0 | |||
| a2fc417be6 | |||
| 57498b9bb1 | |||
| b87a7e929c | |||
| ea23b4bb46 | |||
| 3881e38a6d | |||
| 7a72743e5c | |||
| 65651405b6 | |||
| 81cd935bba | |||
| fe989c9550 | |||
| e15fb18d6b | |||
| 0230795f55 | |||
| b1fca3a25b | |||
| 3eba38a80b | |||
| 0992aacec6 | |||
| bfe184dea4 | |||
| 4ee13b0336 | |||
| fdd530a3e7 | |||
| 2b676d985d | |||
| b89eedd5be | |||
| d38ad242d6 | |||
| c2b16434fb | |||
| ab100c683f | |||
| b8cff43545 | |||
| 25e0f7e062 | |||
| ab3943eef3 | |||
| 4ebb5b8c25 | |||
| a6787a7ecb | |||
| d816e4b07e | |||
| 665d9f9702 | |||
| 0696ca2ae3 | |||
| d6d3d5b685 | |||
| 1c27d55e5a | |||
| fd558db034 | |||
| fbe8c0c854 | |||
| 88b6db6166 | |||
| 2b08665508 | |||
| 4f8355e934 | |||
| 417afdc1d5 | |||
| 235a38f67b | |||
| f54e71fc03 | |||
| 6d178dc59e | |||
| b4a3545460 | |||
| 1f6d9659ed | |||
| a52d57ae6c | |||
| d0497ec42c | |||
| 4c42e11268 | |||
| f175ad6240 | |||
| a4383a7cec | |||
| d07ffc4662 | |||
| d17738eaf1 | |||
| 50fa94b3ad | |||
| 44f9d68ef1 | |||
| e4857135b1 | |||
| 1f6335092a | |||
| f035195b56 | |||
| b8e5c4d1a9 | |||
| 4e6582b543 | |||
| e4ee2d768b | |||
| 5e594c51f7 | |||
| 26ddbebe14 | |||
| 48c2b4f0cd | |||
| cf08062def | |||
| 4f25b33d3b | |||
| 072dec03b4 | |||
| 529c60adae | |||
| 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 |
@@ -1,59 +1,58 @@
|
||||
name: 🔁 Weekly Branch Promotions
|
||||
name: 🔁 Branch Promotions
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 6 * * 1"
|
||||
- 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: weekly-branch-promotions
|
||||
group: branch-promotions
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
open-promotion-pr:
|
||||
name: Open ${{ matrix.head }} → ${{ matrix.base }}
|
||||
design-to-develop:
|
||||
name: Open design → develop
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- head: develop
|
||||
base: design
|
||||
title: "chore: merge develop into design"
|
||||
- head: design
|
||||
base: main
|
||||
title: "chore: merge design into main"
|
||||
|
||||
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@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 🔁 Open promotion PR
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
BASE_BRANCH: ${{ matrix.base }}
|
||||
HEAD_BRANCH: ${{ matrix.head }}
|
||||
PR_TITLE: ${{ matrix.title }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git fetch origin "$BASE_BRANCH" "$HEAD_BRANCH"
|
||||
git fetch origin develop design
|
||||
|
||||
if git merge-base --is-ancestor "origin/$HEAD_BRANCH" "origin/$BASE_BRANCH"; then
|
||||
echo "No promotion needed: $BASE_BRANCH already contains $HEAD_BRANCH."
|
||||
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 "$BASE_BRANCH" \
|
||||
--head "$GITHUB_REPOSITORY_OWNER:$HEAD_BRANCH" \
|
||||
--base develop \
|
||||
--head "$GITHUB_REPOSITORY_OWNER:design" \
|
||||
--json number \
|
||||
--jq '.[0].number // empty')"
|
||||
|
||||
@@ -63,7 +62,50 @@ jobs:
|
||||
fi
|
||||
|
||||
gh pr create \
|
||||
--base "$BASE_BRANCH" \
|
||||
--head "$HEAD_BRANCH" \
|
||||
--title "$PR_TITLE" \
|
||||
--body "Automated weekly promotion PR from \`$HEAD_BRANCH\` to \`$BASE_BRANCH\`."
|
||||
--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
-33
@@ -52,7 +52,7 @@ intro → start-intro → naming → bienvenue → star-move → mission2 → se
|
||||
- **Actions** :
|
||||
- Stocke `activityCity: false` dans le store Zustand
|
||||
- Joue l'audio `alertCentral`
|
||||
- **État** : Les objets avec hook `useActivityCity()` détectent le changement et jouent leurs animations
|
||||
- **État** : Les systèmes lisent `activityCity` depuis `useGameStore` pour adapter leur comportement
|
||||
- **Attente** : Le joueur atteint la zone de trigger pour `searching_problem`
|
||||
|
||||
### 7. `searching_problem`
|
||||
@@ -81,15 +81,13 @@ intro → start-intro → naming → bienvenue → star-move → mission2 → se
|
||||
|
||||
| Fichier | Rôle |
|
||||
| --------------------------------------- | --------------------------------------------------------- |
|
||||
| `src/stores/gameStore.ts` | Store Zustand pour l'état global du jeu |
|
||||
| `src/stateManager/GameStepManager.ts` | Synchronise avec le store Zustand |
|
||||
| `src/managers/stores/useGameStore.ts` | Store Zustand pour l'état global du jeu |
|
||||
| `src/components/game/GameFlow.tsx` | Gère les transitions automatiques et la lecture audio |
|
||||
| `src/components/ui/IntroUI.tsx` | Affiche l'input pour le prénom et le message de bienvenue |
|
||||
| `src/components/zone/ZoneDetection.tsx` | Détecte quand le joueur entre dans une zone |
|
||||
| `src/components/3d/CentralObject.tsx` | Objet interactif "central" pour la mission 2 |
|
||||
| `src/world/GameStageContent.tsx` | Monte les contenus de mission dans la scène |
|
||||
| `src/data/audioConfig.ts` | Chemins des fichiers audio |
|
||||
| `src/data/zones.ts` | Configuration des zones de transition |
|
||||
| `src/hooks/useActivityCity.ts` | Hook pour détecter le changement d'activité de la ville |
|
||||
|
||||
---
|
||||
|
||||
@@ -134,35 +132,14 @@ export const ZONES: Zone[] = [
|
||||
## Store Zustand
|
||||
|
||||
```typescript
|
||||
// src/stores/gameStore.ts
|
||||
// src/managers/stores/useGameStore.ts
|
||||
interface GameState {
|
||||
step: GameStep;
|
||||
activityCity: boolean;
|
||||
playerName: string;
|
||||
canMove: boolean;
|
||||
setStep: (step: GameStep) => void;
|
||||
setActivityCity: (value: boolean) => void;
|
||||
setPlayerName: (name: string) => void;
|
||||
setCanMove: (canMove: boolean) => void;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hooks personnalisés
|
||||
|
||||
### useActivityCity
|
||||
|
||||
Permet aux objets 3D de réagir au changement d'activité de la ville :
|
||||
|
||||
```typescript
|
||||
import { useActivityCity } from "@/hooks/useActivityCity";
|
||||
|
||||
function MyAnimatedObject() {
|
||||
const activityCity = useActivityCity(); // true par défaut, false en mission2
|
||||
|
||||
// L'animation se déclenche quand activityCity change à false
|
||||
// Utiliser useEffect pour réagir au changement
|
||||
mainState: MainGameState;
|
||||
missionFlow: {
|
||||
activityCity: boolean;
|
||||
canMove: boolean;
|
||||
playerName: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -143,6 +143,7 @@ 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/editor.md` | Editor implementation details |
|
||||
| `docs/technical/animation.md` | Animated, explodable, and reusable 3D model components |
|
||||
| `docs/user/features.md` | Implemented feature inventory |
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -51,8 +51,6 @@ 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.
|
||||
|
||||
+18
-20
@@ -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:
|
||||
|
||||
@@ -124,11 +115,12 @@ If `model.glb` and `model.gltf` are both missing, the editor renders a fallback
|
||||
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.
|
||||
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.
|
||||
@@ -136,6 +128,12 @@ If `model.glb` and `model.gltf` are both missing, the editor renders a fallback
|
||||
- `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,10 +144,10 @@ 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
|
||||
|
||||
|
||||
+10
-33
@@ -52,7 +52,7 @@ intro → start-intro → naming → bienvenue → star-move → mission2 → se
|
||||
- **Actions** :
|
||||
- Stocke `activityCity: false` dans le store Zustand
|
||||
- Joue l'audio `alertCentral`
|
||||
- **État** : Les objets avec hook `useActivityCity()` détectent le changement et jouent leurs animations
|
||||
- **État** : Les systèmes lisent `activityCity` depuis `useGameStore` pour adapter leur comportement
|
||||
- **Attente** : Le joueur atteint la zone de trigger pour `searching_problem`
|
||||
|
||||
### 7. `searching_problem`
|
||||
@@ -81,15 +81,13 @@ intro → start-intro → naming → bienvenue → star-move → mission2 → se
|
||||
|
||||
| Fichier | Rôle |
|
||||
| --------------------------------------- | --------------------------------------------------------- |
|
||||
| `src/stores/gameStore.ts` | Store Zustand pour l'état global du jeu |
|
||||
| `src/stateManager/GameStepManager.ts` | Synchronise avec le store Zustand |
|
||||
| `src/managers/stores/useGameStore.ts` | Store Zustand pour l'état global du jeu |
|
||||
| `src/components/game/GameFlow.tsx` | Gère les transitions automatiques et la lecture audio |
|
||||
| `src/components/ui/IntroUI.tsx` | Affiche l'input pour le prénom et le message de bienvenue |
|
||||
| `src/components/zone/ZoneDetection.tsx` | Détecte quand le joueur entre dans une zone |
|
||||
| `src/components/3d/CentralObject.tsx` | Objet interactif "central" pour la mission 2 |
|
||||
| `src/world/GameStageContent.tsx` | Monte les contenus de mission dans la scène |
|
||||
| `src/data/audioConfig.ts` | Chemins des fichiers audio |
|
||||
| `src/data/zones.ts` | Configuration des zones de transition |
|
||||
| `src/hooks/useActivityCity.ts` | Hook pour détecter le changement d'activité de la ville |
|
||||
|
||||
---
|
||||
|
||||
@@ -134,35 +132,14 @@ export const ZONES: Zone[] = [
|
||||
## Store Zustand
|
||||
|
||||
```typescript
|
||||
// src/stores/gameStore.ts
|
||||
// src/managers/stores/useGameStore.ts
|
||||
interface GameState {
|
||||
step: GameStep;
|
||||
activityCity: boolean;
|
||||
playerName: string;
|
||||
canMove: boolean;
|
||||
setStep: (step: GameStep) => void;
|
||||
setActivityCity: (value: boolean) => void;
|
||||
setPlayerName: (name: string) => void;
|
||||
setCanMove: (canMove: boolean) => void;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hooks personnalisés
|
||||
|
||||
### useActivityCity
|
||||
|
||||
Permet aux objets 3D de réagir au changement d'activité de la ville :
|
||||
|
||||
```typescript
|
||||
import { useActivityCity } from "@/hooks/useActivityCity";
|
||||
|
||||
function MyAnimatedObject() {
|
||||
const activityCity = useActivityCity(); // true par défaut, false en mission2
|
||||
|
||||
// L'animation se déclenche quand activityCity change à false
|
||||
// Utiliser useEffect pour réagir au changement
|
||||
mainState: MainGameState;
|
||||
missionFlow: {
|
||||
activityCity: boolean;
|
||||
canMove: boolean;
|
||||
playerName: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
# Game States & Substates
|
||||
|
||||
Documentation technique pour le testing et debugging du flow de jeu.
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
```
|
||||
intro ──► bike ──► pylone ──► ferme ──► outro
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Main States
|
||||
|
||||
| State | Description |
|
||||
| -------- | ------------------------------------------------------------------ |
|
||||
| `intro` | Séquence d'introduction (cinématique, naming, premier déplacement) |
|
||||
| `bike` | Mission de réparation du vélo |
|
||||
| `pylone` | Quête du pylone (alert → searching → helped → manipulation) |
|
||||
| `ferme` | Mission de réparation de la ferme |
|
||||
| `outro` | Fin du jeu |
|
||||
|
||||
---
|
||||
|
||||
## Intro (GameStep)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ intro.currentStep │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
intro ──► sequence_video ──► naming ──► start-move ──► bike
|
||||
```
|
||||
|
||||
### Étapes
|
||||
|
||||
| Step | Trigger | Action | Passage vers |
|
||||
| ---------------- | -------- | ---------------------------------------------- | ------------------------------------------------ |
|
||||
| `intro` | Initial | État initial | Auto → `sequence_video` quand `sceneReady: true` |
|
||||
| `sequence_video` | GameFlow | Déclenche la cinématique dans `GameCinematics` | Fin cinématique → `naming` |
|
||||
| `naming` | IntroUI | Affiche l'input pour le prénom | Submit → `start-move` |
|
||||
| `start-move` | GameFlow | Active le mouvement (`canMove: true`) | Via zone "fabrikExit" → `bike` |
|
||||
|
||||
### Transition vers bike
|
||||
|
||||
- **Trigger**: Zone `fabrikExit` dans `zones.ts`
|
||||
- **Action**: `advanceGameState()` dans `ZoneDetection.tsx`
|
||||
- **Résultat**: `mainState: "bike"`, `intro.hasCompleted: true`
|
||||
|
||||
---
|
||||
|
||||
## Bike (MissionStep)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ bike.currentStep (MissionStep) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
locked ──► waiting ──► inspected ──► fragmented ──► scanning ──► repairing ──► reassembling ──► done
|
||||
```
|
||||
|
||||
### Transition vers pylone
|
||||
|
||||
- **Trigger**: `bike.currentStep: "done"`
|
||||
- **Action**: `completeBikeState()` dans useGameStore
|
||||
- **Résultat**: `mainState: "pylone"`, `pylone.currentStep: "locked"`
|
||||
|
||||
---
|
||||
|
||||
## Pylone (PyloneStep)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ pylone.currentStep (PyloneStep) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
locked (bypass) ──► alert ──► searching ──► helped ──► manipulation ──► outro
|
||||
```
|
||||
|
||||
### Étapes
|
||||
|
||||
| Step | Trigger | Action | Passage vers |
|
||||
| -------------- | ---------------------------------- | ---------------------------------- | --------------------------------------------------------- |
|
||||
| `locked` | Initial (après bike) | État initial | **Bypass automatique** → `alert` via `advanceGameState()` |
|
||||
| `alert` | `advanceGameState()` | Affiche l'alerte (à implémenter) | Via `advanceGameState()` |
|
||||
| `searching` | `advanceGameState()` | Déclenché par zone "searchingZone" | Via `advanceGameState()` |
|
||||
| `helped` | Interaction avec `NPCHelper` | Dialogue avec le villageois | Via interaction 3D |
|
||||
| `manipulation` | Interaction avec `PyloneDestroyed` | Interaction avec l'objet central | Via `advanceGameState()` → `outro` |
|
||||
|
||||
### Bypass automatique
|
||||
|
||||
```typescript
|
||||
// useGameStore.ts - advancePyloneStep()
|
||||
if (state.pylone.currentStep === "locked") {
|
||||
return { pylone: { ...state.pylone, currentStep: "alert" } };
|
||||
}
|
||||
```
|
||||
|
||||
### Transition vers outro
|
||||
|
||||
- **Trigger**: `pylone.currentStep: "manipulation"` + `advanceGameState()`
|
||||
- **Action**: `advancePyloneStep()` détecte fin de la séquence
|
||||
- **Résultat**: `mainState: "outro"`
|
||||
|
||||
---
|
||||
|
||||
## Ferme (MissionStep)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ferme.currentStep (MissionStep) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
locked ──► waiting ──► inspected ──► fragmented ──► scanning ──► repairing ──► reassembling ──► done
|
||||
```
|
||||
|
||||
### Transition vers outro
|
||||
|
||||
- **Trigger**: `ferme.currentStep: "done"`
|
||||
- **Action**: `completeFermeState()` dans useGameStore
|
||||
- **Résultat**: `mainState: "outro"`, `ferme.irrigationFixed: true`
|
||||
|
||||
---
|
||||
|
||||
## Outro
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ outro.hasStarted │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
waiting ──► started
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debug Panel
|
||||
|
||||
Le debug panel permet de tester toutes les transitions :
|
||||
|
||||
### Utilisation
|
||||
|
||||
1. Ouvrir le jeu en mode debug (`Debug: true` dans `Debug.ts`)
|
||||
2. Le panneau "Game State" apparaît en bas à gauche
|
||||
3. **Main state**: Sélectionner le state principal
|
||||
4. **Sub state**: Sélectionner le sub-state
|
||||
5. **Previous/Next step**: Avancer ou reculer d'un step
|
||||
6. **Reset**: Remettre à l'état initial
|
||||
|
||||
### Raccourcis clavier
|
||||
|
||||
| Action | Clavier |
|
||||
| ------- | ------------------ |
|
||||
| Avancer | Debug panel button |
|
||||
| Reculer | Debug panel button |
|
||||
| Reset | Debug panel button |
|
||||
|
||||
---
|
||||
|
||||
## Comment tester chaque section
|
||||
|
||||
### Tester l'intro
|
||||
|
||||
1. Vérifier que `sceneReady: false` au démarrage
|
||||
2. Attendre que le loader termine (`sceneReady: true`)
|
||||
3. Vérifier `intro.currentStep: "intro"` → auto vers `sequence_video`
|
||||
4. Si cinématique fonctionne : `sequence_video` → `naming`
|
||||
5. Entrer un prénom : `naming` → `start-move`
|
||||
6. Vérifier `canMove: true` après `start-move`
|
||||
7. Entrer dans la zone `fabrikExit` → `mainState: "bike"`
|
||||
|
||||
### Tester bike
|
||||
|
||||
1. Via debug panel, avancer jusqu'à `done`
|
||||
2. Vérifier `mainState: "pylone"`
|
||||
|
||||
### Tester pylone
|
||||
|
||||
1. Via debug panel, avancer (bypass `locked` → `alert`)
|
||||
2. Vérifier `pylone.currentStep: "alert"`
|
||||
3. Avancer : `alert` → `searching` → `helped` → `manipulation`
|
||||
4. Après `manipulation`, vérifier `mainState: "outro"`
|
||||
|
||||
### Tester ferme
|
||||
|
||||
1. Via debug panel, avancer dans bike jusqu'à `done`
|
||||
2. Vérifier `mainState: "pylone"` → `ferme` (après pylone)
|
||||
3. Avancer jusqu'à `done`
|
||||
4. Vérifier `mainState: "outro"`
|
||||
|
||||
---
|
||||
|
||||
## Fichiers clés
|
||||
|
||||
| Fichier | Rôle |
|
||||
| ------------------------------------------------- | ------------------------------------- |
|
||||
| `src/managers/stores/useGameStore.ts` | Store Zustand avec toutes les actions |
|
||||
| `src/types/game.ts` | Définition de `GameStep` |
|
||||
| `src/types/gameplay/pylone.ts` | Définition de `PyloneStep` |
|
||||
| `src/types/gameplay/repairMission.ts` | Définition de `MissionStep` |
|
||||
| `src/components/game/GameFlow.tsx` | Logique de transition de l'intro |
|
||||
| `src/components/zone/ZoneDetection.tsx` | Déclenchement des zones |
|
||||
| `src/components/ui/debug/GameStateDebugPanel.tsx` | Outil de debug |
|
||||
|
||||
---
|
||||
|
||||
## État initial
|
||||
|
||||
```typescript
|
||||
{
|
||||
mainState: "intro",
|
||||
isCinematicPlaying: false,
|
||||
sceneReady: false,
|
||||
missionFlow: {
|
||||
activityCity: true,
|
||||
canMove: false,
|
||||
dialogMessage: null,
|
||||
playerName: "",
|
||||
},
|
||||
intro: { currentStep: "intro", dialogueAudio: null, hasCompleted: false, isBikeUnlocked: false },
|
||||
bike: { currentStep: "locked", dialogueAudio: null, isRepaired: false },
|
||||
pylone: { currentStep: "locked", dialogueAudio: null, isPowered: false },
|
||||
ferme: { currentStep: "locked", dialogueAudio: null, irrigationFixed: false },
|
||||
outro: { dialogueAudio: null, hasStarted: false },
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,266 @@
|
||||
# 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 | 56 | Moderate draw calls, heavy 2048 texture set. |
|
||||
| `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` | 56 | 14 |
|
||||
|
||||
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 next useful runtime tool is a debug-only performance folder that can isolate model families. This should be mounted only 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
|
||||
loadRadius: 45
|
||||
unloadRadius: 45
|
||||
updateInterval: 350ms
|
||||
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. Reduce `lafabrik` texture count and downscale flat/low-detail maps.
|
||||
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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
+41
-11
@@ -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,17 +45,33 @@ 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 |
|
||||
| Toggle multi-select | `Shift` + right click |
|
||||
| Deselect | `Esc` or click empty space |
|
||||
| Lock selection | `Lock` button in Selection |
|
||||
| Clear selection | `X` button in Selection |
|
||||
@@ -73,9 +89,21 @@ 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.
|
||||
- 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.
|
||||
- Click empty space or 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:
|
||||
|
||||
@@ -88,9 +116,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 +131,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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,11 +33,11 @@ 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.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,21 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"cinematics": [
|
||||
{
|
||||
"id": "intro_sequence",
|
||||
"trigger": "intro_sequence",
|
||||
"cameraKeyframes": [
|
||||
{ "time": 0, "position": [8, 5, 12], "target": [0, 2, 0] },
|
||||
{ "time": 8, "position": [12, 4, -6], "target": [10, 1.4, -8] },
|
||||
{ "time": 16, "position": [5, 6, -15], "target": [0, 3, -20] },
|
||||
{ "time": 24, "position": [0, 8, -30], "target": [0, 0, -40] }
|
||||
],
|
||||
"dialogueCues": [
|
||||
{ "time": 0, "dialogueId": "intro_welcome" },
|
||||
{ "time": 8, "dialogueId": "intro_explanation" },
|
||||
{ "time": 16, "dialogueId": "intro_mission" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "intro_overview",
|
||||
"timecode": 0,
|
||||
|
||||
+40548
-35030
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.
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