Files
shadow-over-bishkek001/server
2026-11-09 17:58:20 +00:00
..
2026-11-09 17:58:20 +00:00
2026-11-09 17:58:20 +00:00
2026-11-05 23:02:53 +06:00
2026-11-08 17:24:15 +06:00
2026-11-09 17:58:20 +00:00
2026-11-09 17:58:20 +00:00

Headless GameServer

This target runs the authoritative InternalGameServer without SDL, OpenGL, audio, Lua, or rendering dependencies. It exposes a deterministic lockstep session through a Boost.Beast WebSocket listener (Windows and Linux).

Requirements

  • CMake >= 3.16, a C++17 compiler (MSVC, or GCC/Clang on Linux)
  • Ubuntu: sudo apt install build-essential cmake git unzip curl
  • Internet access on the first configure (see below)

Eigen and Boost are downloaded automatically by cmake/FetchDependenciesServer.cmake into thirdparty/ the first time CMake configures the project (Boost zip is ~200 MB). A system Eigen3 is used if installed; Boost is always taken from thirdparty/boost_1_90_0 so versions match the Windows build. Nothing else (SDL, zlib, libzip, ...) is needed.

Build

Linux (recommended helper, 1 compile job to stay within low RAM):

server/scripts/build.sh            # JOBS=2 server/scripts/build.sh for more

Or manually:

cmake -S server -B server/build -DCMAKE_BUILD_TYPE=Release
cmake --build server/build --target GameServer --parallel 1
ctest --test-dir server/build --output-on-failure

Windows:

cmake -S server -B server/build
cmake --build server/build --config Release --target GameServer
ctest --test-dir server/build -C Release --output-on-failure

Memory note: compiling Boost.Beast code can use 1-1.5 GB per translation unit. On a small VDS keep --parallel 1; if the compiler gets OOM-killed, add temporary swap (sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile). The running server itself needs only a few tens of MB.

After every build the repository's resources/ directory is copied next to the binary (server/build/resources, or server/build/Release/resources on Windows). Content paths are resolved from the process working directory, so the server must be started from the directory that contains resources/.

Run

server/scripts/run-server.sh

This does cd server/build and runs

./GameServer --campaign battle_zone_multiplayer --address 127.0.0.1 --port 8081

Environment overrides: CAMPAIGN, LOCATION, ADDRESS, PORT, BUILD_DIR.

Windows equivalent:

.\server\build\Release\GameServer.exe --campaign battle_zone_multiplayer

Command line options: --campaign ID, --location ID, --campaigns PATH (default resources/config/campaigns.json), --address IP (default 0.0.0.0), --port N (default 8080), --ticks N and --no-sleep (smoke tests).

The server sends one state snapshot when a player connects, then broadcasts one numbered command bundle every 100 ms. Bundles may be empty. Destination input is scheduled two ticks (200 ms) in the future. Tick IDs, rather than server or client wall-clock values, define simulation time.

Production deployment (Ubuntu, systemd + nginx)

The server listens on loopback only (127.0.0.1:8081); nginx terminates TLS and proxies WebSocket traffic.

1. systemd service

sudo cp server/deploy/shadow-gameserver.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now shadow-gameserver
systemctl status shadow-gameserver
journalctl -u shadow-gameserver -f        # logs

The unit caps memory at 512 MB and restarts on failure. After rebuilding run sudo systemctl restart shadow-gameserver. Edit User=/paths in the unit if the repository lives elsewhere. To run a second server, copy the unit with another PORT (and campaign) and add a matching /server002/ location.

2. nginx

sudo cp server/deploy/nginx-api.shadowoverbishkek.com.conf \
        /etc/nginx/sites-available/api.shadowoverbishkek.com
sudo ln -s /etc/nginx/sites-available/api.shadowoverbishkek.com \
           /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

location /server001/ proxies to http://127.0.0.1:8081/ with WebSocket upgrade headers and a 1 hour read/send timeout (the trailing slashes strip the /server001 prefix).

3. HTTPS certificate

DNS for api.shadowoverbishkek.com must point at the VDS first.

sudo certbot --nginx -d api.shadowoverbishkek.com

Certbot issues a Let's Encrypt certificate, adds the 443 ssl block and an HTTP to HTTPS redirect, and installs auto-renewal (systemctl list-timers | grep certbot).

Connecting

Public endpoint: wss://api.shadowoverbishkek.com/server001/

Handshake check from any machine (expects HTTP/1.1 101 Switching Protocols):

curl -i -N --max-time 3 \
  -H "Connection: Upgrade" -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" \
  https://api.shadowoverbishkek.com/server001/

Or interactively: npx wscat -c wss://api.shadowoverbishkek.com/server001/.

Locally on the VDS, bypassing nginx: ws://127.0.0.1:8081.

Client limitation: src/game_server/WebsocketGameClient.cpp currently speaks plain ws://host:port to / (no TLS, no path). Until TLS and path support are added to the client, the graphical game can only connect directly to a non-proxied server (e.g. ws://127.0.0.1:8080), not to the wss:// URL.

Troubleshooting

  • Animation metadata failed ... Invalid animation file: the .anim files are Git LFS pointer stubs (version https://git-lfs...). Fetch the real files with git lfs pull (or copy resources/ from a full checkout) and rebuild.

  • GameServer.LocationState test currently fails on every platform: it expects battle_zone and battle_zone_multiplayer to share a start-state file, but campaigns.json points them to different files.

  • Server content setup failed: the server was not started from the directory containing resources/ (use run-server.sh).

  • WebSocket listener setup failed: port already in use (ss -ltnp | grep 8081).

  • 502 Bad Gateway from nginx: service not running (systemctl status shadow-gameserver).

  • Connection drops after ~60 s: check proxy_read_timeout in the nginx location.

  • Compiler "Killed" / internal compiler error: out of memory, see the memory note.