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",
});