feat: player run/walk gaits (run by default, hold Shift to walk) #177

Closed
opened 2026-07-24 01:21:19 +02:00 by panda · 3 comments
Collaborator

Add a run/walk gait to player movement. Today there is a single speed (MoveCooldownTicks = 2, ~200ms/step); this splits it into a faster run (default) and a slower walk (hold a modifier), server-authoritative.

Design reviewed via critical-design-review (Invariants Check below). ModernUO/UO reference: the client sends the movement direction + a "running" bit; the server enforces the rate floor (fastwalk-prevention) and, in UO, gates run on stamina — we defer stamina (see follow-up).

Scope

  • Wire (Shared/Protocol) — MoveRequest(Direction, bool Running) and PlayerMoved(…, bool Running); bump ProtocolVersion.Current in the same change.
  • Server — two tunables on GameOptions: WalkCooldownTicks (default 3, ~300ms — a touch slower than today) and RunCooldownTicks (default 1, ~100ms). MovementSystem picks the base cooldown from the intent's Running flag, then scales by terrain move-cost as today (marsh slows both). The run cooldown is the floor — a modified client claiming Running always cannot move faster than run. Broadcast carries Running so observers animate correctly.
  • Client — run is the default gait; hold Shift to walk. GameInputController sends Running and matches its send cadence to the active gait. The entity scales its glide duration + walk-anim fps by gait (shorter glide + faster legs when running); the self from its own intent, other players from PlayerMoved.Running.
  • Art — extract the real UO human/run_* animation (body 0x190, action group 2 = Run) from the classic .mul client and bake it into the pack (UO-derived placeholder, like the existing walk/stand/attack — to be redone with original art later). Fallback: if the run frames are absent, reuse walk_* at higher fps (no placeholder shape).
  • Docs — docs/architecture.md movement note; document the two new GameOptions.

Invariants Check (agreed)

  • Scope ✓ only the gait; stamina explicitly deferred. Server-authoritative ✓ client sends Running as intent; server picks the cooldown and enforces the run floor; position validated as today. Protocol versioned ✓ MoveRequest+PlayerMoved gain a field → bump in the same change. String catalog N/A (no new text this MVP). Single-threaded sim ✓ gait selection in MovementSystem, sim thread only. World.cs HARD GATE ✓ logic in MovementSystem, World only delegates. Screen HARD GATE ✓ input→intent in GameInputController, glide/anim on the entity, gait from the dispatcher. Client engine-independence ✓ gait→(glide,fps,sendInterval) mapping is pure (Client.Core), unit-tested. Gameplay/Networking separation ✓ rule in Gameplay/, wire in Protocol/. Extend by type not switch ✓ gait is a movement parameter (bool/small enum), a cooldown lookup — not a polymorphic type. Server-paced actions ✓ run = shorter per-step cooldown, not compressible below the floor. Persistence ✓ gait transient, nothing new serialized. Typed options ✓ WalkCooldownTicks/RunCooldownTicks on GameOptions, default in one place. Broadcasts absolute / AoI ✓ PlayerMoved keeps absolute X,Y (+Running), reconciled in AoI, no new event type. Multi-platform ✓ pure .NET; Shift via existing input. Assets ✓ real UO run art (placeholder family), missing → reuse walk faster, never a placeholder shape. Asset naming ✓ human/run_* descriptive. ModernUO ✓ follow "client requests gait, server enforces floor"; diverge by deferring stamina. Docs & DoD ✓ this issue + architecture note.
  • GM auth / Identity / Persistence(Auth) / Process separation — N/A (no admin action, no account/schema/process change).

Verification plan

  • Unit (World/MovementSystem) — a run step applies in fewer ticks than a walk step; a fast client can't beat the run floor; marsh move-cost scales both gaits; overweight still blocks; teleport stays exempt.
  • Wire — ProtocolJson round-trip for the new Running fields on MoveRequest/PlayerMoved.
  • Client (Client.Core unit) — gait→(glideSeconds, animFps, sendInterval) mapping.
  • Harness — speed isn't visible in a still: prove it with a timed run (walk vs run over the same path → step count / elapsed) plus a screenshot of the run pose. (A short GIF if a visual is wanted.)

Definition of Done (delta)

  • Holding the walk modifier moves measurably slower than the default run over the same path (timed).
  • A modified/fast client cannot exceed the run speed (server rejects the early move) — unit-tested.
  • Marsh move-cost still applies to both gaits (a run through marsh is slower than a run on grass).
  • Other players are seen running/walking at the correct speed (broadcast Running drives their glide + animation).
  • ProtocolVersion.Current bumped in the same change; wire round-trips.
