Plattform

Eine Runtime, ein Protokoll – das Spiel als programmierbare Umgebung

Die W3 Runtime wird ins Originalspiel injiziert, erfasst auf dem Spiel-Thread die ganze Welt, führt Befehle aus und meldet Ergebnisse. Deine KI sagt nur, was passieren soll, und spricht über ein versioniertes Shared-Memory-Protokoll mit ihr.

Schichten

Untere Schichten wissen nichts von oberen

In der API-Schicht steht keine einzige Zeile „Soll ich angreifen?“-Logik; Brains importieren sich nicht gegenseitig und hängen nur vom SDK ab. Das ist die Voraussetzung für die Arena: Die Plattform braucht nur „API-Schicht + Schiedsrichter“, und jedes Brain lässt sich einstecken.

Dein Agent / Bot
Jedes Modell, jede Sprache
Claude CodeCursorChatGPTQwen lokalPython-BotReferenz-KI
Entscheidung
import openwar3 · oder WebSocket-/JSON-Gateway · MCP
OpenWar3 SDK
Python · openwar3
Game / BotSnapshot w3worldSchnellspur w3fastcombat Kampfberechnungpathing Wegfindungw3claim Arbitrierungcanvas Overlayjass Kanalschemes Schemata
API
Welt-Push alle 50 ms · Befehle greifen in ca. 1 Frame · jeder mit Quittung
W3P-Protokoll v2
Shared Memory · Zero-Copy · versioniert
War3WorldWar3EventsWar3MapWar3TreesWar3Fast BefehlsspurWar3Canvas Overlay
Protokoll
Batch-Ausführung auf dem Spiel-Thread · 4~8 µs pro Befehl · 4 ms Budget pro Durchlauf
W3 Runtime
Läuft auf dem Spiel-Thread
Weltzustand-PublisherEvent-StreamBefehlsausführungAbfragenRechte & PerspektiveCanvas-RenderingJASS-AufrufeVersionsunterstützungHealth & Circuit Breaker
Runtime
War3.exe 1.27 · Originalspiel, keine Datei auf der Festplatte wird verändert
Echtzeit-Informationsschicht

Die ganze Karte, alle 50 ms

Die Runtime erfasst alles in einem Durchgang auf dem Spiel-Thread und pusht es in Shared Memory; Clients lesen direkt und stehen nicht mehr beim Spiel-Thread an. Eine Erfassung dauert im Median 0.5–0.9 ms (100–120 Einheiten); die Zeiten der einzelnen Abschnitte stehen stets im Kopf des Weltblocks.

Spieler ×16

Gold, Holz, Nahrung und Limit, insgesamt gesammelte Menge, Volk

Einheiten ×1024

Typ, Besitzer, Koordinaten, HP/Mana, aktuelle Order und Ziel, tatsächliches Angriffsziel, Stufe/Erfahrung/Fähigkeitspunkte, Sichtbarkeit für jeden Spieler

Einheitendetails ×256

12 Fähigkeiten (Stufe, verbleibende Abklingzeit), 8 Buffs, 6 Inventarplätze; Helden zuerst

Produktionstabelle ×128

Ausbilden / Erforschen / Bauen / Aufwerten: Warteschlange, Gesamtdauer, verstrichene Zeit, ob blockiert

Gegenstände am Boden ×256

Typ, Position, Haltbarkeit; Aufheben oder Verbrauchen löst ein Event aus

Bäume ×4096

Position und HP zerstörbarer Objekte, alle 2 s aktualisiert

Karte

Begehbar-/Bebaubar-Raster (128 pro Zelle), spielbarer Bereich, Startpositionen; wenige Sekunden nach Spielstart berechnet

Zeit

Engine-Spieluhr, Tageszeit (Tag/Nacht), Spielgeschwindigkeit, ob ein Spiel läuft

Event-Stream Ringpuffer mit 8192 Einträgen, jeder mit Sequenznummer – liest du zu langsam und wirst überschrieben, merkt das SDK es
unit.appearedunit.diedunit.removedunit.damagedorder.changedowner.changedhero.levelupitem.appeareditem.removedspell.castselection.changedmessageplayer.leftgame.startedgame.ended damage · Jeder Treffer, auf Engine-Ebenekilled · Jeder Treffer, auf Engine-Ebene production.done · Auch die des Gegners
Semantische Befehle

