Skip to content

Network server ​

The dedicated game server, for multiplayer testing and deployment.

Quick start ​

bash
npm run net:server

The entry point is src/cli/headless-netserver-v2.ts. It boots a GameServer with a BSP collision world, a gametype and a WebSocket listener.

Command-line options ​

bash
npm run net:server -- [options]
OptionDefaultDescription
--port <n>7777Server port
--map <name>wdm7Map to load (also --map=<name>)
--gametype <name>, -gDeathmatchDeathmatch, TeamDeathmatch, Duel, Amphi or SnD. An unknown name falls back to Deathmatch
--name <s>, --hostnameSHAG Server v2Server name shown to clients
--max <n>, --maxclients16Maximum players; up to eight spectators connect beyond it
--snapshotrate <n>60Snapshot broadcast rate in Hz
--game <path>./gameGame data path
--cert <path>./certs/localhost.pemTLS certificate for wss://
--key <path>./certs/localhost-key.pemTLS private key for wss://
--shutdownoffExit when the last client disconnects
--single-matchoffOne match per server: post-game is final, players return to the lobby, bots are released and the process exits
--private-matchoffMark recorded demos as private; the lobby sets this for private rooms
--training-demo <name>–Record a training demo: warmup never ends and every tick is recorded, saved to public/data/demos/training/<map>_<name>.shagdemo
--help, -h–Show help

The first two bare arguments are read as the map and the gametype, so npm run net:server -- wdm2 Duel also works.

SHAG_GAME_CERT_PATH and SHAG_GAME_KEY_PATH replace the default certificate paths, which is how servers spawned by the lobby find them.

Examples ​

bash
# Default settings
npm run net:server

# Custom port and name
npm run net:server -- --port 8080 --name "My Server"

# Search & Destroy on a bomb map, 10 players
npm run net:server -- --map wbomb1 --gametype SnD --max 10

# One-shot match server, exits when the match ends
npm run net:server -- --map wdm2 --gametype Duel --single-match

TLS ​

When both certificate files exist, the server accepts secure WebSocket (wss://) connections, which a page served over HTTPS requires. When either file is missing, the server prints a warning and serves plain ws://, which is enough for local development.

To create development certificates:

bash
mkcert -install && mkcert localhost
# move localhost.pem and localhost-key.pem into ./certs/

Server output ​

The server logs each connection and join, for example:

text
[GameServer] WebSocket connected: client_1
[GameServer] Join request from: Alice

Architecture ​

text
GameServer
├── Simulation           # Authoritative world state, 125 Hz fixed tick
├── GametypeController   # Match state, scoring, respawn, spawn selection
├── ClientManager        # Connected players, input queues
├── SnapshotBuilder      # Per-client snapshot assembly (PVS-scoped)
├── SnapshotBroadcaster  # Delta-compressed fan-out at the snapshot rate
├── LagCompensation      # Server-side rewind for hit detection
└── Transport            # WebSocket (ws:// or wss://)

Tick rate ​

The server simulates at 125 Hz (8 ms per tick) and broadcasts snapshots at 60 Hz by default, roughly every other tick. The tick rate is fixed: movement and weapon timings are tuned against 125 Hz, and the game server refuses any other rate. The snapshot rate is configurable with --snapshotrate.

  • Physics runs one fixed dt per tick, with no variable-step integration.
  • Input is applied on the tick it belongs to, not on arrival.
  • Snapshots are delta-compressed per client against the last state that client acknowledged.

Stopping the server ​

Press Ctrl+C for a graceful shutdown:

text
^C
[Server] Shutting down...

Alternatively, start it with --shutdown to exit once the last client leaves, or with --single-match to exit after one completed match.

Testing ​

Single client ​

Terminal 1:

bash
npm run net:server

Terminal 2:

bash
npm run net:client

Multiple clients ​

bash
# Terminal 1: server
npm run net:server

# Terminals 2-4: clients
npm run net:client -- --name Alice
npm run net:client -- --name Bob
npm run net:client -- --name Charlie

From the game ​

  1. Start the server: npm run net:server
  2. Start the game: npm run dev
  3. Open the console with backquote/tilde
  4. Type connect ws://localhost:7777

See also ​

Released under the MIT License.