Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 988c8305bc | |||
| 5ee52ec752 | |||
| 3ece1d76de | |||
| 3222d2ed3d | |||
| 41c38a35b2 | |||
| 6399a2f89f | |||
| f5d3d080c8 | |||
| cac66ebaee | |||
| c155d847e9 | |||
| f567540f22 | |||
| 688302d985 | |||
| f9d7c3f00e | |||
| 28c6ef199f | |||
| ff79448ce8 |
@@ -1,58 +1,59 @@
|
||||
name: 🔁 Branch Promotions
|
||||
name: 🔁 Weekly Branch Promotions
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 6 * * 1,4" # Lundi et Jeudi à 6h UTC (design → develop)
|
||||
- cron: "0 6 * * 1" # Lundi à 6h UTC (develop → main)
|
||||
- cron: "0 6 * * 1"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
promotion:
|
||||
description: "Which promotion to run"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- design-to-develop
|
||||
- develop-to-main
|
||||
- both
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: branch-promotions
|
||||
group: weekly-branch-promotions
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
design-to-develop:
|
||||
name: Open design → develop
|
||||
open-promotion-pr:
|
||||
name: Open ${{ matrix.head }} → ${{ matrix.base }}
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name == 'schedule') ||
|
||||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.promotion == 'design-to-develop' || github.event.inputs.promotion == 'both'))
|
||||
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"
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
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 develop design
|
||||
git fetch origin "$BASE_BRANCH" "$HEAD_BRANCH"
|
||||
|
||||
if git merge-base --is-ancestor origin/design origin/develop; then
|
||||
echo "No promotion needed: develop already contains design."
|
||||
if git merge-base --is-ancestor "origin/$HEAD_BRANCH" "origin/$BASE_BRANCH"; then
|
||||
echo "No promotion needed: $BASE_BRANCH already contains $HEAD_BRANCH."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
existing_pr="$(gh pr list \
|
||||
--state open \
|
||||
--base develop \
|
||||
--head "$GITHUB_REPOSITORY_OWNER:design" \
|
||||
--base "$BASE_BRANCH" \
|
||||
--head "$GITHUB_REPOSITORY_OWNER:$HEAD_BRANCH" \
|
||||
--json number \
|
||||
--jq '.[0].number // empty')"
|
||||
|
||||
@@ -62,50 +63,7 @@ jobs:
|
||||
fi
|
||||
|
||||
gh pr create \
|
||||
--base develop \
|
||||
--head design \
|
||||
--title "chore: merge design into develop" \
|
||||
--body "Automated promotion PR from \`design\` to \`develop\`."
|
||||
|
||||
develop-to-main:
|
||||
name: Open develop → main
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name == 'schedule' && github.event.schedule == '0 6 * * 1') ||
|
||||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.promotion == 'develop-to-main' || github.event.inputs.promotion == 'both'))
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 🔁 Open promotion PR
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git fetch origin main develop
|
||||
|
||||
if git merge-base --is-ancestor origin/develop origin/main; then
|
||||
echo "No promotion needed: main already contains develop."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
existing_pr="$(gh pr list \
|
||||
--state open \
|
||||
--base main \
|
||||
--head "$GITHUB_REPOSITORY_OWNER:develop" \
|
||||
--json number \
|
||||
--jq '.[0].number // empty')"
|
||||
|
||||
if [ -n "$existing_pr" ]; then
|
||||
echo "Promotion PR already open: #$existing_pr."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
gh pr create \
|
||||
--base main \
|
||||
--head develop \
|
||||
--title "chore: merge develop into main" \
|
||||
--body "Automated weekly promotion PR from \`develop\` to \`main\`."
|
||||
--base "$BASE_BRANCH" \
|
||||
--head "$HEAD_BRANCH" \
|
||||
--title "$PR_TITLE" \
|
||||
--body "Automated weekly promotion PR from \`$HEAD_BRANCH\` to \`$BASE_BRANCH\`."
|
||||
|
||||
@@ -143,7 +143,6 @@ 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 |
|
||||
|
||||
@@ -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 },
|
||||
}
|
||||
```
|
||||
@@ -1,51 +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.
|
||||
|
||||
## Main Bottlenecks Found
|
||||
|
||||
The most important signal is draw calls, not only triangle count.
|
||||
|
||||
| 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` is especially expensive because three visible instances can multiply thousands of primitives into thousands of draw calls. Instancing reduces repeated instance cost, but the source asset still needs a cleaner export.
|
||||
|
||||
## 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 `THREE.InstancedMesh`.
|
||||
|
||||
This keeps the existing map authoring format while reducing repeated draw calls for selected assets.
|
||||
|
||||
## Asset-Side Follow-Up
|
||||
|
||||
Design/export should prioritize:
|
||||
|
||||
1. Merge `generateur` meshes from 3152 primitives to a small number of material groups.
|
||||
2. Reduce `lafabrik` texture count and downscale flat/low-detail maps.
|
||||
3. Merge `ecole` primitives because it uses a single material.
|
||||
4. 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.
|
||||
+1
-1
@@ -7,7 +7,7 @@ import tseslint from "typescript-eslint";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(["dist", "POC-grass"]),
|
||||
globalIgnores(["dist"]),
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,21 @@
|
||||
{
|
||||
"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,
|
||||
|
||||
+35030
-40800
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.
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