73 Commits

Author SHA1 Message Date
Tom Boullay e073fc375b fix(world): warm up map shadows from environment
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-31 11:00:40 +02:00
Tom Boullay bff8a16290 feat(intro): add ebike onboarding sequence
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-31 10:42:46 +02:00
Tom Boullay a3f611e227 fix(webgl): auto-restore context after loss
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
The Canvas onCreated callback used to log Context Lost but never asked
the GPU to restore it, which left the page on a frozen black canvas
until the user reloaded. We now grab the WEBGL_lose_context extension
on mount and call restoreContext() 500ms after a loss, giving the GPU
time to free memory before we ask for a new context. The existing
webglcontextrestored handler reinstates the shadow map settings, so
recovery is transparent to the user.

This does not prevent context loss itself — frequent losses still
indicate VRAM pressure or HMR-driven context churn — but it removes
the need to reload manually when the GPU recycles us.
2026-05-30 20:58:58 +02:00
Tom Boullay b578e68c2e Update SiteTransitionOverlay.tsx 2026-05-30 20:55:51 +02:00
Tom Boullay 7c691a8044 fix: show dialogue subtitles on black screen
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-30 20:25:21 +02:00
Tom Boullay f24704091a chore(logging): downgrade 'lite map skipped' to debug
This log fires every time the lite map loader skips heavy nodes, which
is the expected fast-path. It does not need to show up in a normal
console session — moving it to logger.debug keeps it accessible under
?debug for diagnostics while removing the noise from default runs.
2026-05-30 20:20:15 +02:00
Tom Boullay e6bfcbe960 feat(intro): polish loading transition 2026-05-30 20:11:40 +02:00
Tom Boullay 0fa7a82175 fix(perf): prevent Canvas double-mount on /site redirect
HomePage used to mount the Canvas before its effect fired the redirect
to /site, then unmount it as soon as the route changed. That left the
WebGL context torn down mid-load with GLTF requests still in flight,
which on slow GPUs ended in a 'Context Lost' and a stuck 1 FPS render
once the user came back from /site. The fix is a synchronous cookie
check after all hooks: if the user has not visited /site today we
return null and let the redirect happen without ever creating a GL
context.

Also drops the GameMap 'lite map skipped' log from warn to info: it
is an expected lite-loading path, not a problem worth a yellow warning.
2026-05-30 19:51:57 +02:00
Tom Boullay 82dc47a296 fix(assets): correct texture URIs in gant_r and pylone GLTFs
- gant_r/model.gltf: align casing with the actual files on disk
  (gant_basecolor / gant_occlusionroughnessmetallic) so the GLTFLoader
  stops logging 'Couldn't load texture' on case-sensitive filesystems
- pylone/model.gltf: nine missing Image_N.png references replaced with
  the existing color.png / normal.png so the pylone renders without
  console errors. Material slots are mapped by role: normalTexture ->
  normal.png, baseColorTexture and metallicRoughnessTexture -> color.png
2026-05-30 19:51:51 +02:00
Tom Boullay 970adf4853 feat(a11y): WCAG AA polish on the site onboarding flow
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
- index.css: add visible :focus-visible rings for .site-card-button
  and .site-button so keyboard users can see where focus lives
- SiteCard: drop outline:none, add aria-pressed and aria-label so
  screen readers announce selection state
- SiteButton: add the .site-button class for the shared focus ring
- SiteDisclaimerScreen: keyboard skip via Enter / Space / Escape, a
  role="region" + aria-label wrapper and aria-live="polite" on the
  message; honour prefers-reduced-motion on the fade
- IntroVideoPlayer: role="region" with a skip hint in aria-label,
  preload="auto", and aria-hidden on the decorative caption span
2026-05-30 18:44:03 +02:00
Tom Boullay 07b09c22af fix(site): repair onboarding audio cleanup, redirect, and manifest fetches
- loadDialogueManifest: cache the resolved manifest at module level and
  dedupe concurrent fetches so each screen no longer re-downloads it
- useGameStore: completeIntroState now also advances intro.currentStep
  to "playing" so callers do not need a separate setIntroStep call
- SiteNamingScreen and SiteTransitionOverlay: replace ref-based guards
  with an isCancelled flag captured per effect. The previous guards
  persisted across StrictMode remounts, leaving mount 2 unable to
  re-run the effect after mount 1's chain was cancelled, which broke
  the fade animations, the second narrator dialogue and the redirect.
  Both screens now also call stopCurrentDialogue on unmount so audio
  cannot bleed across routes, and the transition gets a safety timeout
  in case the dialogue audio fails to fire its "ended" event
- SiteTransitionOverlay: keep the <Subtitles /> mount inside the
  overlay so it renders inside the z-index 1000 stacking context
  (above the black screen); the one in SiteLayout sits behind it
- IntroDialogueOverlay: route through playDialogueById instead of
  AudioManager.playSoundWithCallback so the narrator subtitles play
  in sync, and add the same isCancelled cleanup pattern
- IntroRevealOverlay: rely on completeIntro alone now that it advances
  intro.currentStep, and skip the fade when reduced motion is requested
- SiteMobileBlocker: correct logo path from public/... to /...
2026-05-30 18:43:53 +02:00
Tom Boullay 0f6860f1ae refactor(site): extract shared utilities and centralise dialogue IDs
- new src/hooks/ui/useIsMobile.ts (matchMedia + useSyncExternalStore)
  replacing the resize-handler hook inlined inside pages/site/page.tsx
- new src/hooks/ui/usePrefersReducedMotion.ts
- new src/data/site/dialogueIds.ts so site and intro components stop
  carrying hard-coded narrator IDs
- siteConfig: add SITE_BACKGROUND_STYLE shared by SiteLayout and
  SiteMobileBlocker, rename forcedName to presetPlayerName, fix the
  swapped id/label pairing on situation cards
- useSiteStore: rename selectedExperience/Situation to *Index so the
  stored value (an array index) is obvious in callers
- audioConfig: drop dead AUDIO_PATHS placeholders
- propagate the renames and SITE_BACKGROUND_STYLE through SiteLayout,
  SiteWelcomeScreen, SiteSituationScreen and pages/site/page.tsx
