# Базовая версия — чеклист реализации Практический план: что писать, в каком порядке и когда считать готовым. Дизайн-решения и обоснования — в `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`. - [x] Unity 6 LTS, URP, Addressables, Input System, Localization. DOTween — следующим шагом (пока UI без внешних твинов). - [x] Сцена `Boot` + runtime bootstrap; `Meta`/`Battle` появятся дальше. - [x] Composition root + DI (VContainer), без синглтон-свалок. - [x] `ApiClient` (HTTP/JSON), `PlayerState`, таймауты и разбор `ApiError`. - [ ] `GameConnection` (WebSocket + MessagePack), очередь событий, `ClientGameState`. - [ ] Экраны: логин/регистрация — готово; главное меню — каркас; профиль, магазин, коллекция, декбилдер, очередь, настройки — дальше. - [ ] Декбилдер: фильтры (клан, грейд, редкость, тип, арт), счётчики, валидация в реальном времени. - [ ] Коллекция: арты раздельно, «сколько в колодах», разбор в пыль с подтверждением. - [ ] Магазин: бустер (анимация открытия), триал-деки, обмен токенов. - [ ] Локализация RU/EN, переключение на лету (пакет подключён). - [ ] Настройки: язык, звук, FPS, мьют. **Готово, когда:** все мета-экраны работают против реального сервера, язык переключается без перезапуска. --- ## 8. Клиент: боевая сцена `[20–30 дней]` - [ ] Поле: Vanguard, 2 колонки × 2 ряда, Guardian, рука, Damage, Soul, Drop, Trigger. - [ ] Старт: выбор grade 0, маллиган. - [ ] Ввод: клик/тап → контекстное меню → подсветка валидных целей. - [ ] Отображение силы/крита с модификаторами и подсветкой баффов. - [ ] Очередь анимаций строго по событиям сервера: ride, call, attack, drive, damage, retire, boost, guard. - [ ] Скип/ускорение анимаций. - [ ] Экран противника: таймлайн и текстовый лог боя. - [ ] Таймер хода, индикатор реконнекта, кнопка сдачи. - [ ] Экран результата: победа/поражение, Gold/XP, кнопка «ещё матч». - [ ] Догон состояния после реконнекта (resync + переигрывание очереди). **Готово, когда:** полный матч играется мышью без консоли и без рассинхрона с сервером. --- ## 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. Доставка** | Чистая машина → установка → игра |