An authoritative server for Ace of Spades 1.x (Battle Builders)
Python 3 + Cython · ENet · server-authoritative · physics reverse-engineered from the original compiled client
BattleSpades is a clean-room reimplementation of the dedicated server for the classic Ace of Spades "Battle Builders" (0.x/1.x) protocol. It talks to the original, unmodified game client — the physics, netcode, and packet formats were reverse-engineered from the compiled game and calibrated against observed retail movement, shooting and block-edit behavior. The maintained references distinguish implemented contracts from parity and release acceptance still required.
The goal: a complete, correct, hackable server that anyone can run in one command, so the classic game stays alive and playable — and so it's a solid base for ports to other languages.
Works with the stock Steam client, the non-Steam client, and the open-source aceofspades_revival client build.
- Status
- Quick start
- Portable beta releases
- What works
- Architecture
- Building from source
- ENet networking
- Configuration
- Running & hosting
- Commands
- Testing & tooling
- Roadmap
- Contributing
- Credits
- License
Playable. The source implements movement, combat, terrain edits/collapse, pickups, deaths/respawns, objectives and bots against recovered retail contracts. Focused regressions and measured retail comparisons support those paths; they do not establish exact parity for every map, mode or network condition. See What works, the Roadmap and the release gates in the runbook.
The cleaned local outputs are dist/ and release-dist/; release/ contains
tracked packaging inputs. Retained binaries can predate source edits. Build and
check the exact release you intend to use. The native client's current commands
and feature guides are indexed in its documentation.
- Run the unit/regression suite with
py -3.12 -m pytest tests -q. - Run the capacity and retail-client validation gates described in
docs/RUNBOOK.mdbefore a release. Historical test counts and timing measurements are not guarantees for the current checkout. - Movement parity: mean client↔server position delta in the millimetre range over
thousands of frames (
py scripts/replay_parity.py— must stayALL PASS) - Physics ground truth and every measured constant live in
docs/PROTOCOL.md
For a dedicated server without a Python or compiler installation, use a portable beta release. The source workflow below is intended for development and custom server builds.
You need Python 3.10–3.12 (3.12 is the primary dev target) and a C/C++ compiler
(MSVC Build Tools on Windows, gcc/clang on Linux/macOS). The compiler builds the
Cython extensions plus the pinned, vendored pyenet/ENet transport. A virtual environment
(venv) is recommended so compiled dependencies stay isolated.
One-liner — clone, install deps, build the Cython core, and launch:
Linux / macOS
git clone https://github.com/KikoTs/BattleSpades.git && cd BattleSpades && ./scripts/install.sh && python run_server.pyWindows (PowerShell)
git clone https://github.com/KikoTs/BattleSpades.git; cd BattleSpades; .\scripts\install.ps1; python run_server.pyThen point any Ace of Spades 1.x client at your.ip:27015. Edit config.toml
to select the startup map, mode, population, and administrative settings.
Manual steps (what the installer does)
# (recommended) create + activate an isolated environment first
python3.10 -m venv venv
source venv/bin/activate # Linux/macOS
# .\venv\Scripts\Activate.ps1 # Windows (PowerShell)
pip install -r requirements.txt # build/runtime Python dependencies
python setup.py build_ext --inplace # compile the Cython extensions
python run_server.py # start the server on port 27015VERSION defines the release version. The release workflow targets
six standalone server archives. Each archive
contains the launcher, Python/native runtime, editable config.toml, VXL maps,
KV6 prefabs, plugin directory, and license notices.
| Operating system | Archives |
|---|---|
| Windows | windows-x86_64, windows-arm64 |
| Linux | linux-x86_64, linux-arm64 |
| macOS | macos-x86_64, macos-arm64 |
Download the archive matching the host from the repository's GitHub Releases page, extract the complete directory, and validate it before opening a public server:
# Windows
.\BattleSpades.exe --check
.\BattleSpades.exe# Linux / macOS
./BattleSpades --check
./BattleSpadesThe reconstructed retail tutorial is intentionally a separate program. It is
not a selectable public mode and run_server.py cannot start it:
# Source checkout
py -3.12 run_tutorial.py --check
py -3.12 run_tutorial.py
# Portable Windows release (use ./BattleSpadesTutorial on Linux/macOS)
.\BattleSpadesTutorial.exe --check
.\BattleSpadesTutorial.exeThat launcher locks the process to the original Training.vxl, tutorial mode
ID 10, twelve isolated training lanes, and the stock staged inventory flow:
the gallery grants/equips the pistol, then the climb grants the block tool and
spade. It disables bots, plugins, public registration, voting, rotation, and
competitive rules in memory without rewriting config.toml.
The reconstructed Map Creator is also an isolated program. It uses the stock client's native six-tab Construct browser, five-item backpack, prefab ghosts, Game Data object placer, validation HUD, palette, and raw-voxel editor tools, while the dedicated process owns persistence:
# Source checkout. Retail UGC art remains user-supplied and is never bundled.
py -3.12 run_map_creator.py --check --retail-root G:\AoSRevival\AceOfSpades_no_steam_new
py -3.12 run_map_creator.py --project MyMap --terrain grassland `
--target-mode ctf --retail-root G:\AoSRevival\AceOfSpades_no_steam_new
# Save into the catalog used by Main Menu -> Map Creator -> Publish Map.
py -3.12 run_map_creator.py --project MyMap --terrain grassland `
--target-mode ctf --retail-root G:\AoSRevival\aos-nonsteam\src `
--publish-root G:\AoSRevival\aos-nonsteam\src\hosted_ugc
# Portable Windows release (use ./BattleSpadesMapCreator elsewhere)
.\BattleSpadesMapCreator.exe --project MyMap --terrain grassland `
--target-mode ctf --retail-root C:\Games\AceOfSpadesNew projects may use desert, lunar, mountain, grassland, temple,
urban, marsh, snowy, or water. Publish validation supports tdm,
ctf, dem, mh, oc, tc, vip, zom, and dia. The launcher creates
and continuously checkpoints a portable .vxl/.txt/.ugc triplet under
ugc-projects/ by default. --publish-root <client>/hosted_ugc instead writes
that triplet into the exact maps/ catalog enumerated by the stock Publish Map
menu; an optional same-stem .png is its Workshop preview. It requires the
operator's legally installed retail ugc/maps and ugc/kv6 assets;
BattleSpades does not redistribute those proprietary baseplates or models.
No system Python or compiler is needed. Change the default admin password
changeme before exposing UDP port 27015. Verify the downloaded zip against
the release's SHA256SUMS.txt.
The macOS beta is unsigned and unnotarized, so Gatekeeper may require an explicit operator override. The release does not claim Apple notarization.
| Area | State |
|---|---|
| Movement / physics | Frame-accurate server sim, oracle-calibrated to the compiled client (walk, sprint, crouch, wade, climb, gravity, friction) |
| Jumping | Full client↔server-synced jump (input edge-latched, reconciliation calibrated) |
| Shooting | All hit-scan guns (rifle, SMG, shotgun, sniper, pistol, MG) driven by the client's own per-weapon damage / fire-rate / clip tables; hit-scan from the reported aim, headshots, tracers at the right spot |
| Blocks | Build (BlockLine) + break (spade dig & bullet damage) — the exact aimed cell is removed on every client, block-colored debris |
| Structure collapse | Cut a structure off from the ground and the disconnected chunk falls (flood-fill detection + client fall animation) |
| Grenades | Thrown entity + fuse + bounce physics + blast damage (falloff + line-of-sight) + 3×3×3 block destruction |
| Pickups | Ammo / health crates, restock on spawn |
| Combat lifecycle | Damage, kills, kill feed, death → grave entity → timed respawn |
| Game modes | Ten retail Match Lobby modes plus Arena; see mode coverage |
| Map Creator | Isolated retail-compatible UGC host with nine terrain baseplates, all 373 native catalog entries, prefabs, carving, palettes, 19 Game Data objects, mode validation, preview PNGs, and atomic project checkpoints |
| Bots | Supervised thread or process worker with direct voxel navigation, fair perception/aim, class actions, and mode-specific roles |
| Map transfer | Full VXL streaming with correct CRC validation |
| Admin / chat | Player + admin command set, team management |
BattleSpades/
├── run_server.py # entry point (async event loop + logging)
├── run_tutorial.py # isolated retail tutorial entry point
├── run_map_creator.py # isolated retail UGC editor entry point
├── config.toml # all server settings
├── setup.py # Cython build definition
│
├── aoslib/ # Cython core (compiled)
│ ├── world.pyx # movement physics, boxclipmove, grenade/entity sim
│ ├── vxl.pyx # byte-faithful VXL map format
│ └── kv6.pyx # voxel model format
├── shared/ # Cython wire layer (compiled)
│ ├── packet.pyx # every packet's read/write (the protocol)
│ ├── bytes.pyx # ByteReader/ByteWriter
│ └── glm.pyx # vector math
│
├── server/ # server logic (pure Python)
│ ├── main.py # 60 Hz sim loop, WorldUpdate broadcast, entities, grenades
│ ├── player.py # per-player state, input buffering, reconciliation
│ ├── combat_runtime.py # shooting, block damage, collapse
│ ├── world_manager.py # map ops, block mutation, flood-fill
│ ├── connection.py # ENet peer + handshake
│ └── bot_ai/ # bot ownership, worker, navigation, and actions
├── protocol/ # packet dispatch + runtime decoders
├── modes/ # tdm / ctf / classic_ctf / arena / vip / zombie
├── commands/ # player + admin commands
├── plugins/ # optional plugin hooks
├── maps/ # stock .vxl maps (shipped)
├── scripts/ # build + reverse-engineering / verification tooling
├── tests/ # pytest suite
└── docs/ # six focused server/operator/reversal references
Design principles
- Server-authoritative — the server re-simulates every player at a fixed 60 Hz; the client predicts locally and is reconciled via per-player WorldUpdate self-rows.
- Cython where it counts — physics, map ops, and (de)serialization are compiled; game logic stays in readable Python.
- Bounded hot paths — asyncio/ENet work and gameplay packet drains have explicit budgets; WorldUpdate serialization is shared by equivalent clients.
- Non-blocking logging — formatting and I/O use a bounded background queue; slow sinks drop records instead of stalling the 60 Hz simulation.
The Cython extensions must be compiled before first run (and re-compiled after editing any
.pyx). Stop the server before rebuilding — a running server locks the compiled
.pyd/.so files.
python setup.py build_ext --inplace
# or the convenience wrapper:
python scripts/build.pyRequires a working C toolchain:
| Platform | Toolchain |
|---|---|
| Windows | Visual Studio Build Tools → "Desktop development with C++" (MSVC + Windows SDK) |
| Debian/Ubuntu | sudo apt install build-essential python3-dev |
| Fedora/RHEL | sudo dnf install gcc python3-devel |
| macOS | xcode-select --install |
See docs/RUNBOOK.md for cross-compilation notes (the project ships on
Windows x64 and Linux amd64/arm64).
BattleSpades uses pyenet and ENet for reliable UDP—the same transport family used by the original game (protocol version 168, single channel, range-coder compression).
The pinned pyenet 1.3.17 wrapper and ENet 1.3.17 C sources live under
vendor/pyenet/ and compile as the enet extension during setup.py build_ext.
This avoids PyPI's obsolete Cython<1 dependency and missing macOS/ARM wheels,
so every release target builds the same source with the pinned Cython 3 toolchain.
Everything lives in config.toml. Highlights:
[server]
name = "BattleSpades Server"
port = 27015
max_players = 32
tick_rate = 60 # server simulation rate — keep at 60 (client-paired, physics-calibrated)
[game]
default_mode = "tdm" # tdm | ctf | cctf | zom | vip | mh | tc | dia | dem | oc
default_map = "ArcticBase"
respawn_time = 5.0
friendly_fire = false
[bots]
enabled = true
population_mode = "backfill"
fill_target = 12
max_bots = 12
reserve_human_slots = 2
difficulty = "mixed" # casual | normal | hard | mixed
worker = "thread" # process is also supported
perception_hz = 10
decision_hz = 8
path_requests_per_second = 24
main_thread_budget_ms = 0.75
seed = 0
clean_slate_games = 3 # recycle only the isolated planner every 3 games
[teams]
team1_name = "TEAM1_COLOR" # string-table IDs the client localizes (renders "Blue"/"Green")
team2_name = "TEAM2_COLOR"
[admin]
password = "changeme" # CHANGE THIS before hosting publiclyNever save
config.tomlwith a UTF-8 BOM (e.g. PowerShellSet-Content -Encoding utf8) — the BOM breakstoml.loadand the server silently falls back to defaults.
For local tweaks that shouldn't be committed, copy the configuration to
config.local.toml (gitignored) and pass it explicitly:
python run_server.py --config config.local.toml.
python run_server.pyOne executable can supervise several isolated configs. The included
fleet.toml launches all ten recovered public Match Lobby modes:
| Server | Mode | Game UDP | Query UDP |
|---|---|---|---|
| Official TDM | tdm |
27015 | 27016 |
| Official CTF | ctf |
27017 | 27018 |
| Official Classic CTF | cctf |
27019 | 27020 |
| Official Zombie | zom |
27021 | 27022 |
| Official VIP | vip |
27023 | 27024 |
| Official Multi-Hill | mh |
27025 | 27026 |
| Official Territory Control | tc |
27027 | 27028 |
| Official Diamond Mine | dia |
27029 | 27030 |
| Official Demolition | dem |
27031 | 27032 |
| Official Occupation | oc |
27033 | 27034 |
Every profile uses the recovered retail-compatible map rotation and duration. Backfill is capped at six bots per empty instance so launching the complete fleet does not immediately allocate the old 120-bot worst case:
# Source checkout
py -3.12 run_server.py --fleet fleet.toml
# Portable Windows release
.\BattleSpades.exe --fleet fleet.tomlEach configs/ profile owns a unique game/query/Steam-init port
set and log file. Config paths are resolved beside the fleet manifest,
including paths containing non-English characters. The launcher validates
every config and rejects duplicate names or game UDP ports before starting
anything. Ctrl+C sends a clean shutdown command to every child and terminates
only a child that exceeds the configured shutdown timeout. Public Revival
registry and Steam publishing are disabled in the examples; enable them per
profile only after assigning unique public identities and forwarding the
listed game/query ports plus Steam-init UDP ports 8766–8775.
To host directly, forward UDP 27015 (or your configured game port) and set
a real admin.password. The ENet socket answers direct A2S/LAN queries. The
optional [steam] bridge registers app 224540 with Valve's current registry;
also forward its updater and query UDP ports (defaults 8766 and game port +
1). Valve retired the legacy UDP list used by the unmodified 2015 in-game
browser, so verify registration with the source-tree checker described in the
runbook.
Player — /help, /kill, /team <blue|green>, /score, /players,
/pm <player> <msg>, /me <action>, /stats, /ping
Admin (after /admin <password>) — /kick, /ban, /mute, /unmute,
/tp <player>, /god, /map <name>, /mode <tdm|ctf|cctf|zom|vip|mh|tc|dia|dem|oc|arena>, /restart, /say <msg>,
/fog <r> <g> <b>, /time, /balance, /bots status,
/bots fill <count>, /bots add <count> [team],
/bots remove <count|name|all>, /bots difficulty <casual|normal|hard|mixed>
py -3.12 -m pytest tests/ -q # unit/regression tests
py scripts/replay_parity.py # offline movement-parity check (must be ALL PASS)The scripts/ directory also holds the reverse-engineering rig used to build this server:
an in-game physics oracle / console (game_console.py, auto_join.py,
oracle_experiments.py) that drives the real client to extract ground-truth physics and
replay it through the Python engine. Details in docs/RUNBOOK.md.
Retail top-screen announcement packets, localization parameters, team-color
IDs, and the recovered template variables are documented in
docs/PROTOCOL.md.
These six references describe the maintained server. Check configuration and
implementation details against config.toml, source, and tests; dated session
reports and obsolete deployment claims are not current documentation.
docs/ADMIN_GUIDE.md: every config option, rule, command, and plugin hook.docs/GAMEPLAY.md: modes, official map sets, gameplay invariants, and bots.docs/ARCHITECTURE.md: service ownership and data flow.docs/PROTOCOL.md: packets, retail evidence, and the reverse-engineering workflow.docs/BOT_NAVIGATION.md: VXL semantic atlas, safe cache format, map-matrix simulation, and water/stuck recovery.docs/RUNBOOK.md: build, operate, diagnose, soak, release, and container deployment.
Current limitations are documented with their features in
docs/GAMEPLAY.md,
docs/BOT_NAVIGATION.md, and
docs/PROTOCOL.md. Further work needs reproducible failures
and retail-client acceptance, especially for objective modes and destructible
terrain navigation.
Contributions welcome — especially maps, game modes, and platform build reports. Please:
- Keep
py -m pytest tests/ -qandpy scripts/replay_parity.pygreen. - Rebuild Cython (
python setup.py build_ext --inplace) after editing any.pyx. - Read
docs/RUNBOOK.mdanddocs/PROTOCOL.mdbefore touching netcode or physics — those values are hard-won measurements.
See CONTRIBUTING.md.
- Built for the Ace of Spades revival effort. Companion open-source client build: KikoTs/aceofspades_revival.
- Ace of Spades was created by Ben Aksoy / Jagex. This is an independent server reimplementation for preservation and play; it does not ship the original client executable or proprietary game code. Portable server releases include the project's tracked VXL/KV6 gameplay content.
- Networking via pyenet / ENet.
- Community fixes: build & setup improvements from @TylerJaacks.
MIT — see LICENSE.