# Архитектура Vanguard ReMaster **[C]** Ниже — фактическое устройство репозитория. Если задача требует изменить границу систем, сначала проверь существующий код и спроси пользователя; не вводи параллельную архитектуру. ## 1. Технологии и версии - **Unity:** `6000.6.6f1` (Unity 6), URP `17.6.0`, Input System `1.20.0`, uGUI `2.6.0`, Addressables `4.1.1`, Localization `1.5.13`, Newtonsoft.Json `3.2.2`, VContainer `1.19.0`. - **C#/сервер:** .NET SDK `10.0.100`, `TargetFramework net10.0`, `LangVersion latest`, `Nullable enable`, implicit usings. Фактически это современный C# (C# 14 toolchain); не используй preview-фичи без проверки. - **Сеть:** WebSocket `/game`, бинарные MessagePack-кадры; контракты — `Vanguard.Shared` (`netstandard2.1` для Unity + `net10.0` для сервера). - **Сервер:** ASP.NET Core, EF Core/PostgreSQL, Redis, BCrypt, Orleans (`Microsoft.Orleans.Server 10.4.0`), DTO/проекции. - **Клиент:** сцены `Boot`, `Login`, `Lobby`, `DeckBuilder`, `Battle`; 3D-поле + uGUI HUD. DOTween/UniTask **не установлены**; анимации — `AnimationConfig`, `Tick`, корутинные/сервисные последовательности. ## 2. Границы решения ```text content/ → Vanguard.Content → Vanguard.Rules ← Vanguard.Server ↑ ↓ Vanguard.Shared ← Unity client ``` - **`Vanguard.Rules`** — чистая библиотека правил: карты, колоды, состояние, `GameEngine`, события, реплеи. Не добавлять сеть, БД, Orleans, Unity, `File`/`Directory` или UI. - **`Vanguard.Content`** — загрузка YAML, каталог, валидация контента, токенизация текста/переводы. Не содержит правил хода. - **`Vanguard.Shared`** — DTO, MessagePack-контракты, union-типы, проекции (`MatchProjection`), маппинг `GameCommand ↔ IntentPayload`. `netstandard2.1` не должен ссылаться на `Vanguard.Rules`; проекции собираются в `net10.0`. - **`Vanguard.Server`** — ASP.NET, auth/meta, matchmaking, тикеты, WebSocket, `MatchGrain`, хранение и Orleans-adapter. Зависит от Rules/Shared/Content; обратной зависимости быть не должно. - **Unity client** — только представление и ввод; не является источником истины и не повторяет правила сервера, кроме локальных UX-проверок. ## 3. Серверная игровая логика ### 3.1. `GameEngine` - Единственный владелец изменения `GameState` в матче. - Принимает `GameCommand`, проверяет фазу/шаг/зону/стоимость/цели, порождает `GameEvent` и меняет состояние. - `GameState`: игроки, зоны, фазы, `Step`, `BattleState`, pending trigger/ ability/choice/order/queue, RNG, `InTransit`, winner. - `Phase`: `Mulligan, Stand, Draw, Ride, Main, Battle, End`. - `Step`: `None, Guard, Drive, Trigger, Ability, Choice`. - `CardInstance`: `Id`, `PrintingId`, `Rested`, `FaceUp`, модификаторы, granted abilities, restrictions, overrides. - `CardDefinition` — логическая карта по `nameId`; `CardPrinting` — принт/арт. - `EffectivePower/EffectiveCritical` пересчитывают CONT-бонусы и модификаторы. - `LegalCommands()` — список допустимых команд для текущего игрока; UI использует проекцию, но сервер всё равно перепроверяет. ### 3.2. События, способности, порядок - `AbilityTrigger`: `OnRideOver`, `OnPlace`, `OnBoost`, `OnPhaseStart`, `OnAttackDeclared`, `OnAttackHit`, `OnBoosted`, `OnBoostedHit`, `OnDriveCheck`, `OnGuard`, `OnRetireFromRC`, `OnOpponentRetire`, `OnLeaveGuardian`, `OnDamageTaken`. - `AbilityDefinition` (AUTO): `Trigger`, `Condition`, `Optional`, `CanPay`, `Pay`, `Resolve`. - `ActivatedAbility` (ACT): `CanActivate`, `RestsSelf`, `RetiresSelf`, `RequiresDiscard`, `FromHand`, `FromDropZone`, `Resolve`. - `ContinuousAbility` (CONT): `Applies`, `PowerBonus`, `CriticalBonus`; исключения — `CardScript.CanBeBoosted` и `RestrictionType`. - `TriggerFromCard`/`TriggerAll` собирают срабатывания; `RaiseBatch` либо сразу предлагает одно, либо формирует `PendingOrder`. - `_resolvingDepth` + `PendingQueue`: новые эффекты во время эффекта не исполняются рекурсивно, а разрешаются после текущего. - `PendingChoice` (`AskUnitChoice`, `AskUnitMultiChoice`, `AskHandCardChoice`, `AskEmptyRearGuardChoice`, `AskDamageChoice`) — единый механизм выбора; клиент не должен сам решать, что выбрано. ### 3.3. Матч, сеть, хранение - `MatchGrain` — один матч, все входы через `SemaphoreSlim`; хранит `GameEngine`, журнал идемпотентности `intentId`, таймеры, места. - `GameWebSocketEndpoint` принимает соединение; `MatchConnectionRegistry` связывает игрока с матчем; `MatchTicker` двигает часы/таймауты. - `MatchActorGrain` — Orleans-обёртка: активация/восстановление, вызовы `MatchGrain`; доменная логика остаётся в `MatchGrain`. - `IMatchStore`/`EfMatchStore` — snapshot/header в PostgreSQL; `MatchRecoveryService` восстанавливает; Redis используется для тикетов/ очереди/кластера, не как единственная истина матча. - Интент: `ClientIntent { intentId, basedOnSeq, intent }` → `MatchGrain.SubmitAsync` → проверка идемпотентности/очереди → `IntentMapping.ToCommand` → `GameEngine.Execute` → события → `ServerIntentResult` + `ServerEvents` + проекция. - `MatchProjection.ForViewer` скрывает: руку противника, колоду, закрытый урон, seed/RNG, чужие pending-выборы; показывает свои руку/урон/зоны. - `eventSeq` общий и монотонный; скрытые события занимают номер, поэтому пропуски в `ServerEvents` нормальны. Клиент обновляет `LastSeq` по `toSeq`, а не по последнему видимому событию. ### 3.4. Правила потоков - ASP.NET/HTTP — мета, auth, коллекция, колоды, магазин, история, тикеты. - WebSocket — только матч. - Все ошибки наружу — `ServerIntentResult`/`ServerError`; исключения не должны ломать матч. - Reconnect: `ClientHello/resumeFromSeq`; при невозможности — полный snapshot. Матч не пересоздаётся из-за обрыва. - Сервер не доверяет `slot`, `printingId`, `damageIndex`, целям; проверяет их по своему состоянию. ## 4. Клиент Unity ### 4.1. Composition и сцены - `VanguardApp` — persistent composition root: `VContainer`, `AppConfig`, `TokenStore`, `PlayerState`, `ApiClient`, `AuthService`, `MetaService`, `SceneFlow`. - `SceneFlow` — единая точка переходов; `Boot` служебная, `Login`, `Lobby`, `DeckBuilder`, `Battle` — отдельные сцены. - `SceneEnvironment` строит 3D-фон; `SceneControllerBase` — доступ к контейнеру/Canvas; `BattleSceneController` создаёт `BattleView`. - UI в основном code-generated uGUI (`UiKit`), статические боевые overlay prefab'ы лежат в `Resources/Overlays`. - Reference-разрешение интерфейса — **1920×1080**, `CanvasScaler matchWidthOrHeight = 0.5`; standalone открывается в 1920×1080 fullscreen. - Сервисы не зависят от конкретной сцены. ### 4.2. Сеть и состояние - `GameConnection`: connect, hello, send intents, read loop, ping, pending intents, `LastSeq`, `CurrentView`, reconnect. - UI не открывает сокет напрямую; `BattleView` читает `MatchView` и отправляет `IntentPayload`. - После snapshot/hello поле перерисовывается из snapshot, а не из догадок. - UI узнаёт об изменениях через события `GameConnection` (`SnapshotReceived`, `EventsReceived`, `ClockReceived`) и перерисовку `BattleView.Render`; не строить отдельную клиентскую модель правил. - Клиент не меняет авторитарное состояние; локальные правила (`BoardPlacementRules`) только для подсветки/драга. - Все сетевые операции — `async`/`CancellationToken`; не блокировать Unity-поток. - `TokenStore` сейчас хранит dev-токены в `PlayerPrefs`; для релиза нужен защищённый store. Авторитарное состояние матча клиент не сохраняет — источник истины и persistence на сервере. ### 4.3. Боевой вид - `BattleView` — HUD, оверлеи, меню карт, выборы, стоимость, guard, trigger, feedback; `Tick()` вызывается из сцены. - `GameBoard3D` — 3D-поле, слоты, рука, стопки, пул `Card3D`, drag/drop, диффы снапшота, `CardMoveAnimator`, `CameraDirector`, `ArrowDirector`. - `Card3D` — карта: арт/рубашка/торец, `SetPose`, Rest/флип, arc move, подсветка, stats; пул не уничтожает объект каждый кадр. Rest — +90° по часовой вокруг Y; карта урона лежит на боку против часовой; Trigger Zone своей стороны +90°, противника −90° относительно базы; флип — вокруг X. Эффект кладёт карту в Trigger до разрешения, затем в руку/урон. - `CardMoveAnimator` — очередь дуговых перемещений, stagger, `InputLock` `card-move`, таймаут/`CancelCard`; не оставлять лок навсегда. - `CameraDirector` — снимок pose, zoom/return/restore; `EnsureCamera` не перезаписывает камеру, пока режиссёр активен. - `TriggerCheckSequence` — zoom → reveal → return, цепочка, InputLock `trigger-sequence`, возврат перед выбором цели. - `ArrowDirector`/`AttackArrowView` — attack/boost стрелки, grow/dash, якоря у края карт. - `AnimationConfig` (ScriptableObject) — длительности, скорости, camera, arrow, stagger; `PlaymatLayoutConfig` — геометрия/камера/рука/зоны. - `InputLockService` — счётчик причин; `ReleaseAll` только по состоянию UI/ сбою, не для обхода законной блокировки. ## 4.4. C# и Unity-соглашения (фактические) - **Сервер:** file-scoped namespaces, `Nullable enable`, implicit usings, проекты `net10.0`; доменные типы — `sealed class`/`sealed record`, `required` для обязательных полей, `init` для неизменяемых данных. Исключения — только для инфраструктурных ошибок; игровые отказы — `CommandResult.Reject`. - **Клиент:** block-scoped namespaces; `MonoBehaviour` для сцен/компонентов; `ScriptableObject` для `AnimationConfig`, `PlaymatLayoutConfig`, `ArrowConfig`; приватные поля `_camelCase`, публичные свойства/методы `PascalCase`, сериализуемые поля — `lowerCamelCase`. - **События:** сервер — `GameEvent`/`TriggerFromCard`; клиент — C# `event Action` для UI/board. Не смешивай игровые события с UnityEvent, если нужна чистая логика. - **Асинхронность:** сеть/сервер — `async`/`Task`/`CancellationToken`; клиентский UI — `Tick()` из сцены и сервисные таймеры. DOTween/UniTask не установлены; не добавляй их без решения пользователя. - **Производительность:** пул `Card3D`, общие материалы/шейдеры, не `Instantiate/Destroy` каждый кадр; не создавай `Material`/`Texture2D` в `Update`; кешируй `GetComponent`. - **Сцены/префабы/метаданные:** сцены ссылаются на скрипты по GUID; при копировании сцен/контроллеров копируй `.meta`; не открывай второй Unity с тем же проектом; batch — копия без `Library`. - **Ресурсы:** статический UI/карты — `Resources`/`Resources/Overlays`; Addressables-пакет есть, но арт пока грузится через `CardImageService` из `Resources/CardArt`. Не обещай Addressables-миграцию без задачи. - **Логирование/ошибки:** сервер — `ILogger`; клиент — `Debug.Log`/ `Debug.LogException`; ошибки пользователю — статус/оверлей, не «сырой» stack trace. Не логируй токены, seed и скрытые карты. ## 5. Данные и ресурсы - Карты: `content/sets/*.yaml` (BT01/TD01/TD02), `content/translations/ru.yaml`, `content/keywords.yaml`, `content/trial-decks.yaml`, `content/pack_odds.yaml`. - `CardDefinition` — логика/статы; `CardPrinting` — конкретный арт. - 94 карты / 124 принта; 10 кланов; SP — отдельные принты. - Арт в клиенте сейчас через `Resources/CardArt/{imageKey}` и `CardImageService`; Addressables-пакет установлен, но активная загрузка арта ещё не переведена на него. - Данные карт не дублировать в ScriptableObject: SO — визуал/конфиг, статы и тексты — из каталога/сервера. - Тексты карт хранятся шаблонами с токенами (`{auto}`, `{cb:2}`, `{vcrc}`, `{clan:kagero}`); переводы не должны ломать токены. Ключевые слова/иконки — `content/keywords.yaml`, правила — `docs/translation-rules.md`. ## 5.1. Как добавлять функционал 1. Сначала правило/механика в `Vanguard.Rules` + тест; не начинать с UI. 2. Если меняется сетевой контракт — `Vanguard.Shared`, проекция, `IntentMapping`, пересборка DLL для Unity; версия/совместимость. 3. Сервер: `GameEngine`/`MatchGrain`/таймеры; не доверять клиенту. 4. Клиент: `GameConnection`/`BattleView`/`GameBoard3D`; только представление. 5. Проверки: rules/server tests, EditMode/smoke, `validate`, translations. 6. Обновить Skill, если решение долговременное; не дублировать в AGENTS. ## 6. Тесты и проверки - `server/tests/Vanguard.Rules.Tests` — `GameEngine`, карты, колоды, протокол, каталожные инварианты. - `server/tests/Vanguard.Server.Tests` — auth/meta, E2E, matchmaking, persistence, Orleans, practice bot, WebSocket. - `client/Assets/Vanguard/Tests/Editor` — раскладка, 3D-поле, оверлеи, локализация, smoke-инварианты. - `SceneSmoke.Run` / `RunPlaymat` — загрузка сцен/песочницы; `StandaloneBuild.BuildWindowsCli` — Windows-сборка. - Серверные тесты/validate/translations — только `ssh home`/Docker.