14 Commits

Author SHA1 Message Date
math-pixel 988c8305bc update remove naming step 2026-05-14 11:58:43 +02:00
math-pixel 5ee52ec752 feat: video intro 2026-05-14 11:50:40 +02:00
math-pixel 3ece1d76de feat: video player 2026-05-14 11:35:03 +02:00
math-pixel 3222d2ed3d update: position intro 2026-05-14 11:16:58 +02:00
math-pixel 41c38a35b2 feat/net shader 2026-05-14 10:34:36 +02:00
math-pixel 6399a2f89f feat: add net shader 2026-05-13 17:07:12 +02:00
math-pixel f5d3d080c8 update bike 2026-05-13 14:05:25 +02:00
math-pixel cac66ebaee Merge branch 'develop' into feat/intro 2026-05-13 11:14:25 +02:00
math-pixel c155d847e9 Merge branch 'develop' into feat/intro 2026-05-13 11:11:28 +02:00
math-pixel f567540f22 fix: step issue 2026-05-13 10:00:19 +02:00
math-pixel 688302d985 wip 2026-05-13 09:05:45 +02:00
math-pixel f9d7c3f00e update: loading waiting 2026-05-12 21:47:54 +02:00
math-pixel 28c6ef199f feat: sequencing 2026-05-12 21:44:43 +02:00
math-pixel ff79448ce8 update : cinematic trigger 2026-05-12 17:07:53 +02:00
707 changed files with 38033 additions and 84581 deletions
+29 -71
View File
@@ -1,58 +1,59 @@
name: 🔁 Branch Promotions name: 🔁 Weekly Branch Promotions
on: on:
schedule: schedule:
- cron: "0 6 * * 1,4" # Lundi et Jeudi à 6h UTC (design → develop) - cron: "0 6 * * 1"
- cron: "0 6 * * 1" # Lundi à 6h UTC (develop → main)
workflow_dispatch: workflow_dispatch:
inputs:
promotion:
description: "Which promotion to run"
required: true
type: choice
options:
- design-to-develop
- develop-to-main
- both
permissions: permissions:
contents: read contents: read
pull-requests: write pull-requests: write
concurrency: concurrency:
group: branch-promotions group: weekly-branch-promotions
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
design-to-develop: open-promotion-pr:
name: Open design → develop name: Open ${{ matrix.head }} → ${{ matrix.base }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: | strategy:
(github.event_name == 'schedule') || fail-fast: false
(github.event_name == 'workflow_dispatch' && (github.event.inputs.promotion == 'design-to-develop' || github.event.inputs.promotion == 'both')) matrix:
include:
- head: develop
base: design
title: "chore: merge develop into design"
- head: design
base: main
title: "chore: merge design into main"
steps: steps:
- name: ⬇️ Checkout - name: ⬇️ Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: 🔁 Open promotion PR - name: 🔁 Open promotion PR
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
BASE_BRANCH: ${{ matrix.base }}
HEAD_BRANCH: ${{ matrix.head }}
PR_TITLE: ${{ matrix.title }}
run: | run: |
set -euo pipefail set -euo pipefail
git fetch origin develop design git fetch origin "$BASE_BRANCH" "$HEAD_BRANCH"
if git merge-base --is-ancestor origin/design origin/develop; then if git merge-base --is-ancestor "origin/$HEAD_BRANCH" "origin/$BASE_BRANCH"; then
echo "No promotion needed: develop already contains design." echo "No promotion needed: $BASE_BRANCH already contains $HEAD_BRANCH."
exit 0 exit 0
fi fi
existing_pr="$(gh pr list \ existing_pr="$(gh pr list \
--state open \ --state open \
--base develop \ --base "$BASE_BRANCH" \
--head "$GITHUB_REPOSITORY_OWNER:design" \ --head "$GITHUB_REPOSITORY_OWNER:$HEAD_BRANCH" \
--json number \ --json number \
--jq '.[0].number // empty')" --jq '.[0].number // empty')"
@@ -62,50 +63,7 @@ jobs:
fi fi
gh pr create \ gh pr create \
--base develop \ --base "$BASE_BRANCH" \
--head design \ --head "$HEAD_BRANCH" \
--title "chore: merge design into develop" \ --title "$PR_TITLE" \
--body "Automated promotion PR from \`design\` to \`develop\`." --body "Automated weekly promotion PR from \`$HEAD_BRANCH\` to \`$BASE_BRANCH\`."
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\`."
+31 -8
View File
@@ -52,7 +52,7 @@ intro → start-intro → naming → bienvenue → star-move → mission2 → se
- **Actions** : - **Actions** :
- Stocke `activityCity: false` dans le store Zustand - Stocke `activityCity: false` dans le store Zustand
- Joue l'audio `alertCentral` - Joue l'audio `alertCentral`
- **État** : Les systèmes lisent `activityCity` depuis `useGameStore` pour adapter leur comportement - **État** : Les objets avec hook `useActivityCity()` détectent le changement et jouent leurs animations
- **Attente** : Le joueur atteint la zone de trigger pour `searching_problem` - **Attente** : Le joueur atteint la zone de trigger pour `searching_problem`
### 7. `searching_problem` ### 7. `searching_problem`
@@ -81,13 +81,15 @@ intro → start-intro → naming → bienvenue → star-move → mission2 → se
| Fichier | Rôle | | Fichier | Rôle |
| --------------------------------------- | --------------------------------------------------------- | | --------------------------------------- | --------------------------------------------------------- |
| `src/managers/stores/useGameStore.ts` | Store Zustand pour l'état global du jeu | | `src/stores/gameStore.ts` | Store Zustand pour l'état global du jeu |
| `src/stateManager/GameStepManager.ts` | Synchronise avec le store Zustand |
| `src/components/game/GameFlow.tsx` | Gère les transitions automatiques et la lecture audio | | `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/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/zone/ZoneDetection.tsx` | Détecte quand le joueur entre dans une zone |
| `src/world/GameStageContent.tsx` | Monte les contenus de mission dans la scène | | `src/components/3d/CentralObject.tsx` | Objet interactif "central" pour la mission 2 |
| `src/data/audioConfig.ts` | Chemins des fichiers audio | | `src/data/audioConfig.ts` | Chemins des fichiers audio |
| `src/data/zones.ts` | Configuration des zones de transition | | `src/data/zones.ts` | Configuration des zones de transition |
| `src/hooks/useActivityCity.ts` | Hook pour détecter le changement d'activité de la ville |
--- ---
@@ -132,14 +134,35 @@ export const ZONES: Zone[] = [
## Store Zustand ## Store Zustand
```typescript ```typescript
// src/managers/stores/useGameStore.ts // src/stores/gameStore.ts
interface GameState { interface GameState {
mainState: MainGameState; step: GameStep;
missionFlow: {
activityCity: boolean; activityCity: boolean;
canMove: boolean;
playerName: string; 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
} }
``` ```
-1
View File
@@ -143,7 +143,6 @@ WS ws://localhost:8000/ws
| `docs/technical/hand-tracking.md` | Webcam, backend/browser MediaPipe, glove, and gesture flow | | `docs/technical/hand-tracking.md` | Webcam, backend/browser MediaPipe, glove, and gesture flow |
| `docs/technical/zustand.md` | Game, settings, and subtitle stores | | `docs/technical/zustand.md` | Game, settings, and subtitle stores |
| `docs/technical/three-debugging.md` | DevTools workflow for stepping into Three.js internals | | `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/editor.md` | Editor implementation details |
| `docs/technical/animation.md` | Animated, explodable, and reusable 3D model components | | `docs/technical/animation.md` | Animated, explodable, and reusable 3D model components |
| `docs/user/features.md` | Implemented feature inventory | | `docs/user/features.md` | Implemented feature inventory |
+2 -1
View File
@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass, field
from typing import Any from typing import Any
from uuid import uuid4 from uuid import uuid4
@@ -13,6 +13,7 @@ class ClientConnection:
websocket: WebSocket websocket: WebSocket
is_processing: bool = False is_processing: bool = False
last_frame_at: float = 0.0 last_frame_at: float = 0.0
metadata: dict[str, Any] = field(default_factory=dict)
class ConnectionManager: class ConnectionManager:
+13 -4
View File
@@ -509,7 +509,12 @@ Gère :
- menu ouvert/fermé ; - menu ouvert/fermé ;
- volumes ; - volumes ;
- sous-titres ; - sous-titres ;
- langue. - langue ;
- `repairRuntime`.
Piège :
`repairRuntime` est stocké et affiché, mais pas encore utilisé par `RepairGame`.
### Subtitle store ### Subtitle store
@@ -527,8 +532,9 @@ Phrase simple :
Si on te pose une question précise, réponds vrai. Si on te pose une question précise, réponds vrai.
| Sujet | Réponse honnête | | Sujet | Réponse honnête |
| ------------------------- | -------------------------------------------------------------------- | | ------------------------- | ------------------------------------------------------------------------ |
| Lock mouvement réparation | Les étapes repair actives bloquent le déplacement via le hook dédié. | | 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. | | Old debug flags | `noMusic`, `noMap`, `noDialogues`, etc. ne sont plus branchés. |
| Player physics | Le joueur n'est pas Rapier, il utilise capsule + octree. | | Player physics | Le joueur n'est pas Rapier, il utilise capsule + octree. |
| Collision map | L'octree vient de nodes dédiés, actuellement `terrain`. | | Collision map | L'octree vient de nodes dédiés, actuellement `terrain`. |
@@ -827,6 +833,8 @@ Pour réutiliser le même flow sur plusieurs missions et garder les variations d
### Qu'est-ce qui est incomplet ? ### Qu'est-ce qui est incomplet ?
- pas de vrai `GameManager` global ; - pas de vrai `GameManager` global ;
- lock mouvement réparation désactivé ;
- `repairRuntime` pas consommé ;
- editor save uniquement en dev ; - editor save uniquement en dev ;
- hand tracking encore approximatif sur profondeur et smoothing. - hand tracking encore approximatif sur profondeur et smoothing.
@@ -861,7 +869,8 @@ Fichiers à avoir en tête :
Réponses pièges à réviser : Réponses pièges à réviser :
- lock mouvement repair actif sur les étapes dédiées ; - lock mouvement repair désactivé actuellement ;
- `repairRuntime` pas consommé ;
- player pas Rapier ; - player pas Rapier ;
- save editor pas production ; - save editor pas production ;
- old boot flags non branchés. - old boot flags non branchés.
+2
View File
@@ -51,6 +51,8 @@ public/models/electricienne_animated/model.gltf
with the `Dance` animation. 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 ## 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. 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.
+20 -18
View File
@@ -4,7 +4,7 @@ This document describes the map editor that exists in the current codebase.
## Purpose ## Purpose
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. 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.
## Routing ## Routing
@@ -52,7 +52,7 @@ src/
## Responsibilities ## Responsibilities
`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/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/hooks/editor/useEditorSceneData.ts` loads the default map data and handles folder uploads. `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/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. 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/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls.
`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`. `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/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`, 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/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/editor/loadEditorScene.ts` contains editor-only upload handling for user-selected folders. `src/utils/editor/loadEditorScene.ts` contains editor-only upload handling for user-selected folders.
@@ -87,13 +87,22 @@ interface MapNode {
position: [number, number, number]; position: [number, number, number];
rotation: [number, number, number]; rotation: [number, number, number];
scale: [number, number, number]; scale: [number, number, number];
sourcePath?: number[];
} }
``` ```
`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. `public/map.json` is expected to be a `MapNode[]`.
Group nodes use `role: "group"`; editable nodes keep `name`, `type`, `position`, `rotation`, and `scale`. ```json
[
{
"name": "pylone",
"type": "Mesh",
"position": [0, 5, 0],
"rotation": [0, 1.57, 0],
"scale": [1, 1, 1]
}
]
```
Each node `name` maps to a model folder: Each node `name` maps to a model folder:
@@ -115,12 +124,11 @@ 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. 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. `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`. 6. `EditorScene` renders the grid, lights, camera controls, and map nodes inside `Suspense`.
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. 7. `EditorControls` exposes transform mode, history actions, export, save, JSON preview, selection lock, and the cinematic/dialogue/SRT editors.
## Controls ## Controls
- Click: select a node. - Click: select a node.
- `Shift` + right click: add or remove a node from the multi-selection.
- `Esc`: clear selection. - `Esc`: clear selection.
- Click empty space: 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, empty-space clicks, and `Esc` from changing the current selection.
@@ -128,12 +136,6 @@ If `model.glb` and `model.gltf` are both missing, the editor renders a fallback
- `T`: translate mode. - `T`: translate mode.
- `R`: rotate mode. - `R`: rotate mode.
- `S`: scale 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+Z` or `Cmd+Z`: undo.
- `Ctrl+Y` or `Cmd+Y`: redo. - `Ctrl+Y` or `Cmd+Y`: redo.
- `WASD`, `ZQSD`, or arrow keys: move in player-controller mode. - `WASD`, `ZQSD`, or arrow keys: move in player-controller mode.
@@ -144,10 +146,10 @@ If `model.glb` and `model.gltf` are both missing, the editor renders a fallback
The editor supports two output paths: The editor supports two output paths:
- Export JSON downloads the current hierarchical map tree as `map.json`. - Export JSON downloads the current `MapNode[]` as `map.json`.
- Save to Server posts the current hierarchical map tree to `/api/save-map`. - Save to Server posts the current `MapNode[]` to `/api/save-map`.
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. 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.
## Editor Loading Overlay ## Editor Loading Overlay
+31 -8
View File
@@ -52,7 +52,7 @@ intro → start-intro → naming → bienvenue → star-move → mission2 → se
- **Actions** : - **Actions** :
- Stocke `activityCity: false` dans le store Zustand - Stocke `activityCity: false` dans le store Zustand
- Joue l'audio `alertCentral` - Joue l'audio `alertCentral`
- **État** : Les systèmes lisent `activityCity` depuis `useGameStore` pour adapter leur comportement - **État** : Les objets avec hook `useActivityCity()` détectent le changement et jouent leurs animations
- **Attente** : Le joueur atteint la zone de trigger pour `searching_problem` - **Attente** : Le joueur atteint la zone de trigger pour `searching_problem`
### 7. `searching_problem` ### 7. `searching_problem`
@@ -81,13 +81,15 @@ intro → start-intro → naming → bienvenue → star-move → mission2 → se
| Fichier | Rôle | | Fichier | Rôle |
| --------------------------------------- | --------------------------------------------------------- | | --------------------------------------- | --------------------------------------------------------- |
| `src/managers/stores/useGameStore.ts` | Store Zustand pour l'état global du jeu | | `src/stores/gameStore.ts` | Store Zustand pour l'état global du jeu |
| `src/stateManager/GameStepManager.ts` | Synchronise avec le store Zustand |
| `src/components/game/GameFlow.tsx` | Gère les transitions automatiques et la lecture audio | | `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/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/zone/ZoneDetection.tsx` | Détecte quand le joueur entre dans une zone |
| `src/world/GameStageContent.tsx` | Monte les contenus de mission dans la scène | | `src/components/3d/CentralObject.tsx` | Objet interactif "central" pour la mission 2 |
| `src/data/audioConfig.ts` | Chemins des fichiers audio | | `src/data/audioConfig.ts` | Chemins des fichiers audio |
| `src/data/zones.ts` | Configuration des zones de transition | | `src/data/zones.ts` | Configuration des zones de transition |
| `src/hooks/useActivityCity.ts` | Hook pour détecter le changement d'activité de la ville |
--- ---
@@ -132,14 +134,35 @@ export const ZONES: Zone[] = [
## Store Zustand ## Store Zustand
```typescript ```typescript
// src/managers/stores/useGameStore.ts // src/stores/gameStore.ts
interface GameState { interface GameState {
mainState: MainGameState; step: GameStep;
missionFlow: {
activityCity: boolean; activityCity: boolean;
canMove: boolean;
playerName: string; 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
} }
``` ```
+226
View File
@@ -0,0 +1,226 @@
# 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 },
}
```
-266
View File
@@ -1,266 +0,0 @@
# 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.
+8 -1
View File
@@ -160,6 +160,7 @@ State:
- `dialogueVolume` - `dialogueVolume`
- `subtitlesEnabled` - `subtitlesEnabled`
- `subtitleLanguage` - `subtitleLanguage`
- `repairRuntime`
Audio setters clamp values between `0` and `1`, then call: Audio setters clamp values between `0` and `1`, then call:
@@ -169,6 +170,8 @@ AudioManager.getInstance().setCategoryVolume(category, nextVolume);
This keeps UI state and browser audio state synchronized. 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 ## Subtitle Store
`useSubtitleStore` is intentionally tiny. `useSubtitleStore` is intentionally tiny.
@@ -219,11 +222,13 @@ Current overlays:
- `GameStateDebugPanel`: compact debug UI for viewing and switching main/sub states - `GameStateDebugPanel`: compact debug UI for viewing and switching main/sub states
- `Crosshair`: player aiming helper - `Crosshair`: player aiming helper
- `InteractPrompt`: interaction prompt - `InteractPrompt`: interaction prompt
- `RepairMovementLockIndicator`: indicator shown while repair steps lock movement - `RepairMovementLockIndicator`: indicator intended for repair movement lock
- `HandTrackingVisualizer`: hand tracking SVG fallback/debug visualization - `HandTrackingVisualizer`: hand tracking SVG fallback/debug visualization
- `Subtitles`: active dialogue subtitle overlay - `Subtitles`: active dialogue subtitle overlay
- `GameSettingsMenu`: options menu and settings controls - `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 ## Regression Rules
- Do not store per-frame values in Zustand. - Do not store per-frame values in Zustand.
@@ -236,4 +241,6 @@ Current overlays:
## Next Steps ## 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. - Move broader mission orchestration into a clearer layer if intro, mission, dialogue, and cinematic branching grows.
+11 -41
View File
@@ -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: Use the editor when you need to:
- move, rotate, scale, add, or delete objects from `public/map.json` - move, rotate, or scale objects from `public/map.json`
- inspect the raw JSON generated by the editor - inspect the raw JSON generated by the editor
- preview and edit cinematics from `public/cinematics.json` - preview and edit cinematics from `public/cinematics.json`
- create, preview, and validate dialogue entries from `public/sounds/dialogue/dialogues.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: The map editor reads the same map data as the runtime scene:
- `public/map.json` contains the current hierarchical runtime map. - `public/map.json` contains the object list.
- `public/models/{name}/model.glb` contains the matching 3D model for each object name. `model.gltf` is still supported as a fallback during migration. - `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. - Missing models are displayed as gray fallback cubes, so incomplete maps remain editable.
## Map Node Format ## Map Node Format
`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: Each entry in `public/map.json` represents one object:
| Field | Description | | Field | Description |
| ---------- | ------------------------------------------------- | | ---------- | ------------------------------------------------- |
@@ -45,33 +45,17 @@ Only the `Editor` group is open by default. Open the other groups when you need
1. Open `/editor` in the local app. 1. Open `/editor` in the local app.
2. Click an object in the scene to select it. 2. Click an object in the scene to select it.
3. Use `Shift + right click` on other objects to add or remove them from the current multi-selection. 3. Choose a transform mode: translate, rotate, or scale.
4. Choose a transform mode: translate, rotate, or scale. 4. Drag the transform gizmo in the 3D view.
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`. 5. Check the JSON inspector if you need exact values.
6. Keep `Snap terrain on move` enabled when placing objects on the terrain. 6. Use undo or redo if the transform is not correct.
7. Use `Center on object` or `Reset camera` from the `View` section when navigating large maps. 7. Export the JSON or save it to the dev server.
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 ## Controls
| Action | Input | | Action | Input |
| -------------------- | -------------------------- | | -------------------- | -------------------------- |
| Select object | Click object | | Select object | Click object |
| Toggle multi-select | `Shift` + right click |
| Deselect | `Esc` or click empty space | | Deselect | `Esc` or click empty space |
| Lock selection | `Lock` button in Selection | | Lock selection | `Lock` button in Selection |
| Clear selection | `X` button in Selection | | Clear selection | `X` button in Selection |
@@ -89,21 +73,9 @@ Use the trash button in `Selection` to delete the selected node from the map tre
The `Selection` section shows the selected object name and its index in `public/map.json`. The `Selection` section shows the selected object name and its index in `public/map.json`.
- Click an object to select it. - 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. - Click empty space or press `Esc` to clear the selection.
- Use the `X` button to clear the selection explicitly. - Use the `X` button to clear the selection explicitly.
- Use the `Lock` button to protect the current selection while editing. - 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: When selection is locked:
@@ -116,11 +88,9 @@ 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 `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 ## JSON Inspector
The `JSON` section shows the editable node data: The `JSON` section shows the raw map data that will be exported or saved:
- When no object is selected, it shows the full map node list. - 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. - When an object is selected, it highlights the JSON lines for that object.
@@ -131,11 +101,11 @@ Use it to verify exact numeric transform values before saving or exporting. The
### Export JSON ### Export JSON
`Export JSON` downloads the current hierarchical map tree as `map.json`. Use this when you want to manually replace `public/map.json`. `Export JSON` downloads the current map node list as `map.json`. Use this when you want to manually replace `public/map.json`.
### Save To Server ### Save To Server
`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. `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.
The button is hidden in production builds because production persistence is not implemented. The button is hidden in production builds because production persistence is not implemented.
+2 -2
View File
@@ -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 - Fragmentation through repair-case trigger or two-fists hand gesture
- Exploded model visualization through `ExplodableModel` - Exploded model visualization through `ExplodableModel`
- Scan visual that steps through exploded parts - Scan visual that steps through exploded parts
- Broken-part detection by configured `nodeName`, with diagnostics when configured parts are missing - Broken-part detection by configured `nodeName`, with fallback to first scanned parts
- Persistent broken-part highlight and broken-part prompt after discovery - Persistent broken-part highlight and broken-part prompt after discovery
- Grabbable replacement part choices, including distractor parts - Grabbable replacement part choices, including decoys
- Grabbable broken parts that must be deposited back into the case - Grabbable broken parts that must be deposited back into the case
- Snap-to-placeholder placement - Snap-to-placeholder placement
- Correct-part, wrong-part, and stored-part visual feedback - Correct-part, wrong-part, and stored-part visual feedback
+2 -2
View File
@@ -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. 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 shared repair movement-lock hook and HTML indicator keep movement disabled during active repair steps. 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.
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 `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 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. 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.
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. `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.
+15
View File
@@ -1,6 +1,21 @@
{ {
"version": 1, "version": 1,
"cinematics": [ "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", "id": "intro_overview",
"timecode": 0, "timecode": 0,
+34073 -39591
View File
File diff suppressed because it is too large Load Diff
-35051
View File
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.

Some files were not shown because too many files have changed in this diff Show More