Un runtime, un protocole, pour faire du jeu un environnement programmable
W3 Runtime est injecté dans le jeu d’origine ; sur le thread du jeu, il capture le monde entier, exécute les commandes et renvoie les résultats. Votre IA n’a qu’à dire « quoi faire », en dialoguant avec lui via un protocole en mémoire partagée versionné.
Les couches basses ignorent l’existence des couches hautes
La couche d’interface ne contient pas une ligne de logique « faut-il attaquer » ; les cerveaux ne s’importent pas entre eux et ne dépendent que du SDK. C’est la condition pour que l’Arène fonctionne : elle n’a besoin que de « couche d’interface + arbitre », et n’importe quel cerveau peut s’y brancher.
Toute la carte, toutes les 50 ms
Le runtime capture l’information en une fois sur le thread du jeu et la pousse en mémoire partagée ; le client la lit directement, sans faire la queue sur le thread du jeu. Une capture prend en médiane 0.5 à 0.9 ms (100 à 120 unités), et le détail des temps est toujours inscrit dans l’en-tête du bloc monde.
Joueurs ×16
Or, bois, nourriture et plafond, total récolté, race
Unités ×1024
Type, propriétaire, coordonnées, PV et mana, ordre et cible en cours, cible réellement attaquée, niveau, expérience et points de compétence, visibilité pour chaque joueur
Détails d’unité ×256
12 compétences (niveau, recharge restante), 8 buffs, 6 cases d’inventaire ; héros en priorité
Table de production ×128
Entraînement / recherche / construction / amélioration : file, durée totale, temps écoulé, blocage éventuel
Objets au sol ×256
Type, position, durabilité ; un événement quand un objet est ramassé ou utilisé
Arbres ×4096
Position et PV des destructibles, rafraîchis toutes les 2 secondes
Carte
Grilles de déplacement / construction en cases de 128, zone jouable, points de départ ; calculées en quelques secondes en début de partie
Temps
Horloge du moteur, heure de jeu (jour/nuit), vitesse de jeu, en partie ou non
Le chemin, c’est le runtime qui le choisit
Récolter, battre en retraite, lancer un sort… « faire agir une unité » emprunte des chemins très différents dans le moteur. Toute cette expérience est intégrée au runtime : vous dites quoi faire, il exécute avec une recette vérifiée et relit dans la même frame l’ordre avant et après, qui sert d’accusé de réception.
- Un lot envoyé en une fois, exécuté dans la même frame
- Un accusé pour chaque commande : code d’état + code de raison du moteur + durée d’exécution
- File Shift, points de passage, constructions enchaînées
- Requêtes : compteurs de technologie, faisabilité, visibilité, or restant, cible d’attaque de l’IA de l’ordinateur
Ce n’est jamais le passage entre processus qui est lent
La lecture et l’écriture en mémoire partagée se comptent en nanosecondes ; lire un instantané prend environ 0.05 ms. L’ancien canal était lent parce que chaque requête devait prendre un verrou unique pour toute la machine, puis attendre le prochain relevé des messages par le jeu (une fois par frame, 16 à 33 ms). Le cerveau de référence passait 93 % de son temps réel à attendre.
- Prendre le mutex partagé par toute la machine
- Poster un message au thread du jeu
- Attendre le prochain relevé des messages par le jeu (une fois par frame)
- Attendre l’événement de fin, libérer le verrou
Avec 6 processus simultanés, le débit plafonne à environ 88 appels/s.
- Chaque client a sa propre voie : un écrivain, un lecteur, sans verrou
- Exécution dans la distribution des événements du thread du jeu (des centaines de fois par seconde) ; sans commande en attente, le coût se limite à quelques comparaisons d’entiers
- 16 commandes par envoi, toutes exécutées dans la même distribution ; chaque vidage dispose d’un budget de 4 ms
- Chaque commande a une échéance : après une pause, les commandes expirées ne sont jamais exécutées
Chaque accusé indique la durée d’exécution de la commande, et l’en-tête de la voie garde la plus lente ainsi que la durée du dernier vidage — on voit immédiatement où ça coince.
| Niveau | Canal | Latence | Utilisateurs | Statut |
|---|---|---|---|---|
| 0 | Instantané poussé + flux d’événements | Environ 0.4 ms pour lire une copie ; nouvelle copie toutes les 50 ms (réglable jusqu’à 16 ms) | Tous les Bots | Vérifié en jeu |
| 1 | Voie rapide | ~1 frame ; médiane de 0.06 ms avec 6 processus, débit d’environ 3000 appels/s | Par défaut dans le SDK | Vérifié en jeu |
| 2 | Canal de contrôle | 20 ~ 40 ms | Solution de repli, quelques opérations d’interface | Vérifié en jeu |
| 3 | Passerelle (WebSocket / JSON) | niveau 1 + ~1 ms | Tout langage, pages web, LLM (MCP), autre machine | Vérifié en jeu |
Mesuré (instances de test 1.27, 2026-09-23 / 24)
Notes d’ingénierie : en début de partie, des commandes 1000 fois plus lentes pendant 3 minutes
En ajoutant la durée d’exécution à chaque commande, nous avons constaté que pendant les premières minutes, à partir de la deuxième commande d’un lot, chacune prenait 4 à 10 ms, avant de tomber brusquement à quelques microsecondes vers 180 secondes de jeu. L’échantillonneur du thread du jeu intégré au runtime a capturé 1700 échantillons, dont 93 % dans la fonction de journalisation du runtime lui-même — chaque ligne ouvrait puis fermait le fichier de journal de façon synchrone, et en début de partie chaque ordre du moteur produit une ligne. Avec le journal de débogage désactivé par défaut et une écriture asynchrone, une commande à la 14e seconde de jeu ne prend plus que 4 à 8 µs.
Nous croyons aux chiffres mesurés, pas aux suppositions.
Chaque voie a un rôle
Le contrôle de propriété et le filtrage selon la vision se font dans le runtime — la couche d’exécution du moteur ne vérifie pas elle-même à qui appartient une unité, il faut donc le faire ici. Pour faire s’affronter deux IA, on ouvre deux voies player dans la même partie.
| Rôle | Voit | Peut |
|---|---|---|
| player | Tout son camp + ennemis et neutres dans la vision (mode équitable) | Commander uniquement ses propres unités Joueurs, votre Bot |
| observer | Toute la carte | Aucun ordre ; requêtes, contrôle de la caméra, lecture du HUD Réalisation, commentaire, analyse |
Ne pas deviner, ne pas planter
Les versions 1.24 à 1.28 partagent la même structure de moteur, idéale pour « un runtime + plusieurs profils ». À partir de la 1.29, comme pour Reforged, il s’agit d’un autre moteur, sans promesse de compatibilité automatique.
- Identification Lire la ressource de version et le hash du Game.dll pour choisir le profil
- Table de symboles Les recettes référencent des noms de symboles, pas des valeurs numériques ; chaque symbole porte sa convention d’appel et la forme de ses paramètres
- Repli par signatures Sur une version inconnue, recherche des motifs d’octets en début de fonction ; utilisé seulement si une correspondance unique est trouvée
- Autotest au démarrage Chaque symbole est vérifié sans effet de bord ; les capacités qui échouent sont marquées indisponibles
- Liste des capacités Si le SDK voit qu’une capacité est indisponible, l’appel lève une erreur explicite au lieu de renvoyer 0 en silence
Le bug d’un Bot ne doit pas faire planter toute la partie
C’est aussi pourquoi chaque IA tourne dans son propre processus : dans le même processus, un pointeur nul fait planter toute la partie ; si un processus séparé plante, seul son camp s’arrête.
| Mécanisme | Méthode | Statut |
|---|---|---|
| Ne pas faire tomber le jeu | Tous les appels au jeu sont protégés contre les exceptions ; budget de 4 ms par vidage (minuterie haute précision) | Fait |
| Clients isolés | Une voie et un quota par client ; un client bloqué ne gêne pas les autres | Fait |
| Pas d’exécution après expiration | Chaque commande a une échéance ; une fois expirée, elle est marquée mais pas exécutée, et n’est pas rejouée après une pause | Fait |
| Observabilité | Durée d’exécution de chaque commande ; détail des temps de chaque capture dans l’en-tête du bloc monde | Fait |
| Reconnexion | Le SDK se reconnecte automatiquement lors d’un changement de partie ou de processus | Partiel |
| Disjoncteur par capacité | Une capacité en panne N fois de suite → marquée indisponible, événement émis ; les autres continuent normalement | Prévu |
Vos propres créations, dans le jeu
Les commandes sémantiques permettent à une IA de jouer comme un joueur ; la couche d’extension vous permet de changer ce que les joueurs voient et vivent — dessiner votre propre interface cliquable, appeler toutes les fonctions des créateurs de cartes. Les deux approches se distinguent selon qu’elles sont sûres ou non en multijoueur.
- Zones de texte, panneaux, barres de progression, images, cercles qui épousent le terrain, itinéraires fléchés
- Ancrés à une unité, à des coordonnées du monde ou à une position à l’écran ; polices CJK, coins arrondis, transparence, n’importe quelle couleur
- Dessinés par le runtime lui-même, sans créer d’objet de jeu ni modifier l’état du jeu
- Une ligne de Python par élément, ou via HTTP, ou en écrivant directement la mémoire partagée
- Boutons et cartes de choix cliquables, surbrillance au survol ; dessinés sous le curseur de la souris, le jeu ne reçoit pas le clic qui les touche
- Créer des unités, modifier des caractéristiques, effets, panneaux, boîtes de dialogue, sons, caméra, brouillard, météo…
- Console Farsight, ligne de commande, HTTP, Python — la même syntaxe de script partout
- Les effets courants en une ligne chacun : texte flottant, liaisons, cercles de portée, dialogue avec portrait, filtres plein écran
- En multijoueur, seules les fonctions en lecture seule sont autorisées, pour éviter les désynchronisations
| Canevas | Fonctions d’affichage JASS | |
|---|---|---|
| Qui dessine | Le runtime | Le jeu lui-même |
| Multijoueur | Sûr : dessiné uniquement sur votre écran | Solo uniquement |
| Style | Libre : polices, coins arrondis, transparence, images | Style natif du jeu |
| Suit un objet | Unités / coordonnées du monde / position à l’écran | Selon la fonction |
| Coût | 0.2 ~ 0.35 ms par frame | Environ 13 ms par appel |
Ce que fait le joueur arrive directement dans le flux d’événements
Le runtime rapporte directement les actions du joueur : quel bouton dessiné il a cliqué, quel raccourci clavier il a pressé, où il a cliqué au sol, ce qu’il a sélectionné, quel sort il a lancé, ce qu’il a tapé dans le chat. Les événements de déclencheur propres au jeu (entrée dans une zone, boutons de boîte de dialogue, touches fléchées) peuvent toujours se brancher par un déclencheur vide : n’enregistrer que l’événement, sans condition ni action, puis compter combien de fois il s’est exécuté.