2026-05-30 18:43:35 +02:00
Tom Boullay 6ae21a2427 fix(site): unified card styles, import Nersans One font, native naming input
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-30 17:56:42 +02:00
Tom Boullay 29342d796c fix(site): reduce situation card font size 2026-05-30 17:21:44 +02:00
Tom Boullay 60e3c92511 fix(site): update situation cards 2026-05-30 17:06:29 +02:00
Tom Boullay 02c1fb33d0 feat(dialogues): support multi-cue subtitles
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-30 04:00:25 +02:00
Tom Boullay ce5dc8ada0 update: intro flow overlays 2026-05-30 04:00:20 +02:00
Tom Boullay a2cff0567e feat: add site onboarding route 2026-05-30 04:00:09 +02:00
Tom Boullay 8cfee1ac93 update: reorganize public assets 2026-05-30 03:59:45 +02:00
Tom Boullay 4c5e2ed945 feat(types): add SiteStep and refactor GameStep for new intro flow
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-30 02:14:10 +02:00
Tom Boullay 345d49f485 add: cinemactics and assets
🔍 Lint / 🪄 Check lint (push) Has been cancelled
🔍 Lint / 🎨 Check format (push) Has been cancelled
🔍 Lint / 🔎 Typecheck (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
🔍 Lint / 🏗 Build (push) Has been cancelled
2026-05-30 02:04:39 +02:00
math-pixel a6cc028848 Merge pull request 'Feat/gallery' (#9) from feat/galerie into develop
🔍 Lint / 🪄 Check lint (push) Has been cancelled
🔍 Lint / 🎨 Check format (push) Has been cancelled
🔍 Lint / 🔎 Typecheck (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
🔍 Lint / 🏗 Build (push) Has been cancelled
Reviewed-on: #9
2026-05-29 07:00:36 +00:00
Tom Boullay 52bb1b2915 chore: code quality audit and lint fixes
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
- Fix all 63 ESLint errors across codebase
- Consolidate MaterialWithTextureSlots type in src/types/three/three.ts
- Add CSS custom properties for design tokens
- Extract ebike constants to src/data/ebike/ebikeConfig.ts
- Add proper TypeScript types for window extensions
- Fix React hooks violations (refs during render, setState in effects)
- Remove unused exports and redundant CSS
- Add type guards for Three.js material handling
- Clean up AI slop comments and legacy CSS patterns
2026-05-29 09:00:04 +02:00
Tom Boullay ade301389e merge develop into feat/galerie - resolve model and code conflicts 2026-05-29 02:25:46 +02:00
Tom Boullay 47e50d9318 fix: issue in galley mode 2026-05-29 02:18:17 +02:00
math-pixel c7df58099a Merge pull request 'Feat/map-environment' (#6) from feat/map-environment into develop
🔍 Lint / 🪄 Check lint (push) Has been cancelled
🔍 Lint / 🎨 Check format (push) Has been cancelled
🔍 Lint / 🔎 Typecheck (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
🔍 Lint / 🏗 Build (push) Has been cancelled
Reviewed-on: #6
2026-05-29 00:00:50 +00:00
Tom Boullay f7b4a07e41 fix: bug on textute vegetation item
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-29 02:00:35 +02:00
Tom Boullay 89044a18ec merge develop into feat/map-environment
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-29 01:45:08 +02:00
Tom Boullay 95ca1bbfde hore(review): tighten pre-merge audit cleanup
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-29 01:34:10 +02:00
Tom Boullay 093ffd726d fix(review): address audit findings before merge
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-29 01:23:08 +02:00
Tom Boullay 4728690a11 Create outro.mp4
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-29 00:52:53 +02:00
Tom Boullay 343a122c06 fix(editor): restore stable map editing behavior 2026-05-29 00:52:44 +02:00
tom-boullay d5675fe82c feat: restaure l'éditeur map et ajoute les personnages
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-28 15:49:57 +02:00
tom-boullay fcdbf7270c perf: applique les échelles aux assets instanciés 2026-05-28 15:49:26 +02:00
tom-boullay 0b3d49e8d1 feat: ajoute les potagers à la map 2026-05-28 15:48:33 +02:00
tom-boullay 9bbed06ddc feat: ancre les réparations sur la map chargée 2026-05-28 15:47:53 +02:00
tom-boullay ba50224e6e refactor: nettoie l'architecture monde et les docs 2026-05-28 15:47:16 +02:00
tom-boullay 1a91b1d7ae refactor: clean map gameplay architecture 2026-05-28 11:15:45 +02:00
math-pixel fb466a63cb Merge pull request 'Merge e_bike + gps into develop' (#7) from feat/gps into develop
🔍 Lint / 🪄 Check lint (push) Has been cancelled
🔍 Lint / 🎨 Check format (push) Has been cancelled
🔍 Lint / 🔎 Typecheck (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
🔍 Lint / 🏗 Build (push) Has been cancelled
Reviewed-on: #7
2026-05-28 05:55:18 +00:00
math-pixel a75c3fd896 uptd : change location of map_backougnround & fix : remove old netshader into world tsx
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-28 07:54:47 +02:00
math-pixel 603e521714 Merge branch 'develop' into feat/gps 2026-05-28 07:50:25 +02:00
math-pixel 49ef8f58b4 Merge pull request 'Add Net shader into develop' (#8) from feat/shader-net into develop
🔍 Lint / 🪄 Check lint (push) Has been cancelled
🔍 Lint / 🎨 Check format (push) Has been cancelled
🔍 Lint / 🔎 Typecheck (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
🔍 Lint / 🏗 Build (push) Has been cancelled
Reviewed-on: #8
2026-05-28 05:47:52 +00:00
math-pixel 0a322acf88 Merge branch 'develop' into feat/shader-net
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-27 18:08:46 +02:00
math-pixel a397febd52 fix zoom
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-27 17:23:06 +02:00
math-pixel c15cad2ab0 fix zoom 2026-05-27 17:22:14 +02:00
math-pixel 011e7815a2 update gps 2026-05-27 17:15:08 +02:00
Tom Boullay 054cb975da fix: hide gallery export planes
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-25 19:13:02 +02:00
Tom Boullay cf71148935 fix: smooth gallery preview seams
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
2026-05-25 18:02:36 +02:00
Tom Boullay 1b2241df49 feat: add gallery lighting controls 2026-05-25 17:57:51 +02:00
Tom Boullay d7351e5f37 fix: render gallery skybox unlit double-sided 2026-05-25 17:53:46 +02:00
Tom Boullay 6a412c7b00 fix: stabilize gallery skybox rendering 2026-05-25 17:31:27 +02:00
Tom Boullay e9fb36f9dc style: simplify gallery UI and rename route 2026-05-25 17:13:21 +02:00
Tom Boullay 36180279b2 docs: document model gallery 2026-05-25 16:24:12 +02:00
Tom Boullay 626dc47bbe feat: add model gallery viewer 2026-05-25 16:23:36 +02:00
math-pixel 970253801a add map on bike 2026-05-22 18:28:05 +02:00
math-pixel 246da0019a add transparency gps 2026-05-20 16:56:01 +02:00
math-pixel 09a9471814 feature gps works 2026-05-20 15:29:23 +02:00
math-pixel 6e9318457a gps component 2026-05-20 14:45:40 +02:00
math-pixel 54a353de03 first implementation of pathfinding 2026-05-20 14:34:26 +02:00
math-pixel 8b619bfc28 feat: add NetShader and UnicolorShader with a debug component for visual testing in the world scene 2026-05-19 22:54:29 +02:00
math-pixel 4faa226326 working move kikle 2026-05-19 17:10:34 +02:00
math-pixel dd66966507 working move kikle 2026-05-19 17:04:01 +02:00
math-pixel 5893afe42a working move kikle 2026-05-19 16:34:48 +02:00
math-pixel 1ead7ab3a7 working move kikle 2026-05-19 16:17:02 +02:00
math-pixel 047c58678b working move kikle 2026-05-19 16:12:58 +02:00
math-pixel ed9051b0dc working move kikle 2026-05-19 16:10:57 +02:00
math-pixel 08be6bee48 add good inclinason cam 2026-05-19 15:54:40 +02:00
math-pixel ce0eb90321 inhance move 2026-05-19 15:50:11 +02:00
math-pixel 96d7ec7fc0 move forward cam 2026-05-19 15:36:50 +02:00
math-pixel 9ab4b4a002 first move with bike 2026-05-19 15:32:59 +02:00
math-pixel d13dd0fda0 wip bike movement 2026-05-17 12:30:40 +02:00
math-pixel fbedb90bca working bike 2026-05-17 08:15:16 +02:00
math-pixel cff7744ad9 wip 2026-05-17 07:41:29 +02:00
1134 changed files with 15013 additions and 3815 deletions
+13 -49
View File
@@ -15,12 +15,9 @@ export class SomeManager {
return SomeManager._instance; return SomeManager._instance;
} }
private constructor() { private constructor() {}
// init logic
}
destroy(): void { destroy(): void {
// cleanup logic
SomeManager._instance = null; SomeManager._instance = null;
} }
} }
@@ -29,42 +26,11 @@ export class SomeManager {
## Managers in this project ## Managers in this project
| Manager | File | Role | | Manager | File | Role |
| -------------------- | ------------------------------------ | ----------------------------------------------------------------------------- | | -------------------- | ------------------------------------ | -------------------------------------------------------------- |
| `AudioManager` | `src/managers/AudioManager.ts` | Music and SFX playback. | | `AudioManager` | `src/managers/AudioManager.ts` | Music and SFX playback. |
| `InteractionManager` | `src/managers/InteractionManager.ts` | Focus, nearby, trigger, grab, and hand-grab interaction state. | | `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. |
## Target-State GameManager ## Subscribe Pattern
`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 {
cinematic!: CinematicManager;
audio!: AudioManager;
zone!: ZoneManager;
private constructor() {
this.cinematic = CinematicManager.getInstance();
this.audio = AudioManager.getInstance();
this.zone = ZoneManager.getInstance();
}
}
```
When a `GameManager` exists, components and hooks should access other managers through it:
```ts
// Correct
GameManager.getInstance().cinematic.play("intro");
// Wrong — never import sub-managers directly in components
CinematicManager.getInstance().play("intro");
```
## Target-State Subscribe Pattern
```ts ```ts
private listeners = new Set<() => void>() private listeners = new Set<() => void>()
@@ -79,28 +45,26 @@ private emit(): void {
} }
``` ```
In that target-state manager, every `set*()` method calls `this.emit()` to notify subscribers. Managers that expose state to React call `this.emit()` from every `set*()` method that changes subscribed state.
## Target-State React Bridge Hook ## React Bridge Hook
```ts ```ts
// hooks/useGameState.ts // hooks/interaction/useInteraction.ts
export function useGameState() { const manager = InteractionManager.getInstance();
const game = GameManager.getInstance();
const [state, setState] = useState(game.getState());
useEffect(() => { export function useInteraction(): InteractionSnapshot {
return game.subscribe(() => setState({ ...game.getState() })); return useSyncExternalStore(
}, [game]); manager.subscribe.bind(manager),
manager.getState.bind(manager),
return state; );
} }
``` ```
## Rules ## Rules
- Do not add a `GameManager` unless the feature requires a real shared gameplay state owner. - 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. - Current managers may be imported directly.
- Keep singleton managers limited to side-effect services or shared interaction state. - Keep singleton managers limited to side-effect services or shared interaction state.
- Always call `destroy()` on cleanup when a manager owns external resources. - Always call `destroy()` on cleanup when a manager owns external resources.
- Never create manager instances with `new` — always use `.getInstance()`. - Never create manager instances with `new` — always use `.getInstance()`.
+3
View File
@@ -23,3 +23,6 @@
# Video (cinematics) # Video (cinematics)
*.mp4 filter=lfs diff=lfs merge=lfs -text *.mp4 filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text *.webm filter=lfs diff=lfs merge=lfs -text
# ML models
*.task filter=lfs diff=lfs merge=lfs -text
+8 -3
View File
@@ -69,16 +69,21 @@ jobs:
- name: 📥 Install - name: 📥 Install
run: npm ci run: npm ci
- name: 🧹 Lint
run: npm run lint
- name: 🎨 Format check
run: npm run format:check
- name: 📦 Build - name: 📦 Build
run: npm run build run: npm run build
- name: 📏 Check bundle size - name: 📏 Check bundle size
run: | run: |
# Check generated app assets only; public/ model files are runtime assets copied to dist. # Check generated JS/CSS bundles only; public runtime assets are copied to dist/assets too.
SIZE=$(du -k dist/assets | cut -f1) SIZE=$(node -e "const fs=require('fs');const path=require('path');function walk(dir){return fs.readdirSync(dir,{withFileTypes:true}).flatMap((entry)=>{const file=path.join(dir,entry.name);return entry.isDirectory()?walk(file):file;});}const bytes=walk('dist/assets').filter((file)=>/\.(js|css)$/.test(file)).reduce((sum,file)=>sum+fs.statSync(file).size,0);console.log(Math.ceil(bytes/1024));")
echo "Bundle size: ${SIZE}KB" echo "Bundle size: ${SIZE}KB"
# Threshold: 5000KB (configurable)
THRESHOLD=5000 THRESHOLD=5000
if [ "$SIZE" -gt "$THRESHOLD" ]; then if [ "$SIZE" -gt "$THRESHOLD" ]; then
-164
View File
@@ -1,164 +0,0 @@
# Game Flow - La Fabrik
## Étapes du jeu
```
intro → start-intro → naming → bienvenue → star-move → mission2 → searching_problem → preparation → outOfFabrik
```
---
## Détail des étapes
### 1. `intro` (initial)
- État initial au chargement du jeu
- Aucune action, juste une étape de départ
- Transition automatique vers `start-intro`
### 2. `start-intro`
- **Déclenchement** : Auto-transition depuis `intro` quand la scène est chargée
- **Action** : Joue l'audio d'intro (`intro`)
- **Attente** : Attend que l'audio se termine
- **Transition** : Vers `naming` quand l'audio se termine
### 3. `naming`
- **Déclenchement** : Quand l'audio d'intro se termine
- **Action** : Affiche un input pour demander le prénom du joueur
- **Attente** : L'utilisateur entre son prénom et valide
- **Transition** : Vers `bienvenue` quand l'utilisateur valide
### 4. `bienvenue`
- **Déclenchement** : Quand l'utilisateur valide son prénom
- **Actions** :
- Affiche "Bienvenue {prénom} !" à l'écran
- Joue l'audio de bienvenue
- **Attente** : Attend que l'audio se termine
- **Transition** : Vers `star-move` quand l'audio se termine
### 5. `star-move`
- **Déclenchement** : Quand l'audio de bienvenue se termine
- **Action** : Active le mouvement du joueur (`setCanMove(true)`)
- **État** : Le joueur peut maintenant se déplacer librement
- **Zone** : La détection de zone devient active (ZoneDetection)
### 6. `mission2`
- **Déclenchement** : Quand le joueur entre dans la zone `fabrikExit` (position: `[-5, 25, -15]`)
- **Actions** :
- Stocke `activityCity: false` dans le store Zustand
- Joue l'audio `alertCentral`
- **État** : Les systèmes lisent `activityCity` depuis `useGameStore` pour adapter leur comportement
- **Attente** : Le joueur atteint la zone de trigger pour `searching_problem`
### 7. `searching_problem`
- **Déclenchement** : Quand le joueur entre dans la zone `searchingProblemZone` (position: `[-5, 25, -30]`)
- **Actions** :
- Joue l'audio `searchingProblem`
- Affiche l'objet "central" (position: `[1, 15, -45]`)
- **Attente** : Le joueur interagit avec l'objet "central"
### 8. `preparation`
- **Déclenchement** : Quand le joueur interagit avec l'objet "central"
- **Actions** :
- Bloque le mouvement (`setCanMove(false)`)
- Cache l'objet "central"
### 9. `outOfFabrik`
- **Déclenchement** : (non implémenté pour le moment)
- **Action** : Transition vers l'étape finale
---
## Fichiers clés
| Fichier | Rôle |
| --------------------------------------- | --------------------------------------------------------- |
| `src/managers/stores/useGameStore.ts` | Store Zustand pour l'état global du jeu |
| `src/components/game/GameFlow.tsx` | Gère les transitions automatiques et la lecture audio |
| `src/components/ui/IntroUI.tsx` | Affiche l'input pour le prénom et le message de bienvenue |
| `src/components/zone/ZoneDetection.tsx` | Détecte quand le joueur entre dans une zone |
| `src/world/GameStageContent.tsx` | Monte les contenus de mission dans la scène |
| `src/data/audioConfig.ts` | Chemins des fichiers audio |
| `src/data/zones.ts` | Configuration des zones de transition |
---
## Configuration audio
```typescript
// src/data/audioConfig.ts
export const AUDIO_PATHS = {
intro: "/sounds/fa.mp3",
bienvenue: "/sounds/fa.mp3",
alertCentral: "/sounds/fa.mp3",
searchingProblem: "/sounds/fa.mp3",
};
```
---
## Configuration des zones
```typescript
// src/data/zones.ts
export const ZONES: Zone[] = [
{
id: "fabrikExit",
position: [-5, 25, -15],
radius: 10,
height: 20,
targetStep: "mission2",
},
{
id: "searchingProblemZone",
position: [-5, 25, -30],
radius: 10,
height: 20,
targetStep: "searching_problem",
},
];
```
---
## Store Zustand
```typescript
// src/managers/stores/useGameStore.ts
interface GameState {
mainState: MainGameState;
missionFlow: {
activityCity: boolean;
canMove: boolean;
playerName: string;
};
}
```
---
## Debug
En mode debug (`?debug` dans l'URL), on peut voir :
- **Game Step** : L'étape actuelle dans le panneau lil-gui
- **Player Position** : Position X, Y, Z du joueur en temps réel
- **Zone Visualization** : Anneaux visuels au sol pour les zones + cylindres transparents
---
## Notes techniques
- Le mouvement du joueur est bloqué tant que `canMove` est `false`
- Le store Zustand (`useGameStore`) est la source principale de vérité
- `GameStepManager` synchronise automatiquement avec le store Zustand lors des transitions
- Les transitions via les zones utilisent `GameStepManager.transitionTo()` qui met à jour le store
- L'audio utilise un callback `onEnded` pour déclencher les transitions automatiques
+14 -6
View File
@@ -10,7 +10,7 @@ The current prototype puts the player in a repair-oriented world where they prog
- Production map loaded from `public/map.json` - Production map loaded from `public/map.json`
- Progressive map/model/collision/stage loading overlay - Progressive map/model/collision/stage loading overlay
- Player controller with pointer lock, `ZQSD` movement, jump, octree collision, trigger input, and grab input - Player controller with pointer lock, `ZQSD` movement, jump, octree collision, trigger input, and grab input
- Reusable repair-game flow for `bike`, `pylone`, and `ferme` - Reusable repair-game flow for `ebike`, `pylon`, and `farm`
- Repair case animation, exploded model scan, broken-part markers, grabbable replacements, snap-to-placeholder placement, install validation, reassembly, and completion - Repair case animation, exploded model scan, broken-part markers, grabbable replacements, snap-to-placeholder placement, install validation, reassembly, and completion
- Shared interaction system for trigger and grab objects - Shared interaction system for trigger and grab objects
- Rapier physics for gameplay objects while the player keeps a Three.js octree collision controller - Rapier physics for gameplay objects while the player keeps a Three.js octree collision controller
@@ -18,7 +18,7 @@ The current prototype puts the player in a repair-oriented world where they prog
- Category-based audio manager for music, SFX, and dialogue - Category-based audio manager for music, SFX, and dialogue
- Dialogue manifest, SRT subtitles, subtitle overlay, and dialogue queueing - Dialogue manifest, SRT subtitles, subtitle overlay, and dialogue queueing
- Cinematic manifest with GSAP camera keyframes and optional dialogue cues - Cinematic manifest with GSAP camera keyframes and optional dialogue cues
- In-game settings menu for volumes, subtitles, subtitle language, and the currently staged repair-runtime toggle - In-game settings menu for volumes, subtitles, and subtitle language
- Debug mode with `?debug`, lil-gui controls, game-state panel, hand-tracking panel, debug camera, physics playground, and R3F perf - Debug mode with `?debug`, lil-gui controls, game-state panel, hand-tracking panel, debug camera, physics playground, and R3F perf
- `/editor` route for map transforms, SRT editing, dialogue manifest editing, cinematic manifest editing, preview, validation, export, and dev-server saves - `/editor` route for map transforms, SRT editing, dialogue manifest editing, cinematic manifest editing, preview, validation, export, and dev-server saves
- `/docs` route that renders the repository documentation inside the app - `/docs` route that renders the repository documentation inside the app
@@ -26,10 +26,11 @@ The current prototype puts the player in a repair-oriented world where they prog
## Routes ## Routes
| Route | Purpose | | Route | Purpose |
| --------- | --------------------------------------------------- | | ---------- | --------------------------------------------------- |
| `/` | Playable 3D experience | | `/` | Playable 3D experience |
| `/?debug` | Playable scene with debug GUI and overlays | | `/?debug` | Playable scene with debug GUI and overlays |
| `/editor` | Local map, dialogue, subtitle, and cinematic editor | | `/editor` | Local map, dialogue, subtitle, and cinematic editor |
| `/gallery` | 3D model gallery for browsing project assets |
| `/docs` | In-app documentation index | | `/docs` | In-app documentation index |
## Tech Stack ## Tech Stack
@@ -98,6 +99,7 @@ Useful local URLs:
```txt ```txt
http://localhost:5173/?debug http://localhost:5173/?debug
http://localhost:5173/editor http://localhost:5173/editor
http://localhost:5173/gallery
http://localhost:5173/docs http://localhost:5173/docs
``` ```
@@ -110,9 +112,15 @@ npm run format:check
npm run build npm run build
``` ```
Regenerate runtime map data after editing `public/map_raw.json` that came from the hierachy node of the model Blocking.gltf:
```bash
npm run map:transform
```
## Optional Hand-Tracking Backend ## Optional Hand-Tracking Backend
The app can use browser-side MediaPipe, but the default debug source is the local backend. The app can use the local Python backend, but the default debug source is browser-side MediaPipe.
```bash ```bash
python3.11 -m venv backend/.venv python3.11 -m venv backend/.venv
@@ -149,13 +157,13 @@ WS ws://localhost:8000/ws
| `docs/user/features.md` | Implemented feature inventory | | `docs/user/features.md` | Implemented feature inventory |
| `docs/user/main-feature.md` | User-facing repair-game walkthrough | | `docs/user/main-feature.md` | User-facing repair-game walkthrough |
| `docs/user/editor.md` | Editor user guide | | `docs/user/editor.md` | Editor user guide |
| `docs/user/gallery.md` | Model gallery user guide |
| `docs/code-review-preparation.md` | French code-review preparation support | | `docs/code-review-preparation.md` | French code-review preparation support |
## Current Caveats ## Current Caveats
- This is still a prototype, not a complete game runtime. - This is still a prototype, not a complete game runtime.
- The repair-runtime toggle is stored in settings and displayed in the UI, but the repair game currently still runs locally in React/Three. - `useRepairMovementLocked()` locks player movement during focused repair steps and drives the repair movement indicator.
- `useRepairMovementLocked()` currently returns `false`, so the movement-lock rule and indicator are present but disabled on `develop`.
- Production editor persistence does not exist. Save endpoints in `vite.config.ts` are local Vite dev-server helpers. - Production editor persistence does not exist. Save endpoints in `vite.config.ts` are local Vite dev-server helpers.
- The player uses octree collision while gameplay objects use Rapier. Keep that boundary deliberate unless the whole player controller is migrated. - The player uses octree collision while gameplay objects use Rapier. Keep that boundary deliberate unless the whole player controller is migrated.
Binary file not shown.
+8 -8
View File
@@ -19,7 +19,7 @@ La-Fabrik est une expérience web 3D en React, Vite, Three.js et React Three Fib
Le joueur est dans un monde 3D et avance dans une progression de réparation : Le joueur est dans un monde 3D et avance dans une progression de réparation :
```txt ```txt
intro -> bike -> pylone -> ferme -> outro intro -> ebike -> pylon -> farm -> outro
``` ```
Les trois piliers à connaître pour la review : Les trois piliers à connaître pour la review :
@@ -62,7 +62,7 @@ HomePage
-> World -> World
-> GameMap -> GameMap
-> GameStageContent -> GameStageContent
-> RepairGame bike/pylone/ferme -> RepairGame ebike/pylon/farm
-> GameMusic -> GameMusic
-> GameDialogues -> GameDialogues
-> Player -> Player
@@ -121,7 +121,7 @@ Phrase à retenir :
Piège à connaître : Piège à connaître :
`useRepairMovementLocked()` retourne actuellement `false`. Le lock de mouvement est prévu dans le code et l'UI, mais il est désactivé sur `develop`. `useRepairMovementLocked()` lit maintenant l'étape de mission active et verrouille le déplacement pendant les phases de réparation qui doivent immobiliser le joueur.
### Interaction ### Interaction
@@ -324,7 +324,7 @@ Ouvrir dans cet ordre :
`RepairGame` reçoit une mission : `RepairGame` reçoit une mission :
```tsx ```tsx
<RepairGame mission="bike" position={[8, 0, -6]} /> <RepairGame mission="ebike" position={[42.2399, 4.5484, 34.6468]} />
``` ```
Puis il vérifie : Puis il vérifie :
@@ -347,7 +347,7 @@ Les variations mission sont dans `repairMissions.ts` :
### Pourquoi c'est bien ### Pourquoi c'est bien
- Un seul flow réutilisable. - Un seul flow réutilisable.
- Moins de duplication entre `bike`, `pylone`, `ferme`. - Moins de duplication entre `ebike`, `pylon`, `farm`.
- Les règles générales restent dans les composants. - Les règles générales restent dans les composants.
- Les variations restent dans la data. - Les variations restent dans la data.
- Le debug panel peut tester les mêmes steps que le vrai jeu. - Le debug panel peut tester les mêmes steps que le vrai jeu.
@@ -471,9 +471,9 @@ Main states :
```txt ```txt
intro intro
bike ebike
pylone pylon
ferme farm
outro outro
``` ```
+1 -1
View File
@@ -46,7 +46,7 @@ It supports:
The debug physics scene currently uses it to preview: The debug physics scene currently uses it to preview:
```txt ```txt
public/models/electricienne_animated/model.gltf public/models/electricienne-animated/model.gltf
``` ```
with the `Dance` animation. with the `Dance` animation.
+2 -3
View File
@@ -107,7 +107,7 @@ It owns:
- `mainState` - `mainState`
- intro state - intro state
- `bike`, `pylone`, and `ferme` mission state - `ebike`, `pylon`, and `farm` mission state
- outro state - outro state
- `isCinematicPlaying` - `isCinematicPlaying`
- progression actions - progression actions
@@ -297,8 +297,7 @@ public/models/{name}/model.gltf
- The repository is still a prototype. - The repository is still a prototype.
- There is no central production `GameManager`. - There is no central production `GameManager`.
- The repair game is implemented, but broader mission orchestration is still light. - The repair game is implemented, but broader mission orchestration is still light.
- `useRepairMovementLocked()` currently returns `false`, so repair movement lock is disabled even though the rule and UI component exist. - `useRepairMovementLocked()` locks player movement during focused repair steps.
- The repair-runtime setting is stored in settings but not consumed by the repair-game implementation.
- Player collision and Rapier gameplay physics are separate systems. - Player collision and Rapier gameplay physics are separate systems.
- Editor persistence is local development tooling only. - Editor persistence is local development tooling only.
- Debug systems are still part of active scene composition and should remain easy to identify. - Debug systems are still part of active scene composition and should remain easy to identify.
+7 -9
View File
@@ -113,7 +113,7 @@ If `model.glb` and `model.gltf` are both missing, the editor renders a fallback
2. `useEditorSceneData` calls `loadMapSceneData()`. 2. `useEditorSceneData` calls `loadMapSceneData()`.
3. `loadMapSceneData()` loads `/map.json` and available model URLs. 3. `loadMapSceneData()` loads `/map.json` and available model URLs.
4. If `/map.json` is missing, the page displays a folder-upload flow. 4. If `/map.json` is missing, the page displays a folder-upload flow.
5. `EditorSceneLoadingTracker` uses drei `useProgress()` to update the fullscreen editor loading overlay while models load. 5. The route-level loading overlay reports map JSON loading, then hands off to the editor scene once the map payload is ready.
6. `EditorScene` renders the grid, lights, camera controls, and map nodes inside `Suspense`. 6. `EditorScene` renders the grid, lights, camera controls, and map nodes inside `Suspense`.
7. `EditorControls` exposes transform mode, terrain snap, terrain-selection lock, add/delete node, precise scale inputs, history actions, camera focus/reset, export, save, JSON preview, selection lock, multi-selection status, and the cinematic/dialogue/SRT editors. 7. `EditorControls` exposes transform mode, terrain snap, terrain-selection lock, add/delete node, precise scale inputs, history actions, camera focus/reset, export, save, JSON preview, selection lock, multi-selection status, and the cinematic/dialogue/SRT editors.
@@ -122,8 +122,7 @@ If `model.glb` and `model.gltf` are both missing, the editor renders a fallback
- Click: select a node. - Click: select a node.
- `Shift` + right click: add or remove a node from the multi-selection. - `Shift` + right click: add or remove a node from the multi-selection.
- `Esc`: clear selection. - `Esc`: clear selection.
- Click empty space: clear selection. - Selection lock button: prevent object clicks and `Esc` from changing the current selection.
- Selection lock button: prevent object clicks, empty-space clicks, and `Esc` from changing the current selection.
- Selection clear button: intentionally clear the current selection even when the lock is active. - Selection clear button: intentionally clear the current selection even when the lock is active.
- `T`: translate mode. - `T`: translate mode.
- `R`: rotate mode. - `R`: rotate mode.
@@ -151,14 +150,13 @@ The dev-only `/api/save-map` endpoint is implemented by the Vite plugin in `vite
## Editor Loading Overlay ## Editor Loading Overlay
The editor uses `SceneLoadingOverlay` like the runtime scene. `EditorSceneLoadingTracker` lives in `src/pages/editor/page.tsx` and reads drei `useProgress()` inside the canvas. The editor uses `SceneLoadingOverlay` like the runtime scene for the route-level map JSON loading phase.
The route tracks two loading phases: The route tracks the map JSON loading phase:
- map JSON loading through `useEditorSceneData()` - map JSON loading through `useEditorSceneData()`
- model loading through `useProgress()`
The overlay is rendered outside the canvas so it remains visible while the R3F scene mounts. The scene itself is wrapped in `Suspense` with a `null` fallback; the visual feedback is handled by the overlay instead of by the canvas fallback. The overlay is rendered outside the canvas so it remains visible while the editor route mounts. Model loading is left to R3F `Suspense` boundaries to avoid progress updates during model render.
## Panel Groups ## Panel Groups
@@ -190,9 +188,9 @@ The state is passed to:
- `EditorControls`, to render the lock/unlock button - `EditorControls`, to render the lock/unlock button
- `EditorScene`, to block `Esc` deselection when locked - `EditorScene`, to block `Esc` deselection when locked
- `EditorMap`, to block object selection and empty-space deselection when locked - `EditorMap`, to block object selection when locked
The clear button calls `onClearSelection` directly from `EditorControls`. This is intentionally separate from scene click behavior so the user always has an explicit way to clear the selection. The clear button calls `onClearSelection` directly from `EditorControls`. Clicking empty canvas space does not clear the current selection; use `Esc` or the explicit clear button instead.
## Dialogue SRT Editing ## Dialogue SRT Editing
-164
View File
@@ -1,164 +0,0 @@
# Game Flow - La Fabrik
## Étapes du jeu
```
intro → start-intro → naming → bienvenue → star-move → mission2 → searching_problem → preparation → outOfFabrik
```
---
## Détail des étapes
### 1. `intro` (initial)
- État initial au chargement du jeu
- Aucune action, juste une étape de départ
- Transition automatique vers `start-intro`
### 2. `start-intro`
- **Déclenchement** : Auto-transition depuis `intro` quand la scène est chargée
- **Action** : Joue l'audio d'intro (`intro`)
- **Attente** : Attend que l'audio se termine
- **Transition** : Vers `naming` quand l'audio se termine
### 3. `naming`
- **Déclenchement** : Quand l'audio d'intro se termine
- **Action** : Affiche un input pour demander le prénom du joueur
- **Attente** : L'utilisateur entre son prénom et valide
- **Transition** : Vers `bienvenue` quand l'utilisateur valide
### 4. `bienvenue`
- **Déclenchement** : Quand l'utilisateur valide son prénom
- **Actions** :
- Affiche "Bienvenue {prénom} !" à l'écran
- Joue l'audio de bienvenue
- **Attente** : Attend que l'audio se termine
- **Transition** : Vers `star-move` quand l'audio se termine
### 5. `star-move`
- **Déclenchement** : Quand l'audio de bienvenue se termine
- **Action** : Active le mouvement du joueur (`setCanMove(true)`)
- **État** : Le joueur peut maintenant se déplacer librement
- **Zone** : La détection de zone devient active (ZoneDetection)
### 6. `mission2`
- **Déclenchement** : Quand le joueur entre dans la zone `fabrikExit` (position: `[-5, 25, -15]`)
- **Actions** :
- Stocke `activityCity: false` dans le store Zustand
- Joue l'audio `alertCentral`
- **État** : Les systèmes lisent `activityCity` depuis `useGameStore` pour adapter leur comportement
- **Attente** : Le joueur atteint la zone de trigger pour `searching_problem`
### 7. `searching_problem`
- **Déclenchement** : Quand le joueur entre dans la zone `searchingProblemZone` (position: `[-5, 25, -30]`)
- **Actions** :
- Joue l'audio `searchingProblem`
- Affiche l'objet "central" (position: `[1, 15, -45]`)
- **Attente** : Le joueur interagit avec l'objet "central"
### 8. `preparation`
- **Déclenchement** : Quand le joueur interagit avec l'objet "central"
- **Actions** :
- Bloque le mouvement (`setCanMove(false)`)
- Cache l'objet "central"
### 9. `outOfFabrik`
- **Déclenchement** : (non implémenté pour le moment)
- **Action** : Transition vers l'étape finale
---
## Fichiers clés
| Fichier | Rôle |
| --------------------------------------- | --------------------------------------------------------- |
| `src/managers/stores/useGameStore.ts` | Store Zustand pour l'état global du jeu |
| `src/components/game/GameFlow.tsx` | Gère les transitions automatiques et la lecture audio |
| `src/components/ui/IntroUI.tsx` | Affiche l'input pour le prénom et le message de bienvenue |
| `src/components/zone/ZoneDetection.tsx` | Détecte quand le joueur entre dans une zone |
| `src/world/GameStageContent.tsx` | Monte les contenus de mission dans la scène |
| `src/data/audioConfig.ts` | Chemins des fichiers audio |
| `src/data/zones.ts` | Configuration des zones de transition |
---
## Configuration audio
```typescript
// src/data/audioConfig.ts
export const AUDIO_PATHS = {
intro: "/sounds/fa.mp3",
bienvenue: "/sounds/fa.mp3",
alertCentral: "/sounds/fa.mp3",
searchingProblem: "/sounds/fa.mp3",
};
```
---
## Configuration des zones
```typescript
// src/data/zones.ts
export const ZONES: Zone[] = [
{
id: "fabrikExit",
position: [-5, 25, -15],
radius: 10,
height: 20,
targetStep: "mission2",
},
{
id: "searchingProblemZone",
position: [-5, 25, -30],
radius: 10,
height: 20,
targetStep: "searching_problem",
},
];
```
---
## Store Zustand
```typescript
// src/managers/stores/useGameStore.ts
interface GameState {
mainState: MainGameState;
missionFlow: {
activityCity: boolean;
canMove: boolean;
playerName: string;
};
}
```
---
## Debug
En mode debug (`?debug` dans l'URL), on peut voir :
- **Game Step** : L'étape actuelle dans le panneau lil-gui
- **Player Position** : Position X, Y, Z du joueur en temps réel
- **Zone Visualization** : Anneaux visuels au sol pour les zones + cylindres transparents
---
## Notes techniques
- Le mouvement du joueur est bloqué tant que `canMove` est `false`
- Le store Zustand (`useGameStore`) est la source principale de vérité
- `GameStepManager` synchronise automatiquement avec le store Zustand lors des transitions
- Les transitions via les zones utilisent `GameStepManager.transitionTo()` qui met à jour le store
- L'audio utilise un callback `onEnded` pour déclencher les transitions automatiques
+1 -1
View File
@@ -32,7 +32,7 @@ This keeps hand tracking active while the player is inside an interaction zone,
The production repair activation conditions are: The production repair activation conditions are:
- active `mainState` is `bike`, `pylone`, or `ferme` - active `mainState` is `ebike`, `pylon`, or `farm`
- the active mission step is `inspected`, `repairing`, `reassembling`, or `done` - 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. This keeps the webcam off during `waiting`, `fragmented`, and `scanning`, then enables hand input only when the repair flow is expected to use hands.
+1 -1
View File
@@ -184,7 +184,7 @@ Input is ignored while:
- the settings menu is open - the settings menu is open
- a cinematic is playing - a cinematic is playing
Movement lock is read separately from `useRepairMovementLocked`, but that hook currently returns `false` on this branch. Movement lock is read separately from `useRepairMovementLocked`, which locks the player during focused repair steps.
## UI Prompt ## UI Prompt
+1 -1
View File
@@ -74,7 +74,7 @@ These vegetation and crop assets account for almost all of the current `~69M` tr
## Debug Performance Controls ## Debug Performance Controls
The next useful runtime tool is a debug-only performance folder that can isolate model families. This should be mounted only when `?debug` is enabled. The debug-only performance folder can isolate model families when `?debug` is enabled.
Proposed controls: Proposed controls:
+4 -5
View File
@@ -14,7 +14,6 @@ The store owns the `missionFlow` slice:
```ts ```ts
missionFlow: { missionFlow: {
step: GameStep;
activityCity: boolean; activityCity: boolean;
playerName: string; playerName: string;
canMove: boolean; canMove: boolean;
@@ -31,14 +30,14 @@ Managers stay responsible for local runtime services:
- `AudioManager` owns audio elements, audio pools, music playback, category volume, and stereo pan. - `AudioManager` owns audio elements, audio pools, music playback, category volume, and stereo pan.
- `InteractionManager` owns transient focused/nearby/held interaction handles. - `InteractionManager` owns transient focused/nearby/held interaction handles.
Mission progression is not owned by a manager. Components update the store through explicit actions such as `setFlowStep`, `setCanMove`, `showDialog`, and `hideDialog`. Mission progression is not owned by a manager. Components update the store through explicit actions such as `setIntroStep`, `setCanMove`, `showDialog`, and `hideDialog`.
## Runtime Components ## Runtime Components
- `src/components/game/GameFlow.tsx` reacts to `missionFlow.step` and triggers one-off side effects such as intro audio and movement unlocks. - `src/components/game/GameFlow.tsx` reacts to intro state and triggers one-off side effects such as intro audio and movement unlocks.
- `src/components/zone/ZoneDetection.tsx` reads the camera position and moves the flow to a target step when the player enters a configured zone. - `src/components/zone/ZoneDetection.tsx` reads the camera position and moves the flow to a target step when the player enters a configured zone.
- `src/components/three/interaction/CentralObject.tsx` and `VillageoisHelperObject.tsx` expose temporary interactive mission objects. - `src/world/GameStageContent.tsx` mounts repair games and their mission-start triggers.
- `src/pages/page.tsx` mounts mission HTML overlays: `IntroUI`, `BienvenueDisplay`, and `DialogMessage`. - `src/pages/page.tsx` mounts mission HTML overlays: `IntroUI`, `DialogMessage`, and subtitles.
- `src/world/player/PlayerController.tsx` reads `missionFlow.canMove` as an additional movement lock. - `src/world/player/PlayerController.tsx` reads `missionFlow.canMove` as an additional movement lock.
## Step Sequence ## Step Sequence
+9 -9
View File
@@ -9,10 +9,10 @@ The repair game is the current core gameplay loop. It gives three missions the s
Implemented missions: Implemented missions:
| Mission | Object | Role | | Mission | Object | Role |
| -------- | ------------- | --------------------------------------------- | | ------- | ------------- | --------------------------------------------- |
| `bike` | E-bike | Repair a damaged cooling core | | `ebike` | E-bike | Repair a damaged cooling core |
| `pylone` | Power pylon | Restore relay/panel-like broken parts | | `pylon` | Power pylon | Restore relay/panel-like broken parts |
| `ferme` | Vertical farm | Stabilize irrigation/sensor-like broken parts | | `farm` | Vertical farm | Stabilize irrigation/sensor-like broken parts |
## Main Files ## Main Files
@@ -79,7 +79,7 @@ src/managers/stores/useGameStore.ts
- `setMissionStep(mission, nextStep)` - `setMissionStep(mission, nextStep)`
- `completeMission(mission)` - `completeMission(mission)`
The important architectural choice is that reusable repair components do not call `setBikeState`, `setPyloneState`, or `setFermeState` directly. They use generic mission actions so the same component can run for all three missions. The important architectural choice is that reusable repair components do not call `setEbikeState`, `setPylonState`, or `setFarmState` directly. They use generic mission actions so the same component can run for all three missions.
## Data-Driven Mission Config ## Data-Driven Mission Config
@@ -159,7 +159,7 @@ The repair case appears near the mission object. The player can:
Both paths move to `fragmented`. Both paths move to `fragmented`.
Important current detail: `useRepairMovementLocked()` currently returns `false`, so the movement-lock rule and indicator are present but disabled in the current branch. `useRepairMovementLocked()` locks player movement during focused repair steps and drives the repair movement indicator.
### Fragmented ### Fragmented
@@ -324,9 +324,9 @@ src/world/GameStageContent.tsx
Current positions: Current positions:
```tsx ```tsx
<RepairGame mission="bike" position={[8, 0, -6]} /> <RepairGame mission="ebike" position={[42.2399, 4.5484, 34.6468]} />
<RepairGame mission="pylone" position={[64, 0, -66]} /> <RepairGame mission="pylon" position={[64, 0, -66]} />
<RepairGame mission="ferme" position={[-24, 0, 42]} /> <RepairGame mission="farm" position={[-24, 0, 42]} />
``` ```
Only the repair game whose `mission` matches `useGameStore().mainState` renders active content. Only the repair game whose `mission` matches `useGameStore().mainState` renders active content.
+11 -2
View File
@@ -72,14 +72,23 @@ It tracks:
- `gameMapLoaded`: map data and visible map nodes settled - `gameMapLoaded`: map data and visible map nodes settled
- `gameStageLoaded`: Rapier gameplay stage mounted - `gameStageLoaded`: Rapier gameplay stage mounted
- `showGameStage`: true when the map is ready enough to mount gameplay content - `showGameStage`: true when the map is ready enough to mount gameplay content
- `gameplayReady`: true when map, stage, and octree are all ready - `shadowsReady`: renderer, shadow lights, and scene matrices have been forced once after the scene is mounted
- `gameplayReady`: true when map, stage, octree, and the shadow warmup are all ready
The final game-scene readiness condition is: The base game-scene readiness condition before the shadow warmup is:
```ts ```ts
showGameStage && gameStageLoaded && octree !== null; showGameStage && gameStageLoaded && octree !== null;
``` ```
After that condition is met, `SceneShadowWarmup` runs one final loading step:
```txt
Activation des ombres -> Ombres prêtes -> Gameplay prêt
```
This keeps the loading overlay visible until the renderer shadow map, shadow-casting light, and mounted scene graph have all been explicitly refreshed.
The debug physics scene is ready when: The debug physics scene is ready when:
```ts ```ts
+19 -19
View File
@@ -29,9 +29,9 @@ They are under `src/managers/stores/` because they are shared runtime state, not
## Store Responsibilities ## Store Responsibilities
| Store | Responsibility | | Store | Responsibility |
| ------------------ | ----------------------------------------------------------------- | | ------------------ | ------------------------------------------------------------- |
| `useGameStore` | Durable game progression, mission steps, cinematic input lock | | `useGameStore` | Durable game progression, mission steps, cinematic input lock |
| `useSettingsStore` | Menu visibility, volumes, subtitle options, repair-runtime toggle | | `useSettingsStore` | Menu visibility, volumes, and subtitle options |
| `useSubtitleStore` | Currently displayed subtitle cue | | `useSubtitleStore` | Currently displayed subtitle cue |
## Managers vs Stores ## Managers vs Stores
@@ -65,18 +65,18 @@ Main states:
| Main state | Role | | Main state | Role |
| ---------- | ------------------------------- | | ---------- | ------------------------------- |
| `intro` | Onboarding and opening sequence | | `intro` | Onboarding and opening sequence |
| `bike` | E-bike repair sequence | | `ebike` | E-bike repair sequence |
| `pylone` | Power pylon repair sequence | | `pylon` | Power pylon repair sequence |
| `ferme` | Vertical farm repair sequence | | `farm` | Vertical farm repair sequence |
| `outro` | Ending sequence | | `outro` | Ending sequence |
Other important state: Other important state:
- `isCinematicPlaying` - `isCinematicPlaying`
- `intro` - `intro`
- `bike` - `ebike`
- `pylone` - `pylon`
- `ferme` - `farm`
- `outro` - `outro`
Mission steps: Mission steps:
@@ -125,28 +125,28 @@ For development and debug tooling, direct setters also exist:
```ts ```ts
const setMainState = useGameStore((state) => state.setMainState); const setMainState = useGameStore((state) => state.setMainState);
setMainState("bike"); setMainState("ebike");
``` ```
Direct setters are useful for debug panels, but production gameplay should prefer business actions such as: Direct setters are useful for debug panels, but production gameplay should prefer business actions such as:
- `advanceGameState` - `advanceGameState`
- `completeBike` - `completeEbike`
- `completePylone` - `completePylon`
- `completeFerme` - `completeFarm`
- `completeMission` - `completeMission`
Mission gameplay that can target `bike`, `pylone`, or `ferme` should prefer generic mission actions: Mission gameplay that can target `ebike`, `pylon`, or `farm` should prefer generic mission actions:
```ts ```ts
const setMissionStep = useGameStore((state) => state.setMissionStep); const setMissionStep = useGameStore((state) => state.setMissionStep);
const completeMission = useGameStore((state) => state.completeMission); const completeMission = useGameStore((state) => state.completeMission);
setMissionStep("bike", "inspected"); setMissionStep("ebike", "inspected");
completeMission("bike"); completeMission("ebike");
``` ```
This keeps reusable gameplay components such as `RepairGame` from duplicating mission-specific branches like `setBikeState`, `setPyloneState`, and `setFermeState`. This keeps reusable gameplay components such as `RepairGame` from duplicating mission-specific branches like `setEbikeState`, `setPylonState`, and `setFarmState`.
## Settings Store ## Settings Store
@@ -188,9 +188,9 @@ State/actions:
Current production repair placement: Current production repair placement:
```tsx ```tsx
<RepairGame mission="bike" position={[8, 0, -6]} /> <RepairGame mission="ebike" position={[42.2399, 4.5484, 34.6468]} />
<RepairGame mission="pylone" position={[64, 0, -66]} /> <RepairGame mission="pylon" position={[64, 0, -66]} />
<RepairGame mission="ferme" position={[-24, 0, 42]} /> <RepairGame mission="farm" position={[-24, 0, 42]} />
``` ```
`RepairGame` reads the active mission step from the store and writes transitions through generic actions such as `setMissionStep` and `completeMission`. `RepairGame` reads the active mission step from the store and writes transitions through generic actions such as `setMissionStep` and `completeMission`.
+2 -3
View File
@@ -72,7 +72,7 @@ Use the trash button in `Selection` to delete the selected node from the map tre
| -------------------- | -------------------------- | | -------------------- | -------------------------- |
| Select object | Click object | | Select object | Click object |
| Toggle multi-select | `Shift` + right click | | Toggle multi-select | `Shift` + right click |
| Deselect | `Esc` or click empty space | | Deselect | `Esc` |
| Lock selection | `Lock` button in Selection | | Lock selection | `Lock` button in Selection |
| Clear selection | `X` button in Selection | | Clear selection | `X` button in Selection |
| Translate mode | `T` | | Translate mode | `T` |
@@ -91,7 +91,7 @@ The `Selection` section shows the selected object name and its index in `public/
- Click an object to select it. - Click an object to select it.
- Use `Shift + right click` on objects to add or remove them from a multi-selection. - Use `Shift + right click` on objects to add or remove them from a multi-selection.
- When several objects are selected, the gizmo appears on the selection group and applies translate, rotate, or scale to each selected node. - When several objects are selected, the gizmo appears on the selection group and applies translate, rotate, or scale to each selected node.
- Click empty space or press `Esc` to clear the selection. - Press `Esc` to clear the selection.
- Use the `X` button to clear the selection explicitly. - Use the `X` button to clear the selection explicitly.
- Use the `Lock` button to protect the current selection while editing. - Use the `Lock` button to protect the current selection while editing.
- Use the scale fields to edit X/Y/Z scale precisely. - Use the scale fields to edit X/Y/Z scale precisely.
@@ -108,7 +108,6 @@ This is intended for map objects that should sit on the ground. Disable it when
When selection is locked: When selection is locked:
- clicking another object does not change the selection - clicking another object does not change the selection
- clicking empty space does not clear the selection
- pressing `Esc` does not clear the selection - pressing `Esc` does not clear the selection
- the `X` button still clears the selection intentionally - the `X` button still clears the selection intentionally
+9 -10
View File
@@ -37,7 +37,7 @@ This document lists the user-visible and developer-facing features implemented i
- Input lock while the settings menu is open - Input lock while the settings menu is open
- Input lock while a cinematic is playing - Input lock while a cinematic is playing
- Octree collision against dedicated map collision nodes, currently scoped to the `terrain` node - Octree collision against dedicated map collision nodes, currently scoped to the `terrain` node
- Repair movement-lock hook and indicator exist, but the hook currently returns `false`, so movement is not locked during repair on the current branch - Repair movement lock during focused repair steps, with a matching UI indicator
## Physics And Collision ## Physics And Collision
@@ -63,12 +63,12 @@ This document lists the user-visible and developer-facing features implemented i
## Repair Gameplay ## Repair Gameplay
- Reusable `RepairGame` mounted for `bike`, `pylone`, and `ferme` - Reusable `RepairGame` mounted for `ebike`, `pylon`, and `farm`
- Mission progression driven by Zustand and shared `MissionStep` types - Mission progression driven by Zustand and shared `MissionStep` types
- Production repair positions: - Production repair positions:
- `bike` at `[8, 0, -6]` - `ebike` at `[42.2399, 4.5484, 34.6468]`
- `pylone` at `[64, 0, -66]` - `pylon` at `[64, 0, -66]`
- `ferme` at `[-24, 0, 42]` - `farm` at `[-24, 0, 42]`
- Debug physics repair playground zones for all three missions - Debug physics repair playground zones for all three missions
- Data-driven mission config in `src/data/gameplay/repairMissions.ts` - Data-driven mission config in `src/data/gameplay/repairMissions.ts`
- Mission flow: `locked -> waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done` - Mission flow: `locked -> waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done`
@@ -95,7 +95,7 @@ This document lists the user-visible and developer-facing features implemented i
## Game Progression Store ## Game Progression Store
- Zustand `useGameStore` for durable gameplay progression - Zustand `useGameStore` for durable gameplay progression
- Main states: `intro`, `bike`, `pylone`, `ferme`, `outro` - Main states: `intro`, `ebike`, `pylon`, `farm`, `outro`
- Per-mission repair step state - Per-mission repair step state
- Per-mission completion flags - Per-mission completion flags
- Generic mission helpers: `setMissionStep`, `completeMission`, `advanceGameState`, `rewindGameState`, `resetGame` - Generic mission helpers: `setMissionStep`, `completeMission`, `advanceGameState`, `rewindGameState`, `resetGame`
@@ -108,12 +108,11 @@ This document lists the user-visible and developer-facing features implemented i
- Music, SFX, and dialogue volume sliders - Music, SFX, and dialogue volume sliders
- Subtitle visibility toggle - Subtitle visibility toggle
- Subtitle language choice between French and English - Subtitle language choice between French and English
- Repair-runtime choice between JavaScript and Python modes stored in settings
- Quit action that clears browser-accessible cookies and returns to `/` - Quit action that clears browser-accessible cookies and returns to `/`
- Crosshair overlay - Crosshair overlay
- Interaction prompt - Interaction prompt
- Subtitle overlay - Subtitle overlay
- Repair movement-lock indicator component, currently inactive because the lock hook returns `false` - Repair movement-lock indicator
- Debug overlay layout - Debug overlay layout
- Scene loading overlay - Scene loading overlay
@@ -192,7 +191,7 @@ This document lists the user-visible and developer-facing features implemented i
- Debug game-state panel - Debug game-state panel
- Debug hand-tracking panel - Debug hand-tracking panel
- Physics test scene with floor, grabbable object, trigger object, repair zones, and animated model preview - Physics test scene with floor, grabbable object, trigger object, repair zones, and animated model preview
- Animated `electricienne_animated` model preview restored in the debug physics scene - Animated `electricienne-animated` model preview restored in the debug physics scene
## Map And Content Editor ## Map And Content Editor
@@ -230,7 +229,7 @@ This document lists the user-visible and developer-facing features implemented i
- Technical docs for architecture, scene runtime, repair game, interaction, editor, audio, hand tracking, Zustand, Three debugging, animation, and target architecture - Technical docs for architecture, scene runtime, repair game, interaction, editor, audio, hand tracking, Zustand, Three debugging, animation, and target architecture
- User docs for implemented features, main feature, editor usage, and code-review preparation - User docs for implemented features, main feature, editor usage, and code-review preparation
## Not Implemented Or Incomplete ## Known Gaps
- Complete production mission manager/orchestrator - Complete production mission manager/orchestrator
- Full mission HUD or minimap - Full mission HUD or minimap
+46
View File
@@ -0,0 +1,46 @@
# Galerie des modèles
La galerie est disponible sur `/gallery`. Elle permet de parcourir les modèles 3D présents dans `public/models/` sans lancer la boucle de gameplay principale.
## Objectif
Cette page sert à remercier et valoriser le travail des designers du projet La Fabrik. Chaque modèle est affiché dans un canvas dédié, avec la même skybox et le même lighting que l'expérience principale.
## Utilisation
1. Ouvrir `/gallery`.
2. Utiliser les flèches en bas de l'écran pour passer au modèle précédent ou suivant.
3. Tourner autour du modèle avec la souris ou le doigt.
4. Utiliser le bouton de réglages à droite pour ouvrir ou fermer le panneau lumière.
5. Lire le diagnostic texture discret pour savoir si le modèle chargé semble correct côté textures.
## Fonctionnement
- La liste des modèles est déclarée dans `src/data/galleryModels.ts`.
- Le viewer utilise `@react-three/fiber` et `@react-three/drei`.
- `OrbitControls` permet de manipuler la caméra autour du modèle.
- `Bounds` et `Center` recadrent automatiquement le modèle actif.
- `SkyModel` réutilise la skybox du jeu, avec un matériau non éclairé uniquement dans la galerie pour éviter que certaines faces deviennent noires avec une caméra orbitale libre.
- Les lumières reprennent les valeurs par défaut du jeu, puis peuvent être ajustées dans le panneau latéral.
- `OrbitControls` autorise une orbite verticale complète pour inspecter le dessous des modèles.
- Le viewer désactive les normal maps dans la preview pour limiter les coutures visibles sur certains exports découpés en plusieurs meshes.
- Les animations GLTF présentes dans un modèle sont lancées automatiquement.
- Un diagnostic simple inspecte les matériaux chargés pour signaler les textures absentes ou non exploitables.
## Ajouter un modèle
1. Ajouter le dossier du modèle dans `public/models/{nom}`.
2. Vérifier que le modèle possède un fichier chargeable, par exemple `model.gltf`, `model.glb` ou un nom explicite comme `potager.gltf`.
3. Ajouter une entrée dans `src/data/galleryModels.ts` avec un `id`, un `name` et un `path`.
Exemple :
```ts
{ id: "nouveau-modele", name: "Nouveau modèle", path: "/models/nouveau-modele/model.gltf" }
```
## Limites connues
- Le navigateur ne liste pas automatiquement les dossiers de `public/models/`, donc la liste reste déclarative.
- Les modèles très lourds peuvent prendre du temps à charger.
- La galerie est un viewer simple : elle ne remplace pas les outils d'inspection avancée comme Blender ou le viewer d'upload.
+8 -8
View File
@@ -8,7 +8,7 @@ The main feature is a reusable repair flow mounted in the production game scene.
The current user flow is: The current user flow is:
1. Enter a mission state such as `bike`, `pylone`, or `ferme`. 1. Enter a mission state such as `ebike`, `pylon`, or `farm`.
2. Move close to the active repair object in the game scene. 2. Move close to the active repair object in the game scene.
3. Aim at the object and press the interaction key when prompted. 3. Aim at the object and press the interaction key when prompted.
4. The mission step moves from `waiting` to `inspected`. 4. The mission step moves from `waiting` to `inspected`.
@@ -21,7 +21,7 @@ The current user flow is:
11. Move each scanned broken part into a compatible placeholder so the damaged parts are stored in the case. 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. 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`. 13. The exploded object animates back into its assembled form with completion particles, then moves to `done`.
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`. 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 `farm`.
## Why It Matters ## Why It Matters
@@ -39,11 +39,11 @@ In `inspected`, `RepairGame` can also move to `fragmented`. Keyboard input goes
In `fragmented`, the repair object is rendered with `ExplodableModel`, then automatically advances to `scanning`. In `scanning`, the exploded model remains visible, a blue scan visual moves from part to part, and a red halo/wire marker plus the configured broken UI video stay attached to configured broken parts after the scanner reaches them. The scan matches configured broken parts by `nodeName` and reports diagnostics when a configured node is missing. In `repairing`, the case opens in a larger focused transform, `RepairCaseModel` traverses the case GLTF for empty nodes named `placeholder_*`, several grabbable replacement parts appear on those slot positions, and releasing a part near a slot snaps it into place with a short GSAP animation. Scanned broken parts are also rendered as grabbable objects and must be deposited into a compatible slot before the final install target validates. If `brokenParts[].caseSlotName` is configured, that broken part snaps only to the matching slot; otherwise it can use any available slot. If the current case asset has no slot nodes, the flow keeps using fallback focus positions and logs the fallback. Replacement parts show green or red placement feedback after snapping, broken parts show stored feedback after deposit, and the install target gives a short blocked feedback if the player tries to validate too early. The install target only validates when the configured correct replacement part is placed and all scanned broken parts have been deposited. In `reassembling`, the exploded model animates back into its assembled position with green completion particles before the flow moves to `done`. In `done`, the repaired object remains visible with a completion target; validating closes the repair case first, then plays the case exit animation before advancing the global mission progression. In `fragmented`, the repair object is rendered with `ExplodableModel`, then automatically advances to `scanning`. In `scanning`, the exploded model remains visible, a blue scan visual moves from part to part, and a red halo/wire marker plus the configured broken UI video stay attached to configured broken parts after the scanner reaches them. The scan matches configured broken parts by `nodeName` and reports diagnostics when a configured node is missing. In `repairing`, the case opens in a larger focused transform, `RepairCaseModel` traverses the case GLTF for empty nodes named `placeholder_*`, several grabbable replacement parts appear on those slot positions, and releasing a part near a slot snaps it into place with a short GSAP animation. Scanned broken parts are also rendered as grabbable objects and must be deposited into a compatible slot before the final install target validates. If `brokenParts[].caseSlotName` is configured, that broken part snaps only to the matching slot; otherwise it can use any available slot. If the current case asset has no slot nodes, the flow keeps using fallback focus positions and logs the fallback. Replacement parts show green or red placement feedback after snapping, broken parts show stored feedback after deposit, and the install target gives a short blocked feedback if the player tries to validate too early. The install target only validates when the configured correct replacement part is placed and all scanned broken parts have been deposited. In `reassembling`, the exploded model animates back into its assembled position with green completion particles before the flow moves to `done`. In `done`, the repaired object remains visible with a completion target; validating closes the repair case first, then plays the case exit animation before advancing the global mission progression.
The mission config now carries the mission-specific variations. `bike` repairs one cooling core, `pylone` scans and stores both the lamp relay and a damaged panel with slower scan/reassembly timing, and `ferme` scans and stores an irrigation pump plus humidity sensor with faster scan/reassembly timing. The mission config now carries the mission-specific variations. `ebike` repairs one cooling core, `pylon` scans and stores both the lamp relay and a damaged panel with slower scan/reassembly timing, and `farm` scans and stores an irrigation pump plus humidity sensor with faster scan/reassembly timing.
## Key Files ## Key Files
- `src/world/GameStageContent.tsx` mounts production `RepairGame` instances for `bike`, `pylone`, and `ferme`. - `src/world/GameStageContent.tsx` mounts production `RepairGame` instances for `ebike`, `pylon`, and `farm`.
- `src/components/three/gameplay/RepairCompletionStep.tsx` renders the final repaired object, completion target, case exit animation, and mission UI prompt. - `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/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/RepairBrokenPartHighlight.tsx` renders the red halo and wire marker around detected broken parts during scanning.
@@ -56,16 +56,16 @@ The mission config now carries the mission-specific variations. `bike` repairs o
- `src/components/three/gameplay/RepairPromptVideo.tsx` renders `.webm` prompts inside the 3D scene. - `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/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/three/gameplay/RepairScanVisual.tsx` renders the scan halo and scan line around the active part.
- `src/components/ui/RepairMovementLockIndicator.tsx` renders the HTML indicator intended for repair movement lock. - `src/components/ui/RepairMovementLockIndicator.tsx` renders the HTML repair movement-lock indicator.
- `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/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/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, but currently returns `false`. - `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/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/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/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/repairCaseConfig.ts` stores repair case model, sound, and animation constants.
- `src/data/gameplay/repairGameConfig.ts` stores repair flow timing 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/data/gameplay/repairMissions.ts` stores reusable repair mission config for `ebike`, `pylon`, and `farm`.
- `src/managers/stores/useGameStore.ts` stores mission progression state and generic mission step helpers. - `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. - `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.
@@ -73,7 +73,7 @@ The mission config now carries the mission-specific variations. `bike` repairs o
The production repair flow currently requires: The production repair flow currently requires:
- the active `mainState` to be one of `bike`, `pylone`, or `ferme` - the active `mainState` to be one of `ebike`, `pylon`, or `farm`
- `GameStageContent` mounted inside the game scene Rapier `Physics` boundary - `GameStageContent` mounted inside the game scene Rapier `Physics` boundary
- model assets available under `public/models/` - model assets available under `public/models/`
- sound assets available under `public/sounds/` - sound assets available under `public/sounds/`
+1
View File
@@ -22,6 +22,7 @@
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"three": "0.182.0", "three": "0.182.0",
"three-stdlib": "^2.36.1",
"zustand": "^5.0.12" "zustand": "^5.0.12"
}, },
"devDependencies": { "devDependencies": {
+2
View File
@@ -14,6 +14,7 @@
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check .", "format:check": "prettier --check .",
"map:transform": "node scripts/transformMap.cjs",
"preview": "vite preview", "preview": "vite preview",
"typecheck": "tsc -b" "typecheck": "tsc -b"
}, },
@@ -32,6 +33,7 @@
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"three": "0.182.0", "three": "0.182.0",
"three-stdlib": "^2.36.1",
"zustand": "^5.0.12" "zustand": "^5.0.12"
}, },
"devDependencies": { "devDependencies": {
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.
+4 -228
View File
@@ -584,22 +584,6 @@
} }
] ]
}, },
{
"name": "arbre",
"type": "Object3D",
"position": [50.072, 2.2583, 78.7082],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "arbre",
"type": "Mesh",
"position": [50.072, 2.2583, 78.7082],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "arbre", "name": "arbre",
"type": "Object3D", "type": "Object3D",
@@ -888,22 +872,6 @@
} }
] ]
}, },
{
"name": "arbre",
"type": "Object3D",
"position": [59.1794, 2.2557, 73.349],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "arbre",
"type": "Mesh",
"position": [59.1794, 2.2557, 73.349],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "arbre", "name": "arbre",
"type": "Object3D", "type": "Object3D",
@@ -1112,22 +1080,6 @@
} }
] ]
}, },
{
"name": "arbre",
"type": "Object3D",
"position": [74.0452, 2.309, 59.2374],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "arbre",
"type": "Mesh",
"position": [74.0452, 2.309, 59.2374],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "arbre", "name": "arbre",
"type": "Object3D", "type": "Object3D",
@@ -2754,22 +2706,6 @@
} }
] ]
}, },
{
"name": "buisson",
"type": "Object3D",
"position": [73.7334, 1.1132, 54.1382],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "buisson",
"type": "Mesh",
"position": [73.7334, 1.1132, 54.1382],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "buisson", "name": "buisson",
"type": "Object3D", "type": "Object3D",
@@ -3330,22 +3266,6 @@
} }
] ]
}, },
{
"name": "buisson",
"type": "Object3D",
"position": [67.9046, 0.5562, 74.8395],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "buisson",
"type": "Mesh",
"position": [67.9046, 0.5562, 74.8395],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "buisson", "name": "buisson",
"type": "Object3D", "type": "Object3D",
@@ -3714,22 +3634,6 @@
} }
] ]
}, },
{
"name": "buisson",
"type": "Object3D",
"position": [73.5205, 0.3748, 75.9136],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "buisson",
"type": "Mesh",
"position": [73.5205, 0.3748, 75.9136],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "buisson", "name": "buisson",
"type": "Object3D", "type": "Object3D",
@@ -3858,22 +3762,6 @@
} }
] ]
}, },
{
"name": "buisson",
"type": "Object3D",
"position": [66.999, 1.7223, 48.3983],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "buisson",
"type": "Mesh",
"position": [66.999, 1.7223, 48.3983],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "buisson", "name": "buisson",
"type": "Object3D", "type": "Object3D",
@@ -4914,22 +4802,6 @@
} }
] ]
}, },
{
"name": "buisson",
"type": "Object3D",
"position": [61.3924, 0.4621, 82.2195],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "buisson",
"type": "Mesh",
"position": [61.3924, 0.4621, 82.2195],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "buisson", "name": "buisson",
"type": "Object3D", "type": "Object3D",
@@ -5122,22 +4994,6 @@
} }
] ]
}, },
{
"name": "buisson",
"type": "Object3D",
"position": [61.1082, 0.6236, 77.7642],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "buisson",
"type": "Mesh",
"position": [61.1082, 0.6236, 77.7642],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "buisson", "name": "buisson",
"type": "Object3D", "type": "Object3D",
@@ -5170,22 +5026,6 @@
} }
] ]
}, },
{
"name": "buisson",
"type": "Object3D",
"position": [53.1033, 1.6054, 63.3842],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "buisson",
"type": "Mesh",
"position": [53.1033, 1.6054, 63.3842],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "buisson", "name": "buisson",
"type": "Object3D", "type": "Object3D",
@@ -5266,22 +5106,6 @@
} }
] ]
}, },
{
"name": "buisson",
"type": "Object3D",
"position": [59.647, 1.5484, 59.429],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "buisson",
"type": "Mesh",
"position": [59.647, 1.5484, 59.429],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "buisson", "name": "buisson",
"type": "Object3D", "type": "Object3D",
@@ -5410,22 +5234,6 @@
} }
] ]
}, },
{
"name": "buisson",
"type": "Object3D",
"position": [69.2496, 0.6286, 71.5478],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "buisson",
"type": "Mesh",
"position": [69.2496, 0.6286, 71.5478],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "buisson", "name": "buisson",
"type": "Object3D", "type": "Object3D",
@@ -6226,22 +6034,6 @@
} }
] ]
}, },
{
"name": "buisson",
"type": "Object3D",
"position": [58.3126, 0.686, 77.9828],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "buisson",
"type": "Mesh",
"position": [58.3126, 0.686, 77.9828],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "buisson", "name": "buisson",
"type": "Object3D", "type": "Object3D",
@@ -37602,23 +37394,6 @@
"rotation": [0, 0, 0], "rotation": [0, 0, 0],
"scale": [1, 1, 1], "scale": [1, 1, 1],
"children": [ "children": [
{
"name": "ebike",
"type": "Object3D",
"role": "group",
"position": [0, 0, 0],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{
"name": "ebike",
"type": "Object3D",
"position": [42.2399, 4.5484, 34.6468],
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
}
]
},
{ {
"name": "zone1_residence", "name": "zone1_residence",
"type": "Object3D", "type": "Object3D",
@@ -39565,7 +39340,8 @@
"rotation": [0, 0.0027, 0.0819], "rotation": [0, 0.0027, 0.0819],
"scale": [1, 1, 1] "scale": [1, 1, 1]
} }
] ],
"id": "repair:pylon"
}, },
{ {
"name": "pylone", "name": "pylone",
@@ -40476,14 +40252,14 @@
"name": "lafabrik", "name": "lafabrik",
"type": "Object3D", "type": "Object3D",
"position": [59.4973, 6.2746, 64.6354], "position": [59.4973, 6.2746, 64.6354],
"rotation": [-3.1416, -0.7309, -3.1416], "rotation": [-3.1416, 2.4107, -3.1416],
"scale": [1, 2, 1], "scale": [1, 2, 1],
"children": [ "children": [
{ {
"name": "lafabrik", "name": "lafabrik",
"type": "Mesh", "type": "Mesh",
"position": [59.4973, 6.2746, 64.6354], "position": [59.4973, 6.2746, 64.6354],
"rotation": [-3.1416, -0.7309, -3.1416], "rotation": [-3.1416, 2.4107, -3.1416],
"scale": [1, 2, 1] "scale": [1, 2, 1]
} }
] ]
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