Platform

One runtime, one protocol, turning the game into a programmable environment

W3 Runtime is injected into the original game, where it captures the whole world, executes commands and reports results on the game thread. Your AI only has to say what to do, talking to it through a versioned shared-memory protocol.

Layers

Lower layers don't know upper layers exist

The interface layer contains not a single line of “should we fight” logic; brains never import each other and depend only on the SDK. That's what makes the Arena possible — the platform only needs the interface layer plus a referee, and anyone's brain can plug in.

Your agent / bot
Any model, any language
Claude CodeCursorChatGPTQwen local modelPython botReference AI
Decisions
import openwar3 · or the WebSocket / JSON gateway · MCP
OpenWar3 SDK
Python · openwar3
Game / Botw3world snapshotw3fast fast lanecombat calculatorpathingw3claim arbitrationcanvas overlayjass channelschemes
Interface
World pushed every 50 ms · commands land in ~1 frame · a receipt for every one
W3P protocol v2
Shared memory · zero-copy · versioned
War3WorldWar3EventsWar3MapWar3TreesWar3Fast command lanesWar3Canvas overlay
Protocol
Executed in batches on the game thread · 4~8 µs each · 4 ms budget per drain
W3 Runtime
Runs on the game thread
World state publisherEvent streamSemantic command executorQueriesPermissions and viewsSelf-drawn canvasJASS callsVersion supportHealth and circuit breakers
Runtime
War3.exe 1.27 · the original game, no files on disk modified
Real-time information layer

The whole map, every 50 ms

The runtime captures everything in one pass on the game thread and pushes it into shared memory; clients read it directly instead of queueing on the game thread. A capture takes a median of 0.5–0.9 ms (100–120 units), and per-stage timings are always written into the world block header.

Players ×16

Gold, lumber, food and food cap, total gathered, race

Units ×1024

Type, owner, position, HP/mana, current order and target, what it's actually attacking, level/XP/skill points, each player's visibility of it

Unit details ×256

12 abilities (level, remaining cooldown), 8 buffs, 6 inventory slots; heroes first

Production table ×128

Train / research / build / upgrade: queue, total duration, elapsed time, whether blocked

Ground items ×256

Type, position, durability; events fire when picked up or used

Trees ×4096

Destructible positions and HP, refreshed every 2 seconds

Map

128-unit walkable / buildable grid, playable area, start locations; computed within seconds of game start

Time

Engine game clock, in-game time (day/night), game speed, whether a game is in progress

Event stream 8192-entry ring buffer with a sequence number on every entry; the SDK notices when slow reads get overwritten
unit.appearedunit.diedunit.removedunit.damagedorder.changedowner.changedhero.levelupitem.appeareditem.removedspell.castselection.changedmessageplayer.leftgame.startedgame.ended damage · Engine-level, every hitkilled · Engine-level, every hit production.done · Opponents' too
Semantic commands

The runtime picks the path

Gathering, retreating, casting… each is just “make a unit do something,” yet each takes a different path inside the engine. All of that know-how lives in the runtime. You say what to do; it executes with a verified recipe and, in the same frame, reads back the order before and after as the receipt.

  • Submit a batch at once, executed in the same frame
  • Every command gets a receipt: status code + engine reason code + execution time
  • Shift-queueing, waypoints, chained builds
  • Queries: tech counts, feasibility, visibility, gold remaining, the computer opponent's attack target
Receipts and reason codes
moveattack_moveattackstopholdpatrolattack_groundgatherrepairbuildbuild_nearbuild_queuetraincancellearncastrallyrevivepick_upuse_itemdrop_itemgive_itemsell_itembuypathcall_to_armspausebatch
Low latency

Crossing processes was never the slow part

Shared-memory reads and writes take nanoseconds, and reading a snapshot takes ~0.05 ms. The old channel was slow because every request had to grab one machine-wide lock and then wait for the game's next message fetch (once per frame, 16–33 ms). The reference brain spent 93% of its wall-clock time waiting on it.

Old · control channel20–40 ms / call
  1. Grab a machine-wide mutex
  2. Post a message to the game thread
  3. Wait for the game's next message fetch (once per frame)
  4. Wait for the completion event, release the lock

With 6 processes sharing it, throughput tops out at about 88 calls/s.

New · fast lane~1 frame, batched
  1. Each client owns its own lane: one writer, one reader, no locks
  2. Execution happens inside the game thread's event dispatch (hundreds of times per second); with nothing submitted, the overhead is just a few integer comparisons
  3. 16 commands submitted at once all execute in the same dispatch; each drain has a 4 ms time budget
  4. Every command carries a deadline: after a pause and resume, expired commands never execute

Every command's receipt includes its execution time, and the lane header records the slowest command and how long the last drain took — so you can see exactly where the time goes.

TierChannelLatencyUsed byStatus
0 Pushed snapshot + event stream ~0.4 ms per read; a new one every 50 ms (adjustable down to 16 ms) All bots Verified
1 Fast lane ~1 frame; median 0.06 ms with 6 concurrent processes, ~3000 calls/s throughput SDK default Verified
2 Control channel 20–40 ms Fallback, a few UI-type operations Verified
3 Gateway (WebSocket / JSON) Tier 1 + ~1 ms Any language, browser pages, LLMs (MCP), another machine Verified

Measured (1.27 test instance, 2026-09-23 / 24)

