AOS REVIVALMenuJoin Discord
Voxel engineers building a large timber structure in Ace of Spades Battle Builder
Operations board // live status

Implementationstatus

What works, what has been checked against the original game, and what is still missing — for every protocol packet, game mode, feature and platform. Filter it, search it, and open any row for the technical detail.

Updated Maintained from the BattleSpades server and client docs

21Retail-verified
215Done
20In testing
9Known gap
22Not used
10Not planned
Status key

What the labels mean

Packet rows carry two statuses: one for the BattleSpades dedicated server and one for the new BattleSpades client. The counts above add both sides together. Recent changes are explained in What's new; the byte layouts live in the packet reference.

  • Retail-verified

    Works, and has been compared against the original retail game (live side-by-side, captured bytes or the stock game code).

  • Done

    Implemented and covered by automated tests; not every detail has a retail side-by-side yet.

  • In testing

    Built and passing tests, but a live play-test or deployment is still owed before we call it done.

  • Known gap

    Missing or behaves differently from retail. Tracked and on the list.

  • Not used

    Understood, but deliberately left unused: the game plays correctly without it, or sending it would crash stock clients.

  • Not planned

    Out of scope for the revival for now.

Status matrix

Everything, in one board

Protocol packets

All 119 retail Protocol 168 packet ids, with what the BattleSpades server and the BattleSpades client do with each one. Generated from the server's protocol catalogue and the client's packet definitions.

ClockSyncID 0 · Both ways

Round-trip clock/loop_count sync; client 1 tick ahead.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Round-trip clock/loop_count sync; client 1 tick ahead. The client sends it unsequenced every 60 loops; the reply is sent ENet UNSEQUENCED too, so it is never held behind a lost reliable packet (a lost reply is replaced by the next one).

PlaceDynamiteID 1 · Client → server

Tool/loadout-gated Miner charge; server creates entity type 10 and owns its fuse/blast.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Tool/loadout-gated Miner charge; server creates entity type 10 and owns its fuse/blast.

WorldUpdateID 2 · Server → client

30 Hz unreliable position/state feed.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

30 Hz unreliable position/state feed. Header loop is the global snapshot clock; each human row pong is that player's consumed ClientData loop. Peerless bots have no client clock and use the authoritative server loop as a monotonic remote-row stamp; leaving bot pong at zero makes retail deduplicate every later bot position. Rocket-turret rows carry the turret entity id as the same signed short CreateEntity wrote (uint16 registry ids above 32767 become negative, e.g. 40000 -> -25536).

Client: Decoded by the session layer; drives every remote player and the scoreboard ping.

EntityUpdatesID 3 · Neither (inactive)

Not used: entities move with ChangeEntity instead, and a truncated batch would crash stock clients.

ServerNot usedClientNot used
Technical detail

Server catalogue status: Planned

IDA (stock gameScene.pyd): self.process_entity_updates(packet.updated_entities), a moving-entity delta stream. BattleSpades moves entities with ChangeEntityPosition(21); no mode needs the batch form yet.

ClientDataID 4 · Client → server

Buffered client input, applied at matching tick.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Buffered client input, applied at matching tick. The player byte uses bits 0–6 for player_id; bit 7 is palette_enabled.

SetHPID 5 · Server → client

Sets a player's HP (spawn/heal/damage feedback).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Sets a player's HP (spawn/heal/damage feedback).

ShootPacketID 6 · Client → server

Client fire request.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Client fire request. The server validates cadence/origin/orientation and resolves authoritative damage; retail has no incoming packet-6 action handler.

PaintBlockPacketID 7 · Both ways

Validated (paint tool, reach/range, target solidity) before the authoritative color mutation, which is journaled for late joiners.

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Handled+Sent

Validated (paint tool, reach/range, target solidity) before the authoritative color mutation, which is journaled for late joiners. Accepted paints are echoed to every in-game client, owner included (combat_runtime), so a Map Creator editor that is also a UGC map client converges. On a solid voxel it recolours only: user_blocks health and any DamagedBlock health are unchanged (live 2026-09-26), and WorldManager.set_block keeps the cell's recorded health and damage for the same solid→solid recolour.

ShootFeedbackPacketID 8 · Server → client

Remote firearm shot only.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Remote firearm shot only. Sent to observers (not the already-predicting human shooter); the native handler verifies the visible character's tool and calls character.shoot(seed), producing gun audio/muzzle effects. Never send for spades/melee: those tools have no shoot method and use WorldUpdate action bit 0x01.

ShootResponseID 9 · Server → client

Authoritative player-hit response.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Authoritative player-hit response. Broadcast with damage_by=shooter_id; native clients show blood to observers but play the hit-confirm sound/crosshair only for the matching local shooter.

UseOrientedItemID 10 · Both ways

Validated: active normalized tool, cadence, stock, and the reported origin's reach from the authoritative body.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Validated: active normalized tool, cadence, stock, and the reported origin's reach from the authoritative body. Legacy grenade-family objects are relayed to observers; entity-backed projectiles use CreateEntity instead. Never relay GL tool 55 into the retail client's stale GLGrenade packet constructor.

SetColorID 11 · Both ways

Palette state for block, flare, and Block Cannon tools (5/22/29/48).

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Palette state for block, flare, and Block Cannon tools (5/22/29/48). Every new life starts from its configured team RGB and publishes that snapshot after CreatePlayer; later UI choices are broadcast only to observers because the sender already applied them locally. Relays are throttled per player (one per 0.1 s); the server state always updates and one trailing relay publishes the newest colour, so observers never keep a stale palette.

SetUGCEditModeID 12 · Both ways

Map Creator only: the editor host's request changes the project target validation mode and the server rebroadcasts the committed mode (reliable, never journaled) to every editor; guests' requests and every non-UGC…

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Map Creator only: the editor host's request changes the project target validation mode and the server rebroadcasts the committed mode (reliable, never journaled) to every editor; guests' requests and every non-UGC server ignore it.

SetClassLoadoutID 13 · Both ways

Normalized server-side; an unchanged selection is ignored, and a changed one is staged and applied at the next life (a live Character dies with KILL_CLASS_CHANGE).

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Normalized server-side; an unchanged selection is ignored, and a changed one is staged and applied at the next life (a live Character dies with KILL_CLASS_CHANGE). Only the Map Creator's live UGC backpack edit is committed in place and acknowledged with a reliable SetClassLoadout (instant=1). Retail may omit the trailing zero UGC-count byte; the bounded decoder accepts only that optional empty tail. UGC preserves one shared five-item prefab/Game Data backpack.

ExistingPlayerID 14 · Neither (inactive)

Not used: joiners receive the roster through CreatePlayer instead.

ServerNot usedClientNot used
Technical detail

Server catalogue status: Reversed/unused

Roster entry (id, demo, team, class, tool, pickup, dead, score, forced_team, language, colour, name, loadout, prefabs). Not sent: the roster goes out as CreatePlayer(28). Live 2026-09-26: the client reads pickup signed, so 0xFF arrives as -1 and the created player gets pickup_id=None; 0 gives pickup_id=0 (the minimap PICKUPS[0] KeyError case). The earlier "no 0xFF sentinel" note was wrong; switching the roster to 14 would be a roster-lifecycle change, not needed for parity today.

NewPlayerConnectionID 15 · Client → server

Client's join announcement (name/team/class), parsed in handshake.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Client's join announcement (name/team/class), parsed in handshake. Accepted only after this connection's InitialInfo → MapSync → StateData completed (every stock client, Steam or ticket-less legacy, sends SteamSessionTicket(105) first); earlier or duplicate in-flight packets are ignored. Before CreatePlayer, names are normalized to a case-insensitively unique 15-byte wire value; duplicate names can steal the native client's local-player association.

Client: Sent when you pick a team and class.

ChangeEntityID 16 · Server → client

Server-owned turret/MG target, carrier, ammo, state, and map-pickup position.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Server-owned turret/MG target, carrier, ammo, state, and map-pickup position. Action 1 (SET_POSITION) reliably settles a pickup after its supporting structure breaks.

ChangePlayerID 17 · Server → client

Existing-player state changes.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Existing-player state changes. Action SET_HIGH_MINIMAP_VISIBILITY (8) exposes the CTF intel carrier, both VIP bosses, or ZOM's final survivor through terrain; each mode owns marker cleanup and late-join replay.

POIFocusID 18 · Server → client

