Files
open-wc/docs/modules/local-player-movement.md
T
sindoring 97bb53f6cd rnd(M02): extract character presenters
Work-Package: M02-RND-CHARACTER-PRESENTATION-001

Agent: sindo-main-codex

Tests: presentation/input/movement/terrain/camera regressions; coordinate/streaming/docs/coordination gates; renderer dry-run 7/7

Fidelity: preserves current sandbox model/outfit and Stand/Idle/Run/Walk behavior; build-12340 parity remains unverified
2026-07-14 23:42:12 +04:00

13 KiB
Raw Blame History

Local Player Movement

Metadata

Field Value
Status Implemented
Target/work package M02 / M02-GMP-MOVEMENT-001, camera basis and character-presentation consumer updates
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

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 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
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 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

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

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

sequenceDiagram
    participant Input as PlayerInputSource
    participant Scene as ThirdPersonWowController
    participant Move as LocalPlayerMovementController
    participant Present as CharacterAnimationPresenter 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_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/tests/scenes/player_input_regression.tscn Asset-free integrated player fixture
src/tools/verify_player_input.gd Input-to-movement scene regression
  • 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.