Overwatch Tournaments нужны функциональные cookie, чтобы сохранять вход и язык интерфейса. Аналитические cookie необязательны и только показывают, как используются страницы, — подробнее в политике конфиденциальности.

Перейти к содержимому

Балансировщик, драфт и миксы

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

Способ формирования команд

tournament.team_formation — строка со значением по умолчанию balancer. В коде используются два значения:

ЗначениеЧто происходит
balancerПул уходит солверу, организатор принимает один из вариантов и экспортирует его в турнирные команды
draftКапитаны разбирают пул вживую, экспорт идёт из завершённой сессии драфта

Команды, которые капитаны собрали ещё на регистрации, — третий, отдельный путь экспорта в турнирные команды; он описан в статье Регистрация и составы. Менять team_formation или форму ростера, пока активна сессия драфта, нельзя — запрос отвечает 400 и называет статус мешающей сессии.

Балансировщик

Общая подготовка

Прежде чем запустится любой из движков, пул проходит одну и ту же подготовку.

  • Игроки, которые не могут сыграть ни одну нужную роль, отбрасываются. Если на какую-то роль формы ростера нет ни одного способного игрока — ошибка.
  • Число команд считается как целая часть от деления размера пула на размер команды, а затем ограничивается потолком конкретного движка.
  • Остаток не блокирует запуск — лишние просто сидят. Игроки, помеченные must_play, в остаток не попадают: их переносят в начало перед отсечением, и только если их больше, чем есть слотов, запрос отказывает. Внутри остальных порядок отсечения задаёт rotation_priority по возрастанию.
  • Если капитаны включены, верхние по рейтингу игроки помечаются капитанами и закрепляются за ролью: первой подходящей из их собственных предпочтений, иначе первой доступной. Ранжирование капитанов детерминированное — по убыванию максимального рейтинга, при равенстве по идентификатору игрока.
  • Дальше идёт поиск допустимого распределения ролей с откатом, который никогда не выселяет капитана. Не нашлось — ошибка с объяснением, каких ролей не хватает.

Турнирный движок

Турнирный солвер — многокритериальный эволюционный поиск на Rust, который строит произвольное число команд и оптимизирует две цели одновременно:

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

Результат — набор недоминируемых вариантов, из которых организатор выбирает. У каждого запуска есть жёсткий предел по времени (time_limit_ms, по умолчанию и максимально 600000 мс): по его исчерпании эволюция и полировка останавливаются и отдаётся лучшее найденное. Это осознанный размен воспроизводимости на задержку — обычные запуски сходятся задолго до предела.

Движок миксов

Движок миксов — вендоренное C++-ядро, которое перебирает все допустимые разбиения перебором по битовым маскам с ранним отсечением, и работает ровно на двух равных командах. Он собирается только под Linux; там, где расширение недоступно, расчёт микса молча уходит на турнирный движок.

Ручка mix_comfort_tilt наклоняет качество между «поровну по силе» и «побольше желаемых ролей»; середина воспроизводит собственные веса движка. Порог отсечения кандидатов — не требование: если лучшее разбиение оказалось выше него, движок отвечает ошибкой, и запрос повторяется без порога. Поскольку перебор уже закончен к моменту усечения, миксу отдаётся глубокий список вариантов, а не десяток.

Как это вызвать

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

POST /api/v1/balancer/tournaments/42/balance HTTP/1.1
Host: owt.craazzzyyfoxx.me
Authorization: Bearer <токен сессии или API-ключ>
Content-Type: application/json

{}

Ответ — 202 и job_id со ссылками status_url / result_url; статус читается как queued, running, succeeded или failed. Есть и синхронный близнец для интеграций — POST /api/v1/balancer/balance?workspace_id=1, где весь пул едет в player_data, форма ростера в role_mask, а команды возвращаются в том же ответе. Из воркспейса он не читает ничего, бюджет солвера у него срезан до 60 секунд, и пул, который в них не укладывается, отвечает 504 — такой считается асинхронно.

Живой драфт

Сессия

Состояния сессии: setup → ready → live ⇄ paused → completed | cancelled.

Форматы — snake, linear, custom. В custom у каждого раунда своё правило: linear, reverse, weakest_first, strongest_first, team_avg_asc, team_avg_desc. Порядок капитанов задаётся отдельно: manual (порядок выбора), weakest_first, strongest_first или random — детерминированное перемешивание Mulberry32 с сидом из настроек, чтобы предпросмотр и подтверждение всегда сходились. Стратегия автопика — best_fit (по умолчанию), best_available или role_need.

