All movement in MMO Project is server-authoritative. The client sends a target tile coordinate; the server resolves the full A* path across the contiguousDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/TaylorZaneKirk/MMO-Project/llms.txt
Use this file to discover all available pages before exploring further.
WorldChunkCatalog, advances one step per 600 ms tick, and sends back the authoritative movement_applied message. The client never speculatively applies a position change — it waits for the server result before updating the local character position.
Movement intent
The client submits amovement_intent message carrying target_tile_x and target_tile_y in world-space coordinates. MovementService.QueueMovementAsync receives the intent, reads the character’s current map_presence, normalizes the position through WorldPositionNormalizationService, and clamps the target to the world boundary via WorldChunkCatalog.ClampWorldTile. A clamped target is noted internally so the final result can be reported as partial instead of reached.
If a movement is already in progress for that character, the incoming intent replaces the planned tail of the path while preserving the already-committed next step, preventing position discontinuities.
Resolution
MovementService uses cardinal A* pathfinding across the full world tile grid. The algorithm first attempts two axis-priority straight-line passes (vertical-first, then horizontal-first) before falling back to a full A* open-set search. All path computation works in world-tile space; chunk and local-tile conversions happen only when committing the resolved step.
Each call to AdvanceActiveMovementsAsync dequeues one step from every character’s active path queue and commits it. The tick loop runs at a fixed 600 ms interval driven by ZoneSimulationWorker.
movement_status: blocked.
Movement statuses
| Status | Meaning |
|---|---|
started | Path queued; the character has not yet moved |
stepping | Intermediate step along a multi-tile path |
reached | Character arrived at the exact requested target |
partial | Path ended at the closest reachable tile because the exact target was outside world bounds |
blocked | No valid cardinal path exists to the target |
Persistence
Every resolved movement step is written to the database before themovement_applied message is sent. MovementService calls ICharacterWorldStatePersistencePolicy.PersistMovementStepAsync after updating map_presence, passing the resolved map_id, local_tile_x, and local_tile_y. CharacterStateCommitService delegates this write to CharacterWorldStateSaveService. There is no deferred save — the character’s committed position in the characters table is always the last step that the server acknowledged.
When a step crosses a chunk boundary, the crossedMapBoundary flag is set in the call to PersistMovementStepAsync, which can be used for future zone-transition hooks.
movement_applied payload
Themovement_applied message sent back to the client includes all position data needed for rendering and delta detection:
| Field | Description |
|---|---|
tile_x / tile_y | Resolved local tile in the current chunk |
previous_tile_x / previous_tile_y | Previous local tile coordinates before this step |
world_tile_x / world_tile_y | Absolute world-space coordinates of the new position |
facing | Computed direction: north, south, east, or west |
movement_status | One of started, stepping, reached, partial, or blocked |
map_revision | Current map revision at the time of the step |
Map revision
Everymovement_applied message carries a map_revision allocated by RuntimeRevisionService at the moment the step is published. Delta clients compare this revision against their last known value to detect gaps in the stream. A detected gap triggers a world_resync_request, which the server answers with a full world_snapshot.
Background workers
ZoneSimulationWorker is a hosted BackgroundService that fires a PeriodicTimer every 600 ms and calls ZoneSimulationService.TickAsync. Each tick advances all active character movements and all NPC state machines, then publishes the results. NPC movement changes are broadcast as npc_state_update messages to every connected client on the affected map.
CharacterWorldStateSaveWorker handles any deferred persistence needs that accumulate between ticks, such as batched dirty-state flushes triggered by CharacterStateCommitService.FlushPendingStatesAsync.