Add a **run/walk gait** to player movement. Today there is a single speed (`MoveCooldownTicks = 2`, ~200ms/step); this splits it into a faster **run** (default) and a slower **walk** (hold a modifier), server-authoritative. Design reviewed via `critical-design-review` (Invariants Check below). ModernUO/UO reference: the client sends the movement direction + a "running" bit; the server enforces the rate floor (fastwalk-prevention) and, in UO, gates run on stamina — we **defer stamina** (see follow-up). ## Scope - **Wire (`Shared/Protocol`)** — `MoveRequest(Direction, bool Running)` and `PlayerMoved(…, bool Running)`; **bump `ProtocolVersion.Current`** in the same change. - **Server** — two tunables on `GameOptions`: `WalkCooldownTicks` (default **3**, ~300ms — a touch slower than today) and `RunCooldownTicks` (default **1**, ~100ms). `MovementSystem` picks the base cooldown from the intent's `Running` flag, then scales by terrain move-cost as today (marsh slows both). The run cooldown is the **floor** — a modified client claiming `Running` always cannot move faster than run. Broadcast carries `Running` so observers animate correctly. - **Client** — run is the **default** gait; **hold Shift to walk**. `GameInputController` sends `Running` and matches its send cadence to the active gait. The entity scales its **glide duration + walk-anim fps** by gait (shorter glide + faster legs when running); the self from its own intent, other players from `PlayerMoved.Running`. - **Art** — extract the real UO **`human/run_*`** animation (body 0x190, action group 2 = Run) from the classic `.mul` client and bake it into the pack (UO-derived **placeholder**, like the existing walk/stand/attack — to be redone with original art later). Fallback: if the run frames are absent, reuse `walk_*` at higher fps (no placeholder shape). - **Docs** — `docs/architecture.md` movement note; document the two new `GameOptions`. ## Invariants Check (agreed) - **Scope** ✓ only the gait; stamina explicitly deferred. **Server-authoritative** ✓ client sends `Running` as intent; server picks the cooldown and enforces the run floor; position validated as today. **Protocol versioned** ✓ MoveRequest+PlayerMoved gain a field → bump in the same change. **String catalog** N/A (no new text this MVP). **Single-threaded sim** ✓ gait selection in `MovementSystem`, sim thread only. **`World.cs` HARD GATE** ✓ logic in `MovementSystem`, World only delegates. **`Screen` HARD GATE** ✓ input→intent in `GameInputController`, glide/anim on the entity, gait from the dispatcher. **Client engine-independence** ✓ gait→(glide,fps,sendInterval) mapping is pure (Client.Core), unit-tested. **Gameplay/Networking separation** ✓ rule in `Gameplay/`, wire in `Protocol/`. **Extend by type not switch** ✓ gait is a movement parameter (`bool`/small enum), a cooldown lookup — not a polymorphic type. **Server-paced actions** ✓ run = shorter per-step cooldown, not compressible below the floor. **Persistence** ✓ gait transient, nothing new serialized. **Typed options** ✓ `WalkCooldownTicks`/`RunCooldownTicks` on `GameOptions`, default in one place. **Broadcasts absolute / AoI** ✓ `PlayerMoved` keeps absolute X,Y (+`Running`), reconciled in AoI, no new event type. **Multi-platform** ✓ pure .NET; Shift via existing input. **Assets** ✓ real UO run art (placeholder family), missing → reuse walk faster, never a placeholder shape. **Asset naming** ✓ `human/run_*` descriptive. **ModernUO** ✓ follow "client requests gait, server enforces floor"; diverge by deferring stamina. **Docs & DoD** ✓ this issue + architecture note. - GM auth / Identity / Persistence(Auth) / Process separation — **N/A** (no admin action, no account/schema/process change). ## Verification plan - **Unit (World/MovementSystem)** — a run step applies in fewer ticks than a walk step; a fast client can't beat the run floor; marsh move-cost scales both gaits; overweight still blocks; teleport stays exempt. - **Wire** — `ProtocolJson` round-trip for the new `Running` fields on `MoveRequest`/`PlayerMoved`. - **Client (Client.Core unit)** — gait→(glideSeconds, animFps, sendInterval) mapping. - **Harness** — speed isn't visible in a still: prove it with a **timed run** (walk vs run over the same path → step count / elapsed) plus a screenshot of the run pose. (A short GIF if a visual is wanted.) ## Definition of Done (delta) - Holding the walk modifier moves **measurably slower** than the default run over the same path (timed). - A modified/fast client **cannot** exceed the run speed (server rejects the early move) — unit-tested. - Marsh move-cost still applies to **both** gaits (a run through marsh is slower than a run on grass). - Other players are seen running/walking at the correct speed (broadcast `Running` drives their glide + animation). - `ProtocolVersion.Current` bumped in the same change; wire round-trips.
Author
Collaborator

Stamina (deferred from this MVP) tracked in #178.

Stamina (deferred from this MVP) tracked in #178.
Author
Collaborator

Implemented in #180 — timed in-game: walk 3.35s vs run 1.21s over the same 10-tile path (2.77x), real UO human/run_* extracted, all DoD items covered.

Implemented in #180 — timed in-game: walk 3.35s vs run 1.21s over the same 10-tile path (2.77x), real UO `human/run_*` extracted, all DoD items covered.
Author
Collaborator

Marco aiutami invece di farti le unghie a tema pokemon 💅

Risolta: run/walk gaits su main (#180).

Marco aiutami invece di farti le unghie a tema pokemon 💅 Risolta: run/walk gaits su main (#180).
panda closed this issue 2026-08-02 00:47:14 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo#177
No description provided.