StadMagicОбзорАренаМирыРейтингДокументыКоманда

Игровой API StadMagic (совместимый с DatsMagic)

Эта страница описывает игровой REST API для ботов. У него ровно один метод: клиент отправляет команды и в том же ответе получает актуальный снимок мира. Этот контракт не меняется. У веб-визуализатора есть отдельный realtime-канал, описанный ниже; он не предназначен для ботов. Отдельных игровых `GET`-эндпоинтов нет.

Адрес арены

Текущую арену и её адрес показывает главная страница Hub. Адрес может сохраняться между запусками, но состояние, карта и доступность ковров сбрасываются при смене запуска. Для локального запуска типичный адрес — `http://127.0.0.1:8080`.

Единственный игровой метод

`POST /play/magcarp/player/move`

Заголовки:


X-Auth-Token: <токен-команды>

Content-Type: application/json

Accept: application/json

Accept-Encoding: gzip

Тело — JSON-объект `transports`; каждый элемент относится к одному ковру и содержит его `id` и вектор `acceleration`:


{

  "transports": [

    {"id": "ec50282c5bbb11d2_0", "acceleration": {"x": 40, "y": 0}},

    {"id": "ec50282c5bbb11d2_1", "acceleration": {"x": -12, "y": 16}}

  ]

}

Один зарегистрированный токен может выполнять не более **5 запросов в скользящую секунду**; шестой запрос в текущем окне получит `429` до сборки полного ответа. Пустые запросы тоже учитываются. Дополнительно остаётся лимит не более одного непустого пакета команд за игровой тик. Сжатие — опциональное: если отправить `Accept-Encoding: gzip`, сервер может вернуть тот же JSON с `Content-Encoding: gzip`; стандартная HTTP-библиотека должна распаковать тело перед JSON parsing. Без этого заголовка приходит обычный JSON.

Realtime-канал сайта

Для `/arena` Hub предоставляет `POST /api/visualizer/ticket`. Игрок передаёт командный токен только заголовком `X-Auth-Token` и пустое JSON-тело `{}`. Наблюдатель вызывает endpoint без токена. Hub возвращает короткоживущий одноразовый ticket и адрес WebSocket активной арены. Сам командный токен никогда не помещается в WebSocket, URL или игровые сообщения.

WebSocket использует подпротокол `stadmagic.v1`; ticket передаётся только на handshake как дополнительный `Sec-WebSocket-Protocol` со значением `stadmagic-ticket.<ticket>`. Сервер отправляет сообщения вида `{"type":"snapshot","tick":42,"state":{...}}`, где `state` — Desert DTO из игрового API. Клиентские команды имеют вид `{"type":"commands","transports":[{"id":"...","acceleration":{"x":12,"y":-8}}]}`. В пределах одного тика веб-команды коалесцируются, используется последняя валидная команда. Это правило относится только к веб-каналу; REST ограничение для ботов остаётся прежним.

Ручной режим сайта продлевает lease отдельным небольшим `POST /api/visualizer/lease` с `X-Auth-Token` раз в 500 мс. Он не проксирует игровой API и не получает снимок. После выключения управления отправляется `{ "releaseLeaseId": "..." }`; если браузер или сеть пропадут, lease истечёт автоматически.

Старый `POST /api/visualizer/move` Hub-proxy оставлен как совместимый fallback. Обычному боту следует обращаться непосредственно к игровому `POST /play/magcarp/player/move`.

Ковры создаются автоматически при первом запросе зарегистрированного токена. В ответе сервер вернёт их реальные непрозрачные ID — используйте именно их, не составляйте ID вручную. ID не является токеном авторизации. Чтобы оставить конкретный ковер без управляющего ускорения, передайте `{ "x": 0, "y": 0 }`. Если элемент отсутствует или в нём нет `acceleration`, новая команда этому ковру не задаётся, а сервер продолжает применять его последнее принятое ускорение.

Успешный ответ `200 OK`

Ответ — полный снимок `Desert` для команды, от имени которой передан токен. Ниже пример формы ответа; конкретные значения, число ковров и сущности зависят от активного мира.


