Feat/map-environment #6

Merged
math-pixel merged 116 commits from feat/map-environment into develop 2026-05-29 00:00:51 +00:00
12 changed files with 69 additions and 29 deletions
Showing only changes of commit 95ca1bbfde - Show all commits
+1 -1
View File
@@ -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:
+6 -7
View File
@@ -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;
+26 -2
View File
@@ -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({
<color attach="background" args={[fallbackColor]} />
) : null;
const fallback = fallbackModelPath ? (
<SkyModelErrorBoundary key={fallbackModelPath} fallback={colorFallback}>
<SkyModelErrorBoundary
key={fallbackModelPath}
fallback={colorFallback}
label="Fallback sky"
modelPath={fallbackModelPath}
>
<SkyModelContent
modelPath={fallbackModelPath}
scale={fallbackModelScale}
@@ -74,7 +93,12 @@ export function SkyModel({
);
return (
<SkyModelErrorBoundary key={modelPath} fallback={fallback}>
<SkyModelErrorBoundary
key={modelPath}
fallback={fallback}
label="Primary sky"
modelPath={modelPath}
>
<SkyModelContent modelPath={modelPath} scale={scale} />
</SkyModelErrorBoundary>
);
+1 -1
View File
@@ -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<string> = new Set(
REPAIR_MISSION_IDS,
);
+6
View File
@@ -83,6 +83,12 @@ export const VEGETATION_TYPE_KEYS = [
export type VegetationType = (typeof VEGETATION_TYPE_KEYS)[number];
export const VEGETATION_MAP_NODE_NAMES: ReadonlySet<string> = 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)
+6 -3
View File
@@ -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,
}
+7
View File
@@ -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;
}
+2 -1
View File
@@ -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;
+3 -1
View File
@@ -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;
+7 -1
View File
@@ -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;
}
}
+2 -10
View File
@@ -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)
);
}
+2 -2
View File
@@ -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",
});