Files
open-wc/docs/modules/player-input.md
sindoring 8776a6b362 gmp(M02): add 3.3.5 input profile
Work-Package: M02-GMP-INPUT-PROFILE-335-001
Agent: sindo-main-codex
Tests: player input/movement/capability/terrain/camera/presentation, coordinate/streaming, docs/coordination and renderer dry-run passed
Fidelity: build-12340 binding hashes and selected defaults pinned; TrinityCore turn rate and WoWee mouse routing are compatibility references with explicit limits
2026-07-15 00:17:34 +04:00

14 KiB

Player Input

Metadata

Field Value
Status Partial
Target/work package M02 / M02-GMP-INPUT-001, M02-GMP-SANDBOX-CAPABILITIES-001, M02-GMP-INPUT-PROFILE-335-001
Owners Gameplay input boundary; sindo-main-codex for current package
Last verified M02-GMP-INPUT-PROFILE-335-001 worktree, 2026-07-15
Profiles/capabilities Distinct RenderSandbox and Blizzlike335 action layouts; debug execution remains RenderSandbox-only

Purpose

Convert remappable Godot Input Map actions into an immutable, scene-free MoveIntent that can be consumed by the independently testable local movement controller. Select either the preserved renderer-sandbox layout or the verified build-12340 movement bindings at the scene composition boundary.

Non-goals

  • Define authoritative server movement, prediction or reconciliation.
  • Own character transform, terrain queries, camera state or animation state.
  • Implement jump, autorun, left-button select/move or persisted binding settings.
  • Remove process-wide debug actions; Blizzlike335 sampling ignores them and the movement capability boundary also rejects directly constructed debug requests.

Context and boundaries

flowchart LR
    Devices[Keyboard and mouse] --> InputMap[Godot Input Map]
    InputMap --> Source[PlayerInputSource]
    Profile[RenderSandbox or Blizzlike335] --> Source
    Source --> Intent[MoveIntent]
    Intent --> Movement[LocalPlayerMovementController]
    Profile[PlayerMovementCapabilities] --> Movement
    Movement --> Controller[ThirdPersonWowController adapter]
    Controller --> Camera[ThirdPersonCameraRig]

Allowed dependencies:

  • PlayerInputSource may read Godot's process-wide Input adapter.
  • The scene controller may consume MoveIntent and camera-specific action names.
  • MoveIntent may contain scalar axes and request flags only.

Forbidden dependencies:

  • MoveIntent must not read Input, inherit Node/Resource, or contain world coordinates.
  • The input source must not mutate player transforms, camera nodes or animation state.
  • Renderer, terrain parsers and network codecs must not read Input Map actions.

Public API

Symbol Kind Purpose Thread/lifetime Errors
MoveIntent Immutable value Carries normalized forward, strafe, vertical and turn axes plus the sandbox sprint request Created and consumed on the main physics tick; caller retains reference Constructor clamps axes; no I/O failure
MoveIntent.has_translation() Query Reports whether any translation axis is non-zero Any thread while value remains immutable None
PlayerInputActions Constants Provides stable action names shared by configuration and adapters Process lifetime Missing project actions are detected by verification
PlayerInputSource.new(profile_id) Constructor Selects sandbox or compatibility action layout once Scene lifetime Unknown IDs fail closed to Blizzlike335
PlayerInputSource.sample_move_intent() Adapter method Samples the selected action layout for one physics tick Main thread; returned intent is independent of adapter lifetime Missing actions resolve to zero strength and are a configuration defect
PlayerInputSource.compose_move_intent(...) Pure factory Clamps strengths, cancels opposites and normalizes diagonal planar input Any thread with scalar inputs None
PlayerInputSource.compose_blizzlike_335_move_intent(...) Pure factory Composes Q/E strafe, A/D turn and right-mouse A/D strafe semantics Any thread with scalar inputs None

Inputs and outputs

Direction Contract/data Producer Consumer Ownership Thread/lifetime
Input Movement action strengths Godot InputMap/Input PlayerInputSource Engine-owned snapshot Sampled on main physics tick
Input Mouse action events Godot _unhandled_input dispatch ThirdPersonCameraRig through player dispatch Engine-owned event Current input dispatch only
Output MoveIntent PlayerInputSource LocalPlayerMovementController through scene composition Immutable caller-held value One physics tick
Output Debug sprint request/vertical axes PlayerInputSource Capability-gated movement controller Immutable intent fields One physics tick
Output Signed turn axis PlayerInputSource LocalPlayerMovementController Immutable scalar One physics tick
Output Camera action names PlayerInputActions ThirdPersonCameraRig Static constants Process lifetime

