Passerelle
Passerelle WebSocket / JSON : les interfaces publiques que le SDK Python peut appeler le sont aussi depuis JS, C#, Go, Rust, une page web ou un programme sur une autre machine. Trois rôles, un client JS et une page de démonstration dans le navigateur fournis ; la latence est celle de la voie rapide, plus environ 1 ms.
La passerelle enveloppe la voie rapide et l’état poussé en WebSocket / JSON. Les interfaces publiques du catalogue de l’API que le SDK Python peut appeler sont aussi accessibles depuis JS, C#, Go, Rust, une page web, un programme sur une autre machine ou un LLM, avec les mêmes noms de méthodes et les mêmes paramètres. La latence est celle de la voie rapide, plus environ 1 ms.
Le plus simple : page d’accueil de Farsight, « Centre de contrôle » → Passerelle → Démarrer (arrêter, redémarrer, consulter les journaux et ouvrir la page de démonstration se font aussi depuis cette carte). En ligne de commande :
python gateway/server.py # ws://127.0.0.1:8870/ws (port défini par ports.gateway dans openwar3.json)
python gateway/server.py --open # idem, puis ouvre la page de démonstration http://127.0.0.1:8870/demo dès que le port écoute
python gateway/server.py --host 0.0.0.0 # pour le réseau local : jeton exigé automatiquement (bin/gateway/token.txt)
python gateway/server.py --allow-origin http://localhost:5173 # pour que votre propre page web puisse aussi se connecter
Connexion et rôles
Adresse de connexion : ws://127.0.0.1:8870/ws?inst=9&role=dev (pid= peut remplacer inst= ; si un jeton est exigé, ajoutez &token=).
| Rôle | Peut appeler | Idéal pour |
|---|---|---|
dev | Tout : observation, commandes, contrôle du jeu, bac à sable (JASS qui modifie le monde), dessin d’interface | Outils locaux, mods de jeu, compagnons |
player (avec &player=N) | Observation, commandement des unités du joueur N, dessin d’interface ; mode équitable par défaut : ne voit que ce qui est dans la vision du joueur N (&fair=0 pour le désactiver) | Un Bot ou un LLM qui joue à la place d’un joueur donné |
observer | Lecture seule (le runtime rejette directement ses commandes) | Spectateurs, commentaire, collecte de données |
player n’a pas accès : au contrôle du jeu (terminer la partie, changer la vitesse, mettre en pause), à players et enemy_ai_plan, qui dévoilent le jeu caché des autres, à canvas.image, qui ferait ouvrir un fichier local au processus du jeu, ni à JASS. Les requêtes qui prennent un numéro de joueur, comme resources, tech ou stats, ne portent que sur lui-même.
Une connexion = une session, qui occupe une voie rapide (le runtime en a 16 au total). La passerelle accepte au plus 12 sessions simultanées, pour en laisser quelques-unes aux Bots, aux mods et à Farsight. À la déconnexion, seuls les éléments dessinés et les raccourcis clavier de cette session sont retirés ; ce que d’autres programmes ont dessiné reste en place.
Messages
Une fois connecté, vous recevez d’abord hello : version du protocole, rôle, identifiant du processus du jeu, liste des méthodes que ce rôle peut appeler. Ensuite, chaque requête porte un id, et la réponse porte le même id :
→ {"id": 1, "op": "call", "method": "units", "args": ["me"]}
← {"id": 1, "ok": true, "result": [{"addr": 123456, "type": "hpea", "owner": 0, "x": -4480, "y": 2120, "hp": 220, "hp_max": 220}]}
→ {"id": 2, "op": "call", "method": "move", "args": [[{"unit": 123456}], 100, 200]}
→ {"id": 3, "op": "call", "method": "ui.button", "args": ["shop", "Acheter une potion"], "kwargs": {"screen": [40, 300]}}
→ {"id": 4, "op": "subscribe", "state": {"hz": 4, "units": "mine"}, "events": true}
← {"type": "state", ...} {"type": "events", ...} puis poussés en continu
→ {"id": 5, "op": "overview"} la partie en une page : ressources, effectifs par type, héros, ennemis visibles, production
→ {"id": 6, "op": "jass", "code": "call PingMinimapEx(0, 0, 3, 255, 0, 0, false)"} réservé à dev
→ {"id": 7, "op": "api"} catalogue des méthodes (il y a aussi ping / unsubscribe)
- Paramètre d’unité :
{"unit": adresse}, l’adresse étant leaddrdu JSON de l’unité ; on peut y ajouter"handle": [lo, hi]pour vérifier que cette adresse n’a pas été réutilisée par une autre unité. - Noms de méthode : les méthodes publiques de Game, plus
ui.*(button / choice / toast / hotkey / mouse / cursor…),canvas.*(text / panel / bar / image / circle / path / remove…) etjass.<nom_de_fonction>(réservé à dev). - Un client distant ne peut pas transmettre de fonction de rappel : les clics et les raccourcis clavier arrivent par les événements poussés, et l’événement
ui.clickportekey. Voir Interface et entrées. - Une erreur dans un appel ne concerne que cet appel (
ok: falsepluserror) ; la connexion reste ouverte ; il en va de même si ce que vous envoyez n’est pas du JSON. - Les champs JSON des événements sont les mêmes que dans le protocole W3P, avec en plus des champs pratiques (
spell,key,text,chat,button,player,mods).
HTTP fonctionne aussi, pratique pour des appels ponctuels et pour curl : GET /api?role=player liste le catalogue des méthodes, POST /call avec inst, role, method, args, kwargs effectue un appel. /call réutilise les sessions : si le jeu est relancé et change de processus, une nouvelle session est ouverte automatiquement ; celles inactives depuis 10 minutes sont fermées.
Clients
JS (navigateur ou Node 22+, sans dépendance) : gateway/clients/js/openwar3.mjs
import { OpenWar3, unit } from "./openwar3.mjs";
const ow = new OpenWar3("ws://127.0.0.1:8870/ws", { inst: 9, role: "dev" });
await ow.connect();
const mine = await ow.api.units("me");
await ow.api.move(mine.slice(0, 3).map(unit), 100, 200);
await ow.api.ui.button("hi", "Cliquez-moi", { screen: [40, 300] }); // dernier objet simple = arguments nommés
ow.on("event:ui.click", (e) => console.log("Clic sur", e.key));
await ow.subscribe({ state: { hz: 2, units: "mine" }, events: true });
Node 20 / 21 nécessite --experimental-websocket. Exemple complet dans gateway/clients/js/example.mjs.
Page de démonstration dans le navigateur http://127.0.0.1:8870/demo : la partie, la liste de nos unités, un bouton à poser dans le jeu, le flux d’événements — tout sur une seule page.
Autres langages : n’importe quelle bibliothèque WebSocket + le JSON ci-dessus suffisent, sans toucher à la mémoire partagée.
LLM : utilisez directement le serveur MCP, qui fournit les opérations courantes sous forme d’outils prêts à l’emploi.
Mesures
2026-09-25, vérification point par point sur une vraie partie, 16/16 (9 pour la passerelle + 7 pour MCP) : poignée de main (rôle dev, 121 méthodes), units('me'), la partie en une page, notification à l’écran, pose d’un bouton ; après abonnement, clic sur ce bouton dans le jeu → ui.click poussé au client ; JASS ; une unité invalide ne produit d’erreur que pour cet appel ; HTTP /call (rôle observer).
Le client JS (Node) et la page de démonstration ont aussi été testés : le bouton posé depuis la page web a été cliqué dans le jeu, et le journal d’événements de la page a reçu ui.click.
Sécurité
- Par défaut, la passerelle n’écoute qu’en local sur
127.0.0.1, sans jeton (comme Farsight). Si--hostn’est pas une adresse locale, un jeton est exigé automatiquement ;--authl’exige aussi en local. - Les autres sites ouverts dans le navigateur ne peuvent pas se connecter : toute connexion lancée par un navigateur porte son origine (
Origin), et la passerelle n’accepte que sa propre page de démonstration et les adresses passées à--allow-origin; les programmes comme Python, Node ou curl n’envoient pas d’origine et se connectent normalement. Quand elle n’écoute qu’en local, elle vérifie aussiHost, ce qui bloque les attaques consistant à faire résoudre un nom de domaine externe vers la machine locale. - Le rôle est déclaré par le client à la connexion : en mode local, c’est une convention, pas une frontière de sécurité. Sur l’Arène, c’est le processus arbitre qui décidera qui reçoit quel rôle : voir Arène.