Платформа

Один рантайм, один протокол — игра становится программируемой средой

W3 Runtime внедряется в оригинальную игру и в игровом потоке собирает состояние мира, исполняет команды и сообщает результаты. Вашему ИИ достаточно сказать «что сделать» — общение идёт по версионированному протоколу общей памяти.

Слои

Нижний слой не знает о верхнем

В интерфейсном слое нет ни строчки логики «атаковать или нет»; мозги не импортируют друг друга и зависят только от SDK. На этом держится Арена: платформе нужны лишь «интерфейсный слой + судья», а подключить можно чей угодно мозг.

Ваш агент / бот
Любая модель, любой язык
Claude CodeCursorChatGPTлокальная Qwenбот на Pythonэталонный ИИ
Решения
import openwar3 · или шлюз WebSocket / JSON · MCP
OpenWar3 SDK
Python · openwar3
Game / Botснимок w3worldбыстрая полоса w3fastcombat — расчёт бояpathing — поиск путиw3claim — арбитражcanvas — холстjass — JASS-каналschemes — ИИ-схемы
Интерфейс
мир каждые 50 ms · команда исполняется ≈ за 1 кадр · квитанция на каждую
Протокол W3P v2
Общая память · zero-copy · версионирование
War3WorldWar3EventsWar3MapWar3TreesWar3Fast — полосы командWar3Canvas — холст
Протокол
пачками в игровом потоке · 4~8 µs на команду · бюджет 4 ms на проход
W3 Runtime
Работает в игровом потоке
публикация состояния мирапоток событийисполнитель семантических командзапросыправа и точки зрениясобственная отрисовка холставызовы JASSподдержка версийздоровье и предохранители
Рантайм
War3.exe 1.27 · оригинальная игра, никакие файлы на диске не изменяются
Слой данных в реальном времени

Вся карта — каждые 50 ms

Рантайм собирает данные за один проход в игровом потоке и выкладывает их в общую память; клиенты читают напрямую и больше не стоят в очереди к игровому потоку. Один сбор — в медиане 0.5 ~ 0.9 ms (100 ~ 120 юнитов), время по этапам всегда записано в заголовке блока мира.

Игроки ×16

Золото, древесина, пища и лимит, всего добыто, раса

Юниты ×1024

Тип, владелец, координаты, здоровье и мана, текущий приказ и цель, кого юнит на самом деле атакует, уровень, опыт, очки навыков, видимость для каждого игрока

Подробности юнитов ×256

12 способностей (уровень, оставшаяся перезарядка), 8 баффов, 6 ячеек инвентаря; герои в приоритете

Производство ×128

Тренировка / исследование / строительство / улучшение: очередь, общая длительность, прошедшее время, не застряло ли

Предметы на земле ×256

Тип, позиция, прочность; подбор или использование порождает событие

Деревья ×4096

Позиции и здоровье разрушаемых объектов, обновление каждые 2 секунды

Карта

Сетка проходимости и застройки с клеткой 128, игровая область, стартовые точки; рассчитывается за несколько секунд в начале матча

Время

Игровые часы движка, время суток, скорость игры, идёт ли матч

Поток событий Кольцевой буфер на 8192 записи, у каждой — порядковый номер; если читать слишком медленно и данные перезапишутся, SDK это заметит
unit.appearedunit.diedunit.removedunit.damagedorder.changedowner.changedhero.levelupitem.appeareditem.removedspell.castselection.changedmessageplayer.leftgame.startedgame.ended damage · Каждый удар на уровне движкаkilled · Каждый удар на уровне движка production.done · В том числе у соперника
Семантические команды

Как исполнить — решает рантайм

Добыча, отступление, применение способностей… Всё это «приказать юниту», но внутри движка пути совершенно разные. Весь этот опыт собран в рантайме: вы говорите, что сделать, он исполняет проверенным рецептом и в том же кадре возвращает квитанцию с приказом до и после.

  • Одна отправка — одна пачка, исполняется в одном кадре
  • Квитанция на каждую команду: код статуса + код причины движка + время выполнения
  • Очередь через Shift, точки маршрута, постройка нескольких зданий подряд
  • Запросы: счётчики технологий, выполнимость, видимость, остаток золота, цель атаки компьютерного противника
Квитанции и коды причин
moveattack_moveattackstopholdpatrolattack_groundgatherrepairbuildbuild_nearbuild_queuetraincancellearncastrallyrevivepick_upuse_itemdrop_itemgive_itemsell_itembuypathcall_to_armspausebatch
Низкая задержка

Дело никогда не было в межпроцессном взаимодействии

