# Third-Person Camera ## Metadata | Field | Value | |---|---| | Status | Implemented | | Target/work package | M02 / M02-RND-CAMERA-001 | | Owners | Camera presentation state and CameraPivot/Camera3D lifecycle | | Last verified | `working tree`, 2026-07-14 | | Profiles/capabilities | Current sandbox orbit/zoom; identity collision policy | ## Purpose Own third-person mouse capture, orbit yaw/pitch, zoom state and Camera3D local transforms on the existing `CameraPivot` node. Expose an explicit replaceable camera-distance collision policy while preserving the current no-collision sandbox baseline. ## Non-goals - Implement an unverified physics raycast or claim build-12340 collision parity. - Own player movement, terrain queries, streaming focus or character visuals. - Implement first-person, follow lag, shoulder camera, cinematics or view modes. - Change current sensitivity, pitch/zoom limits, FOV or far plane. ## Context and boundaries ```mermaid flowchart LR InputMap[Remappable camera actions] --> Player[ThirdPersonWowController dispatch] Player --> Rig[ThirdPersonCameraRig] Mouse[Mouse motion] --> Player Rig --> Character[Character yaw transform] Rig --> Pivot[CameraPivot local transform] Rig --> Policy[CameraCollisionPolicy] Policy --> Distance[Resolved distance] Distance --> Camera[Camera3D local transform] Rig --> FlightBasis[Godot-world pivot basis] FlightBasis --> Movement[LocalPlayerMovementController adapter] ``` Allowed dependencies: - Existing `PlayerInputActions` names and Godot input events. - Explicit character, pivot and Camera3D Node references on the main thread. - Replaceable `CameraCollisionPolicy` selected by composition/tests. - Movement adapter may read the rig's Godot-world basis during sandbox flight. Forbidden dependencies: - ADT/terrain parsing, movement velocity, gameplay state or animation ownership. - Camera rig discovering renderer/network/server services. - Default collision policy performing hidden raycasts or changing baseline distance. - Collision policy mutating rig/camera/character nodes. ## Public API | Symbol | Kind | Purpose | Thread/lifetime | Errors | |---|---|---|---|---| | `ThirdPersonCameraRig` | `Node3D` component | Owns orbit/zoom state on CameraPivot | Main thread; scene-owned | Reports missing Camera3D | | `initialize_for_character(character_body)` | Composition method | Attaches yaw target and captures initial character yaw | Main thread after scene ready | Null leaves yaw target absent | | `handle_camera_input(event)` | Input adapter | Applies capture/release/zoom/orbit actions | Main input thread | Returns whether camera consumed state | | `set_collision_policy(policy)` | Composition method | Replaces distance resolution policy | Main thread | Null rejected; old policy retained | | `refresh_camera_transform()` | Presentation update | Applies pivot pose and resolved camera distance | Main/physics thread | Missing camera leaves state but no Camera3D mutation | | `godot_world_flight_movement_basis()` | Read model | Returns pivot global basis for sandbox flight | Main thread | None | | `CameraCollisionPolicy.resolve_camera_distance(pivot, requested)` | Policy boundary | Resolves camera distance in Godot units | Main thread in current implementation | Rig clamps output to `[0, requested]` | | `NoCameraCollisionPolicy` | Identity policy | Preserves requested distance with no physics query | Any main-thread call | None | Read-only rig state: `yaw_radians`, `pitch_radians`, `requested_distance_units`, `resolved_distance_units` and `is_mouse_captured`. ## Inputs and outputs | Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime | |---|---|---|---|---|---| | Input | Camera action `InputEvent` | Godot/player dispatch | Camera rig | Engine-owned event | One dispatch | | Input | Mouse relative pixels | Godot input | Camera rig | Engine-owned event | One dispatch | | Input | Character `Node3D` | Player composition | Camera rig | Scene-owned reference | Rig scene lifetime | | Input | Collision policy | Composition/test | Camera rig | Rig-held RefCounted | Until replacement | | Output | Character yaw | Camera rig | Player transform | Scene mutation | Orbit event | | Output | Pivot pitch/height | Camera rig | CameraPivot | Scene mutation | Refresh | | Output | Requested/resolved distance | Camera rig/policy | Camera3D | Rig-owned scalar/read model | Until next input/refresh | | Output | Godot-world `Basis` | Camera rig | Flight movement adapter | Value copy | Physics tick | Side effects: - Mutates mouse mode, character yaw, CameraPivot local transform and Camera3D local transform. - Marks Camera3D current and sets its far plane once at ready. - Releases captured mouse on rig removal. - Creates no files, caches, RIDs, workers, network messages or gameplay state. ## Data flow ```mermaid flowchart LR Action[Camera action or mouse delta] --> State[Yaw pitch requested distance capture] State --> Clamp[Pitch/zoom clamps] Clamp --> Pivot[Pivot height and pitch] Clamp --> Request[Requested distance] Request --> Policy[CameraCollisionPolicy] Policy --> Safe[Clamp 0..requested] Safe --> Camera[Camera3D Z distance] State --> Character[Character yaw] ``` ## Lifecycle/state ```mermaid stateDiagram-v2 [*] --> MouseVisible MouseVisible --> MouseCaptured: camera rotate pressed MouseCaptured --> MouseVisible: camera rotate released MouseCaptured --> MouseVisible: release cursor MouseCaptured --> MouseCaptured: mouse motion / orbit and clamp MouseVisible --> MouseVisible: zoom and clamp MouseCaptured --> [*]: rig removed / release mouse MouseVisible --> [*]: rig removed ``` Zoom and collision distance are orthogonal to capture state. The requested zoom persists while a policy may temporarily choose a shorter resolved distance. ## Main sequence ```mermaid sequenceDiagram participant Engine as Godot Input participant Player as ThirdPersonWowController participant Rig as ThirdPersonCameraRig participant Policy as CameraCollisionPolicy participant Camera as Camera3D Engine->>Player: InputEvent Player->>Rig: handle_camera_input(event) Rig->>Rig: update capture/orbit/zoom state Rig->>Policy: resolve_camera_distance(pivot, requested) Policy-->>Rig: resolved distance Rig->>Camera: apply local position/rotation opt captured mouse motion Rig->>Player: mutate character yaw transform end ``` ## Ownership, threading and resources - Parent player scene owns the CameraPivot rig and Camera3D child. - Rig holds non-owning scene references to character and camera. - Rig owns scalar camera state and the RefCounted collision policy. - All input/state/scene mutations occur on the main thread. - Policy output is a scalar; policies must not mutate supplied nodes. - No background tasks, resources, RIDs or caches are created. ## Errors, cancellation and recovery | Failure | Detection | Behavior | Diagnostic | Recovery | |---|---|---|---|---| | Camera3D missing | `_ready` lookup | State continues; transform application skipped | `ThirdPersonCameraRig` error with path | Restore child/path and recreate scene | | Character missing | Initialization | Orbit state updates without character yaw mutation | Composition/test failure | Call initializer with player node | | Null collision policy | Setter guard | Current policy retained | Rig error | Pass valid policy | | Policy returns negative/too large | Rig clamp | Distance clamped to `[0, requested]` | Policy regression tests | Fix/replace policy | | Rig removed while captured | `_exit_tree` | Mouse made visible | Lifecycle regression | Recreate rig normally | There is no asynchronous cancellation or persisted rollback. Recreating the rig restores configured defaults. ## Configuration and capabilities | Setting/capability | Default | Profile | Runtime mutable | Effect | |---|---|---|---|---| | Initial pitch | `-18°` | Current sandbox | State-owned after ready | Initial orbit elevation | | Pitch limits | `-65°..35°` | Current sandbox | Exported | Mouse clamp | | Mouse sensitivity | `0.003` rad/pixel | Current sandbox | Exported | Orbit delta | | Pivot height | `1.7` units | Current sandbox | Exported | Camera eye origin | | Initial distance | `8` units | Current sandbox | Exported before ready | Requested zoom | | Zoom limits/step | `2..18`, step `1` | Current sandbox | Exported | Requested distance clamp | | Far plane | `50000` units | Renderer sandbox | Exported | Camera far plane | | Collision policy | `NoCameraCollisionPolicy` | Current sandbox | Replaceable | Identity distance/no raycast | ## Persistence, cache and migration Camera state and policy are transient. No SavedVariables/settings schema, cache or migration is introduced. Scene paths remain `ThirdPersonPlayer/CameraPivot/Camera3D`, so renderer diagnostics and capture tools require no path migration. ## Diagnostics and observability - Logs: missing camera/null policy emit explicit component errors. - Read models: yaw, pitch, requested/resolved distance and capture state. - Tests: dedicated verifier reports state/policy/scene counts. - Metrics/correlation IDs: not applicable to constant-time local presentation. ## Verification - State regression: initial yaw/pitch/distance/height/far plane, capture/release, mouse orbit, character yaw, pitch clamps and zoom clamps. - Policy regression: fixed-distance test policy changes Camera3D Z; identity policy restores requested distance; baseline policy contains no physics query. - Scene integration: Eastern Kingdoms, Kalimdor and asset-free regression scenes attach the rig to the existing CameraPivot. - Boundary: player no longer owns capture/yaw/pitch/distance or camera transform helper. - Existing input/movement/terrain regressions and renderer dry-run remain required. - Fidelity evidence: current numeric defaults and identity collision behavior only; original-client orbit/collision comparison remains open. - Performance: constant-time scalar/transform work once per physics tick and input event; no default raycast. ## Extension points - Add an opt-in raycast collision policy after reference fixtures define mask, margin, recovery smoothing and first-person limit. - Add follow/shoulder/cinematic rigs as separate components, not branches inside this rig. - Persist user camera settings through a future versioned settings module. - Input contexts may suppress camera actions before dispatch without changing rig state. ## Capability status | Capability | Status | Evidence | Gap/next step | |---|---|---|---| | Independent third-person rig | Implemented | Three scene wirings and player boundary regression | Integrate target checklist after review | | Existing orbit/zoom behavior | Implemented | Numeric state/transform regression | Compare against build 12340 | | Camera-relative sandbox flight basis | Implemented | Basis and movement regressions | Restrict flight to sandbox profile | | Replaceable collision policy | Implemented | Fixed and identity policy tests | Implement verified collision policy | | Active camera collision | Planned | Identity policy intentionally preserves baseline | Capture original-client occluder fixtures first | | First-person/follow policies | Planned | Outside current sandbox baseline | Later completeness work | ## Known gaps and risks - Default collision policy performs no occluder avoidance, matching the previous sandbox. - Exact WoW camera orbit direction, pitch limits, zoom curve, collision margin and recovery smoothing are unverified. - Exported initial values are captured at ready; runtime mutation may require an explicit reconfiguration API later. - Mouse mode remains process-global Godot state and must be coordinated with future UI contexts. ## Source map | Path | Responsibility | |---|---| | `src/scenes/player/third_person_camera_rig.gd` | CameraPivot state, input and transforms | | `src/scenes/player/camera_collision_policy.gd` | Replaceable distance-policy boundary | | `src/scenes/player/no_camera_collision_policy.gd` | Identity baseline policy | | `src/scenes/player/third_person_wow_controller.gd` | Camera event dispatch and movement-basis consumer | | `src/scenes/streaming/*_streaming.tscn` | Runtime rig composition | | `src/tests/scenes/player_input_regression.tscn` | Asset-free rig/player composition | | `src/tools/verify_third_person_camera_rig.gd` | State, policy, scene and boundary regression | ## Related decisions and references - ADR: none; current behavior and scene paths are preserved. - Upstream/reference: `targets/02-player-decomposition.md`, `targets/roadmap/02-rendering-and-graphics.md`, `targets/roadmap/04-gameplay-systems.md`, `docs/ARCHITECTURE.md`.