gmp(M02): extract local player movement

Work-Package: M02-GMP-MOVEMENT-001
Agent: sindo-main-codex
Tests: local movement and player input regressions; asset-free scene; renderer dry-run; coordinate, streaming, documentation and coordination gates
Fidelity: preserves current sandbox speed, acceleration, sprint and camera-relative flight behavior; build-12340 parity remains unverified
This commit is contained in:
2026-07-14 22:59:33 +04:00
parent 14dead194b
commit 435e1c95d2
9 changed files with 601 additions and 56 deletions
+242
View File
@@ -0,0 +1,242 @@
# 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 | `working tree`, 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[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 | Outside module | Scene skips existing ground snap | Existing terrain diagnostics | TerrainQuery extraction will own policy |
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/collision movement policy | Planned | Outside module by design | Extract `TerrainQuery` next |
| 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 until `TerrainQuery` extraction.
- 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/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`.