From 1a4c5cdc5e82fd09162b9acbcc6c17c8f7d59236 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Fri, 8 May 2026 02:07:03 +0100 Subject: [PATCH] clean: remove obsolete repair debug code + unused core utilities --- docs/technical/animation.md | 5 +- docs/technical/architecture.md | 2 +- docs/technical/zustand.md | 2 +- docs/user/main-feature.md | 15 +-- .../three/gameplay/RepairCaseModel.tsx | 30 ++++- .../three/gameplay/RepairCaseObject.tsx | 102 --------------- .../three/gameplay/RepairGameZone.tsx | 121 ------------------ .../three/gameplay/RepairModuleSlot.tsx | 113 ---------------- .../three/gameplay/RepairObjectModel.tsx | 7 +- .../three/interaction/GrabbableObject.tsx | 2 +- src/components/three/models/AnimatedModel.tsx | 7 +- src/components/three/models/SimpleModel.tsx | 7 +- .../three/models/useAnimatedModel.ts | 11 +- src/components/ui/HandTrackingVisualizer.tsx | 2 +- src/controls/editor/FlyController.tsx | 1 - src/data/docs/docsTranslations.ts | 4 +- src/data/gameplay/repairCaseConfig.ts | 2 + src/data/gameplay/repairGameConfig.ts | 11 -- src/data/gameplay/repairGameModelCatalog.ts | 29 ----- src/hooks/gameplay/useModelSelection.ts | 79 ------------ .../handTracking/useBrowserHandTracking.ts | 44 +------ .../handTracking/useRemoteHandTracking.ts | 44 +------ src/index.css | 40 ------ src/lib/handTracking/handTrackingSession.ts | 36 ++++++ src/managers/AudioManager.ts | 2 +- src/managers/InteractionManager.ts | 17 +-- src/routes/DocsRoute.tsx | 66 ++++------ src/utils/core/EventEmitter.ts | 22 +--- src/utils/core/Sizes.ts | 50 -------- src/utils/core/Time.ts | 42 ------ src/utils/debug/Debug.ts | 15 ++- src/utils/three/modelLoadLogger.ts | 6 +- src/world/GameMap.tsx | 2 +- src/world/debug/TestMap.tsx | 3 - 34 files changed, 144 insertions(+), 797 deletions(-) delete mode 100644 src/components/three/gameplay/RepairCaseObject.tsx delete mode 100644 src/components/three/gameplay/RepairGameZone.tsx delete mode 100644 src/components/three/gameplay/RepairModuleSlot.tsx delete mode 100644 src/data/gameplay/repairGameModelCatalog.ts delete mode 100644 src/hooks/gameplay/useModelSelection.ts create mode 100644 src/lib/handTracking/handTrackingSession.ts delete mode 100644 src/utils/core/Sizes.ts delete mode 100644 src/utils/core/Time.ts diff --git a/docs/technical/animation.md b/docs/technical/animation.md index ce22beb..f2cff1d 100644 --- a/docs/technical/animation.md +++ b/docs/technical/animation.md @@ -37,9 +37,8 @@ Use `useClonedObject` when a GLTF scene is reused by a component instance. It me src/components/three/ ├── gameplay/ │ ├── RepairCaseModel.tsx -│ ├── RepairCaseObject.tsx -│ ├── RepairGameZone.tsx -│ └── RepairModuleSlot.tsx +│ ├── RepairGame.tsx +│ └── RepairRepairingStep.tsx ├── interaction/ │ ├── GrabbableObject.tsx │ ├── InteractableObject.tsx diff --git a/docs/technical/architecture.md b/docs/technical/architecture.md index c291e5b..b72979b 100644 --- a/docs/technical/architecture.md +++ b/docs/technical/architecture.md @@ -62,7 +62,7 @@ Keep the player and map octree outside the Rapier provider until there is a deli - `src/components/three/models/` contains reusable model helpers such as `ExplodableModel`. - `src/components/three/interaction/` contains reusable interaction wrappers such as `InteractableObject`, `TriggerObject`, and `GrabbableObject`. - `src/components/three/handTracking/` contains R3F hand tracking debug models such as the glove overlays. -- `src/components/three/gameplay/` contains the repair gameplay components: the reusable production `RepairGame` flow, the repair case, the debug repair game zone, and module slots. +- `src/components/three/gameplay/` contains the reusable production `RepairGame` flow, repair case, repair steps, and repair prompt components. - `src/components/three/world/` contains reusable world/environment objects such as `SkyModel`. ## Editor System diff --git a/docs/technical/zustand.md b/docs/technical/zustand.md index 3e28951..90e1d81 100644 --- a/docs/technical/zustand.md +++ b/docs/technical/zustand.md @@ -181,4 +181,4 @@ Current overlays: ## Next Steps -The next natural step is to move repair validation from this local scene interaction into richer mission data when each mission has distinct broken module nodes, replacement assets, and narrative completion beats. +Move repair validation into mission data once each mission has distinct broken module nodes, replacement assets, and completion events. diff --git a/docs/user/main-feature.md b/docs/user/main-feature.md index c3c6a90..53b7c29 100644 --- a/docs/user/main-feature.md +++ b/docs/user/main-feature.md @@ -21,27 +21,22 @@ The current user flow is: 11. Press `E` on the green install target to move to `done` and show the reassembled object. Wrong parts turn the target red and cannot finish the repair. 12. Press `E` on the completion target to call `completeMission` and move to the next mission, or to `outro` after `ferme`. -The older debug repair sandbox still exists in the physics test scene, but the production path now starts from the reusable `RepairGame` component. - ## Why It Matters -This feature validates the core repair fantasy before a full mission system exists. It tests whether repair objects, physical proximity, model selection, audio feedback, and exploded model visualization can work together in the 3D scene. +This feature validates the repair loop before a full mission system exists. It tests whether repair objects, physical proximity, model selection, audio feedback, and exploded model visualization can work together in the 3D scene. ## Current Behavior In `waiting`, the active mission renders its repair object and the `interagir.webm` prompt in the game scene. The interaction uses the shared focus/raycast interaction system, so the player still gets the normal `E` prompt. -When the player inspects the object, `RepairGame` writes `inspected` through the generic mission store action. The repair case then appears from the mission config. When the player is close enough, the existing case model floats upward and rotates gently to signal interactivity. +When the player inspects the object, `RepairGame` writes `inspected` through the generic mission store action. The repair case then appears from the mission config with a small pop animation. When the player is close enough, the existing case model floats upward and rotates gently to signal interactivity. In `inspected`, `RepairGame` can also move to `fragmented`. The player can use the interaction key or hold both fists closed for one second. The hand-tracking path is state-based, so it does not depend on being inside a local object interaction radius. In `fragmented`, the repair object is rendered with `ExplodableModel`, then automatically advances to `scanning`. In `scanning`, a blue scan visual and the `cassé.webm` prompt are shown before the flow advances to `repairing`. In `repairing`, the case opens, several grabbable replacement parts appear, and the install target only validates the configured correct part for the active mission. In `done`, the repaired object remains visible with a completion target that advances the global mission progression. -Repair module slots and model-selection behavior still exist in the debug prototype. They can be migrated into the reusable repair flow in later steps if the repair interaction needs more depth. - ## Key Files -- `src/world/debug/TestMap.tsx` mounts the repair-game prototype in the debug physics scene. - `src/world/GameStageContent.tsx` mounts production `RepairGame` instances for `bike`, `pylone`, and `ferme`. - `src/components/three/gameplay/RepairCompletionStep.tsx` renders the final repaired object, completion target, and mission UI prompt. - `src/components/three/gameplay/RepairGame.tsx` composes the reusable production repair flow. @@ -53,14 +48,10 @@ Repair module slots and model-selection behavior still exist in the debug protot - `src/hooks/gameplay/useRepairFragmentationInput.ts` handles the `inspected -> fragmented` keyboard and hand-tracking input. - `src/hooks/gameplay/useRepairMissionStep.ts` reads the active mission step from the game store. - `src/hooks/handTracking/useBothFistsHold.ts` detects the reusable two-fists hold gesture. -- `src/components/three/gameplay/RepairGameZone.tsx` composes the repair-game zone. -- `src/components/three/gameplay/RepairCaseObject.tsx` connects the repair case to trigger interaction and audio. - `src/components/three/gameplay/RepairCaseModel.tsx` renders and animates the case model. -- `src/components/three/gameplay/RepairModuleSlot.tsx` renders repair slots and model selection behavior. - `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/repairGameConfig.ts` stores repair zone and slot positions. -- `src/data/gameplay/repairGameModelCatalog.ts` stores selectable repair models. +- `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/managers/stores/useGameStore.ts` stores mission progression state and generic mission step helpers. diff --git a/src/components/three/gameplay/RepairCaseModel.tsx b/src/components/three/gameplay/RepairCaseModel.tsx index 01485db..49cd9f1 100644 --- a/src/components/three/gameplay/RepairCaseModel.tsx +++ b/src/components/three/gameplay/RepairCaseModel.tsx @@ -11,6 +11,8 @@ import { REPAIR_CASE_FLOAT_UP_SPEED, REPAIR_CASE_LID_NODE_NAME, REPAIR_CASE_OPEN_ROTATION_OFFSET_DEGREES, + REPAIR_CASE_POP_DURATION, + REPAIR_CASE_POP_Y_OFFSET, REPAIR_CASE_ROTATION_AMPLITUDE_DEGREES, REPAIR_CASE_ROTATION_RESET_SPEED, } from "@/data/gameplay/repairCaseConfig"; @@ -55,16 +57,30 @@ export function RepairCaseModel({ const floatHeight = useRef(0); const animationActiveRef = useRef(false); const phase = useRef({ x: 0, y: 0, z: 0 }); + const pop = useRef({ scale: 0.001, yOffset: REPAIR_CASE_POP_Y_OFFSET }); const initialOpen = useRef(open); const openedRotationZ = useRef(0); const parsedScale = toVector3Scale(scale); useEffect(() => { + const popAnimation = pop.current; + phase.current = { x: Math.random() * Math.PI * 2, y: Math.random() * Math.PI * 2, z: Math.random() * Math.PI * 2, }; + + gsap.to(popAnimation, { + scale: 1, + yOffset: 0, + duration: REPAIR_CASE_POP_DURATION, + ease: "back.out(1.7)", + }); + + return () => { + gsap.killTweensOf(popAnimation); + }; }, []); useEffect(() => { @@ -119,7 +135,12 @@ export function RepairCaseModel({ floatSpeed, delta, ); - group.position.y = position[1] + floatHeight.current; + group.position.y = position[1] + floatHeight.current + pop.current.yOffset; + group.scale.set( + parsedScale[0] * pop.current.scale, + parsedScale[1] * pop.current.scale, + parsedScale[2] * pop.current.scale, + ); animationActiveRef.current = isNear; @@ -158,12 +179,7 @@ export function RepairCaseModel({ }); return ( - + ); diff --git a/src/components/three/gameplay/RepairCaseObject.tsx b/src/components/three/gameplay/RepairCaseObject.tsx deleted file mode 100644 index 2307794..0000000 --- a/src/components/three/gameplay/RepairCaseObject.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import type { ReactNode } from "react"; -import { Component } from "react"; -import { TriggerObject } from "@/components/three/interaction/TriggerObject"; -import { RepairCaseModel } from "@/components/three/gameplay/RepairCaseModel"; -import { - REPAIR_CASE_MODEL_PATH, - REPAIR_CASE_OPEN_SOUND_PATH, -} from "@/data/gameplay/repairCaseConfig"; -import { AudioManager } from "@/managers/AudioManager"; -import type { Vector3Tuple } from "@/types/three/three"; -import { logModelLoadError } from "@/utils/three/modelLoadLogger"; - -interface RepairCaseErrorBoundaryProps { - children: ReactNode; -} - -interface RepairCaseErrorBoundaryState { - hasError: boolean; -} - -class RepairCaseErrorBoundary extends Component< - RepairCaseErrorBoundaryProps, - RepairCaseErrorBoundaryState -> { - constructor(props: RepairCaseErrorBoundaryProps) { - super(props); - this.state = { hasError: false }; - } - - static getDerivedStateFromError(): RepairCaseErrorBoundaryState { - return { hasError: true }; - } - - componentDidCatch(error: Error): void { - logModelLoadError( - { - modelPath: REPAIR_CASE_MODEL_PATH, - scope: "RepairCaseObject", - position: [0, -0.45, 0], - scale: 1.5, - }, - error, - ); - } - - render(): ReactNode { - if (this.state.hasError) { - return ; - } - - return this.props.children; - } -} - -interface RepairCaseObjectProps { - position: Vector3Tuple; - open: boolean; - onInspect: () => void; -} - -export function RepairCaseObject({ - position, - open, - onInspect, -}: RepairCaseObjectProps): React.JSX.Element { - return ( - { - if (open) return; - AudioManager.getInstance().playSound(REPAIR_CASE_OPEN_SOUND_PATH); - onInspect(); - }} - > - - - - - ); -} - -function RepairCaseFallback(): React.JSX.Element { - return ( - - - - - - - - - - - ); -} diff --git a/src/components/three/gameplay/RepairGameZone.tsx b/src/components/three/gameplay/RepairGameZone.tsx deleted file mode 100644 index cf0aaed..0000000 --- a/src/components/three/gameplay/RepairGameZone.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { Text } from "@react-three/drei"; -import { RepairCaseObject } from "@/components/three/gameplay/RepairCaseObject"; -import { RepairModuleSlot } from "@/components/three/gameplay/RepairModuleSlot"; -import { - REPAIR_GAME_MODULE_SLOTS, - REPAIR_GAME_ZONE_LABEL, - REPAIR_GAME_ZONE_ORIGIN, - REPAIR_GAME_ZONE_RADIUS, -} from "@/data/gameplay/repairGameConfig"; -import { useGameStore } from "@/managers/stores/useGameStore"; - -const CASE_CLOSED_STEPS = new Set(["locked", "waiting"]); - -export function RepairGameZone(): React.JSX.Element { - const mainState = useGameStore((state) => state.mainState); - const bikeStep = useGameStore((state) => state.bike.currentStep); - const setMainState = useGameStore((state) => state.setMainState); - const setBikeState = useGameStore((state) => state.setBikeState); - const caseOpen = !CASE_CLOSED_STEPS.has(bikeStep); - const slotsDisabled = !caseOpen; - - const inspectRepairCase = (): void => { - if (mainState !== "bike") { - setMainState("bike"); - } - - if (CASE_CLOSED_STEPS.has(bikeStep)) { - setBikeState({ currentStep: "inspected" }); - } - }; - - const markModelSelected = (): void => { - if (mainState !== "bike") { - setMainState("bike"); - } - - if (bikeStep === "inspected") { - setBikeState({ currentStep: "fragmented" }); - } - }; - - const markModuleSplit = (): void => { - if (mainState !== "bike") { - setMainState("bike"); - } - - if (bikeStep === "fragmented") { - setBikeState({ currentStep: "scanning" }); - } - }; - - return ( - - - - - - - - - - - - - {REPAIR_GAME_ZONE_LABEL} - - - - - {REPAIR_GAME_MODULE_SLOTS.map((slot) => ( - - ))} - - ); -} diff --git a/src/components/three/gameplay/RepairModuleSlot.tsx b/src/components/three/gameplay/RepairModuleSlot.tsx deleted file mode 100644 index 6ffed47..0000000 --- a/src/components/three/gameplay/RepairModuleSlot.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { Html } from "@react-three/drei"; -import { useCallback, useState } from "react"; -import { TriggerObject } from "@/components/three/interaction/TriggerObject"; -import { ExplodableModel } from "@/components/three/models/ExplodableModel"; -import { REPAIR_GAME_MODEL_CATALOG } from "@/data/gameplay/repairGameModelCatalog"; -import type { ModelCatalogItem } from "@/data/gameplay/repairGameModelCatalog"; -import { useModelSelection } from "@/hooks/gameplay/useModelSelection"; -import type { Vector3Tuple } from "@/types/three/three"; - -interface RepairModuleSlotProps { - position: Vector3Tuple; - label: string; - disabled?: boolean; - onModelSelected?: () => void; - onSplit?: () => void; -} - -export function RepairModuleSlot({ - position, - label, - disabled = false, - onModelSelected, - onSplit, -}: RepairModuleSlotProps): React.JSX.Element { - const [selectedModel, setSelectedModel] = useState( - null, - ); - const [split, setSplit] = useState(false); - const handleSelect = useCallback( - (model: ModelCatalogItem) => { - setSelectedModel(model); - setSplit(false); - onModelSelected?.(); - }, - [onModelSelected], - ); - const selection = useModelSelection(REPAIR_GAME_MODEL_CATALOG, handleSelect); - const triggerLabel = disabled - ? "Ouvrir la mallette d'abord" - : selectedModel - ? split - ? `Réassembler ${label}` - : `Démonter ${label}` - : `Choisir ${label}`; - - return ( - - { - if (disabled) return; - - if (selectedModel) { - setSplit((value) => { - const nextSplit = !value; - if (nextSplit) { - onSplit?.(); - } - return nextSplit; - }); - return; - } - - selection.open(); - }} - > - {selectedModel ? ( - - ) : ( - - - - - )} - - - {selection.isOpen ? ( - -
- {label} - Fleches: choisir - E/Enter: valider -
    - {REPAIR_GAME_MODEL_CATALOG.map((model, index) => ( -
  • - {model.name} -
  • - ))} -
