87 Commits

Author SHA1 Message Date
Tom Boullay 88194828ce fix: merge
🔍 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-05-11 17:01:02 +02:00
Tom Boullay b0bb127459 Merge pull request #7 from La-Fabrik-Durable/feat/main-feature
Feat/main feature
2026-05-08 01:56:18 +02:00
Tom Boullay f84aa748cd Update HandTrackingGlove.tsx 2026-05-08 00:54:42 +01:00
Tom Boullay a1ff534aa7 add hand tracking source debug switch 2026-05-06 23:23:10 +01:00
Tom Boullay 5824ae162a add browser hand tracking source 2026-05-06 23:23:04 +01:00
Tom Boullay d7dd76a853 fix hand tracking glove root transform 2026-05-06 23:22:56 +01:00
Tom Boullay 9a1849b0f8 fix: flickering hands 2026-05-06 23:16:58 +01:00
Tom Boullay 74a901a48b fix; distance grab objetc 2026-05-02 21:34:23 +02:00
Tom Boullay 584a68bce6 add hand tracking glove bone mapping 2026-05-02 11:38:02 +02:00
Tom Boullay 94cea80af4 fix hand tracking glove fallback and loading 2026-05-02 11:35:28 +02:00
Tom Boullay 0b950a4557 fix hand tracking glove rendering 2026-05-02 11:32:00 +02:00
Tom Boullay 442bfbc8d4 update: remove old model elec 2026-05-02 11:02:51 +02:00
Tom Boullay 5bd0680b64 fix three and rapier warning dependencies 2026-05-02 11:01:50 +02:00
Tom Boullay 04ece5b1d2 fix electricienne debug model loading 2026-05-02 10:58:00 +02:00
Tom Boullay 8fbb2e9428 feat add left hand tracking glove model 2026-05-02 00:14:56 +02:00
Tom Boullay 2aa662669f feat add model loading diagnostics 2026-05-02 00:14:47 +02:00
Tom Boullay 4031f0de87 cleaning repo models 2026-05-01 23:54:48 +02:00
Tom Boullay e6d78d203a update: models made them working 2026-05-01 23:45:58 +02:00
Tom Boullay 50ddd35979 update: debug overlay layout controls 2026-05-01 23:39:04 +02:00
Tom Boullay 6f264969ee Update TestMap.tsx 2026-04-30 16:29:56 +02:00
Tom Boullay 106b68d487 connect repair gameplay to zustand progression 2026-04-30 16:25:54 +02:00
Tom Boullay aaedd9e3a4 fix: models 2026-04-30 15:48:45 +02:00
Tom Boullay 1b50fe4f5b Merge branch 'develop' into feat/main-feature 2026-04-30 15:48:35 +02:00
Tom Boullay 4bc385fb09 Update arbre.bin 2026-04-30 15:09:27 +02:00
Tom Boullay 65450d9208 Merge branch 'design' into feat/main-feature 2026-04-30 15:09:22 +02:00
Tom Boullay 9fa4439de8 Merge pull request #11 from La-Fabrik-Durable/feat/zustand
Feat/zustand
2026-04-30 15:07:44 +02:00
Tom Boullay c7128d58ed resolve three component type exports 2026-04-30 15:06:26 +02:00
Tom Boullay 0858525c44 address zustand progression review feedback 2026-04-30 14:59:41 +02:00
Tom Boullay e7bb4d2b63 clarify managers and zustand store responsibilities 2026-04-30 14:38:07 +02:00
Tom Boullay 0f845f28c5 add zustand game state 2026-04-30 14:29:29 +02:00
Tom Boullay d740e2a436 add : some sounds 2026-04-30 14:25:36 +02:00
Tom Boullay cf20aa8ea4 connect game progression state to world 2026-04-30 14:24:59 +02:00
Tom Boullay 85b91e63cb add zustand game progression store 2026-04-30 14:04:01 +02:00
Tom Boullay 9998fb65f8 chore: align repo health checks and docs 2026-04-30 13:51:39 +02:00
Tom Boullay c5b672cdb5 add: prettier eslint 2026-04-30 13:36:07 +02:00
Tom Boullay fda70bade2 refactor: clean architecture and remove unused code 2026-04-30 13:33:28 +02:00
Tom Boullay c698b9ef78 refactor: split hooks types and utils by domain 2026-04-30 11:49:18 +02:00
Tom Boullay 081e87c96d refactor: organize three components by domain 2026-04-30 11:35:53 +02:00
Tom Boullay ab8376b03e fix: correct repair case open state rotation 2026-04-30 10:42:47 +02:00
Tom Boullay d5f537eb8b feat: add game music loop and mallette sounds 2026-04-30 10:06:00 +02:00
Tom Boullay 475a4c7c5e refactor: prepare main feature gameplay object and use GLB sky model 2026-04-30 10:02:00 +02:00
Tom Boullay d7b77b2f44 feat: expand main feature model catalog 2026-04-29 23:30:40 +02:00
Tom Boullay 793997ed06 feat: add main feature module selection 2026-04-29 23:30:31 +02:00
Tom Boullay 72e4047420 feat: add openable repair case model 2026-04-29 23:30:22 +02:00
Tom Boullay 638e10a132 chore: track bin assets with lfs 2026-04-29 17:07:44 +02:00
Tom Boullay 4e594d36fa 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 3a0639bdaa feat: support glb model assets 2026-04-29 16:18:24 +02:00
Tom Boullay d0361c0a38 Merge branch 'develop' into feat/main-feature 2026-04-29 15:01:17 +02:00
Tom Boullay 471424f83d update: docs 2026-04-29 13:01:10 +02:00
math-pixel 60c966be93 Merge branch 'design' of https://github.com/La-Fabrik-Durable/La-Fabrik into design 2026-04-29 12:16:51 +02:00
math-pixel 62deb6e322 add object 2026-04-29 12:00:11 +02:00
math-pixel bc3f28bdb2 feat: add tree 2026-04-29 11:57:43 +02:00
Tom Boullay 2a3b088294 fix: position perf panel beside debug gui 2026-04-29 11:56:46 +02:00
Tom Boullay 5627373752 feat: improve hand grab targeting 2026-04-29 11:40:17 +02:00
Tom Boullay 5b14a1d971 fix: decouple hand tracking from crosshair focus 2026-04-29 11:13:11 +02:00
Tom Boullay 7a3dd976e7 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 fffabc01c2 feat: improve fist grab depth tracking 2026-04-29 10:52:35 +02:00
Tom Boullay c5bf10a7fb update: upload-gltf add a new model -> hand_l
📦 Model
   model.gltf

🎨 Textures (color)
   color_gant.png (compressed)

🧭 Textures (normal)
   normal_gant.png (compressed)

🧱 Textures (orm)
   orm_gant.png (compressed)

🧩 Assets
   hanf_l.bin
2026-04-29 10:45:16 +02:00
Tom Boullay d9fc9d0a15 feat: grab objects with closed fist raycast 2026-04-29 10:40:48 +02:00
Tom Boullay d4dd0fa283 refactor: replace pinch gesture with fist gesture 2026-04-29 10:34:11 +02:00
Tom Boullay e42c06b888 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 3230b644e4 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 3503ff52ed fix: guard hand landmark visualization 2026-04-29 09:52:46 +02:00
Tom Boullay a8ece3a448 Create model.gltf 2026-04-29 09:05:04 +02:00
Tom Boullay 3f1e15f616 Merge branch 'design' into feat/main-feature 2026-04-29 09:05:00 +02:00
Tom Boullay b0f0f3cb91 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 35cd3c7c64 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 cfa1bd9e16 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
Tom Boullay b9a3fbfc99 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 9d814c9924 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 eb875068eb 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 e8a5a44218 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 9c12c7a9e5 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 1907f2623b 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 cf5be3d45d 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
Tom Boullay 9ff75e0516 fix: persist debug modes and skip missing map models 2026-04-28 16:35:33 +02:00
Tom Boullay 3b8c59db87 Merge branch 'develop' into feat/main-feature 2026-04-28 16:27:05 +02:00
Tom Boullay 5e0125e05a 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 b81f85cd50 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 8f1a553601 update: upload-gltf add a new model -> gant
📦 Model
   model.gltf

🎨 Textures (color)
   color_gant.png (compressed)

🧭 Textures (normal)
   normal_gant.png (compressed)

🧱 Textures (orm)
   orm_gant.png (compressed)

🧩 Assets
   gant.bin
