|
|
|
@@ -0,0 +1,253 @@
|
|
|
|
|
# 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`.
|