{

  "errors": [],

  "anomalies": [

    {

      "effectiveRadius": 850,

      "id": "anomaly_12",

      "radius": 24,

      "strength": -35,

      "velocity": {"x": -18, "y": 7},

      "x": 4100,

      "y": 3200

    }

  ],

  "attackCooldownMs": 10000,

  "attackDamage": 30,

  "attackExplosionRadius": 30,

  "attackRange": 200,

  "bounties": [

    {"points": 75, "radius": 5, "x": 4300, "y": 3000}

  ],

  "enemies": [

    {

      "health": 100,

      "killBounty": 0,

      "shieldLeftMs": 0,

      "status": "alive",

      "velocity": {"x": -20, "y": 3},

      "x": 5200,

      "y": 2500

    }

  ],

  "mapSize": {"x": 10000, "y": 10000},

  "maxAccel": 40,

  "maxSpeed": 110,

  "name": "my-team",

  "points": 75,

  "reviveTimeoutSec": 2,

  "shieldCooldownMs": 40000,

  "shieldTimeMs": 5000,

  "transportRadius": 5,

  "transports": [

    {

      "anomalyAcceleration": {"x": 0, "y": 0},

      "attackCooldownMs": 0,

      "deathCount": 0,

      "health": 100,

      "id": "ec50282c5bbb11d2_0",

      "selfAcceleration": {"x": 40, "y": 0},

      "shieldCooldownMs": 0,

      "shieldLeftMs": 0,

      "status": "alive",

      "velocity": {"x": 8, "y": 0},

      "x": 3200,

      "y": 3000

    }

  ],

  "wantedList": []

}

Поля ответа

| Поле | Тип | Значение |

|---|---|---|

| `errors` | `string[]` | Ошибки отдельных элементов запроса. Некорректный/чужой ID или нечисловое ускорение могут попасть сюда, тогда как остальные валидные команды пакета будут приняты. |

| `anomalies` | `Anomaly[]` | Текущие аномалии арены. `x`, `y` — центр; `velocity` — вектор движения; `effectiveRadius` — внешняя граница зоны сил; `radius` — смертельное ядро; `strength` — знаковая сила (положительная притягивает, отрицательная отталкивает). |

| `attackCooldownMs`, `attackDamage`, `attackExplosionRadius`, `attackRange` | number | Настройки мира, оставленные в совместимом формате Desert API. Сейчас атака игровым endpoint не реализована. |

| `bounties` | `Bounty[]` | Доступные монеты. `x`, `y` — центр, `points` — начисляемое золото. `radius` в текущем контракте равен `transportRadius`; не считайте его отдельным точным радиусом графического спрайта. |

| `enemies` | `Unit[]` | Ковры всех остальных команд плоским списком. Поле не группирует ковры по командам и включает только существующие сущности. |

| `mapSize` | `{x, y}` | Ширина и высота арены в игровых единицах. Начало координат — левый нижний угол; `x` растёт вправо, `y` вверх. |

| `maxAccel` | number | Максимальная длина управляющего вектора одного ковра. Сервер ограничивает более длинный вектор до этого значения. |

| `maxSpeed` | number | Максимальная длина скорости ковра. Скорость ограничивается этим значением после каждого тика. |

| `name` | string | Имя команды, выбранное при регистрации. |

| `points` | integer | Текущий счёт команды (золото всех её ковров после начислений и штрафов за гибель). |

| `reviveTimeoutSec` | number | Настройка мира о задержке возрождения; фактическое наличие респавна определяется профилем мира. |

| `shieldCooldownMs`, `shieldTimeMs` | number | Совместимые настройки Desert API. Щит игровым endpoint сейчас не реализован. |

| `transportRadius` | number | Радиус ковра, в игровых единицах. Используется, в частности, при сборе монет и проверке столкновений. |

| `transports` | `Transport[]` | Полный список собственных ковров, включая погибшие. Стабильный `id` сохраняется после респавна. |

| `wantedList` | `Unit[]` | Совместимое поле Desert API; сейчас всегда пустое. |

Поля ковра `Transport`

| Поле | Тип | Значение |

|---|---|---|

| `id` | string | Стабильный ID ковра; верните его в элементе команды, чтобы управлять этим ковром. |

| `x`, `y` | number | Координаты центра ковра. |

| `velocity` | `{x, y}` | Фактический вектор скорости сейчас. Его длина — скорость, направление — направление движения. |

| `selfAcceleration` | `{x, y}` | Последний эффективный управляющий вектор после ограничения сервером; не обязательно уже отражает команду, только что отправленную в текущем запросе. |

| `anomalyAcceleration` | `{x, y}` | Суммарный вектор сил аномалий в текущей позиции ковра. Это не команда управления и не часть `selfAcceleration`. |

