VanguardReMaster/docs/HANDOFF.md

362 lines
20 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.

# Передача сессии
Состояние проекта на **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. Текущее состояние
| Проверка | Результат |
|---|---|
| Тесты | **129/129** |
| `validate` | 0 ошибок, 0 предупреждений |
| `translations check` | 94/94 |
| `stress 1000` | 1000/1000 партий, 0 ошибок |
| Карты | **94/94** (66 с текстом + 28 ванильных) |
| Протокол и контракты | `docs/protocol.md`, `Vanguard.Shared` готовы |
| Сервер / клиент / лаунчер | не начаты |
Блок «Игровое ядро» из `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, пройдено 129
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-реплей полной партии с проверкой хэша.
`server/tests/Vanguard.Rules.Tests/ProtocolTests.cs` — **12** тестов:
- round-trip всех кадров клиента и сервера через MessagePack, включая
вложенные union'ы `MatchView` и `EventView`;
- отказ на пустом и слишком большом кадре;
- соответствие интентов и `GameCommand` в обе стороны;
- скрытие руки противника, колоды и закрытого урона;
- отсутствие seed/RNG в DTO;
- `LegalIntents` только у решающего игрока;
- скрытие поиска в колоде от противника и сохранение глобальных номеров
событий при фильтрации.
### 4.7. Сеть: протокол и контракты
- `docs/protocol.md` — версия 1: WebSocket + MessagePack, handshake по
одноразовому тикету, `intentId` и идемпотентность, `eventSeq` и resync,
heartbeat, реконнект 0/2/10/30 с, таймер 90 с + банк 5 мин, авто-поражение
через 5 минут отсутствия, таблица видимости зон.
- `Vanguard.Shared` — мультитаргет `netstandard2.1;net10.0`:
- `netstandard2.1` — DTO и MessagePack для Unity, без ссылки на правила;
- `net10.0` — дополнительно проекция состояния поверх `Vanguard.Rules`.
- DTO: кадры клиента и сервера, 18 интентов, 30 событий, `MatchView`,
`PlayerView`, `CardView`, `ClockView`.
- `ProtocolResolver` — `StandardResolver` + интернирование строк;
`MessagePackSecurity.UntrustedData`; лимит кадра 256 КиБ.
- `MatchProjection` — отдельный `MatchView` на каждого игрока;
`EventProjection` — фильтрация скрытых событий и диапазон по `eventSeq`;
`IntentMapping` — единственное место связи DTO с командами движка.
- Проекция вызывается для наблюдателя без побочных эффектов и не трогает
`LegalCommands()` чужого игрока (иначе утекала бы рука решающего).
---
## 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/src/Vanguard.Shared
/Protocol/*.cs кадры, интенты, события, view, кодек
/Projection/*.cs проекция состояния, фильтр событий, маппинг
/server/tests/Vanguard.Rules.Tests 129 тестов
/content/sets карты
/content/translations/ru.yaml переводы
/docs/translation-rules.md канон перевода
/docs/protocol.md протокол матча, версия 1
/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. **[x] `docs/protocol.md`** — версия 1:
кадры, handshake по тикету, `intent` с `intentId` и `eventSeq`,
resync, реконнект 0 / 2 / 10 / 30 с, отсутствие 5 минут → авто-поражение,
таймер 90 с + банк 5 мин, heartbeat, коды ошибок, таблица видимости.
2. **[x] `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. Продолжить с `Vanguard.Server`: ASP.NET Core + WebSocket, тикеты,
`IMatchGrain`, матчмейкинг. `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 не срабатывал.
- **Shared.** Мультитаргет: файлы `Projection/` компилируются только для
`net10.0`; в `netstandard2.1` (Unity) ссылки на `Vanguard.Rules` нет.
- **Видимость.** `LegalCommands()` описывает решающего игрока. Вызывать его
для наблюдателя нельзя — в списке есть printingId из чужой руки.
- **Порядок фаз.** Вход в Основную — `EnterMainPhase()` с `OnPhaseStart`.
- **Отложенные продолжения.** `State.Continuation` — что выполнить после паузы;
`State.PendingQueue` + `_resolvingDepth` запрещают вложенность эффектов.
- **`OfferAbility`.** Возвращает `Skipped / Paused / Resolved`; вызывающий код
продолжает очередь только при `Skipped`. Ошибка здесь уже приводила к потере
шага защиты.
- **`InTransit`.** Найденная карта, ждущая выбора круга, лежит в
`State.InTransit`, иначе инвариант «50 карт» падает.
- **Задний ряд.** Обычные карты атакуют только передний ряд; Tejas — задний
в своей колонке через `snipeBackRow`.
- **Реестр карт.** Новая карта без записи в `CardScripts.Default` молча
останется без способностей; проверять покрытие скриптом.
- **Тестовые колоды.** `BuildDeck()` в тестах не содержит все карты; для
конкретной карты использовать `MakeCard()` и класть вручную.