Add session handoff with current state and next steps
docs/HANDOFF.md records the completed engine, card coverage, replay, stress results, key files, decisions, remaining server/meta/client work and the pitfalls found during development. README links to it.
This commit is contained in:
parent
99532b6e8f
commit
9e1ed87d5d
|
|
@ -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` — каталог карт с разбором состава сетов.
|
||||
|
|
|
|||
|
|
@ -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()` и класть вручную.
|
||||
Loading…
Reference in New Issue