| `status` | string | `alive` или `dead`. При респавне тот же ID снова становится `alive`. |

| `health` | integer | `100` у живого ковра и `0` у погибшего; сейчас это индикатор статуса, а не уменьшаемый запас здоровья. |

| `deathCount` | integer | Число гибелей этого ID за текущую сессию арены. |

| `attackCooldownMs`, `shieldCooldownMs`, `shieldLeftMs` | number | Совместимые поля; в текущей реализации равны нулю, соответствующие действия не поддерживаются. |

Поля элемента `enemies` имеют форму `Unit`: `x`, `y`, `velocity`, `health`, `status`, `killBounty`, `shieldLeftMs`. У противников `status` также равен `alive`/`dead`; `killBounty` и shield сейчас возвращаются как нули.

Ошибки HTTP

Тело ошибки имеет общий вид `{"error":"..."}`.

| HTTP | Пример `error` | Причина и действие клиента |

|---|---|---|

| `400` | `invalid vector values` | Тело не соответствует JSON-схеме или содержит некорректные значения. Проверьте `transports` и конечность чисел. |

| `400` | `session is not active` | Сессия арены сейчас не принимает команды; повторите запрос после запуска следующей арены. |

| `400` | `player_destroyed` | Погиб весь флот, а в мире нет респавна. Этот токен больше не может управлять этой сессией. |

| `401` | `unauthorized` | Нет `X-Auth-Token`, токен пустой или не зарегистрирован для Hub-арены. Проверьте токен и регистрацию. |

| `429` | `rate limit exceeded: 1 command per tick` | Для команды уже принят пакет ускорений в этом игровом тике. Не ретрайте немедленно; дождитесь следующего тика. |

| `429` | `rate limit exceeded: 5 requests per second per token` | Токен уже выполнил 5 запросов за последнюю секунду. Снизьте частоту; не запускайте немедленные повторы. |

| `500` | `internal server error` | Ошибка сервера. Повторите позже; не запускайте плотный цикл повторов. |

Ошибки уровня отдельного ковра возвращаются в `errors` успешного ответа. Например, неизвестный ID будет пропущен; валидные команды остальных ковров при этом могут быть применены. Если запрос содержит хотя бы одну валидную команду, команда в целом считается отправившей пакет на этот тик.

Пример запроса


curl --fail-with-body \

  -X POST 'http://127.0.0.1:8080/play/magcarp/player/move' \

  -H 'X-Auth-Token: my-team-secret' \

  -H 'Content-Type: application/json' \

  --data '{"transports":[{"id":"ec50282c5bbb11d2_0","acceleration":{"x":40,"y":0}}]}'

Первый ответ содержит созданный флот. Затем отправляйте каждую команду с соответствующим ID. Серверный шаг времени равен 200 мс; ограничение — один пакет с командами на токен за тик. Ответ — снимок состояния при обработке запроса, а не отдельное подтверждение того, что физический тик уже завершился.

Регистрация токена

Сначала откройте `/register` и выберите уникальное имя команды (1–48 печатных символов; регистр букв при проверке уникальности не учитывается). Hub создаст криптографически случайный токен и покажет его один раз после успешной регистрации. Скопируйте и сохраните секрет до закрытия страницы. Один токен соответствует одной команде во всех мирах; первый игровой запрос создаёт её флот. Передавайте токен только в `X-Auth-Token`, не в URL, код публичного репозитория или логи. Имя можно сменить запросом `POST /api/teams` с JSON `{"token":"<ваш токен>","name":"Новое имя"}`.

Арена всегда сверяет токен с приватным реестром Hub. Отсутствующий, неизвестный или отозванный токен получает `401 Unauthorized`; это правило действует и при отдельном запуске сервера. Для отдельного сервера укажите реестр через `DATS_TOKEN_REGISTRY_PATH` либо используйте стандартный `modules/arena-hub/data/registry.json`.

Hub API — не игровое управление

Сайт Hub отдельно предоставляет `/api/worlds`, `/api/runs`, `/api/leaderboard`, `/api/votes` и `POST /api/teams`. Например, `POST /api/teams` с телом `{"name":"My Team"}` создаёт команду; успешный ответ имеет форму `{"team_id":"<идентификатор>","name":"My Team","token":"<секрет, показать один раз>"}`. Эти маршруты обслуживают веб-интерфейс, каталог, регистрацию и результаты; посылать туда игровые команды нельзя. Для игровых команд используется только `POST /play/magcarp/player/move`.