0ce3cae208
Work-Package: M02-GMP-TERRAIN-QUERY-001 Agent: sindo-main-codex Tests: coordination and documentation gates; diff check Fidelity: documents inherited height/snap regression and remaining collision evidence gap
246 lines
12 KiB
Markdown
246 lines
12 KiB
Markdown
# Local Player Movement
|
||
|
||
## Metadata
|
||
|
||
| Field | Value |
|
||
|---|---|
|
||
| Status | Implemented |
|
||
| Target/work package | M02 / M02-GMP-MOVEMENT-001 |
|
||
| Owners | Gameplay local movement state; scene composition owns the instance |
|
||
| Last verified | `a45d521`, 2026-07-14 |
|
||
| Profiles/capabilities | Current render sandbox; production/debug profile gate pending |
|
||
|
||
## 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
|
||
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 animation 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 |
|
||
|---|---|---|---|---|
|
||
| `LocalPlayerMovementController.new(...)` | Constructor | Sets explicit speeds, acceleration and sandbox sprint multiplier | Created by composition root; retained for player scene lifetime | Caller owns configuration validation |
|
||
| `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 |
|
||
| `toggle_sandbox_flight()` | Command | Toggles flight and clears velocity | Owning physics/input thread | Returns new flight state |
|
||
| `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 | Godot-world `Basis` | Player scene 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/animation 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
|
||
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 / clear velocity
|
||
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 Present as Facing/Animation adapter
|
||
Input-->>Scene: MoveIntent
|
||
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.
|
||
- 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 |
|
||
| 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 |
|
||
|
||
## 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.
|
||
- 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.
|
||
- Sandbox debug capability gating can reject sprint/flight before commands reach this controller.
|
||
|
||
## 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 | Pitched-basis and toggle/reset cases | Restrict to sandbox profile |
|
||
| 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`.
|
||
- Sprint and free flight still lack a typed sandbox capability gate.
|
||
- 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/terrain/terrain_query.gd` | Replaceable ground-height input boundary |
|
||
| `src/scenes/player/third_person_wow_controller.gd` | Composition, basis selection, displacement/terrain/presentation adapters |
|
||
| `src/tools/verify_local_player_movement.gd` | Pure numeric, state-transition and dependency 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`.
|