Side effects:

  • PlayerInputSource has no side effects beyond reading current engine input.
  • ThirdPersonCameraRig owns mouse mode, orbit state and camera transforms; player remains only the event-dispatch composition boundary.
  • No filesystem, cache, network, database or renderer-resource mutation occurs.

Data flow

flowchart LR
    Project[project.godot action defaults] --> Map[InputMap]
    Remap[User or editor remapping] --> Map
    Map -->|movement strengths| Source[PlayerInputSource]
    Profile[Selected input profile] --> Source
    Map -->|camera events| Camera[ThirdPersonCameraRig]
    Source -->|clamp cancel normalize| Intent[MoveIntent]
    Intent --> Consumer[LocalPlayerMovementController]
    Profile[PlayerMovementCapabilities] --> Consumer
    Consumer --> Adapter[ThirdPersonWowController]

Lifecycle/state

The adapter is stateless. The scene composition root creates one PlayerInputSource and LocalPlayerMovementController in _ready; each physics tick produces a new immutable intent. Flight enabled/disabled state is owned by the movement controller and is not hidden in the input adapter. RenderSandbox retains debug requests in its intent; Blizzlike335 does not sample them, and the immutable movement capability also rejects directly constructed requests.

Main sequence

sequenceDiagram
    participant Engine as Godot Input
    participant Source as PlayerInputSource
    participant Intent as MoveIntent
    participant Move as LocalPlayerMovementController
    participant Player as ThirdPersonWowController adapter
    Engine->>Source: movement action strengths
    Source->>Intent: compose normalized axes and requests
    Source-->>Player: sample_move_intent()
    Player->>Move: calculate yaw delta, then advance(intent, selected basis, delta)
    Move-->>Player: displacement and velocity state

Ownership, threading and resources

  • The controller owns the input-source reference for its scene lifetime.
  • Each MoveIntent is immutable and retained only by its consumer.
  • Sampling and controller mutation occur on the main physics thread.
  • The module creates no Nodes, Resources, RIDs, workers or background jobs.
  • Input Map definitions are project configuration; runtime remapping remains engine-owned and does not mutate the domain value.

Errors, cancellation and recovery

Failure Detection Behavior Diagnostic Recovery
Required action missing Headless contract verifier Godot returns zero input for that action PLAYER_INPUT failure naming action Restore action or user mapping
Conflicting opposite actions Pure composition Strengths cancel deterministically Covered by contract test Release/remap one action
Strength outside range Pure composition Value clamps to [0, 1] before axis calculation Covered by contract test Input adapter may be corrected independently
Controller disabled/unloaded Scene lifecycle Sampling stops with physics processing Existing scene lifecycle Re-enable or recreate scene
Debug request in Blizzlike335 Input profile and movement capability checks Input adapter omits it; directly constructed request has no effect Profile/capability regressions Use RenderSandbox only when debug movement is intended

There is no asynchronous operation to cancel and no persisted state to roll back.

Configuration and capabilities

Setting/capability Default Profile Runtime mutable Effect
Forward/backward Physical W/S and Up/Down Both Yes, through Input Map Produces signed forward axis
Strafe left/right Physical A/D RenderSandbox Yes Preserves renderer baseline
Strafe left/right Physical Q/E Blizzlike335 Yes Matches selected build-12340 defaults
Keyboard turn Physical A/D and Left/Right Blizzlike335 Yes Produces signed turn axis
Camera-held A/D Physical A/D while right mouse is held Blizzlike335 Yes Produces strafe and suppresses keyboard turn
Camera rotate Right mouse button Current render sandbox Yes Captures/releases mouse for orbit
Camera zoom Mouse wheel Current render sandbox Yes Adjusts current camera distance
Debug sprint Shift Executable only in RenderSandbox Yes Requests multiplier; Blizzlike335 ignores it
Debug flight up/down/toggle E/Q/Space Executable only in RenderSandbox Yes Requests free flight; Blizzlike335 remains grounded

Persistence, cache and migration

The action names are additive project configuration. No saved binding schema, cache or migration exists yet. Renaming an action is a contract change and must provide a settings migration once user settings are persisted.

Diagnostics and observability

  • Logs: dedicated verifier emits PLAYER_INPUT PASS or one failure per invariant.
  • Metrics: none; sampling cost is one small value allocation per physics tick.
  • Debug views: none.
  • Correlation IDs: not applicable to local synchronous input.

Verification

  • Unit/contract tests: src/tools/verify_player_input.gd validates all actions, both profile defaults, axis cancellation/clamping/normalization, turn routing, safe profile fallback, fixture provenance and debug request flags.
  • Integration/E2E: src/tests/scenes/player_input_regression.tscn is loaded by the verifier, which drives sandbox forward/camera/flight plus compatibility keyboard turn and right-mouse strafe through real scene controllers.
  • Profile integration: verify_player_movement_capabilities.gd drives sprint and flight actions through real RenderSandbox and Blizzlike335 scene instances.
  • Fidelity evidence: sanitized fixture player_input_profile_335.json pins the private build-12340 DefaultBindings.wtf/Bindings.xml hashes and selected movement/camera commands. TrinityCore 3.3.5 revision 2853a621d6af91a803787a2b8a509f4ce3e0300d pins the server-contract player turn rate. WoWee revision 626243e937fb93965fa583a6507ed5a1aa7dda4b supports right-mouse A/D strafe routing. Both are compatibility references, not direct original-client timing/conditional-behavior proof.
  • Performance budgets: no I/O, jobs or scene lookup per sample; one immutable intent allocation per physics tick.
  • Manual diagnostics: run the Eastern Kingdoms scene and remap one movement action in Project Settings before checking movement.

Extension points

  • A gamepad/accessibility adapter may call compose_move_intent without changing movement consumers.
  • A later input-context owner may suppress gameplay actions while preserving the same intent contract.
  • A future server-aware movement predictor may replace the current local movement consumer without changing PlayerInputSource.

Capability status

Capability Status Evidence Gap/next step
Remappable current sandbox controls Implemented verify_player_input.gd action/default checks Add persisted user binding settings later
PlayerInputSource → MoveIntent seam Implemented Pure composition and controller boundary checks Local movement consumer is now extracted
Selected 3.3.5a movement defaults Implemented Pinned private-source hashes, sanitized selected bindings and profile regressions Jump, autorun and left-button behavior remain planned
Compatibility-profile exclusion of sprint/flight Implemented Typed capability and two real-scene regressions Application shell selection remains future work
Jump/fall/swim/fly gameplay semantics Planned M02 target and gameplay roadmap Implement after terrain/movement state extraction

Known gaps and risks

  • Only selected movement/camera binding facts are implemented; the fixture explicitly records unimplemented defaults.
  • Input Map still exposes debug bindings process-wide; the movement consumer now rejects their effects outside RenderSandbox.
  • Camera state is extracted; active collision remains an explicit planned policy.
  • There is no persisted user keybinding format or UI yet.

Source map

Path Responsibility
project.godot Additive default Input Map bindings
src/domain/input/move_intent.gd Immutable locomotion request contract
src/gameplay/input/player_input_actions.gd Stable project action names
src/gameplay/input/player_input_source.gd Engine Input Map adapter and pure composition
src/gameplay/movement/local_player_movement_controller.gd Scene-free movement consumer
src/gameplay/movement/player_movement_capabilities.gd Defense-in-depth execution gate for debug requests
src/scenes/player/third_person_camera_rig.gd Camera-action consumer and orbit/zoom state
src/scenes/player/third_person_wow_controller.gd Composition and input-event dispatcher
src/tests/scenes/player_input_regression.tscn Asset-free controller movement/camera regression fixture
src/tests/fixtures/player_input_profile_335.json Sanitized build-12340 binding and server turn-rate provenance
src/tools/verify_player_input.gd Headless contract/integration regression
src/tools/verify_player_movement_capabilities.gd Sandbox versus compatibility-profile integration regression
  • ADR: none; this implements the existing architecture and M02 seam.
  • Upstream/reference: targets/02-player-decomposition.md, targets/roadmap/04-gameplay-systems.md, docs/ARCHITECTURE.md, and the pinned sources recorded in player_input_profile_335.json.