Handler reads only target_x/y/z and (with a local player) activates the GameScene LookAtController aimed at that point.

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Sent

Handler reads only target_x/y/z and (with a local player) activates the GameScene LookAtController aimed at that point. No timeout and no release packet: live 2026-09-26 the lock held until the local player died (ChaseController) or was spawned (server respawn_player of a living player cleared it) or the map changed. Demolition sends it to every non-spectator team player when the objective airstrike launches, aimed at the base being struck (on a double destruction each team watches the base it destroyed); the round restart respawns them. server.hud_packets.poi_focus.

DestroyEntityID 19 · Server → client

Removes an entity previously announced to that GameScene.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Removes an entity previously announced to that GameScene. Server-only objective markers must never receive a destroy packet. For Snowball it removes the visual/effect only; it does not apply blast impulse.

HitEntityID 20 · Server → client

Visual impact callback for server-authoritative damageable-entity hits.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Visual impact callback for server-authoritative damageable-entity hits.

Entity / CreateEntityID 21 · Server → client

Entity wire format + create; used for map crates, type-13 static/player flare lights, deployables, persistent ground intel 16, and moving projectile types including chemical 32, GL 33, sticky 34, and launched mine 37.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Entity wire format + create; used for map crates, type-13 static/player flare lights, deployables, persistent ground intel 16, and moving projectile types including chemical 32, GL 33, sticky 34, and launched mine 37. The retail runtime table does not contain legacy FLAG=0 or BASE=1; sending BASE here freezes GameScene.create_entity with KeyError: 1. Both Entity and CreateEntity share id 21.

CreateAmbientSoundID 22 · Server → client

Registers a map-owned ambient controller.

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Sent

Registers a map-owned ambient controller. Empty points are a global bed; authored points define local emitters. Must precede packet 24 with the same loop ID. LIVE-VERIFIED.

PlaySoundID 23 · Server → client

One-shot positional/UI sound: pickups, round/kill cues, and observer-only block-tool impacts.

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Sent

One-shot positional/UI sound: pickups, round/kill cues, and observer-only block-tool impacts. The actor predicts its own mining sound and is excluded. LIVE-VERIFIED.

PlayAmbientSoundID 24 · Server → client

Allocates the streaming ambient GameSound registered by packet 22.

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Sent

Allocates the streaming ambient GameSound registered by packet 22. Global beds are unpositioned; local loops bootstrap at the listener and are moved by the native point controller. LIVE-VERIFIED.

StopSoundID 25 · Server → client

Byte-sized loop-id teardown for server-owned PlaySound/PlayAmbientSound streams.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Byte-sized loop-id teardown for server-owned PlaySound/PlayAmbientSound streams. The shared helper validates 0–255; the native receiver treats an unknown id as an idempotent no-op.

PlayMusicID 26 · Server → client

Music track (server/audio.py — last-minute game_ending track at 61s remaining).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Music track (server/audio.py — last-minute game_ending track at 61s remaining). The in-round last_man_standing bed at round start/join is a deliberate deviation behind [audio] mode_start_music (default on; off = retail silence, StopMusic only).

StopMusicID 27 · Server → client

Stop the current music track (server/audio.py).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Stop the current music track (server/audio.py).

CreatePlayerID 28 · Server → client

Spawns a player on clients; also carries the roster.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Spawns a player on clients; also carries the roster. Loadout and all three selected prefab names come from the same committed ClassSelection. Every live player name must be unique before this packet is emitted; the packet direction contains no movement-owner identity field.

PrefabCompleteID 29 · Server → client

Sent to the builder after its prefab commits (or is refused), always after the packet-30 echo.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Sent to the builder after its prefab commits (or is refused), always after the packet-30 echo.

BuildPrefabActionID 30 · Both ways

Shared PrefabActionService validates selection, reach, line of sight, body overlap, reservations, per-player queue share, and stock (blocks >= len(model points)); a footprint with any voxel outside the buildable map is…

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Handled+Sent

Shared PrefabActionService validates selection, reach, line of sight, body overlap, reservations, per-player queue share, and stock (blocks >= len(model points)); a footprint with any voxel outside the buildable map is refused whole. Competitive prefabs commit every model voxel in one post-physics tick (prefab_competitive_cell_budget caps cells per tick; never split) with retail replace_solids semantics and DEFAULT_PREFAB_HEALTH (9) in the server damage model, charge exactly one block per model voxel, then broadcast ONE packet 30 (add_to_user_blocks=1, color = the blend base the server used, index range 0..0) to every in-game client including the owner, then 29 to the owner. The stock client expands the KV6 itself: add_user_block(..., 9.0, replace_solids=True) per voxel, 50/50 blend_color(packet.color, voxel) (client re-randomises the low 2 bits per channel), one smoke ring per top-layer voxel, and the owner's wallet drops once per model voxel even over existing solids; send_build_prefab does not predict. LIVE-VERIFIED 2026-09-26 (68-voxel superminibunker: 45 replaced solids, owner wallet 500→432 = server 432, observer wallet unchanged, all cells appear in one frame with 32 smoke rings, 9 SMG hits break a cell on owner, observer, late joiner and server). UGC keeps the off-thread KV6 preparation, raw model colours, bounded main-thread batches, and the native index-range echo.

ErasePrefabActionID 31 · Both ways

UGC Map Creator only (ignored in competitive play).

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

UGC Map Creator only (ignored in competitive play). Native carve with verified yaw/pitch/roll fields. The action is echoed only after bounded live-world target validation, then the expanded set is removed through the authoritative block-destroy path.

BlockBuildID 32 · Both ways

Single-block place; handled on receive, echoed to the builder only.

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Handled+Sent

Single-block place; handled on receive, echoed to the builder only. Client material table BLOCK_BUILD_TYPE_STATS: block_type 0 adds a user block with health 9.0 (1 = snow, 3.0) and debits the local owner one block (live 2026-09-26). The server records every committed build at 9.0 (USER_BLOCK_HEALTH); observers get 33 + a 38 user row. No longer used for prefab cells.

BlockBuildColoredID 33 · Server → client

Per-block coloured placement for ordinary-build observers, terrain repair, persistent Block Cannon impacts, and MapSync join catch-up.

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Sent

Per-block coloured placement for ordinary-build observers, terrain repair, persistent Block Cannon impacts, and MapSync join catch-up. The stock client adds it as a user block with health 3.0 and no wallet change, and ignores it on an already-solid voxel (live 2026-09-26). Block Cannon cells are recorded at 3.0 server-side; every other 33 is followed on the same reliable stream by a 38 user row carrying the server's health (9.0 built/prefab, 5.0 map voxel) and, for a damaged cell, a damaged row — live builds (_send_observer_block_health), terrain repair and join catch-up (TerrainRepairService.canonical_health_packets).

BlockOccupyID 34 · Neither (inactive)

Not used: the server never holds a block in a pending-build state that other players must be kept out of.

ServerNot usedClientNot used
Technical detail

Server catalogue status: Reversed/unused

Handler reads x, y, z, player_id → block_manager.occupy_block, storing occupied_blocks[(x,y,z)] = player_id; live 2026-09-26 an occupied air cell fails valid_to_add for every client (build ghost refused) until BlockLiberate(35) or clear_occupied_blocks. It reserves cells only; BattleSpades commits builds immediately (BlockLine/BlockBuildColored), so there is no pending cell to reserve.

BlockLiberateID 35 · Server → client

Only valid inside the Map Creator; anywhere else the stock client never sends it, so the server treats it as a forged packet.

ServerDoneClientDone
Technical detail

Server catalogue status: Rejected outside UGC

The stock client only *receives* 35 (process_packet_block_liberate → block_manager.liberate_block; IDA 2026-09-26 found no send site in gameScene.pyd, Steam or non-Steam); digging arrives as ShootPacket(6). A 35 from a client outside the Map Creator is a forged packet → anticheat.protocol_violation (kick when [anticheat] kick_on_protocol_violation). In UGC the legacy path stays with reach + line-of-sight checks. The server never sends 35.

ExplodeCorpseID 36 · Server → client

Three bytes: player id and effect flag.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Three bytes: player id and effect flag. Sent only to in-game clients whose roster knows that id (known_player_lives); loading clients are repaired by send_catchup_state on first ClientData. A normal death sends it after the retail corpse fuse (0s, or 1.0s with a selected jetpack) before creating the falling entity-11 grave; this activates the native controllable death camera. Classic CTF retains its client-owned ClassicCorpse until a hit (flag 1) or silent cleanup (flag 0). It is not entity type 12.

DamageID 37 · Server → client

The only terrain-removal path.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

The only terrain-removal path. BlockManager.handle_damage expands the TYPE's footprint around floor(position+0.5) and calls add_damage on each solid cell with z ≤ 238; the damage byte is quarter units rounded to nearest. Footprints (live-fitted exactly for all 44 types, 2026-09-26; server/block_damage_model.py): single cell (0,1,4,6,25,26,28,29,34,42,43); z column z-1..z+1 (2,5,36); machete z,z+1 (35); 3×3×3 cube, x-major, ceil4(amount + E·r) with E = 5 (3), 8 (17), 0 (31); sphere d² < R², z-major (z,x,y), ceil4(amount·(1−d²/R²) + 2·r) with R = 2 (22,23), 3 (10,11,12,13,14,15,21,33,38,40), 4 (7,8,24,30,37), 5 (39), 6 (9,18), 7 (19), 8 (16,41); none (20,27,32). r = successive random() of a Python-2 random.Random(seed) re-seeded per packet, one draw per footprint cell (solid or not); ceil4 rounds up to 0.25. The server applies the identical per-cell damage (per-cell health) and sends ONE packet with its seed for digs, projectile/deployable blasts and drill bores; kills use 31.75. Snowball sends one reliable zero-damage type-20 event at impact before DestroyEntity(19), allowing the native explosion manager to predict impulse.

BlockManagerStateID 38 · Server → client

Per-cell block health.

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Sent

Per-cell block health. Wire (stock client shared.packet round trip, 2026-09-26, little-endian): u8 38, i32 damaged_n, damaged_n × (i16 x, i16 y, i16 z, u8 remaining×4, u8 b, u8 g, u8 r), i32 user_n, user_n × (i16 x, i16 y, i16 z, u8 health×4), i32 occupied_n, occupied_n × (i16 x, i16 y, i16 z, u8 player_id). receive_block_manager_state MERGES both tables (live-verified): a user row sets user_blocks[cell] (initial health, unscaled); a damaged row sets DamagedBlock(remaining, original_color) (remaining is SCALED by health_multiplier) and darkens the voxel 0.125 per point of damage against the initial health held at that moment — so user rows are always sent in earlier packets (block_state_packets). Sent to observers after 33 builds, after every solid 33 in terrain repair/join catch-up, and by PrefabActionService.reveal_to (called from reveal_world_to after replay_map_mutations) for every recorded non-default health plus every partially damaged cell. Implicit-colour interior voxels get an equivalent user row (remaining/scale) instead of a damaged row so their client-owned colour is not repainted. encode_block_manager_state writes the bytes (the reconstructed server classes do not match).

ServerBlockActionID 39 · Neither (inactive)

Not used: the stock client's handler does nothing with it.

ServerNot usedClientNot used
Technical detail

Server catalogue status: Reversed/unused

Live 2026-09-26: process_packet_server_block_action reads no packet attribute at all; it is a no-op stub in the stock client. Never worth sending.

BlockLineID 40 · Client → server

How the 1.x client actually PLACES blocks (line of blocks).

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

How the 1.x client actually PLACES blocks (line of blocks). Validated (reach, face adjacency, stock) before commit; committed cells are recorded at 9.0. The builder gets the 40 echo (client health 9.0) + PaintBlock pins; observers get 33 per cell + one 38 user-row table.

MinimapBillboardID 41 · Neither (inactive)

Ready on the server but not used by any mode: objective zones and entities already draw their own minimap icons.

ServerNot usedClientDone
Technical detail

Server catalogue status: Reversed/unused

Handler reads entity_id, color, x, y, z, icon_name, tracking (key is never read) → minimap.add_billboard(id, r/255, g/255, b/255, x, y, z, icon_name, tracking); the same id updates in place. Draws a minimap icon plus an in-world marker. icon_name loads png/ui/<name>.png; an unknown name raises IOError in the handler, so server.hud_packets.BILLBOARD_ICONS whitelists the stock minimap/marker art. tracking=1 binds to scene.entities[entity_id] (a CreateEntity id; the icon follows it) and an unknown id is dropped with "invalid billboard tracking target id (KeyError)". Live-verified 2026-09-26 over the wire: static add, tracking on a crate, recolour/move, clear. No mode sends it: Demolition/CTF/TC/MH/Occupation bases already draw through 43, and crates, radar stations, C4, turrets and carried intel/bomb/diamond draw themselves. add_billboard / reveal_billboards / clear_all_billboards.

MinimapBillboardClearID 42 · Neither (inactive)

Ready on the server but not used by any mode (pairs with MinimapBillboard).

ServerNot usedClientDone
Technical detail

Server catalogue status: Reversed/unused

Reads entity_id → minimap.remove_billboard; an unknown id is a silent no-op (live 2026-09-26). server.hud_packets.remove_billboard.

MinimapZoneID 43 · Server → client

CTF team-base zone and icon.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

CTF team-base zone and icon. Six signed-short fields are raw voxel min/max bounds for X/Y/Z; key is native visible_team, and icon 6 is ZONE_ICON_CTF. Sent at mode start and late join.

MinimapZoneClearID 44 · Server → client

Clears a packet-43 zone by its exact six-coordinate identity; used by objective-mode phase/lifecycle cleanup.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Clears a packet-43 zone by its exact six-coordinate identity; used by objective-mode phase/lifecycle cleanup. Retail Python 2 wire vector verified.

StateDataID 45 · Server → client

Per-spawn game/team/lighting snapshot (sent at join, prefix 0x31).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Per-spawn game/team/lighting snapshot (sent at join, prefix 0x31). Prefab and entity catalog lengths are signed little-endian 16-bit counts, not padded bytes; this carries all 373 native UGC items. VIP sends gangster locks and ZOM sends phase-aware team/class locks. team_headcount_type is 6 by default (draws like retail TEAM_SCORE_VALUE); Zombie sends 0 (TEAM_PLAYERS_COUNT_VALUE, team player counts) -- per-mode retail values unrecovered.

Client: Decoded by the session layer (mode, teams, lighting, rules).

KillActionID 46 · Server → client

Broadcast kill/death event. kill_count is the killer's retail multikill chain (kills no more than MULTIKILLMAXTIMEGAP 6 s apart, server/kill_feed.py), not the life streak or the scoreboard kill total. /kill sends…

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Broadcast kill/death event. kill_count is the killer's retail multikill chain (kills no more than MULTIKILLMAXTIMEGAP 6 s apart, server/kill_feed.py), not the life streak or the scoreboard kill total. /kill sends CLASS_CHANGE_KILL: the stock client clears the domination pair on TEAM_CHANGE_KILL.

GenericVoteMessageID 47 · Both ways

Kick and next-map vote overlay open/update/close plus client CAST.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Kick and next-map vote overlay open/update/close plus client CAST. The server sends exact opaque candidate records; the retail client binds the first three to F1/F2/F3. Title, description, and every candidate are exactly repr((string_id, arguments_tuple)): native GenericVotingHUD.decode_string literal-evaluates and indexes both elements, so a raw map name or historical one-item tuple crashes or is misread as KICK_PLAYER.

InitiateKickMessageID 48 · Client → server

Client starts a kick vote → VoteManager (server/voting.py).

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Client starts a kick vote → VoteManager (server/voting.py). The stock client validates nothing; a refused kick is answered with the retail LocalisedMessage(50) to the starter (KICK_DENIED_FOR_SPECTATOR, _REASON_SELF_KICK, _REASON_KICK_HOST, _REASON_VOTE_IN_PROGRESS, _REASON_VOTE_TOO_SOON {seconds}, KICK_NOT_ENOUGH_PLAYERS), which also closes the open kick menu after 0.5 s. Cooldowns per starter: 300 s, 45 s after a cancel (retail MIN_TIME_BETWEEN_[CANCELLED_]KICK_VOTES).

Client: Sent by the vote-kick screen (K).

ChatMessageID 49 · Both ways

Player chat is relayed only as ALL(0) or TEAM(1): a client-supplied SYSTEM(2)/BIG(3) is coerced to ALL, TEAM lines reach only the sender's team, text is cut to the client's MAX_CHAT_MESSAGE_LENGTH (200), each player…

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Player chat is relayed only as ALL(0) or TEAM(1): a client-supplied SYSTEM(2)/BIG(3) is coerced to ALL, TEAM lines reach only the sender's team, text is cut to the client's MAX_CHAT_MESSAGE_LENGTH (200), each player has a token-bucket limit (burst 5, then 1/s; excess dropped silently), and recipients must know the sender's id (known_player_lives), so a dead joiner's chat never names an unknown id. Slash commands and the private /__local_ugc_title bridge are never relayed. Private system replies use type 2; global server/mode announcements use CHAT_BIG type 3 and render at the top of every retail HUD.

LocalisedMessageID 50 · Server → client

Top-screen string-table announcement.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Top-screen string-table announcement. Resolves string_id, optionally resolves every positional parameter as another localization ID (for example TEAM1_COLOR), formats {0}/{1}/{2}, and supports replace-previous behavior. See Broadcast templates below. Server-sent retail ids added 2026-09-28: PLAYER_LEFT {name} (before PlayerLeft 64), COUNTDOWN_FROM_TEN 9..1 after the 10 s cue, private TEAM_SWITCH_WAIT / TEAM_SWITCH_NOT_ALLOWED / TEAM_LOCKED / TEAM_FULL team-change refusals (menu packet 77 and /team) and TEAM_FULL to a joiner the join balance moved. PLAYER_JOINED keeps localise_parameters for the team id, so an identifier-shaped player name gets one trailing space (never a string-table key) and is not translated.

SkyboxDataID 51 · Both ways

Null-terminated retail mesh-environment filename (sent at join, prefix 0x30).

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Null-terminated retail mesh-environment filename (sent at join, prefix 0x30). It comes from the active VXL's validated sidecar skybox_texture/skybox_name; [world].default_skybox is the missing-metadata fallback. C→S only in the Map Creator: the host's UGC Settings choice is validated to a safe skydome basename and relayed to the other editors; ignored elsewhere.

MapEndedID 52 · Server → client

Native full-scene rollover trigger.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Native full-scene rollover trigger. It freezes the compiled GameScene; the compatibility hook opens LoadingMenu and acknowledges readiness with ClientInMenu(110). Only then may the server send a fresh loader handshake over the same authenticated peer. Same-map score presentation deliberately omits it.

ShowGameStatsID 53 · Server → client

Opens GameScene.show_game_statistics(False).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Opens GameScene.show_game_statistics(False). Used only after voted-map preflight and only for maps with a bundled retail level screenshot; custom maps and same-map restarts omit it.

MapDataStartID 54 · Server → client

Opens the native UGC source-map transfer before MapDataValidation.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Opens the native UGC source-map transfer before MapDataValidation.

Client: Map download path.

MapSyncStartID 55 · Server → client

Bare-id map sync start (prefix 0x32).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Bare-id map sync start (prefix 0x32).

Client: Map sync path.

MapDataChunkID 56 · Server → client

Persistent-zlib UGC source VXL chunks produced from 1048-byte input slices.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Persistent-zlib UGC source VXL chunks produced from 1048-byte input slices.

Client: Map download path.

MapSyncChunkID 57 · Server → client

Map content chunk stream (prefix 0x31).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Map content chunk stream (prefix 0x31).

Client: Map sync path.

MapDataEndID 58 · Server → client

Terminates the pre-validation UGC source-map stream.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Terminates the pre-validation UGC source-map stream.

Client: Map download path.

MapSyncEndID 59 · Server → client

Map sync stream terminator.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Map sync stream terminator.

Client: Map sync path.

MapDataValidationID 60 · Both ways

CRC handshake; server replies with OUR file CRC.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

CRC handshake; server replies with OUR file CRC.

Client: Answers with the CRC of the local map so an identical map is not downloaded again (in testing against auto map-sync servers).

PackStartID 61 · Neither (inactive)

Server-pushed resource packs are not planned for now.

ServerNot plannedClientNot planned
Technical detail

Server catalogue status: Planned

Resource-pack transfer start (network buffering).

PackResponseID 62 · Neither (inactive)

Server-pushed resource packs are not planned for now.

ServerNot plannedClientNot planned
Technical detail

Server catalogue status: Planned

Client ack for pack transfer (network buffering).

PackChunkID 63 · Neither (inactive)

Server-pushed resource packs are not planned for now.

ServerNot plannedClientNot planned
Technical detail

Server catalogue status: Planned

Resource-pack chunk (network buffering).

PlayerLeftID 64 · Server → client

Announce a player disconnect, only to in-game clients that were told about that id (a dead joiner was never created on peers); those clients then forget the id.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Announce a player disconnect, only to in-game clients that were told about that id (a dead joiner was never created on peers); those clients then forget the id. Loading clients get it from roster catch-up on first ClientData.

ProgressBarID 65 · Neither (inactive)

Never sent: the stock client crashes the first time it draws this progress bar.

ServerNot usedClientNot used
Technical detail

Server catalogue status: Reversed/blocked

Wire: fixed16 progress, fixed16 rate, colour1, colour2. Every value the fixed16 writer can produce shows the bar (no hide sentinel), and the stock client crashes on the first draw: hud.pyd ProgressBar.draw calls draw.pyd draw_progress_bar with 7 arguments (it takes 5) and the TypeError ends the reactor (live 2026-09-26, byte-identical to Steam). Never sent; set_progress/clear_progress are no-ops. See "ProgressBar (65)" below.

RankUpsID 66 · Neither (inactive)

Never sent by policy: rank-ups and XP are handled by AoSPlay accounts instead.

ServerNot usedClientDone
Technical detail

Server catalogue status: Planned

XP/rank changes at map end (match lifecycle/progression).

Client: The end-screen rank-up timeline can read it, but servers never send it.

GameStatsID 67 · Server → client

End-of-round scoreboard widget (server/scoreboard.py, on_mode_end).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

End-of-round scoreboard widget (server/scoreboard.py, on_mode_end). Per team, up to NOOF_GAME_STATS_TO_SHOW (3, server-only) (player_id, stat_type) rows drawn at random from every one of the 30 retail GAME_STAT_TYPES someone on the team earned this round (selection policy inferred; values are counted round evidence, server/combat_scores.py), listed in stat-id order.

UGCObjectivesID 68 · Server → client

Exact current/min/max/priority rows for shared and target-mode Map Creator requirements.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Exact current/min/max/priority rows for shared and target-mode Map Creator requirements.

RestockID 69 · Server → client

Resource-specific refill.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Resource-specific refill. Type 0 is the full-life spawn/general restock; a physical ammo crate must send type 3. Health (4), block (5), and jetpack (6) crates use their own paths. Sending type 0 for an ammo crate also restores client health.

PickPickupID 70 · Server → client

Authoritative objective pickup; initializes carried tool and burden state.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Authoritative objective pickup; initializes carried tool and burden state. CTF removes the type-16 ground entity and enables the carrier's high-visibility minimap marker.

DropPickupID 71 · Both ways

Client drop request validated against sender/current pickup, then relayed with authoritative identity, type, position, and capped throw velocity.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Client drop request validated against sender/current pickup, then relayed with authoritative identity, type, position, and capped throw velocity. DropPickup clears the carried tool but does not persist ground intel, so CTF follows it with a type-16 CreateEntity at the settled dry-ground position.

ForceShowScoresID 72 · Server → client

Forces the scoreboard open when a round ends, then releases it so the stats screen can show.

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Sent

IDA (stock gameScene.pyd, body 0x101A0300 -> GameScene.force_show_scores): forced=1 opens the ViewScores menu through manager.set_menu, stops movement and locks the menu to the scene; forced=0 (clear_force_show_scores) returns control. LIVE 2026-09-24 (tracer client): the client shows its scores scene (frontend MenuScene host) while staying connected, and the in-place restart's forced=0 drops it straight back into the same GameScene. Sent at every round end and released by the restart (lobby.end_round_scoreboard, default true).

ShowTextMessageID 73 · Server → client

The 'X WINS!' headline on the end-of-round stats screen (one of nine built-in messages, not free text).

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Sent

IDA (body 0x101A0490 -> GameScene.show_text_message(message_id, duration)): only when the active menu is ViewGameStats does it call set_message, i.e. it is the headline of the terminal statistics screen. The nine ids (NEXT_MAP_MESSAGE..TEAM_SCORES_DRAW) select HUD-local strings (END_OF_MAP, TEAM_DEFEAT, BASE_DESTROYED, ZOMBIE_WIN, SURVIVOR_WIN, GAME_DRAWN). Sent right after ShowGameStats(53) on a map rollover (lobby.end_round_headline, default true); inert and therefore not sent on same-map restarts. Not a free-text overlay.

FogColorID 74 · Server → client

Live fog override from the admin command.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Live fog override from the admin command. Initial fog comes from the active map sidecar in StateData; the runtime override also persists into later spawn/rejoin snapshots.

TimeScaleID 75 · Neither (inactive)

Not used: it slows the client's clock while the server keeps 60 Hz, which would desynchronise movement.

ServerNot usedClientNot used
Technical detail

Server catalogue status: Reversed/unused

Handler sets scene.time_scale = packet.scale. Live 2026-09-26 at 0.25 the client kept its 60 Hz loop_count but scene.time advanced at a quarter rate, so its prediction runs slow while the server simulates at full speed. No retail sender; not sent and no admin command.

WeaponReloadID 76 · Both ways

Reload request handled; also sent as reload confirmation.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Reload request handled; also sent as reload confirmation.

ChangeTeamID 77 · Client → server

Client team switch request.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Client team switch request. Refused (private CHAT_SYSTEM notice) within 5 s of the player's previous accepted switch, and, when [teams] auto_balance is on in a mode where players choose teams (no prepare_join_team), when the target side already leads by balance_threshold (same rule as joiners). Mode allows_team_change locks still apply; spectating is never balance-refused.

Client: Sent from the team menu and the in-world '.' key.

ChangeClassID 78 · Client → server

Client class switch request.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Client class switch request.

Client: Sent from the class menu and the in-world ',' key.

LockTeamID 79 · Server → client

Locks a team so nobody can switch into it (sent by Zombie at the outbreak).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

IDA (body 0x1019EB70): sets teams[team_id].locked and refreshes an open SelectTeam/SelectClass/ChangeTeam menu. Zombie sends it at the countdown/outbreak boundary so players already in the scene see the same locks StateData gives joiners.

TeamLockClassID 80 · Server → client

Locks a team's class choice (sent by Zombie during the outbreak).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

IDA (body 0x1019F880): sets teams[team_id].locked_class; the HUD then refuses the class menu (ZOMBIE_OUTBREAK_CLASS_SELECT). Zombie locks the survivor team at the outbreak and unlocks it on round start; the server rejects survivor class changes during the outbreak.

TeamLockScoreID 81 · Server → client

Sets teams[team_id].locked_score; live 2026-09-26 the client then ignores team SetScore(85) rows for that team (player rows still apply).

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Sent

Sets teams[team_id].locked_score; live 2026-09-26 the client then ignores team SetScore(85) rows for that team (player rows still apply). Sent by /lockscore <1 | 2 | all> on | off via server.hud_packets.set_team_lock_score, which also sets Team.locked_score server-side, re-sends the team score on unlock, and replays to joiners (reveal_hud_state). StateData does not carry it.

TeamInfiniteBlocksID 82 · Server → client

Sets teams[team_id].infinite_blocks; the client block, prefab, flare and snow-blower tools then skip their block-count checks (client blockTool/blockToolCommon/prefabTool/tool bytecode).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Sets teams[team_id].infinite_blocks; the client block, prefab, flare and snow-blower tools then skip their block-count checks (client blockTool/blockToolCommon/prefabTool/tool bytecode). Sent by /infiniteblocks <1 | 2 | all> on | off via server.hud_packets.set_team_infinite_blocks (sets Team.infinite_blocks, which the deployable and prefab wallets already honour; replayed to joiners).

TeamMapVisibilityID 83 · Server → client

Not used: radar stations are detected by the client itself, and this packet revealed the wrong team.

ServerNot usedClientDone
Technical detail

Server catalogue status: Unused

Sets the client's teams[team_id].can_see_other_team (a whole-enemy-team minimap reveal, no range). Radar does NOT use it: the stock Minimap detects enemies itself from each team RadarStationEntity (can_detect_player, 250 blocks), verified live 2026-09-26.

DisplayCountdownID 84 · Server → client

HUD round-timer countdown (server/scoreboard.py, seconds remaining; a mode's countdown_seconds_remaining may substitute a phase clock: Zombie pre-outbreak, Demolition build phase).

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Sent

HUD round-timer countdown (server/scoreboard.py, seconds remaining; a mode's countdown_seconds_remaining may substitute a phase clock: Zombie pre-outbreak, Demolition build phase). LIVE-VERIFIED.

SetScoreID 85 · Server → client

Lightweight mid-game team/player score update (HUD).

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Lightweight mid-game team/player score update (HUD). PLAYER rows go only to in-game clients that know that id (known_player_lives); a departed id (forgotten after PlayerLeft) or an id reused by a new joiner is never scored.

UseCommandID 86 · Client → server

Mount/dismount the nearest unoccupied machine-gun entity.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Mount/dismount the nearest unoccupied machine-gun entity.

PlaceMGID 87 · Client → server

Validated type-7 mounted-machine-gun placement; yaw/team/health and join persistence are server-owned.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Validated type-7 mounted-machine-gun placement; yaw/team/health and join persistence are server-owned.

PlaceRocketTurretID 88 · Client → server

Validated Engineer/Rocketeer turret placement and server-owned targeting/rockets.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Validated Engineer/Rocketeer turret placement and server-owned targeting/rockets.

PlaceLandmineID 89 · Client → server

Validated placement, four-second arm, buried proximity detection, and blast.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Validated placement, four-second arm, buried proximity detection, and blast.

PlaceMedPackID 90 · Client → server

Validated type-30 placement, three 25-HP team uses, health/destruction; two-client retail rendering verified.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Validated type-30 placement, three 25-HP team uses, health/destruction; two-client retail rendering verified.

PlaceRadarStationID 91 · Client → server

Validated radar-station placement; a station lasts 45 s and the client reveals enemies within 250 blocks itself.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Validated type-36 placement, native 35-second fuse/life, team minimap reveal, team-change cleanup, and damageable destruction.

PlaceC4ID 92 · Client → server

Validated oriented type-38 placement with owner stock tracking; two-client retail rendering verified.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Validated oriented type-38 placement with owner stock tracking; two-client retail rendering verified.

DetonateC4ID 93 · Client → server

Detonates only the sender's live charges.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Detonates only the sender's live charges.

BlockSuckerPacketID 94 · Both ways

Sanitized remote state relay plus authoritative timed voxel pull/grant.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Sanitized remote state relay plus authoritative timed voxel pull/grant.

DisguisePacketID 95 · Client → server

Loadout/tool-gated disguise state, replicated through WorldUpdate bit 0x02.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Loadout/tool-gated disguise state, replicated through WorldUpdate bit 0x02.

DisableEntityID 96 · Neither (inactive)

Not used: only C4 supports it, and any other entity id would crash the stock handler.

ServerNot usedClientNot used
Technical detail

Server catalogue status: Reversed/unused

Handler: if entity_id in scene.entities: scene.entities[entity_id].disable(). Only C4Entity has disable() (sets enable_explode=False, live 2026-09-26); any other entity type raises AttributeError in the handler. Not sent: server C4 has no inert-but-present state (charges leave with their owner-bound cleanup).

PlaceUGCID 97 · Both ways

Host-only raw-voxel placement/removal for all 19 Game Data items; range, tool, bounds, duplicate, and project-cap checks precede authoritative echo.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Host-only raw-voxel placement/removal for all 19 Game Data items; range, tool, bounds, duplicate, and project-cap checks precede authoritative echo.

InitialUGCBatchID 98 · Server → client

Bounded initial/reconnect replay of persisted UGC objects.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Bounded initial/reconnect replay of persisted UGC objects.

ReqestUGCEntitiesID 99 · Client → server

Retail refresh request; spelling is native.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Retail refresh request; spelling is native. Replays packet 98 plus packet 68 validation.

UGCMessageID 100 · Client → server

Recovered editor control channel (Map Creator only): MAPINFO requests get the preview reply; host MAP_VALIDATION/CONVERT_TO_GAME requests resend objectives and request a checkpoint; late REQUEST_VXL/NO_VXL source-map…

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Recovered editor control channel (Map Creator only): MAPINFO requests get the preview reply; host MAP_VALIDATION/CONVERT_TO_GAME requests resend objectives and request a checkpoint; late REQUEST_VXL/NO_VXL source-map requests are ignored (the launcher answers them before MapDataValidation). The server never sends 100.

UGCMapLoadingFromHostID 101 · Neither (inactive)

Not used on dedicated servers: it only reports Steam-lobby host loading progress.

ServerNot usedClientDone
Technical detail

Server catalogue status: Reversed/unused

Local Steam-lobby host progress packet. Unsafe and unnecessary on dedicated direct connect; packets 54/56/58 provide the source map before validation.

Client: Decoded for Steam-hosted Map Creator sessions.

UGCMapInfoID 102 · Both ways

Optional bounded PNG preview exchange; disk checkpointing stays off the gameplay thread.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Optional bounded PNG preview exchange; disk checkpointing stays off the gameplay thread.

VoiceDataID 103 · Neither (inactive)

Voice chat is out of scope. The packet is decoded safely as raw bytes but never relayed.

ServerNot plannedClientNot planned
Technical detail

Server catalogue status: Planned

Voice-chat audio frames (voice). Out of scope: no handler, never relayed. The compiled codec decodes the payload as UTF-8 (corrupting); protocol/runtime_packets.decode_runtime_packet(103) keeps it as raw bytes for any receive/trace path.

PlaceFlareBlockID 104 · Client → server

Flare tool 22 only; raw voxel-short coordinates, ten-block cost, contact/range validation, and coloured entity type 13 with late-join persistence.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Flare tool 22 only; raw voxel-short coordinates, ten-block cost, contact/range validation, and coloured entity type 13 with late-join persistence. A successful FLARE BLOCK log is this packet, not ordinary BlockLine(40).

SteamSessionTicketID 105 · Client → server

Steam session ticket, read during the connection handshake (not through the normal handler table).

ServerDoneClientDone
Technical detail

Server catalogue status: —

Steam auth ticket; received in handshake (not via register_handler).

Client: Sent during the handshake (empty ticket for offline guests).

TerritoryBaseStateID 106 · Server → client

Territory owner, attacker and capture progress; the capture bar was verified filling in both the retail and the new client.

ServerRetail-verifiedClientDone
Technical detail

Server catalogue status: Sent

Territory owner, attacker, action, and fixed16 capture amount; replayed on join and updated during capture. Retail Python 2 wire vector verified. Actions in TC_DETAIL_NOT_REQUIRED (ENTERING 3, LEAVING 4, CONTENDED 6, UNCONTENDED 7) are only base_index, action (3 bytes): the retail reader stops there, so the long form leaves trailing bytes the client parses as packet 3 (NoDataLeft crash).

DebugDrawID 107 · Neither (inactive)

Development tooling only; never part of normal play.

ServerNot usedClientNot used
Technical detail

Server catalogue status: Planned

Debug draw primitives (dev tooling).

LockToZoneID 108 · Server → client

Six-short native movement clamp used for Demolition's build phase.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Six-short native movement clamp used for Demolition's build phase. Retail Python 2 wire vector verified.

HelpMessageID 109 · Server → client

Localized tutorial HelpPanel rows with the packet's exceptional big-endian float delay.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Localized tutorial HelpPanel rows with the packet's exceptional big-endian float delay. Retail Python 2 wire vector verified.

ClientInMenuID 110 · Client → server

Client reports it's in a menu (handshake/idle gating).

ServerDoneClientDone
Technical detail

Server catalogue status: Handled

Client reports it's in a menu (handshake/idle gating).

Client: Sent when menus open and close; also the spectator admission edge.

PasswordID 111 · Neither (inactive)

Password-protected servers are not available yet.

ServerKnown gapClientKnown gap
Technical detail

Server catalogue status: Planned

Password packet (auth).

PasswordNeededID 112 · Neither (inactive)

Password-protected servers are not available yet.

ServerKnown gapClientKnown gap
Technical detail

Server catalogue status: Planned

Server requests a password (auth).

PasswordProvidedID 113 · Neither (inactive)

Password-protected servers are not available yet.

ServerKnown gapClientKnown gap
Technical detail

Server catalogue status: Planned

Client submits a password (auth).

InitialInfoID 114 · Server → client

First join packet: map filename, checksum, direct per-class movement scales, and a null-terminated texture_skin string.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

First join packet: map filename, checksum, direct per-class movement scales, and a null-terminated texture_skin string. Each movement value is the complete 1/64-rounded authority scale; clients must not divide it by the class baseline. VIP sends mafia; the empty string selects the normal skin.

Client: Decoded by the session layer.

ForceTeamJoinID 115 · Server → client

Map Creator sends team 2/instant 0 after loading so Start opens the native prefab/Game Data selector.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Map Creator sends team 2/instant 0 after loading so Start opens the native prefab/Game Data selector.

Client: Honoured when the class menu opens.

PositionDataID 116 · Client → server

Records the client-reported position and its drift from the authoritative body (diagnostics / tick stats pos=); only the non-default movement_authority = "client" mode lets fresh reports pin the body.

ServerDoneClientNot used
Technical detail

Server catalogue status: Handled

Records the client-reported position and its drift from the authoritative body (diagnostics / tick stats pos=); only the non-default movement_authority = "client" mode lets fresh reports pin the body.

Client: A compatibility/diagnostic report; the native client does not need to send it.

TeamProgressID 117 · Server → client

Flag-controlled numerator/denominator or fixed16-percent objective row; Demolition sends authoritative base health.

ServerDoneClientDone
Technical detail

Server catalogue status: Sent

Flag-controlled numerator/denominator or fixed16-percent objective row; Demolition sends authoritative base health. Retail Python 2 wire vector verified.

SetGroundColorsID 118 · Both ways

Complete UGC terrain/water palette from the host, persisted and replayed to editor guests.

ServerDoneClientDone
Technical detail

Server catalogue status: Handled+Sent

Complete UGC terrain/water palette from the host, persisted and replayed to editor guests.

Game modes

Every retail mode plus the revival-only extras.

Retail modes

Team DeathmatchRetail modes

Live on the official servers. Kill scores, start cue, round clock announcements and the retail class list (six classes, no Rocketeer).

Done
Technical detail

Generic kill score via BaseMode.award_generic_kill_score; TDM Reloading Kill / Defend / Distraction events (50 each); start cue broadcast at every round start; DEFAULT_TEAM_CLASSES order. Native client live pass 2026-09-27 (HUD, scoreboard, death hint, end screen).

Capture the FlagRetail modes

Plays to 5 captures like retail. Intel sits on the real base point, carriers show on the minimap after 30 s, and the carried-intel HUD matches the retail client.

Retail-verified
Technical detail

RULE_CTF_SCORE_TARGET 10 -> 5; intel home on the authored base point; INTEL_MINIMAP_EXPOSURE_TIME 30 s; objectives frozen during the end screen; carried-objective HUD compared live against the retail client (ev/ctf r02/r03).

Classic CTFRetail modes

Classic rules restored: built blocks break in one spade hit, 5-block build reach, classic corpses and carried intel drawn.

In testing
Technical detail

User blocks stored at DEFAULT_BLOCK_HEALTH 5 in classic (server and client), build/paint reach 5, ClassicCorpse.kv6 and back-intel attachment in the native client. No full live pass recorded yet.

Territory ControlRetail modes

Retail announcements, capture bars that fill correctly, capture-point resupply and the retail capture-rate table. Join crash on TC maps fixed.

Retail-verified
Technical detail

TerritoryBaseState(106) short form for actions 3/4/6/7 (fixed a NoDataLeft join crash); capture_amount is the retail 0..100 attacker percentage, verified live on CityOfChicago in both clients. Capture rate per tick follows stock A2550; the exact interpolation is inferred and still wants a retail capture.

Multi-HillRetail modes

Retail hill announcements, separate First to Hill / Claim Hill awards, held-hill resupply and a proper win sting.

Done
Technical detail

MULTIHILL_* LocalisedMessages, BASE_DEPLETED on hill timeout, tie resolution (higher score, exact tie = GAME_DRAWN). Score limit 100 is a server choice (no retail rule recovered).

OccupationRetail modes

Bomb announcements, spawn cues, team-switch credit and bot escorts fixed. Waiting on a full live play-test.

In testing
Technical detail

BASE_OCCUPIED_ATTACK/_DEFEND when a bomb carrier enters the target; restart entity-id hygiene; BranCastle removed from the Occupation rotation (retail marks it invalid).

DemolitionRetail modes

Destroy/repair awards, the siren before the shells, a visible build-phase timer and the airstrike camera focus. Waiting on a full live play-test.

In testing
Technical detail

POIFocus(18) at the airstrike; DisplayCountdown(84) drives the 30 s build phase; Defend Base 100 / Assault Base 50; GreatWall removed from the Demolition rotation (retail marks it invalid).

Diamond MineRetail modes

Diamonds count down their lifetime above their heads, the carried-diamond HUD matches retail, and the map vote opens at 12 of 15 diamonds.

Retail-verified
Technical detail

CreateEntity fuse carries RULE_DIAMOND_LIFETIME; DIA_DIAMONDS_TO_TRIGGER_MAP_VOTE; carry HUD compared live against retail (ev/dia r01).

VIPRetail modes

Sudden death after the VIP falls, 'No respawns!' on screen, VIP score events and the V.I.P HUD.

Done
Technical detail

VIP_SUDDEN_DEATH_* drain with SetHP type 4; NEVER_RESPAWN_TIME 255 in KillAction; RULE_VIP_NOOF_ROUNDS counts sub-rounds played (inferred, VERIFY with a retail capture).

ZombieRetail modes

Three rounds per map, patient-zero spawn protection, team and class locks during the outbreak, and survivor bots that fortify a refuge.

Done
Technical detail

RULE_ZOMBIE_NOOF_ROUNDS restarts on the same map; LockTeam(79)/TeamLockClass(80) at phase boundaries; HeadCount type 0 (team counts) pending a retail capture; zombie bots still cannot climb into refuges.

Revival extras

ArenaRevival extras

Not a retail mode. Respawn, leave, score and restart fixes; mid-round joiners now respawn correctly.

Done
Technical detail

Still uses free-text announcements (no retail strings exist). Native client now keeps sending neutral ClientData while dead so the server sees the scene-ready edge.

TutorialRevival extras

Offline tutorial with the retail help messages.

Done
Technical detail

HelpMessage(109) with its big-endian delay float; map/mode commands are refused inside the isolated tutorial runtime.

Map Creator (UGC)Revival extras

Build maps together in the Map Creator. Hosting and editing work; F10 quick save and the map-preview camera are not in the new client yet.

In testing
Technical detail

UGC packets 97-102 handled; user blocks untracked in UGC like retail; native client has no client-side UGC serializer for F10 quick save and no ScreenshotHud yet.

Features

Social, Steam, accounts, inventory, creator tools and server systems.

Social (AoSPlay)

Friends and friend requestsSocial (AoSPlay)

Accepting or declining a friend request was failing for everyone. The AoSPlay backend fix has been live since 28 September; the client-side retries and tab counts ship with the next client build.

In testing
Technical detail

PostgreSQL 42P08 (inconsistent types for $4) made every accept/decline roll back. Fixed with typed parameters; social tests 17/17. Client retries idempotent writes, keeps buttons usable while reconnecting, shows REQUESTS (n) / INVITES (n) counts. Backend fix deployed to AoSPlay production on 2026-09-28.

Lobby invites and presenceSocial (AoSPlay)

Invites now work while you are already in a lobby (LEAVE + JOIN), offline friends can be invited, and busy lobbies no longer freeze the friends list or evict their owner.

In testing
Technical detail

Sync page reduced to 100 events and resumes at the newest event for new processes; lobby writes refresh presence grace; polls cannot starve behind writes (8 s bound); 1.5 s foreground polling. Verified with a two-identity harness at 1.1-2.0 s latency and 8% injected 504s; real two-player test owed.

Steam

Sign in through SteamSteam

The Steam sign-in button no longer flickers or disappears: it shows 'Connecting to Steam...', then appears, and retries on its own if Steam starts later.

In testing
Technical detail

Non-blocking bridge start with a 20 s bound and 10 s retries; bridge output separated from the retail DLL's breakpad prints; SteamAppId set before loading the DLL; stale HKCU ActiveProcess registration diagnosed (restart Steam). Two-account verification owed.

Steam Join Game and invitesSteam

Friends can use Join Game or Invite to Game from Steam, whether your game is open or closed, and never get sent to 127.0.0.1.

In testing
Technical detail

Presence publishes '+connect steam:<host>' (hosts, Steam joiners) or '+connect host:port' (public dedicated servers); handles GameRichPresenceJoinRequested_t, GameLobbyJoinRequested_t and launch parameters in both runtimes; overlay invite dialog wired. ctest 151/151; live two-account test owed.

Hosting through Steam relaysSteam

Host a match from the client and let friends join through Valve's relay network, with AoSPlay's relay as the fallback.

In testing
Technical detail

SteamNetworkingSockets relay transport; first two-machine PC to Mac joins proven 2026-09-23, then teardown and identity fixes. Needs a fresh two-machine pass with the current build.

Steam stats and achievementsSteam

Statistics are reported under Ace of Spades itself with the retail achievement names. The exact unlock conditions are still being researched.

In testing
Technical detail

Retail achievement names copied, never reported as Spacewar. Full achievement conditions are listed as separate future work in the client roadmap.

Accounts

AoSPlay accounts, guests and XPAccounts

Register, play as a guest, or link Steam. XP and levels are earned on servers approved for trusted stats.

Done
Technical detail

Argon2id passwords, Ed25519 guest installations, Steam OpenID device exchange, one-use server join tickets. XP needs a server registered with its own aos_srv_* token and marked stats_trusted by an administrator.

Inventory and Supply CratesAccounts

Level-up Supply Crates and cosmetic inventory work in the client; receipts, lost-reply recovery and every UI path are still going through release testing.

In testing
Technical detail

Client roadmap release gate: immutable receipts, ownership/revisions, lost mutation replies, fallbacks for missing content, owner/observer cosmetic state.

Creators

Skin StudioCreators

Submit KV6 skins from your account; moderators review them and approved skins appear in the public gallery.

Done
Technical detail

Immutable submissions, version-checked audited review, rarity assignment on approval. See /creators.

Workshop archiveCreators

The preserved Steam Workshop archive is browsable on the site.

Done
Technical detail

See /workshop.

Map Creator in the new clientCreators

Collaborative map building works; quick save (F10), the map-preview camera and some editor panels are not ported yet.

In testing
Technical detail

Release gate: simultaneous editing, host controls, interrupted sessions, save/reopen and published archive integrity.

Playing

Server browser and Quick PlayPlaying

The browser no longer wipes your selection every 10 seconds, remembers your region, and sorts by mode name.

Done
Technical detail

Auto-refresh removed; RECEIVED_N_SERVERS counts every row; region saved in server_region.txt; Quick Play START enabled as soon as a server is chosen.

BotsPlaying

Bots fight and move like players, balance their skill to the humans, jetpack up ledges, and step aside when a human needs a slot.

Done
Technical detail

Known gaps: zombie bots cannot climb into survivor refuges, bots never open a parachute, and the zombie refuge wall has no door.

Server systems

Anti-cheatServer systems

Impossible packets get you kicked; everything else (shot origin, aim, input timing, statistics) is logged for review but does not kick yet.

In testing
Technical detail

enforce_* switches stay off until real-client logs under packet loss are reviewed; statistical report writes logs/anticheat.jsonl and never kicks; bots never exercise these checks.

Conduct: AFK, griefing, names, chatServer systems

AFK warning at 9 minutes and kick at 10, grief points for team damage, look-alike names blocked, chat cannot be forged.

Done
Technical detail

AFK times are our choice (only the ERROR_AFK_TIMEOUT code is retail). NFKC + homoglyph-skeleton name uniqueness; 200-character chat lines with a token bucket.

Team balance and map voteServer systems

Joiners go to the smaller team, drift is repaired between lives, and the map vote only offers maps that fit the lobby and that retail allowed for the mode.

Done
Technical detail

Map pool = the mode's retail playlist filtered by invalid_modes; rotation shuffled once at startup; per-map max_players respected.

Lag compensationServer systems

Your shots are checked against where targets were on your screen. Not yet tested on real internet ping.

In testing
Technical detail

Target hitboxes rewound by about one RTT (max 250 ms); stale/future snapshot claims logged only.

Password-protected serversServer systems

Not available yet.

Known gap
Technical detail

Packets 111-113 need pre-GameScene gating, attempt throttling, constant-time compare and secret-safe logging.

Server-sent resource packsServer systems

Servers cannot push custom content packs to players; not needed to play.

Not planned
Technical detail

Packets 61-63 would need a separate phase machine with caps, checksums, acknowledgements, timeouts and cancellation.

Voice chatServer systems

In-game voice chat is not planned. Use Discord or Steam voice.

Not planned
Technical detail

VoiceData(103) is decoded safely as raw bytes and never relayed.

Platforms

Where the server and the client run today.

BattleSpades server

Windows (x86-64, ARM64)BattleSpades server

Portable server archives; every release must pass the full test suite on all platforms.

Done
Technical detail

release.yml builds and tests six archives and publishes only when every platform passes.

Linux (x86-64, ARM64)BattleSpades server

Runs the official fleet in Europe and the USA, including ARM64 hosts.

Done
Technical detail

Strict float evaluation (-fno-fast-math, -ffp-contract=off) keeps all 2040 retail parity cases bit-exact on Linux and macOS.

macOS (Intel, Apple silicon)BattleSpades server

Portable archives for both Mac architectures.

Done
Technical detail

Same release gate as the other platforms.

BattleSpades client

Windows x64BattleSpades client

Portable builds on the releases page. Point the installer at your own copy of Ace of Spades: Battle Builder.

Done
Technical detail

Retail content is never bundled; each release carries the matching server for Create Match.

macOSBattleSpades client

Builds and has joined matches over Steam relays; not yet a published download.

In testing
Technical detail

macOS 15 deployment target, Metal backend; CI workflow runs on request.

LinuxBattleSpades client

Builds from source; packaged downloads still to come.

In testing
Technical detail

Release gate: clean extracted-package runs on every advertised OS/backend.

Original clients

Original Steam client (aos.exe)Original clients

The unmodified Steam game joins BattleSpades servers. New behaviour is always checked against it.

Retail-verified
Technical detail

Stock-client checks use the Steam aos.exe because the patched dev client masks some bugs. One launch-frame jump correction per jump remains on the unpatched client (an optional patch removes it).

Original non-Steam clientOriginal clients

Also supported; the recommended client-side patches (jump restore, audio guard) apply to it.

Done
Technical detail

client_patches/session_transition_patch.py install_audio_guard fixes a stock rollover freeze.

Retail parity areas

How close each part of the game feels to the original 1.x retail release.

Gameplay

Movement physicsGameplay

Walking, sprinting, jumping, climbing, crouching, wading and jetpacks are a line-for-line copy of the retail physics.

Retail-verified
Technical detail

float32 port of world.pyx; 2040 retail parity cases; class tables match all 18 classes; jump restore patch gives 0 corrections per jump.

Weapon valuesGameplay

Damage, fire rate, reload and range now use the original Steam values, not the modded numbers from an old decompile.

Done
Technical detail

Read by executing the stock aos.pkg weapon classes under Py2.7; per-part damage order (TORSO, HEAD, ARMS, LEGS, LEGS); head and class multipliers apply to the victim; RPG2 6 per life. Range falloff and block penetration have no recovered values.

Explosions, fire and chemical gooGameplay

Blast damage follows the retail curve with line-of-sight rays; molotovs leave burning blocks and chemical bombs leave dissolving green goo.

Done
Technical detail

ExplosionDamageManager D*(R^2-d^2)/R^2 with 0.5/0.3/0.2 rays; goo = entity 31, up to 48 patches, 4 s (timings inferred from block fire).

Block health, damage and prefabsGameplay

Blocks take the same damage and darken the same way as retail, craters match between players, and other players' prefabs no longer create invisible walls.

Retail-verified
Technical detail

One block-health model (map 5, built 9, packet-33 cells 3); exact footprints for all 44 damage types from a live capture; BlockManagerState(38) on join; competitive prefabs committed whole in one tick.

SpawnsGameplay

Smarter anti-camp spawns, retail indoor spawn areas, 3 s spawn protection, and no more being unable to spawn when the base is dug down to the water.

Done
Technical detail

Respawn time is 5 s by choice (retail is 10 s).

Parachute and jetpacksGameplay

Press SPACE in the air to open a parachute; jetpacks burn to empty without snapping you back.

Done
Technical detail

Live pass 2026-09-27. Remaining: a ~0.26-block forward nudge per jetpack burn; bots never deploy a canopy.

Crate airdrops and class gearGameplay

Crates parachute in with the retail sounds, radar stations last 45 s, Miners carry one dynamite, Engineers get the seven stock constructs.

Done
Technical detail

Crate respawn 25 s; drop altitude and fly-by variant inferred.

Presentation

Kill feed, banners and stingersPresentation

Double kills, dominations and revenge show their banners and play their stingers.

Done
Technical detail

Multikill window 6.0 s; domination threshold 4 (convention, the retail value is not recoverable). Live pass 2026-09-27.

Announcements and end screenPresentation

Mode events use the original translated strings, and the end screen shows the scoreboard, then the stats screen with 'X WINS!' and the award rows.

Retail-verified
Technical detail

LocalisedMessage(50) with retail ids; rollover order 72(1) -> 5 s -> flush, 72(0), 53, 73 -> 12 s -> flush, 52; GameStats awards sample all 30 retail stat types. Which chat lane retail used for informational lines is unverified.

Sounds, music and reverbPresentation

Retail-style dynamic reverb, gunshot levels, footsteps, ambience and music fades. Music that never started after joining is fixed.

In testing
Technical detail

Round 4 audio (reverb zones, 0.5 weapon gain, rolloff 0.15, 128 voices, StopMusic fade) not yet play-tested live. Server audio fix: silent flush before every music switch. An in-round music bed is kept on purpose ([audio] mode_start_music).

HUD, menus, vote-kick, class select, key bindingsPresentation

Retail chat colours and big-text queue, localised labels, vote-kick screen on K, class menu with the server's class data, and key names that follow your rebinding.

Done
Technical detail

Still open: machine-gun deploy HUD, 3D labels for turret ammo and radar lifetime, the English 'JOIN ' prefix on the team button.

Graphics RETAIL tierPresentation

New installs start on the RETAIL look (retail lighting, fog and AO). Flare lights do not glow in this tier yet.

Known gap
Technical detail

Compatibility tier shown as RETAIL (decision D1); server fog authoritative in every tier (D2). P3-13: vxl.pyd add_static_light still to recover.

View models, muzzle flash and tracersPresentation

Every weapon is held like retail, the muzzle flash sits on the barrel, and tracers draw one per pellet.

In testing
Technical detail

Holds checked for all 65 tools; per-weapon retail flash offsets; tracers start 16 blocks out. No live side-by-side yet; the deployed MG flash is not ported.

Death cameraPresentation

The retail death, chase and fly cameras: look at your killer, follow them, or fly around while you wait.

In testing
Technical detail

DeathController / ChaseController / FlyController; switches on the killer's streak like retail. Live check owed.

Network

Input and packet lossNetwork

A lost input packet no longer leaves you a frame behind, so play stays smooth on real connections.

Done
Technical detail

ClientData is unsequenced like retail; server refills missing labels (input_gap_fill_limit 8). At ~80 ms and 0.5% loss corrections fell from 145 to 0-1. Remaining: lost button edges cause a few small corrections per minute.

Connection watchdog and join timeoutsNetwork

A stalled server shows the retail 'Disconnect in ...' countdown instead of freezing, and failed joins return to the menu with a clear reason.

In testing
Technical detail

RetailConnectionWatchdog after 300 silent loops; 5 s ENet connect timeout; 30 s no-progress timer; retail disconnect reason table.

Stalls behind lost reliable packetsNetwork

In busy fights a single lost packet can still briefly freeze other players on your screen.

Known gap
Technical detail

ENet head-of-line blocking on the single channel; fixing it needs a second channel, which the stock client does not read.

Map download and late-join terrainNetwork

Joining mid-match shows the current terrain straight away, and maps load with far less server memory.

Done
Technical detail

map_air_catchup_enabled=false (1310 probed cells, 0 mismatches); per-column fill colour cut MayanJungle from 286 MB to 27 MB; native client answers MapDataValidation with the local CRC (in testing).

Where this comes from

Generated, not guessed

The packet rows are generated from the BattleSpades server's protocol catalogue and the packet definitions in the BattleSpades client. Modes, features, platforms and parity areas are curated from the project's parity reports. Everything is stored in one JSON file so it is easy to review and update.

Regenerating the data

With the BattleSpades and BattleSpadesClient checkouts next to this site, edit status-curated.json if needed, then run:

node scripts/generate-implementation-status.mjs
node scripts/generate-implementation-status.mjs --check
node scripts/generate-implementation-status.mjs --validate

Output: src/content/project-status/implementation-status.json (as of 2026-09-28).

Spotted a status that is out of date?

Let us know on Discord.

Join the DiscordBeta status