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

240 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Архитектура 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<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.