fix(world): wrap stage and player in suspense to prevent scene remount

Late asset loads inside GameStageContent (e.g. EbikeSpeedometer's
useTexture) and the spawn-player block were bubbling Suspense up to the
root boundary in pages/page.tsx, which unmounted World mid-load and
triggered a redundant octree rebuild + shadow re-config. Localize the
suspension by wrapping each block in its own Suspense fallback.

Also mount DebugOctreeVisualization conditionally on the new debug
toggle.
This commit is contained in:
Tom Boullay
2026-06-01 14:14:27 +02:00
parent fd0b9e2749
commit 63952912b5
+21 -13
View File
@@ -9,12 +9,16 @@ import { useCameraMode } from "@/hooks/debug/useCameraMode";
import { useEnvironmentDebug } from "@/hooks/debug/useEnvironmentDebug"; import { useEnvironmentDebug } from "@/hooks/debug/useEnvironmentDebug";
import { useMapPerformanceDebug } from "@/hooks/debug/useMapPerformanceDebug"; import { useMapPerformanceDebug } from "@/hooks/debug/useMapPerformanceDebug";
import { useCharacterDebug } from "@/hooks/debug/useCharacterDebug"; import { useCharacterDebug } from "@/hooks/debug/useCharacterDebug";
import { useDebugVisualsDebug } from "@/hooks/debug/useDebugVisualsDebug";
import { useSceneMode } from "@/hooks/debug/useSceneMode"; import { useSceneMode } from "@/hooks/debug/useSceneMode";
import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot"; import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot";
import { useWorldSceneLoading } from "@/hooks/world/useWorldSceneLoading"; import { useWorldSceneLoading } from "@/hooks/world/useWorldSceneLoading";
import { useGameStore } from "@/managers/stores/useGameStore"; import { useGameStore } from "@/managers/stores/useGameStore";
import { useDebugVisualsStore } from "@/managers/stores/useDebugVisualsStore";
import { DebugCameraControls } from "@/components/debug/scene/DebugCameraControls"; import { DebugCameraControls } from "@/components/debug/scene/DebugCameraControls";
import { DebugHelpers } from "@/components/debug/scene/DebugHelpers"; import { DebugHelpers } from "@/components/debug/scene/DebugHelpers";
import { DebugOctreeVisualization } from "@/components/debug/DebugOctreeVisualization";
import { DebugPlayerModel } from "@/components/debug/DebugPlayerModel";
import { HandTrackingGlove } from "@/components/three/handTracking/HandTrackingGlove"; import { HandTrackingGlove } from "@/components/three/handTracking/HandTrackingGlove";
import { Environment } from "@/world/Environment"; import { Environment } from "@/world/Environment";
import { GameCinematics } from "@/world/GameCinematics"; import { GameCinematics } from "@/world/GameCinematics";
@@ -36,10 +40,15 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
useEnvironmentDebug(); useEnvironmentDebug();
useMapPerformanceDebug(); useMapPerformanceDebug();
useCharacterDebug(); useCharacterDebug();
useDebugVisualsDebug();
const cameraMode = useCameraMode(); const cameraMode = useCameraMode();
const sceneMode = useSceneMode(); const sceneMode = useSceneMode();
const mainState = useGameStore((state) => state.mainState); const mainState = useGameStore((state) => state.mainState);
const showDebugPlayerModel = useDebugVisualsStore(
(state) => state.showPlayerModel,
);
const showDebugOctree = useDebugVisualsStore((state) => state.showOctree);
const { status, usageStatus } = useHandTrackingSnapshot(); const { status, usageStatus } = useHandTrackingSnapshot();
const { const {
octree, octree,
@@ -48,9 +57,6 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
handleGameStageLoaded, handleGameStageLoaded,
handleGameMapLoaded, handleGameMapLoaded,
handleOctreeReady, handleOctreeReady,
handleShadowWarmupReady,
handleShadowWarmupStarted,
shouldWarmUpShadows,
} = useWorldSceneLoading({ sceneMode, onLoadingStateChange }); } = useWorldSceneLoading({ sceneMode, onLoadingStateChange });
const playerSpawnPosition = const playerSpawnPosition =
sceneMode === "game" sceneMode === "game"
@@ -65,15 +71,15 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
return ( return (
<> <>
<Environment <Environment />
shadowWarmup={{
active: shouldWarmUpShadows,
onReady: handleShadowWarmupReady,
onStarted: handleShadowWarmupStarted,
}}
/>
<Lighting /> <Lighting />
<DebugHelpers /> <DebugHelpers />
{showDebugOctree ? <DebugOctreeVisualization octree={octree} /> : null}
{showDebugPlayerModel ? (
<Suspense fallback={null}>
<DebugPlayerModel />
</Suspense>
) : null}
{showHandTrackingGloves ? ( {showHandTrackingGloves ? (
<Suspense fallback={null}> <Suspense fallback={null}>
<HandTrackingGlove handedness="left" /> <HandTrackingGlove handedness="left" />
@@ -92,11 +98,13 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
{showGameStage ? ( {showGameStage ? (
<Physics> <Physics>
<GameStageLoaded onLoaded={handleGameStageLoaded} /> <GameStageLoaded onLoaded={handleGameStageLoaded} />
<GameStageContent /> <Suspense fallback={null}>
<GameStageContent />
</Suspense>
</Physics> </Physics>
) : null} ) : null}
{spawnPlayer ? ( {spawnPlayer ? (
<> <Suspense fallback={null}>
<GameMusic /> <GameMusic />
{mainState === "outro" ? <GameCinematics /> : null} {mainState === "outro" ? <GameCinematics /> : null}
{mainState !== "intro" ? <GameDialogues /> : null} {mainState !== "intro" ? <GameDialogues /> : null}
@@ -105,7 +113,7 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
octree={octree} octree={octree}
spawnPosition={playerSpawnPosition} spawnPosition={playerSpawnPosition}
/> />
</> </Suspense>
) : null} ) : null}
</> </>
) : ( ) : (