diff --git a/docs/technical/map-performance.md b/docs/technical/map-performance.md index 76a0e24..57d560d 100644 --- a/docs/technical/map-performance.md +++ b/docs/technical/map-performance.md @@ -74,7 +74,7 @@ These vegetation and crop assets account for almost all of the current `~69M` tr ## Debug Performance Controls -The next useful runtime tool is a debug-only performance folder that can isolate model families. This should be mounted only when `?debug` is enabled. +The debug-only performance folder can isolate model families when `?debug` is enabled. Proposed controls: diff --git a/src/components/editor/scene/EditorScene.tsx b/src/components/editor/scene/EditorScene.tsx index ab7257e..6f8ec3e 100644 --- a/src/components/editor/scene/EditorScene.tsx +++ b/src/components/editor/scene/EditorScene.tsx @@ -6,8 +6,12 @@ import * as THREE from "three"; import type { OrbitControls as OrbitControlsImpl } from "three-stdlib"; import { EditorMap } from "@/components/editor/scene/EditorMap"; import { FlyController } from "@/controls/editor/FlyController"; -import type { CinematicDefinition } from "@/types/cinematics/cinematics"; -import type { MapNode, TransformMode, SceneData } from "@/types/editor/editor"; +import type { + EditorCinematicPreviewRequest, + MapNode, + TransformMode, + SceneData, +} from "@/types/editor/editor"; const EDITOR_CAMERA_HOME_POSITION = new THREE.Vector3(0, 50, 100); const EDITOR_CAMERA_HOME_TARGET = new THREE.Vector3(0, 0, 0); @@ -23,11 +27,6 @@ function isEditableShortcutTarget(target: EventTarget | null): boolean { ); } -export interface EditorCinematicPreviewRequest { - id: string; - cinematic: CinematicDefinition; -} - interface EditorSceneProps { sceneData: SceneData; selectedNodeIndex: number | null; diff --git a/src/components/three/world/SkyModel.tsx b/src/components/three/world/SkyModel.tsx index 938550b..4186432 100644 --- a/src/components/three/world/SkyModel.tsx +++ b/src/components/three/world/SkyModel.tsx @@ -3,6 +3,7 @@ import { useGLTF } from "@react-three/drei"; import { Component, useEffect, useMemo, useRef, type ReactNode } from "react"; import * as THREE from "three"; import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; +import { logger } from "@/utils/core/Logger"; interface SkyModelProps { fallbackModelScale?: number | undefined; @@ -20,6 +21,8 @@ interface SkyModelContentProps { interface SkyModelErrorBoundaryProps { children: ReactNode; fallback: ReactNode; + label: string; + modelPath: string; } interface SkyModelErrorBoundaryState { @@ -43,6 +46,17 @@ class SkyModelErrorBoundary extends Component< return { hasError: true }; } + componentDidCatch(error: Error): void { + logger.warn( + "SkyModel", + `${this.props.label} model failed; using fallback`, + { + error, + modelPath: this.props.modelPath, + }, + ); + } + render(): ReactNode { if (this.state.hasError) { return this.props.fallback; @@ -63,7 +77,12 @@ export function SkyModel({ ) : null; const fallback = fallbackModelPath ? ( - + + ); diff --git a/src/data/gameplay/repairMissionState.ts b/src/data/gameplay/repairMissionState.ts index 6c86ba9..29ec61f 100644 --- a/src/data/gameplay/repairMissionState.ts +++ b/src/data/gameplay/repairMissionState.ts @@ -2,8 +2,8 @@ import type { MissionStep, RepairMissionId, } from "@/types/gameplay/repairMission"; +import { REPAIR_MISSION_IDS } from "@/types/gameplay/repairMission"; -const REPAIR_MISSION_IDS = ["ebike", "pylon", "farm"] as const; const REPAIR_MISSION_ID_VALUES: ReadonlySet = new Set( REPAIR_MISSION_IDS, ); diff --git a/src/data/world/vegetationConfig.ts b/src/data/world/vegetationConfig.ts index a2aaf99..3ddae67 100644 --- a/src/data/world/vegetationConfig.ts +++ b/src/data/world/vegetationConfig.ts @@ -83,6 +83,12 @@ export const VEGETATION_TYPE_KEYS = [ export type VegetationType = (typeof VEGETATION_TYPE_KEYS)[number]; +export const VEGETATION_MAP_NODE_NAMES: ReadonlySet = new Set( + Object.values(VEGETATION_TYPES) + .filter((config) => config.enabled) + .map((config) => config.mapName), +); + export function getVegetationModelScaleMultiplier(name: string): number { return ( Object.values(VEGETATION_TYPES).find((config) => config.mapName === name) diff --git a/src/pages/editor/page.tsx b/src/pages/editor/page.tsx index 3eda0d1..26145bd 100644 --- a/src/pages/editor/page.tsx +++ b/src/pages/editor/page.tsx @@ -2,13 +2,16 @@ import { useCallback, useEffect, useState } from "react"; import { Canvas, useThree } from "@react-three/fiber"; import { EditorControls } from "@/components/editor/EditorControls"; import { EditorScene } from "@/components/editor/scene/EditorScene"; -import type { EditorCinematicPreviewRequest } from "@/components/editor/scene/EditorScene"; import { SceneLoadingOverlay } from "@/components/ui/SceneLoadingOverlay"; import { Subtitles } from "@/components/ui/Subtitles"; import { useEditorHistory } from "@/hooks/editor/useEditorHistory"; import type { CinematicDefinition } from "@/types/cinematics/cinematics"; import { useEditorSceneData } from "@/hooks/editor/useEditorSceneData"; -import type { MapNode, TransformMode } from "@/types/editor/editor"; +import type { + EditorCinematicPreviewRequest, + MapNode, + TransformMode, +} from "@/types/editor/editor"; import type { SceneLoadingState } from "@/types/world/sceneLoading"; import { logger } from "@/utils/core/Logger"; import { @@ -82,7 +85,7 @@ export function EditorPage(): React.JSX.Element { ); const editorLoadingState: SceneLoadingState = isMapLoading ? { - currentStep: "Récupération blocking", + currentStep: "Chargement de la carte", progress: 0.08, status: "loading" as const, } diff --git a/src/types/editor/editor.ts b/src/types/editor/editor.ts index e4f3d9c..cd90b1d 100644 --- a/src/types/editor/editor.ts +++ b/src/types/editor/editor.ts @@ -1,3 +1,5 @@ +import type { CinematicDefinition } from "@/types/cinematics/cinematics"; + export type { HierarchicalMapNode, MapNode, @@ -5,3 +7,8 @@ export type { } from "@/types/map/mapScene"; export type TransformMode = "translate" | "rotate" | "scale"; + +export interface EditorCinematicPreviewRequest { + id: string; + cinematic: CinematicDefinition; +} diff --git a/src/types/game.ts b/src/types/game.ts index 123ac40..d824cf6 100644 --- a/src/types/game.ts +++ b/src/types/game.ts @@ -1,4 +1,5 @@ import type { Vector3Tuple } from "@/types/three/three"; +import type { RepairMissionId } from "@/types/gameplay/repairMission"; export type GameStep = | "intro" @@ -12,7 +13,7 @@ export type GameStep = | "manipulation" | "outOfFabrik"; -export type MainGameState = "intro" | "ebike" | "pylon" | "farm" | "outro"; +export type MainGameState = "intro" | RepairMissionId | "outro"; export interface Zone { id: string; diff --git a/src/types/gameplay/repairMission.ts b/src/types/gameplay/repairMission.ts index bef3cbe..17163f5 100644 --- a/src/types/gameplay/repairMission.ts +++ b/src/types/gameplay/repairMission.ts @@ -4,7 +4,9 @@ import type { Vector3Tuple, } from "@/types/three/three"; -export type RepairMissionId = "ebike" | "pylon" | "farm"; +export const REPAIR_MISSION_IDS = ["ebike", "pylon", "farm"] as const; + +export type RepairMissionId = (typeof REPAIR_MISSION_IDS)[number]; export interface RepairMissionTriggerConfig { mission: RepairMissionId; diff --git a/src/utils/debug/debugGameStateCookie.ts b/src/utils/debug/debugGameStateCookie.ts index 83268d2..aa68d83 100644 --- a/src/utils/debug/debugGameStateCookie.ts +++ b/src/utils/debug/debugGameStateCookie.ts @@ -1,3 +1,5 @@ +import { logger } from "@/utils/core/Logger"; + const DEBUG_GAME_STATE_COOKIE_NAME = "la-fabrik-debug-game-state"; const DEBUG_GAME_STATE_COOKIE_MAX_AGE = 60 * 60 * 24 * 30; @@ -18,7 +20,11 @@ export function readDebugGameStateCookie(): unknown { try { return JSON.parse(decodeURIComponent(value)); - } catch { + } catch (error) { + logger.warn("DebugGameState", "Invalid debug game state cookie cleared", { + error: error instanceof Error ? error : String(error), + }); + clearDebugGameStateCookie(); return null; } } diff --git a/src/utils/map/mapRuntimeClassification.ts b/src/utils/map/mapRuntimeClassification.ts index 8f02234..21dfe43 100644 --- a/src/utils/map/mapRuntimeClassification.ts +++ b/src/utils/map/mapRuntimeClassification.ts @@ -1,16 +1,8 @@ import type { MapNode } from "@/types/map/mapScene"; +import { VEGETATION_MAP_NODE_NAMES } from "@/data/world/vegetationConfig"; import { isInstancedMapNodeName } from "@/utils/map/isInstancedMapNodeName"; const MAP_STRUCTURE_NODE_NAMES = new Set(["Scene", "blocking", "terrain"]); -const RUNTIME_VEGETATION_NODE_NAMES = new Set([ - "arbre", - "buisson", - "champdeble", - "champdesoja", - "champsdetournesol", - "potager", - "sapin", -]); function isRuntimeStructureMapNode(name: string): boolean { return MAP_STRUCTURE_NODE_NAMES.has(name); @@ -26,7 +18,7 @@ export function isRuntimeSingleMapNode(node: MapNode): boolean { } return ( - !RUNTIME_VEGETATION_NODE_NAMES.has(node.name) && + !VEGETATION_MAP_NODE_NAMES.has(node.name) && !isInstancedMapNodeName(node.name) ); } diff --git a/src/world/GameMap.tsx b/src/world/GameMap.tsx index 08a78d9..aee0ee8 100644 --- a/src/world/GameMap.tsx +++ b/src/world/GameMap.tsx @@ -148,7 +148,7 @@ export function GameMap({ useEffect(() => { onLoadingStateChange?.({ - currentStep: "Récupération blocking", + currentStep: "Chargement de la carte", progress: 0.05, status: "loading", }); @@ -163,7 +163,7 @@ export function GameMap({ } onLoadingStateChange?.({ - currentStep: "Importation des models", + currentStep: "Importation des modèles", progress: 0.18, status: "loading", });