2026-04-28 16:01:41 +02:00
math-pixel e3162d6588 Merge pull request #9 from La-Fabrik-Durable/feat/deploy-test
Feat/deploy test
2026-04-28 10:45:25 +02:00
Tom Boullay 7dea0f99a8 add: stylesheet 2026-04-28 09:07:56 +02:00
Tom Boullay 06e59a972f docs: clarify backend virtual environment setup 2026-04-27 17:11:08 +02:00
Tom Boullay 149f9aa26c clean: package json 2026-04-27 16:27:57 +02:00
Tom Boullay e25152b3e5 feat move debug cube with remote hand tracking 2026-04-27 16:07:54 +02:00
Tom Boullay 641d2f8871 feat add remote hand tracking backend 2026-04-27 15:49:02 +02:00
Tom Boullay 2b6bcc4d92 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
828 changed files with 17382 additions and 1166 deletions
+2 -1
View File
@@ -24,7 +24,8 @@ You are working on **La Fabrik**, an interactive 3D web experience built with Re
## Current Architecture Rules
- Scene objects live in `src/world/` and `src/components/3d/`.
- 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/`.
+8 -6
View File
@@ -58,19 +58,18 @@ if (debug.active) {
r3f-perf is loaded only in debug mode to avoid dependency issues in production:
```tsx
// src/utils/debug/DebugPerf.tsx
import { Suspense, lazy } from "react";
import { Debug } from "@/utils/debug/Debug";
import { useShowDebugPerf } from "@/hooks/debug/useShowDebugPerf";
const Perf = lazy(() => import("r3f-perf").then((m) => ({ default: m.Perf })));
export function DebugPerf() {
const debug = Debug.getInstance();
if (!debug.active) return null;
const showDebugPerf = useShowDebugPerf();
if (!showDebugPerf) return null;
return (
<Suspense fallback={null}>
<Perf position="top-left" />
<Perf position="top-right" />
</Suspense>
);
}
@@ -89,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
+19 -16
View File
@@ -28,14 +28,17 @@ 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. |
| Manager | File | Role |
| -------------------- | ------------------------------------ | ----------------------------------------------------------------------------- |
| `AudioManager` | `src/managers/AudioManager.ts` | Music and SFX playback. |
| `InteractionManager` | `src/managers/InteractionManager.ts` | Focus, nearby, trigger, grab, and hand-grab interaction state. |
| `GameManager` | target-state only | Future single source of truth for phase, zone, mission, input lock, dialogue. |
| `CinematicManager` | target-state only | Future GSAP timeline orchestrator. |
| `ZoneManager` | target-state only | Future zone entry/exit detection and LOD triggers. |
## 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
+2 -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
@@ -21,4 +22,4 @@
# Video (cinematics)
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text
+4 -4
View File
@@ -74,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"
+6 -1
View File
@@ -1,9 +1,14 @@
# Dependencies
node_modules/
.venv/
backend/.venv/
__pycache__/
*.pyc
# Build
dist/
dist-ssr/
.vite/
*.local
# Environment
@@ -37,4 +42,4 @@ Thumbs.db
# 3D Assets Cache (drei, GLTFJSX)
.drei/
.glitchdrei-cache/
.glitchdrei-cache/
+43 -58
View File
@@ -24,7 +24,6 @@ Built with React, Three.js, and Vite. Runs in the browser, no installation requi
| [@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/) |
| [@react-three/postprocessing](https://github.com/pmndrs/postprocessing) |
| [GSAP](https://gsap.com/docs/v3/Installation/) |
### Performance & Effects
@@ -48,74 +47,60 @@ la-fabrik/
│ └── sounds/
└── src/
├── world/ # Single persistent 3D world
│ ├── World.tsx # Main scene composition
│ ├── Map.tsx # Base map, always mounted
├── world/ # Persistent 3D world composition
│ ├── World.tsx # Active scene composition
│ ├── GameMap.tsx # Map loading and octree collision
│ ├── 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/
│ ├── EventEmitter.ts # Simple typed pub/sub utility
│ ├── Sizes.ts # Viewport size tracking
│ ├── Time.ts # Animation frame timing utility
── debug/ # Dev-only tools and scene inspection
├── Debug.ts # Global lil-gui manager
│ ├── DebugPerf.tsx # r3f-perf overlay mounted in Canvas
│ ├── isDebugEnabled.ts # Debug query-string helper
│ └── scene/
│ ├── DebugHelpers.tsx # Grid + axes helpers shown in debug mode
│ └── DebugCameraControls.tsx # Free debug camera for map inspection
├── hooks/
│ └── debug/
│ ├── useCameraMode.ts
│ ├── useDebugFolder.ts
│ ├── useDebugStore.ts
│ └── useSceneMode.ts
│ ├── 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
├── App.tsx # Canvas bootstrap
└── main.tsx
```
+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
+51
View File
@@ -0,0 +1,51 @@
# 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
│ ├── RepairCaseObject.tsx
│ ├── RepairGameZone.tsx
│ └── RepairModuleSlot.tsx
├── interaction/
│ ├── GrabbableObject.tsx
│ ├── InteractableObject.tsx
│ └── TriggerObject.tsx
├── models/
│ └── ExplodableModel.tsx
└── world/
└── SkyModel.tsx
```
+54 -16
View File
@@ -4,29 +4,33 @@ This document describes the code that exists today in the repository.
## Runtime Structure
- `src/App.tsx` mounts the `Canvas`, the 3D `World`, the debug perf overlay, and the HTML overlays.
- `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/world/Map.tsx` loads the main map model and builds the collision octree.
- `src/world/debug/TestScene.tsx` provides a debug-oriented interaction and physics scene.
- `src/world/player/PlayerComponent.tsx` mounts the camera and controller.
- `src/world/GameMap.tsx` loads map nodes from `public/map.json`, resolves available models, and builds the collision octree.
- `src/world/debug/TestMap.tsx` provides a debug-oriented interaction and physics map.
- `src/world/player/Player.tsx` mounts the camera and controller.
- `src/world/player/PlayerController.tsx` owns pointer lock movement, jump handling, and interaction input.
## Interaction Model
- `src/stateManager/InteractionManager.ts` is the current interaction state source.
- `src/components/3d/InteractableObject.tsx` handles focus detection through distance and raycasting.
- `src/components/3d/TriggerObject.tsx` implements trigger-style interactions.
- `src/components/3d/GrabbableObject.tsx` implements hold-and-release interactions.
- `src/hooks/useInteraction.ts` exposes the interaction snapshot to React UI.
- `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/stateManager/AudioManager.ts` currently provides pooled one-shot sound playback.
- `src/managers/AudioManager.ts` currently provides pooled one-shot sound playback and looped music playback.
- Trigger interactions may play audio directly through `AudioManager`.
## Debug System
@@ -34,14 +38,48 @@ This document describes the code that exists today in the repository.
- 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/utils/debug/DebugPerf.tsx` lazily mounts `r3f-perf` in debug mode.
- `src/utils/debug/scene/DebugHelpers.tsx` mounts debug helpers.
- `src/utils/debug/scene/DebugCameraControls.tsx` mounts the free debug camera.
- `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/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 current core repair gameplay prototype: the repair case, repair game zone, and module slots.
- `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/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.
## 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 filters out nodes whose model cannot be resolved.
## Current Limitations
- The repository is still a prototype, not the full intended game runtime.
- `src/world/debug/TestScene.tsx` is still part of the active scene composition.
- There is no central gameplay orchestrator such as `GameManager` yet.
- 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`.
- Missions, zones, cinematics, and dialogue systems are not implemented.
- 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.
+146
View File
@@ -0,0 +1,146 @@
# 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
│ └── scene/
│ ├── EditorMap.tsx
│ └── EditorScene.tsx
├── controls/
│ └── editor/
│ └── FlyController.tsx
├── hooks/
│ └── editor/
│ ├── useEditorHistory.ts
│ └── useEditorSceneData.ts
├── types/
│ └── editor/
│ └── editor.ts
└── utils/
├── editor/
│ └── loadEditorScene.ts
└── map/
└── loadMapSceneData.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/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.
## 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.
+129
View File
@@ -0,0 +1,129 @@
# Hand Tracking Technical Notes
This document describes the hand tracking system that exists in the current codebase.
## Purpose
Hand tracking is 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.
The feature is scoped to the debug physics scene rather than production gameplay input.
## 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 in the debug physics scene.
## Activation Rules
Hand tracking is intentionally gated so the webcam and backend are not used all the time.
The current 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.
## 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
- The feature is debug-only and focused on the physics test scene.
- 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.
+5 -5
View File
@@ -5,7 +5,7 @@ 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 is intentionally aspirational.
- This document describes intended direction, not implemented behavior.
- If this document conflicts with the current implementation, the current implementation wins.
## Goals
@@ -40,12 +40,12 @@ This document describes the intended medium-term architecture for the project.
- performance overlay
- scene helpers
- free camera and calibration controls
- temporary test scenes used during development
- debug test scenes used during development
### UI Layer
- `src/components/ui/` should contain player-facing HTML overlays.
- Expected future examples:
- Candidate examples:
- crosshair
- loading flow
- mission HUD
@@ -54,7 +54,7 @@ This document describes the intended medium-term architecture for the project.
### Gameplay Layer
- As the project grows, gameplay state can move toward a clearer orchestration layer.
- Likely future concerns:
- Likely concerns:
- missions
- zones
- cinematics
@@ -67,4 +67,4 @@ This document describes the intended medium-term architecture for the project.
- 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 later.
- Debug-only runtime paths should be clearly marked and easy to remove when obsolete.
+164
View File
@@ -0,0 +1,164 @@
# 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" |
"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`.
## World Integration
`src/world/GameStageContent.tsx` subscribes to `mainState` and mounts stage-specific content.
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
`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
The next natural step is to replace the temporary stage anchors in `GameStageContent` with real stage components, for example `IntroContent`, `BikeContent`, `PyloneContent`, `FermeContent`, and `OutroContent`.
+83
View File
@@ -0,0 +1,83 @@
# 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.
## 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.
+18 -2
View File
@@ -5,7 +5,7 @@ This document lists features that are implemented in the current codebase.
## Scene
- Fullscreen React Three Fiber scene
- Main map scene loaded from `public/models/map/model.gltf`
- Main map scene loaded from `public/map.json` and matching `public/models/{name}/model.glb` or `model.gltf` assets
- Debug physics test scene selectable from the debug panel
- Ambient and directional lighting
- Environment background setup
@@ -33,11 +33,26 @@ This document lists features that are implemented in the current codebase.
## Debug Tooling
- `?debug` query param enables the debug panel
- `lil-gui` controls for camera mode, scene mode, and interaction spheres
- `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 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`
## Not Implemented Yet
- mission system
@@ -47,3 +62,4 @@ This document lists features that are implemented in the current codebase.
- loading flow
- minimap and mission HUD
- full production separation between gameplay and debug scenes
- production backend persistence for editor saves
+80
View File
@@ -0,0 +1,80 @@
# Main Feature
This document explains the current repair-game prototype in La-Fabrik.
## What It Does
The main feature is a repair interaction sandbox mounted in the debug physics scene. It lets the player approach a repair case, open it, and interact with module slots that can show selectable models and exploded-model states.
The current user flow is:
1. Open the app with `?debug`.
2. Switch the scene to `Physics` in the debug panel.
3. Move close to the repair case.
4. Press the interaction key when prompted.
5. Watch the case open or close with sound feedback.
6. Interact with repair module slots to cycle/select repair models.
## Why It Matters
This feature validates the core repair fantasy 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
The repair case reacts to player proximity. When the player is close enough, it floats upward and rotates gently to signal interactivity. When the player moves away, it returns to its resting transform.
Interacting with the case toggles its open state. The lid animation is handled with GSAP because it is a discrete interaction animation, not a continuous per-frame loop.
Repair module slots are configured from static gameplay data. They render selectable repair models and can use exploded model visualization to show parts separated from their original positions.
## Key Files
- `src/world/debug/TestMap.tsx` mounts the repair-game prototype in the debug physics scene.
- `src/components/three/gameplay/RepairGameZone.tsx` composes the repair-game zone.
- `src/components/three/gameplay/RepairCaseObject.tsx` connects the repair case to trigger interaction and audio.
- `src/components/three/gameplay/RepairCaseModel.tsx` renders and animates the case model.
- `src/components/three/gameplay/RepairModuleSlot.tsx` renders repair slots and model selection behavior.
- `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 zone and slot positions.
- `src/data/gameplay/repairGameModelCatalog.ts` stores selectable repair models.
## Debug Requirements
The repair-game prototype currently requires:
- the app opened with `?debug`
- the debug scene set to `Physics`
- model assets available under `public/models/`
- sound assets available under `public/sounds/`
Frontend command:
```bash
npm run dev
```
Debug URL:
```txt
http://localhost:5173/?debug
```
## Related Hand Tracking
Hand tracking is a separate debug interaction layer. It can move grabbable physics objects with webcam input, but it is not yet integrated into the repair-game mission flow.
For hand tracking, run the Python backend separately:
```bash
source backend/.venv/bin/activate
python -m backend.main
```
## Current Limitations
- It is mounted only in the debug physics scene.
- There is no mission progression system yet.
- There is no central `GameManager` or Zustand store in this branch.
- Hand tracking is available as debug interaction input, not as final repair gameplay.
- 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,
]);
+1685 -362
View File
File diff suppressed because it is too large Load Diff
+20 -4
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,19 +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",
@@ -41,5 +49,13 @@
"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.
+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.
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