Commit Graph

662 Commits

Author SHA1 Message Date
Tom Boullay 9841b14388 feat(repair): per-node scan voicelines, refroidisseur diagnostic gates scanning -> repairing
The ebike refroidisseur diagnostic line used to fire on the
'repairing' step via EbikeRepairNarrator, AFTER the scan sequence had
already raced through every part on a fixed timer. Visually the red
broken-part highlight appeared and disappeared before the player ever
heard which part was actually broken.

Now the scan sequence itself can carry per-node voice lines via a new
optional config field on each broken part. When the scan lands on a
part that has a voice line:
- the audio is played immediately (with its subtitle);
- the red broken-part highlight is on screen the entire time
  (existing cumulative highlight behaviour from RepairScanSequence);
- the next-part advance is gated on the audio's  event;
- a 15s ceiling fallback (and per-part fallback when manifest is
  missing) keeps the flow from stalling if the audio never resolves.

Cancel paths (component unmount, mission switch, debug rewind) pause
the audio, clear the subtitle, and drop both the  listener and
the fallback timer to avoid leaks or double-advances.

Changes:
- types/repairMission: new optional .
- data/repairMissions: ebike refroidisseur broken part now declares
  .
- RepairScanSequence: per-part effect now branches on .
  Default per-part timer is preserved for parts without an audio line
  (incl. all pylon/farm broken parts and ebike's non-diagnostic parts).
- EbikeRepairNarrator: drop the 'repairing' entry from the step ->
  dialogue map (the diagnostic now plays earlier, during scanning).
  'fragmented' (scan hint) and 'done' (repaired voiceover) are
  unchanged.

End result: player hears 'le refroidisseur a laché...' exactly while
the red sphere is pulsing on the cooling node, and the case opens for
the repairing step the moment the line ends.
2026-06-03 04:20:14 +02:00
Tom Boullay 317db48bcc feat(repair): make fragmented -> scanning event-driven via onSplitSettled
The fragmented -> scanning transition used to fire on a blind
setTimeout of REPAIR_FRAGMENTATION_SEQUENCE_SECONDS regardless of
whether the explode lerp had actually finished. With the new sphere-
reveal flow this got out of sync (sphere grows for 2.5s before
fragmented even mounts), so the timer could fire too early or while
the parts were still flying out.

Now the ExplodedModel emits a single 'settled' callback when its
internal lerp converges on its target (1 = fully exploded, 0 = fully
reassembled). RepairGame listens for settled-at-1 on the fragmented
ExplodableModel and advances to scanning on that event.

The legacy timer is kept as a generous safety net
(REPAIR_FRAGMENTATION_SEQUENCE_SECONDS + 2 seconds) so that if the
model fails to load (no parts -> no settled event ever fires) the
flow can never get stuck on the fragmented step.

Changes:
- ExplodedModel.ts:
  - new ExplodedModelOptions.onSettled: (settledAt: 0 | 1) => void
  - track settledAtTarget to ensure the callback fires exactly once
    per lerp (re-armed when setSplit() flips the target).
- ExplodableModel.tsx: new onSplitSettled prop, forwarded to the
  underlying ExplodedModel via a stable useCallback that reads the
  latest prop through a ref so the instance is not recreated mid-anim.
- RepairGame.tsx:
  - wire onSplitSettled on the fragmented ExplodableModel to
    setMissionStep(mission, 'scanning').
  - keep the existing setTimeout but extend it as a fallback only.

Pylon and farm benefit from the same fix automatically since they
share the same RepairGame fragmented branch.
2026-06-03 04:18:10 +02:00
Tom Boullay fe30596a5a feat(ebike): auto-advance inspected -> fragmented after sphere reveal
The ebike repair flow used to require the player to press E twice in
'inspected' to trigger fragmentation, which:
- duplicated the entry interaction (waiting->inspected was already E)
- gave no visual confirmation that the inspect step did anything
- left the dark focus bubble hidden until *after* the explode happened

Now the bubble's GSAP grow tween (expo.out, 2.5s) starts as soon as the
mission enters 'inspected', visually engulfing the parked bike inside
its dark cocoon before the explode animation kicks in. After the tween
finishes the mission auto-advances to 'fragmented', which mounts the
ExplodableModel at the same parked world position.

Changes:
- RepairFocusBubble: export BUBBLE_GROW_DURATION_SECONDS so the timer
  in RepairGame stays in sync with the actual tween duration.
- RepairGame.shouldFocusBubbleBeActive(step, mission): ebike opens the
  bubble one phase earlier ('inspected'); pylon/farm keep their
  original 'fragmented'+ behaviour to avoid changing their UX.
- RepairGame: add a setTimeout(BUBBLE_GROW_DURATION_SECONDS * 1000)
  scoped to ebike + 'inspected' that calls setMissionStep('fragmented').
- RepairGame: hide the RepairMissionCase + 'press to fragment' prompt
  during ebike's 'inspected' phase (auto-flow doesn't need them).
  Pylon/farm still see the case + prompt during their 'inspected'.
