editor: level-follow camera, snapshot walk/drone suite, opening placement regressions - #752
Merged
Conversation
Switching level presentation (stacked/exploded/solo) never moved the camera — the level-frame effect only fired on selection change — and a freshly created level framed at y=0 because the effect read the level Object3D's position before LevelSystem had lerped it anywhere. The effect now derives the destination analytically (stacked elevation + exploded gap, shared with LevelSystem via getLevelPresentationY), watches levelMode, and skips when already on target — which also swallows the thumbnail generator's synchronous stacked/restore round-trip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018gQSsJ7nfdARkNH5PcKUjt
… lock, wheel lens + click shutter - The Studio capbar's preselected crop no longer hides the standard/viewport/area pill: preselecting seeds the overlay, and only an explicit host lockCrop (the publish cover's exact-shape capture) hides the switcher. - Switching the snapshot camera to walk/drone locks the pointer in the same click (flushSync mounts the controls first) instead of demanding a second canvas click. - While walk/drone hold the lock: wheel drives the lens (accumulated sub-degree deltas, wheel-up zooms in) and left click fires the shutter alongside Enter. Walk's door-toggle click is silenced during capture, and the acquiring click can't shoot (shutter gates on the lock being held). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018gQSsJ7nfdARkNH5PcKUjt
Two regressions in opening placement: - #718 rewrote MoveWindowTool to publish drag state through useLiveNodeOverrides, including `parentId` — but reparenting is structural: the wall's CSG merge and the renderer's nesting walk the wall's `children` array, which an override never joins. Placing a window preset showed no on-wall preview at all (no cut, no mesh — only the override-independent guides), while doors, still on scene writes, worked. The wall branch and free-follow now write the scene exactly like MoveDoorTool (reparent on host change, direct mesh transform + live transforms on same-host slides), and stale overrides are dropped when entering the wall mode. - The door/window PLACEMENT tools still fed `calculateCursorRotation` into the cursor and facing triangle — the helper #643 identified as π off and migrated every other caller away from. The triangle pointed at the far side of the wall on half the walls. Both tools now use the wall-child world yaw (`itemRotation - wallAngle`, the move tools' convention), and the helper is deleted so nothing can regress onto it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018gQSsJ7nfdARkNH5PcKUjt
… re-locks Four snapshot-camera fixes: - E/R open doors and windows again during capture walk (only the CLICK path is capture-gated now — a locked click is the shutter), and the walkthrough crosshair (dot → green ring over an interactable) renders in the capture overlay, which replaces the walkthrough HUD. - Esc acts like P in walk/drone: the browser's pointer-lock exit pauses (cursor freed, camera and capture kept) instead of bailing to orbit and throwing away the framed pose; the overlay only dismisses on Esc from orbit. Covers both the keydown path and the no-keydown native unlock. - The click shutter actually fires: FirstPersonControls' document-capture mousedown handler stops propagation while locked, so the overlay's listener moves to window-capture (and the door-toggle mousedown yields during capture). - Switching cameras right after freeing the cursor hit the browser's ~1.25s re-lock cooldown — the reason drone (only reachable with a free cursor) never locked while walk-from-orbit did. The lock helper retries once after the cooldown while still framing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018gQSsJ7nfdARkNH5PcKUjt
From the click/Enter until the saved toast clears, look, walk physics and drone motion hold still — a late WASD tap or mouse twitch no longer shifts the frame out from under the shot the user just took. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018gQSsJ7nfdARkNH5PcKUjt
First Esc frees the cursor (pause); with the cursor already free, Esc now cancels capture — setCaptureMode(false) lands the camera back on orbit — instead of doing nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018gQSsJ7nfdARkNH5PcKUjt
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5f48afe. Configure here.
| useEffect(() => { | ||
| useEditor.getState().setCaptureShutterHold(captureState !== 'idle') | ||
| return () => useEditor.getState().setCaptureShutterHold(false) | ||
| }, [captureState]) |
There was a problem hiding this comment.
Shutter hold leaks after cancel
High Severity
captureShutterHold is tied only to captureState !== 'idle', and leaving capture via setCaptureMode(false) does not reset captureState. Canceling during capturing (X, Esc, or a shot that never reaches snapshot:saved) leaves the hold true, so the next walk or drone session cannot look or move until capture is opened again.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 5f48afe. Configure here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What does this PR do?
Adjustments pass across three areas:
Level camera — switching level presentation (stacked/exploded/solo) now pans the camera to the current level, and a freshly created level frames correctly: the level-frame effect watches
levelModeand computes its destination analytically (getLevelPresentationY, shared withLevelSystem) instead of reading a mid-lerp/unpositionedObject3D.Studio snapshot camera — a host-preselected crop seeds the standard/viewport/area pill instead of hiding it (only an explicit
lockCrophides it, for exact-shape captures like publish covers). Walk/drone: pointer locks in the same click that picks the camera (with a one-shot retry after the browser's ~1.25s re-lock cooldown — the reason drone, only reachable with a freed cursor, never locked); wheel drives the lens and left-click fires the shutter alongside Enter; E/R still open doors while framing and the walkthrough crosshair renders in the capture overlay; Esc pauses (frees the cursor, keeps camera + capture) and a second Esc cancels; from the shutter until the saved toast, look/walk/drone freeze so a late twitch can't shift the shot.Opening placement regressions — #718's
MoveWindowToolpublished drag state (includingparentId) throughuseLiveNodeOverrides, but reparenting is structural: the wall's CSG merge and renderer nesting walkwall.children, so window presets showed no on-wall preview at all. The wall branch and free-follow are back on door-parity scene writes. And the door/window placement tools were the last two callers of the π-offcalculateCursorRotation(#643): the facing triangle pointed at the far side of the wall on half the walls — both now use the wall-child world yaw and the helper is deleted.How to test
opening-placement-preview.spec.tsincl. side↔yaw contract.)Screenshots / screen recording
Recorded run of the opening-placement e2e is in the private repo showcase (
e2e/showcase/dist). Happy to attach a clip on request.Checklist
bun devbun checkto verify)mainbranchNote
Medium Risk
Window drag now issues scene graph updates during preview (reparenting), and capture/walkthrough input routing is easy to regress; changes are editor UX and placement preview, not auth or persistence APIs.
Overview
Level orbit camera now pans when the selected level or level presentation mode (stacked/exploded/solo) changes. Target height comes from
getLevelPresentationY(shared withLevelSystem) instead of live mesh Y, so new levels and mid-lerp mode switches frame correctly; a small idempotence check avoids redundant moves (e.g. thumbnail generation).Studio snapshot capture is expanded for walk/drone:
lockCropis the only way hosts hide the crop UI (preselected crop just seeds the pill). Switching to walk/drone **flushSync**s first-person on andrequestWalkthroughPointerLock(with optional re-lock retry). While pointer-locked, wheel adjusts lens and click shoots;captureShutterHoldfreezes movement/look from shutter through saved toast. Esc in capture pauses instead of exiting orbit; overlay showsWalkthroughCrosshairand updated hints.Door/window placement: removes the incorrect
calculateCursorRotationhelper and derives world yaw from wall-child math (fixes facing triangle on half the walls). Window move preview reparents viauseScene.updateNodeinstead of live overrides so wall CSG and nesting see the window on the wall again.Reviewed by Cursor Bugbot for commit 5f48afe. Bugbot is set up for automated code reviews on this repo. Configure here.