Server Architecture
The server (server/) is the authoritative simulation layer. It owns the canonical game state, runs the game library (game/) as a shared library, and delivers entity snapshots to clients each frame.
Startup
SV_Init (server/sv_main.c) runs at process startup:
- Loads the game library (
libgame.so/game.dylib) viadlopenand obtains agame_export_t *pointer by callingGetGameAPI. - Calls
ge->Init()to let the game library set up its global state. - Calls
SV_InitGamewhich reads the map archive, spawns world entities (doodads, pre-placed units, starting locations), and notifies the game library that a new map has loaded.
Main Loop
SV_Frame(DWORD msec) is called from the platform main loop alongside CL_Frame. Unlike the client, the server advances only when enough real time has elapsed to cover a fixed FRAMETIME (100 ms):
void SV_Frame(DWORD msec) {
svs.realtime += msec;
if (svs.realtime < sv.time)
return; // not yet time for a new game frame
SV_ReadPackets(); // 1. process client commands
SV_RunGameFrame(); // 2. advance simulation
SV_SendClientMessages(); // 3. send snapshots to clients
}
This fixed-step approach decouples the simulation rate from the render rate and makes replay and deterministic simulation practical.
1. SV_ReadPackets
Drains the network receive buffer (loopback ring or UDP socket) for each connected client slot. Dispatches clc_* messages:
| Opcode | Effect |
|---|---|
clc_connect |
Allocate a client slot, send svc_serverdata + baselines + UI layout |
clc_move |
Store the usercmd_t for this client for use in ge->ClientThink |
clc_stringcmd |
Forward console commands to SV_ExecuteClientCommand |
2. SV_RunGameFrame
void SV_RunGameFrame(void) {
sv.framenum++;
sv.time += FRAMETIME;
ge->RunFrame(); // runs the game library's g_main.c G_RunFrame
}
G_RunFrame (in game/g_main.c) iterates every live entity and calls G_RunEntity, which dispatches on movetype and calls the entity's think or update callback.
3. SV_SendClientMessages
For each connected client, SV_BuildClientFrame (server/sv_ents.c) collects the entities visible to that client into a snapshot. SV_WriteFrameToClient then delta-encodes the snapshot against the previous one using MSG_WriteDeltaEntity and transmits it as an svc_packetentities message.
Delta compression ensures only changed entity fields are sent, keeping bandwidth usage low even with many active entities.
Game Library Interface
The server communicates with the game library through two vtable structs defined in game/api/:
game_import_t — server services provided to the game
The server fills this struct and passes it to GetGameAPI. It provides:
| Function | Purpose |
|---|---|
LinkEntity / UnlinkEntity |
Register an entity with the spatial index |
BoxEdicts |
Spatial query — all entities inside a bounding box |
Trace |
Sweep test against world geometry |
PointContents |
Terrain/water flags at a world position |
SetModel |
Assign a model by name to an entity |
MemAlloc / MemFree |
Server heap |
WriteXxx |
Network message write helpers |
game_export_t — entry points exported by the game library
| Function | Purpose |
|---|---|
Init |
Called once at server startup |
Shutdown |
Called when the server shuts down |
SpawnEntities |
Parse map entities and place initial units |
RunFrame |
Advance simulation by one FRAMETIME tick |
ClientConnect |
Called when a new client slot is allocated |
ClientBegin |
Called when a client finishes loading the map |
ClientThink |
Per-frame input processing for each client |
ClientDisconnect |
Client disconnected — clean up player entity |
Entity System
Entities are fixed-size edict_t structs stored in a flat array (g_edicts[]) in the game library. The server and game library share a read-only view of this array via ge->edicts and ge->num_edicts.
Each entity has:
- entityState_t s — the fields transmitted to clients (origin, angles, model index, frame, effects flags, …)
- entityShared_t shared — spatial data used by the server for link/unlink/trace (bounding box, link count, …)
- All other fields (health, AI, move state, callbacks) are game-library-private and never sent over the network
Only the entityState_t part is delta-encoded and sent to clients.
Snapshots and Delta Compression
The server maintains a ring buffer of clientSnapshot_t records per client (in server/sv_ents.c). Each snapshot stores the full entityState_t set visible to that client at a given frame. SV_WriteFrameToClient compares the latest snapshot against the acknowledged one and emits only the fields that differ:
// server/sv_ents.c
MSG_WriteDeltaEntity(&msg, &oldState, &newState, false, newState.number == cl->clientNum+1);
The client's acknowledged frame number is tracked per slot so the server can re-send if a snapshot was lost.
Key Files
| File | Purpose |
|---|---|
server/sv_main.c |
SV_Frame, SV_Init, SV_ReadPackets |
server/sv_ents.c |
SV_BuildClientFrame, SV_WriteFrameToClient, delta encoding |
game/g_main.c |
G_RunFrame, G_ClientBegin, entity-per-frame dispatch |
game/g_phys.c |
Entity movement, collision response |
game/g_commands.c |
Player command handlers (move, attack, ability) |
game/g_monster.c |
Unit lifecycle, animation state machine |
common/net.c |
Loopback + UDP transport shared with the client |
common/msg.c |
Message serialisation and delta encoding helpers |