Рейтинг TAC: как он рассчитывается
Рейтинг оценивает уровень игры участника (и постоянной пары игроков) на основе зарегистрированных результатов игр. На этой странице сначала дается общее объяснение, а затем — подробное описание для разработчиков.
Общая информация
Что такое рейтинг TAC?
Рейтинг — это число, которое показывает уровень игры в TAC — как рейтинг Эло в шахматах, но с более современным методом расчёта. Он рассчитывается только на основе партий, занесённых в приложение. Очки не начисляются вручную.
Существует два вида рейтинга: индивидуальный рейтинг для каждого участника и командный рейтинг для постоянной пары игроков (двух участников, которые играют вместе).
Когда появляется рейтинг
Рейтинг рассчитывается только в том случае, если на обеих сторонах участвует как минимум один зарегистрированный участник. Гости (игроки без учётной записи) не учитываются. Таким образом, если один участник играет только с гостями или против них, сравнение не происходит, а значит, рейтинг не начисляется. Для получения командного рейтинга необходимо, чтобы два участника без гостей играли вместе в одной команде.
Как меняется рейтинг?
После каждой рейтинговой партии рейтинг обновляется. Победа над более сильным соперником заметно повышает его, а поражение от более слабого — снижает ещё сильнее. Победа над гораздо более слабым соперником почти не влияет на рейтинг, так как такой результат был ожидаем.
У каждого рейтинга есть показатель неопределённости: в начале система мало знает о тебе, поэтому твой рейтинг может заметно меняться. Чем больше ты играешь, тем точнее и стабильнее становится оценка.
Предварительный рейтинг
Пока у участника меньше 10 рейтинговых партий, рейтинг считается предварительным — он ещё недостаточно информативен для честного сравнения.
Различные таблицы лидеров
Рейтинг ведётся раздельно по разным категориям, чтобы сравнение оставалось честным:
- Глобальный — по всем клубам.
- Клуб — только партии в рамках одного клуба.
- Встреча — только партии конкретной встречи/турнира.
- Сезон — только партии в рамках одного сезона в клубе.
Неактивность
Если долго не играть, оценка рейтинга со временем становится менее точной. Сам рейтинг (оценочная сила) при этом остаётся прежним — снижается только уровень уверенности до следующей партии.
Техническое описание (для разработчиков)
Модель и начальные значения
Рейтинг основан на OpenSkill (байесовский метод Венга-Лина с моделью Плакетта-Люса). Эталонная реализация работает на Java с библиотекой com.pocketcombats:openskill; сам метод не зависит от языка программирования и подробно описан ниже.
Каждый рейтинг — это нормальное распределение со средним значением μ (расчетная сила) и стандартным отклонением σ (неопределенность). Начальные значения для нового игрока:
μ₀ = 25.0 σ₀ = 25 / 3 ≈ 8.3333
Отображаемое сравнимое значение — это консервативный ординал — сила за вычетом поправки на неопределённость:
ordinal = μ − Z · σ (Z = 3)
Для параметров партии используются стандартные значения библиотеки (Weng-Lin) без переопределения: β = σ₀ / 2 ≈ 4.167 (разброс между силой и текущей формой) и τ = σ₀ / 100 ≈ 0.083 (аддитивный динамический коэффициент, который немного увеличивает σ перед каждым обновлением, чтобы рейтинг оставался динамичным).
Оценить партию
Партия состоит из двух сторон (команда A и команда B). Рейтинг каждой стороны формируется из индивидуальных рейтингов игроков (сумма μ и сумма σ² соответственно, DefaultTeamRatingAggregator). На основе этого определяются ранги:
- Сторона победителя: ранг
1, сторона проигравшего: ранг2. - Ничья: обе стороны — ранг
1.
На основе этих рангов модель Плакета — Люса обновляет данные каждого участника: сила μ смещается в сторону фактического результата, а неопределённость σ снижается с каждой новой партией. Величина корректировки зависит от разницы между ожидаемым и фактическим результатом, а также от текущего уровня неопределённости.
Вероятность победы стороны А над стороной Б (для прогнозов/матчмейкинга) рассчитывается на основе агрегированных значений команд с помощью кумулятивной функции нормального распределения Φ:
P(A > B) = Φ( (μ_A − μ_B) / √(σ_A² + σ_B² + β²) )
Две проекции: одиночная и парная
Каждая партия засчитывается в двух независимых проекциях:
- Индивидуальный — субъект это ID участника. Требуется как минимум один реальный участник с обеих сторон.
- Пара (команда) — субъект представляет собой неупорядоченную пару участников. Полная сторона (2 участника) образует пару; сторона с гостем пару не образует.
Распределение по командам на основе собранных данных: result_player.team принимает значение A или B; победитель указан в result.winner_team (если поле пустое, партия считается ничьей). Игроки без ID участника (гости) исключаются из расчёта рейтинга. Если одна сторона состоит только из гостей, она сама не учитывается в рейтинге, но служит в качестве стандартного противника (субъекта с начальными значениями), чтобы другая сторона всё равно могла быть засчитана. Партии с exclude_from_rating полностью пропускаются.
Партиции и хронологическое повторение
Партиция — это кортеж (scope, scopeRefId). Есть четыре области (scope): GLOBAL (без Ref), CLUB (clubId), EVENT (eventId), SEASON (seasonId). Каждая партиция считается независимо — одна партия одновременно попадает в несколько партиций.
Рейтинги зависят от последовательности и взаимосвязаны (результат партии зависит от рейтингов до неё). Поэтому они не пересчитываются инкрементально, а формируются путём хронологического пересчёта партий внутри раздела. История снимков по каждой партии (μ/σ до и после каждой игры) является источником истины; материализованные таблицы member_rating / team_rating хранят только текущее состояние для быстрого чтения.
- Полный пересчёт: все партии раздела пересчитываются заново, начиная с исходных значений.
- Частичный повтор с определённого момента: состояние загружается из последнего снапшота *до* момента изменения (сидинг) и проигрывается заново с этой точки — снапшоты после этого момента удаляются.
Материализованные значения на субъекта
После пересчёта текущая запись для каждого участника перезаписывается:
| Поле | Значение |
|---|---|
| mu, sigma | Значения после последней партии |
| ordinal | mu − 3 · sigma (консервативно) |
| ordinalPrevious | Порядковый номер перед последней партией (для стрелки тренда) |
| peakOrdinal / peakAt | лучшее достигнутое место и время |
| gamesCount | Количество засчитанных партий в разделе |
| provisional | да, пока gamesCount < 10 |
| lastGameAt | Время последней рейтинговой партии |
Снижение рейтинга за неактивность
Ежедневная задача (по умолчанию: 04:00) повышает неопределённость неактивных рейтингов. Для каждого рейтинга, последняя партия в котором была более 30 дней назад, действует следующее:
σ ← min(σ + 0.05, 8.3333) ordinal ← μ − 3 · σ
- За каждую партию σ увеличивается на
0.05, с ограничениемσ_max = 8.3333(= начальное значение σ₀). - Изменяются только рейтинги ниже верхнего предела (одна команда Batch-UPDATE).
- Рамки EVENT не подвержены Decay (турнир — это фиксированный период времени).
Обработка (асинхронная, в конечном счёте)
Расчёт никогда не происходит в режиме inline при сохранении результата, а выполняется отложенно: изменение результата помечает затронутые разделы после коммита (AFTER_COMMIT) как «грязные» (добавление в очередь пересчёта). Отдельный воркер обрабатывает очередь последовательно и заново пересчитывает каждый затронутый раздел, начиная с момента изменения. Благодаря этому связанные рейтинги остаются согласованными, а процесс сохранения не блокируется.
Изменение результата всегда затрагивает: GLOBAL, клуб, в котором прошла партия, встречу/турнир (старый и новый, если дата была перенесена), а также любой сезон, в период которого попадает время партии.