Files
mixmaker/memory_bank/domain_model.md
lemintare a311b7761d
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / compose (push) Has been cancelled
Refactor bracket validation logic to support draft state
This commit updates the bracket validation process by introducing a new `ValidateDraft` method, allowing for incomplete edits in the draft state. The existing `Validate` method has been modified to enforce complete validation only upon confirmation. Additionally, new tests have been added to ensure that incomplete drafts are accepted until confirmation, while still rejecting invalid references. Documentation has been updated to reflect these changes in the API and architecture.
2026-07-19 13:43:25 +03:00

17 KiB
Raw Blame History

Предметная модель

Основные сущности

Player

Игрок связан с Discord-аккаунтом и имеет отображаемое имя и собственный Overwatch-ранг для каждой роли: Tank, Damage, Support. Лестница содержит 40 значений от Bronze 5 до Champion 1: Bronze, Silver, Gold, Platinum, Diamond, Master, Grandmaster, Champion, каждый с дивизионами 5 → 1. Для вычислений значения преобразуются в ordinal 140.

Игрок редактирует ранги самостоятельно; Admin может видеть время последнего изменения. Игрок выбирает любое число предпочитаемых ролей, до трёх желаемых союзников и до трёх игроков для avoid. Предпочтения и avoid являются мягкими пожеланиями, а не гарантией состава; один игрок не может одновременно находиться в обоих списках.

Account

Учётная запись Discord с глобальной ролью Player, Moderator или Admin. Moderator имеет те же операционные права, что Admin, но не может назначать и снимать модераторов. Moderator назначается только Admin; начальные Admin задаются списком Discord ID в конфигурации развёртывания. Изменение роли записывается в аудит.

Event

Запланированный игровой вечер: название, дата и время, часовой пояс, описание, дедлайн регистрации, формат команд, регламент, турнир и текущее состояние.

EventRegistration

Отметка игрока в календаре со статусом Going, Maybe или NotGoing. Игрок меняет собственный статус, а Admin может изменить его за игрока. Регистрация хранит автора и время последнего изменения, чтобы интерфейс явно показывал административное действие. В момент закрытия регистрации Admin подтверждает список участников; только Going-игроки по умолчанию попадают в балансировку.

Discord-анонс события

Созданный микс проецируется во внешний Discord-канал как информационный анонс со ссылкой на страницу Event. Параметр команды создания определяет, сопровождается ли анонс уведомлением @everyone; он не является сохраняемым состоянием Event. Анонс не является источником состояния регистрации: RSVP, доступность события и все переходы workflow по-прежнему определяет backend Mixmaker. Недоступность Discord не отменяет валидное создание Event.

Discord-роли состава

Подтверждённый Roster является источником desired state для внешних Discord-ролей. Каждый участник получает временную роль с Team.Name, роль своего slot-а Tank/Damage/Support, а капитан — отдельную ${Team.Name} Captain. Discord role ID и выданные ботом назначения сохраняются отдельно от доменных Team и Account, чтобы повторная синхронизация была идемпотентной.

Team/captain roles принадлежат конкретному Event и удаляются при отмене, завершении, удалении или возврате к редактированию roster. Tank/Damage/Support общие для guild: после cleanup их участники пересчитываются по всем оставшимся активным событиям. Guest и пользователь, отсутствующий в guild, не блокируют подтверждение состава и фиксируются как предупреждение интеграции. Для связанного Discord-аккаунта вне guild синхронизация остаётся pending и автоматически выдаёт актуальные роли после его вступления.

Для каждого Event бот также поддерживает четыре временные RSVP-роли: общую Зарегистрирован: {Event.Name} для любого явного ответа и взаимоисключающие Идёт, Возможно, Не идёт. Изменение EventRegistration атомарно меняет desired status-role; удаление регистрации снимает все RSVP-назначения. Эти роли сохраняются после закрытия регистрации и удаляются вместе с другими event-specific ролями при завершении, отмене или удалении Event.

В списке участников Discord отдельными группами отображаются только принадлежность к Team и общая регистрация. Роль команды располагается выше registration-role: распределённый игрок показывается под своей командой, а нераспределённый ответивший — в группе регистрации. Ролевой slot, конкретный RSVP-статус и Captain не влияют на группировку.

Mixmaker является источником истины только для Discord role ID, сохранённых как managed. Периодическая полная сверка восстанавливает удалённые managed roles и точный набор их участников, включая снятие лишних назначений. Любые роли сервера вне managed mapping неприкосновенны.

Жизненный цикл события

Событие проходит серверно контролируемые состояния:

  1. RegistrationOpen — игроки меняют RSVP, Admin добавляет гостей;
  2. RegistrationClosed — список участников зафиксирован, обычные RSVP заблокированы;
  3. Balancing — создаются варианты команд и резерв;
  4. RostersDraft — Admin выбирает вариант, меняет игроков одной роли местами и заменяет их резервными;
  5. RostersConfirmed — составы 1/2/2 и капитаны проверены и заблокированы;
  6. BracketDraft — staff задаёт порядок Bo3 и источники Team / Winner / Loser для слотов;
  7. Live — созданы готовые серии, разрешены только действия текущего шага жеребьёвки, банов или результата;
  8. Completed — выполнены все запланированные матчи;
  9. Cancelled — Admin отменил событие; оно остаётся в календаре, но игровые действия заблокированы.

