docs: update scene runtime and debug toggles
- Drop SceneShadowWarmup section, document centralized shadow config. - Document the localized Suspense boundaries in World.tsx. - Document the new player model and octree debug visualizations. - Open note about intermittent first-load shadow rendering.
This commit is contained in:
@@ -74,28 +74,32 @@ It tracks:
|
|||||||
- `gameMapLoaded`: map data and visible map nodes settled
|
- `gameMapLoaded`: map data and visible map nodes settled
|
||||||
- `gameStageLoaded`: Rapier gameplay stage mounted
|
- `gameStageLoaded`: Rapier gameplay stage mounted
|
||||||
- `showGameStage`: true when the map is ready enough to mount gameplay content
|
- `showGameStage`: true when the map is ready enough to mount gameplay content
|
||||||
- `shadowsReady`: renderer, shadow lights, and scene matrices have been forced once after the scene is mounted
|
- `gameplayReady`: true when map, stage, and octree are all ready
|
||||||
- `gameplayReady`: true when map, stage, octree, and the shadow warmup are all ready
|
|
||||||
|
|
||||||
The base game-scene readiness condition before the shadow warmup is:
|
The game-scene readiness condition is:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
showGameStage && gameStageLoaded && octree !== null;
|
showGameStage && gameStageLoaded && octree !== null;
|
||||||
```
|
```
|
||||||
|
|
||||||
After that condition is met, `SceneShadowWarmup` runs one final loading step:
|
Shadows are configured once when `Lighting` mounts (renderer `shadowMap.enabled`, sun
|
||||||
|
`shadow.autoUpdate = true`, bias and frustum from `SHADOW_CONFIG` in
|
||||||
|
`src/data/world/lightingConfig.ts`). The shadow map then refreshes every frame and
|
||||||
|
follows the player camera through the sun's `target`. The earlier `SceneShadowWarmup`
|
||||||
|
step has been removed — the visible loading overlay no longer waits for a forced
|
||||||
|
shadow refresh because `autoUpdate` covers steady-state rendering.
|
||||||
|
|
||||||
```txt
|
### Avoiding global scene remounts
|
||||||
Activation des ombres -> Ombres prêtes -> Gameplay prêt
|
|
||||||
```
|
|
||||||
|
|
||||||
This keeps the loading overlay visible until the renderer shadow map, shadow-casting light, and mounted scene graph have all been explicitly refreshed.
|
Heavy stage components (`GameStageContent`, `Player`, dialogues) load assets via
|
||||||
|
`useGLTF`/`useTexture` without preload (e.g. `EbikeSpeedometer` calls `useTexture`
|
||||||
After the warmup, shadow maps switch back to manual refreshes driven by `Lighting`.
|
when the bike mounts). To prevent any late suspension from bubbling up to the
|
||||||
The sun still follows the player camera, but the shadow map is only marked dirty
|
root `<Suspense>` boundary in `src/pages/page.tsx` and unmounting the entire
|
||||||
when the camera has moved enough and a short refresh interval has elapsed. This
|
world (which would trigger a redundant octree rebuild and shadow re-config), the
|
||||||
keeps shadows present after loading without paying for a full shadow render every
|
game stage block and the spawn-player block are wrapped in their own
|
||||||
frame across the dense vegetation chunks.
|
`<Suspense fallback={null}>` boundaries inside `src/world/World.tsx`. Any new
|
||||||
|
sibling that suspends late should be added inside one of these boundaries or get
|
||||||
|
its own.
|
||||||
|
|
||||||
The debug physics scene is ready when:
|
The debug physics scene is ready when:
|
||||||
|
|
||||||
|
|||||||
@@ -20,3 +20,50 @@ If DevTools still opens a bundled file, stop the dev server, clear Vite's cached
|
|||||||
rm -rf node_modules/.vite
|
rm -rf node_modules/.vite
|
||||||
npm run dev:three-debug
|
npm run dev:three-debug
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Visual debug toggles
|
||||||
|
|
||||||
|
The `Debug` folder of the runtime debug GUI exposes inspection toggles backed by
|
||||||
|
`src/managers/stores/useDebugVisualsStore.ts`:
|
||||||
|
|
||||||
|
- **Show Player Model** — renders the main character GLTF in front of the
|
||||||
|
current camera (`src/components/debug/DebugPlayerModel.tsx`). The model is
|
||||||
|
positioned in camera-local space so it stays visible regardless of pitch.
|
||||||
|
- **Show Octree** — overlays the collision octree as colored line segments,
|
||||||
|
one wireframe per spatial cell (`src/components/debug/DebugOctreeVisualization.tsx`).
|
||||||
|
Cells are colored by depth. Use it to inspect collision precision around
|
||||||
|
doorways or passages.
|
||||||
|
- **Octree Max Depth** — caps how deep the octree visualization recurses
|
||||||
|
(default 6). Increase to see leaf-level subdivisions; decrease to keep the
|
||||||
|
scene readable when the tree is large.
|
||||||
|
|
||||||
|
The octree visualization reads the live `Octree` instance from `World`. The
|
||||||
|
mesh uses `depthTest: false` and a high `renderOrder`, so cells stay visible
|
||||||
|
through opaque geometry.
|
||||||
|
|
||||||
|
## Shadow rendering intermittence (open investigation)
|
||||||
|
|
||||||
|
Shadows occasionally fail to render on initial load even though the
|
||||||
|
`Lighting` configuration runs to completion (verified through diagnostic logs).
|
||||||
|
The issue is not deterministic across runs with identical config. Suspected
|
||||||
|
contributors:
|
||||||
|
|
||||||
|
- WebGL context restoration timing (`webglcontextrestored` rebinds shadow map
|
||||||
|
state in `src/pages/page.tsx`).
|
||||||
|
- First-frame shadow map being rendered before any mesh has its
|
||||||
|
`castShadow`/`receiveShadow` flag set; `autoUpdate=true` should fix it on the
|
||||||
|
next frame, but a single dropped frame is still visible at very first paint.
|
||||||
|
- HMR/state interactions in dev mode that do not occur in production builds.
|
||||||
|
|
||||||
|
Mitigations already applied:
|
||||||
|
|
||||||
|
- Shadow config centralized in `src/data/world/lightingConfig.ts`
|
||||||
|
(`bias=0`, `normalBias=0`, `cameraSize=95`, matching the historically working
|
||||||
|
values from `develop`).
|
||||||
|
- Late-suspension Suspense boundaries in `World.tsx` to prevent global scene
|
||||||
|
remounts that would re-run shadow setup mid-load.
|
||||||
|
|
||||||
|
If the issue reproduces in production, capture a screenshot plus the
|
||||||
|
`[diag]`-style logs from `useOctreeGraphNode`, `Lighting`, and `GameMapCollision`
|
||||||
|
to confirm whether the third configuration pass is happening (which would
|
||||||
|
indicate a remaining suspending hook outside the existing Suspense boundaries).
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
} from "@/data/world/octreeCollisionConfig";
|
} from "@/data/world/octreeCollisionConfig";
|
||||||
import { getMapModelScaleMultiplier } from "@/data/world/mapInstancingConfig";
|
import { getMapModelScaleMultiplier } from "@/data/world/mapInstancingConfig";
|
||||||
import { useCharacterDebugStore } from "@/managers/stores/useCharacterDebugStore";
|
import { useCharacterDebugStore } from "@/managers/stores/useCharacterDebugStore";
|
||||||
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
import { WorldBoundsCollision } from "@/world/collision/WorldBoundsCollision";
|
import { WorldBoundsCollision } from "@/world/collision/WorldBoundsCollision";
|
||||||
import type { MapNode } from "@/types/map/mapScene";
|
import type { MapNode } from "@/types/map/mapScene";
|
||||||
import type { OctreeReadyHandler, Vector3Tuple } from "@/types/three/three";
|
import type { OctreeReadyHandler, Vector3Tuple } from "@/types/three/three";
|
||||||
@@ -125,21 +126,24 @@ export function GameMapCollision({
|
|||||||
const settledCollisionNodesRef = useRef(new Set<number>());
|
const settledCollisionNodesRef = useRef(new Set<number>());
|
||||||
const loadedNotifiedRef = useRef(false);
|
const loadedNotifiedRef = useRef(false);
|
||||||
const [settledCollisionNodeCount, setSettledCollisionNodeCount] = useState(0);
|
const [settledCollisionNodeCount, setSettledCollisionNodeCount] = useState(0);
|
||||||
|
const mainState = useGameStore((state) => state.mainState);
|
||||||
const terrainHeight = useTerrainHeightSampler();
|
const terrainHeight = useTerrainHeightSampler();
|
||||||
const collisionNodes = nodes.filter(isCollisionNode);
|
const collisionNodes = nodes.filter(isCollisionNode);
|
||||||
|
const includeCharacterCollisions = mainState !== "ebike";
|
||||||
|
const characterCollisionCount = includeCharacterCollisions
|
||||||
|
? CHARACTER_IDS.length
|
||||||
|
: 0;
|
||||||
const collisionSourceCount =
|
const collisionSourceCount =
|
||||||
collisionNodes.length + proxyNodes.length + CHARACTER_IDS.length;
|
collisionNodes.length + proxyNodes.length + characterCollisionCount;
|
||||||
const collisionReady =
|
const collisionReady =
|
||||||
mapReady && settledCollisionNodeCount >= collisionNodes.length;
|
mapReady && settledCollisionNodeCount >= collisionNodes.length;
|
||||||
const characterCollisionSignature = useCharacterDebugStore((state) =>
|
const characterCollisionSignature = useCharacterDebugStore((state) =>
|
||||||
CHARACTER_IDS.map((id) => {
|
includeCharacterCollisions
|
||||||
const character = state.characters[id];
|
? CHARACTER_IDS.map((id) => {
|
||||||
return [
|
const character = state.characters[id];
|
||||||
...character.position,
|
return [...character.position, ...character.rotation].join(",");
|
||||||
...character.rotation,
|
}).join("|")
|
||||||
...character.scale,
|
: "characters-hidden",
|
||||||
].join(",");
|
|
||||||
}).join("|"),
|
|
||||||
);
|
);
|
||||||
const collisionRebuildKey = collisionReady
|
const collisionRebuildKey = collisionReady
|
||||||
? `${collisionNodes.length}:${collisionSourceCount}:${characterCollisionSignature}`
|
? `${collisionNodes.length}:${collisionSourceCount}:${characterCollisionSignature}`
|
||||||
@@ -221,7 +225,7 @@ export function GameMapCollision({
|
|||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
{mapReady ? (
|
{mapReady && includeCharacterCollisions ? (
|
||||||
<CharacterCollisionProxies terrainHeight={terrainHeight} />
|
<CharacterCollisionProxies terrainHeight={terrainHeight} />
|
||||||
) : null}
|
) : null}
|
||||||
{mapReady
|
{mapReady
|
||||||
|
|||||||
Reference in New Issue
Block a user