# Передача сессии Состояние проекта на **21 октября 2026**. Документ нужен, чтобы новая сессия продолжила работу без повторного разбора истории. --- ## 1. Проект Фан-ремастер **Cardfight!! Vanguard** базовой эры (2011), формат **Classic**. MVP: бустер **BT01** + триал-деки **TD01/TD02** — 94 карты, 124 принта. Стек: - сервер — **.NET 10**, планируются **Orleans**, PostgreSQL, Redis; - клиент — **Unity 6** (ещё не начат); - лаунчер — **WPF** (ещё не начат); - контент — YAML + C#-скрипты карт; - транспорт — WebSocket/TCP + MessagePack (решение зафиксировано, кода ещё нет). Репозиторий: - ветка `master`; - remote `ssh://git@192.168.31.158:2222/mpakobecex/VanguardReMaster.git`; - последний коммит на момент передачи — **`99532b6`**; - рабочее дерево чистое. Главные принципы: - сервер авторитарный, клиент тонкий; - скрытая информация (колода, рука противника, закрытый урон, seed) не уходит клиенту; - `Vanguard.Rules` — чистая библиотека без сети, БД и Orleans: команда на вход, события на выход; - каждый блок работы коммитится и пушится в `master`. --- ## 2. Текущее состояние | Проверка | Результат | |---|---| | Тесты | **117/117** | | `validate` | 0 ошибок, 0 предупреждений | | `translations check` | 94/94 | | `stress 1000` | 1000/1000 партий, 0 ошибок | | Карты | **94/94** (66 с текстом + 28 ванильных) | | Сервер / клиент / лаунчер | не начаты | Блок «Игровое ядро» из `IMPLEMENTATION_CHECKLIST.md` закрыт по его критериям: боты доигрывают 1000 партий, инварианты держатся, реплей воспроизводится бит-в-бит по хэшу событий. --- ## 3. Как проверить `dotnet` установлен в `%LOCALAPPDATA%\dotnet`, поэтому в PowerShell: ```powershell $env:PATH = "$env:LOCALAPPDATA\dotnet;$env:PATH" dotnet test server\Vanguard.slnx dotnet run --project server\src\Vanguard.Cli -- validate dotnet run --project server\src\Vanguard.Cli -- translations check dotnet run --project server\src\Vanguard.Cli -- stress 1000 ``` Ожидаемо: ``` Пройден! : не пройдено 0, пройдено 117 OK: ошибок 0, предупреждений 0 Переведено: 94/94 Партий: 1000, завершено: 1000, ошибок: 0 ``` Solution — **`server/Vanguard.slnx`**, не `.sln`. --- ## 4. Что сделано ### 4.1. Контент - 94 карты, 124 принта в `content/sets/BT01.yaml`, `TD01.yaml`, `TD02.yaml`. - Импортёр `tools/import_cards.py`. - Валидатор контента: щиты, Sentinel, параллельные арты, лимиты колоды. - Токенизация текстов, `content/keywords.yaml`. - Русские переводы `content/translations/ru.yaml` — 94/94. - Канон перевода `docs/translation-rules.md` v0.8. ### 4.2. Игровое ядро `server/src/Vanguard.Rules/Engine/GameEngine.cs`. Реализовано: - фазы `Mulligan → Stand → Draw → Ride → Main → Battle → End`; - стартовый Авангард, маллиган, добор; - райд, призыв, перемещение в пределах колонки (занятый круг меняется местами); - атака по Авангарду и переднему ряду Арьергарда; - поддержка, перехват, идеальная защита (Sentinel); - drive check и damage check с выбором целей; - триггеры: Critical, Stand, Draw, Heal; - победа/поражение, детерминированный RNG; - стоимости: Counter Blast, Soul Blast, сброс, поворот себя, уничтожение себя; - поиск в колоде без просмотра, поиск с призывом и с райдом; - просмотр верхней карты; - способности: `Triggered`, `Continuous`, `Activated`; - модификаторы силы/крита с источником и сроком жизни (`StatModifier`); - выдача и снятие способностей (`GrantedAbility`); - запреты (`CardRestriction`): не вставать, не атаковать, не поддерживать, не перехватывать, не быть выбранным; - снятие Сдерживания эффектом; - возврат карты с поля в колоду после боя и в конце хода; - способности из руки, сброса и круга защиты; - `AskUnitMultiChoice` для «до N» / «ровно N»; - `LegalCommands()` — все легальные команды текущего момента; - `GameInvariants.Check()` — проверка 50 карт, уникальных `instanceId`, урона ≤ 6. Триггеры (`AbilityTrigger`): `OnRideOver`, `OnPlace`, `OnBoost`, `OnBoosted`, `OnBoostedHit`, `OnDriveCheck`, `OnGuard`, `OnRetireFromRC`, `OnOpponentRetire`, `OnLeaveGuardian`, `OnPhaseStart`, `OnAttackDeclared`, `OnAttackHit`, `OnDamageTaken` (объявлен, но пока не вызывается — карт с ним нет). ### 4.3. Карты - **59** скриптов в `CardScripts.cs`, `CardScriptsBT01.cs`, `CardScriptsTD.cs`. - **6** карт Sentinel/Heal обрабатываются данными и движком без скрипта. - **Tejas** реализован флагом `snipeBackRow` в YAML. - **28** ванильных карт скрипта не требуют. - Итого **66/66** карт с текстом закрыты. ### 4.4. Реплей `server/src/Vanguard.Rules/Replay/`: - `CommandCodec` — команда в стабильную строку и обратно; - `MatchRecord` — seed, колоды, команды, хэш событий; - `MatchRecorder` — записывает только принятые команды; - `ReplayRunner` — воспроизводит и проверяет хэш; - `EventHasher` — FNV-1a по тексту событий. Запись переживает JSON и воспроизводится бит-в-бит. ### 4.5. Диагностика - `server/src/Vanguard.Rules/Diagnostics/GameInvariants.cs`; - CLI-команда `stress [партий]` — случайный бот, лимит 5000 шагов на партию. ### 4.6. Тесты `server/tests/Vanguard.Rules.Tests/GameEngineTests.cs` — **117** тестов: - механики фаз, боя, защиты, перехвата, триггеров; - все ключевые карты и стоимости; - 50 детерминированных партий случайного бота с инвариантами; - JSON-реплей полной партии с проверкой хэша. --- ## 5. Структура и ключевые файлы ``` /server/src/Vanguard.Rules /Engine/GameEngine.cs основной движок (~2500 строк) /Engine/GameState.cs состояние, зоны, InTransit /Engine/GameModels.cs CardInstance, Step, Phase, PendingChoice /Engine/GameCommands.cs команды /Engine/GameEvents.cs события /Engine/Modifiers.cs StatModifier, GrantedAbility, CardRestriction /Engine/Abilities/Abilities.cs AbilityDefinition, ActivatedAbility, триггеры /Engine/Abilities/CardScripts.cs реестр Default + базовые карты /Engine/Abilities/CardScriptsBT01.cs /Engine/Abilities/CardScriptsTD.cs /Cards/CardDefinition.cs статы, Sentinel, Restraint, snipeBackRow /Decks/DeckValidator.cs правила колоды /Replay/ запись и воспроизведение /Diagnostics/GameInvariants.cs /server/src/Vanguard.Content загрузка YAML и валидация /server/src/Vanguard.Cli validate, stats, translations, stress /server/tests/Vanguard.Rules.Tests 117 тестов /content/sets карты /content/translations/ru.yaml переводы /docs/translation-rules.md канон перевода /docs/HANDOFF.md этот файл ``` --- ## 6. Принятые решения Их не нужно пересматривать без причины. - Формат называется **Classic**, не Standard. - Имена карт английские; `nameRu` зарезервировано. - Термины: Авангард, Арьергард, ранг, смена, поддержка, проверка, фазы Подъёма/Добора/Смены/Основная/Боевая/Завершающая, завершающий шаг, Сдерживание, «вы», буква ё. - Способности ищутся по `nameId`, а не по принту. - Лимит 4 копий — по `nameId`, только правило декбилдера. - Дубль в коллекции — по `printingId`; порог 4, замена внутри редкости. - Бустер: 5 карт = 4 C + 1 R+; веса R 70 / RR 22 / RRR 7 / SP 1. - Gold за матч: 30/15/20 (победа/поражение/ничья); Gold = XP / 5. - Таймер хода 90 с + банк 5 мин. - Порядок триггеров выбирает игрок; опциональные способности спрашиваются. - Непрерывные эффекты пересчитываются при каждом запросе. - Поиск в колоде: 0 — уведомление и перемешивание, 1 — сразу, 2+ — выбор. - Эффекты не вкладываются: цепочка строго друг за другом. - Каждый блок — коммит и push в `master`. --- ## 7. Что осталось ### 7.1. Ближайший блок — сервер Порядок: 1. **`docs/protocol.md`** — спецификация: - кадры, handshake, версия, ping/pong; - `intent` с `intentId`, `eventSeq`; - `resync` по запросу; - реконнект 0 / 2 / 10 / 30 с; - отсутствие 5 минут → авто-поражение; - таймер 90 с + банк 5 мин. 2. **`Vanguard.Shared`**: - DTO интентов и событий; - MessagePack-резолвер; - проекция состояния под клиента. 3. **`Vanguard.Server`**: - ASP.NET Core + WebSocket; - `IGameTicketService` (одноразовый тикет, TTL 60 с); - `IMatchGrain` — 1 матч = 1 grain, последовательная обработка; - матчмейкинг: 2 очереди (Classic Ranked / Casual); - скрытый MMR, расширяющееся окно; - идемпотентность по `intentId`. 4. **Проекция состояния**: - своя рука и свой триггер — полностью; - открытые зоны — полностью; - колода, рука противника, закрытый урон — только количество; - seed и RNG не уходят. 5. **Персистентность**: - PostgreSQL — снапшот матча, восстановление grain; - Redis — тикеты, очереди, кэш. 6. **Docker Compose** + `/health`. Критерий готовности: два клиента играют полный матч по сети; kill клиента на любом шаге восстанавливается. ### 7.2. Мета-API - `User`, BCrypt, JWT (access 15 мин + refresh 30 дней, ротация). - `register`, `login`, `refresh`, `logout`, `me`. - Коллекция по принтам, без верхнего предела. - Колоды: CRUD, `validate`, лимит 30, шаринг по коду. - Магазин, бустеры, триал-деки, токены. - Пыль: разбор C 5 / R 10 / RR 25 / RRR 100, крафт ×4, SP не крафтится. - Онбординг: триал-дека + 5 токенов. - Матчмейкинг, результат матча, Gold/XP, история. - Rate limiting. ### 7.3. Клиент - Unity 6 LTS, URP, Addressables, Input System, Localization, DOTween. - Сцены `Boot`, `Meta`, `Battle`. - VContainer, `ApiClient`, `GameConnection`. - Экраны: логин, меню, магазин, коллекция, декбилдер, очередь, настройки. - Боевая сцена: поле, рука, зоны, анимации, таймер. - Локализация RU/EN на лету. ### 7.4. Лаунчер - WPF: обновления, запуск клиента, выбор сервера. ### 7.5. Мелкие технические долги - `OnDamageTaken` не вызывается — добавить, когда появится карта с таким триггером. - Глобальный лимит шагов внутри движка не enforced — только в стресс-тесте. - `GameEngine.cs` большой; при росте разнести на partial-файлы. - `MatchRecord` сейчас JSON; для сети позже MessagePack. - В `LegalCommands()` не входят активируемые способности со стоимостью «сброс карты» — для UI их нужно будет перечислить с вариантами сброса. --- ## 8. Порядок действий в новой сессии 1. Прочитать `docs/HANDOFF.md`, `IMPLEMENTATION_CHECKLIST.md`, `README.md`. 2. Прогнать проверки из раздела 3. 3. Начать с `docs/protocol.md` и `Vanguard.Shared`. 4. После каждого блока: тесты, `validate`, `translations check`, коммит, push. 5. Не ломать `Vanguard.Rules`: сервер должен зависеть от него, а не наоборот. --- ## 9. Подводные камни - **Кодировка.** Не использовать PowerShell `Get-Content`/`Set-Content` для UTF-8 файлов с русским текстом: однажды это уже испортило комментарии. Правки — только через `edit`/`write`, либо Python с `encoding="utf-8"`. - **PATH.** `dotnet` лежит в `%LOCALAPPDATA%\dotnet`; без добавления в PATH команды не находятся. - **Solution.** `server/Vanguard.slnx`, не `.sln`. - **Тесты.** `StartedEngine()` принудительно ставит Conroe стартовым Авангардом, чтобы Barcgal не срабатывал. - **Порядок фаз.** Вход в Основную — `EnterMainPhase()` с `OnPhaseStart`. - **Отложенные продолжения.** `State.Continuation` — что выполнить после паузы; `State.PendingQueue` + `_resolvingDepth` запрещают вложенность эффектов. - **`OfferAbility`.** Возвращает `Skipped / Paused / Resolved`; вызывающий код продолжает очередь только при `Skipped`. Ошибка здесь уже приводила к потере шага защиты. - **`InTransit`.** Найденная карта, ждущая выбора круга, лежит в `State.InTransit`, иначе инвариант «50 карт» падает. - **Задний ряд.** Обычные карты атакуют только передний ряд; Tejas — задний в своей колонке через `snipeBackRow`. - **Реестр карт.** Новая карта без записи в `CardScripts.Default` молча останется без способностей; проверять покрытие скриптом. - **Тестовые колоды.** `BuildDeck()` в тестах не содержит все карты; для конкретной карты использовать `MakeCard()` и класть вручную.