Welchen Weg ein Befehl nimmt, entscheidet die Runtime

Abbauen, Rückzug, Zaubern … Alles heißt „eine Einheit soll etwas tun“, doch in der Engine nimmt jedes einen anderen Weg. Dieses Wissen steckt komplett in der Runtime: Du sagst, was passieren soll; sie führt es mit einem verifizierten Rezept aus und liest im selben Frame die Order vor und nach dem Befehl als Quittung zurück.

  • Ein Batch pro Übermittlung, Ausführung im selben Frame
  • Jeder Befehl mit Quittung: Statuscode + Reason-Code der Engine + Ausführungszeit
  • Shift-Warteschlange, Wegpunkte, Serienbau
  • Abfragen: Technologiezähler, Machbarkeit, Sichtbarkeit, Restgold, Angriffsziel des Computergegners
Quittungen & Reason-Codes
moveattack_moveattackstopholdpatrolattack_groundgatherrepairbuildbuild_nearbuild_queuetraincancellearncastrallyrevivepick_upuse_itemdrop_itemgive_itemsell_itembuypathcall_to_armspausebatch
Niedrige Latenz

Langsam war nie die Prozessgrenze

Shared-Memory-Zugriffe kosten Nanosekunden, ein Snapshot liest sich in ca. 0.05 ms. Der alte Kanal war langsam, weil jede Anfrage erst ein maschinenweites Lock ergattern und dann warten musste, bis das Spiel das nächste Mal Nachrichten abholt (einmal pro Frame, 16–33 ms). Das Referenz-Brain verbrachte 93 % seiner Wanduhrzeit mit diesem Warten.

Alt · Steuerkanal20–40 ms / Aufruf
  1. Maschinenweiten Mutex ergattern
  2. Eine Nachricht an den Spiel-Thread schicken
  3. Warten, bis das Spiel das nächste Mal Nachrichten abholt (einmal pro Frame)
  4. Auf das Fertig-Event warten, Lock freigeben

Mit 6 Prozessen gleichzeitig blieb der Durchsatz bei ca. 88 Aufrufen/s stecken.

Neu · Schnellspurca. 1 Frame, als Batch
  1. Jeder Client hat eine eigene Spur: ein Schreiber, ein Leser, kein Lock
  2. Ausführung im Event-Dispatch des Spiel-Threads (hunderte Male pro Sekunde); ohne Übermittlung kostet das nur ein paar Integer-Vergleiche
  3. 16 Befehle pro Übermittlung, alle im selben Dispatch ausgeführt; 4 ms Zeitbudget pro Durchlauf
  4. Jeder Befehl mit Deadline: Nach einer Pause werden abgelaufene Befehle nicht mehr ausgeführt

Jede Quittung enthält die Ausführungszeit; der Spurkopf hält den langsamsten Befehl und die Dauer des letzten Durchlaufs fest – wo es hakt, sieht man sofort.

StufeKanalLatenzNutzerStatus
0 Gepushter Snapshot + Event-Stream Lesen ca. 0.4 ms; alle 50 ms ein neuer (bis 16 ms einstellbar) Alle Bots Live verifiziert
1 Schnellspur ca. 1 Frame; Median bei 6 parallelen Prozessen 0.06 ms, Durchsatz ca. 3000/s SDK-Standard Live verifiziert
2 Steuerkanal 20–40 ms Fallback, einige UI-Operationen Live verifiziert
3 Gateway (WebSocket / JSON) Stufe 1 + ca. 1 ms Jede Sprache, Browserseiten, LLMs (MCP), andere Rechner Live verifiziert

Gemessen (1.27-Testinstanz, 2026-09-23 / 24)

