Skip to content

Latest commit

 

History

104 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BattleSpades

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.

Table of contents

Status

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.md before 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 stay ALL PASS)
  • Physics ground truth and every measured constant live in docs/PROTOCOL.md

Quick start

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.py

Windows (PowerShell)

git clone https://github.com/KikoTs/BattleSpades.git; cd BattleSpades; .\scripts\install.ps1; python run_server.py

Then 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 27015

Portable beta releases

VERSION 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
./BattleSpades

The 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.exe

That 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\AceOfSpades

New 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.

What works

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

Architecture

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.

Building from source

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.py

Requires 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).

ENet networking

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.

Configuration

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 publicly

Never save config.toml with a UTF-8 BOM (e.g. PowerShell Set-Content -Encoding utf8) — the BOM breaks toml.load and 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.

Running & hosting

python run_server.py

One 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.toml

Each 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.

Commands

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>

Testing & tooling

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.

Documentation

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.

Roadmap

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.

Contributing

Contributions welcome — especially maps, game modes, and platform build reports. Please:

  1. Keep py -m pytest tests/ -q and py scripts/replay_parity.py green.
  2. Rebuild Cython (python setup.py build_ext --inplace) after editing any .pyx.
  3. Read docs/RUNBOOK.md and docs/PROTOCOL.md before touching netcode or physics — those values are hard-won measurements.

See CONTRIBUTING.md.

Credits

  • 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.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

Contributing

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages