291 Commits

Author SHA1 Message Date
Tom Boullay 5c8c35cc72 fix: lint and format 2026-05-11 17:32:47 +02:00
Tom Boullay 711c336f1e Merge branch 'develop' into feat/repair-game 2026-05-11 17:31:14 +02:00
math-pixel 12c96b43c1 Merge pull request 'Feat/env-manager' (#1) from feat/env-manager into develop
Reviewed-on: #1
2026-05-11 15:23:31 +00:00
Tom Boullay 2bb2fff310 Update .gitignore 2026-05-11 17:23:00 +02:00
Tom Boullay 052ab34740 fix: merge 2026-05-11 17:01:02 +02:00
Tom Boullay ff21b80d57 fix: a pb with octree 2026-05-11 16:43:02 +02:00
Tom Boullay 8088e67625 fix: a pb with octree 2026-05-11 16:41:11 +02:00
Tom Boullay 6f5137229d update: en dialogue sub 2026-05-11 13:37:12 +02:00
Tom Boullay 6d3a8fde52 update: cinematic references 2026-05-11 13:22:15 +02:00
Tom Boullay 26a9c1c4d4 Update docsTranslations.ts 2026-05-11 13:21:01 +02:00
Tom Boullay f91756bc27 update: document repair movement lock indicator 2026-05-11 13:17:20 +02:00
Tom Boullay e9875206ff update: show repair movement lock indicator 2026-05-11 13:15:16 +02:00
Tom Boullay 841f96f3fd update: doc 2026-05-11 13:14:08 +02:00
Tom Boullay 00b1780b10 update: document repair movement lock 2026-05-11 13:12:37 +02:00
Tom Boullay c81bacc208 update: doc dialogue and cinematic tools 2026-05-11 13:10:26 +02:00
Tom Boullay 49d1a7324c update: lock player movement during repair 2026-05-11 13:09:50 +02:00
Tom Boullay 131fe39311 docs: repair interaction flow 2026-05-11 13:05:46 +02:00
Tom Boullay 3c72298a45 update: assit dialogue and srt creation 2026-05-11 13:05:03 +02:00
Tom Boullay 49e96c7f5a update: improve repair debug mission switching 2026-05-11 13:03:46 +02:00
Tom Boullay e5aeab6534 update: edit cinematic dialogue 2026-05-11 13:01:56 +02:00
Tom Boullay cec4d6ad0d update: reset repair runtime state 2026-05-11 13:01:32 +02:00
Tom Boullay 202ac4628d fix: sequence repair case completion exit 2026-05-11 12:58:37 +02:00
Tom Boullay 6a4d0f7eb1 update: sync dialogue and cinematic 2026-05-11 12:58:12 +02:00
Tom Boullay 40368c22fc add: blocked repair install feedback 2026-05-11 12:56:54 +02:00
Tom Boullay 788e9f0fb3 update: feedback repair model and improve repair case interaction feedback 2026-05-11 12:54:54 +02:00
Tom Boullay 7a439d0879 add: cinematic preview 2026-05-11 12:53:18 +02:00
Tom Boullay 9f5c105c1b update: add dialogue preview 2026-05-11 12:48:59 +02:00
Tom Boullay 72f8acb61c Update RepairCaseModel.tsx 2026-05-11 12:48:38 +02:00
Tom Boullay 3791ab71cd update: add cinecmatic editor 2026-05-11 12:11:58 +02:00
Tom Boullay 1ad4a164ea fix: clarify repair mission locked flow 2026-05-11 12:10:35 +02:00
Tom Boullay cc8ea20536 update: stabilize repair mission stage mounting 2026-05-11 11:51:06 +02:00
Tom Boullay bd14042ca0 update: add dialogue manifest 2026-05-11 11:48:05 +02:00
Tom Boullay 7036b7b0f9 fix: preload repair mission assets 2026-05-11 11:47:20 +02:00
Tom Boullay f4635def91 update: align srt duration 2026-05-11 11:38:19 +02:00
Tom Boullay e16d6e15fa fix repair game suspense boundaries 2026-05-11 11:37:54 +02:00
Tom Boullay f9a6390260 fix: add tracking + add new models 2026-05-11 11:25:17 +02:00
Tom Boullay b521cdd361 update: french srt 2026-05-11 11:22:06 +02:00
Tom Boullay 4218fde63a update: add runtine camera keyframe 2026-05-11 11:13:49 +02:00
Tom Boullay c2ba26ca86 add: loading 2026-05-11 11:11:46 +02:00
Tom Boullay f74a0c5eae update: audio already use 2026-05-11 10:29:46 +02:00
Tom Boullay a93d2dc92c update: audimanager 2026-05-11 10:22:12 +02:00
Tom Boullay 4b7498ae13 update: trigger dialogue en fonction du gameplay 2026-05-11 10:03:07 +02:00
Tom Boullay a34396b958 docs: queue dialogue 2026-05-11 09:43:40 +02:00
Tom Boullay 8cbf696b6e docs: add some docs 2026-05-11 09:18:46 +02:00
Tom Boullay 311c243506 add: dev dialogue manisfest validation panel 2026-05-11 09:09:34 +02:00
math-pixel e8fb859f79 update 2026-05-11 08:56:54 +02:00
math-pixel 17836ec889 update 2026-05-11 08:32:16 +02:00
Tom Boullay 6101bca84f add: dev manifest api validation 2026-05-10 00:54:33 +01:00
Tom Boullay 5889a325ec update: add stereo 2026-05-10 00:50:34 +01:00
Tom Boullay 08f89c9de5 fix: add config vite error srt 2026-05-10 00:49:19 +01:00
Tom Boullay 5ec10f4a35 update: gros commit fix editor srt panel 3 2026-05-10 00:40:26 +01:00
Tom Boullay 71b46b5fea update: gros commit fix editor srt panel 2 2026-05-10 00:37:28 +01:00
Tom Boullay 4a8a1368b2 update: gros commit fix editor srt panl 2026-05-10 00:35:23 +01:00
Tom Boullay c9e5ce3279 update: confort + ui 2026-05-10 00:33:18 +01:00
Tom Boullay f55be58c0b add: audio preview 2026-05-10 00:31:16 +01:00
Tom Boullay 48f2c9ef80 update: generate complete srt template 2026-05-10 00:29:42 +01:00
Tom Boullay 807503fde5 update: fix bug de merde 2026-05-10 00:27:48 +01:00
Tom Boullay 75b77a52bf update: validation/errors srt 2026-05-10 00:25:45 +01:00
Tom Boullay 916b5d304f update: save srt files 2026-05-10 00:23:37 +01:00
Tom Boullay 3d81a9281a add: french subtitles 2026-05-10 00:20:16 +01:00
Tom Boullay 0c8b9070bb add: add str editing panel 2026-05-10 00:13:42 +01:00
Tom Boullay 0fbf6bfa0e add: trigger dialogue with timecode 2026-05-10 00:10:16 +01:00
Tom Boullay 53fdf3cb1e update: play audio + srt sync 2026-05-10 00:07:56 +01:00
Tom Boullay 8ef1da0e9a upatde: load dialogue en fonction du language 2026-05-10 00:04:59 +01:00
Tom Boullay 1c30b73253 add: load dialohue manifest 2026-05-10 00:02:48 +01:00
Tom Boullay f5bc7cb08e add: dialoguejson 2026-05-10 00:00:36 +01:00
Tom Boullay d9525b0aaf add: parser srt files 2026-05-09 23:53:19 +01:00
Tom Boullay 5b43f503ac add: basic subtitle 2026-05-09 23:51:22 +01:00
Tom Boullay 974f1e33fb add: settings menu + menu store 2026-05-09 23:45:05 +01:00
Tom Boullay ce67d07107 add: global cat volumes 2026-05-09 23:37:07 +01:00
Tom Boullay 53add29a48 add: type audio playback cat 2026-05-09 23:30:14 +01:00
Tom Boullay 33524f8409 add: reusable world video prompt billboard 2026-05-09 01:28:06 +01:00
Tom Boullay e0eae67ace fix repair game interaction coordinate spaces 2026-05-09 01:19:16 +01:00
Tom Boullay 254311bddf add: playground in testmap 2026-05-08 03:07:52 +01:00
Tom Boullay 6d9eac291e big clean up 2026-05-08 03:02:26 +01:00
Tom Boullay e4f6ec211c add: configure mission-specific repair variants 2026-05-08 02:41:57 +01:00
Tom Boullay ead3634aab add: animate repair reassembly 2026-05-08 02:40:31 +01:00
Tom Boullay 19a83982a9 add: require broken part deposit before repair 2026-05-08 02:36:14 +01:00
Tom Boullay bebb9ac5a3 add: snap repair parts to case placeholders 2026-05-08 02:33:06 +01:00
Tom Boullay d02ef54bdc add: focus repair case view 2026-05-08 02:22:15 +01:00
Tom Boullay 3f3f623832 fix: track Logger filename casing 2026-05-08 02:20:14 +01:00
Tom Boullay 0256dfa812 add: show broken part prompt during scan 2026-05-08 02:18:00 +01:00
Tom Boullay 5c688fdaf7 add: highlight broken repair parts during scan 2026-05-08 02:16:13 +01:00
Tom Boullay 7a3baa4c0b add: scan fragmented repair parts sequentially 2026-05-08 02:12:58 +01:00
Tom Boullay 95d9bd4f3e add: animation on repair case 2026-05-08 02:10:19 +01:00
Tom Boullay eee69825c6 clean: remove obsolete repair debug code + unused core utilities 2026-05-08 02:07:03 +01:00
Tom Boullay 15c3d1858f add: repair mission completion step 2026-05-08 01:48:40 +01:00
Tom Boullay 7ee842c535 update: validate correct repair replacement part 2026-05-08 01:47:07 +01:00
Tom Boullay d4f215a948 update: require replacement placement before repair completion 2026-05-08 01:45:00 +01:00
Tom Boullay 7bbcf4359e add: repair install completion step 2026-05-08 01:41:29 +01:00
Tom Boullay eed0077dd1 add: repair fragmentation and scan flow 2026-05-08 01:39:23 +01:00
Tom Boullay f5da2f4994 update: enable hand tracking for repair steps (not only when we are close to something) 2026-05-08 01:30:27 +01:00
Tom Boullay ed60114d06 add: repair game inspection sub state 2026-05-08 01:27:32 +01:00
Tom Boullay 861a369776 add: physics in game scene 2026-05-08 01:17:35 +01:00
Tom Boullay 96796bca65 add: repair mission config 2026-05-08 01:14:30 +01:00
Tom Boullay 8fa4b087ba update: add generic repair mission store helpers 2026-05-08 01:09:42 +01:00
Tom Boullay 9371c50110 Merge pull request #7 from La-Fabrik-Durable/feat/main-feature
Feat/main feature
2026-05-08 01:56:18 +02:00
Tom Boullay fb230911a7 Update HandTrackingGlove.tsx 2026-05-08 00:54:42 +01:00
Tom Boullay 457eebc0e3 add hand tracking source debug switch 2026-05-06 23:23:10 +01:00
Tom Boullay 03dfef4aad add browser hand tracking source 2026-05-06 23:23:04 +01:00
Tom Boullay 4bcdbef974 fix hand tracking glove root transform 2026-05-06 23:22:56 +01:00
Tom Boullay 553dc6eb0a fix: flickering hands 2026-05-06 23:16:58 +01:00
Tom Boullay 7ae45d4cfa fix; distance grab objetc 2026-05-02 21:34:23 +02:00
Tom Boullay 27928b00a6 add hand tracking glove bone mapping 2026-05-02 11:38:02 +02:00
Tom Boullay ac7f60060c fix hand tracking glove fallback and loading 2026-05-02 11:35:28 +02:00
Tom Boullay fe662ebe7d fix hand tracking glove rendering 2026-05-02 11:32:00 +02:00
Tom Boullay bdc06f772f update: remove old model elec 2026-05-02 11:02:51 +02:00
Tom Boullay f7a589a11f fix three and rapier warning dependencies 2026-05-02 11:01:50 +02:00
Tom Boullay c71cd35f4d fix electricienne debug model loading 2026-05-02 10:58:00 +02:00
Tom Boullay 0cb5f57182 feat add left hand tracking glove model 2026-05-02 00:14:56 +02:00
Tom Boullay 1d64582383 feat add model loading diagnostics 2026-05-02 00:14:47 +02:00
Tom Boullay 4d7d2efdcc cleaning repo models 2026-05-01 23:54:48 +02:00
Tom Boullay d438c02ad9 update: models made them working 2026-05-01 23:45:58 +02:00
Tom Boullay eef39ab53d update: debug overlay layout controls 2026-05-01 23:39:04 +02:00
Tom Boullay 1a783f1867 Update TestMap.tsx 2026-04-30 16:29:56 +02:00
Tom Boullay bb08054722 connect repair gameplay to zustand progression 2026-04-30 16:25:54 +02:00
Tom Boullay 1625895708 fix: models 2026-04-30 15:48:45 +02:00
Tom Boullay a87ffac63f Merge branch 'develop' into feat/main-feature 2026-04-30 15:48:35 +02:00
Tom Boullay 44a77a0197 Update arbre.bin 2026-04-30 15:09:27 +02:00
Tom Boullay f301a1e318 Merge branch 'design' into feat/main-feature 2026-04-30 15:09:22 +02:00
Tom Boullay 39fbf2333d Merge pull request #11 from La-Fabrik-Durable/feat/zustand
Feat/zustand
2026-04-30 15:07:44 +02:00
Tom Boullay 5f018e6de7 resolve three component type exports 2026-04-30 15:06:26 +02:00
Tom Boullay a14f776e5d address zustand progression review feedback 2026-04-30 14:59:41 +02:00
Tom Boullay 8884edb281 clarify managers and zustand store responsibilities 2026-04-30 14:38:07 +02:00
Tom Boullay b9970c4e03 add zustand game state 2026-04-30 14:29:29 +02:00
Tom Boullay 6a29cfdb0b add : some sounds 2026-04-30 14:25:36 +02:00
Tom Boullay 7c7dbdb588 connect game progression state to world 2026-04-30 14:24:59 +02:00
Tom Boullay 2696289483 add zustand game progression store 2026-04-30 14:04:01 +02:00
Tom Boullay b79f5c9314 chore: align repo health checks and docs 2026-04-30 13:51:39 +02:00
Tom Boullay 5265cdc7e5 add: prettier eslint 2026-04-30 13:36:07 +02:00
Tom Boullay cfb1eaf39a refactor: clean architecture and remove unused code 2026-04-30 13:33:28 +02:00
Tom Boullay b1187b68ae refactor: split hooks types and utils by domain 2026-04-30 11:49:18 +02:00
Tom Boullay 9ac5844182 refactor: organize three components by domain 2026-04-30 11:35:53 +02:00
Tom Boullay 37eded8d7e fix: correct repair case open state rotation 2026-04-30 10:42:47 +02:00
Tom Boullay 92097e5256 feat: add game music loop and mallette sounds 2026-04-30 10:06:00 +02:00
Tom Boullay 01c583ba96 refactor: prepare main feature gameplay object and use GLB sky model 2026-04-30 10:02:00 +02:00
Tom Boullay f66609178b feat: expand main feature model catalog 2026-04-29 23:30:40 +02:00
Tom Boullay 2783b13488 feat: add main feature module selection 2026-04-29 23:30:31 +02:00
Tom Boullay 8b3f24b90b feat: add openable repair case model 2026-04-29 23:30:22 +02:00
Tom Boullay 5173002283 chore: track bin assets with lfs 2026-04-29 17:07:44 +02:00
Tom Boullay fd5294fb54 Merge remote-tracking branch 'origin/feat/main-feature' into feat/main-feature
# Conflicts:
#	src/world/GameMap.tsx
2026-04-29 16:57:58 +02:00
Tom Boullay 95e7e9c0a5 feat: support glb model assets 2026-04-29 16:18:24 +02:00
Tom Boullay 94b8dd0385 Merge branch 'develop' into feat/main-feature 2026-04-29 15:01:17 +02:00
Tom Boullay ad6f1552c6 update: docs 2026-04-29 13:01:10 +02:00
math-pixel d3ba5f6cfb Merge branch 'design' of https://github.com/La-Fabrik-Durable/La-Fabrik into design 2026-04-29 12:16:51 +02:00
math-pixel 05ef9e012a add object 2026-04-29 12:00:11 +02:00
math-pixel aa8b31033f feat: add tree 2026-04-29 11:57:43 +02:00
Tom Boullay 719ddef3ba fix: position perf panel beside debug gui 2026-04-29 11:56:46 +02:00
math-pixel 462b80e1c2 Merge pull request #10 from La-Fabrik-Durable/feat-animation
Feat/animation
2026-04-29 11:50:07 +02:00
Tom Boullay 90bd216efe feat: improve hand grab targeting 2026-04-29 11:40:17 +02:00
math-pixel ba9833d407 fix: pr 2026-04-29 11:35:17 +02:00
math-pixel b736de5f25 Merge branch 'feat-animation' of https://github.com/La-Fabrik-Durable/La-Fabrik into feat-animation 2026-04-29 11:23:57 +02:00
math-pixel d482ae4634 fix: pr issues 2026-04-29 11:23:40 +02:00
Tom Boullay 7958b2c62a fix: decouple hand tracking from crosshair focus 2026-04-29 11:13:11 +02:00
math-pixel d3a3d5eeca Update src/hooks/useCharacterAnimation.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-29 11:07:40 +02:00
math-pixel dca8f5a0d3 update: animation doc 2026-04-29 11:00:32 +02:00
Tom Boullay 8e08c3d5e0 update: upload-gltf add a new model -> createurdepluie
📦 Model
   model.gltf

🎨 Textures (color)
   color_bac_eau.png (compressed)
   color_cable_1.png (compressed)
   color_cable_2.png (compressed)
   color_refroidisseur.png (compressed)
   color_resistance.png (compressed)
   color_shell.png (compressed)
   color_tuyau.png (compressed)

🧭 Textures (normal)
   normal_bac_eau.png (compressed)
   normal_cable_1.png (compressed)
   normal_cable_2.png (compressed)
   normal_refroidisseur.png (compressed)
   normal_resistance.png (compressed)
   normal_shell.png (compressed)
   normal_tuyau.png (compressed)

🧱 Textures (orm)
   orm_bac_eau.png (compressed)
   orm_cable_1.png (compressed)
   orm_cable_2.png (compressed)
   orm_refroidisseur.png (compressed)
   orm_resistance.png (compressed)
   orm_shell.png (compressed)
   orm_tuyau.png (compressed)

🧩 Assets
   createurdepluie2.bin
2026-04-29 11:00:08 +02:00
Tom Boullay 882f3cc71b feat: improve fist grab depth tracking 2026-04-29 10:52:35 +02:00
math-pixel 6c85485622 fix : comflic 2026-04-29 10:51:40 +02:00
Tom Boullay a14ff9d913 feat: grab objects with closed fist raycast 2026-04-29 10:40:48 +02:00
Tom Boullay cc4c11f934 refactor: replace pinch gesture with fist gesture 2026-04-29 10:34:11 +02:00
Tom Boullay 0a0519cead update: upload-gltf add a new model -> gant_r
📦 Model
   model.gltf

🎨 Textures (color)
   color_gant.png (compressed)

🧭 Textures (normal)
   normal_gant.png (compressed)

🧱 Textures (orm)
   orm_gant.png (compressed)

🧩 Assets
   gant_r.bin
2026-04-29 10:03:04 +02:00
Tom Boullay 7d09c29828 update: upload-gltf add a new model -> gant_r_pad
📦 Model
   model.gltf

🎨 Textures (color)
   color_galet.png (compressed)
   color_gant.png (compressed)

🧭 Textures (normal)
   normal_galet.png (compressed)
   normal_gant.png (compressed)

🧱 Textures (orm)
   orm_galet.png (compressed)
   orm_gant.png (compressed)

🧩 Assets
   gant_r_pad.bin
2026-04-29 10:02:21 +02:00
Tom Boullay 28e3ac4c06 fix: guard hand landmark visualization 2026-04-29 09:52:46 +02:00
Tom Boullay cc78420d9c Create model.gltf 2026-04-29 09:05:04 +02:00
Tom Boullay 2747a95847 Merge branch 'design' into feat/main-feature 2026-04-29 09:05:00 +02:00
Tom Boullay d116f26a42 update: upload-gltf add a new model -> talkie
📦 Model
   model.gltf

🎨 Textures (color)
   color_boutonb.png (compressed)
   color_e_cran.png (compressed)
   color_boutona.png (compressed)
   color_cadre.png (compressed)
   color_hautparleur.png (compressed)
   color_touches.png (compressed)
   color_cable2.png (compressed)
   color_talkie.png (compressed)
   color_antenne.png (compressed)
   color_prise.png (compressed)
   color_cable1.png (compressed)

🪶 Textures (roughness)
   roughness_talkie.png (compressed)
   roughness_antenne.png (compressed)
   roughness_touches.png (compressed)
   roughness_prise.png (compressed)
   roughness_hautparleur.png (compressed)
   roughness_cable2.png (compressed)
   roughness_cable1.png (compressed)
   roughness_cadre.png (compressed)
   roughness_boutonb.png (compressed)
   roughness_e_cran.png (compressed)
   roughness_boutona.png (compressed)

🧭 Textures (normal)
   cadre_normal_opengl.png (compressed)
   talkie_normal_opengl.png (compressed)
   hautparleur_normal_opengl.png (compressed)
   hautparleur_normal.png (compressed)
   prise_normal_opengl.png (compressed)
   boutonb_normal_opengl.png (compressed)
   touches_normal.png (compressed)
   antenne_normal.png (compressed)
   boutona_normal_opengl.png (compressed)
   cadre_normal.png (compressed)
   touches_normal_opengl.png (compressed)
   e_cran_normal.png (compressed)
   cable2_normal.png (compressed)
   boutona_normal.png (compressed)
   talkie_normal.png (compressed)
   cable1_normal.png (compressed)
   boutonb_normal.png (compressed)
   prise_normal.png (compressed)
   cable2_normal_opengl.png (compressed)
   antenne_normal_opengl.png (compressed)
   cable1_normal_opengl.png (compressed)
   e_cran_normal_opengl.png (compressed)

🔩 Textures (metalness)
   metalness_cable1.png (compressed)
   metalness_boutonb.png (compressed)
   metalness_touches.png (compressed)
   metalness_e_cran.png (compressed)
   metalness_boutona.png (compressed)
   metalness_talkie.png (compressed)
   metalness_antenne.png (compressed)
   metalness_hautparleur.png (compressed)
   metalness_cadre.png (compressed)
   metalness_cable2.png (compressed)
   metalness_prise.png (compressed)

⛰ Textures (height)
   height_hautparleur.png (compressed)
   height_touches.png (compressed)
   height_antenne.png (compressed)
   height_cadre.png (compressed)
   height_e_cran.png (compressed)
   height_cable2.png (compressed)
   height_boutona.png (compressed)
   height_talkie.png (compressed)
   height_cable1.png (compressed)
   height_boutonb.png (compressed)
   height_prise.png (compressed)

🌑 Textures (ao)
   ao_cable1.png (compressed)
   ao_e_cran.png (compressed)
   ao_boutonb.png (compressed)
   ao_touches.png (compressed)
   ao_antenne.png (compressed)
   ao_talkie.png (compressed)
   ao_boutona.png (compressed)
   ao_cable2.png (compressed)
   ao_prise.png (compressed)
   ao_hautparleur.png (compressed)
   ao_cadre.png (compressed)

🧩 Assets
   model.bin
2026-04-29 08:44:37 +02:00
Tom Boullay 9f746aa7b0 update: upload-gltf add a new model -> refroidisseur
📦 Model
   model.gltf

🎨 Textures (color)
   color_refroidisseur.png (compressed)

🪶 Textures (roughness)
   roughness_refroidisseur.png (compressed)

🧭 Textures (normal)
   refroidisseur_normal.png (compressed)
   refroidisseur_normal_opengl.png (compressed)

🔩 Textures (metalness)
   metalness_refroidisseur.png (compressed)

⛰ Textures (height)
   height_refroidisseur.png (compressed)

🌑 Textures (ao)
   ao_refroidisseur.png (compressed)

🧩 Assets
   model.bin
2026-04-29 08:42:35 +02:00
Tom Boullay 847d6834fd update: upload-gltf add a new model -> immeuble1
📦 Model
   model.gltf

🎨 Textures (color)
   color_buisson.png (compressed)
   color_fenetre.png (compressed)
   color_feuilles.png (compressed)
   color_maison.png (compressed)
   color_panneau.png (compressed)
   color_porte.png (compressed)
   color_tronc.png (compressed)

🧭 Textures (normal)
   normal_buisson.png (compressed)
   normal_fenetre.png (compressed)
   normal_feuilles.png (compressed)
   normal_maison.png (compressed)
   normal_panneau.png (compressed)
   normal_porte.png (compressed)
   normal_tronc.png (compressed)

🧱 Textures (orm)
   orm_buisson.png (compressed)
   orm_fenetre.png (compressed)
   orm_feuilles.png (compressed)
   orm_maison.png (compressed)
   orm_panneau.png (compressed)
   orm_porte.png (compressed)
   orm_tronc.png (compressed)

🧩 Assets
   immeuble1-2.bin
2026-04-28 20:18:56 +02:00
math-pixel bd641328b0 feat: animator 2026-04-28 20:14:37 +02:00
Tom Boullay dbbb67e55a update: upload-gltf add a new model -> maison1
📦 Model
   model.gltf

🎨 Textures (color)
   color_buisson.png (compressed)
   color_contours.png (compressed)
   color_fenetre.png (compressed)
   color_maison.png (compressed)
   color_panneau.png (compressed)
   color_porte.png (compressed)
   color_toit.png (compressed)

🧭 Textures (normal)
   normal_buisson.png (compressed)
   normal_contours.png (compressed)
   normal_fenetre.png (compressed)
   normal_maison.png (compressed)
   normal_panneau.png (compressed)
   normal_porte.png (compressed)
   normal_toit.png (compressed)

🧱 Textures (orm)
   orm_buisson.png (compressed)
   orm_contours.png (compressed)
   orm_fenetre.png (compressed)
   orm_maison.png (compressed)
   orm_panneau.png (compressed)
   orm_porte.png (compressed)
   orm_toit.png (compressed)

🧩 Assets
   maison.bin
2026-04-28 20:07:17 +02:00
Tom Boullay 10dac2e56b update: upload-gltf add a new model -> persoprincipal
📦 Model
   model.gltf

🎨 Textures (color)
   color_defaultmaterial.png (compressed)

🧭 Textures (normal)
   normal_defaultmaterial.png (compressed)

🧱 Textures (orm)
   orm_defaultmaterial.png (compressed)

🧩 Assets
   mc.bin
2026-04-28 20:02:25 +02:00
Tom Boullay aaad1b5884 update: upload-gltf add a new model -> fermier
📦 Model
   model.gltf

🎨 Textures (color)
   color_defaultmaterial.png (compressed)

🧭 Textures (normal)
   normal_defaultmaterial.png (compressed)

🧱 Textures (orm)
   orm_defaultmaterial.png (compressed)

🧩 Assets
   fermier.bin
2026-04-28 19:58:21 +02:00
Tom Boullay bf98f028fe update: upload-gltf add a new model -> gerant
📦 Model
   model.gltf

🎨 Textures (color)
   defaultmaterial_basecolor.png (compressed)
   defaultmaterial_base_color.png (compressed)

🪶 Textures (roughness)
   roughness_defaultmaterial.png (compressed)

🧭 Textures (normal)
   defaultmaterial_normal.png (compressed)
   defaultmaterial_normal_opengl.png (compressed)

🔩 Textures (metalness)
   metalness_defaultmaterial.png (compressed)

⛰ Textures (height)
   height_defaultmaterial.png (compressed)

🧱 Textures (orm)
   orm_defaultmaterial.png (compressed)

🧩 Assets
   gerant.bin
2026-04-28 19:56:34 +02:00
Tom Boullay bff24030ee update: upload-gltf add a new model -> sapin
📦 Model
   model.gltf

🎨 Textures (color)
   color_mat.1.png (compressed)
   color_mat.png (compressed)

🧭 Textures (normal)
   normal_mat.1.png (compressed)
   normal_mat.png (compressed)

🧱 Textures (orm)
   orm_mat.1.png (compressed)
   orm_mat.png (compressed)

🧩 Assets
   sapin.bin
2026-04-28 19:54:38 +02:00
Tom Boullay 6531d9b183 update: upload-gltf add a new model -> eolienne
📦 Model
   model.gltf

🎨 Textures (color)
   color_feuilles1st.png (compressed)
   color_he_lisse.png (compressed)
   color_pied.png (compressed)
   color_tiges1st.png (compressed)
   color_moteur.png (compressed)
   color_feuilles2nd.png (compressed)
   color_tiges2nd.png (compressed)
   color_cul.png (compressed)

🪶 Textures (roughness)
   roughness_tiges2nd.png (compressed)
   roughness_moteur.png (compressed)
   roughness_feuilles2nd.png (compressed)
   roughness_tiges1st.png (compressed)
   roughness_he_lisse.png (compressed)
   roughness_cul.png (compressed)
   roughness_pied.png (compressed)
   roughness_feuilles1st.png (compressed)

🧭 Textures (normal)
   pied_normal.png (compressed)
   feuilles2nd_normal.png (compressed)
   tiges1st_normal.png (compressed)
   tiges1st_normal_opengl.png (compressed)
   cul_normal.png (compressed)
   he_lisse_normal.png (compressed)
   tiges2nd_normal_opengl.png (compressed)
   pied_normal_opengl.png (compressed)
   cul_normal_opengl.png (compressed)
   feuilles2nd_normal_opengl.png (compressed)
   feuilles1st_normal_opengl.png (compressed)
   tiges2nd_normal.png (compressed)
   moteur_normal_opengl.png (compressed)
   feuilles1st_normal.png (compressed)
   moteur_normal.png (compressed)
   he_lisse_normal_opengl.png (compressed)

🔩 Textures (metalness)
   metalness_feuilles2nd.png (compressed)
   metalness_feuilles1st.png (compressed)
   metalness_cul.png (compressed)
   metalness_he_lisse.png (compressed)
   metalness_tiges1st.png (compressed)
   metalness_moteur.png (compressed)
   metalness_tiges2nd.png (compressed)
   metalness_pied.png (compressed)

⛰ Textures (height)
   height_pied.png (compressed)
   height_feuilles2nd.png (compressed)
   height_tiges1st.png (compressed)
   height_cul.png (compressed)
   height_he_lisse.png (compressed)
   height_tiges2nd.png (compressed)
   height_feuilles1st.png (compressed)
   height_moteur.png (compressed)

🪟 Textures (opacity)
   opacity_he_lisse.png (compressed)

🌑 Textures (ao)
   ao_feuilles2nd.png (compressed)
   ao_cul.png (compressed)
   ao_feuilles1st.png (compressed)
   ao_moteur.png (compressed)
   ao_tiges1st.png (compressed)
   ao_he_lisse.png (compressed)
   ao_pied.png (compressed)
   ao_tiges2nd.png (compressed)
2026-04-28 19:19:25 +02:00
Tom Boullay 38fa9c1ff2 update: upload-gltf add a new model -> packderelance
📦 Model
   model.gltf

🎨 Textures (color)
   color_tetemart.png (compressed)
   color_charnie_res.png (compressed)
   color_mousse.png (compressed)
   color_patinf.png (compressed)
   color_lock.png (compressed)
   color_cabledroit.png (compressed)
   color_cablegauche.png (compressed)
   color_puces.png (compressed)
   color_manchemart.png (compressed)
   color_mousse_bas.png (compressed)
   color_patsup.png (compressed)

🪶 Textures (roughness)
   roughness_mousse.png (compressed)
   roughness_charnie_res.png (compressed)
   roughness_cablegauche.png (compressed)
   roughness_patsup.png (compressed)
   roughness_mousse_bas.png (compressed)
   roughness_manchemart.png (compressed)
   roughness_cabledroit.png (compressed)
   roughness_patinf.png (compressed)
   roughness_lock.png (compressed)
   roughness_puces.png (compressed)
   roughness_tetemart.png (compressed)

🧭 Textures (normal)
   patsup_normal.png (compressed)
   mousse_normal_opengl.png (compressed)
   patinf_normal_opengl.png (compressed)
   cablegauche_normal.png (compressed)
   cablegauche_normal_opengl.png (compressed)
   lock_normal.png (compressed)
   patinf_normal.png (compressed)
   patsup_normal_opengl.png (compressed)
   mousse_bas_normal.png (compressed)
   mousse_normal.png (compressed)
   cabledroit_normal_opengl.png (compressed)
   cabledroit_normal.png (compressed)
   manchemart_normal.png (compressed)
   tetemart_normal_opengl.png (compressed)
   tetemart_normal.png (compressed)
   manchemart_normal_opengl.png (compressed)
   puces_normal.png (compressed)
   charnie_res_normal.png (compressed)
   lock_normal_opengl.png (compressed)
   mousse_bas_normal_opengl.png (compressed)
   puces_normal_opengl.png (compressed)
   charnie_res_normal_opengl.png (compressed)

🔩 Textures (metalness)
   metalness_mousse.png (compressed)
   metalness_puces.png (compressed)
   metalness_tetemart.png (compressed)
   metalness_charnie_res.png (compressed)
   metalness_mousse_bas.png (compressed)
   metalness_cabledroit.png (compressed)
   metalness_manchemart.png (compressed)
   metalness_lock.png (compressed)
   metalness_patinf.png (compressed)
   metalness_patsup.png (compressed)
   metalness_cablegauche.png (compressed)

⛰ Textures (height)
   height_patsup.png (compressed)
   height_cablegauche.png (compressed)
   height_lock.png (compressed)
   height_patinf.png (compressed)
   height_mousse_bas.png (compressed)
   height_mousse.png (compressed)
   height_cabledroit.png (compressed)
   height_manchemart.png (compressed)
   height_tetemart.png (compressed)
   height_puces.png (compressed)
   height_charnie_res.png (compressed)

🌑 Textures (ao)
   ao_puces.png (compressed)
   ao_mousse.png (compressed)
   ao_charnie_res.png (compressed)
   ao_mousse_bas.png (compressed)
   ao_cabledroit.png (compressed)
   ao_tetemart.png (compressed)
   ao_manchemart.png (compressed)
   ao_lock.png (compressed)
   ao_patinf.png (compressed)
   ao_patsup.png (compressed)
   ao_cablegauche.png (compressed)
2026-04-28 19:14:52 +02:00
math-pixel e8f621d35f wip 2026-04-28 16:54:00 +02:00
Tom Boullay aa211d16b7 fix: persist debug modes and skip missing map models 2026-04-28 16:35:33 +02:00
Tom Boullay 44c27be640 Merge branch 'develop' into feat/main-feature 2026-04-28 16:27:05 +02:00
Tom Boullay 8a58f75864 update: upload-gltf add a new model -> gants
📦 Model
   model.gltf

🎨 Textures (color)
   color_galet.png (compressed)
   color_gant.png (compressed)

🧭 Textures (normal)
   normal_galet.png (compressed)
   normal_gant.png (compressed)

🧱 Textures (orm)
   orm_galet.png (compressed)
   orm_gant.png (compressed)

🧩 Assets
   gants.bin
2026-04-28 16:07:26 +02:00
Tom Boullay 0c7d5b61ac update: upload-gltf add a new model -> galet
📦 Model
   model.gltf

🎨 Textures (color)
   color_galet.png (compressed)

🧭 Textures (normal)
   normal_galet.png (compressed)

🧱 Textures (orm)
   orm_galet.png (compressed)

🧩 Assets
   galet.bin
2026-04-28 16:02:46 +02:00
Tom Boullay 5c5ba0937e Merge pull request #6 from La-Fabrik-Durable/feat/docs-routing
Feat/docs-routing
2026-04-28 15:04:29 +02:00
Tom Boullay dc7de253de address docs routing review feedback 2026-04-28 15:02:50 +02:00
Tom Boullay 97e0606439 clean docs router declarations 2026-04-28 14:53:28 +02:00
Tom Boullay a11db585de move debug components out of utils 2026-04-28 14:47:26 +02:00
Tom Boullay 6d858cfa7d standardize source naming conventions 2026-04-28 14:46:27 +02:00
Tom Boullay 19bad2c8be fix runtime map loading lifecycle 2026-04-28 14:42:49 +02:00
Tom Boullay e01d6f27ba rename pages 2026-04-28 14:25:29 +02:00
Tom Boullay 64b53a762d clean branch-scoped code quality issues 2026-04-28 14:23:37 +02:00
Tom Boullay 9818e719ce organize data configs by domain 2026-04-28 14:17:21 +02:00
Tom Boullay 2251a81ac1 refactor feature folders by code type 2026-04-28 14:14:15 +02:00
Tom Boullay eebeee9ed8 refactor docs into feature folder 2026-04-28 13:54:41 +02:00
Tom Boullay 9c7c59973e group docs navigation by audience 2026-04-28 13:48:03 +02:00
Tom Boullay dee232ac45 add editor documentation pages 2026-04-28 13:47:56 +02:00
Tom Boullay 2117146de5 fix react three peer dependencies 2026-04-28 13:47:49 +02:00
Tom Boullay 632d4b5c95 update: app and main 2026-04-28 13:32:54 +02:00
Tom Boullay 291e747f7b Merge branch 'develop' into feat/docs-routing 2026-04-28 13:31:40 +02:00
Tom Boullay 48b53746d1 Merge pull request #4 from La-Fabrik-Durable/feat-editor
Feat/editor
2026-04-28 13:22:44 +02:00
Tom Boullay ddde5e33f3 fix editor map reliability 2026-04-28 11:06:09 +02:00
Tom Boullay b5f2b7c990 Update Map.tsx 2026-04-28 10:53:57 +02:00
Tom Boullay bce1a05615 Create Map.tsx 2026-04-28 10:52:05 +02:00
Tom Boullay 81515e3e31 Update debugConfig.ts 2026-04-28 10:48:42 +02:00
math-pixel ed9582562a Merge pull request #9 from La-Fabrik-Durable/feat/deploy-test
Feat/deploy test
2026-04-28 10:45:25 +02:00
math-pixel 2c295fa7ee Merge pull request #8 from La-Fabrik-Durable/feat/deploy-test
Feat/deploy test
2026-04-28 10:44:12 +02:00
Tom Boullay 4a2f9cfb50 Merge branch 'feat-editor' of https://github.com/La-Fabrik-Durable/La-Fabrik into feat-editor 2026-04-28 10:43:08 +02:00
Tom Boullay d70de8c9a4 cleaaning 2026-04-28 10:42:57 +02:00
math-pixel f83df3dd21 Delete test-editor.html 2026-04-28 10:39:58 +02:00
Tom Boullay 5e528a90f9 fix: style 2026-04-28 10:30:31 +02:00
Tom Boullay f3888ed0bf tyle: refresh editor controls with monochrome UI 2026-04-28 10:08:17 +02:00
Tom Boullay b8f6fe59db refactor: move game map into world folder 2026-04-28 09:47:09 +02:00
Tom Boullay d7425ac17d docs: document editor architecture and user features 2026-04-28 09:43:51 +02:00
Tom Boullay f8b2c180cd refactor: move editor page and types to conventional folders 2026-04-28 09:29:18 +02:00
Tom Boullay c6b317448f add: stylesheet 2026-04-28 09:07:56 +02:00
math-pixel bed5010f41 update: deploy file 2026-04-27 20:46:52 +02:00
math-pixel 1b1f2088aa update: deploy file 2026-04-27 20:40:05 +02:00
math-pixel 18d0c2baa5 feat: change version 2026-04-27 20:27:21 +02:00
math-pixel 4aaff26e82 fix :editor 2026-04-27 17:25:56 +02:00
Tom Boullay 31c3e43698 docs: clarify backend virtual environment setup 2026-04-27 17:11:08 +02:00
math-pixel c5b04764ec fix: main model map 2026-04-27 16:38:05 +02:00
Tom Boullay 1e444620c1 fix: address docs routing review 2026-04-27 16:32:23 +02:00
Tom Boullay eeca87dd0e clean: package json 2026-04-27 16:27:57 +02:00
math-pixel fde724f3f0 update models loading in /editor 2026-04-27 16:27:56 +02:00
Tom Boullay d5a295e18f feat: add localized docs pages 2026-04-27 16:27:08 +02:00
math-pixel 14626cd6b1 fix: load all models/ 2026-04-27 16:07:57 +02:00
Tom Boullay 9c602cdc63 feat move debug cube with remote hand tracking 2026-04-27 16:07:54 +02:00
Tom Boullay fa8bc229c3 feat add remote hand tracking backend 2026-04-27 15:49:02 +02:00
Tom Boullay 7139ae559e feat: add docs routing 2026-04-27 15:35:56 +02:00
Tom Boullay 1c9d113050 update: upload-gltf add a new model -> pylone
📦 Model
   model.gltf

🎨 Textures (color)
   pied_base_color.png (compressed)
   panneaux_base_color.png (compressed)
   cable2_base_color.png (compressed)
   chap_base_color.png (compressed)
   puces_base_color.png (compressed)
   lampe_base_color.png (compressed)
   cable1_base_color.png (compressed)

🪶 Textures (roughness)
   lampe_roughness.png (compressed)
   panneaux_roughness.png (compressed)
   cable2_roughness.png (compressed)
   cable1_roughness.png (compressed)
   chap_roughness.png (compressed)
   puces_roughness.png (compressed)
   pied_roughness.png (compressed)

🧭 Textures (normal)
   panneaux_normal.png (compressed)
   pied_normal.png (compressed)
   chap_normal.png (compressed)
   lampe_normal.png (compressed)
   chap_normal_opengl.png (compressed)
   panneaux_normal_opengl.png (compressed)
   lampe_normal_opengl.png (compressed)
   cable2_normal.png (compressed)
   cable1_normal.png (compressed)
   pied_normal_opengl.png (compressed)
   cable2_normal_opengl.png (compressed)
   puces_normal.png (compressed)
   cable1_normal_opengl.png (compressed)
   puces_normal_opengl.png (compressed)

🔩 Textures (metalness)
   lampe_metallic.png (compressed)
   cable1_metallic.png (compressed)
   puces_metallic.png (compressed)
   panneaux_metallic.png (compressed)
   chap_metallic.png (compressed)
   cable2_metallic.png (compressed)
   pied_metallic.png (compressed)

🧩 Assets
   cable1_mixed_ao.png (compressed)
   puces_mixed_ao.png (compressed)
   panneaux_height.png (compressed)
   chap_height.png (compressed)
   lampe_opacity.png (compressed)
   lampe_mixed_ao.png (compressed)
   pied_height.png (compressed)
   lampe_height.png (compressed)
   cable2_height.png (compressed)
   cable1_height.png (compressed)
   panneaux_mixed_ao.png (compressed)
   cable2_mixed_ao.png (compressed)
   puces_height.png (compressed)
   pied_mixed_ao.png (compressed)
   chap_mixed_ao.png (compressed)
2026-04-27 14:35:33 +02:00
math-pixel 70720fbdcf fix: address code review comments
- vite.config.ts: fix __dirname for ESM, add 1MB payload limit + JSON validation
- MapViewer.tsx: remove broken window.isTransforming checks, fix callback order
- EditorPage.tsx: derive undoCount from historyManager in handleTransformEnd
- package.json: support Node 20 or 22 in engines
2026-04-27 14:21:50 +02:00
math-pixel cd7ba7503c fix: lint 2026-04-27 14:19:26 +02:00
math-pixel 2bd0fe5b96 fix: format 2026-04-27 13:57:17 +02:00
math-pixel 037174a409 Merge branch 'develop' into feat-editor 2026-04-27 13:55:13 +02:00
math-pixel 7de5678db1 update: map & update: package json for CI 2026-04-27 13:44:14 +02:00
Tom Boullay b523e98bea Update model.gltf
🔍 Lint / 🪄 Check lint (push) Has been cancelled
🔍 Lint / 🎨 Check format (push) Has been cancelled
🔍 Lint / 🔎 Typecheck (push) Has been cancelled
🔍 Lint / 🏗 Build (push) Has been cancelled
📊 Quality / 🔒 Security Audit (push) Has been cancelled
📊 Quality / 📋 Dependency Freshness (push) Has been cancelled
📊 Quality / 📦 Bundle Size (push) Has been cancelled
2026-04-27 13:43:59 +02:00
Tom Boullay 99d3eb2a2a cleaning: repo + model 2026-04-27 13:36:35 +02:00
Tom Boullay 4a97e265c1 Merge pull request #3 from La-Fabrik-Durable/design
upload: models
2026-04-27 13:31:21 +02:00
Tom Boullay 71d7ccbb4b Merge pull request #2 from La-Fabrik-Durable/feat/implem-map-scene-physique
Feat/implem map scene physique
2026-04-27 11:58:38 +02:00
Tom Boullay cd6831166b fix: docs update debug project tree 2026-04-27 11:57:21 +02:00
Tom Boullay 1b32fe9227 Create .prettierignore 2026-04-27 11:23:29 +02:00
Tom Boullay 7a01d86761 fix: format & lint 2026-04-27 11:20:59 +02:00
Tom Boullay 208b43295c clean 2026-04-27 11:14:43 +02:00
Tom Boullay 0e9d711be5 fix: archi 2026-04-27 10:53:50 +02:00
Tom Boullay 3c7fdbc6f5 update: upload-gltf add a new model -> lafabrik
📦 Model
   model.glb (compressed)
🎨 Textures
   tiges_normal.png
   comptoir_normal_opengl.png
   porte_metallic.png
   tiges_height.png
   comptoir_mixed_ao.png
   tiges_metallic.png
   tiges_normal_opengl.png
   comptoir_roughness.png
   fenetre_0_base_color.png
   bat_roughness.png
   tiges_base_color.png
   comptoir_normal.png
   porte_normal.png
   toit_roughness.png
   comptoir_height.png
   porte_height.png
   porte_mixed_ao.png
   comptoir_metallic.png
   tiges_mixed_ao.png
   porte_normal_opengl.png
   dashboard_metallic.png
   anneaux_metallic.png
   anneaux_base_color.png
   panneau_normal_opengl.png
   toit_mixed_ao.png
   anneaux_roughness.png
   porte_stock_base_color.png
   tuyaux_mixed_ao.png
   fenetre_0_mixed_ao.png
   dashboard_base_color.png
   anneaux_mixed_ao.png
   dashboard_mixed_ao.png
   bat_base_color.png
   tuyaux_height.png
   tuyaux_metallic.png
   verre_fenetre_base_color.png
   bat_height.png
   anneaux_normal.png
   toit_metallic.png
   porte_stock_height.png
   bat_normal.png
   porte_stock_roughness.png
   tuyaux_normal.png
   fenetre_0_metallic.png
   porte_stock_normal.png
   tuyaux_roughness.png
   anneaux_height.png
   comptoir_base_color.png
   panneau_height.png
   panneau_base_color.png
   porte_stock_mixed_ao.png
   panneau_normal.png
   bat_mixed_ao.png
   anneaux_normal_opengl.png
   stock_0_mixed_ao.png
   tuyaux_base_color.png
   fenetre_0_normal_opengl.png
   porte_roughness.png
   porte_base_color.png
   toit_normal_opengl.png
   plan_de_travail_normal.png
   plan_de_travail_roughness.png
   plan_de_travail_height.png
   porte_stock_metallic.png
   toit_base_color.png
   stock_0_metallic.png
   stock_0_normal.png
   verre_fenetre_normal.png
   plan_de_travail_normal_opengl.png
   bat_metallic.png
   stock_0_base_color.png
   stock_0_height.png
   tiges_roughness.png
   fenetre_0_roughness.png
   verre_fenetre_height.png
   panneau_roughness.png
   fenetre_0_normal.png
   plan_de_travail_metallic.png
   dashboard_normal_opengl.png
   tuyaux_normal_opengl.png
   plan_de_travail_base_color.png
   fenetre_0_height.png
   porte_stock_normal_opengl.png
   panneau_mixed_ao.png
   dashboard_height.png
   verre_fenetre_normal_opengl.png
   toit_normal.png
   dashboard_normal.png
   bat_normal_opengl.png
   verre_fenetre_metallic.png
   toit_height.png
   plan_de_travail_mixed_ao.png
   stock_0_roughness.png
   panneau_metallic.png
   dashboard_roughness.png
   stock_0_normal_opengl.png
   verre_fenetre_roughness.png
   verre_fenetre_mixed_ao.png
2026-04-24 16:44:54 +02:00
Tom Boullay 0c43c58dfe update: upload-gltf add a new model -> lafabrik
📦 Model
   model.glb (compressed)
🎨 Textures
   tiges_normal.png
   comptoir_normal_opengl.png
   porte_metallic.png
   tiges_height.png
   comptoir_mixed_ao.png
   tiges_metallic.png
   tiges_normal_opengl.png
   comptoir_roughness.png
   fenetre_0_base_color.png
   bat_roughness.png
   tiges_base_color.png
   comptoir_normal.png
   porte_normal.png
   toit_roughness.png
   comptoir_height.png
   porte_height.png
   porte_mixed_ao.png
   comptoir_metallic.png
   tiges_mixed_ao.png
   porte_normal_opengl.png
   dashboard_metallic.png
   anneaux_metallic.png
   anneaux_base_color.png
   panneau_normal_opengl.png
   toit_mixed_ao.png
   anneaux_roughness.png
   porte_stock_base_color.png
   tuyaux_mixed_ao.png
   fenetre_0_mixed_ao.png
   dashboard_base_color.png
   anneaux_mixed_ao.png
   dashboard_mixed_ao.png
   bat_base_color.png
   tuyaux_height.png
   tuyaux_metallic.png
   verre_fenetre_base_color.png
   bat_height.png
   anneaux_normal.png
   toit_metallic.png
   porte_stock_height.png
   bat_normal.png
   porte_stock_roughness.png
   tuyaux_normal.png
   fenetre_0_metallic.png
   porte_stock_normal.png
   tuyaux_roughness.png
   anneaux_height.png
   comptoir_base_color.png
   panneau_height.png
   panneau_base_color.png
   porte_stock_mixed_ao.png
   panneau_normal.png
   bat_mixed_ao.png
   anneaux_normal_opengl.png
   stock_0_mixed_ao.png
   tuyaux_base_color.png
   fenetre_0_normal_opengl.png
   porte_roughness.png
   porte_base_color.png
   toit_normal_opengl.png
   plan_de_travail_normal.png
   plan_de_travail_roughness.png
   plan_de_travail_height.png
   porte_stock_metallic.png
   toit_base_color.png
   stock_0_metallic.png
   stock_0_normal.png
   verre_fenetre_normal.png
   plan_de_travail_normal_opengl.png
   bat_metallic.png
   stock_0_base_color.png
   stock_0_height.png
   tiges_roughness.png
   fenetre_0_roughness.png
   verre_fenetre_height.png
   panneau_roughness.png
   fenetre_0_normal.png
   plan_de_travail_metallic.png
   dashboard_normal_opengl.png
   tuyaux_normal_opengl.png
   plan_de_travail_base_color.png
   fenetre_0_height.png
   porte_stock_normal_opengl.png
   panneau_mixed_ao.png
   dashboard_height.png
   verre_fenetre_normal_opengl.png
   toit_normal.png
   dashboard_normal.png
   bat_normal_opengl.png
   verre_fenetre_metallic.png
   toit_height.png
   plan_de_travail_mixed_ao.png
   stock_0_roughness.png
   panneau_metallic.png
   dashboard_roughness.png
   stock_0_normal_opengl.png
   verre_fenetre_roughness.png
   verre_fenetre_mixed_ao.png
2026-04-24 16:44:44 +02:00
Tom Boullay 089763713a add: a logger utils 2026-04-24 14:02:16 +02:00
math-pixel 1ca13ca13e feat : save map.json on project 2026-04-23 15:40:10 +02:00
math-pixel ca9abf3f3b feat editor 2026-04-23 15:24:40 +02:00
Tom Boullay a8138a5180 Create package-lock.json 2026-04-19 16:51:10 +02:00
Tom Boullay 0f96b5597b refacto : cleaning the codebasebase again 2026-04-19 16:50:11 +02:00
Tom Boullay 1c48441535 refacto: cleanning the codebase 2026-04-17 16:03:29 +02:00
Tom Boullay 7e72f1e803 update : put every constante in the data folder 2026-04-17 15:42:10 +02:00
Tom Boullay 4b14295749 update : add map model + octree algo 2026-04-17 11:36:03 +02:00
Tom Boullay 5111f2e558 update: add a physic scenne 2026-04-17 10:48:18 +02:00
Tom Boullay 1d4f223c35 refacto: enleve la map 2026-04-16 16:11:20 +02:00
Tom Boullay a3feb96bc3 update: upload-gltf add a new model -> workshop/lafabrik
📦 Model
   model.gltf
🎨 Textures
   roughness (manquant)
   normal (manquant)
   metalness (manquant)
   color (manquant)
   displace (manquant)
2026-04-16 15:43:42 +02:00
Tom Boullay 3d490b7fdd update: upload-gltf add a new model -> general/blocking
📦 Model
   model.gltf
🎨 Textures
   roughness (manquant)
   normal (manquant)
   metalness (manquant)
   color (manquant)
   displace (manquant)
2026-04-16 11:04:48 +02:00
Tom Boullay 269cf81595 fix: archi player 2026-04-16 11:00:08 +02:00
Tom Boullay 71c22386be refactor: tighten project structure and strengthen tooling 2026-04-16 10:45:05 +02:00
Tom Boullay fd8b462e1c fix: lint 2026-04-15 16:42:06 +02:00
Tom Boullay 02283946fa feat: add player camera 2026-04-15 16:40:52 +02:00
Tom Boullay 9e9ac8066c feat: add the map 2026-04-15 16:09:02 +02:00
Tom Boullay aded4ee209 feat: add map blocking and cleanup 2026-04-15 13:36:53 +02:00
Tom Boullay e86d4f2077 Merge branch 'main' into design 2026-04-15 13:32:38 +02:00
Tom Boullay c1ca8ca8e0 cleaning 2026-04-15 13:32:21 +02:00
Tom Boullay 753bdafd2e fix ci 2026-04-15 13:30:04 +02:00
Tom Boullay 18fdd8b2a6 update: add more CI 2026-04-15 11:17:48 +02:00
Tom Boullay 2c771d548b update : docs and skills 2026-04-15 11:06:41 +02:00
Tom Boullay 9eff211958 update: upload-gltf add a new model -> map/blocking
📦 Model
   model.gltf
🎨 Textures
   roughness (manquant)
   normal (manquant)
   metalness (manquant)
   color (manquant)
   displace (manquant)
2026-04-15 09:48:07 +02:00
Tom Boullay 1ce0267d12 update: remove eveything 2026-04-15 09:41:46 +02:00
Tom Boullay f0471a3afe update: upload-gltf update -> general/vase
📦 Model
  ↔️ model.gltf (inchange)
🎨 Textures
   normal.jpg
2026-04-15 09:25:02 +02:00
Tom Boullay 9815a38656 update: upload-gltf update -> general/vase
📦 Model
  ↔️ model.gltf (inchange)
🎨 Textures
  🔄 roughness.jpg
  🔄 metalness.jpg
  🔄 color.jpg
  🔄 displace.jpg
   normal.jpg (supprime)
2026-04-15 09:18:02 +02:00
Tom Boullay 9d63bd6444 Merge branch 'design' of https://github.com/La-Fabrik-Durable/La-Fabrik into design 2026-04-15 09:02:24 +02:00
Tom Boullay 89af15683c update: upload-gltf update -> general/vase
📦 Model
  🔄 model.gltf (compressed)
🎨 Textures
   color.jpg
2026-04-14 16:41:58 +02:00
Tom Boullay 980b16895e update: upload-gltf update -> general/vase
📦 Model
  🔄 model.gltf (compressed)
🎨 Textures
   color.jpg (supprime)
2026-04-14 16:40:55 +02:00
Tom Boullay 0b4d59223f update: upload-gltf add a new model -> general/vase
📦 Model
   model.gltf (compressed)
🎨 Textures
   roughness.jpg
   normal.jpg
   metalness.jpg
   color.jpg
   displace.jpg
2026-04-14 16:23:19 +02:00
Tom Boullay 7d5abfc2af update: upload-gltf update -> general/coffeetest
🎨 Textures
   metalness.jpg
2026-04-14 13:56:37 +02:00
Tom Boullay 4a833c4484 update: upload-gltf replace model -> general/coffeetest
📦 Model
  🔄 model.gltf (compressed)
🎨 Textures
  🔄 roughness.jpg
  🔄 normal.jpg
   metalness (manquant)
  🔄 color.jpg
  🔄 displace.jpg
2026-04-14 13:43:44 +02:00
Tom Boullay c36d3631f2 update: upload-gltf add a new model -> general/coffeetest
📦 Model
   model.gltf (compressed)
🎨 Textures
   roughness.jpg
   normal.jpg
   metalness.jpg
   color.jpg
   displace.jpg
2026-04-14 13:30:53 +02:00
Tom Boullay 5d7ce27285 update: from upload-gltf add a new model -> general/coffeetest
📦 Model
   model.gltf

🎨 Textures
   roughness.jpg
   normal.jpg
   metalness.jpg
   color.jpg
   displace.jpg
2026-04-14 12:24:24 +02:00
Tom Boullay 32d4e07ceb add some folder 2026-04-14 12:22:20 +02:00
Tom Boullay af3262376d update: add agent.md + skills 2026-04-14 09:20:30 +02:00
Tom Boullay 2fcb0ca56f Create ci.yml 2026-04-14 09:02:12 +02:00
Tom Boullay e32be4be53 upatde: add prettier 2026-04-14 08:59:36 +02:00
Tom Boullay 71916b2329 update: add basic structure 2026-04-14 08:39:09 +02:00
Tom Boullay 429ae66703 add : finish readme, git lfs and gitignore 2026-04-13 23:30:33 +02:00
Tom Boullay af2cfe128c Update README.md 2026-04-13 23:17:59 +02:00
Tom Boullay a37e46ac49 Update README.md 2026-04-13 22:59:56 +02:00
Tom Boullay 843d61fd54 add: license + v1 archi 2026-04-13 22:24:20 +02:00
Tom Boullay e622646349 add readme 2026-04-13 16:37:37 +02:00
Tom Boullay 9dc89b438b Initial commit 2026-04-13 16:12:21 +02:00
944 changed files with 27825 additions and 1899 deletions
+56
View File
@@ -0,0 +1,56 @@
# Agent - La Fabrik
You are working on **La Fabrik**, an interactive 3D web experience built with React Three Fiber.
## Read This First
- `docs/technical/architecture.md` describes the code that exists today.
- `docs/technical/target-architecture.md` describes the intended target-state.
- Do not assume target-state systems already exist.
## Current Implementation
- Stack: React 19, Three.js, `@react-three/fiber`, `@react-three/drei`, `@react-three/rapier`, TypeScript, Vite
- Zustand is used for shared game progression state.
- Current singleton-style services are limited to:
- `InteractionManager`
- `AudioManager`
- `Debug`
- Current gameplay scope is still prototype-level:
- player movement
- trigger/grab interactions
- debug camera and scene switching
- simple audio playback
## Current Architecture Rules
- Scene objects live in `src/world/` and `src/components/three/`.
- Shared 3D components are grouped by domain under `src/components/three/models/`, `src/components/three/interaction/`, `src/components/three/gameplay/`, and `src/components/three/world/`.
- HTML overlays live in `src/components/ui/`.
- Shared static config lives in `src/data/`.
- Debug tooling lives in `src/utils/debug/` and `src/hooks/debug/`.
- Use the `@/` alias for imports from `src/`.
- Prefer small, direct changes over adding new abstraction layers.
- Shared types should live close to their domain and only move outward when they gain multiple real consumers.
## Target-State Guidance
The project may later grow toward a manager-driven gameplay architecture with clearer separation between:
- production world code
- gameplay orchestration
- UI overlays
- debug tooling
That target-state is aspirational until the matching code exists. If a target-state rule conflicts with the current implementation, treat the current code as the source of truth and improve it incrementally.
## Do Not Assume
- There is no `GameManager` in the current codebase.
- There are no implemented mission, zone, cinematic, or dialogue systems yet.
- Dependency versions are not pinned today; do not rewrite dependency strategy unless explicitly asked.
- The old `# route path ...` file header convention is not in use.
## Skills
Files in `.agent/skills/` are supplemental patterns and examples. Some describe target-state or generic practices rather than the exact current implementation, so verify against the code before applying them.
-89
View File
@@ -1,89 +0,0 @@
# Agent — La Fabrik
You are working on **La Fabrik**, an interactive 3D web experience built with React Three Fiber. The player steps into the role of a technician in Altera (2050) and completes missions: repairing an e-bike, fixing a power grid, upgrading a vertical farm.
## Project Identity
- **Stack:** React 19, Three.js, @react-three/fiber 9, @react-three/drei, @react-three/rapier, GSAP, TypeScript, Vite
- **No external state lib.** State is managed by a custom `GameManager` singleton with a subscribe/getState pattern.
- **No Zustand, no Redux, no Context for global state.**
- **Versions are pinned** (no `^` in dependencies). Do not upgrade packages without explicit request.
## Architecture Rules
### Two patterns coexist
1. **Singleton manager classes** — for orchestration, audio, cinematics, zone detection, debug
2. **Declarative React components** — for all 3D scene objects (map, zones, lights, player, postprocessing)
Scene objects are **never** singleton classes. Managers are **never** React components.
### State ownership
- `GameManager` is the single source of truth for durable gameplay state (phase, zone, mission, input lock, dialogue)
- Other managers (`CinematicManager`, `AudioManager`, `ZoneManager`) handle side effects only — they read from GameManager but do not duplicate its state
- React components subscribe to GameManager through `useGameState()` hook
- **High-frequency values** (movement, camera interpolation, physics) stay in `useRef` + `useFrame` — never in React state
### File conventions
- Every file starts with a comment: `# route path <relative_path>` (e.g. `# route path src/world/Map.tsx`)
- Scene components live in `src/world/` and `src/components/3d/`
- UI overlays live in `src/components/ui/`
- Managers live in `src/stateManager/`
- Hooks live in `src/hooks/`
- Static data lives in `src/data/`
- Shaders live in `src/shaders/`
- Utilities live in `src/utils/`
### Import paths
Use `@/` alias for imports from `src/`:
```ts
import { GameManager } from "@/stateManager/GameManager";
import { useGameState } from "@/hooks/useGameState";
```
### Memory management
- Dispose only what you own (custom materials, render targets, manual clones)
- Never blindly deep-dispose shared/cached assets (drei loaders cache models)
- Use `Dispose.material()`, `Dispose.mesh()`, `Dispose.renderTarget()` from `src/utils/Dispose.ts`
### Debug
- Debug panel activates with `?debug` in URL
- All debug logic goes through `Debug.getInstance()` from `src/utils/Debug.ts`
- Never scatter `if (isDev)` blocks across files
- `r3f-perf` is lazy-loaded only in debug mode via `src/components/3d/DebugPerf.tsx`
## Managers (4 max)
| Manager | Responsibility |
| ------------------ | ------------------------------------------------------------------- |
| `GameManager` | Phase, zone, mission, input lock, dialogue — single source of truth |
| `CinematicManager` | GSAP timelines, camera lock/unlock |
| `AudioManager` | Music, SFX, spatial audio |
| `ZoneManager` | Zone detection, LOD triggers |
## Do NOT
- Create new manager classes without explicit request
- Use Zustand, Redux, or React Context for global state
- Put high-frequency values in React state (`useState`)
- Import `CinematicManager`/`AudioManager`/`ZoneManager` directly from components — always go through `GameManager`
- Upgrade pinned dependency versions
- Create files outside the documented architecture without explicit request
## Skills
See `.agent/skills/` for detailed patterns per technology:
- `best-practices.md` — Code generation conventions (W3C, simple, scalable, modern)
- `r3f.md` — React Three Fiber component patterns
- `three.md` — Three.js conventions and AnimationMixer
- `gsap.md` — GSAP timeline and cinematic patterns
- `managers.md` — Singleton manager implementation
- `memory.md` — GPU memory and disposal rules
- `debug.md` — Debug utility and r3f-perf setup
+11 -6
View File
@@ -8,10 +8,12 @@ Append `?debug` to the URL:
http://localhost:5173?debug
```
The free debug camera is toggled from the debug panel, not mounted permanently.
## Debug singleton
```ts
// src/utils/Debug.ts
// src/utils/debug/Debug.ts
import GUI from "lil-gui";
export class Debug {
@@ -56,18 +58,18 @@ if (debug.active) {
r3f-perf is loaded only in debug mode to avoid dependency issues in production:
```tsx
// src/components/3d/DebugPerf.tsx
import { Suspense, lazy } from "react";
import { useShowDebugPerf } from "@/hooks/debug/useShowDebugPerf";
const Perf = lazy(() => import("r3f-perf").then((m) => ({ default: m.Perf })));
export function DebugPerf() {
const debug = new URLSearchParams(window.location.search).has("debug");
if (!debug) return null;
const showDebugPerf = useShowDebugPerf();
if (!showDebugPerf) return null;
return (
<Suspense fallback={null}>
<Perf position="top-left" />
<Perf position="top-right" />
</Suspense>
);
}
@@ -86,6 +88,9 @@ Usage in Canvas:
- All debug UI goes through `Debug.getInstance()` — never inline `if (isDev)` checks
- r3f-perf is always lazy-imported, never a hard dependency in scene components
- Debug folders should be organized by domain (Lighting, PostFX, Player, Zone)
- Debug folders should be organized by domain (Lighting, Player, Zone, Interaction)
- Global debug controls include camera mode, scene mode, `R3F Perf`, and `Debug Overlay`
- Interaction-specific controls such as interaction spheres belong in the `Interaction` folder
- HTML debug panels should be grouped under `src/components/ui/debug/DebugOverlayLayout.tsx`
- Debug panel must not affect production builds — it simply doesn't mount when `?debug` is absent
- Clean up debug folders in `destroy()` when relevant
+18 -15
View File
@@ -29,13 +29,16 @@ export class SomeManager {
## Managers in this project
| Manager | File | Role |
| ------------------ | -------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `GameManager` | `src/stateManager/GameManager.ts` | Single source of truth. Owns phase, zone, mission, input lock, dialogue. Has `subscribe()` + `getState()`. |
| `CinematicManager` | `src/stateManager/CinematicManager.ts` | GSAP timelines. Locks/unlocks input via GameManager. |
| `AudioManager` | `src/stateManager/AudioManager.ts` | Music, SFX, spatial audio. Reads phase from GameManager. |
| `ZoneManager` | `src/stateManager/ZoneManager.ts` | Zone entry/exit detection, LOD triggers. Notifies GameManager of zone changes. |
| -------------------- | ------------------------------------ | ----------------------------------------------------------------------------- |
| `AudioManager` | `src/managers/AudioManager.ts` | Music and SFX playback. |
| `InteractionManager` | `src/managers/InteractionManager.ts` | Focus, nearby, trigger, grab, and hand-grab interaction state. |
| `GameManager` | target-state only | Future single source of truth for phase, zone, mission, input lock, dialogue. |
| `CinematicManager` | target-state only | Future GSAP timeline orchestrator. |
| `ZoneManager` | target-state only | Future zone entry/exit detection and LOD triggers. |
## GameManager is the orchestrator
## Target-State GameManager
`GameManager` does not exist in the current implementation. The following pattern is target-state guidance only and should not be applied until the manager exists in code.
```ts
export class GameManager {
@@ -51,7 +54,7 @@ export class GameManager {
}
```
Components and hooks access other managers **through GameManager only**:
When a `GameManager` exists, components and hooks should access other managers through it:
```ts
// Correct
@@ -61,7 +64,7 @@ GameManager.getInstance().cinematic.play("intro");
CinematicManager.getInstance().play("intro");
```
## Subscribe pattern (GameManager only)
## Target-State Subscribe Pattern
```ts
private listeners = new Set<() => void>()
@@ -76,9 +79,9 @@ private emit(): void {
}
```
Every `set*()` method calls `this.emit()` to notify subscribers.
In that target-state manager, every `set*()` method calls `this.emit()` to notify subscribers.
## React bridge hook
## Target-State React Bridge Hook
```ts
// hooks/useGameState.ts
@@ -96,8 +99,8 @@ export function useGameState() {
## Rules
- Max 4 managers total
- Only `GameManager` holds durable state with `subscribe()`
- Other managers are side-effect handlers — they do not store persistent state
- Always call `destroy()` on cleanup (App unmount)
- Never create manager instances with `new` — always use `.getInstance()`
- Do not add a `GameManager` unless the feature requires a real shared gameplay state owner.
- Current managers may be imported directly until the target-state orchestrator exists.
- Keep singleton managers limited to side-effect services or shared interaction state.
- Always call `destroy()` on cleanup when a manager owns external resources.
- Never create manager instances with `new` — always use `.getInstance()`.
-15
View File
@@ -66,21 +66,6 @@ import { RigidBody, CuboidCollider } from "@react-three/rapier";
- `type="dynamic"` for movable objects
- Player uses `type="dynamic"` with `lockRotations`
## Postprocessing
```tsx
import { EffectComposer, Bloom, Vignette } from "@react-three/postprocessing";
<EffectComposer>
<Bloom intensity={0.5} luminanceThreshold={0.9} />
<Vignette offset={0.3} darkness={0.5} />
</EffectComposer>;
```
- Always wrap in `<EffectComposer>`
- Keep effects minimal for performance
- Disable heavy effects on low-end devices via Debug panel
## What NOT to do
- Do not use `new THREE.Scene()` or `new THREE.WebGLRenderer()` — R3F handles this
+1
View File
@@ -3,6 +3,7 @@
*.glb filter=lfs diff=lfs merge=lfs -text
*.gltf filter=lfs diff=lfs merge=lfs -text
*.bin filter=lfs diff=lfs merge=lfs -text
# Textures
*.png filter=lfs diff=lfs merge=lfs -text
+7 -1
View File
@@ -3,8 +3,12 @@ name: 🔍 Lint
on:
pull_request:
types: [opened, synchronize, reopened]
branches: [develop, main]
push:
branches: [main]
branches:
- main
- develop
workflow_dispatch:
jobs:
lint:
@@ -71,6 +75,8 @@ jobs:
steps:
- name: ⬇️ Checkout
uses: actions/checkout@v6
with:
lfs: true
- name: 🧰 Setup Node
uses: actions/setup-node@v6
+11 -5
View File
@@ -3,8 +3,12 @@ name: 📊 Quality
on:
pull_request:
types: [opened, synchronize, reopened]
branches: [develop, main]
push:
branches: [main]
branches:
- main
- develop
workflow_dispatch:
jobs:
security:
@@ -53,6 +57,8 @@ jobs:
steps:
- name: ⬇️ Checkout
uses: actions/checkout@v6
with:
lfs: true
- name: 🧰 Setup Node
uses: actions/setup-node@v6
@@ -68,12 +74,12 @@ jobs:
- name: 📏 Check bundle size
run: |
# Get bundle size in KB
SIZE=$(du -k dist | cut -f1)
# Check generated app assets only; public/ model files are runtime assets copied to dist.
SIZE=$(du -k dist/assets | cut -f1)
echo "Bundle size: ${SIZE}KB"
# Threshold: 1000KB (configurable)
THRESHOLD=1000
# Threshold: 5000KB (configurable)
THRESHOLD=5000
if [ "$SIZE" -gt "$THRESHOLD" ]; then
echo "❌ Bundle size ${SIZE}KB exceeds threshold ${THRESHOLD}KB"
+5
View File
@@ -1,9 +1,14 @@
# Dependencies
node_modules/
.venv/
backend/.venv/
__pycache__/
*.pyc
# Build
dist/
dist-ssr/
.vite/
*.local
# Environment
+1
View File
@@ -0,0 +1 @@
22.12.0
+19
View File
@@ -0,0 +1,19 @@
dist
node_modules
public/models
public/**/*.glb
public/**/*.gltf
public/**/*.png
public/**/*.jpg
public/**/*.jpeg
public/**/*.webp
public/**/*.hdr
public/**/*.exr
public/**/*.ktx
public/**/*.ktx2
public/**/*.mp3
public/**/*.wav
public/**/*.ogg
public/**/*.mp4
public/**/*.webm
+66 -65
View File
@@ -8,31 +8,30 @@ Built with React, Three.js, and Vite. Runs in the browser, no installation requi
### Build & Language
| Package | Doc |
| -------------------------------------------------- | ------------------------------------ |
| [TypeScript](https://www.typescriptlang.org/docs/) | https://www.typescriptlang.org/docs/ |
| [React](https://react.dev/learn) | https://react.dev/learn |
| [Vite](https://vite.dev/guide/) | https://vite.dev/guide/ |
| [ESLint](https://eslint.org/docs/latest/) | https://eslint.org/docs/latest/ |
| [Prettier](https://prettier.io/docs/) | https://prettier.io/docs/ |
| Package |
| -------------------------------------------------- |
| [TypeScript](https://www.typescriptlang.org/docs/) |
| [React](https://react.dev/learn) |
| [Vite](https://vite.dev/guide/) |
| [ESLint](https://eslint.org/docs/latest/) |
| [Prettier](https://prettier.io/docs/) |
### 3D Engine
| Package | Doc |
| ----------------------------------------------------------------------------------------- | ---------------------------------------------- |
| [Three.js](https://threejs.org/docs/) | https://threejs.org/docs/ |
| [@react-three/fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction) | https://docs.pmnd.rs/react-three-fiber |
| [@react-three/drei](https://pmndrs.github.io/drei) | https://pmndrs.github.io/drei |
| [@react-three/rapier](https://rapier.rs/docs/) | https://rapier.rs/docs/user_guides/javascript/ |
| [@react-three/postprocessing](https://github.com/pmndrs/postprocessing) | https://github.com/pmndrs/postprocessing |
| [GSAP](https://gsap.com/docs/v3/Installation/) | https://gsap.com/docs/v3/ |
| Package |
| ----------------------------------------------------------------------------------------- |
| [Three.js](https://threejs.org/docs/) |
| [@react-three/fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction) |
| [@react-three/drei](https://pmndrs.github.io/drei) |
| [@react-three/rapier](https://rapier.rs/docs/) |
| [GSAP](https://gsap.com/docs/v3/Installation/) |
### Performance & Effects
| Package | Doc |
| --------------------------------------------------------------------------- | --------------------------------------------------------- |
| [r3f-perf](https://github.com/utsuboco/r3f-perf) | https://github.com/utsuboco/r3f-perf |
| [AnimationMixer](https://threejs.org/docs/#api/en/animation/AnimationMixer) | https://threejs.org/docs/#api/en/animation/AnimationMixer |
| Package |
| --------------------------------------------------------------------------- |
| [r3f-perf](https://github.com/utsuboco/r3f-perf) |
| [AnimationMixer](https://threejs.org/docs/#api/en/animation/AnimationMixer) |
## 🗂 Project Structure
@@ -48,61 +47,63 @@ la-fabrik/
│ └── sounds/
└── src/
├── world/ # Single persistent 3D world
│ ├── Map.tsx # Base map, always mounted
├── world/ # Persistent 3D world composition
│ ├── World.tsx # Active scene composition
│ ├── GameMap.tsx # Map loading and progressive rendering
│ ├── GameMapCollision.tsx # Collision-only octree source
│ ├── Lighting.tsx # Ambient, directional, point lights
│ ├── Environment.tsx # HDRI, fog, sky
│ ├── PostFX.tsx # Bloom, SSAO, chromatic aberration
│ ├── zones/ # Spatial zones — LOD per zone
│ │ ── WorkshopZone.tsx
│ │ ├── PowerGridZone.tsx
│ │ ├── FarmZone.tsx
│ │ ├── SchoolZone.tsx
│ │ └── ResidentialZone.tsx
│ ├── Environment.tsx # Scene background / sky model
│ ├── GameMusic.tsx # Game scene music lifecycle
│ ├── debug/ # Debug-only test scene
│ │ ── TestMap.tsx
│ └── player/
│ ├── FPSController.tsx # PointerLockControls + Rapier movement
── Crosshair.tsx
│ ├── Player.tsx # Player rig composition
── PlayerCamera.tsx # Player camera mount
│ └── PlayerController.tsx # Pointer lock movement and inputs
├── components/
│ ├── 3d/ # Shared reusable 3D elements
│ │ ── InteractiveObject.tsx # Raycasting + outline wrapper
│ ├── three/ # Shared R3F components by domain
│ │ ── gameplay/ # Core repair gameplay prototype
│ │ ├── handTracking/ # R3F hand tracking debug models
│ │ ├── interaction/ # Trigger, grab, focus wrappers
│ │ ├── models/ # GLTF model components
│ │ └── world/ # Environment-specific 3D objects
│ └── ui/ # HTML overlays — outside Canvas
│ ├── NarrativeOverlay.tsx # Floating dialogues
│ ├── MissionHUD.tsx # Current objective
├── MapHUD.tsx # Minimap
├── CinematicBars.tsx # GSAP black bars
└── LoadingScreen.tsx # Asset progress
│ ├── Crosshair.tsx
│ ├── debug/ # Debug-only HTML overlay panels
│ ├── DebugOverlayLayout.tsx
│ ├── GameStateDebugPanel.tsx
│ └── HandTrackingDebugPanel.tsx
│ ├── HandTrackingVisualizer.tsx
│ └── InteractPrompt.tsx
├── stateManager/ # All logic, state, orchestration
│ ├── GameManager.ts # Single source of truth: phase, zone, mission
── CinematicManager.ts # GSAP timelines, camera lock/unlock
│ ├── AudioManager.ts # Music, SFX, spatial audio
│ └── ZoneManager.ts # Zone detection, LOD triggers
├── managers/ # Current singleton-style services
│ ├── AudioManager.ts # Music and SFX playback
── InteractionManager.ts # Focus, nearby, grab state
├── hooks/ # React hooks — thin wrappers on managers
│ ├── useGameState.ts # Subscribes to GameManager
│ ├── useZoneDetection.ts
│ ├── useInteraction.ts
│ ├── useCinematic.ts
│ ├── useAudio.ts
── useLOD.ts
├── hooks/ # React hooks by domain
│ ├── debug/ # Debug state and GUI folders
│ ├── docs/ # Docs language context access
│ ├── editor/ # Editor loading and history
│ ├── gameplay/ # Repair gameplay helpers
│ ├── handTracking/ # Webcam/WebSocket hand tracking
── interaction/ # Interaction manager subscriptions
│ └── three/ # Three.js/R3F helpers
├── data/
│ ├── zones.ts # { id, position, radius, missionId }
│ ├── dialogues.ts # Narrative scripts, PNJ states
── missions.ts # Mission definitions, steps
├── shaders/
│ └── hologram/
│ ├── vertex.glsl
│ └── fragment.glsl
│ ├── interaction/ # Interaction tuning
│ ├── player/ # Player tuning
── gameplay/ # Repair gameplay static config
└── world/ # Environment and lighting config
├── utils/
│ ├── Debug.ts # lil-gui panel
│ ├── EventEmitter.ts # Simple pub/sub for manager-to-manager events
── Dispose.ts # traverse() + dispose() helper
├── App.tsx # Canvas + UI superimposed
│ ├── core/ # Logger and generic utilities
│ ├── debug/ # Dev-only tools and scene inspection
── editor/ # Editor-only parsing utilities
├── map/ # Map loading and validation
│ └── three/ # Three.js helpers
├── types/ # Shared TypeScript domain types
├── App.tsx # App bootstrap and route switch
└── main.tsx
```
@@ -115,8 +116,8 @@ npm install
npm run dev
```
Open `http://localhost:5173` — standard experience.
Open `http://localhost:5173?debug` — debug panel + r3f-perf overlay.
- app: `http://localhost:5173`
- debug mode: `http://localhost:5173?debug`
## 📜 License
+91
View File
@@ -0,0 +1,91 @@
# Hand Tracking Backend
Remote-compatible Python backend for La-Fabrik hand tracking.
The browser captures webcam frames, downsizes them, sends JPEG frames to this backend over WebSocket, and receives hand landmarks plus closed-fist state.
## Setup
```bash
python3.11 -m venv backend/.venv
source backend/.venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r backend/requirements.txt
python backend/download_model.py
```
## Run
Run the Vite frontend and the Python backend in two separate terminals.
Terminal 1:
```bash
npm run dev
```
Terminal 2:
```bash
source backend/.venv/bin/activate
python -m backend.main
```
The WebSocket endpoint is:
```txt
ws://localhost:8000/ws
```
## Health Check
```txt
http://localhost:8000/health
```
## Message Flow
Client sends a compressed frame:
```json
{
"type": "frame",
"timestamp": 1234567890,
"width": 320,
"height": 240,
"image": "base64-jpeg"
}
```
Server responds with detected hands:
```json
{
"type": "hands",
"timestamp": 1234567890,
"hands": [
{
"x": 0.5,
"y": 0.3,
"z": 0.1,
"landmarks": [
{
"x": 0.48,
"y": 0.32,
"z": 0.02
}
],
"handedness": "Right",
"isFist": true,
"score": 0.92
}
]
}
```
## Notes
- The backend does not read `cv2.VideoCapture(0)`.
- This keeps local development and production behavior aligned.
- Each browser connection sends its own webcam frames.
- The backend rate-limits frames per connection and drops work when a client is already being processed.
View File
+37
View File
@@ -0,0 +1,37 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from uuid import uuid4
from fastapi import WebSocket
@dataclass
class ClientConnection:
id: str
websocket: WebSocket
is_processing: bool = False
last_frame_at: float = 0.0
metadata: dict[str, Any] = field(default_factory=dict)
class ConnectionManager:
def __init__(self) -> None:
self._connections: dict[str, ClientConnection] = {}
@property
def count(self) -> int:
return len(self._connections)
async def connect(self, websocket: WebSocket) -> ClientConnection:
await websocket.accept()
connection = ClientConnection(id=str(uuid4()), websocket=websocket)
self._connections[connection.id] = connection
return connection
def disconnect(self, connection: ClientConnection) -> None:
self._connections.pop(connection.id, None)
async def send(self, connection: ClientConnection, payload: dict[str, Any]) -> None:
await connection.websocket.send_json(payload)
+22
View File
@@ -0,0 +1,22 @@
from __future__ import annotations
from pathlib import Path
from urllib.request import urlretrieve
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task"
MODEL_PATH = Path(__file__).with_name("hand_landmarker.task")
def download_model() -> None:
if MODEL_PATH.exists():
print(f"Model already exists at {MODEL_PATH}")
return
print("Downloading MediaPipe Hand Landmarker model...")
urlretrieve(MODEL_URL, MODEL_PATH)
print(f"Model downloaded to {MODEL_PATH}")
if __name__ == "__main__":
download_model()
Binary file not shown.
+142
View File
@@ -0,0 +1,142 @@
from __future__ import annotations
import base64
import math
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import cv2
import mediapipe as mp
import numpy as np
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
@dataclass(frozen=True)
class HandData:
x: float
y: float
z: float
landmarks: list[dict[str, float]]
handedness: str
is_fist: bool
score: float
def to_payload(self) -> dict[str, float | str | bool | list[dict[str, float]]]:
return {
"x": self.x,
"y": self.y,
"z": self.z,
"landmarks": self.landmarks,
"handedness": self.handedness,
"isFist": self.is_fist,
"score": self.score,
}
class HandTracker:
def __init__(self, max_hands: int = 2) -> None:
model_path = Path(__file__).with_name("hand_landmarker.task")
if not model_path.exists():
raise FileNotFoundError(
"Missing hand_landmarker.task. Run `python backend/download_model.py`.",
)
base_options = python.BaseOptions(model_asset_path=str(model_path))
options = vision.HandLandmarkerOptions(
base_options=base_options,
running_mode=vision.RunningMode.IMAGE,
num_hands=max_hands,
)
self._detector = vision.HandLandmarker.create_from_options(options)
def detect_from_base64_jpeg(self, image_base64: str) -> list[HandData]:
image_data = base64.b64decode(image_base64, validate=True)
image_buffer = np.frombuffer(image_data, dtype=np.uint8)
frame = cv2.imdecode(image_buffer, cv2.IMREAD_COLOR)
if frame is None:
raise ValueError("Invalid JPEG frame")
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb_frame)
result = self._detector.detect(mp_image)
return self._to_hands(result)
def close(self) -> None:
self._detector.close()
def _to_hands(self, result: vision.HandLandmarkerResult) -> list[HandData]:
hands: list[HandData] = []
if not result.hand_landmarks or not result.handedness:
return hands
for landmarks, handedness_categories in zip(
result.hand_landmarks,
result.handedness,
):
palm_center = self._average_points(
[landmarks[0], landmarks[5], landmarks[9], landmarks[13], landmarks[17]],
)
is_fist = self._is_fist(landmarks)
handedness = handedness_categories[0]
hands.append(
HandData(
x=palm_center["x"],
y=palm_center["y"],
z=palm_center["z"],
landmarks=[
{"x": point.x, "y": point.y, "z": point.z}
for point in landmarks
],
handedness=handedness.category_name,
is_fist=is_fist,
score=handedness.score,
),
)
return hands
def _is_fist(self, landmarks: list[Any]) -> bool:
palm_center = self._average_points(
[landmarks[0], landmarks[5], landmarks[9], landmarks[13], landmarks[17]],
)
palm_size = self._calculate_distance(landmarks[0], landmarks[9])
if palm_size <= 0:
return False
folded_finger_count = sum(
self._calculate_distance(landmarks[index], palm_center) / palm_size < 1.05
for index in (8, 12, 16, 20)
)
return folded_finger_count >= 4
def _average_points(self, points: list[Any]) -> dict[str, float]:
return {
"x": sum(point.x for point in points) / len(points),
"y": sum(point.y for point in points) / len(points),
"z": sum(point.z for point in points) / len(points),
}
def _calculate_distance(self, point_a: Any, point_b: Any) -> float:
return math.sqrt(
(self._get_coordinate(point_a, "x") - self._get_coordinate(point_b, "x"))
** 2
+ (self._get_coordinate(point_a, "y") - self._get_coordinate(point_b, "y"))
** 2
+ (self._get_coordinate(point_a, "z") - self._get_coordinate(point_b, "z"))
** 2,
)
def _get_coordinate(self, point: Any, axis: str) -> float:
if isinstance(point, dict):
return point[axis]
return getattr(point, axis)
def now_ms() -> int:
return time.monotonic_ns() // 1_000_000
+122
View File
@@ -0,0 +1,122 @@
from __future__ import annotations
import asyncio
from contextlib import asynccontextmanager
from typing import Any
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import JSONResponse
from backend.connection_manager import ClientConnection, ConnectionManager
from backend.hand_tracker import HandTracker, now_ms
MAX_FRAME_BYTES = 220_000
MIN_FRAME_INTERVAL_SECONDS = 0.08
manager = ConnectionManager()
tracker: HandTracker | None = None
detection_lock = asyncio.Lock()
@asynccontextmanager
async def lifespan(app: FastAPI):
global tracker
tracker = HandTracker(max_hands=2)
yield
if tracker:
tracker.close()
app = FastAPI(title="La-Fabrik Hand Tracking", lifespan=lifespan)
@app.get("/health")
async def health() -> JSONResponse:
return JSONResponse(
{
"status": "ok",
"connections": manager.count,
},
)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket) -> None:
connection = await manager.connect(websocket)
await manager.send(connection, status_payload("connected"))
try:
while True:
message = await websocket.receive_json()
response = await handle_message(connection, message)
await manager.send(connection, response)
except WebSocketDisconnect:
manager.disconnect(connection)
except Exception as error:
await manager.send(connection, error_payload(str(error)))
manager.disconnect(connection)
async def handle_message(
connection: ClientConnection,
message: dict[str, Any],
) -> dict[str, Any]:
if message.get("type") != "frame":
return error_payload("Unsupported message type")
current_time = asyncio.get_running_loop().time()
if current_time - connection.last_frame_at < MIN_FRAME_INTERVAL_SECONDS:
return status_payload("rate_limited")
if connection.is_processing:
return status_payload("busy")
image = message.get("image")
if not isinstance(image, str):
return error_payload("Missing image payload")
if len(image) > MAX_FRAME_BYTES:
return error_payload("Frame payload too large")
if tracker is None:
return error_payload("Hand tracker is not ready")
if detection_lock.locked():
return status_payload("busy")
connection.last_frame_at = current_time
connection.is_processing = True
try:
async with detection_lock:
hands = await asyncio.to_thread(tracker.detect_from_base64_jpeg, image)
return {
"type": "hands",
"timestamp": now_ms(),
"hands": [hand.to_payload() for hand in hands],
}
finally:
connection.is_processing = False
def status_payload(status: str) -> dict[str, str | int]:
return {
"type": "status",
"timestamp": now_ms(),
"status": status,
}
def error_payload(message: str) -> dict[str, str | int | list[Any]]:
return {
"type": "error",
"timestamp": now_ms(),
"hands": [],
"message": message,
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
+5
View File
@@ -0,0 +1,5 @@
fastapi==0.115.0
uvicorn[standard]==0.30.6
opencv-python-headless==4.10.0.84
mediapipe==0.10.20
numpy==1.26.4
+50
View File
@@ -0,0 +1,50 @@
# Animation & 3D Components
This document describes the 3D components that are currently used in the runtime.
## Runtime Components
| Domain | Component | Role |
| ----------- | -------------------- | --------------------------------------------------------------------- |
| Interaction | `InteractableObject` | Focus detection through distance and raycasting |
| Interaction | `TriggerObject` | Press-to-trigger interactions, optional sound, optional spawned model |
| Interaction | `GrabbableObject` | Physics grab and hand-tracking grab behavior |
| Model | `ExplodableModel` | Split/reassemble a GLTF model into separated parts |
| Gameplay | `RepairCaseModel` | Repair case lid animation, proximity float, and wobble |
## Continuous Animation
Use `useFrame` for per-frame 3D behavior. Current examples:
- `GrabbableObject` updates held object velocity every frame.
- `ExplodableModel` updates split part positions every frame.
- `RepairCaseModel` updates proximity float and rotation wobble every frame.
- `SkyModel` follows the camera position every frame.
## Timeline Animation
Use GSAP only for discrete timeline-style transitions. Current example:
- `RepairCaseModel` animates the case lid between open and closed rotations.
## 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.
## File Structure
```txt
src/components/three/
├── gameplay/
│ ├── RepairCaseModel.tsx
│ ├── RepairGame.tsx
│ └── RepairRepairingStep.tsx
├── interaction/
│ ├── GrabbableObject.tsx
│ ├── InteractableObject.tsx
│ └── TriggerObject.tsx
├── models/
│ └── ExplodableModel.tsx
└── world/
└── SkyModel.tsx
```
+142 -381
View File
@@ -1,381 +1,142 @@
# Architecture Patterns
The project uses **two complementary patterns**:
- **Singleton service classes** for orchestration and side effects
- **Declarative React components** for all 3D scene objects
This distinction is intentional. Scene elements such as the map, lights, environment, zones, and player are implemented as **React Three Fiber components** and mounted through `<Canvas>`.
Global systems such as gameplay flow, cinematics, audio, and debug tooling are implemented as **manager classes**.
Consistency matters, but the codebase does **not** force the same lifecycle pattern on scene components and global services.
---
## 1. Singleton Pattern for Global Managers Only
Only cross-cutting services use the singleton pattern.
Examples:
- `GameManager`
- `CinematicManager`
- `AudioManager`
- `ZoneManager`
- `Debug`
- `EventEmitter`
These services must exist once, be accessible from anywhere, and coordinate the experience globally.
```ts
// stateManager/GameManager.ts
export class GameManager {
private static _instance: GameManager | null = null;
cinematic!: CinematicManager;
audio!: AudioManager;
zone!: ZoneManager;
static getInstance(): GameManager {
if (!GameManager._instance) {
GameManager._instance = new GameManager();
}
return GameManager._instance;
}
private constructor() {
this.cinematic = CinematicManager.getInstance();
this.audio = AudioManager.getInstance();
this.zone = ZoneManager.getInstance();
}
destroy(): void {
this.cinematic.destroy();
this.audio.destroy();
this.zone.destroy();
GameManager._instance = null;
}
}
```
Usage:
```ts
const game = GameManager.getInstance();
game.startMission("workshop");
```
**Important:** scene objects such as `Map`, `WorkshopZone`, `Lighting`, or `Environment` are **not** singletons and must remain standard React components.
---
## 2. Scene Objects Are React Components, Not Manager Classes
All 3D scene objects are implemented as **declarative React components**.
This includes:
- maps
- lights
- environments
- player controllers
- zones
- interactive props
- postprocessing layers
This keeps the code aligned with the R3F runtime instead of rebuilding a parallel imperative engine.
Example:
```tsx
// world/zones/WorkshopZone.tsx
import { useEffect, useRef } from "react";
import * as THREE from "three";
import { useFrame } from "@react-three/fiber";
import { useGLTF } from "@react-three/drei";
export function WorkshopZone() {
const root = useRef<THREE.Group>(null);
const gltf = useGLTF("/models/workshop/ebike.glb");
const mixer = useRef<THREE.AnimationMixer | null>(null);
useEffect(() => {
mixer.current = new THREE.AnimationMixer(gltf.scene);
return () => {
mixer.current?.stopAllAction();
mixer.current = null;
};
}, [gltf.scene]);
useFrame((_, delta) => {
mixer.current?.update(delta);
});
return <primitive ref={root} object={gltf.scene.clone()} />;
}
```
Per-frame values such as movement, interpolation, camera smoothing, and physics must stay in:
- `useRef`
- `useFrame`
- Rapier bodies
- other frame-based systems
They must **never** go through React state.
---
## 3. Single Source of Truth for Durable Gameplay State
The project uses a single authoritative `GameManager` for durable gameplay state.
React components subscribe to that state through thin hooks.
Other managers communicate through `GameManager`, which acts as the main gameplay orchestrator.
High-frequency values such as movement, camera interpolation, or physics never go through React state and stay in refs or frame-based systems.
```ts
// stateManager/GameManager.ts
type Phase = "loading" | "intro" | "exploring" | "cinematic" | "outro";
type ZoneId = "workshop" | "powerGrid" | "farm" | null;
type GameSnapshot = {
phase: Phase;
activeZone: ZoneId;
missionId: string | null;
missionStep: number;
inputLocked: boolean;
dialogueId: string | null;
};
export class GameManager {
private static _instance: GameManager | null = null;
private listeners = new Set<() => void>();
private state: GameSnapshot = {
phase: "loading",
activeZone: null,
missionId: null,
missionStep: 0,
inputLocked: false,
dialogueId: null,
};
static getInstance(): GameManager {
if (!GameManager._instance) {
GameManager._instance = new GameManager();
}
return GameManager._instance;
}
getState(): GameSnapshot {
return this.state;
}
subscribe(listener: () => void): () => void {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
private emit(): void {
this.listeners.forEach((cb) => cb());
}
setPhase(phase: Phase): void {
this.state.phase = phase;
this.emit();
}
setActiveZone(zone: ZoneId): void {
this.state.activeZone = zone;
this.emit();
}
startMission(id: string): void {
this.state.missionId = id;
this.state.missionStep = 0;
this.emit();
}
}
```
```ts
// hooks/useGameState.ts
import { useEffect, useState } from "react";
import { GameManager } from "@/stateManager/GameManager";
export function useGameState() {
const game = GameManager.getInstance();
const [state, setState] = useState(game.getState());
useEffect(() => {
return game.subscribe(() => {
setState({ ...game.getState() });
});
}, [game]);
return state;
}
```
This keeps the architecture simple:
- **GameManager** owns durable gameplay state
- **other managers** handle side effects
- **React components** render that state
- **R3F frame systems** handle fast-changing values
---
## 4. Side Effects Stay in Specialized Managers
Managers other than `GameManager` should not become secondary state stores.
Their role is to manage side effects and specialized runtime logic, such as:
- GSAP timelines
- audio playback
- zone entry detection
- interaction triggers
- camera lock/unlock
- temporary event coordination
They can read from `GameManager`, react to its state, or notify it of important transitions.
Example flow:
```
Component / Hook
GameManager.getInstance()
├── startMission('workshop')
├── cinematic.play('intro_workshop')
├── audio.playAmbience('workshop')
└── zone.setActive('workshop')
```
This keeps the dependency graph understandable while avoiding duplicated durable state.
---
## 5. Memory Management — Dispose Only What You Own
GPU memory must be cleaned carefully.
However, the project does **not** blindly deep-dispose every object on unmount.
Only resources explicitly created and owned by the current component or manager should be disposed.
This includes things like:
- custom materials
- render targets
- postprocessing passes
- manually created geometries
- manually created textures
- temporary clones with owned resources
Shared or cached assets must **not** be blindly disposed.
```ts
// utils/Dispose.ts
import * as THREE from "three";
export class Dispose {
static material(material: THREE.Material): void {
for (const value of Object.values(material)) {
if (value instanceof THREE.Texture) {
value.dispose();
}
}
material.dispose();
}
static mesh(mesh: THREE.Mesh): void {
mesh.geometry?.dispose();
const materials = Array.isArray(mesh.material)
? mesh.material
: [mesh.material];
for (const material of materials) {
if (material) this.material(material);
}
}
static renderTarget(rt: THREE.WebGLRenderTarget): void {
rt.texture.dispose();
rt.dispose();
}
}
```
Example usage:
```ts
useEffect(() => {
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
});
meshRef.current.material = material;
return () => {
Dispose.material(material);
};
}, []);
```
**Rule:** disposal is ownership-based, not automatic and not blind.
---
## 6. Debug Utility
The debug panel can be activated by appending `?debug` to the URL:
`http://localhost:5173?debug`
All debug logic is centralized in `Debug.ts`.
Do not scatter debug checks across the codebase.
```ts
// utils/Debug.ts
import GUI from "lil-gui";
export class Debug {
private static _instance: Debug | null = null;
readonly active: boolean;
gui: GUI | null = null;
static getInstance(): Debug {
if (!Debug._instance) Debug._instance = new Debug();
return Debug._instance;
}
private constructor() {
this.active = new URLSearchParams(window.location.search).has("debug");
if (this.active) {
this.gui = new GUI({ title: "La-Fabrik Debug" });
}
}
destroy(): void {
this.gui?.destroy();
Debug._instance = null;
}
}
```
Usage:
```ts
const debug = Debug.getInstance();
if (debug.active) {
debug.gui!.add(params, "bloomIntensity", 0, 3).name("Bloom");
}
```
# Current Architecture
This document describes the code that exists today in the repository.
## Runtime Structure
- `src/main.tsx` mounts React.
- `src/App.tsx` mounts the TanStack `RouterProvider`.
- `src/router.tsx` declares the top-level routes:
- `/` mounts the playable 3D scene, debug perf overlay, and HTML overlays.
- `/editor` mounts the map editor page.
- `src/world/World.tsx` composes the active scene, including:
- environment and lighting
- debug helpers and debug camera mode
- either the map scene or the debug physics test scene
- the player rig when the active camera mode is `player`
- `src/hooks/world/useWorldSceneLoading.ts` owns the production scene loading state shared by `World`, `GameMap`, and the player octree readiness.
- `src/world/GameMap.tsx` loads map nodes from `public/map.json`, resolves available models, renders them progressively, and shows fallback cubes for missing models.
- `src/world/GameMapCollision.tsx` builds the player collision octree from dedicated collision nodes only.
- `src/world/GameStageContent.tsx` is wrapped in Rapier `Physics` in the production game scene so stage gameplay objects can use physics without moving the map or player to Rapier. It now mounts reusable `RepairGame` instances for `bike`, `pylone`, and `ferme` mission states.
- `src/world/debug/TestMap.tsx` provides a debug-oriented interaction and physics map with the existing grab/trigger/model-preview objects plus separate `Bike`, `Pylone`, and `Farm` repair playground zones.
- `src/world/player/Player.tsx` mounts the camera and controller.
- `src/world/player/PlayerController.tsx` owns pointer lock movement, jump handling, repair-step movement locking, and interaction input.
## Physics Boundaries
The project currently uses two collision layers with separate responsibilities:
- `GameMapCollision` builds an octree used by the player controller for map collision.
- The player octree must be built from a small collision-only subset of map nodes. It currently uses the `terrain` node only instead of traversing the full visible map, because building an octree from all rendered props can overload the browser renderer.
- `GameStageContent` is wrapped in Rapier `Physics` for gameplay objects such as repair triggers, cases, grabbables, and future mission-specific objects.
- `TestMap` owns its own Rapier `Physics` playground so repair gameplay can be tuned per mission state without depending on the production map layout.
Keep the player and map octree outside the Rapier provider until there is a deliberate migration plan. This avoids mixing player movement rules with object physics before the gameplay systems need it.
## Interaction Model
- `src/managers/InteractionManager.ts` is the current interaction state source.
- `src/components/three/interaction/InteractableObject.tsx` handles focus detection through distance and raycasting.
- `src/components/three/interaction/TriggerObject.tsx` implements trigger-style interactions.
- `src/components/three/interaction/GrabbableObject.tsx` implements hold-and-release interactions.
- `src/hooks/interaction/useInteraction.ts` exposes the interaction snapshot to React UI.
- `src/components/ui/InteractPrompt.tsx` shows the `E` prompt for trigger interactions.
## Audio
- `src/managers/AudioManager.ts` provides pooled one-shot playback, looped music playback, category volumes, and optional stereo pan for one-shot sounds.
- Supported audio categories are `music`, `sfx`, and `dialogue`.
- Trigger interactions may play SFX directly through `AudioManager`.
## Settings Menu
- `src/managers/stores/useSettingsStore.ts` stores settings for music volume, SFX volume, dialogue volume, subtitle visibility, subtitle language, repair runtime, and menu visibility.
- `src/components/ui/GameSettingsMenu.tsx` renders the in-game options menu.
- `src/components/ui/GameUI.tsx` mounts the settings menu as an HTML overlay outside the canvas.
- `Esc` opens and closes the menu, and `src/world/player/PlayerController.tsx` ignores player input while the menu is open.
- Volume changes are forwarded to `AudioManager` by category.
## Dialogues And Subtitles
- `public/sounds/dialogue/dialogues.json` is the runtime dialogue manifest.
- Dialogue audio files live under `public/sounds/dialogue/`.
- Subtitle files live under `public/sounds/dialogue/subtitles/{fr|en}/`.
- The current subtitle model is one SRT file per voice and language.
- `src/types/dialogues/dialogues.ts` contains the dialogue manifest types.
- `src/utils/dialogues/dialogueManifestValidation.ts` validates manifest shape at runtime.
- `src/utils/dialogues/loadDialogueManifest.ts` loads the manifest and SRT cues, with French fallback when the selected language is missing.
- `src/utils/subtitles/parseSrt.ts` parses SRT blocks and timecodes.
- `src/utils/dialogues/playDialogue.ts` plays dialogue audio and synchronizes the active subtitle against the audio element time.
- `src/managers/stores/useSubtitleStore.ts` stores the currently displayed subtitle cue.
- `src/components/ui/Subtitles.tsx` renders the subtitle overlay.
- `src/world/GameDialogues.tsx` currently triggers dialogue entries that define a `timecode`.
- Dialogue playback is queued so multiple dialogue requests do not overlap.
## Cinematics
- `public/cinematics.json` is the runtime cinematic manifest.
- `src/types/cinematics/cinematics.ts` contains cinematic manifest types.
- `src/utils/cinematics/cinematicManifestValidation.ts` validates manifest shape at runtime.
- `src/utils/cinematics/loadCinematicManifest.ts` loads `/cinematics.json`.
- `src/world/GameCinematics.tsx` triggers cinematics that define a global `timecode`.
- Cinematics use GSAP timelines to animate the active camera position and look target.
- `dialogueCues` on a cinematic trigger dialogue IDs at times relative to the cinematic start.
- `src/managers/stores/useGameStore.ts` exposes `isCinematicPlaying`, used to lock player input during cinematics.
## Debug System
- Debug mode is enabled with `?debug`.
- `src/utils/debug/Debug.ts` owns the `lil-gui` instance and debug controls.
- `src/hooks/debug/useCameraMode.ts` and `src/hooks/debug/useSceneMode.ts` subscribe to debug state.
- `src/components/debug/DebugPerf.tsx` lazily mounts `r3f-perf` in debug mode.
- `src/components/ui/debug/DebugOverlayLayout.tsx` mounts the compact HTML debug overlay when enabled from `lil-gui`.
- `src/components/ui/debug/GameStateDebugPanel.tsx` exposes current game state, main/sub-state switching, previous/next step controls, and reset.
- `src/components/ui/debug/HandTrackingDebugPanel.tsx` shows hand tracking status, usage, loaded glove model, hand count, and fist state while hand tracking is active.
- `src/components/ui/SceneLoadingOverlay.tsx` displays the fullscreen loading state for 3D scenes, including the production game scene, debug physics scene, and editor scene.
- `src/components/three/handTracking/HandTrackingGlove.tsx` places the rigged `gant_l` and `gant_r` models on detected hands in the debug physics scene.
- `src/components/debug/scene/DebugHelpers.tsx` mounts debug helpers.
- `src/components/debug/scene/DebugCameraControls.tsx` mounts the free debug camera.
- `lil-gui` global debug controls include camera mode, scene mode, `R3F Perf`, and `Debug Overlay`; interaction-specific controls live in the `Interaction` folder.
## 3D Component Domains
- `src/components/three/models/` contains reusable model helpers such as `ExplodableModel`.
- `src/components/three/interaction/` contains reusable interaction wrappers such as `InteractableObject`, `TriggerObject`, and `GrabbableObject`.
- `src/components/three/handTracking/` contains R3F hand tracking debug models such as the glove overlays.
- `src/components/three/gameplay/` contains the reusable production `RepairGame` flow, repair case, repair steps, and repair prompt components.
- `src/components/three/world/` contains reusable world/environment objects such as `SkyModel`.
## Editor System
- `src/pages/editor/page.tsx` is the route-level editor page for `/editor`.
- `src/components/editor/EditorControls.tsx` renders the HTML editor control panel.
- `src/components/editor/EditorDialogueManifestPanel.tsx` edits `public/sounds/dialogue/dialogues.json`.
- `src/components/editor/EditorCinematicManifestPanel.tsx` edits `public/cinematics.json`.
- `src/components/editor/EditorSrtPanel.tsx` renders the dialogue SRT editor inside the editor control panel.
- `src/components/editor/scene/EditorScene.tsx` composes the editor canvas scene, camera controls, lights, shortcuts, and map rendering.
- `src/components/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls.
- `src/controls/editor/FlyController.tsx` provides player-style editor navigation.
- `src/hooks/editor/useEditorSceneData.ts` loads scene data and handles folder upload fallback.
- `src/hooks/editor/useEditorHistory.ts` owns editor undo and redo state.
- `src/utils/editor/loadEditorScene.ts` handles editor-only folder upload parsing.
- `src/utils/map/loadMapSceneData.ts` is shared by the game scene and editor to load `public/map.json` and resolve model URLs.
- `src/types/editor/editor.ts` contains the shared `MapNode`, `SceneData`, and `TransformMode` types.
- `src/types/gameplay/repairMission.ts` contains shared repair mission ids, mission steps, and guards used across store, config, debug UI, and gameplay components.
## Map Data
- `public/map.json` is expected to be a `MapNode[]`.
- Each map node `name` maps to `public/models/{name}/model.glb` when available, with `public/models/{name}/model.gltf` kept as fallback.
- The editor renders a fallback cube for missing models.
- The game scene renders fallback cubes for nodes whose model cannot be resolved.
- The game scene currently uses `terrain` as the collision source for the player octree. Additional collision nodes should be explicit lightweight collision assets, not arbitrary visible decoration models.
## Current Limitations
- The repository is a prototype, not the full intended game runtime.
- `src/world/debug/TestMap.tsx` is part of the active scene composition.
- There is no central gameplay orchestrator such as `GameManager`.
- Mission state exists in Zustand and the repair flow is implemented as a prototype for the current repair missions.
- Cinematics and dialogues exist as prototype timecode-driven systems; dialogue branching and broader gameplay orchestration are still limited.
- The player uses octree collision and simple movement rules, not a complete gameplay physics stack.
- Editor save-to-server is implemented as a Vite dev-server plugin, not a production backend API.
-153
View File
@@ -1,153 +0,0 @@
# Best Practices
Generate code that is **simple**, **understandable**, **reviewable**, **scalable**, **optimized**, and **modern**. Follow W3C web standards and platform conventions.
## Naming Conventions
### Files
| Type | Convention | Example |
| ---------- | --------------------------- | -------------------- |
| Components | PascalCase | `WorkshopZone.tsx` |
| Hooks | camelCase with `use` prefix | `useGameState.ts` |
| Managers | PascalCase | `GameManager.ts` |
| Utils | PascalCase | `Dispose.ts` |
| Data | PascalCase | `missions.ts` |
| Shaders | kebab-case | `hologram.vert.glsl` |
### Variables & Functions
| Type | Convention | Example |
| ---------------- | -------------------- | ----------------------------------------- |
| Variables | camelCase | `activeZone`, `missionStep` |
| Functions | camelCase | `startMission()`, `setActiveZone()` |
| Constants | UPPER_SNAKE_CASE | `MAX_SPEED`, `DEFAULT_PHASE` |
| React components | PascalCase | `function WorkshopZone()` |
| React hooks | camelCase with `use` | `useGameState()`, `useFrame()` |
| Classes | PascalCase | `class GameManager` |
| Interfaces/Types | PascalCase | `type GameSnapshot`, `interface ZoneData` |
## Code Style
### Simplicity First
```ts
// Good — clear, direct
function getZoneRadius(zone: Zone): number {
return zone.radius;
}
// Bad — over-abstracted
function getZoneRadius(zone: Zone): number {
return zone[ZoneFields.RADIUS] ?? RADIUS_DEFAULTS[zone.type]?.default ?? 50;
}
```
### Early Return
```ts
// Good
if (!gltf) return null;
if (!visible) return null;
return <primitive object={gltf.scene} />;
```
### Avoid Nested Callbacks
```ts
// Good — flat structure
useEffect(() => {
const mixer = new THREE.AnimationMixer(model);
return () => mixer.stopAllAction();
}, [model]);
```
## TypeScript Rules
### Explicit Types for Exports
```ts
export function useGameState(): GameSnapshot { ... }
export function setPhase(phase: Phase): void { ... }
```
### Never use `any`
```ts
// Good
const ref = useRef<THREE.Group>(null);
// Bad
const ref = useRef<any>(null);
```
## Performance
### useRef for Mutable Values
```ts
// Good — no re-render
const position = useRef(new THREE.Vector3());
// Bad — triggers re-render every frame
const [position, setPosition] = useState(new THREE.Vector3());
```
### Memoize Expensive Computations
```ts
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
```
## Scalability
### Single Responsibility
```ts
// Good — focused component
export function WorkshopZone() {
// Only handles workshop zone logic
}
// Bad — does everything
export function WorkshopZone() {
// Handles zone logic + audio + cinematics + missions
}
```
### Constants for Magic Numbers
```ts
const DEFAULT_CAMERA_DISTANCE = 5;
const ZONE_DETECTION_RADIUS = 20;
```
## Accessibility (W3C)
### Semantic HTML
```tsx
// Good
<button onClick={handleInteract} aria-label="Interact with bike">
<Model />
</button>
// Bad
<div onClick={handleInteract}>
<Model />
</div>
```
## Rules
1. **Simplicity** — Every line of code must be justified.
2. **Readability** — Code is read 10x more than it's written.
3. **Reviewability** — PRs should be understandable in < 5 minutes.
4. **Scalability** — Architecture should support growth without refactoring.
5. **Performance** — Don't optimize prematurely, but don't introduce obvious bottlenecks.
6. **Modern** — Use ES2022+ features, TypeScript strict mode, React hooks.
7. **W3C** — Follow web standards: semantic HTML, ARIA, keyboard navigation.
8. **No Over-Engineering** — Avoid patterns that add complexity without benefit.
+234
View File
@@ -0,0 +1,234 @@
# Editor Technical Notes
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.
## Routing
- `/` renders the playable La-Fabrik scene.
- `/editor` renders the map editor.
- `src/App.tsx` mounts TanStack Router through `RouterProvider`.
- `src/router.tsx` defines the `/editor` route and imports `EditorPage` from `src/pages/editor/page.tsx`.
## File Structure
```txt
src/
├── pages/
│ └── editor/
│ └── page.tsx
├── components/
│ └── editor/
│ ├── EditorControls.tsx
│ ├── EditorCinematicManifestPanel.tsx
│ ├── EditorDialogueManifestPanel.tsx
│ ├── EditorSrtPanel.tsx
│ └── scene/
│ ├── EditorMap.tsx
│ └── EditorScene.tsx
├── controls/
│ └── editor/
│ └── FlyController.tsx
├── hooks/
│ └── editor/
│ ├── useEditorHistory.ts
│ └── useEditorSceneData.ts
├── types/
│ └── editor/
│ └── editor.ts
└── utils/
├── dialogues/
│ └── loadDialogueManifest.ts
├── editor/
│ └── loadEditorScene.ts
├── map/
│ └── loadMapSceneData.ts
└── subtitles/
└── parseSrt.ts
```
## 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, and player-mode toggle.
`src/hooks/editor/useEditorSceneData.ts` loads the default map data and handles folder uploads.
`src/hooks/editor/useEditorHistory.ts` owns editor undo and redo history.
`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/EditorControls.tsx` renders the HTML control panel outside the canvas.
`src/components/editor/EditorDialogueManifestPanel.tsx` renders the dialogue manifest editor. It loads `dialogues.json`, edits dialogue entries, previews selected dialogue playback, creates missing French SRT cues, and saves the manifest through a dev-server endpoint.
`src/components/editor/EditorCinematicManifestPanel.tsx` renders the cinematic manifest editor. It loads `cinematics.json`, edits camera keyframes and dialogue cues, previews selected cinematics in the editor canvas, and saves the manifest through a dev-server endpoint.
`src/components/editor/EditorSrtPanel.tsx` renders the dialogue subtitle editor inside the control panel. It loads the dialogue manifest, loads one SRT file per voice/language, validates cue structure, previews dialogue audio, and can save SRT files through a dev-server endpoint.
`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/editor/loadEditorScene.ts` contains editor-only upload handling for user-selected folders.
## Data Format
The shared editor type lives in `src/types/editor/editor.ts`.
```ts
interface MapNode {
name: string;
type: string;
position: [number, number, number];
rotation: [number, number, number];
scale: [number, number, number];
}
```
`public/map.json` is expected to be a `MapNode[]`.
```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:
```txt
public/
├── map.json
└── models/
└── pylone/
└── model.glb
```
If `model.glb` and `model.gltf` are both missing, the editor renders a fallback cube so the node can still be selected and transformed.
## Editor Flow
1. `EditorPage` mounts on `/editor`.
2. `useEditorSceneData` calls `loadMapSceneData()`.
3. `loadMapSceneData()` loads `/map.json` and available model URLs.
4. If `/map.json` is missing, the page displays a folder-upload flow.
5. `EditorScene` renders the grid, lights, camera controls, and map nodes.
6. `EditorControls` exposes transform mode, history actions, export, save, and selection info.
## Controls
- Click: select a node.
- `Esc`: clear selection.
- `T`: translate mode.
- `R`: rotate mode.
- `S`: scale mode.
- `Ctrl+Z` or `Cmd+Z`: undo.
- `Ctrl+Y` or `Cmd+Y`: redo.
- `WASD`, `ZQSD`, or arrow keys: move in player-controller mode.
- `Space`: move upward in player-controller mode.
- `Shift`: move downward in player-controller mode.
## Saving And Exporting
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`.
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.
## Dialogue SRT Editing
Dialogue subtitle editing is part of the `/editor` side panel.
Runtime dialogue files are grouped under `public/sounds/dialogue/`:
```txt
public/
└── sounds/
└── dialogue/
├── dialogues.json
└── subtitles/
├── fr/
│ ├── narrateur.srt
│ ├── fermier.srt
│ └── electricienne.srt
└── en/
└── ...
```
The current model is one SRT file per voice and language. A dialogue entry references the cue it needs through `subtitleCueIndex`; it does not own a dedicated SRT file.
`EditorSrtPanel` uses:
- `loadDialogueManifest()` to read `/sounds/dialogue/dialogues.json`
- `parseSrt()` to validate local textarea content and find active cues during audio preview
- `/api/save-srt` to write edited SRT files during local development
- `/api/validate-dialogues` to validate the manifest, linked audio, French SRT files, and referenced cue indexes
SRT timecodes are relative to the dialogue audio file being previewed, not to the global game timeline.
Missing English SRT files are warnings, not errors, because runtime loading falls back to French subtitles when the selected language is not available. Keep this behavior until the English translation workflow is ready.
## Dialogue Manifest Editing
`EditorDialogueManifestPanel` edits `public/sounds/dialogue/dialogues.json` in memory and persists it through `/api/save-dialogues`.
The panel supports:
- adding a dialogue entry
- deleting a dialogue entry
- editing `id`, `voice`, `audio`, `subtitleCueIndex`, and optional `timecode`
- previewing the selected dialogue through `playDialogueById()`
- creating a missing French SRT cue through `/api/save-srt`
When a dialogue is added, the editor computes the next `subtitleCueIndex` for the selected voice from the manifest. The generated SRT cue is a valid placeholder block and should be edited later in the SRT panel.
`/api/save-dialogues` is implemented in `vite.config.ts`. It validates manifest shape before writing to `public/sounds/dialogue/dialogues.json`.
## Cinematic Manifest Editing
`EditorCinematicManifestPanel` edits `public/cinematics.json` in memory and persists it through `/api/save-cinematics`.
The manifest shape is:
```ts
interface CinematicDefinition {
id: string;
timecode?: number;
cameraKeyframes: CinematicCameraKeyframe[];
dialogueCues?: CinematicDialogueCue[];
}
```
`cameraKeyframes` are relative to the cinematic start. At least two keyframes are required and keyframe times must increase.
`dialogueCues` are also relative to the cinematic start and reference dialogue IDs from `dialogues.json`. They are used by `GameCinematics` to synchronize dialogue playback with camera timelines. A dialogue synchronized this way should not also define a global `timecode` in `dialogues.json`.
The editor preview sends the selected `CinematicDefinition` to `EditorScene`, where GSAP animates the current editor camera. Orbit and fly controls are disabled during preview.
`/api/save-cinematics` is implemented in `vite.config.ts`. It validates manifest shape before writing to `public/cinematics.json`.
## Styling
Editor styles are in `src/index.css` under the `/* Editor page */` section. Classes are prefixed with `editor-` to avoid collisions with the game UI.
## Known Limitations
- Uploaded model object URLs are not revoked after replacement or unmount.
- Large `map.json` files are not virtualized, culled, or LOD-managed.
- There is no snap-to-grid, duplication, material editing, or object creation workflow.
- Save to Server is a Vite dev-server helper, not a production backend API.
- SRT Save is also a Vite dev-server helper, not a production backend API.
- Dialogue and cinematic manifest saves are Vite dev-server helpers, not production backend APIs.
- Dialogue creation still uses placeholder audio paths until real MP3 files are added.
+138
View File
@@ -0,0 +1,138 @@
# Hand Tracking Technical Notes
This document describes the hand tracking system that exists in the current codebase.
## Purpose
Hand tracking started as a debug-stage interaction system used to test direct 3D object manipulation with a webcam. It allows a user to close their fist to grab a nearby object and move it in 3D space without relying on the center crosshair.
It is now also available to the production repair flow when a mission reaches a hand-driven step.
## Runtime Flow
1. The browser captures webcam frames in `src/hooks/handTracking/useRemoteHandTracking.ts`.
2. Frames are sent to the local Python backend over WebSocket.
3. The backend runs MediaPipe hand landmark detection.
4. The backend returns hand data including landmarks, handedness, score, center point, and `isFist`.
5. React stores the latest snapshot in the hand tracking provider.
6. `GrabbableObject` reads that snapshot each frame and uses fist state plus raycasting to grab objects.
7. `HandTrackingGlove` reads the same snapshot and places the rigged `gant_l` and `gant_r` models on the detected hands when hand tracking is active.
## Activation Rules
Hand tracking is intentionally gated so the webcam and backend are not used all the time.
The debug activation conditions are:
- debug mode is active with `?debug`
- scene mode is `physics`
- the player is near an interaction, is holding an object, or is hand-holding an object
This keeps hand tracking active while the player is inside an interaction zone, even if the camera is not aimed directly at the object.
The production repair activation conditions are:
- active `mainState` is `bike`, `pylone`, or `ferme`
- the active mission step is `inspected`, `repairing`, `reassembling`, or `done`
This keeps the webcam off during `waiting`, `fragmented`, and `scanning`, then enables hand input only when the repair flow is expected to use hands.
In the current production repair flow, `inspected` uses a two-fists hold gesture to advance to `fragmented`. The hold must last one second and is independent from local object interaction distance once the mission is in the correct state. Keyboard input for the same transition is handled separately by the repair case trigger, so pressing `E` requires the case to be focused through the shared interaction system.
## Backend
The backend lives in `backend/` and exposes:
- `GET /health` for health checks
- `WS /ws` for frame input and hand tracking output
The Python process uses MediaPipe and the local model file:
```txt
backend/hand_landmarker.task
```
The backend sends normalized hand coordinates and landmarks. The frontend treats the values as screen-space inputs, then maps them into world space with the active Three.js camera.
## Frontend Data Shape
The shared types live in `src/types/handTracking/handTracking.ts`.
```ts
interface HandTrackingHand {
x: number;
y: number;
z: number;
landmarks: HandTrackingLandmark[];
handedness: string;
isFist: boolean;
score: number;
}
```
`x` and `y` are normalized camera coordinates. `z` is a relative depth value from MediaPipe, not an absolute world-space distance.
## Grab Targeting
The hand grab logic lives in `src/components/three/interaction/GrabbableObject.tsx`.
The object is moved toward the visual center of the hand. That center is computed from the bounding box of all landmarks:
```txt
centerX = (minX + maxX) / 2
centerY = (minY + maxY) / 2
```
Starting a grab uses a slightly wider virtual hit zone. Instead of raycasting only from one point, the code casts several rays around the hand center:
- center
- left
- right
- up
- down
If any ray hits the object while the object is within `INTERACTION_RADIUS`, the object enters hand-holding mode.
## Depth Handling
Because MediaPipe `z` is relative, the frontend captures the starting depth when the grab begins:
```txt
initialHandZ = hand.z
initialHoldDistance = hit.distance
```
While holding, the object distance from the camera is adjusted by the change in hand depth:
```txt
holdDistance = initialHoldDistance + (hand.z - initialHandZ) * sensitivity
```
The final hold distance is clamped between the configured grab minimum and maximum distances to avoid unstable movement.
## UI And Debug
The current debug UI includes:
- `HandTrackingDebugPanel` inside `DebugOverlayLayout` for status, usage, loaded glove model, server state, hand count, and fist state
- `HandTrackingVisualizer` for the SVG landmark wireframe fallback
- `HandTrackingGlove` for the left-hand `gant_l` and right-hand `gant_r` models in the R3F scene
- `r3f-perf` for render performance
- `lil-gui` for scene, camera, lighting, interaction, and grab controls
The hand tracking debug panel is a compact HTML grid outside the canvas. `Model loaded` displays the successfully loaded glove models. The SVG hand wireframe is only a fallback while models are loading or if a glove model fails to load.
## Glove Models
The current glove MVP uses `public/models/gant_l/model.gltf` and `public/models/gant_r/model.gltf`, which contain GLTF skins and armatures. Each model is positioned, oriented, and scaled from palm landmarks, then each finger bone chain is rotated toward the matching MediaPipe landmark chain.
The glove models are intentionally smaller than the raw SVG overlay so they do not dominate the camera view.
## Known Limitations
- Production usage is currently limited to repair mission steps that explicitly need hands.
- MediaPipe depth is relative and can be noisy.
- The virtual hit zone is an approximation based on multiple raycasts, not a real 3D collider.
- There is no smoothing layer for hand position or depth yet.
- The SVG hand visualization is a fallback, not the primary display when glove models load correctly.
- Finger bone animation is an approximate landmark-to-bone mapping; it still needs calibration for per-model twist, offsets, and smoothing.
+70
View File
@@ -0,0 +1,70 @@
# Target Architecture
This document describes the intended medium-term architecture for the project.
## Relationship To The Current Code
- `docs/technical/architecture.md` is the source of truth for what exists now.
- This document describes intended direction, not implemented behavior.
- If this document conflicts with the current implementation, the current implementation wins.
## Goals
- Keep `App.tsx` small and orchestration-oriented.
- Separate production world code from debug-only runtime paths.
- Keep one clear source of truth per concern.
- Grow gameplay systems incrementally instead of prebuilding empty architecture.
## Intended Layers
### App Layer
- `App.tsx` mounts the canvas scene and top-level HTML overlays.
- It should stay thin and avoid gameplay logic.
### World Layer
- `src/world/` should contain production scene composition and production scene objects.
- Expected responsibilities:
- world composition
- map, environment, lighting
- player controller
- production interaction anchors
- production post-processing, if needed
### Debug Layer
- Debug-only scenes and tooling should be isolated from the production world path.
- Expected responsibilities:
- `lil-gui`
- performance overlay
- scene helpers
- free camera and calibration controls
- debug test scenes used during development
### UI Layer
- `src/components/ui/` should contain player-facing HTML overlays.
- Candidate examples:
- crosshair
- loading flow
- mission HUD
- narrative overlays
### Gameplay Layer
- As the project grows, gameplay state can move toward a clearer orchestration layer.
- Likely concerns:
- missions
- zones
- cinematics
- dialogue
- audio
- interactions
## Rules
- Prefer direct, working code over speculative scaffolding.
- Shared types should stay close to their domain until they have multiple real consumers.
- Avoid creating new managers or service layers without an active runtime need.
- Debug-only runtime paths should be clearly marked and easy to remove when obsolete.
+188
View File
@@ -0,0 +1,188 @@
# Zustand Game State
This document explains how Zustand is used in the current project.
## Why Zustand Exists Here
The project needs one shared source of truth for the player's progression through the experience.
The current progression is split into main states:
| Main state | Role |
| ---------- | ------------------------------- |
| `intro` | Onboarding and opening sequence |
| `bike` | E-bike repair sequence |
| `pylone` | Power grid sequence |
| `ferme` | Vertical farm sequence |
| `outro` | Ending sequence |
Each main state can also own smaller sub state, such as the current mission step, dialogue audio, or completion flags.
Zustand is useful because React and React Three Fiber components can subscribe only to the state slice they need. When that slice changes, only the subscribed components re-render.
## Store Location
The game progression store lives here:
```txt
src/managers/stores/useGameStore.ts
```
The store is placed under `src/managers/stores/` because it belongs to the gameplay orchestration layer, not to a specific visual component.
## Managers vs Store
Managers are responsible for local runtime objects and imperative behavior.
Examples:
- `AudioManager` owns audio elements and sound pools.
- `InteractionManager` owns transient interaction handles and input-oriented behavior.
Managers can read from or write to the Zustand store when their local behavior needs to affect global gameplay progression.
The Zustand store is responsible for durable global state:
- current main state
- mission sub state
- progression flags
- dialogue/audio references
- state transitions
Rule of thumb:
- manager = runtime objects, side effects, and local imperative logic
- store = global gameplay state that UI or world components can subscribe to
## Current Shape
The store exposes:
- `mainState`: the active game phase
- `intro`: intro-specific state
- `bike`: e-bike mission state
- `pylone`: power grid mission state
- `ferme`: farm mission state
- `outro`: ending state
- actions for direct updates and progression updates
The mission steps currently use this sequence:
```ts
"locked" |
"waiting" |
"inspected" |
"fragmented" |
"scanning" |
"repairing" |
"reassembling" |
"done";
```
## Reading State In Components
Use selectors to read only what the component needs.
```tsx
import { useGameStore } from "@/managers/stores/useGameStore";
export function Example(): React.JSX.Element {
const mainState = useGameStore((state) => state.mainState);
return <p>Current state: {mainState}</p>;
}
```
This is better than reading the whole store, because the component re-renders only when `mainState` changes.
## Updating State
Prefer explicit actions from the store.
```ts
const advanceGameState = useGameStore((state) => state.advanceGameState);
advanceGameState();
```
For development and debug tooling, direct setters also exist:
```ts
const setMainState = useGameStore((state) => state.setMainState);
setMainState("bike");
```
Direct setters are useful for debug panels, but production gameplay should prefer business actions such as `advanceGameState`, `completeBike`, or `completePylone`.
Mission gameplay that can target `bike`, `pylone`, or `ferme` should prefer the generic mission actions:
```ts
const setMissionStep = useGameStore((state) => state.setMissionStep);
const completeMission = useGameStore((state) => state.completeMission);
setMissionStep("bike", "inspected");
completeMission("bike");
```
This keeps reusable gameplay components such as repair flows from duplicating mission-specific branches like `setBikeState`, `setPyloneState`, and `setFermeState`.
## World Integration
`src/world/GameStageContent.tsx` subscribes to `mainState` and mounts stage-specific content.
For repair missions, it mounts the reusable `RepairGame` component with a mission id:
```tsx
<RepairGame mission="bike" position={[8, 0, -6]} />
```
`RepairGame` reads the active mission step from the store and writes transitions through generic actions such as `setMissionStep` and `completeMission`. Shared repair ids, mission steps, and runtime guards live in `src/types/gameplay/repairMission.ts` so static mission config does not depend on the Zustand store. The production repair flow currently supports `waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done -> next mission` state transitions.
Mission-specific behavior stays in `src/data/gameplay/repairMissions.ts`: each mission can define its broken nodes, placeholder targets, scan duration, and reassembly duration without adding mission branches to `RepairGame`.
That means the scene can progressively move toward this pattern:
```tsx
switch (mainState) {
case "intro":
return <IntroContent />;
case "bike":
return <BikeContent />;
case "pylone":
return <PyloneContent />;
case "ferme":
return <FarmContent />;
case "outro":
return <OutroContent />;
}
```
In React Three Fiber, mounting and unmounting JSX controls what appears in the Three.js scene. When a state-specific component disappears from JSX, React removes it from the scene.
## UI Integration
`src/components/ui/GameUI.tsx` groups the HTML overlays used by the playable route.
Current overlays:
- `DebugOverlayLayout`: debug-only overlay shown with `?debug`, including the `GameStateDebugPanel` progression panel
- `GameStateDebugPanel`: compact debug UI for viewing and switching main/sub states, stepping backward or forward, and resetting the store
- `Crosshair`: player aiming helper
- `InteractPrompt`: interaction prompt
- `RepairMovementLockIndicator`: player-facing indicator shown while repair steps temporarily disable movement
`src/pages/page.tsx` should stay thin and mount only the canvas and `GameUI`.
## Regression Rules
- Do not store per-frame values in Zustand.
- Use `useRef` for high-frequency mutable values such as player velocity, temporary vectors, or animation-loop data.
- Use selectors instead of reading the whole store in components.
- Keep gameplay transitions inside store actions when possible.
- Keep debug-only controls behind `?debug`.
- Add new state only when a real runtime feature needs it.
## Next Steps
Move repair validation into mission data once each mission has distinct broken module nodes, replacement assets, and completion events.
+166
View File
@@ -0,0 +1,166 @@
# Editor User Guide
The map editor is available at `/editor`. It is a browser-based tool for inspecting and adjusting the objects listed in `public/map.json`.
## Purpose
Use the editor when you need to move, rotate, or scale existing map objects without editing JSON by hand.
The editor reads the same map data as the runtime scene:
- `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.
- 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:
| Field | Description |
| ---------- | ------------------------------------------------- |
| `name` | Model folder name in `public/models/{name}` |
| `type` | Object category |
| `position` | Object position as `[x, y, z]` |
| `rotation` | Object rotation as `[x, y, z]`, expressed radians |
| `scale` | Object scale as `[x, y, z]` |
## Editing Workflow
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.
## Controls
| Action | Input |
| -------------------- | -------------------------- |
| Select object | Click object |
| Deselect | `Esc` or click empty space |
| Translate mode | `T` |
| Rotate mode | `R` |
| Scale mode | `S` |
| Undo | `Ctrl+Z` |
| Redo | `Ctrl+Y` |
| Locked view movement | `WASD`, `ZQSD`, arrows |
| Move up | `Space` |
| Move down | `Shift` |
## View Mode
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.
## JSON Inspector
The side panel includes a raw JSON inspector:
- 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.
This is useful for checking numeric transform values before saving or exporting.
## Saving Changes
### Export 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` 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.
## Editing Dialogue Subtitles
The side panel also includes dialogue tools for the dialogue manifest and SRT subtitles.
### Dialogue Manifest
Use the `Dialogues` panel to edit `public/sounds/dialogue/dialogues.json` without opening the JSON file manually.
Available actions:
- `Reload` reloads the manifest from disk.
- `Add` creates a local dialogue entry for the current voice and assigns the next available SRT cue index.
- `Save` writes the manifest through the local Vite dev server.
- `Preview dialogue` plays the selected dialogue and shows subtitles in the editor overlay.
- `Create FR SRT cue` creates the matching French SRT cue if it is missing.
- `Delete dialogue` removes the selected entry locally.
After using `Add`, save the manifest to keep the new dialogue entry. The generated SRT cue is written immediately to the French SRT file, but the dialogue manifest is still only local until `Save` is clicked.
New dialogue audio paths start as placeholders such as `/sounds/dialogue/new_dialogue_24.mp3`. Replace them with real MP3 paths before validating the final asset set.
### SRT Editor
Use the `SRT` panel to edit one subtitle file at a time.
1. Choose a voice: `narrateur`, `fermier`, or `electricienne`.
2. Choose a language: `FR` or `EN`.
3. Edit the SRT text directly in the textarea.
4. Use the audio preview to check the selected dialogue.
5. Use `Set start`, `Set end`, `-100ms`, and `+100ms` to adjust the selected cue timing against the audio.
6. Use `Save SRT` during local development, or `Export SRT` to download the file manually.
Each SRT file belongs to one voice, not one dialogue. Cue indexes must match the `subtitleCueIndex` values referenced by the dialogue manifest.
## Validating Dialogue Assets
Use `Validate` in the SRT panel to check the dialogue manifest and linked assets.
The validation checks:
- `public/sounds/dialogue/dialogues.json`
- referenced dialogue audio files
- French SRT files
- subtitle cue indexes referenced by the manifest
Missing English SRT files are warnings, not errors, because the runtime falls back to French subtitles. This is intentional until the English translation workflow is ready.
## Editing Cinematics
Use the `Cinematics` panel to edit `public/cinematics.json`.
Each cinematic contains:
- an `id`
- an optional global `timecode`
- two or more camera keyframes
- optional dialogue cues synchronized to the cinematic timeline
Camera keyframes define:
- `time`: seconds relative to the cinematic start
- `position`: camera position `[x, y, z]`
- `target`: point the camera looks at `[x, y, z]`
Dialogue cues define:
- `time`: seconds relative to the cinematic start
- `dialogueId`: an entry from `public/sounds/dialogue/dialogues.json`
Available actions:
- `Reload` reloads the cinematic manifest from disk.
- `Add` creates a new local cinematic with two camera keyframes.
- `Save` writes `public/cinematics.json` through the local Vite dev server.
- `Preview cinematic` plays the selected camera animation in the editor canvas.
- `Add keyframe` and `Remove` edit the camera path.
- `Add dialogue` and `Remove` edit dialogue cues linked to the cinematic.
- `Delete cinematic` removes the selected cinematic locally.
Cinematic dialogue cues are the preferred way to synchronize a dialogue with a cinematic. Avoid also giving the same dialogue a global `timecode`, or it can be triggered twice.
## Current Limitations
- The editor only modifies existing nodes.
- It does not create or delete objects.
- It does not edit model files or textures.
- It does not provide production persistence.
- Fallback cubes indicate missing models; they are editor placeholders, not exported assets.
- SRT saving is a local Vite dev-server helper, not a production backend feature.
- Dialogue and cinematic saves are local Vite dev-server helpers, not production backend features.
+111 -2
View File
@@ -1,3 +1,112 @@
# Features
# Implemented Features
TODO: Documenter les fonctionnalités du jeu.
This document lists features that are implemented in the current codebase.
## Scene
- Fullscreen React Three Fiber scene
- Main map scene loaded from `public/map.json` and matching `public/models/{name}/model.glb` or `model.gltf` assets
- Minimal fullscreen scene loading overlay for 3D scenes, with a global progress bar used by the production map, debug physics scene, and editor scene
- Debug physics test scene selectable from the debug panel, including grab/trigger tests, an animated model preview, and separate repair playground zones for `bike`, `pylone`, and `ferme`
- Rapier physics context available for production stage gameplay objects
- Ambient and directional lighting
- Environment background setup
## Player
- Player camera mode
- Pointer lock mouse look
- Movement with `ZQSD`
- Jumping
- Movement lock during active repair steps, with an on-screen indicator while keeping trigger interactions available
- Octree-based collision against dedicated map collision nodes, currently scoped to `terrain`
## Interactions
- Focus detection by distance and raycast
- Trigger interactions activated with `E`
- Grab interactions activated with the primary mouse button
- Physics-backed gameplay objects can be mounted inside stage content without replacing player octree collision
- Interaction prompt shown for trigger interactions
## Repair Gameplay
- Reusable production `RepairGame` mounted for `bike`, `pylone`, and `ferme` mission states
- Debug physics playground mounts the same reusable `RepairGame` in `Bike`, `Pylone`, and `Farm` zones so each state can be tuned with isolated positioning before moving placement into the production map
- Repair mission config shared through `src/data/gameplay/repairMissions.ts`, including per-mission broken nodes, placeholder targets, scan timing, and reassembly timing
- Repair-game flow supports `waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done -> next mission` with `.webm` prompts, repair case spawn/opening/exit, focused repair-case view, movement lock indicator during active repair, repair-case trigger interaction, case placeholder traversal, snap-to-placeholder placement, broken-part deposit feedback, `E`, two-fists hold input, exploded and inverse reassembly transitions, completion particles, per-part scan visuals, persistent red broken-part markers, centered broken-part UI videos, multiple grabbable replacement choices, correct-part install validation feedback, and mission completion
## Audio
- Category-based volumes for music, SFX, and dialogue
- Looped background music playback through `AudioManager`
- One-shot sound playback for SFX and dialogue, with simple per-sound pooling
- Optional stereo pan for one-shot sounds
## Dialogue And Subtitles
- Dialogue manifest in `public/sounds/dialogue/dialogues.json`
- Dialogue audio loaded from `public/sounds/dialogue/`
- One SRT subtitle file per voice and language
- French subtitle fallback when the selected language file is missing
- Runtime subtitle overlay with speaker-specific colors
- Timecoded dialogue trigger support for dialogue entries that define `timecode`
- Dialogue queueing to avoid overlapping dialogue playback
## Cinematics
- Cinematic manifest in `public/cinematics.json`
- Timecoded cinematic trigger support
- GSAP camera keyframe playback
- Optional dialogue cues synchronized to cinematic timelines
- Player input lock while a cinematic is active
## Game Options Menu
- `Esc` opens and closes the in-game options menu
- Music, SFX, and dialogue volume sliders
- Subtitle visibility toggle
- Subtitle language choice between French and English
- Repair runtime choice between local JavaScript and Python server mode
- Quit action that clears browser-accessible cookies and returns to `/`
## Debug Tooling
- `?debug` query param enables the debug panel
- `lil-gui` controls for camera mode, scene mode, `R3F Perf`, `Debug Overlay`, and interaction tuning
- Compact debug overlay for game state controls and hand tracking status
- Debug game-state mission switching unlocks locked repair missions at `waiting` for faster testing
- Debug scene helpers
- Free debug camera
- `r3f-perf` overlay
## Map Editor
- `/editor` route for inspecting and editing `public/map.json`
- Automatic loading of `public/map.json` when available
- Folder upload fallback when `map.json` is missing
- Rendering of available `public/models/{name}/model.glb` or `model.gltf` assets
- Fallback cubes for nodes whose model is missing
- Object selection by click
- Transform modes for translate, rotate, and scale
- Keyboard shortcuts for `T`, `R`, `S`, `Esc`, undo, and redo
- Player-style navigation mode with `WASD`, `ZQSD`, arrow keys, `Space`, and `Shift`
- JSON export for downloading the edited map
- Dev-server save endpoint for writing changes back to `public/map.json`
- SRT editor for dialogue subtitles
- Audio preview and timing helpers for SRT cues
- Dev-server save endpoint for SRT files
- Dialogue manifest editor with preview and assisted French SRT cue creation
- Cinematic manifest editor with camera keyframes, dialogue cues, and canvas preview
- Dialogue manifest validation from the editor UI
## Not Implemented Yet
- complete mission system
- zone system
- full cinematic system beyond current timecode prototype
- gameplay-triggered dialogue branches beyond current prototype triggers
- loading flow
- minimap and mission HUD
- full production separation between gameplay and debug scenes
- production backend persistence for editor saves
+110
View File
@@ -0,0 +1,110 @@
# Main Feature
This document explains the current repair-game flow in La-Fabrik.
## What It Does
The main feature is a reusable repair flow mounted in the production game scene. It lets the player approach the active mission object, inspect it, fragment it, scan the broken part, install the correct replacement, validate completion, and move to the next mission state.
The current user flow is:
1. Enter a mission state such as `bike`, `pylone`, or `ferme`.
2. Move close to the active repair object in the game scene.
3. Aim at the object and press the interaction key when prompted.
4. The mission step moves from `waiting` to `inspected`.
5. The repair case appears near the mission object, the player movement controls are locked, and the case can float when the player approaches it.
6. Aim at the repair case and press `E`, or hold both fists closed for one second, to move from `inspected` to `fragmented`.
7. The mission object uses an exploded-model transition, then moves to `scanning`.
8. The scan visual moves across the fragmented model one part at a time and keeps a red marker plus the `cassé.webm` prompt centered on any configured broken part once it has been found.
9. In `repairing`, the case opens in a larger focused view and several grabbable replacement parts appear on the case placeholders.
10. Move the correct replacement part close to a placeholder. When released near a placeholder, it snaps into place with a short animation.
11. Move each scanned broken part into a compatible placeholder so the damaged parts are stored in the case.
12. Press `E` on the green install target to move to `reassembling`. Wrong parts turn the target red and cannot finish the repair.
13. The exploded object animates back into its assembled form with completion particles, then moves to `done` and restores player movement controls.
14. Press `E` on the completion target. The repair case closes, returns to the ground, disappears, then `completeMission` moves to the next mission or to `outro` after `ferme`.
## Why It Matters
This feature validates the repair loop before a full mission system exists. It tests whether repair objects, physical proximity, model selection, audio feedback, and exploded model visualization can work together in the 3D scene.
## Current Behavior
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, player movement is locked while the repair sequence is active, and a small HTML indicator confirms that movement is temporarily unavailable. When the player is close enough, the existing case model floats upward and rotates gently to signal interactivity.
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. Player movement stays locked through `inspected`, `fragmented`, `scanning`, `repairing`, and `reassembling`, while trigger interactions remain available. In `reassembling`, the exploded model animates back into its assembled position with green completion particles before the flow moves to `done`. In `done`, player movement is available again and 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.
## Key Files
- `src/world/GameStageContent.tsx` mounts production `RepairGame` instances for `bike`, `pylone`, and `ferme`.
- `src/components/three/gameplay/RepairCompletionStep.tsx` renders the final repaired object, completion target, case exit animation, and mission UI prompt.
- `src/components/three/gameplay/RepairGame.tsx` composes the reusable production repair flow.
- `src/components/three/gameplay/RepairBrokenPartHighlight.tsx` renders the red halo and wire marker around detected broken parts during scanning.
- `src/components/three/gameplay/RepairBrokenPartPrompt.tsx` centers the configured broken UI video on detected broken parts during scanning.
- `src/components/three/gameplay/RepairInspectionObject.tsx` handles the `waiting` inspection interaction.
- `src/components/three/gameplay/RepairMissionCase.tsx` renders the mission repair case after inspection.
- `src/components/three/gameplay/RepairRepairingStep.tsx` renders grabbable replacement choices, grabbable scanned broken parts, placeholder placement markers, snap placement behavior, correct-part and broken-part placement validation, and the install trigger in `repairing`.
- `src/components/three/gameplay/RepairReassemblyStep.tsx` renders the inverse fragmentation animation before the final completion step.
- `src/components/three/gameplay/RepairCompletionParticles.tsx` renders the green completion particles during reassembly.
- `src/components/three/gameplay/RepairPromptVideo.tsx` renders `.webm` prompts inside the 3D scene.
- `src/components/three/gameplay/RepairScanSequence.tsx` keeps the exploded model visible and advances the scan from part to part.
- `src/components/three/gameplay/RepairScanVisual.tsx` renders the scan halo and scan line around the active part.
- `src/components/ui/RepairMovementLockIndicator.tsx` renders the HTML indicator shown while repair movement is locked.
- `src/hooks/gameplay/useRepairFragmentationInput.ts` handles the `inspected -> fragmented` two-fists input and can optionally bind keyboard input for non-trigger flows.
- `src/hooks/gameplay/useRepairMissionStep.ts` reads the active mission step from the game store.
- `src/hooks/gameplay/useRepairMovementLocked.ts` exposes the shared repair movement-lock rule used by the player controller and UI indicator.
- `src/hooks/handTracking/useBothFistsHold.ts` detects the reusable two-fists hold gesture.
- `src/components/three/gameplay/RepairCaseModel.tsx` renders and animates the case model, and exposes `placeholder_*` transforms when the GLTF provides them.
- `src/components/three/models/ExplodableModel.tsx` renders selectable models with split/exploded visualization.
- `src/data/gameplay/repairCaseConfig.ts` stores repair case model, sound, and animation constants.
- `src/data/gameplay/repairGameConfig.ts` stores repair flow timing constants.
- `src/data/gameplay/repairMissions.ts` stores reusable repair mission config for `bike`, `pylone`, and `ferme`.
- `src/managers/stores/useGameStore.ts` stores mission progression state and generic mission step helpers.
- `src/types/gameplay/repairMission.ts` contains shared repair mission ids, mission steps, and guards used by the store, data config, debug UI, and gameplay components.
## Runtime Requirements
The production repair flow currently requires:
- the active `mainState` to be one of `bike`, `pylone`, or `ferme`
- `GameStageContent` mounted inside the game scene Rapier `Physics` boundary
- model assets available under `public/models/`
- sound assets available under `public/sounds/`
Frontend command:
```bash
npm run dev
```
Debug URL for state switching and inspection:
```txt
http://localhost:5173/?debug
```
The debug physics scene keeps the existing grab, trigger, and animated model tests, and also exposes separate `Bike`, `Pylone`, and `Farm` repair playground zones. Use the debug game-state panel to switch `mainState`; selecting a locked repair mission in that panel opens it at `waiting`, and the matching repair zone mounts the same reusable `RepairGame` flow with that mission's model, broken parts, replacement parts, prompts, and timings.
## Related Hand Tracking
Hand tracking can move grabbable physics objects with webcam input in debug scenes. In the production repair flow, it is also used for the `inspected -> fragmented` transition through the two-fists hold gesture.
For hand tracking, run the Python backend separately:
```bash
source backend/.venv/bin/activate
python -m backend.main
```
## Current Limitations
- The reusable production `RepairGame` currently covers `waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done -> next mission`.
- Mission progression is wired through Zustand using `completeMission` at the end of each repair.
- There is no central `GameManager` in this branch.
- Hand tracking is available for the two-fists input and grabbable repair parts; case interaction and final installation still use the shared `E` trigger path.
- The repair-game content is configured statically in `src/data/gameplay/`.
+2
View File
@@ -2,6 +2,7 @@ import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import prettierRecommended from "eslint-plugin-prettier/recommended";
import tseslint from "typescript-eslint";
import { defineConfig, globalIgnores } from "eslint/config";
@@ -20,4 +21,5 @@ export default defineConfig([
globals: globals.browser,
},
},
prettierRecommended,
]);
+2
View File
@@ -0,0 +1,2 @@
[phases.setup]
nixPkgs = ["nodejs"]
+1832 -503
View File
File diff suppressed because it is too large Load Diff
+21 -5
View File
@@ -3,6 +3,9 @@
"private": true,
"version": "0.0.1",
"type": "module",
"engines": {
"node": ">=20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
@@ -11,18 +14,24 @@
"format": "prettier --write .",
"format:check": "prettier --check .",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
"typecheck": "tsc -b"
},
"dependencies": {
"@mediapipe/tasks-vision": "^0.10.35",
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.0",
"@react-three/postprocessing": "^3.0.4",
"@react-three/fiber": "^9.6.1",
"@react-three/rapier": "^2.2.0",
"@tanstack/react-router": "^1.168.25",
"gsap": "^3.15.0",
"lil-gui": "^0.21.0",
"lucide-react": "^1.11.0",
"r3f-perf": "^7.2.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"three": "^0.183.2"
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
"three": "0.182.0",
"zustand": "^5.0.12"
},
"devDependencies": {
"@eslint/js": "^9.39.4",
@@ -36,10 +45,17 @@
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"lil-gui": "^0.21.0",
"prettier": "^3.8.2",
"typescript": "~6.0.2",
"typescript-eslint": "^8.58.0",
"vite": "^8.0.4"
},
"overrides": {
"@react-three/rapier": {
"@dimforge/rapier3d-compat": "0.19.1"
},
"r3f-perf": {
"@react-three/drei": "$@react-three/drei"
}
}
}
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.
+27
View File
@@ -0,0 +1,27 @@
{
"version": 1,
"cinematics": [
{
"id": "intro_overview",
"timecode": 0,
"dialogueCues": [
{
"time": 0,
"dialogueId": "narrateur_bienvenueaaltera"
}
],
"cameraKeyframes": [
{
"time": 0,
"position": [8, 5, 12],
"target": [0, 2, 0]
},
{
"time": 4,
"position": [12, 4, -6],
"target": [10, 1.4, -8]
}
]
}
]
}
+4587
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.
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