7d5d13f702
Work-Package: M02-GMP-SANDBOX-CAPABILITIES-001 Agent: sindo-main-codex
278 lines
16 KiB
Markdown
278 lines
16 KiB
Markdown
# Local Player Movement
|
||
|
||
## Metadata
|
||
|
||
| Field | Value |
|
||
|---|---|
|
||
| Status | Implemented |
|
||
| Target/work package | M02 / M02-GMP-MOVEMENT-001, M02-GMP-SANDBOX-CAPABILITIES-001 and presentation consumers |
|
||
| Owners | Gameplay local movement state; scene composition owns the instance |
|
||
| Last verified | `742c415`, 2026-07-14 |
|
||
| Profiles/capabilities | `RenderSandbox` debug movement; `Blizzlike335` rejects debug sprint/flight |
|
||
|
||
## Purpose
|
||
|
||
Own current local velocity, acceleration and sandbox flight state independently
|
||
of the player scene. Convert an immutable `MoveIntent` plus an explicit
|
||
Godot-world movement basis into deterministic per-tick displacement without
|
||
querying terrain, cameras, input devices or visual assets.
|
||
|
||
## Non-goals
|
||
|
||
- Apply world transforms or own a `CharacterBody3D`.
|
||
- Query terrain height, collision, water or slopes.
|
||
- Implement jump, fall, swim, server prediction or reconciliation.
|
||
- Select the player basis versus camera-pivot basis; the scene adapter owns that decision.
|
||
- Claim build-12340 movement fidelity from sandbox regression alone.
|
||
|
||
## Context and boundaries
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Input[MoveIntent] --> Movement[LocalPlayerMovementController]
|
||
Basis[Explicit local movement Basis] --> Movement
|
||
Delta[Physics delta seconds] --> Movement
|
||
Profile[Scene profile ID] --> Capabilities[PlayerMovementCapabilities]
|
||
Capabilities --> Movement
|
||
Movement --> Displacement[Godot-world displacement]
|
||
Movement --> Velocity[Read-only velocity state]
|
||
Scene[ThirdPersonWowController] --> Basis
|
||
Displacement --> Scene
|
||
Scene --> Transform[Player world transform]
|
||
Scene --> Terrain[TerrainQuery and ground snap adapter]
|
||
Velocity --> Presentation[Visual facing and CharacterAnimationPresenter adapter]
|
||
```
|
||
|
||
Allowed dependencies:
|
||
|
||
- `MoveIntent` from the gameplay input boundary.
|
||
- Godot value types `Basis` and `Vector3` for renderer world-space direction math.
|
||
- Main-thread scene adapter for basis selection and displacement application.
|
||
|
||
Forbidden dependencies:
|
||
|
||
- `Node`, `Resource`, `Input`, `Camera3D`, `ADTLoader` or scene lookup.
|
||
- World-coordinate conversion or manual ADT/tile formulas.
|
||
- Character assets, animation players, materials, packets or server state.
|
||
|
||
## Public API
|
||
|
||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||
|---|---|---|---|---|
|
||
| `PlayerMovementCapabilities.render_sandbox()` | Factory | Creates the only debug sprint/free-flight capability set | Immutable caller value | None |
|
||
| `PlayerMovementCapabilities.blizzlike_335()` | Factory | Creates the compatibility-safe set with debug movement disabled | Immutable caller value | Name is intent, not a parity claim |
|
||
| `PlayerMovementCapabilities.for_profile_id(...)` | Factory | Maps composition configuration once; unknown IDs fail closed | Immutable caller value | Unknown maps to `Blizzlike335` |
|
||
| `LocalPlayerMovementController.new(...)` | Constructor | Sets explicit speeds, acceleration, sprint multiplier and movement capabilities | Created by composition root; retained for player scene lifetime | Null capability defaults to `Blizzlike335` |
|
||
| `godot_world_velocity_units_per_second` | Read-only state | Current Godot-world velocity after the last update | Stable until next mutation on owning thread | None |
|
||
| `is_flight_enabled` | Read-only state | Current sandbox free-flight mode | Stable until toggle | None |
|
||
| `movement_profile_id` | Read-only state | Effective capability profile ID | Controller lifetime | None |
|
||
| `toggle_sandbox_flight()` | Command | Toggles flight only when explicitly permitted and clears velocity | Owning physics/input thread | Rejected request returns `false` with state unchanged |
|
||
| `advance(move_intent, godot_world_movement_basis, delta_seconds)` | Stateful update | Accelerates toward requested velocity and returns displacement for the tick | Owning physics thread; one call per tick | Negative delta is treated as zero |
|
||
|
||
Constructor units:
|
||
|
||
| Parameter | Unit/meaning |
|
||
|---|---|
|
||
| `run_speed_units_per_second` | Forward Godot units per second |
|
||
| `backward_speed_units_per_second` | Backward Godot units per second |
|
||
| `strafe_speed_units_per_second` | Lateral Godot units per second |
|
||
| `flight_vertical_speed_units_per_second` | Sandbox vertical Godot units per second |
|
||
| `acceleration_units_per_second_squared` | Velocity change per second |
|
||
| `sandbox_sprint_multiplier` | Dimensionless debug multiplier |
|
||
|
||
## Inputs and outputs
|
||
|
||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||
|---|---|---|---|---|---|
|
||
| Input | `MoveIntent` | `PlayerInputSource` | Movement controller | Immutable caller-held value | One physics tick |
|
||
| Input | `PlayerMovementCapabilities` | Application/player scene composition | Movement controller | Immutable caller-held value | Controller lifetime |
|
||
| Input | Godot-world `Basis` | Player or `ThirdPersonCameraRig` adapter | Movement controller | Value copy | One physics tick |
|
||
| Input | Delta seconds | Godot physics loop | Movement controller | Scalar value | One physics tick |
|
||
| Input | Flight toggle command | Remappable sandbox action via scene adapter | Movement controller | Synchronous command | Input event dispatch |
|
||
| Output | Displacement `Vector3` | Movement controller | Player scene adapter | Value copy | Applied in same tick |
|
||
| Output | Velocity `Vector3` | Movement controller | Facing and `CharacterAnimationPresenter` scene adapter | Read-only value copy | Until next update |
|
||
| Output | Flight state | Movement controller | Basis/terrain scene adapter | Read-only boolean | Until next toggle |
|
||
|
||
Side effects:
|
||
|
||
- Mutates only controller-owned velocity and flight state.
|
||
- Does not mutate scene tree, transforms, resources, filesystem, network or cache.
|
||
- The scene adapter remains responsible for transform mutation and ground snap.
|
||
|
||
## Data flow
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Intent[MoveIntent axes] --> Target[Calculate target velocity]
|
||
Basis[Player or camera-pivot Basis] --> Directions[Forward/right directions]
|
||
Directions --> Target
|
||
Config[Speeds and sprint multiplier] --> Target
|
||
Profile[RenderSandbox or Blizzlike335] --> Capability[Immutable debug capabilities]
|
||
Capability -->|allow or reject sprint| Target
|
||
Capability -->|allow or reject flight toggle| Current
|
||
Previous[Owned velocity] --> Integrate[move_toward by acceleration × delta]
|
||
Target --> Integrate
|
||
Integrate --> Current[Updated velocity]
|
||
Current --> Step[velocity × delta]
|
||
Step --> Displacement[Scene-applied displacement]
|
||
```
|
||
|
||
## Lifecycle/state
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> GroundedSandbox
|
||
GroundedSandbox --> FlyingSandbox: toggle [debug flight allowed] / clear velocity
|
||
GroundedSandbox --> GroundedSandbox: toggle [debug flight rejected]
|
||
FlyingSandbox --> GroundedSandbox: toggle / clear velocity
|
||
GroundedSandbox --> GroundedSandbox: advance
|
||
FlyingSandbox --> FlyingSandbox: advance
|
||
```
|
||
|
||
The names describe the existing sandbox mode only. They are not authoritative
|
||
gameplay movement states and do not imply terrain contact or server permission.
|
||
|
||
## Main sequence
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Input as PlayerInputSource
|
||
participant Scene as ThirdPersonWowController
|
||
participant Move as LocalPlayerMovementController
|
||
participant Cap as PlayerMovementCapabilities
|
||
participant Present as CharacterAnimationPresenter adapter
|
||
Input-->>Scene: MoveIntent
|
||
Scene->>Cap: map configured profile once
|
||
Scene->>Move: construct with immutable capabilities
|
||
Scene->>Move: advance(intent, selected basis, delta)
|
||
Move-->>Scene: displacement
|
||
Scene->>Scene: apply global position and ground snap
|
||
Scene->>Move: godot_world_velocity_units_per_second
|
||
Scene->>Present: horizontal motion
|
||
```
|
||
|
||
## Ownership, threading and resources
|
||
|
||
- The player scene creates and exclusively owns one movement controller.
|
||
- The composition root creates one immutable capability value; the controller
|
||
retains it and exposes only its effective profile ID.
|
||
- Velocity and flight state have the same lifetime as that controller.
|
||
- All mutations currently occur on the main input/physics thread.
|
||
- Read-only value returns are copies; consumers cannot mutate owned state.
|
||
- No Nodes, Resources, RIDs, files, jobs or worker threads are created.
|
||
- The supplied `Basis` is a Godot-world direction basis, not a typed world-position contract or coordinate conversion.
|
||
|
||
## Errors, cancellation and recovery
|
||
|
||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||
|---|---|---|---|---|
|
||
| Negative physics delta | `advance` guard | Returns zero displacement; velocity unchanged | Unit regression | Resume with valid non-negative delta |
|
||
| Missing scene basis owner | Scene composition defect | Controller cannot be called correctly | Scene/parser error or boundary test | Restore explicit scene dependency |
|
||
| Flight toggle during motion | Command path | Velocity clears before next step | State-transition regression | Continue from zero velocity |
|
||
| Flight/sprint request in `Blizzlike335` | Capability check | Request has no effect | Profile regression | Select explicit `RenderSandbox` only for tooling/debug scenes |
|
||
| Unknown/null profile capability | Factory/constructor safe default | Uses `Blizzlike335`; debug movement disabled | Capability regression | Correct composition profile ID |
|
||
| Missing terrain data | `TerrainGroundSample.is_available == false` | Scene skips existing ground snap | Stable terrain failure code | Replace/recover query backend |
|
||
|
||
There is no asynchronous cancellation, retry or rollback. Recreating the
|
||
controller resets velocity and flight state deterministically.
|
||
|
||
## Configuration and capabilities
|
||
|
||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||
|---|---|---|---|---|
|
||
| Forward speed | `7.0` units/s | Current sandbox | Fixed for controller lifetime | Target forward velocity |
|
||
| Backward speed | `4.5` units/s | Current sandbox | Fixed for controller lifetime | Target backward velocity |
|
||
| Strafe speed | `4.5` units/s | Current sandbox | Fixed for controller lifetime | Target lateral velocity |
|
||
| Acceleration | `28.0` units/s² | Current sandbox | Fixed for controller lifetime | Acceleration and deceleration |
|
||
| Flight vertical speed | `7.0` units/s | Sandbox debug | Fixed for controller lifetime | Vertical free-flight target |
|
||
| Sprint multiplier | `6.0` | Sandbox debug | Fixed for controller lifetime | Multiplies planar and vertical targets |
|
||
| Camera-relative basis | Selected only while flight is enabled | Sandbox debug | Per tick | Preserves existing pitched flight direction |
|
||
| Debug sprint permission | Enabled | `RenderSandbox` only | Immutable | Allows sprint request multiplier |
|
||
| Debug free-flight permission | Enabled | `RenderSandbox` only | Immutable | Allows toggle and vertical movement |
|
||
| Compatibility-safe debug permissions | Disabled | `Blizzlike335` | Immutable | Ignores sprint/toggle requests |
|
||
|
||
## Persistence, cache and migration
|
||
|
||
Movement state is transient and not serialized. No schema, cache, migration or
|
||
saved settings change is introduced. Future reconnect/replay state requires a
|
||
separate versioned movement snapshot contract.
|
||
|
||
## Diagnostics and observability
|
||
|
||
- Logs: verifier emits `LOCAL_PLAYER_MOVEMENT PASS` or named invariant failures.
|
||
- Metrics: none yet; update is constant-time vector math.
|
||
- Debug views: current character facing and animation remain observable scene outputs.
|
||
- Correlation IDs: not applicable to synchronous local sandbox state.
|
||
|
||
## Verification
|
||
|
||
- Unit/contract: `src/tools/verify_local_player_movement.gd` covers forward,
|
||
backward, strafe, rotated basis, acceleration, deceleration, sprint, flight,
|
||
pitched camera basis, state reset and invalid delta.
|
||
- Capability: `verify_player_movement_capabilities.gd` covers three profile-ID
|
||
cases, safe defaults, sprint/flight allow/reject behavior and two real scene compositions.
|
||
- Boundary: verifier rejects Node/Input/Camera/ADT dependencies and movement
|
||
integration/state remaining in the player scene.
|
||
- Scene integration: `verify_player_input.gd` drives the asset-free player
|
||
regression scene through forward motion and immediate flight toggle.
|
||
- Fidelity evidence: default values and formulas are regression-locked against
|
||
the pre-extraction sandbox. No original-client comparison is claimed.
|
||
- Performance budget: constant-time vector math, no per-tick I/O or scene lookup;
|
||
`MoveIntent` allocation remains owned by the input package.
|
||
|
||
## Extension points
|
||
|
||
- Terrain/collision policy can consume displacement without changing input or velocity ownership.
|
||
- A future server-aware predictor may replace this controller behind the scene composition boundary.
|
||
- A typed movement snapshot may expose deterministic replay state when M08/M09 require it.
|
||
- A future application profile can map to this narrow capability value without
|
||
coupling the movement controller to the application shell.
|
||
|
||
## Capability status
|
||
|
||
| Capability | Status | Evidence | Gap/next step |
|
||
|---|---|---|---|
|
||
| Scene-free local velocity state | Implemented | Pure controller and source boundary regression | Integrate target checklist after review |
|
||
| Existing planar acceleration/speeds | Implemented | Exact numeric unit cases and real scene regression | Compare with build 12340 before fidelity claim |
|
||
| Existing camera-relative free flight | Implemented in `RenderSandbox` | Pitched-basis, profile and toggle/reset cases | Original-client flight semantics remain out of scope |
|
||
| Typed sprint/free-flight exclusion | Implemented | Pure and real-scene profile regressions | Application shell must select profile explicitly later |
|
||
| Terrain height query | Implemented | Typed `TerrainQuery` and injected player regression | Ground-snap policy remains scene-owned |
|
||
| Terrain collision movement policy | Planned | Height-only query does not model collision | Add slopes/holes/collision later |
|
||
| Jump/fall/swim | Planned | M02/M09 roadmap | Requires terrain/liquid and server contracts |
|
||
| Prediction/reconciliation | Planned | M08/M09 roadmap | Requires movement snapshot/network contract |
|
||
|
||
## Known gaps and risks
|
||
|
||
- Numeric defaults preserve the existing sandbox but are not original-client evidence.
|
||
- Configuration is captured at scene `_ready`; runtime mutation of exported speed
|
||
properties does not reconfigure an existing controller.
|
||
- Ground snap remains in the player scene, but its ADT data access is now behind
|
||
a replaceable `TerrainQuery`.
|
||
- Runtime renderer scenes intentionally default to `RenderSandbox`; a future
|
||
game-client composition must explicitly select `Blizzlike335`.
|
||
- `Blizzlike335` currently means only debug movement exclusion and must not be
|
||
interpreted as evidence that remaining movement semantics are 1:1.
|
||
- The module uses Godot math value types; a future engine-independent gameplay
|
||
predictor may require scalar/domain movement contracts.
|
||
|
||
## Source map
|
||
|
||
| Path | Responsibility |
|
||
|---|---|
|
||
| `src/gameplay/movement/local_player_movement_controller.gd` | Owned velocity/flight state and deterministic integration |
|
||
| `src/gameplay/movement/player_movement_capabilities.gd` | Immutable profile-to-debug-capability contract and safe defaults |
|
||
| `src/gameplay/terrain/terrain_query.gd` | Replaceable ground-height input boundary |
|
||
| `src/scenes/player/third_person_camera_rig.gd` | Camera-relative sandbox flight basis producer |
|
||
| `src/scenes/character/character_animation_presenter.gd` | Locomotion presentation consumer behind the scene adapter |
|
||
| `src/scenes/player/third_person_wow_controller.gd` | Composition, basis selection, displacement/terrain and presentation-state adapters |
|
||
| `src/tools/verify_local_player_movement.gd` | Pure numeric, state-transition and dependency regression |
|
||
| `src/tools/verify_player_movement_capabilities.gd` | Profile factory, allow/reject and real-scene regression |
|
||
| `src/tests/scenes/player_input_regression.tscn` | Asset-free integrated player fixture |
|
||
| `src/tools/verify_player_input.gd` | Input-to-movement scene regression |
|
||
|
||
## Related decisions and references
|
||
|
||
- ADR: none; this follows the existing M02 decomposition boundary.
|
||
- Upstream/reference: `targets/02-player-decomposition.md`,
|
||
`targets/roadmap/04-gameplay-systems.md`, `docs/ARCHITECTURE.md`.
|