The full collision BVH is built on every twin route, including ones that can never use
it. Measured at ~780 ms of synchronous main-thread work, and it is the single largest
identified contributor to the freeze in #717.
Evidence
CDP CPU profile of /twins/chatt/?ortho, self time:
_buildNodes 364 ms + 192 ms binned-SAH node builder (src/lib/cod/bvh.js)
_nodeBoundsFromRange 97 ms + 50 ms
StaticWorld.build 74 ms
----------------
~780 ms
Why it happens
tryBuildWalk() in src/twin/TwinCanvas.client.tsx gates only on:
if (!buildings || !terrain || !groundAt) return;
There is no mode check — the "mode" occurrences inside the function are all in comments.
It is called from handleBuildingsMesh, handleTerrainMesh and handleGroundReady, each
of which fires as soon as its data lands, on every route the twin renders.
Nothing outside walk consumes it
?ortho maps to rig mode orbit: const rigMode: RigMode = mode === 'ortho' ? 'orbit' : mode
Rig.collide is invoked only inside _walk() (src/stage/Rig.ts:573); _tour, _orbit
and _follow never call it
rig.walkMove is the embodied-walk seam and is only driven in walk
So orbit, ortho, tour and follow each pay ~780 ms for a structure they cannot touch.
Fix
Defer the build until walk mode is entered. The machinery already exists and was built for
earlier fixes in this arc:
The risk to respect
Entering walk must not now stall for 780 ms. If it does, the block has moved, not gone,
and the honest next step is chunking StaticWorld.build() rather than accepting a stutter
on mode switch. Measure the walk-entry cost as part of the fix.
Related
The full collision BVH is built on every twin route, including ones that can never use
it. Measured at ~780 ms of synchronous main-thread work, and it is the single largest
identified contributor to the freeze in #717.
Evidence
CDP CPU profile of
/twins/chatt/?ortho, self time:Why it happens
tryBuildWalk()insrc/twin/TwinCanvas.client.tsxgates only on:There is no mode check — the "mode" occurrences inside the function are all in comments.
It is called from
handleBuildingsMesh,handleTerrainMeshandhandleGroundReady, eachof which fires as soon as its data lands, on every route the twin renders.
Nothing outside walk consumes it
?orthomaps to rig modeorbit:const rigMode: RigMode = mode === 'ortho' ? 'orbit' : modeRig.collideis invoked only inside_walk()(src/stage/Rig.ts:573);_tour,_orbitand
_follownever call itrig.walkMoveis the embodied-walk seam and is only driven in walkSo orbit, ortho, tour and follow each pay ~780 ms for a structure they cannot touch.
Fix
Defer the build until walk mode is entered. The machinery already exists and was built for
earlier fixes in this arc:
tryBuildWalkRefis already the deferred-call seam (added for [diorama-walk] Walk mode (/chatt?diorama&walk) spawns you below the city — you fall forever #651's ordering race), sotriggering later is wiring rather than new machinery.
builtFromRefcomparing theterrain / buildings / groundAttriple) makes a later call safe and non-repeating.
pendingCollidersRefalready stashes landmark colliders that arrive before the controllerexists and drains them when it is built — that path was added for [diorama-walk] SketchUp landmarks and the bridges have no collision — you walk and ride straight through them #702 and is covered by
tests, so deferring cannot silently lose colliders.
The risk to respect
Entering walk must not now stall for 780 ms. If it does, the block has moved, not gone,
and the honest next step is chunking
StaticWorld.build()rather than accepting a stutteron mode switch. Measure the walk-entry cost as part of the fix.
Related