Quick Design Spec: Player Movement
Type: New Small System Scope: Base locomotion, the aim/facing model, movement collision, and remote-player presentation. Does NOT own: dash (combat verb #2, incl. i-frames and cooldown), swing-locked movement rules (combat), camera (its own quick-spec), or input bindings beyond the two schemes named binding in technical preferences. Date: 2026-08-27 Estimated Implementation: ~2–3 days (prototype code exists; this spec formalizes it)
Overview
Movement is the verb everything else stands on, and it is deliberately boring: one flat speed, instant response, no resource. The prototype confirmed the model — owner-authoritative, client-predicted locomotion that starts the same frame as input — and tuned the number (6 u/s). Friction lives in the world, never in the hands (Pillar 4): no stamina, no encumbrance, no movement penalties from any system, ever. All players move identically at all times (combat Rule 5's flat stats — a new friend is exactly as fast as a veteran).
Core Rules
- **Locomotion: flat 8-directional analog movement at
moveSpeed(6 u/s, prototype-tuned).** Full speed the frame input arrives; stop the frame it ends.accelTimeexists as a knob but defaults to 0 (instant) — any nonzero value must survive the "scrappy hands" test at playtest before shipping. - Authority: owner-authoritative, client-predicted (the confirmed netcode model, prototype REPORT + Forbidden Patterns). Your own movement is never waited on; the host never corrects an owner's position at MVP (intent-validation bounds → netcode ADR).
- Aim is decoupled from movement. M+KB: character faces the mouse cursor always; movement direction is independent (strafe-by-default). Gamepad: left stick moves, right stick aims (twin-stick); **aim persists at last direction when the stick is released** — no hover-only interactions, every action reachable on gamepad (binding, technical preferences). The aim vector is the single input combat's arc test and BB's ghost placement both consume.
- Movement collision blocks, never damages. Blockers: terrain (water, cliffs — authored per land/home), placed structures (BB AC-27: a wall's entire function), and world bounds. **No collision with players or enemies** (prototype precedent: bodies overlap; separation steering keeps enemies apart — player-vs-player blocking is grief-adjacent friction with zero payoff in a co-op game). Water is a hard edge: no swimming at MVP; a docked land is entered by walking across the dock seam, and a drift-in land is unreachable by construction (tide Edge Case 12).
- Nothing modifies movement, ever (MVP). (Carve-out, ADR-0015 2026-08-28: combat's Downed state is a distinct locomotion mode —
downedCrawlSpeed— selected byPlayerState.Downed, not a modifier applied tomoveSpeed; the schema-negative below holds literally, and the regression check reads "no system applies a speed modifier to normal locomotion".) No slows, no roots, no knockback-on-move — combat's knockback displaces during combat resolution (its Rule 11 contract) but no system applies a *speed modifier*. The gear hooks areDamageModifier/DamageReduction/HarvestModifieronly — there is noMoveSpeedModifierhook, and adding one is a design change against Pillar 4's floor, not a tune. - Remote players: interpolated, never extrapolated. Remote transforms render smoothly ~1 interpolation buffer behind ("not teleporty," prototype-confirmed at 70–140 ms). No prediction of remote players (Forbidden Patterns). Teleport-snap only on the authority events that already own it (tide deposit, death respawn, and Base Building's occupied-cell correction — BB Edge Case 21, an owner-rail discrete reposition when a wall lands on the player's cell, registered here 2026-08-27 so this list stays the closed enumeration; their rules, not this spec's. RATIFIED 2026-09-11 by ADR-0009 §5 — the asymmetry stands: the owner writes the corrected cell as a plain state update (never
Teleport), observers slide over the interpolation buffer, the camera does not cut;SnapReasonstays{TideDeposit, DeathRespawn}. (Original proposal text kept below for the record.) Ratification pending (BB third re-review 2026-08-27): BB Edge Case 21 proposes an observer asymmetry for its event — the mover snaps on the owner rail, but observers see a normal one-buffer slide rather than the interpolation cut tide deposit and death respawn give observers. Movement owns the list and must accept or reject that asymmetry here; until it does, BB's wording is a proposal. **Owner and trigger (BB fourth re-review 2026-08-27, CS-15):** the netcode architecture ADR ratifies or rejects it at the first NGO integration story touchingNetworkTransform; mechanically the asymmetry requires BB's correction write to be a plain interpolated state update, notTeleport(), which is what cuts observer interpolation for the other two events). - The base speed is a load-bearing constant.
moveSpeedfeeds LDU'straversalProfilemeasurements (its party-clock check) and combat's spacing bands (aggro/leash radii are tuned against 6 u/s). It joins the registry; retuning it re-runs LDU's gate and a combat-feel pass — the knob table says so.
Tuning Knobs
| Knob | Default | Range | Category | Rationale |
|---|---|---|---|---|
moveSpeed | 6 u/s | 5–8 | feel | Prototype-tuned; registry constant — retune re-runs LDU's party-clock gate + combat spacing feel |
accelTime | 0 ms | 0–80 | feel | 0 = instant (scrappy hands); any nonzero value needs playtest proof it doesn't read as sluggish |
downedCrawlSpeed | 2 u/s | 1.5–3 | feel | Downed-only locomotion mode (combat Rule 9 "crawl-speed"); a mode, never a multiplier of moveSpeed — added by ADR-0015 2026-08-28; retune with bleedOutTimer (drag-toward-rescuer distance) |
remoteInterpBuffer | 1 tick | 1–3 | feel | Remote smoothness vs positional freshness; prototype shipped smooth at ~70 ms. = NetworkTransform.InterpolationBufferTickOffset (global, ADR-0009 §6); effective buffer is RTT-adaptive TickLatency + this; players win any player/enemy arbitration |
snapAcceptRadius | 1.0 u | 0.5–2 | netcode | ADR-0009 §4: a host-commanded snap's override ends when the owner's transform lands within this of the snap point |
snapGraceTicks | 25 | 5–100 | netcode | ADR-0009 §4: the override's hard expiry (unresponsive client) |
skinWidth | 0.01 u | 0.005–0.05 | feel | ADR-0009 §3: the sweep stops this short of a blocker |
Values live in assets/data/movement.json, not hardcoded.
Acceptance Criteria
- [ ] [UNIT] Movement input → full
moveSpeedthe same simulation frame (accelTime 0); input release → stop next frame. Pure function of input state, testable headless. - [ ] [UNIT] Schema-negative: no
MoveSpeedModifierhook, stamina field, or encumbrance term exists anywhere (Rule 5; Pillar 4's floor as a schema fact). - [ ] [UNIT] The aim vector resolves identically from mouse position and right-stick direction (one code path consuming a normalized vector — gamepad parity by construction).
- [ ] [INTEGRATION] Own movement starts within 0–1 frames of input on both host and client at simulated 70–140 ms RTT (the prototype's pass criterion, re-verified in the real codebase).
- [ ] [INTEGRATION] Remote player at ~70 ms renders smooth ("not teleporty") with no extrapolation overshoot on direction reversal.
- [ ] [INTEGRATION] A wall placed by BB blocks movement the instant its host-confirmed placement lands (BB AC-27 from movement's side).
- [ ] [EXPERIENTIAL] The new-friend test, literally: someone who has never played moves, aims, and swings within their first minute with no instruction (Pillar 5's onboarding floor).
- [ ] Regression: no system anywhere applies a movement-speed change (verified by the Rule 5 schema-negative holding across the codebase).
Systems Index
Already tracked as MVP system #5 (Gameplay, quick-spec, "Prototype-tuned"). On approval: status → Designed (quick-spec). moveSpeed registers as a cross-system constant (LDU + combat reference it).
Forward refs owned elsewhere: intent-validation bounds + transform replication cadence → netcode ADR; input rebinding UI → UI/HUD quick-spec + /ux-design; camera follow → Camera quick-spec.