--- name: Vanguard ReMaster description: Use for any work in the Vanguard ReMaster repository: server rules and card abilities, battle mechanics, WebSocket/MessagePack protocol and networking, Unity client UI/3D board, tests, deployment, or project structure questions. --- # Vanguard ReMaster Рабочие правила для этого репозитория. Сначала прочитать `docs/SESSION-STATE.md`, затем `docs/HANDOFF.md` и `docs/client-visual-direction.md`. ## 1. Структура проекта ```text content/ sets/*.yaml карты, принты, тексты, флаги механик translations/ru.yaml русские тексты и токены keywords.yaml ключевые слова docs/ протокол, правила, состояние, аудит способностей server/ src/Vanguard.Shared/ DTO, MessagePack-контракты, union-типы, проекции src/Vanguard.Content/ загрузка YAML, каталог карт, валидация контента src/Vanguard.Rules/ чистое игровое ядро: состояние, команды, события src/Vanguard.Server/ ASP.NET, auth, meta, matchmaking, Orleans, WebSocket src/Vanguard.Cli/ validate, stats, translations, stress, deck tests/Vanguard.Rules.Tests/ тесты правил и карт tests/Vanguard.Server.Tests/тесты сервера и инфраструктуры client/Assets/Vanguard/ Runtime/Api/ HTTP-клиент и DTO мета-API Runtime/Auth/ JWT, refresh, TokenStore Runtime/State/ PlayerState и клиентское состояние Runtime/Meta/ каталог карт, коллекция, колоды Runtime/Battle/ GameConnection, InputLockService, анимации Runtime/Board3D/ 3D-поле, карты, слоты, зоны, drag&drop Runtime/UI/ uGUI-экраны и оверлеи Runtime/App/ сцены, SceneFlow, bootstrap Editor/ сборка сцен, Windows standalone, smoke-проверки Tests/Editor/ Unity EditMode тесты ``` Ключевые границы: - `Vanguard.Rules` — чистая библиотека. Не добавлять в неё сеть, БД, Orleans, Unity или файловый I/O. - `Vanguard.Shared` — контракты. `netstandard2.1`-сборка используется Unity, поэтому там только DTO/сериализация без ссылки на `Vanguard.Rules`. - `Vanguard.Server` зависит от `Rules`, `Shared`, `Content`; обратной зависимости быть не должно. - Клиент не является источником истины: правила, скрытая информация и случайность остаются на сервере. ## 2. Сетевой код ### Протокол - Транспорт матча — WebSocket `/game`, бинарные MessagePack-фреймы. - Контракты и union-номера — только в `Vanguard.Shared/Protocol`. Не дублировать DTO в клиенте или сервере. - Изменение union-номера или `[Key(n)]` — ломающее изменение протокола. Добавлять новые поля/типы, не переиспользовать старые номера. - `ProtocolConstants.Version` и `MaxFrameBytes` — общие ограничения. Сервер обязан отклонять неизвестную версию и слишком большой фрейм. - `ClientIntent` содержит `intentId` (GUID) и `basedOnSeq`; повтор с тем же `intentId` должен быть идемпотентным. Результат — `ServerIntentResult`. - `eventSeq` монотонно растёт. Клиент не применяет события старше `LastSeq`; для пропуска — `ClientResync` и полный snapshot. - Сервер присылает `ServerHello`, `ServerSnapshot`, `ServerEvents`, `ServerIntentResult`, `ServerClock`, ping/pong и `ServerError`. - Скрытые данные (колода, рука противника, закрытый урон, seed) не уходят клиенту. Проверять каждую новую view/проекцию. ### Сервер - HTTP-концы и realtime разделены: REST — мета/аутентификация/матчмейкинг, WebSocket — только матч. - `GameWebSocketEndpoint` принимает соединение; `MatchConnectionRegistry` связывает игрока с матчем; `MatchGrain` хранит авторитарное состояние и обрабатывает интенты. - Команда проходит: intent → проверка `basedOnSeq`/идемпотентности → `GameEngine.Execute` → события → snapshot/clock. - Нельзя доверять клиентским `slot`, `printingId`, `damageIndex` и спискам целей: сервер заново проверяет их по своему состоянию. - Ошибки — через `ServerIntentResult`/`ServerError`, не через исключения наружу. Внутренние исключения логировать, наружу отдавать код/причину. - Reconnect/resume: новый сокет с `resumeFromSeq`; при невозможности — полный snapshot. Не пересоздавать матч из-за обрыва. ### Клиент - Вся сеть — в `GameConnection`: connect, send, receive loop, ping, pending intents, seq, reconnect. UI не работает с сокетом напрямую. - `BattleView`/`MatchGrain`-клиентская часть только читает `MatchView` и отправляет `IntentPayload`; не менять локально авторитарное состояние. - Пока `_sending`/`_snapshotPending`/reconnect overlay активны, ввод блокируется; stale input locks освобождаются по состоянию UI. - После `SnapshotReceived`/`ServerHello` перерисовать поле из snapshot, а не накапливать догадки по событиям. - Все сетевые операции — async + `CancellationToken`; не блокировать Unity-поток. ## 3. Способности карт Источник текста — `content/sets/*.yaml` (`textEn`). Каждая текстовая способность должна иметь ровно одну реализацию: `CardScript`, общий движок или флаг данных. Текст без реализации — баг. Полный аудит: `docs/ability-implementation.md`. ### AUTO `AbilityDefinition`: ```csharp new AbilityDefinition { Trigger = AbilityTrigger.OnPlace, Condition = ctx => ..., Optional = true, CanPay = ctx => ..., Pay = ctx => ..., Resolve = ctx => ... } ``` - `Trigger` — только реальное окно события: `OnPlace`, `OnRideOver`, `OnAttackDeclared`, `OnAttackHit`, `OnBoost`, `OnBoostedHit`, `OnDriveCheck`, `OnGuard`, `OnRetireFromRC`, `OnOpponentRetire`, `OnLeaveGuardian`, `OnPhaseStart`, `OnDamageTaken`. - Проверять зону источника: `IsVanguardOf`, `IsRearGuardOf`, `IsInHand`. `OnPlace` не должен срабатывать от VC, если текст требует RC. - `Optional` + `CanPay`/`Pay` — для «you may pay the cost». Без оплаты эффект не выполняется. - Несколько целей/карт — только `AskUnitChoice`, `AskUnitMultiChoice`, `AskHandCardChoice`, `AskEmptyRearGuardChoice`. - AUTO не должен напрямую менять UI; сервер меняет состояние и добавляет события. Клиент показывает оверлей подтверждения для optional. ### ACT `ActivatedAbility`: - `CanActivate` — только проверка условий; оплата и эффект в `Resolve` (или `Pay` для AUTO). - Флаги: `RestsSelf`, `RetiresSelf`, `RequiresDiscard`, `FromHand`, `FromDropZone`. - ACT из руки/дропа попадает в `LegalCommands` только в разрешённой фазе; клиент показывает действие у карты, не отдельным глобальным списком. - Стоимость (discard, Counter Blast, field units) выбирается по объектам в руке/уроне/поле; подтверждение — кнопка панели «Действия». - `RetiresSelf`/`RestsSelf` не должны активироваться из руки/дропа, если текст этого не разрешает. ### CONT - Сила/критика — `ContinuousAbility` с `Applies`, `PowerBonus`, `CriticalBonus`. - Ограничения, которые не выражаются бонусом, — отдельный движок: `RestrictionType` (`CannotAttack`, `CannotBoost`, `CannotStand`, `CannotIntercept`, `CannotBeChosen`) или `CardScript.CanBeBoosted`. - CONT пересчитывается из состояния каждый раз; не хранить вычисленную силу как истину. ### Общие механики - **Sentinel** — данные `sentinel`; общий Guard + сброс карты того же клана. Отдельный `CardScript` не нужен. - **Heal/Trigger** — данные `trigger`; общий движок drive/damage check. - **Tejas** — `snipeBackRow` в данных + общий движок атаки заднего ряда. - **Restraint** — флаг `restraint` + `IsRestraint`; ACT снимает через `RestraintOverride`. - **Boost/Intercept** — `hasBoost`/`hasIntercept` и общий движок. - **Perfect Guard** — Sentinel + optional discard cost; nullify до конца боя. ### Клиентские правила способностей - ACT/AUTO выбираются кликом по карте/цели, а не списком кнопок. - OPTIONAL AUTO — модальный оверлей «Активировать»/«Пропустить». - Сброс для стоимости — клик по карте в руке. - Counter Blast — клик по карте урона; доступные/выбранные подсвечиваются. - Подтверждение оплаты — в панели «Действия»; отмена — правый клик. - Карта в руке с ACT: клик открывает меню действий с валидными пунктами (Призвать/Сменить/Активировать/Подробнее). Смена/призыв также работают драгом. ## 4. Новые карты и изменения правил Порядок: 1. Добавить/исправить карту в `content/sets/*.yaml`; текст — `textEn`. 2. Найти все AUTO/ACT/CONT/ключевые механики в тексте. 3. Реализовать в `CardScripts` или общем движке; не оставлять дублей. 4. Добавить хотя бы один тест на триггер/активацию/условие. 5. Проверить `CardScripts.Default` целиком: нет ли карты с текстом без скрипта. 6. Прогнать серверные тесты, `validate`, `translations check`. 7. Для клиентских изменений — Unity EditMode, `SCENE_SMOKE`, `PLAYMAT_SMOKE`, пересобрать standalone. ## 5. Игровые механики - **Ride**: hand → VC; ранг не выше текущего +1. Старый Vanguard уходит в soul. Первый ход: без Battle. - **Call**: hand → RC. Занятый RC разрешён: старый юнит уходит в Drop. Правило действует также для call из soul/deck и выбора круга. - **Move**: RC ↔ RC только в своей колонке; занятые круги меняются местами. - **Attack**: только передний ряд; цель — Vanguard или передний RC противника. Tejas может бить задний ряд своей колонки. - **Boost**: стоящий юнит заднего ряда → передний юнит/VC той же колонки; сила бустера добавляется до конца боя. - **Guard**: карты из руки, ранг не выше ранга своего Vanguard. Выбор карт/перехватчиков — по объектам; одна кнопка «Не защищаться»/ «Защищаться». Щит учитывается сразу; после подтверждения — Guardian. - **Intercept**: подходящий RC; атака переносится на него. - **Drive/Damage Check**: только по правилам; триггеры/Heal — общий движок. - **Counter Blast/Soul Blast/Soul Charge/Counter Charge** — общие методы `GameEngine`, не дублировать в скриптах. - **Победа**: 6 damage; после 4+ heal уходит в drop. Проверять инварианты зон и уникальность `instanceId`. ## 6. Клиентские взаимодействия - Выбор — клик/драг по карте, кругу, зоне или полю. Кнопки — только глобальные действия и подтверждения. - Drag-жесты: - передний ряд → карта противника: attack; - задний ряд → передний ряд/VC: boost; - рука → RC: call; - рука → VC: ride. - Панель «Действия» не показывает полный список смен; действия берутся из меню выбранной карты и валидных интентов. - Информационные оверлеи не перехватывают клики и не дублируют выбор кнопками. ## 7. Проверки и окружение ```powershell # сервер — только на ssh home / Docker, не локальным dotnet ssh home "docker run --rm -v ~/VanguardReMaster-test:/src -w /src \ mcr.microsoft.com/dotnet/sdk:10.0 dotnet test server/Vanguard.slnx" # validate / translations ssh home "docker run --rm ... dotnet run --project server/src/Vanguard.Cli -- validate" ssh home "docker run --rm ... dotnet run --project server/src/Vanguard.Cli -- translations check" ``` Клиент: Unity **6000.6.6f1**, URP. Не открывать второй Unity с тем же проектом; batch-проверки — в копии без `Library`. Тесты: ```powershell & 'C:\Program Files\Unity\Hub\Editor\6000.6.6f1\Editor\Unity.exe' ` -batchmode -nographics -projectPath ` -runTests -testPlatform EditMode -testResults \TestResults.xml ``` Smoke: `Vanguard.Editor.SceneSmoke.Run` и `RunPlaymat`. Windows build: `Vanguard.Editor.StandaloneBuild.BuildWindowsCli`. После блока: тесты, `validate`, `translations check`, коммит и push.