VanguardReMaster/IMPLEMENTATION_CHECKLIST.md

351 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Базовая версия — чеклист реализации
Практический план: что писать, в каком порядке и когда считать готовым.
Дизайн-решения и обоснования — в `DEVELOPMENT_CHECKLIST.md`. Данные карт — в `content/cards-mvp.md`.
---
## 0. Границы базовой версии
**Входит:**
- Платформа: Windows. Unity + WPF-лаунчер.
- Формат: `Classic`, один сет — BT01 + TD01 + TD02 (94 карты).
- Аккаунт, коллекция, полный декбилдер, магазин бустеров и триал-дек.
- 2 очереди: Classic Ranked и Classic Casual. Матч 1×1, BO1.
- Полный игровой цикл: матч → результат → Gold/XP → бустер → карта в коллекцию.
- Базовый туториал + выдача триал-деки и 5 токенов.
- Локализация RU + EN.
- Лаунчер с проверкой версии и докачкой.
**Не входит:** турниры, команды, территории, чат, друзья, баттлпасс, квесты, Draft, косметика, мобилка, реплеи в клиенте (только серверная запись).
### Стек (зафиксирован)
| Слой | Технология |
|---|---|
| Сервер | **.NET 10 LTS**, ASP.NET Core, Orleans, EF Core, PostgreSQL 16, Redis 7 |
| Протокол матча | TCP + MessagePack, кадры с префиксом длины |
| Мета-API | HTTP/JSON, JWT |
| Клиент | Unity 6 LTS, URP, Addressables, Input System, Unity Localization, DOTween |
| Лаунчер | WPF, **.NET 10 LTS**, CommunityToolkit.Mvvm |
| Контейнеры | Docker Compose, nginx |
### Оценка (один разработчик, полный день)
| Этап | Дни |
|---|---|
| 1. Репозиторий и каркас | 2–3 |
| 2. Контент-пайплайн | 4–6 |
| 3. Игровое ядро | 20–30 |
| 4. Скрипты 94 карт | 15–25 |
| 5. Сеть и матч | 8–12 |
| 6. Мета-API | 10–14 |
| 7. Клиент: каркас и меню | 15–20 |
| 8. Клиент: боевая сцена | 20–30 |
| 9. Туториал | 5–7 |
| 10. Лаунчер | 5–7 |
| 11. QA и сборка | 7–10 |
| **Итого** | **~110–165** |
---
## 1. Репозиторий и каркас `[2–3 дня]`
- [x] `git init`, ветка `master`.
- [x] `.gitignore`: Unity (`Library/`, `Temp/`, `Logs/`, `UserSettings/`, `Builds/`), .NET (`bin/`, `obj/`), WPF (`publish/`), секреты (`.env`, `appsettings.Production.json`).
- [x] Отдельно игнорировать только Unity-генерируемые `*.csproj`/`*.sln` в `/client`, чтобы не убить `server/*.sln`.
- [x] `.gitattributes`: `* text=auto eol=lf`, `*.unity`, `*.prefab`, `*.asset` → `merge=unityyamlmerge`, `*.png`/`*.wav` → `binary`.
- [x] Структура (на 20.10.2026 — `server`, `content`, `tools`; `client` и `launcher` появятся со своими блоками):
```
/server
Vanguard.slnx
/src/Vanguard.Rules чистое ядро правил, без Orleans и БД
/src/Vanguard.Content загрузчик и валидатор контента
/src/Vanguard.Shared контракты, DTO, MessagePack
/src/Vanguard.Cli headless-раннер матчей
/tests/Vanguard.Rules.Tests
/content/sets/BT01.yaml
/content/sets/TD01.yaml
/content/sets/TD02.yaml
/tools/import_cards.py
```
- [x] `Directory.Build.props`: `net10.0`, `Nullable=enable`, `LangVersion=latest`.
- [x] `docker-compose.yml`: postgres, redis (AOF), game-server с контентом.
- [x] `docker compose up` поднимает БД, Redis и API; `/health` отвечает.
- [x] CI: `.gitea/workflows/ci.yml` проходит на `home-runner`; Actions
включены для репозитория.
**Готово, когда:** `docker compose up` + `dotnet run` дают живой `/health`.
---
## 2. Контент-пайплайн `[4–6 дней]`
- [x] Схема YAML: `printingId`, `nameId`, `cardType`, `grade`, `power`, `shield`, `critical`, `triggerType`, `clan`, `rarity`, `isSentinel`, `isRestraint`, `name_en/ru`, `text_en/ru`, `imageKey`.
- [x] Импорт с официального сайта → три YAML-файла (BT01, TD01, TD02). 94 карты, 124 принта.
- [x] Валидатор:
- [x] `sum(deck) = 50`, `≤4` по `nameId`, `≤4` Sentinel, `≤4` Heal.
- [x] Инвариант щита: grade 0 → 5000 **только** у Draw, иначе 10000.
- [x] Уникальность `printingId`, наличие `nameId`.
- [x] Ссылка SP-принта на существующий базовый номер.
- [x] Загрузчик: YAML → in-memory каталог, используется и сервером, и тестами.
- [x] Ключевые слова и шаблоны: `content/keywords.yaml`, `content/translations/ru.yaml`, команды `translations build|check`.
- [ ] Сид в PostgreSQL: `cards`, `printings`, `sets`, `card_legality` (пустая).
- [ ] Экспорт каталога для клиента (JSON + список Addressables-ключей).
**Готово:** `dotnet run --project server/src/Vanguard.Cli -- validate` → `94 карты, 124 принта, 0 ошибок`.
---
## 3. Игровое ядро `[20–30 дней]` — критический путь
Пишется **как чистая библиотека**: без Orleans, без БД, без сети. Вход — состояние + интент, выход — новое состояние + события.
- [x] `GameState`: игроки, зоны, фазы, счётчики, `rngState`.
- [x] Зоны: Vanguard, Rear-Guard (3 колонки × 2 ряда), Guardian, Soul, Damage, Drop, Trigger, Deck, Hand.
- [x] Юнит: `instanceId`, `printingId`, состояние Stand/Rest.
- [x] Damage: список карт с `faceUp`, скрыт от обоих игроков.
- [x] Фазы: Mulligan → Stand → Draw → Ride → Main → Battle → End.
- [x] Старт: выбор grade 0 → выставление → маллиган.
- [x] Действия: Ride, Call, Attack, Guard, Drive, Damage, Stand/Rest, Retire, Draw.
- [x] Поддержка (Boost) как отдельная способность: по умолчанию у 0–1 ранга, передаёт силу карте впереди в той же колонне; `BoostOverride` позволяет выдать или снять её эффектом.
- [x] Идеальная защита (Sentinel): щит 0; оплата сбросом карты того же клана полностью отменяет попадание; без оплаты не даёт ничего.
- [x] Перехват: карта 2 ранга из переднего ряда выходит в круг защиты, сила атаки сравнивается с ней; `InterceptOverride` и `InterceptFromBackRow` позволяют выдать способность эффектом.
- [x] Перемещение Арьергарда в пределах колонки; если круг занят — карты меняются местами; у центрального заднего ряда пары нет.
- [ ] Discard, Shuffle как отдельные интенты.
- [x] Число проверок принадлежит атакующей карте: 0–2 ранг — одна, 3+ — две; Арьергард по умолчанию без проверок; `DriveChecksOverride` меняет число (в том числе снимает двойную).
- [x] Результат боя: `Hit` / `Guarded` как событие.
- [x] Событийная шина: `Triggered` (OnRideOver, OnPlace, OnBoost, OnBoosted, OnDriveCheck, OnLeaveGuardian, OnPhaseStart, OnAttackDeclared, OnAttackHit), `Continuous` с пересчётом при запросе, `Activated` (повернуть себя + сброс карты).
- [ ] Событие OnDamageTaken и триггеры на получение урона.
- [x] Check Timing: сбор сработавших, порядок (активный игрок первым, выбор игрока), пауза на опциональные способности.
- [x] Выбор целей: `PendingChoice` + `ChooseCommand`, используется Blaster Blade для уничтожения Арьергарда противника.
- [x] Стоимости: CB, SB, сброс карты, поворот себя, уничтожение себя; «карты из поля в душу» и «карта из руки в колоду» — частными хелперами.
- [x] Выбор цели игроком (`PendingChoice` / `ChooseCommand`); «до N» / «ровно N» — `AskUnitMultiChoice`.
- [x] Выбор карты из руки как стоимости эффекта (Morgana: сброс → +4000).
- [x] Модификаторы силы/крита с источником и сроком жизни (`StatModifier`); выдача и снятие способностей эффектами (`GrantedAbility`).
- [x] Выбор от `min` до `max` целей на поле (`AskUnitMultiChoice`) для «до N» и «ровно N».
- [x] Запреты с источником и сроком жизни (`CardRestriction`): не вставать, не атаковать, не поддерживать, не перехватывать, не быть выбранным; снятие Сдерживания эффектом.
- [x] `OnAttackHit` срабатывает и при попадании по Арьергарду.
- [x] Поиск в колоде без просмотра: 0 совпадений — уведомление и перемешивание, 1 — сразу в руку, несколько — выбор; разные арты считаются разными картами.
- [x] Поиск с призывом в круг Арьергарда (Alfred) и с райдом (API готов; Llew и Aleph ждут стоимость «карты из поля в душу»).
- [x] Просмотр верхней карты с решением «оставить на верху / положить в низ» (Cocoa).
- [~] Слои модификаторов силы/крита: усиления от триггеров живут до конца хода, постоянных слоёв нет.
- [x] Валидация интентов: `Rejected(reason)` без изменения состояния.
- [x] Список легальных интентов на каждый момент (для UI и античита).
— `GameEngine.LegalCommands()` покрывает фазы, выборы, триггеры и активируемые способности.
- [~] Детерминированный RNG: один поток на партию, состояние в `GameState`.
- [x] Журнал событий + seed; сериализуемый реплей.
— `MatchRecord`, `MatchRecorder`, `CommandCodec`, `ReplayRunner`, `EventHasher`; запись переживает JSON и воспроизводится бит-в-бит по хэшу событий.
- [~] Защита от бесконечных циклов и дедлоков.
— 50 партий случайного бота в тестах и 1000 через `stress`; лимит 5000 шагов на партию в стресс-тесте. Глобального лимита шагов внутри движка нет.
- [x] Инварианты партии: 50 карт в зонах каждого игрока, уникальные `instanceId`, урон ≤ 6.
- [x] Карты «в пути» между зонами (`GameState.InTransit`) — например, найденная карта ждёт выбора круга.
**Сделано:** каркас движка (`Engine/`), все фазы, бой, способности, 66/66 карт, `LegalCommands`, случайный бот (1000 партий через `stress`), реплей с хэшем событий. Всего 117 тестов.
**Готово, когда:** два бота случайными легальными интентами доигрывают 1000 партий без падений, инварианты (сумма карт, счётчики ≥ 0) держатся, реплей воспроизводится бит-в-бит.
— выполнено: `dotnet run --project server/src/Vanguard.Cli -- stress 1000` → 1000/1000 без ошибок; реплей проверяется тестом.
---
## 4. Карты `[15–25 дней]`
- [ ] Хелперы: `Draw`, `Retire`, `SearchCall`, `SearchHand`, `SearchRide`, `Stand`, `SoulCharge`, `SoulBlast`, `CounterBlast`, `FlipDamage`, `ToSoul`, `ToDeck`, `Discard`, `LookTop`, `ModifyPower`, `ModifyCrit`, `GainKeyword`.
— большая часть уже есть в `GameEngine`; не хватает общих «карты из поля в душу» и «из руки в колоду» с выбором любых карт.
- [~] Шаблон карты: скрипт = класс `CardScript`, текст карты хранится в YAML (отдельного комментария с текстом нет).
- [x] Порядок: сначала простые триггеры, затем стоимости и активируемые способности.
- [x] По каждой карте: скрипт + тест «дано → сыграл → ожидаю» (для ключевых механик).
- [x] RU/EN тексты для всех 94.
- [x] Сила/щит для 14 карт, которых нет в BT01.
- [ ] Дашборд покрытия: `scripted / tested / total`.
— **66 из 66** карт с текстом закрыты: 59 скриптов + Tejas (флаг атаки заднего ряда) + 6 карт Sentinel/Heal, которые обрабатывает движок. Оставшихся карт нет.
- [x] Триггеры `OnDriveCheck` (Asura Kaiser, Brigitte, Tristan, Goku), `OnBoostedHit` (Dreadmaster, Aermo), `OnGuard` (Hagakure), `OnRetireFromRC` (Blightops), `OnOpponentRetire` (Joka, Rakshasa, Yaksha).
- [x] Способности из руки (Gancelot, Yaksha) и из сброса (Monster Frank).
- [x] Возврат карты с поля в колоду: после боя (Juggernaut, Brakki) и в конце хода (Lozenge, Battleraizer).
- [x] Триггер `OnLeaveGuardian` (Rock the Wall, Redshoe Milly).
- [x] Wyvern Strike, Tejas: атака заднего ряда только из своей колонки; обычные карты атакуют только передний ряд.
**Готово, когда:** 94/94 заскриптованы, 94/94 покрыты тестом, приёмка пройдена по чеклисту карты.
---
## 5. Сеть и матч `[8–12 дней]`
- [x] Спецификация протокола в `docs/protocol.md`: кадры, handshake, версия, ping/pong, reconnect.
- [x] `Vanguard.Shared`: DTO интентов и событий, MessagePack-резолвер.
- [x] `IGameTicketService`: одноразовый тикет на подключение (TTL 60 с).
- [x] Транспорт матча: WebSocket (ASP.NET Core); резерв — TCP с префиксом длины. Accept, handshake, auth по тикету, привязка к grain.
- [x] `IMatchGrain`: 1 матч = 1 grain, последовательная обработка.
— реализовано in-process; Orleans-хостинг готов и включается `Orleans:Enabled`.
- [x] Проекция состояния под клиента: своя рука, свой trigger, открытые зоны; **ничего** о колоде, руке противника и закрытом уроне.
- [x] `eventSeq` + `resync` по запросу.
- [x] Реконнект: 0 / 2 / 10 / 30 с, затем экран «не удалось».
- [x] Политика отсутствия: 5 минут → авто-поражение.
- [x] Таймер хода: 90 с + банк 5 мин.
- [x] Снапшот матча в PostgreSQL + восстановление grain после рестарта.
— заголовок + журнал интентов, реплей команд при старте.
- [x] EF-миграции общей схемы (матчи + мета), `Migrate` при старте; InMemory — `EnsureCreated`.
- [x] Redis: одноразовые тикеты и очередь.
- [x] Idempotency по `intentId`.
- [x] HTTP-очередь и тикеты: `docs/server-api.md`.
- [x] E2E: два клиента играют полный матч по WebSocket; обрыв и возврат проверены.
**Готово, когда:** два клиента играют полный матч по сети; kill процесса клиента на любом шаге восстанавливается.
— выполнено в `Vanguard.Server.Tests`; рестарт сервера восстанавливает матчи из PostgreSQL (проверено на `ssh home`).
---
## 6. Мета-API `[10–14 дней]`
- [x] Модель `User`, BCrypt, JWT (access 15 мин + refresh 30 дней, ротация).
- [x] Эндпоинты: `register`, `login`, `refresh`, `logout`, `me`.
- [x] Коллекция: `GET /collection` (сгруппировано по принтам), счётчики без верхней границы.
- [x] Колоды: CRUD, `validate`, лимит 30 на формат, шаринг по коду.
- [x] Магазин: `GET /store`, `POST /buy/booster`, `POST /buy/trialdeck`, `POST /redeem/token`.
- [x] Генератор бустера: слот `4 × C` + `1 × R+` (R 70 / RR 22 / RRR 7 / SP 1).
- [x] Duplicate protection по `printingId`, замена внутри редкости, переброс редкости при её насыщении.
- [x] Экономика: журнал транзакций, `idempotencyKey`, баланс вычисляемый.
- [x] Пыль: разбор C 5 / R 10 / RR 25 / RRR 100, крафт ×4, SP не крафтится.
- [x] Онбординг: выбор триал-деки + 5 токенов, флаг «выбор сделан».
- [x] Матчмейкинг: 2 очереди, скрытый MMR, расширяющееся окно; с JWT по `deckId`.
- [x] Результат матча: начисление Gold 30/15/20 и XP 150/75/100, запись в историю.
- [x] История матчей и реплеи: `GET /matches`, `GET /replays/{id}`, проекция под игрока.
- [x] Rate limiting на все мутирующие эндпоинты.
**Готово, когда:** новый игрок через API проходит путь регистрация → триал-дека → токены → бустеры → колода → очередь.
— выполнено: `MetaGameE2ETests` проходит регистрацию, онбординг, бустер, колоду,
очередь, полный матч, историю и реплей; проверено и на реальном PostgreSQL.
После MVP: косметика, квесты, баттлпасс, Draft, друзья/чат.
---
## 7. Клиент: каркас и меню `[15–20 дней]`
**Первый блок начат:** проект `client/` создан из URP-шаблона Unity
**6000.6.6f1**. Уже работают логин/регистрация, refresh, `/me`, выход и
главный экран профиля. Подробности — `client/README.md` и `docs/HANDOFF.md`.
**Визуальное направление зафиксировано:** 3D-клиент на URP, отдельные
сцены-экраны **Login / Lobby / Collection / DeckBuilder / Battle**;
`Boot` — служебная сцена. Очередь и онбординг — оверлеи Lobby. Текущий
единый Canvas — временный функциональный gray-box. Полное решение —
`docs/client-visual-direction.md`.
- [x] Unity 6 LTS, URP, Addressables, Input System, Localization.
DOTween — следующим шагом (пока UI без внешних твинов).
- [x] 3D-сцены `Login`, `Lobby`, `Collection`, `DeckBuilder`, `Battle`
и `SceneFlow`; `Boot` — стартовая служебная сцена. 3D-окружение пока
из примитивов, UI — code-generated uGUI.
- [x] Composition root + DI (VContainer), без синглтон-свалок.
- [x] `ApiClient` (HTTP/JSON), `PlayerState`, таймауты и разбор `ApiError`.
- [x] `GameConnection` (WebSocket + MessagePack), snapshot/events,
`ClientGameState` в виде `CurrentView` + `BattleView`, очередь и тикет.
- [x] Экраны: логин/регистрация, главное меню, онбординг, коллекция,
список колод; магазин и декбилдер подключены.
- [x] Очередь: classic-ranked, опрос, отмена, тикет, переход в матч.
- [x] Простой декбилдер: фильтры (клан, грейд, триггер) в оверлее,
сброс, сортировка клан→ранг→имя, карточная сетка с количеством,
узкий список колод справа, редактор с вертикальным списком карт,
статистика по рангам и общему числу, добавление/удаление кликом.
- [ ] Декбилдер: drag&drop, live-валидация, редкость/тип/арт, пресеты.
- [x] Базовая коллекция: список, балансы, разбор в пыль с подтверждением;
арты и «сколько в колодах» — дальше.
- [x] Магазин в Lobby: бустер за Gold/токен с показом 5 карт, вторая
триал-дека; анимация открытия — дальше.
- [x] История матчей и реплей: список результатов, события и кадры.
- [ ] Локализация RU/EN, переключение на лету (пакет подключён).
- [ ] Настройки: язык, звук, FPS, мьют.
**Готово, когда:** все мета-экраны работают против реального сервера, язык переключается без перезапуска.
---
## 8. Клиент: боевая сцена `[20–30 дней]`
**Первый gray-box есть:** доска, рука, сила/крит, часы, все легальные
интенты кнопками, сдача и результат. Арты и анимации — следующие.
Финальный визуал — отдельная 3D-сцена `Battle`, а не текущий Canvas
(`docs/client-visual-direction.md`).
- [x] Поле: Vanguard, 5 кругов Арьергарда, рука, damage/soul/drop-счётчики
(без артов и анимаций).
- [x] Старт: маллиган (сервер пока даёт только «закончить маллиган»).
- [x] Ввод: кнопки легальных интентов; подсветка целей — дальше.
- [x] Отображение силы/крита из `MatchView`; модификаторы уже посчитаны сервером.
- [ ] Очередь анимаций строго по событиям сервера: ride, call, attack, drive, damage, retire, boost, guard.
- [ ] Скип/ускорение анимаций.
- [x] Таймер хода, индикатор реконнекта, кнопка сдачи (базово).
- [x] Экран результата: победа/поражение; Gold/XP и «ещё матч» — дальше.
- [x] Догон состояния по `resync`/snapshot после реконнекта (без переигрывания анимаций).
- [ ] Экран противника: таймлайн и текстовый лог боя.
**Готово, когда:** полный матч играется мышью без консоли и без рассинхрона с сервером.
---
## 9. Туториал `[5–7 дней]`
- [ ] `TutorialStep` как ScriptableObject: текст, подсветка, ожидаемое действие.
- [ ] Сценарий: Ride → Call → Boost → Attack → Drive Check → Damage Check → победа.
- [ ] Работает поверх реального движка (локальный матч), не фейковая сцена.
- [ ] Кнопка «Пропустить».
- [ ] По завершении: выбор триал-деки + 5 токенов + флаг онбординга.
**Готово, когда:** новый аккаунт с нуля доходит до первой своей колоды без подсказок извне.
---
## 10. Лаунчер `[5–7 дней]`
- [ ] WPF, MVVM, тёмная тема, окно 1000×640.
- [ ] Логин через тот же API, сохранение refresh-токена в DPAPI.
- [ ] `version.json`: `clientVersion`, `minVersion`, список файлов `{path, hash, size, url}`.
- [ ] Проверка хешей локальных файлов, докачка только изменённого.
- [ ] Прогресс, скорость, пауза, возобновление.
- [ ] Кнопка «Играть» запускает клиент с game-тикетом.
- [ ] Обработка «сервер недоступен», «нет места», «файл повреждён → перекачать».
- [ ] Самообновление лаунчера.
- [ ] Установщик (Inno Setup), установка без прав администратора.
**Готово, когда:** чистая машина → установка → логин → докачка → запуск игры.
---
## 11. Сборка и QA `[7–10 дней]`
- [ ] Скрипт сборки: сервер (docker), клиент (Unity batchmode), лаунчер (dotnet publish), манифест, загрузка на CDN.
- [ ] Тестовый стенд: БД с бесконечной валютой, аккаунты QA.
- [ ] Прогон «золотых матчей»: 50 реплеев воспроизводятся после каждого изменения ядра.
- [ ] E2E: регистрация → туториал → бустеры → колода → матч → награда.
- [ ] Проверка скрытой информации: дамп трафика не содержит руки и колоды противника, закрытого урона и seed.
- [ ] Проверка реконнекта на 20 разных шагах.
- [ ] Профилирование клиента: 60 FPS на средней машине.
- [ ] Инструкция запуска для нового разработчика (README).
---
## 12. Финальный коммит
- [ ] `.gitignore` актуален, в индексе нет `Library/`, `bin/`, `obj/`, `.env`, билдов.
- [ ] Нет секретов и ключей в истории (`git log -p | Select-String "password|secret|key"`).
- [ ] `git add -A` и один коммит:
```
git commit -m "Base version: server, client, launcher"
```
- [ ] Тег: `git tag v0.1.0-base`.
- [ ] Дальше — обычная история, по одному смыслу на коммит.
---
## Гейты (не двигаться дальше, пока не выполнено)
| Гейт | Критерий |
|---|---|
| **G1. Ядро** | 1000 случайных партий без падений, реплей бит-в-бит |
| **G2. Контент** | 94/94 карт заскриптованы и протестированы |
| **G3. Сеть** | Полный матч по сети, реконнект на любом шаге |
| **G4. Мета** | Регистрация → колода → ранкед матч → награда |
| **G5. Клиент** | Полный цикл мышью, без консоли |
| **G6. Доставка** | Чистая машина → установка → игра |