Skip to main content

Documentation 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.

All movement in MMO Project is server-authoritative. The client sends a target tile coordinate; the server resolves the full A* path across the contiguous 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 a movement_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.
// MovementService tick interval
private static readonly TimeSpan TickInterval = TimeSpan.FromMilliseconds(600);
If no path can be found at all the movement is cleared immediately and the result carries movement_status: blocked.

Movement statuses

StatusMeaning
startedPath queued; the character has not yet moved
steppingIntermediate step along a multi-tile path
reachedCharacter arrived at the exact requested target
partialPath ended at the closest reachable tile because the exact target was outside world bounds
blockedNo valid cardinal path exists to the target

Persistence

Every resolved movement step is written to the database before the movement_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

The movement_applied message sent back to the client includes all position data needed for rendering and delta detection:
FieldDescription
tile_x / tile_yResolved local tile in the current chunk
previous_tile_x / previous_tile_yPrevious local tile coordinates before this step
world_tile_x / world_tile_yAbsolute world-space coordinates of the new position
facingComputed direction: north, south, east, or west
movement_statusOne of started, stepping, reached, partial, or blocked
map_revisionCurrent map revision at the time of the step
// MovementResult carries all position fields the transport serializes
public sealed record MovementResult(
    string CharacterId,
    string MapId,
    string PreviousMapId,
    int TileX,
    int TileY,
    int RequestedTileX,
    int RequestedTileY,
    int ResolvedTargetTileX,
    int ResolvedTargetTileY,
    int PreviousTileX,
    int PreviousTileY,
    int WorldTileX,
    int WorldTileY,
    int PreviousWorldTileX,
    int PreviousWorldTileY,
    string MovementStatus,
    string Facing);

Map revision

Every movement_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.
Enable MovementDebugEnabled: true in appsettings.json to log per-step movement resolution. Turn it off in production.

Build docs developers (and LLMs) love