Befehlsdurchsatz
Vorher 88
3.000 /s
6 Prozesse parallel
Median-Wartezeit bei Parallelität
Vorher 67 ms
0.06 ms
Alter Steuerkanal → Schnellspur
16 Befehle
Vorher 121 ms
13 ms
Einzeln → als Batch
8 Bewegungsbefehle
Vorher 68~99 ms
6.5~10 ms
with g.batch()
Eine Erfassung des Weltzustands
Vorher 11.8 ms
0.58 ms
Auf dem Spiel-Thread; als lesbar bestätigte Speicherbereiche werden wiederverwendet
Einzelbefehl zu Spielbeginn
Vorher 4~10 ms
4~8 µs
Sampler fand synchrones Logging, jetzt asynchron
Eine Runde des Referenz-Brains
Vorher 0.15~0.56 s
0.02~0.07 s
39-Minuten-Spiel, 0 Aufgabenfehler
Langsamste Runde des Referenz-Brains
Vorher 1.3~3.2 s
0.24~0.42 s
Im ganzen Spiel keine Runde ≥ 2 s

Aus der Praxis: In den ersten 3 Minuten war jeder Befehl 1000-mal langsamer

Nachdem wir jeden Befehl mit einer Zeitmessung versehen hatten, zeigte sich, dass in den ersten Spielminuten ab dem zweiten Befehl eines Batches jeder 4–10 ms brauchte – bis die Zeit bei etwa 180 Spielsekunden plötzlich auf wenige Mikrosekunden fiel. Der eingebaute Sampler der Runtime für den Spiel-Thread sammelte 1700 Samples; 93 % davon lagen in der Log-Funktion der Runtime selbst – jede Zeile öffnete und schloss die Logdatei synchron, und zu Spielbeginn schreibt jede Engine-Order eine Zeile. Seit das Debug-Log standardmäßig aus ist und asynchron geschrieben wird, braucht auch ein Befehl in Sekunde 14 nur 4–8 µs.

Wir glauben gemessenen Zahlen, nicht Vermutungen.

Rechte & Perspektive

Jede Spur hat eine Rolle

Besitzprüfung und Sichtfilterung passieren in der Runtime – die Ausführungsschicht der Engine prüft den Besitz von Einheiten selbst nicht, also muss es hier geschehen. Zwei KIs gegeneinander heißt: zwei player-Spuren im selben Spiel.

RolleSiehtDarf
player Alles Eigene + Gegner und Neutrale in Sicht (Fair-Modus) Nur eigene Einheiten befehligen
Spieler, dein Bot
observer Ganze Karte Keine Befehle; abfragen, Kamera steuern, HUD lesen
Regie, Kommentar, Analyse
Multi-Version-Support · P4

Nicht raten, nicht abstürzen

1.24–1.28 teilen dieselbe Engine-Struktur – ideal für „eine Runtime + mehrere Profile“. Ab 1.29 und bei Reforged ist es eine andere Engine; automatische Kompatibilität wird nicht zugesagt.

  1. Erkennen Versionsressource und Datei-Hash der Game.dll lesen, Profil wählen
  2. Symboltabelle Rezepte referenzieren Symbolnamen, keine Zahlen; jedes Symbol mit Aufrufkonvention und Parameterform
  3. Signatur-Fallback Auf unbekannten Versionen nach Byte-Signaturen am Funktionsanfang scannen; nur bei eindeutigem Treffer verwenden
  4. Selbsttest beim Start Jedes Symbol wird nebenwirkungsfrei geprüft; was durchfällt, wird als nicht verfügbar markiert
  5. Fähigkeitsliste Erkennt das SDK, dass eine Fähigkeit nicht verfügbar ist, wirft der Aufruf einen klaren Fehler, statt stillschweigend 0 zurückzugeben
Hochverfügbarkeit

Ein Bug in einem Bot darf nicht das ganze Spiel abstürzen lassen

Deshalb läuft jede KI in einem eigenen Prozess: Ein Nullzeiger im selben Prozess bringt das ganze Spiel zum Absturz; stürzt ein eigener Prozess ab, fällt nur diese Seite aus.