Чтение и запись общей памяти занимают наносекунды, снимок читается ≈ за 0.05 ms. Старый канал был медленным, потому что каждый запрос захватывал общую для всей машины блокировку и ждал, пока игра в следующий раз заберёт сообщение (раз в кадр, 16 ~ 33 ms). Эталонный мозг тратил на это ожидание 93% реального времени.

Было · канал управления20 ~ 40 ms на запрос
  1. Захватить общий для всей машины мьютекс
  2. Отправить сообщение игровому потоку
  3. Ждать, пока игра заберёт сообщение (раз в кадр)
  4. Дождаться события завершения, отпустить блокировку

При 6 процессах пропускная способность упиралась примерно в 88 запросов/с.

Стало · быстрая полоса≈ 1 кадр, пачками
  1. У каждого клиента собственная полоса: один писатель, один читатель, без блокировок
  2. Исполнение происходит в диспетчеризации событий игрового потока (сотни раз в секунду); когда отправок нет, накладные расходы — несколько сравнений целых чисел
  3. 16 команд за одну отправку исполняются в одной диспетчеризации; на каждый проход — бюджет 4 ms
  4. У каждой команды есть дедлайн: после снятия с паузы просроченные команды не выполняются

В квитанции каждой команды указано время выполнения, а заголовок полосы хранит самую медленную команду и длительность последнего прохода — сразу видно, где тормозит.

УровеньКаналЗадержкаКто используетСтатус
0 Push-снимок + поток событий чтение ≈ 0.4 ms; новая копия каждые 50 ms (можно до 16 ms) Все боты Проверено в игре
1 Быстрая полоса ≈ 1 кадр; при 6 процессах медиана 0.06 ms, пропускная способность ≈ 3000 команд/с SDK по умолчанию Проверено в игре
2 Канал управления 20 ~ 40 ms Резервный путь, немногие операции с интерфейсом Проверено в игре
3 Шлюз (WebSocket / JSON) уровень 1 + ≈ 1 ms Любые языки, страницы в браузере, LLM (MCP), другая машина Проверено в игре

Замеры (тестовый экземпляр 1.27, 2026-09-23 / 24)

Пропускная способность
Было 88
3 000 команд/с
6 процессов параллельно
Медианное ожидание при параллельной работе
Было 67 ms
0.06 ms
старый канал управления → быстрая полоса
16 команд
Было 121 ms
13 ms
по одной → одной пачкой
8 приказов движения
Было 68~99 ms
6.5~10 ms
with g.batch()
Один сбор состояния мира
Было 11.8 ms
0.58 ms
в игровом потоке; повторно используются области памяти, уже проверенные на читаемость
Одна команда в начале матча
Было 4~10 ms
4~8 µs
сэмплер нашёл синхронный лог, его сделали асинхронным
Цикл эталонного мозга
Было 0.15~0.56 s
0.02~0.07 s
матч 39 минут, 0 ошибок в задачах
Самый медленный цикл эталонного мозга
Было 1.3~3.2 s
0.24~0.42 s
за весь матч ни одного цикла ≥ 2 секунд

Инженерная заметка: первые 3 минуты матча каждая команда была в 1000 раз медленнее

Когда к каждой команде добавили время выполнения, выяснилось: в первые минуты матча каждая команда в пачке, начиная со второй, занимала 4 ~ 10 ms, а примерно на 180-й секунде игрового времени внезапно ускорялась до нескольких микросекунд. Встроенный в рантайм сэмплер игрового потока собрал 1700 сэмплов — 93% пришлись на собственную функцию логирования рантайма: каждая строка синхронно открывала и закрывала файл лога, а в начале матча строка пишется на каждый приказ движка. После того как отладочный лог по умолчанию выключили, а запись логов сделали асинхронной, даже команды на 14-й секунде матча занимают всего 4 ~ 8 µs.

Мы верим измеренным числам, а не догадкам.

Права и точки зрения

У каждой полосы есть роль

Проверка владения и фильтрация по обзору выполняются в рантайме: слой исполнения движка сам не проверяет, кому принадлежит юнит, и восполнить это можно только здесь. Бой двух ИИ — это две полосы player в одном матче.

РольЧто видитЧто может
player Всё своё + враги и нейтралы в пределах обзора (честный режим) Командовать только своими юнитами
Игроки, ваш бот
observer Вся карта Приказывать нельзя; можно делать запросы, управлять камерой, читать HUD
Режиссура, комментаторы, разбор матчей
Поддержка версий · P4

Не гадать и не падать