Command throughput
was 88
3,000 cmd/s
6 concurrent processes
Median wait under concurrency
was 67 ms
0.06 ms
Old control channel → fast lane
16 commands
was 121 ms
13 ms
One by one → one batch
8 moves
was 68~99 ms
6.5~10 ms
with g.batch()
One world-state capture
was 11.8 ms
0.58 ms
On the game thread; reuses memory regions already confirmed readable
Single command at game start
was 4~10 ms
4~8 µs
The sampler traced it to synchronous logging, which was made async
One reference-brain cycle
was 0.15~0.56 s
0.02~0.07 s
39-minute game, 0 task errors
Slowest reference-brain cycle
was 1.3~3.2 s
0.24~0.42 s
No cycle took ≥ 2 s all game

Engineering note: for the first 3 minutes, every command was 1000× slower

After we added per-command execution time, we found that during the first few minutes of a game, every command from the second one in a batch onward took 4–10 ms, then suddenly dropped to a few microseconds at around 180 seconds of game time. The runtime's built-in game-thread sampler collected 1700 samples, and 93% of them landed in the runtime's own logging function — every line written opened and closed the log file synchronously, and at game start every engine order wrote a line. With debug logging off by default and logs flushed to disk asynchronously, even commands at second 14 of a game take only 4–8 µs.

We trust measured numbers, not guesses.

Permissions and views

Every lane has a role

Ownership checks and vision filtering happen in the runtime — the engine's execution layer doesn't check unit ownership itself, so it has to be done here. Two AIs fighting each other is just two player lanes in the same game.

RoleCan seeCan do
player All own units + enemy and neutral units within vision (fair mode) Command own units only
Players, your bot
observer The whole map No commands; can query, control the camera and read the HUD
Director, commentary, post-game review
Multi-version support · P4

No guessing, no crashing

Versions 1.24–1.28 share the same engine structure, which suits “one runtime + multiple profiles.” 1.29 and later, as well as Reforged, use a different engine, and automatic compatibility isn't promised.

  1. Identify Read Game.dll's version resource and file hash, then pick a profile
  2. Symbol table Recipes reference symbol names, not raw numbers; each symbol carries its calling convention and argument shape
  3. Signature-scan fallback On unknown versions, scan for byte signatures at the start of functions, and use a match only if it's unique
  4. Startup self-check Each symbol is verified without side effects; capabilities that fail are marked unavailable
  5. Capability list When the SDK sees that a capability is unavailable, calling it raises a clear error instead of quietly returning 0
High availability

One bot's bug shouldn't crash the whole game

That's also why every AI runs in its own process — a null pointer inside the game process crashes the whole game, but when a separate process crashes, only that side stops.

MechanismHowStatus
Never drag down the game Every game call is exception-guarded; each drain has a 4 ms time budget (high-resolution timer) Shipped
Clients isolated from each other One lane per client, each with its own quota; one stuck client doesn't block the others Shipped
Expired means skipped Every command carries a deadline; expired ones are only marked, never executed, and aren't replayed after a pause and resume Shipped
Observable Execution time for every command; per-stage timings for every capture written into the world block header Shipped
Auto-reconnect The SDK follows game restarts and process changes and reconnects automatically Partial
Capability circuit breaker A capability that fails N times in a row → marked unavailable and an event fires; other capabilities keep working Planned
Extension layer

Build your own things inside the game

Semantic commands let an AI play like a player; the extension layer lets you change what players see and experience — draw your own clickable UI, call every function map makers have. The two paths are split by whether they're safe in multiplayer.

Canvas Multiplayer-safe
0.27–0.34 ms per-frame cost (9 elements, ~63 fps)
  • Text boxes, panels, progress bars, images, terrain-hugging circles, arrowed routes
  • Anchored to units, world coordinates or screen positions; CJK fonts, rounded corners, translucency, any color
  • Drawn by the runtime itself — no game objects created, no game state changed
  • One line of Python per element, or go through HTTP, or write shared memory directly
  • Buttons and choice cards are clickable and highlight on hover; drawn under the mouse cursor, and the game never receives the click that lands on one
Canvas docs
JASS channel Single-player · local tools
1291 JASS functions, called directly by name
  • Spawn units, change stats, effects, boards, dialogs, sounds, camera, fog, weather…
  • Farsight console, command line, HTTP, Python — the same script syntax everywhere
  • Common effects in one line each: floating text, lightning links, range circles, portrait dialogue, full-screen filters
  • In multiplayer, only read-only functions are allowed, to avoid desyncs
JASS channel docs
CanvasJASS display functions
Who draws it The runtime The game itself
Multiplayer Safe: drawn only on your own screen Single-player only
Styling Anything: fonts, rounded corners, translucency, images Native game look
Follows things Units / world coordinates / screen positions Depends on the function
Cost 0.2–0.35 ms per frame ~13 ms per call
1291 functions by purpose
Visual effects 80 UI panels 146 Camera 44 Sound and music 50 Fog and vision 25 Units 161 Items 63 Heroes 32 Players / alliances / resources 71 Triggers / timers 62 Terrain / weather 45 Game flow 57

What the player does goes straight into the event stream

The runtime reports player actions directly: which drawn button they clicked, which hotkey they pressed, where they clicked on the ground, who they selected, what spell they cast, and what they typed in the chat box. The game's own trigger events (entering regions, dialog buttons, arrow keys) can still be hooked with an empty trigger: register only the event, with no conditions or actions, then count how many times it has run.