- Ebike.handleInteract: drop the manual 'inspected -> fragmented'
  press-E branch (now dead). The 'waiting -> inspected' E entry is
  preserved as the single mission entry trigger.

useRepairFragmentationInput stays wired for pylon/farm (and as a
both-fists short-circuit for ebike) since its keyboardEnabled is false
and it can only fire on a deliberate gesture during 'inspected'.
2026-06-03 04:14:54 +02:00
Tom Boullay acdcb5515b refactor(ebike): drop redundant 'locked' substate, single entry trigger
The ebike mission previously had two redundant entry-point sub-states
('locked' and 'waiting') that were behaviorally identical from the
player's perspective:
- both showed the same 'Lancer le Repair Game' prompt
- both allowed the press-E handler in Ebike.tsx to jump to 'inspected'

In addition, the locked state caused two latent bugs:
- the static-map ebike node (GameMap) and the live <Ebike> component
  were rendered simultaneously at the same world position
- a generic RepairMissionTrigger anchor sphere was rendered in
  parallel to Ebike's own InteractableObject (two triggers, same area)

Changes:
- useGameStore: ebike's initial currentStep + completeIntroState target
  is now 'waiting' (pylon/farm still init at 'locked' — they need it).
- Ebike.tsx: drop dead === 'locked' branches in repairGameOwnsEbikeModel
  and the press-E handler.
- EbikeRepairNarrator: only reset the played-set on 'waiting'.
- RepairGame: drop 'locked' from the ebike livePosition guard.
- REPAIR_MISSION_TRIGGERS: empty array (the duplicate ebike anchor
  sphere is gone). Keep the array + RepairMissionTrigger component for
  future re-use.
- GameMap: hide the static-map ebike node as soon as
  mainState === 'ebike' (was: only when ebikeStep !== 'locked').
- repairMissionState.getPreviousMissionStep: ebike rewinds from
  'waiting' to 'waiting' (cap), pylon to 'npc-return', farm to 'locked'.

The 'locked' value is intentionally kept in the MissionStep type union
because the farm mission still uses it as a meaningful kickoff state
driving FarmNarrativeFlow's auto-transition to electricienne_history.
2026-06-03 04:02:32 +02:00
Tom Boullay 5ad2e27a89 fix(ui): scope hand-tracking activation + clean MissionNotification video branch
- HandTrackingProvider: drop the physics-mode auto-activation that turned
  the camera/MediaPipe pipeline on whenever any interactable was nearby
  (e.g. walking near the ebike to mount it). Hand tracking is now gated
  *only* by the active repair-mission step (inspected, repairing,
  reassembling, done). When testing in TestMap, set
  mainState=ebike + currentStep=inspected via the GameStateDebugPanel.
- MissionNotification: video branch no longer inherits the CRT-style
  enter/scan/flicker/sepia animations applied to the PNG branch via
  index.css. The webm assets already animate themselves, so the wrapping
  container is rendered with inline styles only (clip-path silhouette
  preserved, but no .__image-wrap::before scan line, no .__image flicker
  filter, no parent enter animation, no drop-shadow).
2026-06-03 03:44:04 +02:00
Tom Boullay 7bcbba4eb1 fix(ui): polish demo-flow overlays
🔍 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
- OutroVideoOverlay: stagger reveal so 'Next step :' appears immediately and
  'La ferme' fades in 500ms later, instead of both showing at once.