1.24 ~ 1.28 построены на одном движке — это подходит под схему «один рантайм + несколько профилей». 1.29+ и Reforged — другой движок, автоматическую совместимость с ними мы не обещаем.

  1. Определение Читаем ресурс версии и хэш файла Game.dll, выбираем профиль
  2. Таблица символов Рецепты ссылаются на имена символов, а не на числа; у каждого символа указаны соглашение о вызове и форма аргументов
  3. Резерв по сигнатурам На неизвестной версии ищем по байтовой сигнатуре начала функции; используем, только если совпадение единственное
  4. Самопроверка при запуске Каждый символ проверяется без побочных эффектов; не прошедшие проверку возможности помечаются недоступными
  5. Список возможностей Если SDK видит, что возможность недоступна, вызов выбрасывает понятную ошибку, а не тихо возвращает 0
Отказоустойчивость

Баг в одном боте не должен ронять всю игру

Поэтому каждый ИИ работает в отдельном процессе: нулевой указатель внутри процесса игры — это падение всего матча, а если упал отдельный процесс, останавливается только одна сторона.

МеханизмКак сделаноСтатус
Не ронять игру Все вызовы в игру защищены обработкой исключений; бюджет 4 ms на проход (таймер высокой точности) Есть
Клиенты не мешают друг другу У каждого клиента своя полоса и своя квота; зависший клиент не блокирует остальных Есть
Просроченное не выполняется У каждой команды есть дедлайн; просроченные только помечаются и не выполняются, после паузы не воспроизводятся Есть
Наблюдаемость Время выполнения каждой команды; время каждого сбора по этапам записано в заголовке блока мира Есть
Переподключение SDK автоматически переподключается при смене матча или процесса Частично
Предохранители возможностей Возможность отказала N раз подряд → помечается недоступной, отправляется событие, остальные работают как обычно В планах
Слой расширений

Делайте в игре что-то своё

Семантические команды позволяют ИИ действовать как игрок; слой расширений позволяет менять то, что игрок видит и переживает: рисовать собственный интерфейс, который можно нажимать, и вызывать все функции, доступные авторам карт. Эти два пути разделены по тому, безопасны ли они в многопользовательской игре.

Холст Безопасно в многопользовательской игре
0.27 ~ 0.34 ms накладные расходы за кадр (9 элементов, ≈ 63 кадра/с)
  • Текстовые блоки, панели, индикаторы прогресса, изображения, круги по рельефу, маршруты со стрелками
  • Привязка к юниту, мировым координатам или позиции на экране; любые шрифты (включая китайские), скругления, полупрозрачность, любые цвета
  • Рисует сам рантайм: игровые объекты не создаются, состояние игры не меняется
  • В Python — одна строка на элемент; также через HTTP или прямой записью в общую память
  • Кнопки и карточки выбора нажимаются и подсвечиваются при наведении; рисуются под указателем мыши, а клик по ним до игры не доходит
Документация по холсту
JASS-канал Одиночная игра · локальные инструменты
1291 функция JASS, вызов прямо по имени
  • Создание юнитов, изменение характеристик, эффекты, панели, диалоги, звук, камера, туман войны, погода…
  • Консоль Farsight, командная строка, HTTP, Python — один и тот же синтаксис скриптов
  • Частые эффекты — одной строкой: всплывающий текст, линии, круги радиуса, диалог с портретом, полноэкранный фильтр
  • В многопользовательской игре разрешены только функции чтения — чтобы не было рассинхронизации
Документация по JASS-каналу
ХолстВизуальные функции JASS
Кто рисует Сам рантайм Сама игра
Многопользовательская игра Безопасно: рисуется только на вашем экране Только одиночная игра
Стиль Любой: шрифты, скругления, полупрозрачность, изображения Родной стиль игры
Привязка Юнит / мировые координаты / позиция на экране Зависит от функции
Стоимость 0.2 ~ 0.35 ms за кадр ≈ 13 ms на вызов
1291 функция по назначению
Визуальные эффекты 80 Интерфейсные панели 146 Камера 44 Звук и музыка 50 Туман и обзор 25 Юниты 161 Предметы 63 Герои 32 Игроки / союзы / ресурсы 71 Триггеры / таймеры 62 Рельеф / погода 45 Ход игры 57

Действия игрока сразу попадают в поток событий

Рантайм напрямую сообщает о действиях игрока: какую нарисованную кнопку он нажал, какую горячую клавишу, куда кликнул на земле, кого выбрал, какую способность применил, что написал в чат. Собственные триггерные события игры (вход в область, кнопки диалогов, стрелки) по-прежнему можно ловить пустым триггером: только зарегистрировать событие, без условий и действий, и считать, сколько раз он сработал.