Commit Graph

31 Commits

Author SHA1 Message Date
Tom Boullay 08c10acd48 fix(repair-ebike): stop subtitle leak and fake cooling swap
🔍 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 06:47:10 +02:00
Tom Boullay 8d66391fa9 fix(repair-ebike): preserve bike position, unblock scan and reassembly 2026-06-03 06:34:18 +02:00
Tom Boullay 5a6596b755 refactor(repair): unify exploded model across phases, simplify ebike flow
- RepairGame: lift a single ExplodableModel mounted across fragmented
  -> done so the model loads once, animates from its real original
  positions, and never re-instantiates between phases. Eliminates the
  position/rotation jumps and re-explosion that occurred when each
  step instantiated its own model.
- ExplodableModel: expose splitSpeed prop so the explode/reassemble
  lerp can be slowed down (REPAIR_FRAGMENT_SPLIT_SPEED = 1.8) for a
  more deliberate visual where each node is seen leaving its origin.
- RepairScanSequence: drop its own ExplodableModel, receive parts
  from the upstream shared instance. Logs the available part names
  when broken-part nodes can't be matched so config drift is visible.
- RepairReassemblyStep: reduced to the completion particles + a
  delayed onSettled callback. The collapse animation is now driven by
  the shared ExplodableModel switching split=false at the reassembling
  phase. After REPAIR_REASSEMBLY_HOLD_MS (1500ms) the upstream flow
  auto-advances to done.
- RepairEbikeRepairTrigger: new minimal interactable for the ebike
  repairing step. Replaces the heavier grabbable-parts UX (cercles,
  ranger pieces) with a single 'Changez le refroidisseur' prompt that
  advances directly to reassembling. Pylon/farm keep RepairRepairingStep.
- RepairCompletionStep: drop the duplicated RepairObjectModel; the
  shared ExplodableModel renders the repaired model at done.
- RepairGame ebike-done: play narrateur_ebikerepare and call
  completeMission on the audio's ended event (with REPAIR_DONE_DIALOGUE_FALLBACK_MS
  fallback). Hands off to pylon without a Validate button.
- EbikeRepairNarrator: drop the done entry; RepairGame owns it now so
  the audio's end event can drive the mission completion handoff.
- RepairGame: drop the window.ebikeParkedPosition livePosition logic.
  Ebike movement is disabled during the repair flow so the static zone
  position is the source of truth, fixing the floating-bike issue
  observed in TestMap.
2026-06-03 06:21:29 +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
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
math-pixel b1037d5107 split narrator srt 2026-06-03 01:01:48 +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
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 a609314411 feat(repair): mount Ebike on TestMap and snap repair to parked position
The Physique test scene now mounts the real Ebike component for the
ebike repair zone, mirroring GameStageContent so the bike model and
its interactions (mount/dismount, parked position tracking) are
available when testing the repair flow.

RepairGame derives its live world position from
window.ebikeParkedPosition once the ebike mission leaves the
locked/waiting phase, so the repair sequence happens wherever the
player parked the bike rather than at the static zone anchor.
2026-06-02 22:00:01 +02:00
Tom Boullay d29b01e398 feat(repair): broken parts spawn from exploded model node positions
🔍 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
Render the exploded mission model during the repairing step so broken
nodes (e.g. ebike refroidisseur) stay visible in the world, and surface
their world positions to RepairRepairingStep so broken pieces spawn
from where they belong on the model rather than from a static offset.

- ExplodableModel: add hideNodeNames (mesh visibility off, restored on
  unmount) and nodeAnchorNames + onNodeAnchorsChange (per-frame world
  positions debounced via signature so React state updates only when
  the values actually move).
- RepairGame: render ExplodableModel during 'repairing' with the broken
  node names hidden + anchors forwarded; threads brokenAnchors state
  to RepairRepairingStep.
- RepairScanSequence + RepairRepairingStep: propagate targetNodeName
  through scanned and fallback broken-part lists.
- RepairRepairingStep: broken parts spawn at brokenAnchors[targetNodeName]
  when set, falling back to legacy BROKEN_PART_START_OFFSETS otherwise.
2026-06-02 19:12:38 +02:00
Tom Boullay d1bf438465 feat(repair): inject ebike + pylon parts at packderelance anchors
- Ebike replacement parts: cooling core (correct, anchored at refroidisseur)
  + four distractors anchored at cabledroit/cablegauche/pucehaut/pucebas.
  Removes the ad-hoc gant_l/talkie distractors in favor of consistent
  case-anchored visuals.
- Pylon replacement parts: cable1 + cable2 (alternative correct, both with
  caseLockGroup 'pylon-cable' for upcoming soft-lock) + refroidisseur and
  two puce distractors anchored to packderelance.
- Farm replacement parts kept as-is (caseAnchor undefined falls back to
  placeholder slot positions for backward compatibility).
- RepairGame threads anchors from RepairCaseModel through RepairMissionCase
  to RepairRepairingStep; replacement-part initial position now resolves
  to the anchor world position when caseAnchor is set, falling back to the
  legacy slot index otherwise.
2026-06-02 18:37:12 +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 d9cf87d2d6 feat: launch ebike repair from map interaction
🔍 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 10:13:59 +02:00
Tom Boullay d654565f87 chore: address code quality audit findings
🔍 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 08:31:42 +02:00
Tom Boullay 8088e67625 fix: a pb with octree 2026-05-11 16:41:11 +02:00
Tom Boullay cec4d6ad0d update: reset repair runtime state 2026-05-11 13:01:32 +02:00
Tom Boullay 788e9f0fb3 update: feedback repair model and improve repair case interaction feedback 2026-05-11 12:54:54 +02:00
Tom Boullay 7036b7b0f9 fix: preload repair mission assets 2026-05-11 11:47:20 +02:00
Tom Boullay e16d6e15fa fix repair game suspense boundaries 2026-05-11 11:37:54 +02:00
Tom Boullay 6d9eac291e big clean up 2026-05-08 03:02:26 +01:00
Tom Boullay ead3634aab add: animate repair reassembly 2026-05-08 02:40:31 +01:00
Tom Boullay 19a83982a9 add: require broken part deposit before repair 2026-05-08 02:36:14 +01:00
Tom Boullay bebb9ac5a3 add: snap repair parts to case placeholders 2026-05-08 02:33:06 +01:00
Tom Boullay d02ef54bdc add: focus repair case view 2026-05-08 02:22:15 +01:00
Tom Boullay 7a3baa4c0b add: scan fragmented repair parts sequentially 2026-05-08 02:12:58 +01:00
Tom Boullay 15c3d1858f add: repair mission completion step 2026-05-08 01:48:40 +01:00
Tom Boullay 7bbcf4359e add: repair install completion step 2026-05-08 01:41:29 +01:00
Tom Boullay eed0077dd1 add: repair fragmentation and scan flow 2026-05-08 01:39:23 +01:00
Tom Boullay ed60114d06 add: repair game inspection sub state 2026-05-08 01:27:32 +01:00