
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
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.
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.
Round-trip clock/loop_count sync; client 1 tick ahead.
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).
Tool/loadout-gated Miner charge; server creates entity type 10 and owns its fuse/blast.
Technical detail
Server catalogue status: Handled
Tool/loadout-gated Miner charge; server creates entity type 10 and owns its fuse/blast.
30 Hz unreliable position/state feed.
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.
Not used: entities move with ChangeEntity instead, and a truncated batch would crash stock clients.
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.
Buffered client input, applied at matching tick.
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.
Sets a player's HP (spawn/heal/damage feedback).
Technical detail
Server catalogue status: Sent
Sets a player's HP (spawn/heal/damage feedback).
Client fire request.
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.
Validated (paint tool, reach/range, target solidity) before the authoritative color mutation, which is journaled for late joiners.
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.
Remote firearm shot only.
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.
Authoritative player-hit response.
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.
Validated: active normalized tool, cadence, stock, and the reported origin's reach from the authoritative body.
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.
Palette state for block, flare, and Block Cannon tools (5/22/29/48).
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.
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…
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.
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).
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.
Not used: joiners receive the roster through CreatePlayer instead.
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.
Client's join announcement (name/team/class), parsed in handshake.
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.
Server-owned turret/MG target, carrier, ammo, state, and map-pickup position.
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.
Existing-player state changes.
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.
Handler reads only target_x/y/z and (with a local player) activates the GameScene LookAtController aimed at that point.
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.
Removes an entity previously announced to that GameScene.
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.
Visual impact callback for server-authoritative damageable-entity hits.
Technical detail
Server catalogue status: Sent
Visual impact callback for server-authoritative damageable-entity hits.
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.
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.
Registers a map-owned ambient controller.
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.
One-shot positional/UI sound: pickups, round/kill cues, and observer-only block-tool impacts.
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.
Allocates the streaming ambient GameSound registered by packet 22.
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.
Byte-sized loop-id teardown for server-owned PlaySound/PlayAmbientSound streams.
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.
Music track (server/audio.py — last-minute game_ending track at 61s remaining).
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).
Stop the current music track (server/audio.py).
Technical detail
Server catalogue status: Sent
Stop the current music track (server/audio.py).
Spawns a player on clients; also carries the roster.
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.
Sent to the builder after its prefab commits (or is refused), always after the packet-30 echo.
Technical detail
Server catalogue status: Sent
Sent to the builder after its prefab commits (or is refused), always after the packet-30 echo.
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…
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.
UGC Map Creator only (ignored in competitive play).
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.
Single-block place; handled on receive, echoed to the builder only.
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.
Per-block coloured placement for ordinary-build observers, terrain repair, persistent Block Cannon impacts, and MapSync join catch-up.
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).
Not used: the server never holds a block in a pending-build state that other players must be kept out of.
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.
Only valid inside the Map Creator; anywhere else the stock client never sends it, so the server treats it as a forged packet.
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.
Three bytes: player id and effect flag.
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.
The only terrain-removal path.
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.
Per-cell block health.
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).
Not used: the stock client's handler does nothing with it.
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.
How the 1.x client actually PLACES blocks (line of blocks).
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.
Ready on the server but not used by any mode: objective zones and entities already draw their own minimap icons.
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.
Ready on the server but not used by any mode (pairs with MinimapBillboard).
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.
CTF team-base zone and icon.
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.
Clears a packet-43 zone by its exact six-coordinate identity; used by objective-mode phase/lifecycle cleanup.
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.
Per-spawn game/team/lighting snapshot (sent at join, prefix 0x31).
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).
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…
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.
Kick and next-map vote overlay open/update/close plus client CAST.
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.
Client starts a kick vote → VoteManager (server/voting.py).
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).
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…
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.
Top-screen string-table announcement.
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.
Null-terminated retail mesh-environment filename (sent at join, prefix 0x30).
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.
Native full-scene rollover trigger.
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.
Opens GameScene.show_game_statistics(False).
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.
Opens the native UGC source-map transfer before MapDataValidation.
Technical detail
Server catalogue status: Sent
Opens the native UGC source-map transfer before MapDataValidation.
Client: Map download path.
Bare-id map sync start (prefix 0x32).
Technical detail
Server catalogue status: Sent
Bare-id map sync start (prefix 0x32).
Client: Map sync path.
Persistent-zlib UGC source VXL chunks produced from 1048-byte input slices.
Technical detail
Server catalogue status: Sent
Persistent-zlib UGC source VXL chunks produced from 1048-byte input slices.
Client: Map download path.
Map content chunk stream (prefix 0x31).
Technical detail
Server catalogue status: Sent
Map content chunk stream (prefix 0x31).
Client: Map sync path.
Terminates the pre-validation UGC source-map stream.
Technical detail
Server catalogue status: Sent
Terminates the pre-validation UGC source-map stream.
Client: Map download path.
Map sync stream terminator.
Technical detail
Server catalogue status: Sent
Map sync stream terminator.
Client: Map sync path.
CRC handshake; server replies with OUR file CRC.
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).
Server-pushed resource packs are not planned for now.
Technical detail
Server catalogue status: Planned
Resource-pack transfer start (network buffering).
Server-pushed resource packs are not planned for now.
Technical detail
Server catalogue status: Planned
Client ack for pack transfer (network buffering).
Server-pushed resource packs are not planned for now.
Technical detail
Server catalogue status: Planned
Resource-pack chunk (network buffering).
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.
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.
Never sent: the stock client crashes the first time it draws this progress bar.
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.
Never sent by policy: rank-ups and XP are handled by AoSPlay accounts instead.
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.
End-of-round scoreboard widget (server/scoreboard.py, on_mode_end).
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.
Exact current/min/max/priority rows for shared and target-mode Map Creator requirements.
Technical detail
Server catalogue status: Sent
Exact current/min/max/priority rows for shared and target-mode Map Creator requirements.
Resource-specific refill.
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.
Authoritative objective pickup; initializes carried tool and burden state.
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.
Client drop request validated against sender/current pickup, then relayed with authoritative identity, type, position, and capped throw velocity.
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.
Forces the scoreboard open when a round ends, then releases it so the stats screen can show.
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).
The 'X WINS!' headline on the end-of-round stats screen (one of nine built-in messages, not free text).
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.
Live fog override from the admin command.
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.
Not used: it slows the client's clock while the server keeps 60 Hz, which would desynchronise movement.
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.
Reload request handled; also sent as reload confirmation.
Technical detail
Server catalogue status: Handled+Sent
Reload request handled; also sent as reload confirmation.
Client team switch request.
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.
Client class switch request.
Technical detail
Server catalogue status: Handled
Client class switch request.
Client: Sent from the class menu and the in-world ',' key.
Locks a team so nobody can switch into it (sent by Zombie at the outbreak).
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.
Locks a team's class choice (sent by Zombie during the outbreak).
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.
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).
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.
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).
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).
Not used: radar stations are detected by the client itself, and this packet revealed the wrong team.
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.
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).
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.
Lightweight mid-game team/player score update (HUD).
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.
Mount/dismount the nearest unoccupied machine-gun entity.
Technical detail
Server catalogue status: Handled
Mount/dismount the nearest unoccupied machine-gun entity.
Validated type-7 mounted-machine-gun placement; yaw/team/health and join persistence are server-owned.
Technical detail
Server catalogue status: Handled
Validated type-7 mounted-machine-gun placement; yaw/team/health and join persistence are server-owned.
Validated Engineer/Rocketeer turret placement and server-owned targeting/rockets.
Technical detail
Server catalogue status: Handled
Validated Engineer/Rocketeer turret placement and server-owned targeting/rockets.
Validated placement, four-second arm, buried proximity detection, and blast.
Technical detail
Server catalogue status: Handled
Validated placement, four-second arm, buried proximity detection, and blast.
Validated type-30 placement, three 25-HP team uses, health/destruction; two-client retail rendering verified.
Technical detail
Server catalogue status: Handled
Validated type-30 placement, three 25-HP team uses, health/destruction; two-client retail rendering verified.
Validated radar-station placement; a station lasts 45 s and the client reveals enemies within 250 blocks itself.
Technical detail
Server catalogue status: Handled
Validated type-36 placement, native 35-second fuse/life, team minimap reveal, team-change cleanup, and damageable destruction.
Validated oriented type-38 placement with owner stock tracking; two-client retail rendering verified.
Technical detail
Server catalogue status: Handled
Validated oriented type-38 placement with owner stock tracking; two-client retail rendering verified.
Detonates only the sender's live charges.
Technical detail
Server catalogue status: Handled
Detonates only the sender's live charges.
Sanitized remote state relay plus authoritative timed voxel pull/grant.
Technical detail
Server catalogue status: Handled+Sent
Sanitized remote state relay plus authoritative timed voxel pull/grant.
Loadout/tool-gated disguise state, replicated through WorldUpdate bit 0x02.
Technical detail
Server catalogue status: Handled
Loadout/tool-gated disguise state, replicated through WorldUpdate bit 0x02.
Not used: only C4 supports it, and any other entity id would crash the stock handler.
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).
Host-only raw-voxel placement/removal for all 19 Game Data items; range, tool, bounds, duplicate, and project-cap checks precede authoritative echo.
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.
Bounded initial/reconnect replay of persisted UGC objects.
Technical detail
Server catalogue status: Sent
Bounded initial/reconnect replay of persisted UGC objects.
Retail refresh request; spelling is native.
Technical detail
Server catalogue status: Handled
Retail refresh request; spelling is native. Replays packet 98 plus packet 68 validation.
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…
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.
Not used on dedicated servers: it only reports Steam-lobby host loading progress.
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.
Optional bounded PNG preview exchange; disk checkpointing stays off the gameplay thread.
Technical detail
Server catalogue status: Handled+Sent
Optional bounded PNG preview exchange; disk checkpointing stays off the gameplay thread.
Voice chat is out of scope. The packet is decoded safely as raw bytes but never relayed.
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.
Flare tool 22 only; raw voxel-short coordinates, ten-block cost, contact/range validation, and coloured entity type 13 with late-join persistence.
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).
Steam session ticket, read during the connection handshake (not through the normal handler table).
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).
Territory owner, attacker and capture progress; the capture bar was verified filling in both the retail and the new client.
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).
Development tooling only; never part of normal play.
Technical detail
Server catalogue status: Planned
Debug draw primitives (dev tooling).
Six-short native movement clamp used for Demolition's build phase.
Technical detail
Server catalogue status: Sent
Six-short native movement clamp used for Demolition's build phase. Retail Python 2 wire vector verified.
Localized tutorial HelpPanel rows with the packet's exceptional big-endian float delay.
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.
Client reports it's in a menu (handshake/idle gating).
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.
Password-protected servers are not available yet.
Technical detail
Server catalogue status: Planned
Password packet (auth).
Password-protected servers are not available yet.
Technical detail
Server catalogue status: Planned
Server requests a password (auth).
Password-protected servers are not available yet.
Technical detail
Server catalogue status: Planned
Client submits a password (auth).
First join packet: map filename, checksum, direct per-class movement scales, and a null-terminated texture_skin string.
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.
Map Creator sends team 2/instant 0 after loading so Start opens the native prefab/Game Data selector.
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.
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.
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.
Flag-controlled numerator/denominator or fixed16-percent objective row; Demolition sends authoritative base health.
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.
Complete UGC terrain/water palette from the host, persisted and replayed to editor guests.
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
Live on the official servers. Kill scores, start cue, round clock announcements and the retail class list (six classes, no Rocketeer).
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).
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.
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 rules restored: built blocks break in one spade hit, 5-block build reach, classic corpses and carried intel drawn.
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.
Retail announcements, capture bars that fill correctly, capture-point resupply and the retail capture-rate table. Join crash on TC maps fixed.
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.
Retail hill announcements, separate First to Hill / Claim Hill awards, held-hill resupply and a proper win sting.
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).
Bomb announcements, spawn cues, team-switch credit and bot escorts fixed. Waiting on a full live play-test.
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).
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.
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).
Diamonds count down their lifetime above their heads, the carried-diamond HUD matches retail, and the map vote opens at 12 of 15 diamonds.
Technical detail
CreateEntity fuse carries RULE_DIAMOND_LIFETIME; DIA_DIAMONDS_TO_TRIGGER_MAP_VOTE; carry HUD compared live against retail (ev/dia r01).
Sudden death after the VIP falls, 'No respawns!' on screen, VIP score events and the V.I.P HUD.
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).
Three rounds per map, patient-zero spawn protection, team and class locks during the outbreak, and survivor bots that fortify a refuge.
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
Not a retail mode. Respawn, leave, score and restart fixes; mid-round joiners now respawn correctly.
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.
Offline tutorial with the retail help messages.
Technical detail
HelpMessage(109) with its big-endian delay float; map/mode commands are refused inside the isolated tutorial runtime.
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.
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)
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.
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.
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.
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
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.
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.
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.
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.
Host a match from the client and let friends join through Valve's relay network, with AoSPlay's relay as the fallback.
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.
Statistics are reported under Ace of Spades itself with the retail achievement names. The exact unlock conditions are still being researched.
Technical detail
Retail achievement names copied, never reported as Spacewar. Full achievement conditions are listed as separate future work in the client roadmap.
Accounts
Register, play as a guest, or link Steam. XP and levels are earned on servers approved for trusted stats.
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.
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.
Technical detail
Client roadmap release gate: immutable receipts, ownership/revisions, lost mutation replies, fallbacks for missing content, owner/observer cosmetic state.
Creators
Submit KV6 skins from your account; moderators review them and approved skins appear in the public gallery.
Technical detail
Immutable submissions, version-checked audited review, rarity assignment on approval. See /creators.
The preserved Steam Workshop archive is browsable on the site.
Technical detail
See /workshop.
Collaborative map building works; quick save (F10), the map-preview camera and some editor panels are not ported yet.
Technical detail
Release gate: simultaneous editing, host controls, interrupted sessions, save/reopen and published archive integrity.
Playing
The browser no longer wipes your selection every 10 seconds, remembers your region, and sorts by mode name.
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.
Bots fight and move like players, balance their skill to the humans, jetpack up ledges, and step aside when a human needs a slot.
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
Impossible packets get you kicked; everything else (shot origin, aim, input timing, statistics) is logged for review but does not kick yet.
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.
AFK warning at 9 minutes and kick at 10, grief points for team damage, look-alike names blocked, chat cannot be forged.
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.
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.
Technical detail
Map pool = the mode's retail playlist filtered by invalid_modes; rotation shuffled once at startup; per-map max_players respected.
Your shots are checked against where targets were on your screen. Not yet tested on real internet ping.
Technical detail
Target hitboxes rewound by about one RTT (max 250 ms); stale/future snapshot claims logged only.
Not available yet.
Technical detail
Packets 111-113 need pre-GameScene gating, attempt throttling, constant-time compare and secret-safe logging.
Servers cannot push custom content packs to players; not needed to play.
Technical detail
Packets 61-63 would need a separate phase machine with caps, checksums, acknowledgements, timeouts and cancellation.
In-game voice chat is not planned. Use Discord or Steam voice.
Technical detail
VoiceData(103) is decoded safely as raw bytes and never relayed.
Platforms
Where the server and the client run today.
BattleSpades server
Portable server archives; every release must pass the full test suite on all platforms.
Technical detail
release.yml builds and tests six archives and publishes only when every platform passes.
Runs the official fleet in Europe and the USA, including ARM64 hosts.
Technical detail
Strict float evaluation (-fno-fast-math, -ffp-contract=off) keeps all 2040 retail parity cases bit-exact on Linux and macOS.
Portable archives for both Mac architectures.
Technical detail
Same release gate as the other platforms.
BattleSpades client
Portable builds on the releases page. Point the installer at your own copy of Ace of Spades: Battle Builder.
Technical detail
Retail content is never bundled; each release carries the matching server for Create Match.
Builds and has joined matches over Steam relays; not yet a published download.
Technical detail
macOS 15 deployment target, Metal backend; CI workflow runs on request.
Builds from source; packaged downloads still to come.
Technical detail
Release gate: clean extracted-package runs on every advertised OS/backend.
Original clients
The unmodified Steam game joins BattleSpades servers. New behaviour is always checked against it.
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).
Also supported; the recommended client-side patches (jump restore, audio guard) apply to it.
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
Walking, sprinting, jumping, climbing, crouching, wading and jetpacks are a line-for-line copy of the retail physics.
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.
Damage, fire rate, reload and range now use the original Steam values, not the modded numbers from an old decompile.
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.
Blast damage follows the retail curve with line-of-sight rays; molotovs leave burning blocks and chemical bombs leave dissolving green goo.
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).
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.
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.
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.
Technical detail
Respawn time is 5 s by choice (retail is 10 s).
Press SPACE in the air to open a parachute; jetpacks burn to empty without snapping you back.
Technical detail
Live pass 2026-09-27. Remaining: a ~0.26-block forward nudge per jetpack burn; bots never deploy a canopy.
Crates parachute in with the retail sounds, radar stations last 45 s, Miners carry one dynamite, Engineers get the seven stock constructs.
Technical detail
Crate respawn 25 s; drop altitude and fly-by variant inferred.
Presentation
Double kills, dominations and revenge show their banners and play their stingers.
Technical detail
Multikill window 6.0 s; domination threshold 4 (convention, the retail value is not recoverable). Live pass 2026-09-27.
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.
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.
Retail-style dynamic reverb, gunshot levels, footsteps, ambience and music fades. Music that never started after joining is fixed.
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).
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.
Technical detail
Still open: machine-gun deploy HUD, 3D labels for turret ammo and radar lifetime, the English 'JOIN ' prefix on the team button.
New installs start on the RETAIL look (retail lighting, fog and AO). Flare lights do not glow in this tier yet.
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.
Every weapon is held like retail, the muzzle flash sits on the barrel, and tracers draw one per pellet.
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.
The retail death, chase and fly cameras: look at your killer, follow them, or fly around while you wait.
Technical detail
DeathController / ChaseController / FlyController; switches on the killer's streak like retail. Live check owed.
Network
A lost input packet no longer leaves you a frame behind, so play stays smooth on real connections.
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.
A stalled server shows the retail 'Disconnect in ...' countdown instead of freezing, and failed joins return to the menu with a clear reason.
Technical detail
RetailConnectionWatchdog after 300 silent loops; 5 s ENet connect timeout; 30 s no-progress timer; retail disconnect reason table.
In busy fights a single lost packet can still briefly freeze other players on your screen.
Technical detail
ENet head-of-line blocking on the single channel; fixing it needs a second channel, which the stock client does not read.
Joining mid-match shows the current terrain straight away, and maps load with far less server memory.
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).
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).