- MissionNotification: enforce 589/211 aspect-ratio + objectFit cover on the
  <video> branch so webm assets (square 2000x2000) render at the same place
  as the legacy PNG notifications instead of shifting the layout.
- HandTrackingTutorial: add a 5000ms fallback timeout that auto-dismisses
  the overlay if MediaPipe never reports a hand (camera blocked, mouse-only
  player), so the screen never stays stuck.
2026-06-03 03:27:18 +02:00
Tom Boullay 712fb851ad feat(outro): add fade-to-black transition screen with 'Next step: La ferme' before outro video, mute all game audio during playback 2026-06-03 03:08:12 +02:00
Tom Boullay d8b916d31f fix(types): satisfy strict tsc for production build (deploy unblock)
🔍 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-06-03 02:40:54 +02:00
Tom Boullay e9808f8473 fix(ebike): force-stop narrator audio + clear subtitle when leaving ebike state 2026-06-03 02:35:37 +02:00
Tom Boullay 0ddecaa494 fix(ebike): pause repair narrator audio when leaving ebike main state 2026-06-03 02:28:12 +02:00
Tom Boullay 6c36440016 update: srt 2026-06-03 02:25:20 +02:00
Tom Boullay f20c6b9961 docs(repair-game): document ebike narrator cues 2026-06-03 02:12:19 +02:00
Tom Boullay 47b69b01d2 feat(ebike): play narrator cues during repair flow (scan hint, diagnostic, completion) 2026-06-03 02:11:45 +02:00
Tom Boullay 8b0dd31014 Update SiteNamingScreen.tsx 2026-06-03 02:11:15 +02:00
Tom Boullay 171af683f5 feat(dialogues): split ebike repair narration into diagnostic and completion cues 2026-06-03 02:09:57 +02:00
Tom Boullay f820bee64f Merge branch 'develop' of https://git.fabrik.mathieu-chavanel.fr/math-pixel/La-Fabrik 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
2026-06-03 02:05:29 +02:00
math-pixel 1538ef93a5 Merge pull request 'Feat/polish-mission2' (#14) from feat/polish-mission-2 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: #14
2026-06-03 00:03:54 +00:00
math-pixel 1325b7b2af Merge branch 'develop' into feat/polish-mission-2
🔍 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-06-03 02:02:37 +02:00
Tom Boullay 96be49d358 fix(missions): point notifications to existing webm assets 2026-06-03 02:02:10 +02:00
Tom Boullay c2f55e3a2f feat(site): sync naming typewriter to last subtitle cue
🔍 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
Replace the audio "ended"-based trigger with an SRT-driven one: the
typewriter now starts (lastCue.endTime - typewriterDuration) seconds
into the dialogue so the final letter lands at the moment the
narrator finishes speaking. Char delay shortened from 110ms to 70ms
for a snappier reveal. Fallbacks: audio "ended" when no SRT, 8s
absolute timer otherwise.
2026-06-03 01:56:14 +02:00
math-pixel 63c2b294c1 Merge branch 'develop' into feat/polish-mission-2
🔍 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-06-03 01:52:20 +02:00
Tom Boullay 62d0dcf531 upatde; config ebike
🔍 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-06-03 01:48:31 +02:00
Tom Boullay c75c4e0be6 fix(site): keep white card border visible when selected
Replace the border swap with an outer green outline so the white
border stays in place. Selected = white border + green outline,
unselected = white border only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 01:46:13 +02:00
math-pixel 10b0d4fc16 outro anim + vid 2026-06-03 01:45:43 +02:00
Tom Boullay 5f113cbba4 feat(tutorial): add movement and hand-tracking onboarding overlays
Mount two first-time tutorial overlays driven by the game state machine:

- MovementTutorial: visible during the intro reveal and the free-walk
  step before the ebike mount, dismissed on the first Z/Q/S/D keydown.
- HandTrackingTutorial: visible during the early ebike repair steps
  (fragmented, scanning, inspected), dismissed when MediaPipe detects
  any hand on screen.

Both share a generic TutorialOverlay shell (transparent panel, dark
blue border, lucide-react Hand / inline ZQSD keycap icons, centered
text). The overlay sits at z-index 14, behind Subtitles (15) and
the talkie overlay (16), so dialogue/subtitle UI stays in front.

Dismissals stay persistent for the session: keyboard-triggered uses
event-handler setState; hand-detection uses a guarded effect-driven
setState (same pattern as PylonDownedPylon resync).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 01:43:25 +02:00
math-pixel b1037d5107 split narrator srt 2026-06-03 01:01:48 +02:00
Tom Boullay 1cc3b0e47e feat(audio): swap to repair music while a mission is in repair flow
Switch background music to musique-reparation.mp3 whenever any mission
(ebike / pylon / farm) is in the repair mini-game step range
(inspected → done) and back to musique-jeu.mp3 once the mission
leaves that range. Reuses AudioManager.playMusic which handles the
swap cleanly when the path changes.
2026-06-03 01:00:29 +02:00
Tom Boullay 00b1ff9e93 fix(ebike): unlock walking during breakdown + hide interact prompt + 450m ride
Three fixes for the ebike-breakdown substep:

1. PlayerController: the previous `if (!isEbikeBreakdown)` guard
   zeroed _wishDir for everyone during breakdown, including the
   player after they had been auto-dismounted to walk mode. Narrow
   the guard to `isEbikeMounted && isEbikeBreakdown` so the bike
   stops accepting drive input but the player on foot can move.

2. Ebike: track `window.ebikeBreakdownActive` in component state
   and hide the InteractableObject (and therefore the interact
   prompt UI) while the breakdown sequence is active. The bike must
   read as inert and non-interactive while the panne dialogue plays
   and during the auto-dismount that follows.

3. ebikeConfig: bump EBIKE_INTRO_BREAKDOWN_DISTANCE from 15 m to
   450 m so the panne triggers after a real ride instead of a few
   meters from the parked spawn.
2026-06-03 00:46:30 +02:00
Tom Boullay 675a45f02b Update ebikeConfig.ts 2026-06-03 00:44:00 +02:00
Tom Boullay bbae199105 docs(handtracking): document SVG-primary path and isFist origin
Reflect the current runtime in docs/technical/hand-tracking.md:

- SVG visualizer is now the primary hand UI; the 3D glove is opt-in
  via the Show Model debug toggle.
- Reorder the runtime flow to put HandTrackingVisualizer before
  HandTrackingGlove and make explicit that grab, fist detection, SVG
  and optional 3D glove are independent consumers of the same
  landmark snapshot.
- New Fist Detection section showing how isFist() in
  browserHandTracking.ts derives the flag from landmarks alone (palm
  centroid + 4 fingertip distances), and confirming GrabbableObject
  reads that flag directly - no glove involvement.
- Describe the SVG visualizer styling and the feMorphology outline
  trick.
- Mark HandTrackingFallback and the gant_l/_pad assets as legacy in
  the limitations list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 00:42:14 +02:00
Tom Boullay c4cad629c9 feat(handtracking): redesign SVG hand as primary visualization
Rewrite the live hand visualizer as a light-blue silhouette with a
crisp dark-blue outline, suitable as the primary hand UI (replacing
the buggy 3D glove for the default flow):

- Palm polygon (landmarks 0,1,5,9,13,17) and five finger tubes merged
  via an SVG feMorphology filter, so the outline is a single
  continuous ring with no internal seams.
- Q curves bow out to two synthetic wrist corners (perpendicular to
  the palm centerline) for a rounded heel of palm.
- Straight L edges between MCPs along the top - the filter dilation
  rounds the corners visually, no creux.
- Each finger path starts half a stroke inside the palm so the round
  base cap is hidden under the palm fill.
- Whole silhouette shrunk to 65% of the tracked hand size around the
  centroid, with 0.8 group opacity, and a faint MediaPipe skeleton
  overlay (lines + dots) on top.

Update the static fallback silhouettes (HandTrackingFallback) to a
matching curved-path look in a 100x120 viewBox.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 00:42:05 +02:00
math-pixel 18fb5e39e9 edit electricienne + poto 2026-06-03 00:05:07 +02:00
Tom Boullay ff4ead1d24 fix(lint): satisfy react-hooks immutability + set-state-in-effect rules
🔍 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
The new react-compiler-aware lint rules flag legitimate Three.js
external-system synchronizations (texture/uniform/AnimationAction
mutations) and a derived-state reset in PylonDownedPylon. None of
these are bugs — they're the canonical way to bridge React state
with imperative graphics objects — so they're annotated with
targeted eslint-disable comments and a small reorder.

- EbikeGPSMap: disable on uniform/texture sync effects
- EbikeSpeedmeter: disable around the canvas+texture useFrame sync
- PylonFarmerNPC: disable around playAnim (drei AnimationAction
  fadeIn/fadeOut/setLoop/clampWhenFinished) and the effects/frame
  callbacks that invoke it
- PylonDownedPylon: move showUpright/isPylonInteractive declarations
  above the useFrame that reads them (fixes access-before-declared)
  and disable set-state-in-effect on the per-step isRaised reset
2026-06-03 00:04:14 +02:00
Tom Boullay 974f340d33 style: prettier reflow pylon config and lighting effect
Mechanical formatting cleanup carried over from the develop merge:
inline single-line tuples and break long lines per project prettier
config. No behavior change.
2026-06-03 00:03:59 +02:00
Tom Boullay c6283d492c refactor(debug): rename hand-tracking SVG toggle to Model
The debug control now reflects what it actually gates: the 3D hand
model rendering (used by World.tsx to decide whether to show the
hand-tracking gloves), not the legacy SVG visualizer.

- Debug.ts: rename showHandTrackingSvg → showHandTrackingModel
  (state, GUI label "Show Model", getter/setter)
- World.tsx: gate showHandTrackingGloves on the new toggle and
  drop the unused HandTrackingGloveHandedness import
2026-06-03 00:03:44 +02:00
Tom Boullay 83194df14f fix(ebike): allow player input during mount/dismount camera transition
Add lockInput option (default true) to animateCameraTransformTransition
so ebike mount/dismount can keep player input active during the 1s
camera tween instead of locking via setCinematicPlaying.

Also drop the unused camPointPos/dropPointPos debug vars and the
matching debugRestingPosition state — the consuming JSX has been
commented out for a while.
2026-06-03 00:03:29 +02:00
Tom Boullay 918ee49d7c Merge branch 'develop' of https://git.fabrik.mathieu-chavanel.fr/math-pixel/La-Fabrik 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
2026-06-02 23:47:10 +02:00
Tom Boullay c0e7567849 fix(ebike): hide interact prompt while actively riding the bike
While the player is mounted on the e-bike and pressing a movement key,
the persistent 'Descendre du bike' prompt was visible on screen and
polluted the view during gameplay. The InteractableObject is now
unmounted as soon as window.ebikeDriveInputActive flips to true and
remounted the moment the bike comes to a stop.

The driving signal is read in a useFrame and only flips React state
on transitions, so this adds zero per-frame re-renders.
2026-06-02 23:36:13 +02:00
Tom Boullay 931308c92c fix(ui): tone down InteractPrompt and support empty label
- Smaller boxes (36x36 key + 36px-tall label) instead of the previous
  oversized white pills.
- Dark translucent background (rgba(10, 12, 20, 0.55)) with a 1px
  white outline (rgba(255, 255, 255, 0.7)), no border-radius and
  white text so the prompt blends with the dark UI instead of being a
  bright blob over the 3D scene.
- Key cube now has a 3D keyboard-key effect (inset top highlight +
  inset bottom darkening + small bottom drop) so it reads as a
  physical key.
- Key and label are visually separated (gap: 8px) but share the same
  height for alignment.
- InteractPrompt no longer renders the label box when focused.label is
  empty/whitespace, so callers can show the key prompt alone.
2026-06-02 23:27:07 +02:00
Tom Boullay 4e1ca708b2 docs(repair-game): document focus bubble + recursive explosion drill
- Add RepairFocusBubble + useRepairFocusStore to the main files table.
- New 'Focus Bubble' section documenting the shroud lifecycle, the
  cocoon decor pass and the vegetation/zone-overlay hide hook.
- Update the 'Fragmented' section to describe the recursive descent in
  ExplodedModel.createParts and the new modelRotation field used to
  align the fragmented model with the world-space source.
- Drop the stale reference to useRepairMovementLocked (removed in a
  prior commit).
2026-06-02 23:00:30 +02:00
Tom Boullay ca6c8e00b6 feat(repair): hide vegetation and zone overlays during repair focus
When the repair focus bubble is active the vegetation system and zone
debug visuals are unmounted so trees and gizmos don't clip through the
dark sphere shroud. Terrain, water, sky, clouds and grass remain
visible behind the bubble per Option (a).
2026-06-02 22:59:04 +02:00
Tom Boullay 220a661d6d feat(repair): introduce focus bubble shroud for repair mini-game
Adds a dark expanding sphere around the repair model when the player
enters the immersive repair phases (fragmented / scanning / repairing /
reassembling). The bubble grows from 0 to 10m using GSAP expo.out over
2.5s and reverses on focus end, visually isolating the player from the
surrounding map.

- New useRepairFocusStore tracks active state + world center.
- New RepairFocusBubble renders a BackSide sphere shell + a soft cocoon
  decor pass (grid floor + directional light + ambient) inside.
- RepairGame drives setFocus from its lifecycle effect.
- Mounted in both GameStageContent and TestMap so behaviour matches in
  the production scene and the physics test scene.

Also drops the now-unused EBIKE_CONFIG_KEY constant in
GameStageContent.tsx (leftover from a previous remount-key strategy).
2026-06-02 22:57:18 +02:00
math-pixel 0a3966a339 animate and fix electricienne
🔍 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-06-02 22:53:34 +02:00
Tom Boullay be5d03a30c feat(ui): redesign InteractPrompt per Figma DA
- Larger label box and key cube on white-translucent backgrounds
  (rgba(255, 255, 255, 0.92)) with black Inter 900 text and rounded
  12px corners + soft drop shadow.
- Move from bottom: 30% to bottom: 12% so the prompt sits closer to
  the visual center of attention near focused world objects.
- Key cube grown 24x24 -> 64x64 / font 13 -> 32, label padding 0 ->
  16x24 / font 13 -> 22, both bold instead of regular.
2026-06-02 22:53:06 +02:00
Tom Boullay ed0683d814 feat(ebike): rename interact label to 'Lancer le repair game'
Clarifies that interacting with the parked Ebike during the ebike
mission opens the repair mini-game rather than directly performing
a repair action.
2026-06-02 22:52:00 +02:00
Tom Boullay d9a92e336c fix(repair): drill explosion to natural group + apply mission rotation
- ExplodedModel.createParts now descends recursively through single
  mesh-bearing wrapper nodes (e.g. Scene > Moto > Eclatement) until
  reaching a node with multiple mesh-bearing children. Previously the
  first wrapper was used as root, so models with extra Empty/group
  parents fell back to flat leaf meshes lerping in local space.
- Add optional modelRotation field on RepairMissionConfig so fragmented
  + repairing models can match the world-space rotation of the source
  inspection model (parked Ebike).
- Ebike mission now uses EBIKE_WORLD_ROTATION_Y/EBIKE_WORLD_SCALE
  directly so the fragmented bike lines up with the parked bike.
2026-06-02 22:51:35 +02:00
Tom Boullay 89050331df chore(electricienne): switch to idle/walk animations
Replaces the placeholder Dance animation set on the electricienne
character with the standard idle/walk loop used by the other animated
NPCs.
2026-06-02 22:15:36 +02:00
Tom Boullay 0f211cc169 chore(format): apply prettier formatting 2026-06-02 22:15:25 +02:00
Tom Boullay 6a0215d1a6 fix(repair): keep ebike at zone Y in test scene
Adds an opt-out 'snapToTerrain' prop on Ebike so the parked position
keeps the explicit Y supplied by callers instead of resolving against
the world terrain GLTF. TestMap passes snapToTerrain={false} since it
does not render the world terrain — without this the bike was being
positioned at the invisible terrain height, far above the test floor,
and looked missing.
2026-06-02 22:10:31 +02:00
Tom Boullay 2a6a028e1d revert(repair): remove player movement lock during repair
Drops the useRepairMovementLocked hook, the RepairMovementLockIndicator
overlay, and all PlayerController gating tied to repair sub-states.
The repair flow no longer freezes player movement or shows a lock
banner; the player keeps full control while interacting with the case.
2026-06-02 22:04:05 +02:00