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.

The MMO Project server owns the full session lifecycle for every connected client. Login creates a row in the sessions table and returns a session_id. If the connection drops unexpectedly, disconnected_at is stamped on the row and the character’s live map_presence is preserved for a short grace window, allowing the client to reconnect transparently. An explicit logout immediately revokes the session and removes the presence row. Any reconnect attempt after explicit logout is rejected without restoring game state.

Session states

StateTrigger
CreatedLoginService creates the sessions row after successful credential verification
ActiveSessionHandshakeCoordinator completes character load and world entry
DisconnectedWebSocket closes unexpectedly; disconnected_at is stamped on the session
ReconnectedReconnectService restores the session within the grace window
Expiredexpires_at passes without a successful reconnect
RevokedLogoutService sets revoked_at; no further reconnects are accepted

Login flow

LoginService handles the initial credential exchange. On every login request it first validates that the client’s ProtocolVersion matches the configured PrototypeOptions.ProtocolVersion. Mismatched versions are rejected immediately with login_status: unsupported_version before any database work begins. Once the version is confirmed, the account is looked up by name. Inactive accounts return account_inactive. Password verification runs through ASP.NET Identity’s PasswordVerificationResult interface — if the result is SuccessRehashNeeded, the hash is transparently upgraded in the database before continuing. A successful login creates a new sessions row with an expires_at derived from SessionIdleTimeoutMinutes and returns the session_id and account_id to the transport layer.
// LoginResult carries only what the transport needs for the handshake
public sealed record LoginResult(
    string LoginStatus,
    Guid? SessionId,
    Guid? AccountId);
After LoginService returns ok, SessionHandshakeCoordinator drives the account-character load, calls EnterWorldService, and then calls WorldSnapshotService to deliver the initial game state.
1

Client sends login_request

Payload includes account_name, password, and protocol_version.
2

LoginService verifies credentials

Checks protocol version, account existence, account active status, and password hash. Creates the sessions row on success.
3

SessionHandshakeCoordinator runs

Loads the account’s character, calls EnterWorldService to set map presence, then calls WorldSnapshotService to build and send the initial world_snapshot.

Reconnect

ReconnectService enforces a 10-second grace window. When a WebSocket closes unexpectedly the session’s disconnected_at timestamp is recorded but the map_presence row is left intact. A reconnect request within that window passes the following checks in order:
  1. The session_id must match a known session (session_invalid otherwise).
  2. revoked_at must be null — explicitly logged-out sessions are rejected immediately.
  3. expires_at must still be in the future (reauthentication_required otherwise).
  4. disconnected_at must be set and within 10 seconds of UtcNow (reauthentication_required otherwise).
  5. A map_presence row must exist for the character (session_invalid otherwise).
When all checks pass, ReconnectService calls SessionRepository.RestoreSessionAsync and returns reconnect_status: restored along with the character’s map_id, tile_x, and tile_y read from the preserved presence row and a hard-coded facing: south.
// Reconnect result shape
public sealed record ReconnectResult(
    string ReconnectStatus,
    string? SessionId,
    string? CharacterId,
    string? MapId,
    int? TileX,
    int? TileY,
    string? Facing);
The reconnect grace window is a hard-coded 10-second constant in ReconnectService. SessionIdleTimeoutMinutes controls only how long an idle session’s expires_at is extended — not the reconnect window itself.

Logout

LogoutService performs an explicit, immediate revocation. It loads the active session and throws if the session is missing or already revoked — double-logout is a programming error, not a silent no-op. If a map_presence row exists for the character, LogoutService calls ICharacterWorldStatePersistencePolicy.PersistFinalStateAsync with CharacterStatePersistenceReason.LoggedOut before removing the row, ensuring the last known position is committed to the characters table. After persistence, SessionRepository.RevokeSessionAsync stamps revoked_at and MapPresenceRepository.DeletePresenceAsync removes the live row. Any subsequent reconnect attempt against a revoked session returns reconnect_status: session_invalid.

Character state commit

CharacterStateCommitService is the single seam for all character-state persistence. It implements ICharacterWorldStatePersistencePolicy and delegates to CharacterWorldStateSaveService for every write — whether that write originates from a movement step, an explicit logout, a disconnect, or a server shutdown.
public sealed class CharacterStateCommitService : ICharacterWorldStatePersistencePolicy
{
    public Task PersistMovementStepAsync(
        CharacterWorldPosition position,
        bool crossedMapBoundary,
        CancellationToken cancellationToken = default) { ... }

    public Task PersistFinalStateAsync(
        CharacterWorldPosition position,
        CharacterStatePersistenceReason reason,
        CancellationToken cancellationToken = default) { ... }

    public Task FlushPendingStatesAsync(
        CancellationToken cancellationToken = default) { ... }
}
Because all mutations flow through this one interface, adding future stat or buff persistence means implementing the change in CharacterStateCommitService rather than hunting through every service that can alter world state. CharacterStatePersistenceReason gives each caller a clear label (MovementNormalization, LoggedOut, Disconnected, ServerShutdown, Faulted) that can be used for auditing or write-path differentiation.

Persistence tables

TableWhat it stores
sessionsLogin timestamp, disconnected_at, expires_at, revoked_at; one row per authentication attempt
map_presenceLive map_id, tile_x, tile_y for each connected character; used for session visibility and reconnect restoration
characters.map_id / tile_x / tile_yDurable committed position; written immediately by CharacterStateCommitService on every resolved movement step and on logout/disconnect

Build docs developers (and LLMs) love