Architecture: how Bezgelor's server fits together

This is a tour of the Bezgelor server for anyone who wants to understand it or work on it. It follows a player from launching the WildStar client to fighting in a zone, and names the real modules and files along the way, so you can open the code next to the text. You don't need to know Elixir: where a word might be new, it links to the glossary, and Why Elixir & OTP explains the ideas the server is built on.

The client is the original WildStar client, unchanged. Everything on these pages is the server side: the programs that answer the client, run the game world and keep its records.

Bezgelor at a glance The WildStar client connects to three servers in turn, all running inside one Bezgelor server (one BEAM node): 1, the login server (STS, bezgelor_auth, port 6600); 2, the realm server (bezgelor_realm, port 23115); 3, the world server (bezgelor_world, port 24000). The realm and world servers share the protocol code in bezgelor_protocol, and everything uses the shared code in bezgelor_core and bezgelor_crypto. In the world server each player has a connection process and each active zone has a zone instance process; they exchange messages. Both read the game data held in memory (bezgelor_data, ETS tables) and save to the database (bezgelor_db, PostgreSQL). Every part emits events to the event system (bezgelor_events), which stores them in the database; the website and admin pages (bezgelor_portal, port 4000, and the status API bezgelor_api, port 4002) read them and call into the world server directly. WildStar client the original game, unchanged One running server (one BEAM node) 1 · Login (STS) bezgelor_auth · port 6600 2 · Realm server bezgelor_realm · port 23115 3 · World server bezgelor_world · port 24000 Protocol bezgelor_protocol frames, packets, handlers Shared code bezgelor_core (game rules) bezgelor_crypto (ciphers) Connection one process per player Game data bezgelor_data ETS tables in memory Event system bezgelor_events what happened, when Zone instance one per active zone Database bezgelor_db PostgreSQL Website, admin bezgelor_portal bezgelor_api used by realm and world talks to 1, 2, 3 in turn Every part emits events; the website reads them and calls into the world server directly (pink dashed).

The parts

Bezgelor is one Elixir project split into several apps, each a folder under apps/. They are built and started together.

App What it does Start reading at
bezgelor_auth The login server (STS): checks the password and hands out a game token apps/bezgelor_auth/lib/bezgelor_auth/sts/handler.ex
bezgelor_realm The realm server: turns the game token into a session key and points the client at the world server apps/bezgelor_realm/lib/bezgelor_realm/application.ex
bezgelor_world The world server: characters, zones, creatures, spells, quests, groups, mail and everything else in the game apps/bezgelor_world/lib/bezgelor_world/zone/instance.ex
bezgelor_protocol The client's binary protocol: frames, encryption, packets, handlers, and the network listeners apps/bezgelor_protocol/lib/bezgelor_protocol/connection.ex
bezgelor_data The game's static data (creatures, spells, items, zones, quests), extracted from the client's files and held in memory apps/bezgelor_data/lib/bezgelor_data/store.ex
bezgelor_db The database: accounts, characters, inventories, guilds, mail and the rest, through Ecto apps/bezgelor_db/lib/bezgelor_db/characters.ex
bezgelor_events The event system: one record of what happens in the game, for the website, the admin pages and our own analysis apps/bezgelor_events/lib/bezgelor/events.ex
bezgelor_portal The website: public pages (including this one), the player account pages and the admin pages, in Phoenix and LiveView apps/bezgelor_portal/lib/bezgelor_portal_web/router.ex
bezgelor_api A small status API: server status, zones, players online, health checks apps/bezgelor_api/lib/bezgelor_api/router.ex
bezgelor_core Game rules that don't need a process: entities, unit ids, telegraph shapes, creature AI, movement paths, the spatial grid apps/bezgelor_core/lib/bezgelor_core/
bezgelor_crypto The ciphers and key exchange the client expects (SRP6, RC4, the packet cipher), password verifiers and two-factor codes apps/bezgelor_crypto/lib/bezgelor_crypto/
bezgelor_dev A development aid that records packets the server doesn't handle yet, to help work out the protocol apps/bezgelor_dev/lib/bezgelor_dev.ex

One server, one program

All of it runs as one program: one BEAM node, started with iex -S mix phx.server (scripts/start.sh). The three game servers, the website and the status API are separate listeners inside it, each on its own port (config/config.exs):

Listener Port App
Login (STS) 6600 bezgelor_auth
Realm 23115 bezgelor_realm
World 24000 bezgelor_world
Website 4000 bezgelor_portal
Status API 4002 bezgelor_api
PostgreSQL (the database, a separate program) 5433 used by bezgelor_db

Because everything shares one node, the website can ask the world server who is online, or move a stuck character, by calling its functions directly (BezgelorWorld.Portal). There is no internal network API between the parts.

Inside the node, the work is done by thousands of small processes: one for each player's connection, one for each running zone, one for each character's achievement tracker, and so on. They share nothing and talk by messages. That design, and the reasons for it, run through every part of this walkthrough.

The walkthrough

  1. Logging in: the login server, the realm server, and the character screens.
  2. The wire: how bytes become packets and packets become actions, and how the real client checks our packets.
  3. Into the world: the world server's processes, zone instances, entities and movement.
  4. Fighting: spells, telegraphs, creatures, death, loot, experience and quests.
  5. Behind the scenes: the game data in memory, the database and how player progress is saved, the event system, the website and its admin pages, and the tests.

Finding your way in the code

  • Each app keeps its code in apps/<app>/lib/ and its tests in apps/<app>/test/. A module named BezgelorWorld.Zone.Instance lives in apps/bezgelor_world/lib/bezgelor_world/zone/instance.ex: the name tells you the path.
  • Most modules start with a @moduledoc, a plain-language description of what the module is for. It is the best first thing to read.
  • scripts/test_all.sh runs every app's tests and prints a summary. The repository's README.md explains how to set up a machine.
  • Values we chose ourselves, rather than took from the client or from retail sources, are listed with their reasons in docs/design/designed-values.md. How we label data by how sure we are is in Where we differ.
  • Bezgelor began as a port of NexusForever, the C# WildStar server, and many modules say which NexusForever class they follow. NexusForever's source is the best second opinion on how the protocol works.
  • docs/architecture.md is an older, longer document from an earlier stage. Some of its diagrams name processes that no longer exist; where it and these pages disagree, these pages and the code are current.