You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Production. /chatt?diorama&walk — the navbar "Play" link — never enters first-person mode.
The camera stays in orbit, which idle-drifts (src/stage/Rig.ts:681), so the site sits in a
slowly rotating overhead shot of the city forever. Shipped in ba474e0e (#718, PR #720),
deployed 2026-08-13 18:50Z.
The cycle
#718 made the collision-BVH build lazy — a real ~780 ms win — but gated it on the state the
build is required to produce. In src/twin/TwinCanvas.client.tsx:
hop
line
needs
if (walkParam && walkReady && !didAutoWalk.current) setMode('walk')
1192
walkReady
if (walkCtrl) onWalkReady?.()
789
walkCtrl
setWalkCtrl(ctrl) — last line of tryBuildWalk
712
the build to run
if (!wantsWalkWorldRef.current) return;← added by #718
620
wantsWalkWorldRef
wantsWalkWorldRef.current = mode === 'walk' — the only writer
729
mode === 'walk'
Hop 5 closes onto hop 1. The gate asked its own output for permission to open.
Manual entry still worked — digit 3 and the HUD ⋯ → Walk call setMode directly. That
was the workaround.
Why chatt specifically spins: the wide atlasBox drops the tour (hasTour false, :1072), so
the initial mode is orbit, which idle-drifts after 3 s.
The fix
The gate must ask "does this session want a walk world?" — a question with two sources that
are not downstream of the build: the ?walk URL intent at mount, and manual entry. wantsWalkWorldRef is now seeded during render from a walkIntent prop (it must be render
time: React runs child effects before parent effects, so the mesh handovers reach the gate
before any SceneInner effect has), and the mode effect became a latch — monotonic
false→true. The old assignment also tore the gate down on the way out of walk, discarding
meshes that arrived while you were back in orbit.
#718's win is untouched: ?ortho, plain /chatt, the atlas and /twins/* carry no walk, so
no BVH is built. #676's handshake is untouched: seeding changes when the controller is built,
not when the camera enters walk, so the 1200 m/s glide-under-the-city cannot return.
Why nothing caught it
Both guards were shaped so they could not fail — see #725 for the CI-coverage half. tests/unit/walk-world-lazy.test.ts was a regex over the source whose own comment anticipated
this exact bug ("the gate would be a permanent off-switch") and then asserted only that the
unlock line had been typed. It is replaced by tests/unit/walk-world-lazy.test.tsx, which
renders the real component with only the environment mocked and asserts walk is reachable —
proven red before the fix, and re-proven red by reverting the seed alone.
Production.
/chatt?diorama&walk— the navbar "Play" link — never enters first-person mode.The camera stays in
orbit, which idle-drifts (src/stage/Rig.ts:681), so the site sits in aslowly rotating overhead shot of the city forever. Shipped in
ba474e0e(#718, PR #720),deployed 2026-08-13 18:50Z.
The cycle
#718made the collision-BVH build lazy — a real ~780 ms win — but gated it on the state thebuild is required to produce. In
src/twin/TwinCanvas.client.tsx:if (walkParam && walkReady && !didAutoWalk.current) setMode('walk')walkReadyif (walkCtrl) onWalkReady?.()walkCtrlsetWalkCtrl(ctrl)— last line oftryBuildWalkif (!wantsWalkWorldRef.current) return;← added by #718wantsWalkWorldRefwantsWalkWorldRef.current = mode === 'walk'— the only writermode === 'walk'Hop 5 closes onto hop 1. The gate asked its own output for permission to open.
?at=deep links died with it —parseAtParamruns inside the gated function (676-685),so marker return links (Location HUD, spot markers and building inspection — make in-world reports reproducible #706) could not work either.
3and the HUD ⋯ → Walk callsetModedirectly. Thatwas the workaround.
hasTourfalse, :1072), sothe initial mode is
orbit, which idle-drifts after 3 s.The fix
The gate must ask "does this session want a walk world?" — a question with two sources that
are not downstream of the build: the
?walkURL intent at mount, and manual entry.wantsWalkWorldRefis now seeded during render from awalkIntentprop (it must be rendertime: React runs child effects before parent effects, so the mesh handovers reach the gate
before any
SceneInnereffect has), and themodeeffect became a latch — monotonicfalse→true. The old assignment also tore the gate down on the way out of walk, discarding
meshes that arrived while you were back in orbit.
#718's win is untouched:
?ortho, plain/chatt, the atlas and/twins/*carry nowalk, sono BVH is built. #676's handshake is untouched: seeding changes when the controller is built,
not when the camera enters walk, so the 1200 m/s glide-under-the-city cannot return.
Why nothing caught it
Both guards were shaped so they could not fail — see #725 for the CI-coverage half.
tests/unit/walk-world-lazy.test.tswas a regex over the source whose own comment anticipatedthis exact bug ("the gate would be a permanent off-switch") and then asserted only that the
unlock line had been typed. It is replaced by
tests/unit/walk-world-lazy.test.tsx, whichrenders the real component with only the environment mocked and asserts walk is reachable —
proven red before the fix, and re-proven red by reverting the seed alone.