rnd(M02): extract third-person camera rig
Work-Package: M02-RND-CAMERA-001
This commit is contained in:
@@ -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`.
|
||||
Reference in New Issue
Block a user