Skip to content

feat: player movement (authoritative + ValidatePosition)#3

Open
fxckdead wants to merge 14 commits into
mainfrom
feat/player-movement
Open

feat: player movement (authoritative + ValidatePosition)#3
fxckdead wants to merge 14 commits into
mainfrom
feat/player-movement

Conversation

@fxckdead
Copy link
Copy Markdown
Owner

Summary

  • Handle MoveBackwardToLocation (0x01) — set destination, send MoveToLocation
  • Handle ValidatePosition (0x48) — reconcile client vs server position
  • World-tick timer on GameServer advances moving players (100ms cadence)
  • Fix MoveToLocation to use real destination instead of current position

Spec: docs/superpowers/specs/2026-05-22-player-movement-design.md
Plan: docs/superpowers/plans/2026-05-22-player-movement.md

9 commits, all reviewed task-by-task (spec compliance + code quality) before landing.

Test plan

  • Build clean: `cmake --build --preset game-debug`
  • Log in + EnterWorld still works (regression)
  • Click ground — character walks to spot
  • Click own feet — ActionFailed, no movement
  • Click > 9900 units away — ActionFailed, no movement
  • Arrival — server X/Y/Z within 16 units of target, isMoving = false
  • ValidatePosition keeps server X/Y/Z synced during walk

🤖 Generated with Claude Code

fxckdead and others added 14 commits May 22, 2026 22:12
10 task-by-task breakdown covering Player state extensions, new
MoveBackwardToLocation/ValidatePosition request packets, MoveToLocation
fix, opcode wiring on GameClientConnection, and the world-tick timer on
GameServer. Each task ends with build + commit. Final task is manual
smoke verification per CLAUDE.md (no automated tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
character_id_ on GameClientConnection is a slot index (0..6) per the
existing convention — set during handle_request_game_start_packet via
set_character_id(getCharacterObjectId()), where the packet field name
is misleading (it's the slot the client clicked, not a Player object id).
All existing handlers (RequestGameStart, EnterWorld, etc.) consistently
use getCharacterBySlot(player_name_, character_id_).

Task 6/7/8 used getCharacterById(character_id_) which expects a real
Player::getObjectId() value, so slot 0 (a normal first character) hit
the empty-optional branch and the move packet was rejected with
ActionFailed and "no character 0" in the log.

Also drop the character_id_ == 0 guard in advance_player_movement —
slot 0 is valid; the IN_GAME state check already covers "no character
selected".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the hardcoded "safer coordinates for all races" midpoint with
HumanFighter[0] from Mobius's data/stats/chars/baseStats/HumanFighter.xml:
(-71338, 258271, -3104) — Talking Island Village near the warrior
guildmaster. Real spawn point with known walkable terrain.

Mobius picks one of four nearby nodes at random per spawn; we use the
first deterministically until a per-class spawn template lands.

Heading kept at the existing 48969 (~268°).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ValidatePosition handler was setPosition'ing the player to the
client-reported coords on every small-delta VP (~once per second).
That wiped out the world tick's progress between VPs, so the player
could never make actual forward progress under server-authoritative
movement — tick advances 12u, VP arrives, server snaps back to where
the client thinks we are, repeat forever.

Mobius's ValidatePosition.java:86-100 does NOT update server XYZ on
small delta — only triggers conditional Z snaps in narrow sub-cases
that depend on geo (which we don't have). The small-delta intent is
"client and server agree closely enough, leave authoritative pos alone".

Also adds a temporary [Movement] diagnostic in Player::advanceMovement
so we can see the tick firing — to be removed after smoke confirms
the fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The killer bug was a 2-byte misalignment in UserInfo: section 19's
"C6 new h's" loop wrote 13 shorts instead of Mobius's 12 (lines 150-161),
shifting every subsequent field by 2 bytes. The client read garbage for
the movement-speed fields (runSpd/walkSpd) and refused walk animation
while still allowing rotation. Confirmed working in-game after the fix.

Other changes in this commit:

* Trust the client's reported origin in MoveBackwardToLocation. Without
  server geodata the L2 Interlude client snaps the model to its own
  floor and the server's stored Z drifts from the client's. Resyncing
  server position to the client's origin before sending MoveToLocation
  makes FROM match the client's known self position. Pragmatic fix
  until geodata lands.

* Start PC object IDs at 0x10000000 (L2J convention). Real L2 character
  IDs are in this range; using 1 was likely fine here but matches the
  L2J world expectation.

* Restore spawn coords to the original Gludio "safer coordinates"
  (-84318, 244579, -3730). Mobius's HumanFighter spawn at
  (-71338, 258271, -3104) put the model behind a prop in this client.

* Update spawn comment to document the client-side geo snap behavior
  discovered during smoke testing (the visible altitude is determined
  by the client, not by the Z we send).

* Remove the temporary per-tick [Movement] diagnostic log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fxckdead fxckdead force-pushed the feat/player-movement branch from eb685f8 to b18a47b Compare May 23, 2026 07:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant