From a6093144110c780b53171ec0d8993f8784f418ee Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Tue, 2 Jun 2026 22:00:01 +0200 Subject: [PATCH] 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. --- src/components/three/gameplay/RepairGame.tsx | 14 +++++++++++++- src/world/debug/TestMap.tsx | 4 ++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/three/gameplay/RepairGame.tsx b/src/components/three/gameplay/RepairGame.tsx index c6bc9df..c8a990f 100644 --- a/src/components/three/gameplay/RepairGame.tsx +++ b/src/components/three/gameplay/RepairGame.tsx @@ -72,8 +72,20 @@ export function RepairGame({ const [scannedBrokenParts, setScannedBrokenParts] = useState< readonly RepairScannedBrokenPart[] >([]); + // For the ebike mission, use the bike's live parked world position once + // the repair flow leaves the waiting/locked phase so the repair happens + // wherever the player parked the bike, not at the static zone anchor. + // window.ebikeParkedPosition is set by Ebike when the player drops the + // bike and stays stable through the rest of the repair flow. + const livePosition = useMemo(() => { + if (mission !== "ebike" || mainState !== mission) return position; + if (step === "locked" || step === "waiting") return position; + const parked = window.ebikeParkedPosition; + if (!parked) return position; + return [parked[0], parked[1], parked[2]]; + }, [mainState, mission, position, step]); const parsedScale = toVector3Scale(scale); - const snappedPosition = useTerrainSnappedPosition(position); + const snappedPosition = useTerrainSnappedPosition(livePosition); const readyForFragmentation = step === "inspected"; const brokenNodeNames = useMemo(() => getBrokenNodeNames(config), [config]); diff --git a/src/world/debug/TestMap.tsx b/src/world/debug/TestMap.tsx index e1d74b9..e60e8c7 100644 --- a/src/world/debug/TestMap.tsx +++ b/src/world/debug/TestMap.tsx @@ -3,6 +3,7 @@ import { Component, useRef, useState, useEffect } from "react"; import * as THREE from "three"; import { Physics, RigidBody, CuboidCollider } from "@react-three/rapier"; import { Line } from "@react-three/drei"; +import { Ebike } from "@/components/ebike/Ebike"; import { RepairGame } from "@/components/three/gameplay/RepairGame"; import { GrabbableObject } from "@/components/three/interaction/GrabbableObject"; import { AnimatedModel } from "@/components/three/models/AnimatedModel"; @@ -239,6 +240,9 @@ export function TestMap({ onOctreeReady }: TestMapProps): React.JSX.Element { + {zone.mission === "ebike" ? ( + + ) : null} ))}