Рейтинговая система из утверждённого docs/rating/rating-system.md (#22) реализована целиком: многопользовательский Elo с множителем отрыва. Параметры: шкала 1500/400, K 64 → 16 за 20 партий, вес стола G(N), темп / цели / миры, близость по типу победы, clamp [0.5, 2].
Движок.scoring.py — порт rate_match из эталона simulate.py.
Витрины. Топ, профиль, главная и статистика группы больше не агрегируются в SQL: они проигрывают завершённые партии по порядку played_at, finished_at, id.
Цепочки две: общая и групповая.
Правка прошлой партии пересчитывает всё после неё.
«Лучшая партия» определяется по ΔR, лучшая/худшая фракция — по среднему S − E.
Ввод.
В форме завершения, правке результатов и админке: раунд окончания и цели/миры у каждого игрока (всё необязательно, уходит в общий черновик).
В настройках группы — галочка «9 раундов при 5–6 игроках». Она снимается в партию при старте.
Причина last_standing ставится сама, когда невыбывший игрок один; сервер проверяет это правило.
Несогласованный ввод даёт предупреждения, отправку они не блокируют.
Отображение.
Общий топ: без столбца «Поб», «Очки» → «Рейтинг», рейтинг — целое число.
Справка переписана.
Уведомлений игрокам и пояснения на странице топа здесь нет: систему оповещений делаете вы, до релиза рейтинга.
Коммиты
e58b4f6 Рейтинг: движок Elo с множителем отрыва и тесты на примеры документа
8aa6bb7 Рейтинг: раунд, цели, миры, правило 9 раундов и причина last_standing
6ea4a67 Рейтинг: витрины статистики на проигрывании истории
cc64bb4 Рейтинг: ввод деталей партии и предупреждения в формах
adc47be Рейтинг: топ, профиль и справка под новую шкалу
все ΔR трёх синтетических сезонов (полные партии и история без деталей) совпадают с simulate.py до 1e-9;
монотонность и сумма-ноль;
валидация раунда, миров выбывших и last_standing в финише, правке игрока и админа;
снимок правила 9 раундов;
пересчёт после правки прошлой партии;
групповая цепочка;
лучшая партия по ΔR;
одна загрузка истории на главной.
npm run build (tsc + vite) — зелёный; schema.d.ts перегенерирован из живого OpenAPI.
Миграция 0014, копия dev-БД (без CHECK у matches):
число строк до и после совпадает, FK и индексы на месте;
повторный запуск ничего не меняет;
бэкфилл проставил last_standing пяти партиям с одним выжившим.
Миграция 0014, свежая БД со схемой origin/dev (с CHECK), с участниками, вложением и черновиком:
matches пересоздана с ck_match_status и расширенным ck_match_win_reason;
дочерние строки на месте, integrity_check и foreign_key_check чистые;
повторный запуск после stamp 0013 ничего не меняет;
downgrade возвращает прежнюю схему.
Сквозной HTTP-сценарий через vite-прокси на копии БД:
снимок 9 раундов у партии на 5 игроков;
черновик с целями и мирами;
422 «по целям» при одном выжившем;
завершение last_standing с мирами выбывших = 0;
пересчёт топа, главной и группы;
смена галочки группы не трогает начатую партию.
Логика фронта:finishWarnings (все пять видов предупреждений, пустые поля, ничья за 1-е, last_standing) и reasonForSurvivors проверены на скомпилированном esbuild коде.
Не проверено: ручной прогон интерфейса в браузере — расширение Chrome было не подключено. Вёрстку новых полей (цели/миры в PlaceEditor, блок «Итог партии», топ без столбца побед) стоит посмотреть глазами, в том числе на ширине телефона.
Что ломается (Compat/Breaking)
Числа и порядок топа. Около 1500 вместо около 50, это другая величина. Данные не трогаются: рейтинг вычисляется из истории.
API.
OverallStats.score / LeaderboardEntry.score → целый рейтинг; FactionStat.score → средний S − E × 100.
Новое значение win_reason = last_standing.
Партия с одним невыбывшим принимается только с last_standing, иначе 422 с понятным текстом.
PATCH /groups/{id} принимает {name?, nine_rounds_rule?}, старое тело {name} валидно.
БД (миграция 0014).
Добавляются nullable end_round, objectives, worlds и nine_rounds_rule со значением по умолчанию 0.
CHECK причины победы расширяется, если он в БД есть.
Бэкфилл last_standing.
Старые партии считаются без новых полей (раздел 4.8).
Пересоздание matches отказывает при PRAGMA foreign_keys=ON: иначе DROP удалил бы участников каскадом. Штатный alembic upgrade head из entrypoint.sh эту PRAGMA не включает.
Отклонения от плана
В 0014 CHECK заменяется только там, где он уже есть. Оказалось, что в старых БД (как dev) CHECK у matches нет вовсе. Пересоздавать таблицу ради добавления CHECK было бы лишним риском.
Причина победы, раунд окончания и предупреждения вынесены в общий компонент MatchOutcomeFields: он используется в форме завершения и в правке результатов.
Ручная проверка в браузере не выполнена (см. «Проверки»).
## Что сделано
Рейтинговая система из утверждённого `docs/rating/rating-system.md` (#22) реализована целиком: многопользовательский Elo с множителем отрыва. Параметры: шкала 1500/400, K 64 → 16 за 20 партий, вес стола `G(N)`, темп / цели / миры, близость по типу победы, `clamp [0.5, 2]`.
**Движок.** `scoring.py` — порт `rate_match` из эталона `simulate.py`.
**Витрины.** Топ, профиль, главная и статистика группы больше не агрегируются в SQL: они проигрывают завершённые партии по порядку `played_at, finished_at, id`.
- Цепочки две: общая и групповая.
- Правка прошлой партии пересчитывает всё после неё.
- «Лучшая партия» определяется по ΔR, лучшая/худшая фракция — по среднему `S − E`.
**Ввод.**
- В форме завершения, правке результатов и админке: раунд окончания и цели/миры у каждого игрока (всё необязательно, уходит в общий черновик).
- В настройках группы — галочка «9 раундов при 5–6 игроках». Она снимается в партию при старте.
- Причина `last_standing` ставится сама, когда невыбывший игрок один; сервер проверяет это правило.
- Несогласованный ввод даёт предупреждения, отправку они не блокируют.
**Отображение.**
- Общий топ: без столбца «Поб», «Очки» → «Рейтинг», рейтинг — целое число.
- Справка переписана.
Уведомлений игрокам и пояснения на странице топа здесь **нет**: систему оповещений делаете вы, до релиза рейтинга.
## Коммиты
- `e58b4f6` Рейтинг: движок Elo с множителем отрыва и тесты на примеры документа
- `8aa6bb7` Рейтинг: раунд, цели, миры, правило 9 раундов и причина last_standing
- `6ea4a67` Рейтинг: витрины статистики на проигрывании истории
- `cc64bb4` Рейтинг: ввод деталей партии и предупреждения в формах
- `adc47be` Рейтинг: топ, профиль и справка под новую шкалу
## Проверки
- `.\.venv\Scripts\python.exe -m pytest` — **189 passed**. Новое:
- все 15 примеров раздела 6 с числами документа;
- все ΔR трёх синтетических сезонов (полные партии и история без деталей) совпадают с `simulate.py` до 1e-9;
- монотонность и сумма-ноль;
- валидация раунда, миров выбывших и `last_standing` в финише, правке игрока и админа;
- снимок правила 9 раундов;
- пересчёт после правки прошлой партии;
- групповая цепочка;
- лучшая партия по ΔR;
- одна загрузка истории на главной.
- `npm run build` (tsc + vite) — зелёный; `schema.d.ts` перегенерирован из живого OpenAPI.
- **Миграция 0014, копия dev-БД** (без CHECK у `matches`):
- число строк до и после совпадает, FK и индексы на месте;
- повторный запуск ничего не меняет;
- бэкфилл проставил `last_standing` пяти партиям с одним выжившим.
- **Миграция 0014, свежая БД со схемой `origin/dev`** (с CHECK), с участниками, вложением и черновиком:
- `matches` пересоздана с `ck_match_status` и расширенным `ck_match_win_reason`;
- дочерние строки на месте, `integrity_check` и `foreign_key_check` чистые;
- повторный запуск после `stamp 0013` ничего не меняет;
- `downgrade` возвращает прежнюю схему.
- **Сквозной HTTP-сценарий** через vite-прокси на копии БД:
- снимок 9 раундов у партии на 5 игроков;
- черновик с целями и мирами;
- 422 «по целям» при одном выжившем;
- завершение `last_standing` с мирами выбывших = 0;
- пересчёт топа, главной и группы;
- смена галочки группы не трогает начатую партию.
- **Логика фронта:** `finishWarnings` (все пять видов предупреждений, пустые поля, ничья за 1-е, `last_standing`) и `reasonForSurvivors` проверены на скомпилированном esbuild коде.
- **Не проверено:** ручной прогон интерфейса в браузере — расширение Chrome было не подключено. Вёрстку новых полей (цели/миры в `PlaceEditor`, блок «Итог партии», топ без столбца побед) стоит посмотреть глазами, в том числе на ширине телефона.
## Что ломается (`Compat/Breaking`)
- **Числа и порядок топа.** Около 1500 вместо около 50, это другая величина. Данные не трогаются: рейтинг вычисляется из истории.
- **API.**
- `OverallStats.score` / `LeaderboardEntry.score` → целый рейтинг; `FactionStat.score` → средний `S − E` × 100.
- Новое значение `win_reason = last_standing`.
- Партия с одним невыбывшим принимается только с `last_standing`, иначе 422 с понятным текстом.
- `PATCH /groups/{id}` принимает `{name?, nine_rounds_rule?}`, старое тело `{name}` валидно.
- **БД (миграция 0014).**
- Добавляются nullable `end_round`, `objectives`, `worlds` и `nine_rounds_rule` со значением по умолчанию 0.
- CHECK причины победы расширяется, если он в БД есть.
- Бэкфилл `last_standing`.
- Старые партии считаются без новых полей (раздел 4.8).
- Пересоздание `matches` отказывает при `PRAGMA foreign_keys=ON`: иначе `DROP` удалил бы участников каскадом. Штатный `alembic upgrade head` из `entrypoint.sh` эту PRAGMA не включает.
## Отклонения от плана
- В 0014 CHECK заменяется только там, где он уже есть. Оказалось, что в старых БД (как dev) CHECK у `matches` нет вовсе. Пересоздавать таблицу ради добавления CHECK было бы лишним риском.
- Причина победы, раунд окончания и предупреждения вынесены в общий компонент `MatchOutcomeFields`: он используется в форме завершения и в правке результатов.
- Ручная проверка в браузере не выполнена (см. «Проверки»).
Closes #23
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
scoring.py получает движок из docs/rating/rating-system.md: ожидание пары,
K 64 → 16 за 20 партий, вес стола G(N), множитель отрыва (темп, цели, миры,
clamp [0.5, 2]) и близость по типу победы, включая last_standing. rate_match
и replay — чистые функции без БД; replay отдаёт рейтинги без округления,
ΔR и результат относительно ожидания по каждой партии.
Тесты: 15 примеров раздела 6 с числами документа, совпадение констант
и всех ΔR сезона с эталоном simulate.py (полные партии и история без деталей),
монотонность и сумма-ноль. League Points пока остаётся в модуле — витрины
переводятся отдельным коммитом. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Модели и миграция 0014: groups.nine_rounds_rule, снимок правила в matches,
matches.end_round, match_participants.objectives/worlds. CHECK причины победы
расширяется, только если он в БД есть (старые БД без CHECK таблицу не
пересоздают); пересоздание matches отказывает при PRAGMA foreign_keys=ON,
иначе DROP унёс бы участников каскадом. Бэкфилл: last_standing у завершённых
партий с одним невыбывшим. Проверено на копии dev-БД и на схеме origin/dev
с CHECK: строки, CHECK, FK и индексы на месте, повтор и downgrade работают.
API и валидация: end_round от 1 до лимита раундов партии (9 при хоумруле
и 5+ игроках), у выбывшего миров 0, last_standing ровно при одном невыбывшем —
в завершении и правке (игрока и админа); черновик хранит раунд, цели и миры
без проверки правила. MatchRead отдаёт end_round, снимок правила и max_rounds.
PATCH /groups/{id} принимает nine_rounds_rule вместе с названием. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
SQL-агрегат League Points (SCORED_CTE, сглаженное среднее) заменён
проигрыванием завершённых партий по порядку played_at, finished_at, id:
load_history — один запрос на партии с участниками, scoring.replay считает
рейтинг. Цепочки две: общая (все партии) и групповая (только партии группы,
K — по партиям внутри группы). Правка или удаление прошлой партии
пересчитывает всё после неё без отдельной логики.
Топ, профиль, главная и статистика группы берут игры, победы, винрейт
и среднее место из той же истории; score — рейтинг целым числом, сортировка
по неокруглённому. Главная грузит историю один раз. Лучшая партия — наибольший
ΔR в общей цепочке, лучшая/худшая фракция — средний S − E. League Points
из scoring.py удалён.
Тесты: дуэль новичков ±32, ранжирование после 10 партий, пересчёт после
правки прошлой партии, отдельная групповая цепочка, лучшая партия по ΔR,
одна загрузка истории на главной. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Форма завершения и правка результатов (MatchDetailPage): у каждого игрока
в PlaceEditor поля «цели» и «миры» на конец партии (у выбывшего миры
заблокированы нулём), общий блок «Итог партии» — причина победы и раунд
окончания из max_rounds партии. Всё новое уходит в общий черновик.
Причина «последний выживший» ставится сама при одном невыбывшем, выбор
заблокирован; вернули второго — причина сбрасывается и её нужно выбрать.
То же в админской правке партии.
Предупреждения (domain/finishWarnings.ts, отправку не блокируют): тип победы
и цели/миры лидеров не согласуются, досрочный конец без N целей у победителя,
у соперника целей больше, чем у победителя. Настройки группы: переключатель
«9 раундов при 5–6 игроках» через PATCH /groups/{id}. schema.d.ts
перегенерирован. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Общий топ: столбец «Поб» и сортировка по победам убраны, чтобы четырёхзначный
рейтинг помещался в строку; «Очки» → «Рейтинг» (и в карточке профиля).
Справка переписана под Elo: шкала 1500/400, ожидание и формула ΔR, K новичка,
вес стола, множитель отрыва и близость по типу победы, необязательность
раунда, целей и миров, «последний выживший», лимит 8/9 раундов, примеры
из документа, общий и групповой рейтинг, лучшая партия по приросту.
В документе рейтинга статус — утверждено и реализовано. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Что сделано
Рейтинговая система из утверждённого
docs/rating/rating-system.md(#22) реализована целиком: многопользовательский Elo с множителем отрыва. Параметры: шкала 1500/400, K 64 → 16 за 20 партий, вес столаG(N), темп / цели / миры, близость по типу победы,clamp [0.5, 2].Движок.
scoring.py— портrate_matchиз эталонаsimulate.py.Витрины. Топ, профиль, главная и статистика группы больше не агрегируются в SQL: они проигрывают завершённые партии по порядку
played_at, finished_at, id.S − E.Ввод.
last_standingставится сама, когда невыбывший игрок один; сервер проверяет это правило.Отображение.
Уведомлений игрокам и пояснения на странице топа здесь нет: систему оповещений делаете вы, до релиза рейтинга.
Коммиты
e58b4f6Рейтинг: движок Elo с множителем отрыва и тесты на примеры документа8aa6bb7Рейтинг: раунд, цели, миры, правило 9 раундов и причина last_standing6ea4a67Рейтинг: витрины статистики на проигрывании историиcc64bb4Рейтинг: ввод деталей партии и предупреждения в формахadc47beРейтинг: топ, профиль и справка под новую шкалуПроверки
.\.venv\Scripts\python.exe -m pytest— 189 passed. Новое:simulate.pyдо 1e-9;last_standingв финише, правке игрока и админа;npm run build(tsc + vite) — зелёный;schema.d.tsперегенерирован из живого OpenAPI.matches):last_standingпяти партиям с одним выжившим.origin/dev(с CHECK), с участниками, вложением и черновиком:matchesпересоздана сck_match_statusи расширеннымck_match_win_reason;integrity_checkиforeign_key_checkчистые;stamp 0013ничего не меняет;downgradeвозвращает прежнюю схему.last_standingс мирами выбывших = 0;finishWarnings(все пять видов предупреждений, пустые поля, ничья за 1-е,last_standing) иreasonForSurvivorsпроверены на скомпилированном esbuild коде.PlaceEditor, блок «Итог партии», топ без столбца побед) стоит посмотреть глазами, в том числе на ширине телефона.Что ломается (
Compat/Breaking)OverallStats.score/LeaderboardEntry.score→ целый рейтинг;FactionStat.score→ среднийS − E× 100.win_reason = last_standing.last_standing, иначе 422 с понятным текстом.PATCH /groups/{id}принимает{name?, nine_rounds_rule?}, старое тело{name}валидно.end_round,objectives,worldsиnine_rounds_ruleсо значением по умолчанию 0.last_standing.matchesотказывает приPRAGMA foreign_keys=ON: иначеDROPудалил бы участников каскадом. Штатныйalembic upgrade headизentrypoint.shэту PRAGMA не включает.Отклонения от плана
matchesнет вовсе. Пересоздавать таблицу ради добавления CHECK было бы лишним риском.MatchOutcomeFields: он используется в форме завершения и в правке результатов.Closes #23
🤖 Generated with Claude Code
https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Модели и миграция 0014: groups.nine_rounds_rule, снимок правила в matches, matches.end_round, match_participants.objectives/worlds. CHECK причины победы расширяется, только если он в БД есть (старые БД без CHECK таблицу не пересоздают); пересоздание matches отказывает при PRAGMA foreign_keys=ON, иначе DROP унёс бы участников каскадом. Бэкфилл: last_standing у завершённых партий с одним невыбывшим. Проверено на копии dev-БД и на схеме origin/dev с CHECK: строки, CHECK, FK и индексы на месте, повтор и downgrade работают. API и валидация: end_round от 1 до лимита раундов партии (9 при хоумруле и 5+ игроках), у выбывшего миров 0, last_standing ровно при одном невыбывшем — в завершении и правке (игрока и админа); черновик хранит раунд, цели и миры без проверки правила. MatchRead отдаёт end_round, снимок правила и max_rounds. PATCH /groups/{id} принимает nine_rounds_rule вместе с названием. #23 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsdФорма завершения и правка результатов (MatchDetailPage): у каждого игрока в PlaceEditor поля «цели» и «миры» на конец партии (у выбывшего миры заблокированы нулём), общий блок «Итог партии» — причина победы и раунд окончания из max_rounds партии. Всё новое уходит в общий черновик. Причина «последний выживший» ставится сама при одном невыбывшем, выбор заблокирован; вернули второго — причина сбрасывается и её нужно выбрать. То же в админской правке партии. Предупреждения (domain/finishWarnings.ts, отправку не блокируют): тип победы и цели/миры лидеров не согласуются, досрочный конец без N целей у победителя, у соперника целей больше, чем у победителя. Настройки группы: переключатель «9 раундов при 5–6 игроках» через PATCH /groups/{id}. schema.d.ts перегенерирован. #23 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd