Work-Package: M02-RND-CAMERA-001
12 KiB
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
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
PlayerInputActionsnames and Godot input events. - Explicit character, pivot and Camera3D Node references on the main thread.
- Replaceable
CameraCollisionPolicyselected 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
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
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
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.