Переходы выполняются отдельными командами API с проверкой ожидаемой версии. До запуска скрима staff-пользователь может вернуться на один этап назад: черновик удаляется при возврате из RostersDraft, а состав снова разблокируется при возврате из RostersConfirmed. Возврат из Live и Completed запрещён. Нельзя начать скрим без минимум двух полных команд, валидного состава 1/2/2 и капитана в каждой команде. После RostersConfirmed обычная перестановка запрещена. Аварийная замена во время Live доступна staff-пользователю, должна сохранять ролевой состав и записывается в аудит; если заменён капитан, назначение переходит вошедшему вместо него игроку. Полное удаление события является отдельной staff-командой и каскадно удаляет его RSVP, составы, roster draft, серии, турнир и event-scoped аудит.

Team

Состав игроков, назначенный staff-пользователем капитан, цвет/название и рассчитанные показатели силы. Капитан должен входить в текущий состав команды; переназначение фиксируется в аудите. После запуска скрима капитан может задать своей команде отображаемое название; staff может переименовать любую команду, изменение синхронизируется с live- и spectator-экранами.

  • общий средний рейтинг;
  • средний рейтинг по каждой роли;
  • распределение игроков по ролям;
  • оценка доверия к балансу.

Ruleset

Версионируемый набор правил серии:

  • формат Bo3;
  • последовательность режимов;
  • пулы карт по режимам;
  • порядок банов карт и героев;
  • лимиты банов по ролям;
  • ограничения повторных банов в серии.

Series

Матч двух команд в рамках события или турнирной сетки. Хранит жеребьёвку, порядок первого бана, карты, счёт, статус и итогового победителя.

MapResult

Исход отдельной карты: победившая команда либо ничья, счёт/заметка при необходимости и автор фиксации. Результат карты обновляет счёт серии; завершённый Bo3 определяет победителя автоматически. Организатор может исправить ошибочный результат, при этом изменение попадает в аудит.

MapDraft

Для первой карты — пошаговое исключение карт из пула до одной оставшейся. Вторую и последующие карты напрямую выбирает команда, проигравшая предыдущую карту; после ничьей право выбора получает команда, проигравшая начальный жребий. Ранее сыгранные карты исключаются.

HeroDraft

Четыре бана перед картой в порядке A → B → A → B. Каждая команда выбирает двух героев разных ролей. Героя, которого команда уже банила в этой серии, она не может забанить снова; героя, ранее забаненного соперником, банить можно.

Tournament

Versioned граф матчей, в котором каждый слот ссылается на конкретную Team либо Winner/Loser матча из предыдущей колонки. Во время редактирования сохраняются промежуточные состояния с пустыми слотами, повторяющимися командами и несколькими матчами в последней колонке; полные инварианты применяются при подтверждении сетки. Готовые зависимости материализуются в отдельные Bo3-серии автоматически; несколько готовых матчей могут идти параллельно. Подтверждённая последняя колонка содержит один матч, победитель которого становится общим победителем. Для трёх команд стартовый шаблон проводит AB, затем Loser(M1)C, затем финал победителей первых двух матчей.

Правила текущего регламента

Карты

Базовая последовательность Bo3:

  1. Control;
  2. Hybrid или Escort;
  3. Control-тайбрейкер, если счёт 1:1.

Альтернативная последовательность: Control → Push → Escort.

Первый бан первой карты определяется жеребьёвкой. Затем команды по очереди исключают карты внутри пула режима, пока не останется одна. После завершения карты FSM переходит в MapPick: проигравшая команда выбирает следующую доступную карту, после чего начинается hero draft.

Герои

  • перед каждой картой банятся четыре героя;
  • порядок: A → B → A → B;
  • каждая команда банит двух героев разных ролей;
  • один и тот же герой может быть забанен командой только один раз за серию;
  • бан героя соперником не расходует собственную возможность забанить этого героя позже.

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

Целевая функция должна учитывать не только близость общего среднего рейтинга команд, но и:

  • разницу средних рейтингов по ролям;
  • корректность состава 1 Tank / 2 Damage / 2 Support;
  • назначение игроков на предпочитаемые роли;
  • разведение по разным командам игроков из avoid-списков;
  • попадание выбранных игроком союзников в одну команду;
  • запрет повторения составов при регулярных играх;
  • заранее заданные пары или ограничения «вместе/раздельно».

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

Жёсткие ограничения 1/2/2 и уникальности игроков всегда важнее предпочтений. Разница рейтингов имеет больший вес, чем пожелания. В MVP используется детерминированная эвристика: несколько начальных назначений, взвешенная целевая функция и локальные обмены игроков одной роли. Архитектура допускает замену на CP-SAT/MILP solver.

Ручная корректировка не изменяет исходные варианты балансировщика: выбранный вариант копируется в редактируемый roster draft. Обмен допускается только между слотами одинаковой роли. Замена из резерва назначает игрока в конкретный ролевой слот и пересчитывает показатели обеих команд и резерва.

Доменные события

  • EventCreated;
  • PlayerJoinedEvent;
  • EventRegistrationChanged;
  • TeamsBalanced;
  • TeamCaptainAssigned;
  • CoinTossResolved;
  • MapBanned;
  • MapSelected;
  • HeroBanned;
  • MapResultRecorded;
  • MapResultCorrected;
  • SeriesCompleted;
  • BracketAdvanced.