decentraland / decentraland/godot-explorer
[Bug]: Touch look frozen after a scene deletes MainCamera mid transition-back (mobile)
- Dominant language
- Rust
- Stars
- 18
- Forks
- 19
- Avg merge
- 4d 2h
- Merged PRs (30d)
- 43
Description
### 🕹️Platform
Android / iOS (mobile build)
### 🔢App Version
Godot explorer `34fe6bae` — `fix(crdt): DeleteComponent declared a 28-byte length while writing 20`
### 📱Device Information
Not specified — reproduced on mobile build
### 📄Issue description
Touch camera rotation stays frozen after a scene turns its `VirtualCamera` off by deleting `MainCamera` from `engine.CameraEntity`. Walking works, joystick works, taps fire — drag-to-look does nothing for the rest of the scene. Only recovery is teleporting to another parcel scene.
Trigger: the delete coincides with a `movePlayerTo` teleport, and the scene re-sets `MainCamera` on the following frame before deleting it again. The virtual-camera entity is never destroyed.
Root cause: the `last_camera_reached && raycast_use_cursor_position` guard in `dcl_global_camera_controller.gd` never fires — the controller is mid transition-back, `last_camera_reached == false`. `raycast_use_cursor_position` stays `true`, and `mobile_camera_input.gd` routes touch drags to cursor movement instead of camera look. Recovery only exists on `_on_parcel_scene_changed` → `_force_return_to_player_camera`.
Not the same case as #2612 (closed): there the virtual-camera entity is torn down and the guard fires. Here the entity survives and only `MainCamera` is toggled.
Scene: CS 1.6 clone (`boedo.dcl.eth`). First-person `CameraModeArea`; `VirtualCamera` used only for the death cam.
Minimal repro scene:
```ts
import { engine, MainCamera, Transform, VirtualCamera } from '@dcl/sdk/ecs'
import { movePlayerTo } from '~system/RestrictedActions'
export function main() {
const camera = engine.addEntity()
Transform.create(camera, { position: { x: 8, y: 3, z: 8 } })
VirtualCamera.create(camera, { defaultTransition: { transitionMode: VirtualCamera.Transition.Time(0) } })
let elapsed = 0
let step = 0
engine.addSystem((dt) => {
elapsed += dt
if (step === 0 && elapsed > 3) {
MainCamera.createOrReplace(engine.CameraEntity, { virtualCameraEntity: camera })
step = 1
} else if (step === 1 && elapsed > 6) {
movePlayerTo({ newRelativePosition: { x: 4, y: 0, z: 4 } })
MainCamera.deleteFrom(engine.CameraEntity)
step = 2
} else if (step === 2) {
MainCamera.createOrReplace(engine.CameraEntity, { virtualCameraEntity: camera }) // next frame
step = 3
} else if (step === 3) {
MainCamera.deleteFrom(engine.CameraEntity) // frame after
step = 4
}
})
}
```
Scene-side workaround deployed: no `VirtualCamera` on `platform === 'mobile'` (no death cam on mobile), no `MainCamera` re-set on the stale frame. Both removed once this is fixed. The scene-side race is already fixed — the explorer should survive it regardless.
Related:
- [#2612](https://github.com/decentraland/godot-explorer/issues/2612) — same symptom, closed. Virtual-camera entity destroyed there, so the guard fires.
- [#2160](https://github.com/decentraland/godot-explorer/issues/2160) — added the parcel-change recovery that is the only escape today.
- [decentraland/unity-explorer#3936](https://github.com/decentraland/unity-explorer/pull/3936) — desktop fixed the same class of latch by making `MainCameraSystem` state-driven on virtual-camera deactivation (`ISceneIsCurrentListener`) instead of transition-completion-driven.
### ✅Expected behavior
Player camera is current within `DEFAULT_TRANSITION_TIME` (0.35s) of the last `MainCamera.deleteFrom`, `raycast_use_cursor_position == false`, drag-to-look works.
A `MainCamera` re-set during the transition-back should not require another full transition to clear cursor mode. "No active scene virtual camera" (`scene_virtual_camera.entity_id == 0`) should be the source of truth every `_process`, regardless of `last_camera_reached`.
### 🖼️Screenshots / Media
_No response_
### ▶️Steps to reproduce
1. Enter the scene alive — engine camera, no `MainCamera`. Touch look works.
2. Die — scene creates a `VirtualCamera` entity (`Transition.Time(0)`) and calls `MainCamera.createOrReplace(engine.CameraEntity, { virtualCameraEntity })`. Controller enters CINEMATIC / cursor mode as expected.
3. Round restarts — within one tick the scene calls `movePlayerTo(...)` and `MainCamera.deleteFrom(engine.CameraEntity)`.
4. One frame later a synced component makes the scene call `MainCamera.createOrReplace` again with the same virtual-camera entity; it deletes it again on the next frame.
5. Try to drag to look — camera never rotates again. `raycast_use_cursor_position` remains `true`.
6. Teleport to another parcel scene — touch look is restored.
The snippet in the description reproduces steps 2–5 without the game logic.
### 👁️Occurrence
100% of the time
Contributor guide
Assessment
This issue has not been assessed yet.