Skip to content

Character Login Flow

This document describes the full sequence from world-select to the player being in the world.


Full Login Sequence

Game Client              World Server                  Databases / Redis
    │                        │                               │
    │  TCP connect           │                               │
    │───────────────────────>│                               │
    │                        │ Validate world key            │
    │                        │──────────────────────────────>│ GET world:key
    │                        │<──────────────────────────────│ accountId
    │                        │ DEL world:key                 │
    │                        │──────────────────────────────>│
    │                        │                               │
    │  CCharacterListPacket  │                               │
    │───────────────────────>│                               │
    │                        │ CharacterRepository.GetByAccountId
    │                        │──────────────────────────────>│
    │                        │<──────────────────────────────│ List<Character>
    │  SCharacterListPacket  │                               │
    │<───────────────────────│                               │
    │                        │                               │
    │  CCharacterSelectPacket│                               │
    │───────────────────────>│                               │
    │                        │ CharacterRepository.GetById   │
    │                        │──────────────────────────────>│
    │                        │<──────────────────────────────│ Character
    │                        │                               │
    │                        │ [OnCharacterReceived]         │
    │                        │  Build CharacterEntity        │
    │                        │  Assign InstanceId            │
    │                        │  world.SpawnInInstance(conn)  │
    │                        │                               │
    │  SCharacterSelectedPacket                              │
    │<───────────────────────│                               │
    │                        │                               │
    │                        │ CharacterRepository.UpdateAsync (online=true)
    │                        │──────────────────────────────>│
    │                        │                               │
    │                        │ InventoryRepository.GetByCharacterId
    │                        │──────────────────────────────>│
    │                        │<──────────────────────────────│ List<CharacterInventory>
    │                        │ [OnInventoryReceived]         │
    │                        │  Load into entity containers  │
    │                        │  (inventory packet not yet sent to client)
    │                        │                               │
    │                        │ SpellRepository.GetCharacterSpells
    │                        │──────────────────────────────>│
    │                        │<──────────────────────────────│ List<CharacterSpell>
    │                        │ [OnSpellsReceived]            │
    │  SSpellListPacket      │  Resolve SpellMetadata        │
    │<───────────────────────│                               │
    │                        │                               │
    │  [In game — tick loop] │                               │

SCharacterSelectedPacket

Sent immediately after the character entity is built and spawned. Contains:

Field Source
CharacterId character.Id
Name character.Name
Level character.Level
Class (ushort)character.Class
X, Y, Z character.X/Y/Z
Orientation character.Rotation
Running character.Running
Experience character.Experience
RequiredExperience entity.RequiredExperience
MapId character.Map
InstanceId See Instance ID section

Inventory On Login

OnInventoryReceived loads items into entity[InventoryType.*] containers. The inventory packet is not yet sent to the client — the client starts with an empty display until this is implemented.

Planned SInventoryPacket

public class SInventoryPacketItem
{
    public byte Container { get; set; }   // InventoryType enum value
    public byte Slot { get; set; }        // Slot index within container
    public uint ItemId { get; set; }      // Item template ID
    public uint Quantity { get; set; }    // Stack size
    public ushort Durability { get; set; }
}

After all .Load(...) calls, equipment and bag items will be sent to the client. Bank items are deferred until the player interacts with a banker NPC.


Instance ID

All characters entering the default open world share one well-known instance GUID derived from the WorldId:

// Deterministic GUID from WorldId
private static Guid GetMainWorldInstanceId(WorldId worldId)
    => new Guid(worldId.ToString("N").PadLeft(32, '0'));

Set in OnCharacterReceived using IInstanceRegistry.GetOrCreateTownInstance — see instanced-maps.md for the full instance routing design.

Instanced Content (future)

When instanced zones are introduced, IInstanceRegistry.GetOrCreateNormalInstance handles private per-player instances with a 15-minute re-entry window.


Movement Validation

CharacterMovementHandler computes an interpolated server position and compares it to the client-reported position. A warning is logged if the distance difference exceeds MaxDistanceDiffCheck (1.0f). The client position is always accepted.

Planned Authoritative Validation

Client sends CPlayerMovementPacket
  ├── Compute interpolatedPosition (existing)
  ├── Raycast from current position to clientSentPosition via IChunkNavigator
  │   ├── Navmesh allows path → accept client position
  │   └── Navmesh blocks path (collision)
  │         ├── Log anti-cheat event
  │         ├── connection.Character.Position = last valid server position
  │         └── Send SPositionCorrectionPacket (corrected position back to client)
  └── If differenceDistances >= MaxDistanceDiffCheck (speed hack)
        ├── Log + send correction
        └── Increment per-connection rejection counter
              └── N consecutive rejections → flag / disconnect

SPositionCorrectionPacket

Field Type Description
X float Server-authoritative X position
Y float Server-authoritative Y position
Z float Server-authoritative Z position
Timestamp long Server tick time for client reconciliation

Test Coverage

Scenario
Two characters same world → same InstanceId
Character with 5 equipment items → SInventoryPacket with 5 items
Empty inventory → packet sent with 0 items
Bank items not in login packet
Valid navmesh movement → client position accepted
Movement through wall → correction packet sent
N consecutive rejections → connection flagged