# Передача сессии Состояние проекта на **22 октября 2026**. Документ нужен, чтобы новая сессия продолжила работу без повторного разбора истории. Краткая оперативная сводка — `docs/SESSION-STATE.md`; этот файл хранит подробную историю. --- ## 1. Проект Фан-ремастер **Cardfight!! Vanguard** базовой эры (2011), формат **Classic**. MVP: бустер **BT01** + триал-деки **TD01/TD02** — 94 карты, 124 принта. Стек: - сервер — **.NET 10**, **Orleans** (опционально), PostgreSQL, Redis; - клиент — **Unity 6000.6.6f1**, URP, 3D-сцены (каркас готов); - лаунчер — **WPF** (ещё не начат); - контент — YAML + C#-скрипты карт; - транспорт — **WebSocket + MessagePack** (реализован в сервере и клиенте). Репозиторий: - ветка `master`; - remote `ssh://git@192.168.31.158:2222/mpakobecex/VanguardReMaster.git`; - актуальный HEAD смотреть через `git log -1`; - рабочее дерево чистое. Главные принципы: - сервер авторитарный, клиент тонкий; - скрытая информация (колода, рука противника, закрытый урон, seed) не уходит клиенту; - `Vanguard.Rules` — чистая библиотека без сети, БД и Orleans: команда на вход, события на выход; - каждый блок работы коммитится и пушится в `master`. --- ## 2. Текущее состояние | Проверка | Результат | |---|---| | Тесты сервера | **187/187** (142 правил/контрактов + 45 сервер) | | `validate` | 0 ошибок, 0 предупреждений | | `translations check` | 94/94 | | `stress 1000` | 1000/1000 партий, 0 ошибок | | Карты | **94/94** (66 с текстом + 28 ванильных) | | Протокол и контракты | `docs/protocol.md`, `Vanguard.Shared` готовы | | Сервер | сеть, матчмейкинг, PostgreSQL + Redis, Orleans, Docker | | Мета-API | JWT, коллекция, колоды, магазин, экономика, история, реплеи | | Клиент | Unity 6.6, 3D-сцены Login/Lobby/DeckBuilder/Battle, логин/коллекция/колоды/очередь/бой, 124 арта, RU/EN, анимации карт | | Лаунчер | не начат | Блок «Игровое ядро» из `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, пройдено 139 (Vanguard.Rules.Tests) Пройден! : не пройдено 0, пройдено 45 (Vanguard.Server.Tests) OK: ошибок 0, предупреждений 0 Переведено: 94/94 Партий: 1000, завершено: 1000, ошибок: 0 ``` Клиент (Unity): ```powershell & 'C:\Program Files\Unity\Hub\Editor\6000.6.6f1\Editor\Unity.exe' ` -batchmode -nographics -projectPath client ` -runTests -testPlatform EditMode -testResults client\TestResults.xml & 'C:\Program Files\Unity\Hub\Editor\6000.6.6f1\Editor\Unity.exe' ` -batchmode -nographics -projectPath client ` -executeMethod Vanguard.Editor.SceneSmoke.Run ``` Сервер локально: ```powershell dotnet run --project server\src\Vanguard.Server --urls http://localhost:5080 # GET http://localhost:5080/health ``` Тестовый стенд на `ssh home` (Linux, Docker): ```bash ssh home cd ~/VanguardReMaster # .env: VANGUARD_PORT=18080, VANGUARD_WS_URL=ws://192.168.31.158:18080/game docker compose up -d --build curl http://127.0.0.1:18080/health # остановить: docker compose down ``` `Storage:Provider=None` по умолчанию: локальный запуск без базы. Стенд использует `Postgres`, `Redis` и восстанавливает матчи после рестарта. 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()` чужого игрока (иначе утекала бы рука решающего). ### 4.8. Сервер `server/src/Vanguard.Server` — ASP.NET Core 10, один процесс. Матчи, очередь и тикеты сохраняются в PostgreSQL/Redis, если они настроены; по умолчанию (`Storage:Provider=None`) сервер работает без внешних сервисов. - **HTTP**: `POST /api/game/queue`, `GET /api/game/queue/{id}`, `DELETE /api/game/queue/{id}`, `POST /api/game/tickets`, `GET /health`. Контракт — `docs/server-api.md`. - **Матчмейкинг**: две очереди (Classic Ranked / Casual), скрытый MMR, окно ±100 с расширением на 50 каждые 5 с до 2000, выбор ближайшего MMR. Ранкед меняет MMR по Эло (K = 32), casual — нет. - **Тикеты**: одноразовые, TTL 60 с, привязаны к `(playerId, matchId, seat)`; при настроенном Redis изымаются через `GETDEL` (одноразовость между инстансами), иначе — из памяти. - **`IMatchGrain`**: 1 матч = 1 экземпляр, все входы (интенты, подключения, тики) проходят через один семафор; внутри нет ни ASP.NET Core, ни WebSocket — только `IMatchConnection`. Orleans-хост (`MatchActorGrain`) подключается поверх того же контракта; соединения живут в `IMatchConnectionRegistry`, чтобы состояние грана оставалось сериализуемым. - **WebSocket** `/game`: handshake, MessagePack, `intentId`, idempotency, `eventSeq`, `resync`, heartbeat (20 с простоя → ping, ещё 20 с без ответа → `4408`), реконнект, захват сессии (`4409`), rate limit 16 интентов/с. - **Таймеры**: 90 с на решение + банк 300 с, отсутствие 300 с → поражение; `ServerClock` каждые 5 с. - **Результат**: сдача, явный выход, таймер и отсутствие завершают партию через `GameEngine.Finish(winner)`; событие `GameFinished` уходит обоим, результат пишется в профили. - **Проекция**: своя рука, свой выбор, свои легальные интенты; у противника только количества и открытые зоны. `seed` и RNG не покидают сервер. - **PostgreSQL**: таблицы `matches` и `match_intents`. В заголовке — seed, колоды, игроки, часы, банки, статус; в журнале — `intentId`, команда, результат. При старте незавершённые матчи восстанавливаются повтором принятых команд на детерминированном движке, включая ожидания выбора: делегаты движка не сериализуются. Журнал реплея сверяется по `eventSeq`. Банк и дедлайн пишутся каждые ~5 с; потеря при аварии — не больше тика. Схема — EF-миграции (`Migrations/InitialCreate`), на старте `Migrate`. - **Redis**: тикеты и записи очереди (JSON + sorted set по режимам, TTL); очередь поднимается при старте. Ключи с префиксом `vanguard:`. - **Конфигурация**: `Storage:Provider` = `None` / `Memory` / `Postgres`, `Storage:ConnectionString`; `Redis:Configuration`. `Memory` — EF InMemory для тестов, `Postgres` — постоянное хранилище. - **Orleans**: `Orleans:Enabled` (`false` по умолчанию, `true` в compose). Включённый режим поднимает silo с localhost-clustering в том же процессе (`Orleans:SiloPort` / `GatewayPort`, по умолчанию 11111/30000); `MatchActorGrain` держит доменный `MatchGrain`, `IMatchConnectionRegistry` остаётся в процессе. Активный матч держится тикером, завершённые матчи выгружаются после `CompletedMatchTtlSeconds`; страховочный CollectionAge 12 часов. Один silo на инстанс; для нескольких реплик нужен настоящий кластер и внешний реестр соединений. Тест кластера — `OrleansMatchTests`. - **Docker**: `Dockerfile` + `docker-compose.yml` поднимают postgres, redis (AOF) и сервер; `/health` на `http://localhost:8080` (порт меняется через `VANGUARD_PORT`, адрес WebSocket — `VANGUARD_WS_URL`). - **CI**: `.gitea/workflows/ci.yml` — restore, тесты, `validate` и `translations check` на push/PR. Actions включены для репозитория, `home-runner` выполняет job (проверено: все шаги зелёные). `setup-dotnet` указан явным URL с GitHub: в зеркале `code.forgejo.org` его нет. - **Тесты серверного ядра** (36 из 45 в `Vanguard.Server.Tests`): - тикеты: одноразовость и TTL; - heartbeat: ping после 20 с простоя, закрытие через 20 с, сброс любым кадром; - grain: attach, чужое место, duplicate intent, чужой ход, сдача, явный выход, команды после финала, банк времени, отсутствие, старт часов, reconnect с `eventSeq`, захват сессии; - матчмейкинг: подбор, разные очереди, окно, плохая колода, отсутствие стартового Авангарда, отмена, замена очереди, истечение, MMR, перезагрузка очереди из хранилища; - персистентность: EF-хранилище, восстановление матча повтором команд, `MatchRecoveryService` после «рестарта»; - внешние хранилища (`ExternalStorageTests`, запускаются при `VANGUARD_TEST_POSTGRES` / `VANGUARD_TEST_REDIS`): реальные PostgreSQL и Redis, изъятие тикета, очередь; - E2E: два клиента играют полный матч по WebSocket и сходятся в победителе; обрыв и возврат по новому тикету с догоном состояния; - Orleans: грана инициализируется, принимает attach и интент, граница Orleans сериализуется (тестовый `TestCluster`); полный матч двух клиентов через HTTP/WebSocket при `Orleans:Enabled=true`. Проверено на `ssh home`: 132 + 45 теста, включая реальные PostgreSQL и Redis; `docker compose up` поднимает стенд, матч создаётся через HTTP-очередь, а после `docker compose restart server` восстанавливается (`Recovery: 1 of 1`). ### 4.9. Мета-API - **Аккаунты**: `register`, `login`, `refresh`, `logout`, `me`. BCrypt, JWT access 15 минут, refresh 30 дней с ротацией; в БД хранится хэш refresh-токена, старый после ротации отзывается. - **Онбординг**: одна из двух триал-дек, 50 карт в коллекцию и 5 токенов; повтор невозможен. Триал-деки — `content/trial-decks.yaml`. - **Коллекция**: по `printingId`, без верхнего предела; разбор C 5 / R 10 / RR 25 / RRR 100 пыли, крафт 20 / 40 / 100 / 400, SP нельзя. - **Колоды**: CRUD, валидация 50 карт / ≤4 по `nameId` / Sentinel / Heal / стартовый Авангард, проверка владения картами, лимит 30, шаринг по коду. - **Правило атаки**: атакуют только Вэнгард и Арьергард переднего ряда (слоты 3/4); задний ряд (0–2) — только поддержка/boost. Задний ряд также нельзя атаковать без `snipeBackRow`. `LegalCommands` не предлагает атаки из заднего ряда; добавлен регрессионный тест. - **Магазин**: бустер 100 Gold или 1 токен; вторая триал-дека 500 Gold; состав бустера 4 C + 1 R+ (R 70 / RR 22 / RRR 7 / SP 1) из `content/pack_odds.yaml`; duplicate protection по `printingId` с порогом 4. - **Экономика**: журнал транзакций, баланс вычисляется, `idempotencyKey` на всех покупках, разборе и крафте. - **Очередь с JWT**: `{ mode, deckId }`, игрок и колода берутся из токена и коллекции. Dev-режим `playerId + deck` остался только при `Game:AllowDevQueue=true`. - **Rate limiting**: 60/мин на auth, 300/мин на прочие мутации, плюс глобальный лимит по IP. ### 4.10. История матчей и реплеи - `MetaProfileService` после подтверждённого завершения матча пишет историю обоим игрокам, меняет MMR в ранкеде и начисляет Gold/XP. Победа 30 Gold / 150 XP, поражение 15 / 75; сдача, явный выход, таймер и отсутствие дают 0, но остаются в истории. - `GET /api/matches`, `GET /api/matches/{id}` — история игрока. - `GET /api/replays/{id}` восстанавливает матч из seed, колод и журнала принятых команд и отдаёт: - `events` — события, видимые этому игроку; - `frames` — `MatchView` до и после каждой команды. - Реплей собирается под зрителя: рука противника и закрытый урон скрыты, `legalIntents` пуст, часы обнулены. Смотреть может только участник. - Проверено на `ssh home`: живой Postgres, регистрация → онбординг → бустер за токен → колода → очередь → матч; `/api/replays/{id}` отдаёт 11 стартовых событий и кадр, рука противника пуста. После рестарта сервера матч восстановлен (`matches: 1`). Счётчик `Vanguard.Server.Tests` вырос до 45: добавлены auth, мета-профиль, мета-E2E (онбординг → бустер → колода → матч → история → реплей), проверки реплея, явного выхода и истории/реплея после выхода, матч против бота. --- ## 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, deck /server/src/Vanguard.Shared /Protocol/*.cs кадры, интенты, события, view, кодек /Projection/*.cs проекция состояния, фильтр событий, маппинг /server/src/Vanguard.Server /Program.cs ASP.NET Core, DI, WebSocket endpoint /Api/ очередь, тикеты, /health /Auth/ JWT, register/login/refresh, BCrypt /Meta/ коллекция, колоды, магазин, экономика, история, реплеи /Matchmaking/ очереди, MMR, Redis-хранилище очереди /Matches/ IMatchGrain, таймеры, тикер, регистр /Realtime/ WebSocket-обвязка, чтение кадров /Tickets/ одноразовые тикеты: память или Redis /Storage/ EF-хранилище, восстановление матчей /Profiles/ интерфейс MMR /Dockerfile /server/tests/Vanguard.Rules.Tests 132 теста /server/tests/Vanguard.Server.Tests 45 тестов, включая E2E и внешние хранилища /content/sets карты /content/trial-decks.yaml две триал-деки /content/pack_odds.yaml веса бустера /content/translations/ru.yaml переводы /docs/translation-rules.md канон перевода /docs/protocol.md протокол матча, версия 1 /docs/server-api.md HTTP-очередь, тикеты, /health /docs/HANDOFF.md этот файл /docker-compose.yml postgres, redis, server /.env.example VANGUARD_PORT и VANGUARD_WS_URL ``` --- ## 6. Принятые решения Их не нужно пересматривать без причины. - **Общение:** ответы короткие, без повторов и длинных вступлений. - **Сомнения:** любое сомнение/вопрос — диалог с 2–4 вариантами; выбор делает пользователь. Не угадывать. - Формат называется **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 мин. - Транспорт матча — **WebSocket** + MessagePack; TCP с префиксом длины — резерв для Unity, если WebSocket не подойдёт. - Тикет на подключение — одноразовый, TTL 60 с, передаётся в `ClientHello`, не в URL; reconnect получает новый тикет. - `playerId` в HTTP-API — dev-заглушка до JWT. Выставлять сервер в интернет в таком виде нельзя. - Состояние сервера: PostgreSQL — снапшот/журнал матчей, Redis — тикеты и очередь; без конфигурации работает in-memory. `IMatchGrain` отделён от транспорта, Orleans подключается поверх и включается конфигом. - Rate limit — 16 интентов в секунду на соединение. - Скрытый MMR: старт 1000, Эло K = 32, casual рейтинг не меняет. - Аккаунты: BCrypt, access JWT 15 минут, refresh 30 дней с ротацией; в БД хранится только хэш refresh-токена. - Онбординг: одна триал-дека бесплатно + 5 токенов; вторая триал-дека за 500 Gold; бустер 100 Gold или 1 токен. - Коллекция по `printingId` без лимита; лимит 4 по `nameId` — только декбилдер. - Пыль: разбор C 5 / R 10 / RR 25 / RRR 100, крафт 20 / 40 / 100 / 400, SP не разбирается и не крафтится. - Матч: победа 30 Gold / 150 XP, поражение 15 / 75; Gold = XP / 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. **[x] `Vanguard.Server`** — сетевой матч работает: ASP.NET Core + WebSocket, тикеты, `IMatchGrain` (in-process), матчмейкинг, MMR, идемпотентность, таймеры, реконнект, `/health`, Dockerfile и compose. Контракт HTTP — `docs/server-api.md`. 4. **[x] Проекция состояния** подключена к грану и проверена E2E-тестом: своя рука и свой выбор полностью, у противника только количества и открытые зоны, seed и RNG не уходят. 5. **[x] `Docker Compose`** — postgres, redis (AOF) и сервер; `/health`, порт через `VANGUARD_PORT`. Сервер запускается с `Orleans:Enabled=true`. 6. **[x] Персистентность**: - PostgreSQL — заголовок матча + журнал интентов, восстановление повтором команд после рестарта, `MatchRecoveryService`; - Redis — одноразовые тикеты (`GETDEL`) и очередь с перезагрузкой. Осталось в серверном направлении: 7. **Мета-API** — сделано: JWT, аккаунты, коллекция, колоды, магазин, экономика, онбординг, матчмейкинг по `deckId`, история и реплеи. `playerId` остался только как dev-режим при `Game:AllowDevQueue=true`. 8. **[x] Orleans** — `MatchActorGrain` + `OrleansMatchAdapter`; silo включается `Orleans:Enabled=true`, в compose включён. `MatchGrain` остался доменным ядром, соединения вынесены в `IMatchConnectionRegistry`, `MatchActorGrain` восстанавливается из снапшота на активации. Покрыто `OrleansMatchTests` (тестовый кластер) и `OrleansServerE2ETests` (полный матч через HTTP/WebSocket при включённом silo). Критерий готовности серверного блока выполнен: два клиента играют полный матч по сети; kill клиента восстанавливается через новый тикет и `resumeFromSeq`; рестарт сервера восстанавливает матчи из PostgreSQL. CI подключён и проходит; серверный блок закрыт. ### 7.2. Мета-API Сделано: - `User`, BCrypt, JWT (access 15 мин + refresh 30 дней, ротация). - `register`, `login`, `refresh`, `logout`, `me`. - Коллекция по принтам, без верхнего предела; разбор и крафт. - Колоды: CRUD, `validate`, лимит 30, шаринг по коду, проверка владения. - Магазин: бустеры, триал-деки, токены; duplicate protection. - Пыль: разбор C 5 / R 10 / RR 25 / RRR 100, крафт 20 / 40 / 100 / 400; SP не разбирается и не крафтится. - Онбординг: триал-дека + 5 токенов. - Матчмейкинг по JWT и `deckId`; dev-режим только для разработки. - Результат матча: Gold/XP, MMR, история, реплеи. - Rate limiting. Осталось после MVP: косметика, квесты, баттлпасс, Draft, друзья/чат, история покупок в клиенте. ### 7.3. Клиент Первый блок начат: **Unity 6000.6.6f1**, URP-проект в `client/`. Сделано: - проект создан из URP-шаблона Unity 6.6; пакеты: URP, Input System, Addressables, Localization, Newtonsoft.Json, VContainer (OpenUPM); - визуальное направление зафиксировано: **3D**, отдельные сцены-экраны Login/Lobby/DeckBuilder/Battle (`docs/client-visual-direction.md`); `DeckBuilder` совмещает коллекцию и колоды; UI пока code-generated gray-box; - сцена `Boot` + runtime-инициализатор, поэтому клиент стартует и в пустой сцене; - composition root на VContainer: `AppConfig`, `TokenStore`, `ApiClient`, `AuthService`, `MetaService`, `PlayerState`, `SceneFlow`; - HTTP-клиент на `UnityWebRequest` + Newtonsoft.Json, разбор `ApiError`, таймаут 20 с; - login/register/refresh/me/logout, восстановление сессии по refresh, сохранение токенов в `PlayerPrefs` (dev); - экран входа с выбором адреса сервера (стенд по умолчанию `http://192.168.31.158:18080`) и главный экран с балансами, MMR и переходами в разделы; - онбординг: список триал-дек из `/api/store`, выбор одной, 50 карт + 5 токенов, обновление профиля; - коллекция: `/api/collection`, балансы, разбор в пыль и крафт; ПКМ по карте открывает оверлей с текстом способностей; - колоды: `/api/decks`, список справа, редактор; карточная сетка коллекции с сортировкой клан→ранг→имя; - магазин в Lobby: `/api/store`, бустер за Gold/токен с показом 5 карт, покупка второй триал-деки; - история матчей и реплей: `/api/matches`, `/api/replays/{id}`, просмотр событий и кадров состояния; - очередь classic-ranked, опрос статуса, отмена, тикет и `wsUrl`; - `GameConnection` (WebSocket + MessagePack), handshake `ClientHello`, snapshot/events, интенты с ответом сервера, ping/pong, закрытие; - серый боевой экран: доска, рука, часы, все легальные интенты кнопками, сдача с подтверждением, результат; - `Vanguard.Shared.dll` (netstandard2.1) и MessagePack 3.1.12 подключены как плагины; `client/README.md` описывает запуск и ограничения; - сцены `Login`, `Lobby`, `DeckBuilder`, `Battle` и `SceneFlow`; `Boot` — стартовая служебная сцена, `VanguardApp` и сервисы живут между сценами; - 3D-фон на каждой сцене: камера, свет, пол и тематические примитивы; UI пока code-generated uGUI; - EditMode smoke `Vanguard.Tests.Editor` и `Vanguard.Editor.SceneSmoke` проверяют запуск Boot → Login и создание экранов/3D-окружения; live-probe (не входит в набор) проверил онбординг, коллекцию, создание/удаление колоды, а также два WebSocket-подключения, маллиган, сдачу и `GameFinished` против стенда. Дальше по клиенту: 1. Боевой UI: арты, зоны, анимации, скип/ускорение; реконнект и таймауты. 2. Полный декбилдер: фильтры, счётчики, валидация. 3. 3D-наполнение сцен: модели/арт, prefab-экраны, камера. 4. Локализация RU/EN, настройки, DOTween. **Зафиксированное визуальное направление** — `docs/client-visual-direction.md`: 3D-клиент на URP, отдельные сцены-экраны Login / Lobby / DeckBuilder / Battle; Boot и общие сервисы остаются персистентными. Сцены и `SceneFlow` уже созданы; UI пока code-generated gray-box. - Unity 6 LTS, URP, Addressables, Input System, Localization, DOTween. - VContainer, `ApiClient`, `GameConnection`, `SceneFlow`. - Сцены: `Boot` (служебная), `Login`, `Lobby`, `DeckBuilder`, `Battle`; очередь/онбординг — оверлеи Lobby. - История матчей и экран реплея: `GET /api/matches`, `GET /api/replays/{id}`, проигрывание `frames` + `events`. - Локализация RU/EN на лету. ### 7.4. Лаунчер - WPF: обновления, запуск клиента, выбор сервера. ### 7.5. Мелкие технические долги - `OnDamageTaken` не вызывается — добавить, когда появится карта с таким триггером. - Глобальный лимит шагов внутри движка не enforced — только в стресс-тесте. - `GameEngine.cs` большой; при росте разнести на partial-файлы. - `MatchRecord` сейчас JSON; для сети позже MessagePack. - В `LegalCommands()` не входят активируемые способности со стоимостью «сброс карты» — для UI их нужно будет перечислить с вариантами сброса. --- ## 8. Порядок действий в новой сессии 1. Прочитать `docs/SESSION-STATE.md`, `docs/HANDOFF.md`, `IMPLEMENTATION_CHECKLIST.md`, `README.md`, `docs/game-rules.md`. 2. Прогнать проверки из раздела 3. 3. Следующий блок — клиент: магазин/бустеры в Lobby и история/реплей, затем полный декбилдер и 3D-наполнение сцен. Сервер и мета-API готовы: `docs/protocol.md`, `docs/server-api.md`, `Vanguard.Shared`. 4. Перед началом сверить `docs/client-visual-direction.md`: 3D-сцены, `Boot` и общие сервисы персистентны. 5. После каждого блока: тесты, `validate`, `translations check`, коммит, push. 6. Не ломать `Vanguard.Rules`: сервер должен зависеть от него, а не наоборот. --- ## 9. Подводные камни - **Клиент: HTTP.** В Unity по умолчанию запрещены незащищённые соединения: для dev-стенда в `client/ProjectSettings/ProjectSettings.asset` стоит `insecureHttpOption: 2` (Always). Для релиза — HTTPS и вернуть запрет. - **Клиент: WebSocket.** `GameConnection` использует `System.Net.WebSockets.ClientWebSocket`; это окно для Windows-десктопа. Для WebGL/мобильного IL2CPP нужен отдельный транспорт/плагин. - **Кодировка.** Не использовать 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 из чужой руки. - **Сервер: rate limit.** 16 интентов в секунду на соединение. Быстрый бот в E2E специально притормаживается; живой UI в лимит не упирается. - **Сервер: хранилище.** По умолчанию `Storage:Provider=None`: матчи живут в памяти (`NullMatchStore`), мета-API — в EF InMemory. Для рестарта нужен `Postgres` + `Storage:ConnectionString`; `Memory` — EF InMemory для всего. Схема — EF-миграции (`Migrations/InitialCreate`), на старте `MigrateAsync`; для InMemory — `EnsureCreated`. Миграции применяются автоматически. - **Сервер: снапшот матча.** Сериализуется не состояние движка, а seed, колоды, игроки, часы и журнал интентов. При восстановлении команды проигрываются заново; `EventSeq` обязан совпасть, иначе матч пропускается с ошибкой. Делегаты `PendingChoice`/`Continuation` восстанавливаются самим реплеем. - **Сервер: восстановление.** `MatchRecoveryService` поднимает только незавершённые матчи за последние 7 дней. Завершённые остаются историей в PostgreSQL и не восстанавливаются в память. Банк/дедлайн пишутся раз в ~5 с, поэтому авария теряет не больше тика. - **Сервер: Redis.** Тикеты изымаются через `GETDEL` (Redis 6.2+; в compose 7). Очередь — JSON + sorted set по режимам, TTL в два раза больше очереди. Префикс ключей — `Redis:Prefix` (`vanguard:`). - **Сервер: Docker.** На домашнем сервере порт 8080 занят, поэтому стенд поднимается с `VANGUARD_PORT=18080` и `VANGUARD_WS_URL` в `.env`. `docker compose restart server` проверяет восстановление матчей. - **Сервер: внешние тесты.** `VANGUARD_TEST_POSTGRES` и `VANGUARD_TEST_REDIS` включают `ExternalStorageTests`; без них тесты проходят как no-op. Postgres-тест делает `EnsureDeleted`, поэтому БД должна быть тестовой. - **Сервер: тесты.** E2E поднимает `WebApplicationFactory`, играет полный матч по WebSocket и проверяет, что победитель совпал. Случайные партии завершаются за ~350 действий; таймаут теста — 120 с. - **Мета: JWT.** `Jwt:SigningKey` — не короче 32 символов; дефолтный ключ только для разработки. Access 15 минут, refresh 30 дней с ротацией; в БД лежит SHA-256 от refresh, старый токен после ротации недействителен. - **Мета: dev-очередь.** `Game:AllowDevQueue=true` разрешает очередь по `playerId` без JWT. В compose выключено; не включать в production. - **Мета: колода.** Сохранение проверяет не только правила Classic, но и владение каждой картой. Триал-дека даёт карты в коллекцию, а не готовую колоду — её собирают декбилдером. - **Мета: бустер.** 4 C + 1 R+, веса в `content/pack_odds.yaml`. Duplicate protection считает `printingId` и порог 4; при насыщении редкости выбирается другая, иначе допускается дубль. SP не крафтится и не разбирается. - **Мета: награды.** Gold/XP только за `normal`; сдача, явный выход, таймер и отсутствие дают 0. История пишется один раз на `(userId, matchId)`, повтор результата не дублирует валюту. - **Реплей.** Доступен только участнику; собирается из seed, колод и журнала принятых команд. `frames` — состояние после каждой команды, `events` — только видимые зрителю события; рука противника и закрытый урон скрыты. Сырые колоды и seed наружу не отдаются. - **Сервер: таймеры.** Тик раз в секунду. После 90 с банк списывается по фактическому превышению; `TurnDeadlineMs` в этот момент сдвигается, поэтому UI должен показывать ещё и `BankMs`. - **Сервер: часы.** Отсчёт стартует только когда подключились оба места. До этого `ClockView.IsRunning = false`, но таймер отсутствия уже идёт. - **Сервер: проекция.** `ServerSnapshot` и `ServerEvents` собираются отдельно на каждое место; не переиспользовать один `MatchView` на двоих. - **Порядок фаз.** Вход в Основную — `EnterMainPhase()` с `OnPhaseStart`. - **Отложенные продолжения.** `State.Continuation` — что выполнить после паузы; `State.PendingQueue` + `_resolvingDepth` запрещают вложенность эффектов. - **`OfferAbility`.** Возвращает `Skipped / Paused / Resolved`; вызывающий код продолжает очередь только при `Skipped`. Ошибка здесь уже приводила к потере шага защиты. - **`InTransit`.** Найденная карта, ждущая выбора круга, лежит в `State.InTransit`, иначе инвариант «50 карт» падает. - **Задний ряд.** Обычные карты атакуют только передний ряд; Tejas — задний в своей колонке через `snipeBackRow`. - **Реестр карт.** Новая карта без записи в `CardScripts.Default` молча останется без способностей; проверять покрытие скриптом. - **Тестовые колоды.** `BuildDeck()` в тестах не содержит все карты; для конкретной карты использовать `MakeCard()` и класть вручную. ## 2026-10-21 — декбилдер и каталог карт - Добавлен `GET /api/cards` (Bearer): все принты с `grade`, `trigger`, `clan`, `rarity`, `isSentinel`, `isParallel`, `basePrintingId`. Клиент объединяет каталог с коллекцией по `printingId`; правила для фильтров/статистики не дублируются в клиенте. - Новый `DeckBuilderView` в духе HS: - список: сверху фильтры/сброс/создание, в центре сетка коллекции (изображение-заглушка, количество), справа узкий вертикальный список колод; клик по колоде открывает редактор; - редактор: вместо создания — статистика по грейдам и общему числу, справа вертикальный список карт колоды; клик по карте коллекции добавляет, клик по карте колоды убирает одну копию; - сортировка коллекции: клан → ранг → имя. - Ограничения в UI: 50 карт, ≤4 по `nameId`, не больше owned-копий; серверная валидация остаётся источником истины. - Сцена **Collection** и отдельный `CollectionView` удалены; коллекция теперь на экране **Коллекция** внутри `DeckBuilderView`. - ПКМ по карте коллекции/колоды открывает оверлей: изображение-заглушка, статы, текст способности, разбор в пыль и крафт (C/R/RR/RRR; SP и TD нельзя). - `MetaService.CraftAsync`; `GET /api/cards` отдаёт `power`, `shield`, `critical`, `type`, `textEn/textRu` и флаги способностей. - Проверки: серверные 132 + 45 на `ssh home`; Unity EditMode 2/2, `SCENE_SMOKE success=True active=Login`; live `GET /api/cards` — 124 принта; live UI-проба: загрузка 50 карт, создание колоды, добавление/удаление, ПКМ-оверлей, текст способности и разбор карты; live API-проба крафта: разбор 4 C → 20 пыли → крафт C. - `POST /api/game/practice` и `BotMatchService`: серверный случайный бот подключается к своему месту через in-process snapshot-соединение, выбирает легальный интент и играет casual-матч. Практика не пишет историю, не меняет MMR и не выдаёт награды. - Клиент: кнопка «С ботом» в редакторе колоды; `MetaService.StartPracticeAsync`, переход в `Battle`. Серверный E2E-тест `PracticeBotE2ETests` проходит полный матч против бота. - Визуал: `CardArt` генерирует процедурные градиентные арты по клану и рубашку; `BattleView` показывает рамки, имена, ранги, триггеры, силу/крит, REST и скрытые карты; поля обоих игроков, рука и панель действий. Коллекция использует те же арты. - Реальные арты: `imageKey` в `/api/cards`, `CardImageService` читает `Resources/CardArt/{imageKey}`; скачаны все 124 арта. Импорт: `tools/import_card_art.py`. - Boost UI: клик по своему `SUP`-слоту выбирает поддержку, затем цель. - Event-фидбек: всплывающие атака/guard/drive/урон/heal/retire, не блокирует ввод; очередь событий и кнопка «Пропустить анимации». - Боевой UX: клик по карте/зоне, подсветка целей, отдельная группировка действий, выбор силы/эффекта триггера, журнал последних событий и подсказки выбора. - Поле: `R V R` / `R R R` у каждого игрока, зона урона слева, колода и дроп справа, зеркально у противника, зона защиты между полями. - Проверки визуального блока: Unity EditMode 2/2, `SCENE_SMOKE success=True`, live-практика с ботом на стенде. ## 2026-10-22 — перемещения карт и локализация RU/EN - Перемещения карт в бою: - при call/ride карта въезжает из руки (свой) или сверху (противник), с scale-in; - атакующий и цель пульсируют; - карта, ушедшая из слота в дроп, оставляет призрак: летит вниз/вверх, уменьшается и растворяется; для закрытой карты противника используется рубашка, лицо не раскрывается; - позиции карт снимаются после layout, поэтому анимации не зависят от внешних твинов; при новом snapshot анимации пересобираются. - Локализация: - `Localization.cs`: `Loc` + `LocalizedText`; русская строка — ключ, английский перевод — словарь + фразовые подстановки для динамических строк; `да/нет` переводятся по границам слов; - `UiKit.CreateText` автоматически вешает `LocalizedText`, поэтому существующие экраны переведены без переписывания; - переключатель `EN`/`RU` в Login и Lobby; выбор в `PlayerPrefs` (`vanguard.ui.language`), применяется на лету; - имена/тексты карт: `Loc.CardName` / `Loc.CardText` выбирают `nameRu/nameEn` и `textRu/textEn`; карточные поля помечены literal, чтобы словарь не портил переводы карт. - Проверки: Unity EditMode **4/4** (экраны, импорт арта, перевод статики и динамики, live-смена языка, рендер доски с уроном), `SCENE_SMOKE success=True active=Login`. - Ограничения: звука пока нет; полноценные drive/damage-анимации карт и стола — следующий блок; локализация сделана runtime-словарём, без Unity Localization-таблиц (пакет остаётся подключён). ## 2026-10-22 — боевые карты, маллиган, урон - Боевые карты переделаны: арт растянут на всю карту, сверху слот/грейд, снизу плашка с именем и P/C/триггером; в руке — компактный вариант. `CardImageService` проверен тестом: `td01_005` реально загружается. - Rest теперь визуально поворачивает карту на бок; удалён старый текстовый маркер `REST`. Позванные rear-guard'ы входят стоящими (`Call`, `CallFromSoul`, `CallFromDeck` исправлены на `Rested = false`). - Зона урона: отдельная сетка видимых карт на боку, до 6 штук; закрытые показываются рубашкой, открытые — артом. При выборе карты урона (heal или Counter Blast) карты подсвечиваются и кликаются. - Heal: сервер больше не забирает последнюю карту автоматически, а создаёт `ChoiceTargetKind.Damage`; клиент выбирает карту кликом. Counter Blast принимает `DamageIndex` в `ActivateAbilityIntent`/`ResolveAbilityIntent` и использует выбранную открытую карту (остаток суммы — авто). - Маллиган: клиент выбирает карты в руке, подсвечивает их и отправляет `MulliganIntent`; сервер возвращает выбранные в колоду, перемешивает и добирает столько же. После обмена доступно «Закончить маллиган». - Протокол `Vanguard.Shared` расширен (`DamageIndex`, `ChoiceTargetKind`), DLL пересобран и обновлён в `client/Assets/Plugins/Vanguard.Shared`. - Проверки: сервер **139** правил + **45** серверных; клиент EditMode **4/4** (включая рендер доски с уроном и отдыхающей картой); `SCENE_SMOKE success=True active=Login`; `validate` и `translations check` — без ошибок. ## 2026-10-22 — конспект правил - Источник: `https://www.cardfight.ru/avangard-pravila` (редакция 22.05.2019). - Создан `docs/game-rules.md`: форматы, колода, поле, карта, старт/mulligan, победа, фазы, бой, триггеры, стоимости, инварианты и отдельная таблица сверки с текущим кодом. - Расхождения из первой сверки исправлены: урон входит открытым, Counter Blast переворачивает открытое в закрытое; heal доступен при `>=`; mulligan — возврат в низ → добор → shuffle, один раз, оба игрока; в первый ход атака запрещена; первый игрок случайный (для тестов можно зафиксировать); `DeckValidator` требует ровно 16 триггеров, декбилдер показывает счётчик. - Карты на поле (`V`, `R`) и в круге защиты принудительно отдаются клиенту открытыми; в круге защиты дополнительно рисуется ряд карт. - Classic-режим не включает Stride/Вознесение, Legacy, Gift, Front-триггер и Grade support. ## 2026-10-22 — 3D Playmat - Боевое поле переведено в 3D: `Runtime/Board3D` (`Config`, `Data`, `Logic`, `View`), HUD/кнопки остаются uGUI. - Раскладка: локальный игрок всегда внизу; передний ряд `RC–V–RC`, задний `RC–RC–RC`; у противника зеркально. Между половинами — общий круг защиты (карты обоих игроков и текущий Guard). Душа — стопка под V, колода и дроп сбоку, урон — две колонки по три карты на боку. Карты в V/R всегда рисуются лицом вверх, даже если снимок пришёл с `FaceUp=false`. - `PlaymatLayoutConfig` (ScriptableObject): размеры карт, позиции рядов, высоты, рука, камера, палитра. Ассет: `Runtime/Board3D/Config/PlaymatLayoutConfig.asset`. - `PlaymatLayout` — чистая геометрия и поиск цели броска; `BoardPlacementRules` — Ride только на V, Call только на пустой RC, Move только в своей колонке (0↔3, 2↔4, занятый круг меняется местами), Guard — в общий круг. - `Card3D` — тонкая коробка: отдельные лицо/рубашка/торец, коллайдер, арт из `CardImageService`, переворот вокруг X, Rest вокруг Y по часовой стрелке, урон — против часовой, плавный снап. Карты пулятся (`GameBoard3D`), материалы общие, instancing включён. Рубашка — стандартная VF Vanguard `Resources/CardArt/vanguard_back.png` (источник: Card Sleeve Gallery, `Sleeve_06.png`), общая с 2D-UI; при отсутствии файла остаётся процедурный fallback. - `GameBoard3D` строит стол, круги, зоны, стопки и веер руки; raycast-клики, drag&drop на плоскость стола с подъёмом, подсветкой допустимых кругов и индикатором точки броска. Ошибка интента возвращает карту на место. - Камера фиксирована по центру поля: позиция `(0, 4.2, -8)`, цель `(0, 0, 0)`, FOV 46; наклон только по X, без бокового смещения, игрок её не двигает. Рука не лежит на столе, а закреплена в экране снизу по центру (`handViewportX/Y`, `handViewDistance`) и всегда развёрнута лицом к игроку; уменьшена, чтобы не перекрывать задний ряд RC. Драг из руки уходит на стол. - Статистика карт вынесена в отдельные UI-панели: своя — снизу слева, противника — сверху справа (`Рука/Колода/Дроп/Урон/Душа`); 3D-подписи стопок и зон больше не дублируют числа. - Колода и дроп поменяны местами: колода ближе к игроку. Пустой дроп не рисует рубашку; верх дропа показывает последнюю положенную карту, ЛКМ открывает список всего дропа. - Душа — это юниты под картой VC, отдельной стопки на поле нет. ПКМ по VC открывает список души; кнопка души из топ-бара убрана. - Над каждой колодой — триггер-зона (`TRIGGER`), куда выкладывается карта проверки эффекта/урона. Урон — один вертикальный ряд карт на боку. Под картами V/R — динамические `P/C`, жёлтые при изменённой силе/крите. - Справа от круга защиты — панель фаз: «Закончить муллиган» (авто после обмена), «Пропустить смену», «В бой», «Закончить ход». Боевая фаза завершается сама, если нет атак/активаций/выборов. - Event-фидбек guard/drive/intercept показывает имя карты и держится 1.5–2 с; добавлены паузы, чтобы успеть прочитать защиту бота. - Атака проходит при силе атакующего **>=** силы защищающегося. - Песочница: `Assets/Vanguard/Scenes/GameBoard3D.unity` + `PlaymatSandbox`; собирается `Vanguard → Rebuild Playmat Sandbox`, проверяется `SceneSmoke.RunPlaymat`. - Проверки: EditMode **22/22** (геометрия, зеркальность, правила, рендер 3D-карт, пул, рука в поле зрения, обмен колоды/дропа, триггер-зона), `SCENE_SMOKE success=True`, `PLAYMAT_SMOKE success=True cards=25 hand=5/5 fieldVisible=True`. - Ограничения: retire/ghost-анимации старого 2D-поля пока не перенесены; модели карт/стола — процедурные примитивы. ## 2026-10-23 — UX Phase 1: единые списки карт и зоны - Добавлен общий `CardListView` (Screen Space, горизонтальный ScrollRect, порядок «последняя добавленная первой») и `CardDetailOverlay` (арт, имя, клан/тип/грейд, P/Shield/C, триггер, текст). Навигация: `OverlayNavigationService` — стек «список → детали → назад», закрытие возвращает к списку. - Зоны стали кликабельными блоками UI: `Рука / Колода / Дроп / Душа / Урон / Защита / Триггер`; своя панель снизу слева, противника — сверху справа. 3D-стопки и правый клик по VC используют тот же `CardListView`. - Скрытые данные: колода — только закрытые слоты по количеству; рука противника — только рубашки; закрытый урон — «Закрытая карта». Рука противника дополнительно рисуется веером закрытых карт у дальнего края стола (вариант 1), identities не запрашиваются. - Старые `SoulOverlay`/`ShowDrop`/`ShowSoul` удалены; единый оверлей используется для Deck/Drop/Soul/Damage/Guardian/Trigger/Hand. - Проверки: клиент EditMode **22/22** (включая `CardListView`/Detail и зоны), `PLAYMAT_SMOKE success=True cards=25 hand=5/5 fieldVisible=True`. - Дальше по плану: Phase 2 — `InputLockService` + `AnimationConfig` + контекстное меню; Phase 3 — Power/Shield и стрелка; Phase 4 — Guardian стопка, Trigger mirror, debug-подписи. ## 2026-10-23 — UX Phase 2–4: ввод, контекст, бой и раскладка - `InputLockService` — reference-count по причинам (`feedback`, `board`, `overlay`, `card-menu`); ввод возвращается только после последней причины. `AnimationConfig` (ScriptableObject в `Resources/AnimationConfig.asset`): скорости карт, длительности фидбека/флипа/retire, хвост блокировки и стрелка. `Card3D` получает скорости из конфига, `GameBoard3D` блокирует доску на время снапа/переворота, `BattleView` — на время очереди событий и открытых оверлеев. Кнопки интентов/фаз и клики по столу не проходят, пока идёт анимация; «Пропустить анимации» остаётся доступной. - Контекстное меню `CardActionMenuView`: ЛКМ по своей карте V/R открывает список действий, отфильтрованных по `LegalIntents` (атака, атака с поддержкой, перемещение, способность, подробнее). Действия с целями переводят в режим выбора цели; карты противника и зоны открывают `CardDetailOverlay`/списки. Меню закрывается на новом снимке и держит `card-menu` в InputLock. - Боевая плашка `POWER X vs SHIELD Y` в верхней части экрана: сила атакующего против силы защищающегося (с учётом перехватчика) плюс `GuardShield`; пересчитывается на каждом снимке, `NULLIFIED` показывается отдельно. `AttackArrowView` рисует затухающую 3D-стрелку с наконечником от атакующего к цели; точки обновляются каждый кадр по трансформам карт, поэтому стрелка едет за анимацией. - Guardian-карты повёрнуты на 90° вокруг Y, уложены с перекрытием около 30% (spacing 0.62 при высоте 0.88), новые сверху за счёт микро-подъёма. Trigger-зона зеркальна по X и ориентации; 3D-подписи `DECK`/`DROP`/ `TRIGGER` скрыты флагом `showZoneLabels` (по умолчанию false). - Проверки: клиент EditMode **26/26** (InputLock, контекстное меню, стрелка атаки, загрузка `AnimationConfig`), `SCENE_SMOKE success=True`, `PLAYMAT_SMOKE success=True cards=25 hand=5/5 fieldVisible=True`. - Дальше: по желанию вынести overlay-экраны и меню в prefab-ассеты; проверить итоговые позиции на реальном разрешении и, при необходимости, подкрутить `AnimationConfig`/`PlaymatLayoutConfig`. ## 2026-10-23 — Overlay prefabs и проверка раскладки - `CardListView`, `CardDetailOverlay`, `CardActionMenuView` и `ZoneBlockStripView` теперь умеют брать статическую иерархию из `Resources/Overlays/*.prefab`; при отсутствии prefab-а сохраняется прежняя кодовая сборка. Динамические плитки/пункты/подписи по-прежнему создаются кодом. - `OverlayPrefabBuilder` (`Vanguard → Rebuild Overlay Prefabs`) собирает prefab-ассеты из тех же view-классов; `SceneBuilder.BuildAll` вызывает его автоматически. Runtime-загрузка — `OverlayPrefabs`. - Добавлен `Battle_Layout_Fits_Reference_Resolution`: проверяет блоки зон, панель фаз и боевую плашку на 1920×1080 и отсутствие пересечений; при другом разрешении CanvasScaler масштабирует reference-раскладку. - Проверки: клиент EditMode **30/30**, `SCENE_SMOKE success=True`, `PLAYMAT_SMOKE success=True cards=25 hand=5/5 fieldVisible=True`. - Дальше: только 3D-модели/наполнение сцен, анимации retire/ghost и декбилдер; prefab-оверлеи и раскладка закрыты. ## 2026-10-23 — Windows standalone - Добавлен `StandaloneBuild`: меню `Vanguard → Build Windows Standalone` и CLI `-executeMethod Vanguard.Editor.StandaloneBuild.BuildWindowsCli`. Собирает все включённые сцены в `client/Builds/Windows/VanguardReMaster.exe` (папка в `.gitignore`). - В `GraphicsSettings.m_AlwaysIncludedShaders` добавлены URP Lit/Unlit, чтобы runtime-материалы `SceneEnvironment`/`BoardMaterials` не теряли шейдеры при сборке. `Paint`/`CreateUnlit` теперь безопасны при отсутствующем шейдере. - Проверка сборки: Windows x64 build succeeded; при запуске с реальным D3D12-устройством исключений нет. `-nographics` для этого приложения не показателен: runtime-материалы требуют графического устройства. - Сервер для standalone по умолчанию — `http://192.168.31.158:18080`; его нужно поднять/держать доступным. ## 2026-10-23 — Исправления боя и камера - Первый ход: `EnterBattleCommand` больше не попадает в легальные интенты, пока действует правило первого хода; кнопка «В бой» не появляется. Атака по-прежнему запрещена. - Смена: в фазе Ride предлагается и карта того же ранга, и следующего (раньше legal-команды содержали только `+1`, хотя движок разрешал same grade). Кнопки «Сменить: карта» есть в панели действий и в «Фазах». - Маллиган: кнопка динамическая — без выбора «Закончить муллиган», с выбором «Заменить X карт». - Способности: `ActivateAbilityIntent` группируются по карте; в UI одна кнопка «Активировать способность». Если вариантов несколько — overlay выбора способности; карта для сброса выбирается кликом по руке. - Guard: варианты Perfect Guard/сброса показываются отдельным overlay; случайный первый интент больше не выбирается. В фазе защиты есть «Защититься» (при наличии допустимых карт) и «Не защищаться». Карта сброса выбирается кликом по карте в руке. - Правую панель действий можно временно скрыть/показать кнопкой в верхней строке, чтобы видеть поле. При скрытии панель фаз и UI противника сдвигаются вправо. - Кнопка муллигана в панели «Фазы» динамическая: без выбора — «Закончить муллиган», с выбором — «Заменить X карт». После обмена муллиган завершается автоматически, кнопка «Закончить муллиган» скрывается и не блокируется feedback/overlay-локом; при реконнекте авто-завершение взводится повторно из снапшота. - InputLock чистит зависшие причины, если owning-состояние уже неактивно (нет reconnect/overlay/ability/card-menu/feedback). Это убирает блокировку «Сменить» после хода противника, когда сервер уже прислал ride-интенты. Отдельно исправлено накопление `board`-лока: `GameBoard3D` больше не делает повторный `Acquire` на каждом снапшоте во время анимации. - Опциональные AUTO-способности (`PendingAbility`, например Barcgal после рейда на Marron) показываются модальным оверлеем с названием карты и кнопками «Активировать» / «Пропустить»; из боковой панели их кнопки убраны. Боковая панель пока оставлена для остальных действий. - Trigger-зона противника зеркальна: над его колодой в сторону центра, а не за дальним краем. - Карты на столе и в руке масштабируются общим `cardScale = 1.25` (+25%); drag сохраняет базовый масштаб. - Рука центрируется при скрытии боковой панели (`handViewportX = 0.5`); при показе возвращается к значению из layout-конфига. - Камера боя: `(0, 4.2, -8)` → цель `(0, 0, 0)`, только наклон по X; поле по центру, без бокового смещения. - UI: reference-разрешение **1920×1080**, CanvasScaler `matchWidthOrHeight = 0.5`; standalone принудительно `1920×1080 FullScreenWindow`, `defaultIsNativeResolution = 0`. - Потеря WebSocket: полноэкранный `ReconnectOverlay` блокирует бой, автоматические попытки 0/2/10/30 с, после `ServerHello` overlay скрывается; в overlay есть выход в меню. Игра работает в фоне (`runInBackground = 1`), чтобы heartbeat не останавливался при потере фокуса. - Диагностика Ride: `MatchGrain` логирует создание/attach, каждый полученный интент, результат, события и снапшот. В строке состояния есть active, turn, phase, step, printing/ранг вэнгарда, рука с рангами и `rideReady` (карты, которые сервер должен предложить в фазе Ride). Смотреть: `ssh home "docker compose -f ~/VanguardReMaster/docker-compose.yml logs -f server"`. - Проверки: сервер **142** правил + **45** серверных, `validate` и `translations check` OK; клиент EditMode **32/32**.