- 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.
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.
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.