diff --git a/README.md b/README.md index 2522413..48d4096 100644 --- a/README.md +++ b/README.md @@ -113,4 +113,5 @@ dotnet run --project server/src/Vanguard.Cli -- translations check - `plan.md` — исходный дизайн-документ. - `DEVELOPMENT_CHECKLIST.md` — решения, механики, экономика, риски. - `IMPLEMENTATION_CHECKLIST.md` — план реализации по блокам и гейтам. +- `docs/HANDOFF.md` — состояние на конец сессии и порядок следующих шагов. - `content/cards-mvp.md` — каталог карт с разбором состава сетов. diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md new file mode 100644 index 0000000..f10e77f --- /dev/null +++ b/docs/HANDOFF.md @@ -0,0 +1,321 @@ +# Передача сессии + +Состояние проекта на **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()` и класть вручную.