VanguardReMaster/.opencode/skills/vanguard-remaster/architecture.md

17 KiB
Raw Blame History

Архитектура 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. Границы решения

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<T> для 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.