Team coordination¶
The ai/src/team/ package provides a layer for encrypted communication
between drones of the same team, plus a decoy mechanism to mislead
opponents. Everything goes through the server's public Broadcast command.
Layer implemented and tested, but NOT wired up
The entire team/ package is fully implemented and covered by tests, but
no network or strategy code calls it: these modules are only referenced
by one another. In practice, the AI currently neither emits nor decrypts any
team message (the inbound broadcast parser is itself a stub, see
Protocol layer). See Known limitations.
Why encrypt?¶
Broadcast is public: every player in range — including opposing teams —
receives the message. To coordinate its drones without informing the enemy, the
team encodes its messages so they are unreadable to anyone without the shared
key. Decoys, conversely, are deliberately in the clear to divert the
opponent's attention.
This is not real cryptography
Encryption relies on a simple repeated-key XOR (Vigenère-style over bytes).
The goal is only to make traffic unreadable to naive opposing AIs, not to
resist cryptanalysis. This is intentional and sufficient in the game's
context (ai/src/team/crypto.py:12-16).
crypto.py — encryption primitives¶
ai/src/team/crypto.py turns a string into a single-line ASCII token
transmissible via Broadcast (which requires printable text with no \n):
| Function | Role |
|---|---|
xor_cipher(data, key) (ai/src/team/crypto.py:24) |
byte-by-byte XOR, key repeated cyclically. Involutive: xor_cipher(xor_cipher(d, k), k) == d, so it both encrypts and decrypts. Raises ValueError if the key is empty. |
encrypt(plaintext, key) (ai/src/team/crypto.py:45) |
text → UTF-8 → XOR → base64 → ASCII token. |
decrypt(token, key) (ai/src/team/crypto.py:60) |
inverse; returns None if the token is not valid base64 or if the result is not decodable UTF-8. |
Why does decrypt return None instead of raising?
Receiving a message from an opposing team is the normal case: it fails
decryption. Returning None lets it be silently ignored instead of
crashing the drone (ai/src/team/crypto.py:75-83).
protocol.py — typed team messages¶
ai/src/team/protocol.py stacks a layer of typed messages on top of
crypto. Before encryption, a message has the form ZPY|TYPE|payload:
- the team key
TEAM_KEY = "ZPY"serves as a magic prefix (ai/src/team/protocol.py:17); - the separator is
|(ai/src/team/protocol.py:19); - the type comes from the
TeamMessageTypeenum (ai/src/team/protocol.py:22):HERE_LVL,COME_LVL,STONES_NEED,LEVEL_UP_OK,ABORT,DECOY.
What is the ZPY prefix for?
After decryption, opposing noise may by chance decode into printable text.
The ZPY prefix distinguishes a genuine team message from such a false
positive: without the prefix, the message is rejected.
Both functions are fail-soft:
| Function | Role |
|---|---|
encode_message(type, payload, key) (ai/src/team/protocol.py:33) |
builds "ZPY|TYPE|payload" then encrypts it → token ready for Broadcast. |
decode_message(token, key) (ai/src/team/protocol.py:52) |
decrypts and validates: returns (type, payload) or None if decryption fails, the prefix is absent, the arity is wrong, or the type is unknown. |
decoy.py — plaintext lures¶
ai/src/team/decoy.py builds lure messages: plausible broadcasts that are
deliberately unencrypted and carry no team prefix. Allies always reject them
(they fail decode_message), but a naive adversary scanning the public traffic
may waste resources reacting to them (ai/src/team/decoy.py:1-7).
make_decoy_broadcast(seed=None) (ai/src/team/decoy.py:27) picks a template
(_DECOY_TEMPLATES) and a resource (STONES + ["food"]) deterministically:
via seed if provided, otherwise via an internal counter that varies the output
from one call to the next. Example output: need linemate at 3,
regroup 5 for sibur…
flowchart LR
subgraph Allies["Allies (key ZPY)"]
E["encode_message<br/>ZPY|TYPE|payload"] --> ENC["crypto.encrypt<br/>(XOR + base64)"]
ENC --> BC["Broadcast (public)"]
BC --> DEC["crypto.decrypt + decode_message"]
DEC --> OK["valid (type, payload)"]
end
D["make_decoy_broadcast<br/>(plaintext)"] --> BC
BC --> ENEMY["Naive adversary<br/>(may react to the lure)"]
DEC -.->|lure rejected| REJ["None (ignored)"]
Per-module detailed docs
Per-module notes exist in the repository: ai/docs/team/CRYPTO.md,
ai/docs/team/PROTOCOL.md and ai/docs/team/DECOY.md.
Going further¶
- Protocol layer — the inbound broadcast parser
(
parse_broadcast) is a stub: until it is implemented, received team messages cannot be decoded. - Strategy (FSM) — where team messages could one day trigger
transitions (
COME_LVL,STONES_NEED…). - Known limitations — the inventory of unwired pieces.