Wait — I need to double check one thing in the existing code: toggleUI is referenced in keydown handler (document.body.classList.toggle('noui'), toggleUI()) — my toggleUI implementation reads the class state after toggle. Good.
Also snapshot() — preserveDrawingBuffer: true is set on renderer, good.
One issue: in applyQuality, I call resize() — resize is defined later via function declaration hoisting... applyQuality is called at the bottom after resize() definition, so fine.
Another: $('#btn-quality').click() triggers the click handler which flips from high → low. Good.
renderer.info.render.calls — after composer.render() includes portal passes. Fine.
One concern in frame(): sun.position.x += camera.position.x - curWorld * WORLD_DX; — that's wrong! applyEnv sets sun.position relative to the world center already (cx + dir*90). Then I add (camera.x - cx) which moves it relative to player... Actually sun is directional; its position defines direction relative to target. If I set target to camera position and shift sun.x by player local x, the direction changes with player movement — shadows shift direction as you walk, which would look wrong. Better: keep applyEnv's sun position (world-centered), but shift both sun.position and sun.target by the same player offset so direction stays constant while the shadow camera follows the player.
Let me fix:
This keeps direction constant (both shifted equally). Good.
Also potential double storePrevZ() per frame — fine.
Note: renderPortalViews() is called before applyEnv(curWorld) main pass — inside it applyEnv(link.world) is applied, then after the loop we apply curWorld env. Good ordering.
Also check renderPortalViews renders ALL portals of current world (up to 3 in atrium) — each renders scene from vCam with the portal surface showing shimmer due to uPortalDepth=1, preventing recursion. Good.
One more thing: in frame() I set renderer.shadowMap.needsUpdate = true before each main render, but portal passes also render the scene — shadows autoUpdate=false means shadow maps only refresh when needsUpdate=true; I set it before main render... but the portal render passes happen before and would use stale shadow maps — fine (they're 1-frame-old).
Hmm, but actually with autoUpdate=false and needsUpdate=true, the shadow map updates during the next render call — which is the portal pass in the next frame ordering... Order per frame: renderPortalViews() (renders scene 1-3 times), then main render. needsUpdate set before main → shadow update happens during composer.render() → portals use previous frame's shadows. Acceptable.
Let me fix the sun shift issue in my part2 file, then append.