Coordination d'équipe¶
Le paquet ai/src/team/ fournit une couche de communication chiffrée entre
drones d'une même équipe, ainsi qu'un mécanisme de leurres pour induire les
adversaires en erreur. Tout passe par la commande publique Broadcast du
serveur.
Couche implémentée et testée, mais NON branchée
L'ensemble du paquet team/ est entièrement implémenté et couvert par des
tests, mais aucun code du réseau ou de la stratégie ne l'appelle : ces
modules ne sont référencés qu'entre eux. Concrètement, l'IA n'émet ni ne
déchiffre aujourd'hui le moindre message d'équipe (le parseur de broadcast
entrant est lui-même un stub, voir Couche protocole). Voir
Limitations connues.
Pourquoi chiffrer ?¶
Broadcast est public : tout joueur à portée — y compris les équipes
adverses — reçoit le message. Pour coordonner ses drones sans renseigner
l'ennemi, l'équipe encode ses messages de façon illisible pour qui n'a pas la
clé partagée. Les leurres, à l'inverse, sont volontairement en clair pour
détourner l'attention adverse.
Ce n'est pas de la vraie cryptographie
Le chiffrement repose sur un simple XOR à clé répétée (style Vigenère sur les
octets). L'objectif est uniquement de rendre le trafic illisible pour des IA
adverses naïves, pas de résister à une cryptanalyse. C'est intentionnel
et suffisant dans le contexte du jeu (ai/src/team/crypto.py:12-16).
crypto.py — primitives de chiffrement¶
ai/src/team/crypto.py transforme une chaîne en un jeton ASCII sur une seule
ligne, transmissible via Broadcast (qui exige du texte imprimable sans
\n) :
| Fonction | Rôle |
|---|---|
xor_cipher(data, key) (ai/src/team/crypto.py:24) |
XOR octet à octet, clé répétée cycliquement. Involutive : xor_cipher(xor_cipher(d, k), k) == d, donc chiffre et déchiffre. Lève ValueError si la clé est vide. |
encrypt(plaintext, key) (ai/src/team/crypto.py:45) |
texte → UTF-8 → XOR → base64 → jeton ASCII. |
decrypt(token, key) (ai/src/team/crypto.py:60) |
inverse ; renvoie None si le jeton n'est pas du base64 valide ou si le résultat n'est pas de l'UTF-8 décodable. |
Pourquoi decrypt renvoie None plutôt que de lever ?
Recevoir un message d'une équipe adverse est le cas normal : il échoue au
déchiffrement. Renvoyer None permet de l'ignorer silencieusement au
lieu de faire planter le drone (ai/src/team/crypto.py:75-83).
protocol.py — messages d'équipe typés¶
ai/src/team/protocol.py empile au-dessus de crypto une couche de messages
typés. Avant chiffrement, un message a la forme ZPY|TYPE|payload :
- la clé d'équipe
TEAM_KEY = "ZPY"sert de préfixe magique (ai/src/team/protocol.py:17) ; - le séparateur est
|(ai/src/team/protocol.py:19) ; - le type provient de l'énumération
TeamMessageType(ai/src/team/protocol.py:22) :HERE_LVL,COME_LVL,STONES_NEED,LEVEL_UP_OK,ABORT,DECOY.
À quoi sert le préfixe ZPY ?
Après déchiffrement, du bruit adverse peut par hasard donner un texte
imprimable. Le préfixe ZPY permet de distinguer un vrai message d'équipe
d'un faux positif : sans ce préfixe, le message est rejeté.
Les deux fonctions sont tolérantes aux pannes (fail-soft) :
| Fonction | Rôle |
|---|---|
encode_message(type, payload, key) (ai/src/team/protocol.py:33) |
construit "ZPY|TYPE|payload" puis le chiffre → jeton prêt pour Broadcast. |
decode_message(token, key) (ai/src/team/protocol.py:52) |
déchiffre et valide : renvoie (type, payload) ou None si le déchiffrement échoue, si le préfixe est absent, si l'arité est mauvaise, ou si le type est inconnu. |
decoy.py — leurres en clair¶
ai/src/team/decoy.py fabrique des messages-appâts : des broadcasts
plausibles mais non chiffrés et sans préfixe d'équipe. Les alliés les
rejettent systématiquement (ils échouent à decode_message), mais un adversaire
naïf scrutant le trafic public peut gaspiller des ressources à y réagir
(ai/src/team/decoy.py:1-7).
make_decoy_broadcast(seed=None) (ai/src/team/decoy.py:27) choisit un gabarit
(_DECOY_TEMPLATES) et une ressource (STONES + ["food"]) de façon
déterministe : via seed si fourni, sinon via un compteur interne qui fait
varier la sortie d'un appel à l'autre. Exemples produits : need linemate at 3,
regroup 5 for sibur…
flowchart LR
subgraph Allies["Alliés (clé 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["(type, payload) valide"]
end
D["make_decoy_broadcast<br/>(texte en clair)"] --> BC
BC --> ENNEMI["Adversaire naïf<br/>(peut réagir au leurre)"]
DEC -.->|leurre rejeté| REJ["None (ignoré)"]
Documentation détaillée par module
Des notes par module existent dans le dépôt : ai/docs/team/CRYPTO.md,
ai/docs/team/PROTOCOL.md et ai/docs/team/DECOY.md.
Pour aller plus loin¶
- Couche protocole — le parseur de broadcast entrant
(
parse_broadcast) est un stub : tant qu'il n'est pas implémenté, les messages d'équipe reçus ne peuvent pas être décodés. - Stratégie (FSM) — où des messages d'équipe pourraient un jour
déclencher des transitions (
COME_LVL,STONES_NEED…). - Limitations connues — l'inventaire des morceaux non branchés.