Skip to content

A pending camera rotation survives a file swap and replays onto the next model #3364

Description

@louistrue

resetViewerState does not clear pendingCameraRotation, so a rotation recorded before a file swap can be replayed onto the next model's camera when its viewport registers callbacks.

The mechanism

apps/viewer/src/store/slices/cameraSlice.ts:

setCameraRotation: (cameraRotation, actuator) => {
  // ... was recorded in state and never reached the camera: success reported
  // for something that did not happen. Remember it and replay on registration.
  set({ cameraRotation, pendingCameraRotation: actuator ? null : cameraRotation });
},
setCameraCallbacks: (cameraCallbacks) => {
  const pending = get().pendingCameraRotation;
  set({ cameraCallbacks, pendingCameraRotation: null });
  if (pending) cameraCallbacks.setCameraRotation?.(pending);
},

The replay is deliberate and correct on its own: it exists so a rotation set before the renderer is ready is not silently dropped.

The gap is that a session reset does not clear it. resetViewerState resets cameraRotation to defaultCameraRotation() but leaves pendingCameraRotation holding the outgoing model's value. If the next model's viewport then calls setCameraCallbacks, the stale rotation is applied over the freshly reset one.

Scope

Pre-existing. Verified against main before the store-teardown refactor (#3358): resetViewerState there never mentioned pendingCameraRotation either. The refactor preserves the behaviour exactly, which is why it is filed rather than changed there.

Marked unproven: read from the source, not reproduced in a running viewer. Reproducing it wants a rotation set while no actuator is registered, then a file swap, then the new viewport mounting.

Fix

Add pendingCameraRotation to cameraTeardown's owns and set it to null on session-reset. Note that #3358's teardown-registry.test.ts pins the emitted key set per scope and fails in both directions, so the pinned list needs updating in the same commit. That is intended.

How this was found

CodeRabbit, reviewing #3358.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions