gmp(M02): add player input intent seam
Work-Package: M02-GMP-INPUT-001 Agent: sindo-main-codex Tests: verify_player_input; renderer baseline dry-run; coordinate, streaming, documentation and coordination gates Fidelity: preserves current sandbox bindings and movement/camera behavior; exact build-12340 semantics remain unverified
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
| Foundation/data | Planned/Partial | [`../../targets/roadmap/01-foundation-and-data.md`](../../targets/roadmap/01-foundation-and-data.md) |
|
||||
| Coordinate mapping | Implemented | [`coordinate-mapping.md`](coordinate-mapping.md) |
|
||||
| Domain identities | Implemented contract | [`domain-identities.md`](domain-identities.md) |
|
||||
| Player input | Partial | [`player-input.md`](player-input.md) |
|
||||
| Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) |
|
||||
| Network/session | Planned | [`../../targets/roadmap/03-network-and-session.md`](../../targets/roadmap/03-network-and-session.md) |
|
||||
| Gameplay | Prototype/Planned | [`../../targets/roadmap/04-gameplay-systems.md`](../../targets/roadmap/04-gameplay-systems.md) |
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
# Player Input
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Partial |
|
||||
| Target/work package | M02 / M02-GMP-INPUT-001 |
|
||||
| Owners | Gameplay input boundary; `sindo-main-codex` for current package |
|
||||
| Last verified | `working tree`, 2026-07-14 |
|
||||
| Profiles/capabilities | Current render-sandbox defaults; `Blizzlike335` binding semantics not yet verified |
|
||||
|
||||
## Purpose
|
||||
|
||||
Convert remappable Godot Input Map actions into an immutable, scene-free
|
||||
`MoveIntent` that can be consumed by the current player controller and a later
|
||||
independently testable local movement controller.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Define authoritative server movement, prediction or reconciliation.
|
||||
- Own character transform, terrain queries, camera state or animation state.
|
||||
- Claim that the current W/S/A/D sandbox mapping exactly matches build 12340.
|
||||
- Make sprint or free flight available to the production compatibility profile;
|
||||
their profile gate remains a later M02 package.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Devices[Keyboard and mouse] --> InputMap[Godot Input Map]
|
||||
InputMap --> Source[PlayerInputSource]
|
||||
Source --> Intent[MoveIntent]
|
||||
Intent --> Controller[ThirdPersonWowController]
|
||||
Controller --> Movement[Current sandbox movement]
|
||||
Controller --> Camera[Current camera adapter]
|
||||
```
|
||||
|
||||
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 and vertical 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.sample_move_intent()` | Adapter method | Samples configured actions 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 |
|
||||
|
||||
## 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 | Current scene controller | Engine-owned event | Current input dispatch only |
|
||||
| Output | `MoveIntent` | `PlayerInputSource` | Current controller; later local movement controller | Immutable caller-held value | One physics tick |
|
||||
| Output | Camera action names | `PlayerInputActions` | Current scene controller | Static constants | Process lifetime |
|
||||
|
||||
Side effects:
|
||||
|
||||
- `PlayerInputSource` has no side effects beyond reading current engine input.
|
||||
- The existing controller still mutates player/camera transforms and mouse mode;
|
||||
those responsibilities are intentionally not moved by this package.
|
||||
- No filesystem, cache, network, database or renderer-resource mutation occurs.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Project[project.godot action defaults] --> Map[InputMap]
|
||||
Remap[User or editor remapping] --> Map
|
||||
Map -->|movement strengths| Source[PlayerInputSource]
|
||||
Source -->|clamp cancel normalize| Intent[MoveIntent]
|
||||
Intent --> Consumer[ThirdPersonWowController]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
The adapter is stateless. The scene composition root creates one
|
||||
`PlayerInputSource` in `_ready`; each physics tick produces a new immutable
|
||||
intent. Flight enabled/disabled state remains owned by the current sandbox
|
||||
controller and is not hidden in the input adapter.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Engine as Godot Input
|
||||
participant Source as PlayerInputSource
|
||||
participant Intent as MoveIntent
|
||||
participant Player as ThirdPersonWowController
|
||||
Engine->>Source: movement action strengths
|
||||
Source->>Intent: compose normalized axes and requests
|
||||
Source-->>Player: sample_move_intent()
|
||||
Player->>Player: update current sandbox movement 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 |
|
||||
|
||||
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 | Current render sandbox | Yes, through Input Map | Produces signed forward axis |
|
||||
| Strafe left/right | Physical A/D | Current render sandbox | Yes | Produces signed right axis |
|
||||
| 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 | Sandbox debug only by intent; profile gate pending | Yes | Requests existing speed multiplier |
|
||||
| Debug flight up/down/toggle | E/Q/Space | Sandbox debug only by intent; profile gate pending | Yes | Requests existing free-flight controls |
|
||||
|
||||
## 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,
|
||||
default bindings, axis cancellation/clamping/normalization and debug request flags.
|
||||
- Integration/E2E: `src/tests/scenes/player_input_regression.tscn` is loaded by
|
||||
the verifier, which drives forward, camera-zoom and immediate sandbox-flight
|
||||
actions through the real scene controller and also rejects direct
|
||||
physical-key polling.
|
||||
- Fidelity evidence: current sandbox defaults retain W/S/A/D, E/Q, Shift, Space,
|
||||
right mouse and wheel bindings. This is observable-regression coverage only.
|
||||
- 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.
|
||||
- The local movement controller may replace the current scene 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 | Extract local movement controller |
|
||||
| Exact 3.3.5a default semantics | Unknown | No original-client binding fixture in this package | Capture build-12340 defaults and mouse-turn/strafe policy |
|
||||
| Production profile exclusion of sprint/flight | Partial | Debug actions are named explicitly | Add typed profile/capability gate in M02 |
|
||||
| Jump/fall/swim/fly gameplay semantics | Planned | M02 target and gameplay roadmap | Implement after terrain/movement state extraction |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- The defaults preserve the pre-M02 sandbox behavior, not verified original-client semantics.
|
||||
- Sprint and free flight are still executable by the sandbox controller until a
|
||||
typed build-profile gate is introduced.
|
||||
- Camera input is action-mapped but camera state/collision remains in the monolithic controller.
|
||||
- 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/scenes/player/third_person_wow_controller.gd` | Current consumer and camera-event adapter |
|
||||
| `src/tests/scenes/player_input_regression.tscn` | Asset-free controller movement/camera regression fixture |
|
||||
| `src/tools/verify_player_input.gd` | Headless contract/integration regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- 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`.
|
||||
Reference in New Issue
Block a user