MechanismusUmsetzungStatus
Spiel nicht ausbremsen Alle Spielaufrufe mit Ausnahmeschutz; 4 ms Zeitbudget pro Durchlauf (hochauflösender Timer) Vorhanden
Clients isoliert Eine Spur und ein eigenes Kontingent pro Client; hängt einer, blockiert er die anderen nicht Vorhanden
Abgelaufenes wird nicht ausgeführt Jeder Befehl trägt eine Deadline; abgelaufene werden nur markiert, nicht ausgeführt, und nach einer Pause nicht nachgeholt Vorhanden
Beobachtbar Ausführungszeit jedes Befehls; Abschnittszeiten jeder Erfassung im Kopf des Weltblocks Vorhanden
Reconnect Das SDK verbindet sich bei neuem Spiel oder neuem Prozess automatisch neu Teilweise
Circuit Breaker pro Fähigkeit Fällt eine Fähigkeit N-mal hintereinander aus → als nicht verfügbar markiert, Event gesendet, alle anderen laufen weiter Geplant
Erweiterungsschicht

Bau deine eigenen Dinge ins Spiel

Semantische Befehle lassen die KI wie ein Spieler handeln; die Erweiterungsschicht verändert, was Spieler sehen und erleben – eigene klickbare Oberflächen zeichnen, alle Funktionen der Kartenautoren aufrufen. Die beiden Wege sind danach getrennt, ob sie im Multiplayer sicher sind.

Canvas Multiplayer-sicher
0.27 ~ 0.34 ms Kosten pro Frame (9 Elemente, ca. 63 Frames/s)
  • Textfelder, Panels, Fortschrittsbalken, Bilder, Kreise auf dem Gelände, Routen mit Pfeilen
  • Folgt Einheiten, Weltkoordinaten oder Bildschirmpositionen; eigene Schriftarten, abgerundete Ecken, Transparenz, beliebige Farben
  • Die Runtime zeichnet selbst – keine Spielobjekte, keine Änderung am Spielzustand
  • Eine Zeile Python pro Element, alternativ über HTTP oder direkt per Shared Memory
  • Buttons und Auswahlkarten sind klickbar und leuchten beim Hover auf; sie liegen unter dem Mauszeiger, und der Klick darauf kommt beim Spiel nicht an
Canvas-Doku
JASS-Kanal Einzelspieler · lokale Tools
1291 JASS-Funktionen, direkt per Name aufrufbar
  • Einheiten erzeugen, Werte ändern, Effekte, Panels, Dialoge, Sound, Kamera, Nebel, Wetter …
  • Farsight-Konsole, Kommandozeile, HTTP, Python – dieselbe Skriptschreibweise
  • Häufige Effekte in einer Zeile: schwebender Text, Linien, Reichweitenkreise, Porträt-Dialog, Vollbildfilter
  • Im Multiplayer sind nur lesende Funktionen freigegeben, um Desyncs zu vermeiden
Doku zum JASS-Kanal
CanvasJASS-Grafikfunktionen
Wer zeichnet Die Runtime selbst Das Spiel selbst
Multiplayer Sicher: nur im eigenen Bild Nur Einzelspieler
Stil Frei: Schriftart, abgerundete Ecken, Transparenz, Bilder Nativer Spielstil
Folgt Objekten Einheit / Weltkoordinaten / Bildschirmposition Je nach Funktion
Kosten 0.2 ~ 0.35 ms pro Frame ca. 13 ms pro Aufruf
1291 Funktionen nach Zweck
Grafikeffekte 80 UI-Panels 146 Kamera 44 Sound & Musik 50 Nebel & Sicht 25 Einheiten 161 Gegenstände 63 Helden 32 Spieler / Bündnisse / Ressourcen 71 Trigger / Timer 62 Gelände / Wetter 45 Spielablauf 57

Was der Spieler tut, landet direkt im Event-Stream

Die Runtime meldet die Aktionen des Spielers direkt: welchen gezeichneten Button er angeklickt hat, welchen Hotkey er gedrückt hat, wohin er auf den Boden geklickt hat, was er ausgewählt hat, welche Fähigkeit er gewirkt hat und was er ins Chatfeld getippt hat. Die spieleigenen Trigger-Events (Betreten von Regionen, Dialog-Buttons, Pfeiltasten) lassen sich weiterhin über einen leeren Trigger anbinden: nur Events registrieren, keine Bedingung und keine Aktion schreiben, dann zählen, wie oft er ausgelöst wurde.