Skip to content

editor: level-follow camera, snapshot walk/drone suite, opening placement regressions - #752

Merged
wass08 merged 6 commits into
mainfrom
fix/adjustments-sep1
Sep 1, 2026
Merged

editor: level-follow camera, snapshot walk/drone suite, opening placement regressions#752
wass08 merged 6 commits into
mainfrom
fix/adjustments-sep1

Conversation

@wass08

@wass08 wass08 commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

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 levelMode and computes its destination analytically (getLevelPresentationY, shared with LevelSystem) instead of reading a mid-lerp/unpositioned Object3D.

Studio snapshot camera — a host-preselected crop seeds the standard/viewport/area pill instead of hiding it (only an explicit lockCrop hides 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 MoveWindowTool published drag state (including parentId) through useLiveNodeOverrides, but reparenting is structural: the wall's CSG merge and renderer nesting walk wall.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 π-off calculateCursorRotation (#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

  1. Levels: with 2+ levels, cycle stacked → exploded → solo: the camera pans to the selected level each time; add a level in exploded view: the camera navigates to it.
  2. Studio → take snapshot: crop pill visible; Walk locks immediately; wheel changes the lens; click shoots and the frame freezes until saved; Esc frees the cursor, Esc again cancels; E opens a door under the crosshair.
  3. Place a window preset on a wall: ghost + CSG cut track the hover, the facing triangle points at you, the click commits onto the wall. (Covered by the new private-editor e2e opening-placement-preview.spec.ts incl. 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

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

Note

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 with LevelSystem) 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: lockCrop is the only way hosts hide the crop UI (preselected crop just seeds the pill). Switching to walk/drone **flushSync**s first-person on and requestWalkthroughPointerLock (with optional re-lock retry). While pointer-locked, wheel adjusts lens and click shoots; captureShutterHold freezes movement/look from shutter through saved toast. Esc in capture pauses instead of exiting orbit; overlay shows WalkthroughCrosshair and updated hints.

Door/window placement: removes the incorrect calculateCursorRotation helper and derives world yaw from wall-child math (fixes facing triangle on half the walls). Window move preview reparents via useScene.updateNode instead 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.

wass08 and others added 6 commits September 1, 2026 14:51
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
@wass08
wass08 merged commit 9cdafb0 into main Sep 1, 2026
3 checks passed
@wass08
wass08 deleted the fix/adjustments-sep1 branch September 1, 2026 20:45

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5f48afe. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant