diff --git a/src/hooks/three/useOctreeGraphNode.ts b/src/hooks/three/useOctreeGraphNode.ts index 5a1fe31..1cdc44b 100644 --- a/src/hooks/three/useOctreeGraphNode.ts +++ b/src/hooks/three/useOctreeGraphNode.ts @@ -1,9 +1,29 @@ import { useEffect, useRef } from "react"; import type { RefObject } from "react"; -import type { Object3D } from "three"; +import { Mesh, type Object3D } from "three"; import { Octree } from "three-stdlib"; import type { OctreeReadyHandler } from "@/types/three/three"; +// [diag] temporary — count meshes/triangles captured in the octree graph node +function snapshotGraphNode(node: Object3D): { + meshCount: number; + triCount: number; +} { + let meshCount = 0; + let triCount = 0; + node.traverse((obj) => { + if (obj instanceof Mesh) { + meshCount += 1; + const geom = obj.geometry; + const idx = geom.index; + triCount += idx + ? idx.count / 3 + : (geom.attributes.position?.count ?? 0) / 3; + } + }); + return { meshCount, triCount }; +} + export function useOctreeGraphNode( graphNodeRef: RefObject, onOctreeReady: OctreeReadyHandler, @@ -28,6 +48,15 @@ export function useOctreeGraphNode( const octree = new Octree(); octree.fromGraphNode(graphNode); + // [diag] temporary — log octree contents to detect partial builds + const snapshot = snapshotGraphNode(graphNode); + console.log("[octree:build]", { + rebuildKey, + meshCount: snapshot.meshCount, + triCount: Math.round(snapshot.triCount), + timestamp: performance.now().toFixed(0), + }); + onOctreeReady(octree); }, [enabled, graphNodeRef, onOctreeReady, rebuildKey]); } diff --git a/src/world/GameMapCollision.tsx b/src/world/GameMapCollision.tsx index b9344f8..dd83c76 100644 --- a/src/world/GameMapCollision.tsx +++ b/src/world/GameMapCollision.tsx @@ -277,12 +277,22 @@ function CollisionModelInstance({ // by MergedStaticMapModel and is unaffected. if (node.name !== "lafabrik") return; + // [diag] temporary — collect all door-like candidate names to debug stripping + const candidates: string[] = []; const removed: THREE.Object3D[] = []; sceneInstance.traverse((child) => { + if (/porte/i.test(child.name)) { + candidates.push(child.name); + } if (child.name === "porte") { removed.push(child); } }); + console.log("[lafabrik:porte-strip]", { + candidates, + strippedCount: removed.length, + strippedNames: removed.map((c) => c.name), + }); for (const child of removed) { child.removeFromParent(); } diff --git a/src/world/Lighting.tsx b/src/world/Lighting.tsx index d3625ea..547d6df 100644 --- a/src/world/Lighting.tsx +++ b/src/world/Lighting.tsx @@ -98,6 +98,27 @@ export function Lighting(): React.JSX.Element { hasShadowMap: !!sun.current.shadow.map, ...counts, }); + + // [diag] temporary — track WebGL context loss/restore to correlate with shadow drops + const canvas = gl.domElement; + const handleContextLost = (event: Event) => { + event.preventDefault(); + console.log("[ctx:lost]", { timestamp: performance.now().toFixed(0) }); + }; + const handleContextRestored = () => { + console.log("[ctx:restored]", { + timestamp: performance.now().toFixed(0), + }); + if (sun.current) { + sun.current.shadow.needsUpdate = true; + } + }; + canvas.addEventListener("webglcontextlost", handleContextLost); + canvas.addEventListener("webglcontextrestored", handleContextRestored); + return () => { + canvas.removeEventListener("webglcontextlost", handleContextLost); + canvas.removeEventListener("webglcontextrestored", handleContextRestored); + }; }, [gl, scene]); useDebugFolder("Lighting", (folder) => {