-
- - ) : null} -
- ); -} diff --git a/src/components/three/gameplay/RepairObjectModel.tsx b/src/components/three/gameplay/RepairObjectModel.tsx index c6a8e15..671a574 100644 --- a/src/components/three/gameplay/RepairObjectModel.tsx +++ b/src/components/three/gameplay/RepairObjectModel.tsx @@ -1,15 +1,12 @@ import type { ReactNode } from "react"; import { Component } from "react"; import { SimpleModel } from "@/components/three/models/SimpleModel"; -import type { Vector3Scale, Vector3Tuple } from "@/types/three/three"; +import type { ModelTransformProps } from "@/types/three/three"; import { logModelLoadError } from "@/utils/three/modelLoadLogger"; -interface RepairObjectModelProps { +interface RepairObjectModelProps extends ModelTransformProps { label: string; modelPath: string; - position?: Vector3Tuple; - rotation?: Vector3Tuple; - scale?: Vector3Scale; } interface RepairObjectModelBoundaryProps extends RepairObjectModelProps { diff --git a/src/components/three/interaction/GrabbableObject.tsx b/src/components/three/interaction/GrabbableObject.tsx index 967c9b0..d690dd9 100644 --- a/src/components/three/interaction/GrabbableObject.tsx +++ b/src/components/three/interaction/GrabbableObject.tsx @@ -69,7 +69,7 @@ const HAND_HIT_OFFSETS: Array<[number, number]> = [ ]; function getHandCenterPoint(hand: HandTrackingHand): HandTrackingLandmark { - const landmarks = hand.landmarks ?? []; + const landmarks = hand.landmarks; if (landmarks.length === 0) { return { x: hand.x, y: hand.y, z: hand.z }; } diff --git a/src/components/three/models/AnimatedModel.tsx b/src/components/three/models/AnimatedModel.tsx index c467e03..ed37cb9 100644 --- a/src/components/three/models/AnimatedModel.tsx +++ b/src/components/three/models/AnimatedModel.tsx @@ -7,15 +7,12 @@ import { type AnimatedModelContextValue, } from "@/components/three/models/useAnimatedModel"; import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; -import type { Vector3Tuple } from "@/types/three/three"; +import type { ModelTransformProps, Vector3Tuple } from "@/types/three/three"; -export interface AnimatedModelConfig { +export interface AnimatedModelConfig extends ModelTransformProps { modelPath: string; animations?: string[]; defaultAnimation?: string; - position?: Vector3Tuple; - rotation?: Vector3Tuple; - scale?: Vector3Tuple | number; fadeDuration?: number; speed?: number; autoPlay?: boolean; diff --git a/src/components/three/models/SimpleModel.tsx b/src/components/three/models/SimpleModel.tsx index c559db2..9b8e570 100644 --- a/src/components/three/models/SimpleModel.tsx +++ b/src/components/three/models/SimpleModel.tsx @@ -1,12 +1,9 @@ import { useMemo } from "react"; import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; -import type { Vector3Tuple } from "@/types/three/three"; +import type { ModelTransformProps, Vector3Tuple } from "@/types/three/three"; -export interface SimpleModelConfig { +export interface SimpleModelConfig extends ModelTransformProps { modelPath: string; - position?: Vector3Tuple; - rotation?: Vector3Tuple; - scale?: Vector3Tuple | number; castShadow?: boolean; receiveShadow?: boolean; } diff --git a/src/components/three/models/useAnimatedModel.ts b/src/components/three/models/useAnimatedModel.ts index 8fee82a..33d8318 100644 --- a/src/components/three/models/useAnimatedModel.ts +++ b/src/components/three/models/useAnimatedModel.ts @@ -1,4 +1,4 @@ -import { createContext, useContext } from "react"; +import { createContext } from "react"; export interface AnimatedModelContextValue { play: (name: string, fade?: number) => void; @@ -12,12 +12,3 @@ export interface AnimatedModelContextValue { export const AnimatedModelContext = createContext(null); - -export function useAnimatedModel(): AnimatedModelContextValue { - const context = useContext(AnimatedModelContext); - if (!context) { - throw new Error("useAnimatedModel must be used inside AnimatedModel"); - } - - return context; -} diff --git a/src/components/ui/HandTrackingVisualizer.tsx b/src/components/ui/HandTrackingVisualizer.tsx index 98c6e1b..2238554 100644 --- a/src/components/ui/HandTrackingVisualizer.tsx +++ b/src/components/ui/HandTrackingVisualizer.tsx @@ -47,7 +47,7 @@ export function HandTrackingVisualizer(): React.JSX.Element | null { return (