Число раундов не передаётся: оно выводится из формы ростера как team_size - 1. Форму, для которой раундов не остаётся, драфт не принимает.

Часы и конкуренция

Часы серверные. Их ведёт отдельный супервизор, владение сессионными часами удерживается распределённой блокировкой с TTL 10 секунд и продлением раз в 3 секунды, а клиенту остаётся только отрисовывать присланный дедлайн.

Ручной пик и автопик — один и тот же условный UPDATE, ограниченный одновременно состоянием «на часах» и оптимистичным токеном version. Ровно один писатель обновляет строку; проигравший получает 409 pick_already_resolved.

POST /api/v1/balancer/draft/picks/981/select HTTP/1.1
Host: owt.craazzzyyfoxx.me
Authorization: Bearer <токен сессии>
Content-Type: application/json

{ "player_id": 314, "expected_version": 17, "target_role": "damage" }

Тот же expected_version требует и автопик. Отдельно от него draft_session_stale (409) сторожит настройку сессии: если сид пересобрали, предпросмотр подтвердить нельзя.

Автопик и паузы

Автопик выбирает только из пар «игрок — роль», признанных безопасными. Безопасность считается двудольным паросочетанием между оставшимися слотами и доступными игроками, и гипотетический пик проверяется до применения — чтобы он не обнулил чужой слот. Если безопасного варианта нет, драфт не делает принудительный пик: он встаёт на паузу с причиной role_shortage.

Вторая причина паузы — order_recalculated. Правила team_avg_asc и team_avg_desc пересаживают раунд по живым средним в момент его начала, поэтому очередь на первом пике такого раунда может измениться, и драфт останавливается, чтобы это увидели.

Кандидаты берутся из ростеров того же движка, что и баланс: игрок оценивается по рангу той роли, которую реально займёт, а игрок без единого ранга в кандидаты не попадает вовсе — вместо того чтобы стать «последним с нулём». Сама формула фита проще балансировщика намеренно: автопик и подсказки должны отвечать синхронно и детерминированно.

Миксы и кастомки

Статусы кастомки — draft, balanced, completed, cancelled. Участие игрока в пуле — must_play, pool или benched; роли берутся либо все ранжированные (all_ranked), либо явно перечисленные (explicit).

Пределы: не больше 8 команд и не больше 16 со-хостов на игру. Права — отдельная системная роль воркспейса host: это всё, что есть у обычного участника, плюс полное владение ресурсом custom_game; одного членства в воркспейсе для создания микса недостаточно. Писать в пул может только хост, часть действий доступна и со-хостам.

Рекомендация ротации на следующую карту ранжирует пул по одному ключу, по порядку: самая длинная текущая серия пропусков, затем самая короткая серия сыгранных подряд, затем меньше всего сыгранных карт, затем исходный порядок для детерминизма. Закреплённые must_play всегда получают место и стоят впереди всех. Если мест хватает на весь пул или ни у кого из незакреплённых ещё нет истории, все возвращаются как neutral: выдавать стабильность сортировки за вывод о справедливости нельзя.

Realtime

Состояние драфта и балансировщика едет по разным топикам.

ТопикЧто несёт
tournament:{id}:draftСобытия драфта: draft.pick_started, draft.paused, draft.resumed, draft.blocked, draft.completed, draft.cancelled, draft.session_updated, draft.player_updated
tournament:{id}:balancerПравки данных (balancer.registrations_changed, balancer.balance_saved, balancer.teams_changed, balancer.config_changed) и жизненный цикл задач (balancer_job.queued / running / progress / succeeded / failed)
draft:{session_id}:chatЧат комнаты драфта — отдельный топик с собственными правами; пересев сессии создаёт новую комнату
workspace:{id}:pickup_mixСобытия миксов воркспейса

Тики прогресса задачи не сохраняются — поздно подключившийся клиент берёт снимок из статуса задачи. Эфемерны также balancer.presence, который шлюз синтезирует из живых соединений, и balancer.drag — единственный тип события, который шлюз принимает от клиента.

Экспорт команд объявляет устаревание отдельно, как инвалидацию турнирных ресурсов: сами события драфта ничего не инвалидируют, потому что пик пишет только в таблицы драфта.

См. также