362 lines
20 KiB
Markdown
362 lines
20 KiB
Markdown
# Передача сессии
|
||
|
||
Состояние проекта на **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()` и класть вручную.
|