diff --git a/.opencode/skills/vanguard-remaster/SKILL.md b/.opencode/skills/vanguard-remaster/SKILL.md index af13514..e35be09 100644 --- a/.opencode/skills/vanguard-remaster/SKILL.md +++ b/.opencode/skills/vanguard-remaster/SKILL.md @@ -1,270 +1,123 @@ --- 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. +description: Use for any work in the Vanguard ReMaster repository: Cardfight!! Vanguard rules and card abilities, Vanguard.Rules/GameEngine, WebSocket+MessagePack protocol and server, Unity 6 3D client and uGUI, tests, deployment, or project conventions. --- # Vanguard ReMaster -Рабочие правила для этого репозитория. Сначала прочитать -`docs/SESSION-STATE.md`, затем `docs/HANDOFF.md` и -`docs/client-visual-direction.md`. +Фанатский ремейк Cardfight!! Vanguard базовой эры: формат **Classic** +(BT01 + TD01/TD02), сервер на .NET 10 и Unity-клиент 6.6.6f1. Это рабочая база +правил проекта, а не пересказ официального компендиума. -## 1. Структура проекта +## Как пользоваться + +1. Перед задачей читай `docs/SESSION-STATE.md`, затем `docs/HANDOFF.md` и + `docs/client-visual-direction.md`. +2. Для правил/карт/эффектов открывай `game-rules.md` и + `card-implementation.md`. +3. Для сервера, клиента, протокола и зависимостей — `architecture.md`. +4. Для процесса, антипаттернов и открытых вопросов — + `workflow-and-decisions.md`. +5. Не читай все документы сразу, если задача узкая. Не пересказывай их в + ответе. + +Статусы в документах: + +- **[D]** — подтверждённое пользователем решение; +- **[C]** — фактическое устройство проекта (код/конфиг); +- **[O]** — открытый вопрос или незавершённая часть. + +## Критические правила + +- **Источник истины — сервер.** `GameEngine`/`MatchGrain` меняют состояние; + клиент только отправляет `IntentPayload` и рисует `MatchView`. +- **Не выдумывай правила и API.** Если правило не подтверждено, это `[O]`, а + не «как в Vanguard обычно». +- **Сначала вопрос, потом решение.** Для существенной неопределённости + используй инструмент `question` с 2–4 вариантами. +- **Минимальные изменения.** Не переписывай работающие системы, не создавай + дубликаты, не меняй сцены/префабы без причины. +- **Геймплей вручную проверяет пользователь.** Автотесты и smoke — да; + собственное прохождение не выдавай за приёмку. +- **Сервер проверяется только на `ssh home`/Docker.** Локальный `dotnet` и + Docker на рабочем ПК не запускать. +- **Unity batch — только в копии проекта без `Library`.** +- **Выбор объектов — клик/драг по карте, кругу, зоне или полю.** Кнопки — + только глобальные действия и подтверждения. +- **Одна текстовая способность — одна реализация.** `CardScript`, общий + движок или флаг данных. Текст без реализации — баг. +- **После блока:** клиентские EditMode, серверные тесты (на home), `validate`, + `translations check`, коммит и push. + +## Быстрая карта проекта ```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 тесты +content/ YAML карт, переводы, ключевые слова, триал-деки, шансы +docs/ правила, протокол, API, аудит способностей, состояние +server/src/Vanguard.Rules чистое ядро: карты, колоды, GameEngine +server/src/Vanguard.Shared DTO, MessagePack, проекции (netstandard2.1 + net10) +server/src/Vanguard.Content загрузка/валидация YAML +server/src/Vanguard.Server ASP.NET, auth/meta, матчи, Orleans, WebSocket +server/tests правила и сервер/E2E +client/Assets/Vanguard/Runtime Unity: Api/Auth/State/Meta/Battle/Board3D/UI/App +client/Assets/Vanguard/Editor SceneBuilder, StandaloneBuild, SceneSmoke +client/Assets/Vanguard/Tests EditMode ``` -Ключевые границы: +Ключевые классы: `GameEngine`, `GameState`, `CardScripts.Default`, +`MatchGrain`, `MatchProjection`, `GameConnection`, `BattleView`, +`GameBoard3D`, `Card3D`, `CardMoveAnimator`, `CameraDirector`, +`TriggerCheckSequence`, `ArrowDirector`, `InputLockService`. -- `Vanguard.Rules` — чистая библиотека. Не добавлять в неё сеть, БД, - Orleans, Unity или файловый I/O. -- `Vanguard.Shared` — контракты. `netstandard2.1`-сборка используется Unity, - поэтому там только DTO/сериализация без ссылки на `Vanguard.Rules`. -- `Vanguard.Server` зависит от `Rules`, `Shared`, `Content`; обратной - зависимости быть не должно. -- Клиент не является источником истины: правила, скрытая информация и - случайность остаются на сервере. +## Текущие подтверждённые ограничения -## 2. Сетевой код +- Формат — **Classic**, не Standard/Premium/G. Ride Deck, Persona Ride, + Stride, Gift, Legion, Front/Over-триггеры и G-зона не входят в текущую игру. +- Смешивать кланы в колоде **можно**; эффекты с условием клана могут не + сработать. `DeckValidator` этого не запрещает. +- 50 карт, 16 триггеров, ≤4 копий по `nameId`, ≤4 Heal, ≤4 Sentinel; в + коллекции лимита копий нет. +- Верхний `CombatStats` strip — допустимый HUD; world-space Power/Shield ещё + не сделаны. +- Отдельный `CostPaymentOverlay` для multi-target стоимости **требуется**, но + пока реализован только выбор Counter Blast в панели «Действия». +- `AttackResolutionQueue` — требование на будущее, сейчас бой имеет одну цель. +- `DropZoneFaceDown` существует в конфиге; по Classic default `false`. -### Протокол - -- Транспорт матча — 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 +# серверные тесты — только на home 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" -``` +# контент +ssh home "docker run --rm -v ~/VanguardReMaster:/src -w /src \ + mcr.microsoft.com/dotnet/sdk:10.0 dotnet run --project server/src/Vanguard.Cli -- validate" +ssh home "docker run --rm -v ~/VanguardReMaster:/src -w /src \ + mcr.microsoft.com/dotnet/sdk:10.0 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: +Smoke: `Vanguard.Editor.SceneSmoke.Run` / `RunPlaymat`; сборка: `Vanguard.Editor.StandaloneBuild.BuildWindowsCli`. -После блока: тесты, `validate`, `translations check`, коммит и push. +## Дополнительные документы -## 8. Триггеры и особые случаи +- [game-rules.md](game-rules.md) — правила Classic, зоны, фазы, триггеры, + стоимости, порядок эффектов, известные расхождения. +- [architecture.md](architecture.md) — сервер/клиент/протокол, границы, + источник истины, сохранение, Unity-слой. +- [card-implementation.md](card-implementation.md) — YAML, AUTO/ACT/CONT, + выборы, стоимости, тесты и чеклист новой карты. +- [workflow-and-decisions.md](workflow-and-decisions.md) — решения, процесс, + антипаттерны, известные ошибки и открытые вопросы. -- После выбора цели силы у **Draw** дополнительный эффект (draw 1) - разрешается сразу: второй цели на поле нет. -- **Heal** после цели силы просит карту только в **своей** зоне урона и - только если серверные условия выполнены (damage ≥ opponent и есть карты). - Нет карт/условий — дополнительный эффект не активируется. -- Tejas бьёт задний ряд своей колонки и с RC, и с VC (центральный задний - ряд); проверять `snipeBackRow` + `SameColumn`. -- Если у RC доступен только один круг перемещения в колонке, клиент - выполняет перемещение сразу, без выбора цели. -- Подсветка урона при выборе карты ограничена зоной урона локального игрока. +Связанные документы репозитория: `docs/game-rules.md`, +`docs/ability-implementation.md`, `docs/protocol.md`, +`docs/server-api.md`, `docs/client-visual-direction.md`, +`docs/translation-rules.md`. diff --git a/.opencode/skills/vanguard-remaster/architecture.md b/.opencode/skills/vanguard-remaster/architecture.md new file mode 100644 index 0000000..e6a76c6 --- /dev/null +++ b/.opencode/skills/vanguard-remaster/architecture.md @@ -0,0 +1,239 @@ +# Архитектура 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. diff --git a/.opencode/skills/vanguard-remaster/card-implementation.md b/.opencode/skills/vanguard-remaster/card-implementation.md new file mode 100644 index 0000000..6a1d895 --- /dev/null +++ b/.opencode/skills/vanguard-remaster/card-implementation.md @@ -0,0 +1,193 @@ +# Реализация карт и эффектов + +Источники: `content/sets/*.yaml` (`textEn`), `docs/ability-implementation.md`, +`server/src/Vanguard.Rules/Engine/Abilities/*`. + +## 1. Данные карты + +- `CardDefinition` — логическая карта по `nameId`: имя, тип, grade, power, + shield, critical, trigger, clan, `IsSentinel`, `IsRestraint`, + `BoostOverride`, `InterceptOverride`, `CanSnipeBackRow`, тексты. +- `CardPrinting` — конкретный принт: `printingId`, `nameId`, редкость, + `imageKey`, параллельный арт. +- В YAML добавляй/правь карту в `content/sets/{SET}.yaml`; текст — `textEn`. + Русский — `content/translations/ru.yaml`, не дублируй в set-файле. +- Статы/текст — не в ScriptableObject. SO в клиенте — визуал/конфиг, данные + карт приходят из каталога/сервера. +- Лимит 4 копий считается по `nameId`; `printingId` — единица коллекции/ + принта. Параллельный SP — отдельный принт, но та же логическая карта. + +## 2. Один текст — одна реализация + +Каждая текстовая способность карты должна иметь ровно одну реализацию: + +1. `CardScript` — `Abilities` (AUTO), `Activated` (ACT), `Continuous` (CONT); +2. общий движок: Sentinel, Heal/Trigger, Tejas `CanSnipeBackRow`, + Boost/Intercept, Restraint; +3. флаг данных, если поведение полностью описывается данными. + +Если текст есть, а реализации нет — это баг. Если реализация дублирует уже +существующий общий механизм — это тоже баг. Реестр: +`CardScripts.Default` в `CardScripts.cs` + `CardScriptsBT01.cs` + +`CardScriptsTD.cs`. + +## 3. AUTO + +```csharp +public override IEnumerable Abilities +{ + get + { + yield return new AbilityDefinition + { + Trigger = AbilityTrigger.OnPlace, + Optional = true, + Condition = ctx => ctx.Engine.IsRearGuardOf(ctx.Source, ctx.Owner) + && ctx.Catalog.CardOf(ctx.Source.PrintingId).Clan == Clan.Kagero, + CanPay = ctx => ctx.Engine.CanCounterBlast(ctx.Owner, 2), + Pay = ctx => ctx.Engine.CounterBlast(ctx.Owner, 2), + Resolve = ctx => { /* менять состояние только через Engine */ }, + }; + } +} +``` + +Правила: + +- `Trigger` — только реальное окно события из `AbilityTrigger`; не используй + «примерно подходящий» триггер. +- Проверяй зону источника: `IsVanguardOf`, `IsRearGuardOf`, `IsInHand`; + `OnPlace` не должен срабатывать от VC, если текст требует RC. +- `Condition` — проверка события/состояния; `CanPay` — можно ли оплатить; + `Pay` — оплата; `Resolve` — эффект. Не смешивай оплату и проверку. +- `Optional = true` — движок спросит «активировать/пропустить»; без оплаты + эффект не выполняется. +- `OnRideOver` — событие старой карты, `Other` — новая карта. `OnBoost` — + бустер, `Other` — атакуемый; `OnBoosted` — атакуемый, `Other` — бустер. +- `OnAttackHit` разрешается после урона/помещения карты в Damage и после + damage-триггеров; не переноси это окно в момент объявления атаки. +- `OnOpponentRetire` ищет способности на поле и в руке; не добавляй отдельный + обход, если общий движок уже это делает. +- AUTO не вызывает UI напрямую; он меняет состояние и добавляет события. + +Примеры: `ConroeScript` (`OnRideOver` + `AskEmptyRearGuardChoice`), +`GancelotScript` (ACT из VC, CB2, soul condition), `BarcgalScript` +(optional call + ACT search). + +## 4. ACT + +```csharp +public override IEnumerable Activated +{ + get + { + yield return new ActivatedAbility + { + CanActivate = ctx => ctx.Engine.IsVanguardOf(ctx.Source, ctx.Owner) + && ctx.Engine.CanCounterBlast(ctx.Owner, 2), + Resolve = ctx => ctx.Engine.CounterBlast(ctx.Owner, 2), + }; + } +} +``` + +- `CanActivate` — только условия; оплата и эффект — в `Resolve` (или + `Pay` для AUTO). +- Флаги: `RestsSelf`, `RetiresSelf`, `RequiresDiscard`, `FromHand`, + `FromDropZone`. +- `RestsSelf`/`RetiresSelf` из руки/дропа не активируются, если текст этого + не разрешает; движок проверяет это в `AddActivatedCommands`/`Activate`. +- `RequiresDiscard` — клиент выбирает карту в руке; сервер получает + `DiscardPrintingId` и проверяет наличие. +- ACT из руки/дропа попадает в `LegalCommands` только в разрешённой фазе; + клиент показывает действие у карты, не отдельным глобальным списком. +- Несколько валидных ACT на карте: клиент показывает меню/оверлей выбора, + а не отправляет случайную. + +## 5. CONT + +- Сила/крит: `ContinuousAbility` с `Applies`, `PowerBonus`, + `CriticalBonus`. +- Исключения, не выражаемые бонусом: `RestrictionType` + (`CannotAttack`, `CannotBoost`, `CannotStand`, `CannotIntercept`, + `CannotBeChosen`) или `CardScript.CanBeBoosted`. +- CONT пересчитывается из состояния каждый раз; не храни вычисленную силу как + истину. +- Источник/срок модификатора обязательны: `AddPower(..., effectId, + ModifierDuration...)`. +- Для «нельзя поддерживать этот юнит» используй `CanBeBoosted`, а не + `CannotBoost` (это разные стороны). + +## 6. Выборы и цели + +Разрешённые helper'ы: + +- `AskUnitChoice(player, opponent, prompt, slots, onChosen)`; +- `AskUnitMultiChoice(...)`; +- `AskHandCardChoice(...)`; +- `AskEmptyRearGuardChoice(...)`; +- `AskDamageChoice(...)` — внутренний для урона; +- `SearchDeckToHand/ToCall/ToRide` — поиск с автоматическим исходом: + игрок не просматривает колоду вручную; нет карт → shuffle + уведомление; + одна → сразу в руку/на поле; несколько → выбор; разные арты одной карты + (`printingId`) считаются разными результатами. + +Не выбирай карту/цель кнопками в UI вместо объекта. Клиент подсвечивает +валидные объекты и отправляет индекс/`damageIndex`; сервер проверяет по +своему состоянию. + +## 7. Стоимости + +- **Counter Blast**: `CanCounterBlast`, `CounterBlast`; при выборе карт + `DamageIndexes` (multi-target), старый `DamageIndex` — совместимость. +- **Soul Blast/Charge, Counter Charge**: общие методы `GameEngine`; не + дублируй их в скрипте карты. +- **Discard**: `RequiresDiscard` + `DiscardPrintingId`; карта того же клана — + только если текст/Perfect Guard это требует. +- **Rest/Retire self**: флаги `RestsSelf`/`RetiresSelf`. +- **Field units**: выбирай через `AskUnitChoice`/`AskUnitMultiChoice`, не + списком кнопок. +- **[O]** Multi-target Soul Blast и отдельный `CostPaymentOverlay` пока не + реализованы; не описывай их как готовые. + +## 8. Порядок, отмена, жизненный цикл + +- Движок запрещает посторонние команды во время `Step.Trigger/Ability/Choice`; + каждая цепочка должна завершиться или отменить выбор. +- `_resolvingDepth` не даёт новым эффектам исполняться рекурсивно; они + попадают в `PendingQueue` и разрешаются после текущего. +- Новые срабатывания внутри эффекта не запускай вручную; используй + `TriggerFromCard`/`TriggerAll`. +- `ModifierDuration` задаёт срок; не удаляй модификаторы вручную, если + движок уже чистит их по фазе. +- `Optional` без оплаты не должен оставлять состояние pending. +- Если эффект просит выбор, `Resolve` должен дойти до `Step.Choice` и + вернуться через `ChooseCommand`; не продолжай цепочку до выбора. +- Ошибки/некорректные индексы — `CommandResult.Reject`; не бросай исключения + наружу из игрового действия. +- Не подписывай карты на Unity-события и не храни ссылки на UI в Rules. + +## 9. Частые ошибки (из аудита) + +- `OnPlace` срабатывал от VC у карт, которым нужен RC (Starlight Unicorn, + Hungry Dumpty, Clay Doll Mechanic) — проверяй зону. +- Joka/Rakshasa получали бонус, находясь в руке — проверяй `IsRearGuardOf`. +- Conroe не имел AUTO `OnRideOver` — добавляй оба окна, если текст требует. +- Maiden of Libra не проверяла клан авангарда — проверяй условие текста. +- Alfred: CONT-запрет поддержки авангарда — через `CanBeBoosted`, не через + силу. +- Perfect Guard: без оплаты 0 щита и нет nullify; не давай щит «на всякий + случай». +- Heal: не проси цель, если урон меньше соперника или зона пуста. +- Draw: не открывай вторую цель; сразу тяни карту. + +## 10. Чеклист новой карты + +1. Добавь/исправь YAML, `textEn`; RU — в translations. +2. Разбери текст на AUTO/ACT/CONT/общие механики. +3. Реализуй ровно одну реализацию; проверь зону/условие/optional/стоимость. +4. Добавь тест хотя бы на один триггер/активацию/условие. +5. Проверь весь `CardScripts.Default` на текст без скрипта и дубли. +6. Запусти серверные тесты (на home), `validate`, `translations check`. +7. Для клиентских изменений — EditMode, smoke, при необходимости standalone. +8. Не тестируй геймплей вручную за пользователя; он проверяет сам. diff --git a/.opencode/skills/vanguard-remaster/game-rules.md b/.opencode/skills/vanguard-remaster/game-rules.md new file mode 100644 index 0000000..52b7a9d --- /dev/null +++ b/.opencode/skills/vanguard-remaster/game-rules.md @@ -0,0 +1,241 @@ +# Правила Vanguard ReMaster (Classic) + +Статусы: **[D]** подтверждено пользователем, **[C]** подтверждено кодом, +**[O]** неизвестно/расхождение. Полный конспект официальных правил и сверка — +`docs/game-rules.md`; здесь рабочая база проекта. + +## 1. Формат и матч + +- **[D]** Формат проекта — **Classic**: BT01 + TD01/TD02. Не Standard, + Premium или G Standard. +- **[D]** Матч BO1; сайд-дека нет. +- **[D]** В Classic нет Ride Deck, Persona Ride, Stride, Legion, Gift, + G-зоны, Bind, Front/Over-триггеров и Grade Support. +- **[C]** В матче два игрока; первый определяется seeded RNG, но может быть + зафиксирован тестом/сервером. +- **[C][D]** Первый ход: нет Battle и атак. `GameEngine` умеет отключать это + правило флагом для тестов, но серверный матч использует запрет. +- **[D]** Таймер: 90 секунд на ход + банк 5 минут; отсутствие 5 минут — + поражение. Реконнект: 0/2/10/30 секунд, затем полный snapshot. +- **[C]** Победа/поражение: 6 карт урона, пустая колода, сдача/выход/разрыв, + истечение времени или отсутствие. Дополнительное условие «авангард исчез» + есть в конспекте, но отдельной проверки в `GameEngine` нет — **[O]**. + +## 2. Колода + +**[D][C]** Основная колода: + +- ровно **50 карт**; +- ровно **16 триггер-карт**; +- не более **4 копий одной карты по `nameId`**, а не по принту/арту; +- не более **4 Heal-триггеров**; +- не более **4 Sentinel**; +- стартовый авангард — карта grade 0 без триггера; движок выбирает первого + подходящего кандидата из колоды; +- **смешивать кланы разрешено** [D, 2026-10-24]. Это осознанное отклонение от + официального ограничения: эффекты, требующие клан/условие, могут не + сработать. `DeckValidator` единство клана не проверяет. +- Колода должна быть собрана из карт, которыми владеет игрок (проверка + `DeckService`); в коллекции лимита копий нет — 4 копии ограничивают только + колоду. +- **[O]** Легальность по формату/banlist и обязательное наличие ровно одного + стартового авангарда в `DeckValidator` отдельно не выделены. + +## 3. Начало партии + +1. Перемешать колоды. +2. Положить стартового авангарда grade 0 без триггера в VC закрытым. +3. Взять по 5 карт. +4. Mulligan: каждый игрок один раз может вернуть любое количество карт + **в низ колоды**, добрать столько же сверху, затем перемешать. Оба игрока + проходят этап. Возвращённые карты не должны попасть в тот же добор. +5. Открыть стартовых авангардов; первый игрок начинает. + +**[C]** В коде `MulliganCommand` сразу выполняет возврат → добор → shuffle, а +`FinishMulliganCommand` передаёт очередь второму игроку; отдельной команды +«выбрать карты и подтвердить» нет. + +## 4. Зоны + +| Зона | Смысл | +|---|---| +| Deck | закрытая колода, добор сверху | +| Hand | рука; у противника видно только количество | +| Vanguard Circle (VC) | текущий авангард; под ним — Soul | +| Rear-guard Circles (RC) | 5 кругов: 0/1/2 задний ряд, 3/4 передний | +| Guardian Circle (GC) | временная зона защитников/перехватчиков | +| Damage | урон; 6+ — поражение | +| Drop | сброс/уничтоженные карты | +| Trigger | временная зона проверки триггера | +| Soul | карты под VC, не отдельная стопка на поле | + +- **[C]** Карты на VC/RC/GC открыты. Закрытыми могут быть рука/колода и урон + после Counter Blast. +- **[C]** Урон входит **лицом вверх**; Counter Blast переворачивает выбранные + карты лицом вниз; Counter Charge — обратно. +- **[D]** Рука противника отображается только рубашкой; колода — только + количеством; урон лицом вниз — только количеством. +- **[C]** Soul не показывается на столе; доступна через ПКМ/список у VC. +- **[D]** Drop по умолчанию пуст; сверху показывается последняя карта, по + клику — весь список. + +## 5. Фазы хода + +**[C]** Порядок в движке: `Stand → Draw → Ride → Main → Battle → End`. +Mulligan — отдельная фаза до первого хода. + +### Stand + +Все юниты активного игрока в Rest поднимаются, если нет `CannotStand`. +Ограничения `UntilOwnerNextStand` снимаются после подъёма. + +### Draw + +Взять 1 карту сверху. Пустая колода — поражение. + +### Ride + +- Карта из руки на VC; ранг **равен текущему или на 1 выше**. +- Старый авангард уходит в Soul. +- Ride — один раз за ход; можно пропустить. +- Новый авангард входит стоя и открытым. +- **[C]** После Ride сначала срабатывает `OnPlace` новой карты, затем + `OnRideOver` старой; если `OnPlace` открыл pending-способность, текущая + реализация не запускает `OnRideOver` до её завершения. Это техническая + деталь, а не новое правило; при работе с такими картами проверяй сценарий. + +### Main + +- **Call**: из руки в RC; grade ≤ grade своего VC. Занятый RC разрешён: + прежний юнит уходит в Drop. Призыв из soul/deck и выбор круга работают так + же. Число призывов не ограничено. Юнит входит стоя. +- **Move**: RC ↔ RC только в своей колонке: 0↔3, 2↔4; занятые меняются + местами. VC в Move не участвует. +- **Activate**: ACT-способности; AUTO срабатывают по событиям. +- **End Main**: переход в Battle. +- **[C]** В Main также доступны `EndTurn` и (если разрешено) `EnterBattle`. + +### Battle + +1. Атака: стоящий юнит **переднего ряда** (VC или RC 3/4), он уходит в Rest. + Цель — VC или передний RC противника. +2. Boost: стоящий юнит 0/1 ранга из заднего ряда той же колонки уходит в Rest; + его сила добавляется атакующему до конца боя. Карта может получить Boost + эффектом. +3. Guard: защищающийся добавляет карты из руки в GC. Grade ≤ grade своего + авангарда. Щит добавляется сразу. +4. Intercept: подходящий RC (обычно grade 2, передний ряд) выходит в GC; + атака переносится на него. Нельзя перехватывать атаку по самому себе. +5. Drive Check: только из VC; 0–2 ранг — 1 проверка, 3+ — 2; эффект может + изменить/обнулить. Арьергард по умолчанию проверок не делает. +6. Сравнение: сила атакующего **>=** силы защищающегося + щит — попадание. + Иначе промах. При попадании по RC цель уходит в Drop; при попадании по VC + наносится урон, равный критическому удару. +7. Damage Check: по одной карте за урон; триггеры срабатывают, затем карта + идёт в Damage. +8. После боя Guardian/Intercept уходят в Drop. Атакующий может атаковать + снова, если есть стоящие передние юниты. + +**[C]** Boost/Interceptor/Guard карты и их модификаторы очищаются в +`ClearBattle`; `UntilEndOfBattle` не переживает бой. + +### End + +Сначала возвращаются карты с `ReturnToDeckTiming.EndOfTurn`, затем снимаются +`UntilEndOfTurn` модификаторы/выданные способности/ограничения. Ход переходит +противнику, начинается Stand. + +## 6. Атака, защита, крит и урон + +- **[C]** Обычная цель — только VC или передний RC. Задний ряд можно атаковать + только `CanSnipeBackRow` (Tejas) и только в своей колонке. Tejas бьёт задний + ряд и с VC (центральная колонка), и с RC. +- **[C]** Атака/Boost/часть ACT переводят карту в Rest. +- **[C]** Guard: `def.Grade > VanguardGrade` отклоняется. Sentinel сам по себе + даёт 0 щита. Perfect Guard: сброс карты **того же клана** отменяет атаку; + без оплаты — 0 щита и отмены нет. Сброс — опциональная стоимость. +- **[C]** Guard/Intercept карты после боя уходят в Drop; `OnLeaveGuardian` + срабатывает после закрытия боя. +- **[C]** Урон — лицом вверх; 6-я карта вызывает поражение после разрешения + эффектов. Damage Check-триггер сначала кладёт карту в Trigger, применяет + эффект, затем в Damage. +- **[C]** Heal доступен, если урон игрока **>=** урона соперника и в своей + зоне есть карты; лечится карта только из своей зоны урона. + +## 7. Триггеры + +**[D][C]** В Classic только Critical, Draw, Stand, Heal. Front/Over — нет. + +- Триггеры срабатывают при Drive Check и Damage Check. +- Базовый бонус силы — **+5000**; действует до конца хода. +- Power target и второй эффект могут быть разными юнитами, если текст не + запрещает. +- **Critical**: +5000 и +1 крит выбранному юниту. +- **Stand**: +5000 и поднять выбранного отдыхающего арьергарда; если + арьергардов нет, второй эффект не применяется. +- **Draw**: +5000 и немедленно взять карту; отдельной цели для draw нет. +- **Heal**: +5000 и, при условии, лечение 1 урона из своей зоны; если нет + карт/условия — лечение не активируется. +- **[C]** На Damage Check `GrantsPower = true`: усиление тоже применяется. +- После Drive Check карта уходит в руку; после Damage Check — в Damage. +- Бонусы триггеров снимаются в конце хода, в том числе у противника. + +## 8. Стоимости + +| Стоимость | Реализация | +|---|---| +| Counter Blast X | перевернуть X **открытых** карт урона лицом вниз; при выборе — конкретные | +| Counter Charge X | перевернуть X закрытых карт лицом вверх; если меньше — сколько есть | +| Soul Blast X | X карт из Soul в Drop | +| Soul Charge X | X верхних карт колоды в Soul | +| Discard | выбранная карта из руки в Drop | +| Rest self | повернуть источник | +| Retire self | убрать источник в Drop | + +- **[D]** Multi-target Counter Blast: клиент выбирает карты урона; сервер + принимает `DamageIndexes` и переворачивает именно их. Старый одиночный + `DamageIndex` сохранён для совместимости. +- **[O]** Multi-target Soul Blast и другие стоимости ещё не имеют такого же + выбора/протокола. +- **[D]** Стоимость выбирается по объектам, подтверждение — отдельной кнопкой + панели/оверлея; отдельный `CostPaymentOverlay` требуется, но пока не + реализован (текущий CB использует счётчик в панели «Действия»). + +## 9. Эффекты и порядок разрешения + +- **[C]** Источник истины — `GameEngine`; он меняет `GameState` и пишет + `GameEvent`. +- **[D]** Эффект, сработавший при прохождении атаки, разрешается **после** + получения урона и помещения карты в Damage. +- **[D]** Нельзя начинать новый эффект посреди текущего: цепочка идёт строго + друг за другом. `_resolvingDepth` складывает новые триггеры в + `PendingQueue`; после завершения текущего они предлагаются/разрешаются. +- **[D]** Рекурсию не поддерживаем; при найденном кейсе — отдельное решение. +- Несколько одновременных AUTO: игрок выбирает порядок; активный игрок первым + получает доступ к своим срабатываниям (`PendingOrder`/`SelectTriggerCommand`). +- Optional AUTO: движок спрашивает «активировать/пропустить»; без `Pay` + эффект не выполняется. `CanPay` проверяется до предложения. +- CONT пересчитывается из состояния; вычисленную силу не хранить как истину. +- Модификаторы/способности/ограничения имеют источник, `EffectId` и + `ModifierDuration`: `UntilEndOfBattle`, `UntilEndOfTurn`, + `UntilOwnerNextStand`, `Permanent`. +- `OnPlace` не должен срабатывать от VC, если текст требует RC; проверяй + `IsVanguardOf`/`IsRearGuardOf`/`IsInHand`. + +## 10. Особые случаи + +- **[D]** Move: если у RC доступен только один круг в колонке, клиент + выполняет перемещение сразу, без выбора цели. +- **[D]** Draw после выбора цели силы сразу тянет карту; второй цели нет. +- **[D]** Heal просит цель только в своей зоне урона и только при выполнении + серверных условий. +- **[D]** Tejas атакует задний ряд своей колонки с VC/RC. +- **[D]** Подсветка урона ограничена зоной урона локального игрока и режимом + оплаты; вне оплаты урон не подсвечивается. +- **[D]** Call в занятый RC заменяет юнита; старый уходит в Drop. +- **[D]** В первый ход атака и переход в Battle недоступны. +- **[C]** `OnRideOver` текущей реализации срабатывает после `OnPlace`, но + может быть отложен/пропущен при pending-способности `OnPlace`; это + техническая граница, проверяй карты с обоими окнами. +- **[O]** Отдельная проверка «авангард исчез» в `GameEngine` не реализована; + не описывай её как гарантированное поражение без решения. diff --git a/.opencode/skills/vanguard-remaster/workflow-and-decisions.md b/.opencode/skills/vanguard-remaster/workflow-and-decisions.md new file mode 100644 index 0000000..b37fef8 --- /dev/null +++ b/.opencode/skills/vanguard-remaster/workflow-and-decisions.md @@ -0,0 +1,194 @@ +# Решения, процесс и антипаттерны + +## 1. Источники аудита + +**[C]** Проверены: + +- `.opencode/skills/vanguard-remaster/SKILL.md` (прежняя версия); +- `AGENTS.md`, `docs/SESSION-STATE.md`, `docs/HANDOFF.md`, + `docs/game-rules.md`, `docs/ability-implementation.md`, + `docs/protocol.md`, `docs/client-visual-direction.md`, + `docs/3d-ux-backlog.md`, `docs/translation-rules.md`; +- локальная база OpenCode: две сессии Vanguard ReMaster (1259 и 5617 + сообщений), включая сообщения пользователя и финальные тексты ассистента; +- исходный код server/client, YAML-контент, тесты, `README.md`, + `DEVELOPMENT_CHECKLIST.md`, `content/cards-mvp.md`. + +История доступна локально, но не является «полной истиной»: часть решений +могла быть принята вне OpenCode. В спорных местах ниже стоит **[O]**, а не +выдуманное правило. + +## 2. Подтверждённые решения + +### Игра и контент + +- **[D]** Фанатский ремейк Vanguard, формат **Classic**, BT01 + TD01/TD02, + 94 карты / 124 принта. +- **[D]** Две триал-деки: TD01 «Blaster Blade», TD02 «Dragonic Overlord»; + полный декбилдер. +- **[D]** Официальные арты; локализация EN + RU. +- **[D]** В коллекции нет лимита копий/артов; 4 копии по имени — только при + сборке колоды. +- **[D]** Смешивать кланы в колоде разрешено; эффекты с клановыми условиями + могут не работать. Это осознанное правило проекта. +- **[D]** Сайд-дека нет, матч BO1. +- **[D]** Ride Deck/Persona Ride не входят в Classic/текущую игру. +- **[D]** Стартовый авангард — grade 0 без триггера; после обучения игрок + получает триал-деку на выбор и 5 токенов; 1 токен = 1 бустер. +- **[D]** Таймер: 90 с на ход + банк 5 мин; отсутствие/сдача/разрыв — + поражение. +- **[D]** В первый ход нельзя атаковать и переходить в Battle. +- **[D]** Локализация: RU+EN; «ё» разрешена; «усиленная карта»; «уничтожить» + оставляем; фазы — Основная/Боевая/Завершающая/Подъёма/Добора/Смены; + close step — завершающий шаг; call — призвать; названия зон не переводим; + имена карт остаются английскими (`nameRu` — задел). Детали — + `docs/translation-rules.md`. + +### Бустеры и экономика + +- **[D]** В бустере 5 карт: 4 C + 1 гарантированная R+. +- **[C]** Текущие веса R+: R 70% / RR 22% / RRR 7% / SP 1% + (`content/pack_odds.yaml`). Пользователь поручил подобрать вероятности, но + точные числа не перепроверял; это конфиг, а не вечный закон. +- **[D]** Дубли считаются по `printingId`, порог 4; SP — отдельный принт; + при отсутствии замены карта остаётся в коллекции и разбирается в пыль. +- **[D]** Gold начисляется за победу и поражение; **[C]** текущая конфигурация + — 30 / 15 / 20 (победа/поражение/ничья), Gold = XP / 5. Точные числа — + балансный конфиг, можно менять. +- **[C]** Бустер 100 Gold, триал-дека 500 Gold; за сдачу/АФК награды не + начисляются (`RewardsForForfeit = false` по умолчанию). +- **[C]** Пыль: разбор C/R/RR/RRR = 5/10/25/100, крафт ×4; фойл ×2. +- **[C]** XP пишется с первого дня; баттлпасс/квесты/Draft — после MVP. + +### Клиент и UX + +- **[D]** 3D-сцена, отдельные сцены Login/Lobby/DeckBuilder/Battle; HUD — + uGUI; reference 1920×1080. +- **[D]** Камера в покое фиксирована по центру поля с небольшим наклоном; + временно двигается только `CameraDirector` в trigger-последовательности. +- **[D]** Выбор карты/круга/зоны/цели — клик/драг по объекту; кнопки только + для глобальных действий/подтверждений. +- **[D]** Guard: карты из руки с grade ≤ grade своего Vanguard; одна кнопка + «Не защищаться»/«Защищаться»; выбранные подсвечиваются, щит считается + сразу, после подтверждения — в Guardian. +- **[D]** Карты противника нельзя драгать; можно выбирать целью, смотреть + детали и зоны. Клик по своей карте — меню валидных действий. +- **[D]** Зоны открываются общим горизонтальным `CardListView`; сверху — + последняя добавленная карта; из списка доступен `CardDetailOverlay`. +- **[D]** Call в занятый RC разрешён; старый юнит → Drop. +- **[D]** Move: RC↔RC в колонке; занятые меняются; при единственном + доступном круге клиент перемещает сразу. +- **[D]** Draw: после цели силы сразу тянет карту; Heal: только своя зона + урона и только при условиях; Tejas: задний ряд своей колонки. +- **[D]** Optional AUTO — оверлей «Активировать/Пропустить»; ACT из руки — + меню карты; оплата — по объектам, подтверждение в панели/оверлее. +- **[D]** Mulligan: выбор карт в руке; после замены этап завершается + автоматически, неактивная кнопка «Закончить муллиган» не остаётся. +- **[D]** Панель фаз переименована в «Действия» и содержит guard, + подтверждение оплаты и глобальные действия; список всех возможных смен + там не показывается. +- **[D]** Под картами V/R показываются динамические Power/Critical; + верхний `CombatStats` strip — допустимый HUD; world-space Power/Shield — + отложенный долг. +- **[D]** Отдельный `CostPaymentOverlay` для multi-target стоимости нужен; + текущий CB в панели «Действия» — промежуточная реализация. + +## 3. Фактическое устройство (не путать с договорённостью) + +- **[C]** `GameEngine` — авторитарное ядро; `MatchGrain` сериализует интенты; + `MatchProjection` скрывает информацию. +- **[C]** Multi-target CB уже поддержан `DamageIndexes` в + `ActivateAbilityIntent`; старый `DamageIndex` оставлен. +- **[C]** `CardMoveAnimator`, `CameraDirector`, `TriggerCheckSequence`, + `ArrowDirector` есть; анимации на `Tick`, без UniTask/DOTween. +- **[C]** `CardMoveAnimator` имеет таймаут и `CancelCard`; это исправление + зависшего `card-move`-лока (коммит `fd21a96`). +- **[C]** `DropZoneFaceDown` есть, default `false`. +- **[C]** `DeckValidator` не проверяет единство клана; это соответствует + решению «смешивать можно». +- **[C]** `CombatStats` — верхний uGUI strip; отдельного боевого overlay нет. +- **[C]** `docs/protocol.md` ранее указывал неверный порядок отображения + урона; фактически сначала открытые, затем закрытые. + +## 4. Процесс разработки + +1. Прочитай Skill и связанные docs; найди существующую реализацию. +2. Проверь, не решён ли вопрос в коде/истории. Не спрашивай о том, что уже + установлено. +3. Если есть существенная неопределённость — `question` с 2–4 вариантами. +4. Вноси минимальные изменения; не трогай несвязанное. +5. Для правил: тест на сценарий; для карт — тест на триггер/активацию; + для клиента — EditMode/smoke. +6. Серверные проверки — только `ssh home`/Docker; Unity batch — копия без + `Library`. +7. После блока: `validate`, `translations check`, коммит/push. +8. Геймплей вручную проверяет пользователь; не выдавай свой тест за приёмку. + +## 5. Антипаттерны + +| Не делать | Почему | Как делать | +|---|---|---| +| Список кнопок вместо клика по карте/кругу | Нарушает принятый UX | Подсветка объектов + клик/драг | +| `transform.position = ...` в игровой логике | Ломает анимации/локи | `Card3D`/`CardMoveAnimator`/`SetPose` | +| Дублировать эффект в нескольких `CardScript` | Расходится поведение | Один `CardScript`, общий движок или флаг | +| `OnPlace` без проверки VC/RC | Срабатывает не в той зоне | `IsVanguardOf`/`IsRearGuardOf` | +| Менять `GameState` на клиенте | Рассинхрон с сервером | Только `IntentPayload`, рисовать snapshot | +| Запускать `dotnet`/Docker локально | Запрещено пользователем | `ssh home`/Docker | +| Второй Unity с тем же проектом | Ломает Library/импорт | Копия без `Library` | +| Заявлять тест без запуска | Вводит в заблуждение | Указать, что проверено, а что нет | +| Самому проходить геймплей как приёмку | Пользователь проверяет лично | Автотесты + передать сценарий | +| Менять `Vanguard.Shared` без пересборки DLL | Unity увидит старый контракт | Собрать netstandard2.1 и заменить DLL | +| Вечный `InputLock` без таймаута | Блокирует ход/смену | try/finally, timeout, `CancelCard` | +| Освобождать карту из пула во время анимации/обмена | Дубли/неактивные карты | Сначала распределить по `InstanceId`, потом release | +| Новый универсальный effect/DSL без нужды | Усложняет и дублирует | Расширять существующие `AbilityDefinition` | +| Хранить вычисленную силу как истину | CONT пересчитывается | `EffectivePower`/модификаторы | +| Показывать все возможные смены в панели | Пользователь это запретил | Действия из меню карты/валидных интентов | +| Перехватывать клики информационным оверлеем | Мешает выбору по объекту | `raycastTarget = false`/скрыть ввод | +| Считать `printingId` и `nameId` одним | Ломает лимиты/коллекцию | `nameId` — 4 копии, `printingId` — принт | +| Perfect Guard даёт щит без оплаты | Неверная защита | Без оплаты 0 щита и нет nullify | +| Heal просит цель при невыполненных условиях | Лишний выбор/зависание | Сначала серверная проверка, потом `AskDamageChoice` | + +## 6. Известные ошибки и уроки + +- **Зависший `card-move`-лок** (2026-10-24): карта освобождалась из пула, + оставаясь в `_active`; `Tick` больше не вызывался, `InputLock` не + снимался. Исправлено таймаутом/`CancelCard`. Не возвращать. +- **Дубли/нахлёст при обмене RC**: нельзя освобождать карту из занятого + ключа, если она должна переехать в другой ключ. Сначала распределение по + `InstanceId`, затем release. +- **Conroe** долго не имел AUTO `OnRideOver` — проверять весь текст, а не + только очевидные ACT/CONT. +- **Barcgal**: при выборе призыва из колоды клиент должен показать оверлей + доступных карт; не подставлять первый круг/карту автоматически. +- **Ride после хода противника** ломался из-за клиентского ввода/логики, а не + только сервера; проверять lock и `LegalIntents`. +- **Пустой Mulligan**: после замены карт кнопка «Закончить муллиган» не + должна оставаться неактивной; муллиган завершается автоматически. +- **Guard дубли**: Guardian и `Battle.GuardCards` могут содержать одну + карту; клиент дедуплицирует по `InstanceId`. +- **Perfect Guard**: без discard карта даёт 0 щита; не считать Sentinel + обычным щитом. +- **Trigger check**: карта сначала в Trigger, затем эффект, затем hand/ + damage; не переставлять. + +## 7. Открытые вопросы + +- **[O]** Отдельная проверка «авангард исчез» в `GameEngine` отсутствует; + нужно ли добавлять — решать пользователю. +- **[O]** `OnPlace` + `OnRideOver` при pending-способности `OnPlace`: + текущая реализация может не запустить `OnRideOver`; нужно ли чинить — + отдельное решение. +- **[O]** Multi-target Soul Blast/другие стоимости: протокол и UI ещё не + сделаны. +- **[O]** `CostPaymentOverlay` требуется, но его форма/`EffectConfig` и + момент включения ещё не реализованы; текущий CB — промежуточный. +- **[O]** World-space Power/Shield: верхний strip принят как HUD, перенос — + долг. +- **[O]** `AttackResolutionQueue` и несколько boost-стрелок — будущая + архитектура, сейчас бой с одной целью. +- **[O]** `DropZoneFaceDown` UX: default `false`; нужен ли другой режим — + не решено. +- **[O]** Точная легальность формата/banlist и обязательность ровно одного + стартового авангарда в `DeckValidator` не выделены. +- **[O]** Полная история вне OpenCode недоступна; спорные места не считаются + восстановленными. diff --git a/AGENTS.md b/AGENTS.md index d98da58..6f4075e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,76 +1,120 @@ # Правила работы -Это общие правила для ассистента и будущих сессий. +Это общие правила для ассистента и будущих сессий. Детали проекта — в Skill +`vanguard-remaster`; здесь только обязательный процесс. ## Общение -- **Ответы короткие.** Не повторять уже сохранённое в документации, не - пересказывать код и не добавлять длинные вступления. Экономить токены. -- **Любое сомнение или вопрос — диалог.** Если есть выбор, неоднозначность - или риск, сначала показать 2–4 варианта с краткой рекомендацией, дать - пользователю выбрать или ответить самому. Не угадывать и не принимать - решение за него. +- Отвечай **кратко, по существу и на русском**, если не попросили другой язык. +- Не пересказывай задачу, прочитанные файлы и очевидные действия. +- Не пиши длинные вступления, заключения и отчёты по каждому шагу. +- Сообщай только: изменённые файлы, результаты проверок, важные решения и + блокеры. +- Подробные объяснения давай по запросу или если без них можно сделать + неверный вывод. -## Контекст +## Контекст и Skill -- **При работе с этим проектом всегда загружать скилл - `vanguard-remaster`** (`.opencode/skills/vanguard-remaster/SKILL.md`) - до начала анализа или правок. -- Перед началом работы читать `docs/SESSION-STATE.md`, затем - `docs/HANDOFF.md` и `docs/client-visual-direction.md`. -- Главное состояние и следующие шаги хранить в `docs/SESSION-STATE.md`, - подробную историю — в `docs/HANDOFF.md`. -- После блока: тесты, `validate`, `translations check`, коммит и push. +- Перед любой задачей по правилам, картам, способностям, ходам, состоянию + матча, архитектуре, протоколу, клиенту или тестам **загружай Skill + `vanguard-remaster`**. +- В Skill есть отдельные документы: + - `game-rules.md` — подтверждённые правила ККИ; + - `architecture.md` — фактическая архитектура и границы систем; + - `card-implementation.md` — реализация карт и эффектов; + - `workflow-and-decisions.md` — соглашения, антипаттерны и открытые вопросы. +- Перед началом работы читай `docs/SESSION-STATE.md`, затем + `docs/HANDOFF.md` и `docs/client-visual-direction.md`; при работе с + правилами — `docs/game-rules.md`, с картами — `docs/ability-implementation.md`. +- Не пересказывай в ответе то, что уже сохранено в этих документах. + +## Никаких выдумок + +- Не выдумывай правила игры, карты, эффекты, API, классы, пути и пакеты. +- Не считай правила других ККИ правилами этого проекта. +- Не превращай предположение в договорённость. В Skill и документах явно + разделяй: подтверждённое решение, фактическое устройство кода, предположение, + открытый вопрос. +- Перед реализацией изучай существующий код и найденные договорённости. +- Если решение не определено и от него зависит реализация — сначала спроси. + +## Вопросы + +- Для существенной неопределённости используй специальный инструмент + `question` (если он недоступен — задай вопрос обычным сообщением). +- Предлагай 2–4 коротких варианта с рекомендацией и возможность своего ответа. +- Дождись ответа, прежде чем принимать зависящее от него решение. +- Не спрашивай о том, что уже однозначно установлено кодом, документацией или + историей. Не прерывай работу ради мелочей. + +## Изменения + +- Сначала найди существующую реализацию и связанные зависимости. +- Делай минимальные изменения; не переписывай работающие системы без причины. +- Не создавай дубликаты существующих классов, эффектов, оверлеев и сервисов. +- Не меняй несвязанный код, сцены и префабы. +- Сохраняй совместимость существующих механик и контрактов. +- Учитывай побочные эффекты: пул карт, `InputLockService`, порядок событий, + скрытую информацию и авторитет сервера. +- Если ранее согласованное правило расходится с кодом, не выбирай молча: + зафиксируй расхождение и задай вопрос. + +## Игровые проверки + +- **Геймплей вручную проверяет пользователь.** Не запускай тестовый бой и не + объявляй игровую механику принятой по собственному прохождению. +- Автоматические проверки правил, клиентские EditMode-тесты, `validate`, + `translations check` и smoke-проверки запускать можно и нужно. +- Не заявляй об успешной проверке, если она не запускалась. Указывай, что + именно проверено, а что осталось на ручную проверку. + +## Окружение + +- **Не запускай `dotnet` и Docker на рабочем ПК.** Серверные тесты, `validate` + и `translations check` — только на `ssh home` / Docker. +- **Не открывай второй экземпляр Unity с тем же проектом.** Для batch-проверок + делай копию проекта без `Library`. +- `Vanguard.Rules` остаётся чистой библиотекой: без сети, БД, Orleans, Unity и + файлового I/O. +- `Vanguard.Shared` — только DTO/протоколы; `netstandard2.1`-сборка + используется Unity. При изменении протокола пересобирай и заменяй + `client/Assets/Plugins/Vanguard.Shared/Vanguard.Shared.dll`. +- Клиент не является источником истины: состояние матча, RNG и скрытые данные + остаются на сервере. ## UI и взаимодействие -- **Выбор делаем интерактивом по объектам:** клик/драг по карте, кругу, - зоне или полю. Не заменяем выбор карты, круга или цели списком кнопок в - интерфейсе. -- Кнопки допустимы только для глобальных действий и подтверждений: - завершить ход, сдаться, отмена, принять/пропустить опциональную - способность. Если выбирается конкретная карта/круг/зона — это делается - кликом по объекту. -- Информационные оверлеи (`ActionPrompt`, подсказки) не должны перехватывать - клики и не дублируют выбор кнопками. +- Выбор карты, круга, зоны или цели — **клик/драг по объекту на поле**, а не + список кнопок в интерфейсе. +- Кнопки — только глобальные действия и подтверждения: завершить ход, сдаться, + отмена, принять/пропустить optional, подтвердить оплату. +- Информационные оверлеи не перехватывают клики и не дублируют выбор кнопками. +- Верхний `CombatStats` strip считается допустимым HUD; world-space + Power/Shield — отложенный долг. +- Для multi-target стоимости требуется отдельный `CostPaymentOverlay`; текущая + панель «Действия» считается промежуточной реализацией, а не отменой требования. -## Способности +## Карты и способности -- Каждая текстовая способность карты должна иметь ровно одну реализацию: - `CardScript` (`Abilities`/`Continuous`/`Activated`), общий движок - (Sentinel, Heal/Trigger, Tejas) или флаг данных. Текст без реализации — - баг. -- **AUTO**: `AbilityDefinition` с `Trigger`, `Condition`, `Optional`, - `CanPay`/`Pay` и `Resolve`. Проверяй зону/источник через - `IsVanguardOf`/`IsRearGuardOf`/`IsInHand`; `OnPlace` не должен - срабатывать от VC, если текст требует RC. -- **ACT**: `ActivatedAbility`; флаги `RestsSelf`, `RetiresSelf`, - `RequiresDiscard`, `FromHand`, `FromDropZone`. `CanActivate` — только - проверка условий; оплата и эффект — в `Resolve` (или `Pay` для AUTO). -- **CONT**: `ContinuousAbility` для силы/критики; исключения вроде - «нельзя поддерживать» — через `CardScript.CanBeBoosted`, ограничения - передвижения/атаки — через `RestrictionType`. -- **Sentinel**: `IsSentinel` в данных, общий механизм Guard + сброс карты - того же клана; отдельный скрипт не нужен. -- **Trigger/Heal**: данные `trigger`; общий движок проверок и лечения. -- **Ключевые механики**: Tejas — `CanSnipeBackRow`; Restraint — флаг; - Boost/Intercept — `HasBoost`/`HasIntercept` и общий движок. -- **Выбор и цели**: только `AskUnitChoice`, `AskUnitMultiChoice`, - `AskHandCardChoice`, `AskEmptyRearGuardChoice`; клиент не выбирает - карты кнопками вместо объектов. -- **Клиент**: AUTO-подтверждение — оверлей; ACT — меню/оверлей карты; - стоимость способности (сброс/урон/поле) выбирается по объектам и - подтверждается кнопкой панели «Действия». -- После правок способностей проверять весь реестр `CardScripts.Default`, - тесты правил, `validate` и `translations check`. -- Полный аудит 66 карт и правила для новых карт — - `docs/ability-implementation.md`. +- Каждая текстовая способность карты имеет ровно одну реализацию: `CardScript` + (`Abilities`/`Continuous`/`Activated`), общий движок или флаг данных. +- Не добавляй новую универсальную систему эффектов без подтверждённой + необходимости. +- После правок способностей проверяй весь реестр `CardScripts.Default`, тесты + правил, `validate` и `translations check`. +- Точные паттерны AUTO/ACT/CONT, стоимости, выборы целей и частые ошибки — + в Skill `card-implementation.md` и `docs/ability-implementation.md`. -## Технические ограничения +## Проверки и завершение блока -- Не запускать `dotnet` и Docker на рабочем ПК; сервер проверять на - `ssh home`. -- Не открывать второй экземпляр Unity с тем же проектом; для batch-проверок - делать копию проекта без `Library`. -- `Vanguard.Rules` остаётся чистой библиотекой; сервер зависит от неё, а не - наоборот. +После существенного блока: + +1. клиентские EditMode-тесты — в копии проекта; +2. серверные тесты — на `ssh home`/Docker; +3. `validate` и `translations check`; +4. при клиентских изменениях — smoke и при необходимости standalone; +5. коммит и push. + +Состояние и следующие шаги — в `docs/SESSION-STATE.md`, подробная история — в +`docs/HANDOFF.md`. Не дублируй одни и те же правила в нескольких местах: +обновляй Skill, если пользователь согласовал новое долговременное правило. diff --git a/docs/3d-ux-backlog.md b/docs/3d-ux-backlog.md index 4bb64b1..be8c60d 100644 --- a/docs/3d-ux-backlog.md +++ b/docs/3d-ux-backlog.md @@ -15,8 +15,9 @@ - **P1 частично:** `ArrowConfig`, общий `ArrowDirector`, красная attack и синяя boost-стрелка, grow/arc/dash (UV-tile fallback), якоря у края карт. - **P1 частично:** multi-target Counter Blast доведён до сервера - (`DamageIndexes` в `ActivateAbilityIntent`) и клиентского выбора; overlay - заменён счётчиком в панели «Действия» (по правилам проекта). + (`DamageIndexes` в `ActivateAbilityIntent`) и клиентского выбора; текущий + счётчик в панели «Действия» — промежуточная реализация. Отдельный + `CostPaymentOverlay` требуется (решение 2026-10-24), но ещё не сделан. - **Не сделано:** multi-target Soul Blast и другие типы стоимости, `EffectConfig`, отдельный `CostPaymentOverlay`, `AttackResolutionQueue`, world-space Power/Shield, world-space dash shader. @@ -236,8 +237,8 @@ deck / Drop (если faceDown) показывают рубашку; flip про анимации + `AttackResolutionQueue` (п.5). 4. **P1**: `EffectConfig` + `CostPaymentController/Overlay` + протокол/движок multi-target cost (п.4). -5. **P2**: World-Space Power/Shield, удаление/скрытие `CombatStats` при - необходимости, polish и профилирование. +5. **P2**: World-Space Power/Shield; `CombatStats` принят как HUD, удалять + его без отдельного решения нельзя; polish и профилирование. ## 8. Принятые решения @@ -247,11 +248,13 @@ deck / Drop (если faceDown) показывают рубашку; flip про `PlaymatLayoutConfig`. - **Dash:** UV-tile fallback (`LineTextureMode.Tile` + прокрутка offset); world-space shader оставлен как будущая замена. -- **`CombatStats`:** верхний strip пока оставлен как HUD; world-space - Power/Shield — P2. +- **`CombatStats`:** верхний strip **принят как HUD** (решение 2026-10-24); + world-space Power/Shield — отложенный P2. - **Multi-target cost:** расширен `ActivateAbilityIntent.DamageIndexes` (`[Key(3)]`, обратная совместимость); `CounterBlast` принимает массив. - `Soul Blast` и остальные типы — следующий шаг. + Отдельный `CostPaymentOverlay` **требуется**; текущий выбор в панели + «Действия» — промежуточная реализация. `Soul Blast` и остальные типы — + следующий шаг. - **Камера:** владелец — `CameraDirector`; `EnsureCamera()` не перезаписывает pose, пока `CameraDirector.IsActive`. `TriggerCheckSequence` снимает `InputLock` перед выбором цели и возвращает камеру после hold. diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 77810d6..04e7956 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -836,13 +836,15 @@ Battle; Boot и общие сервисы остаются персистент Counter Blast) карты подсвечиваются и кликаются. - Heal: сервер больше не забирает последнюю карту автоматически, а создаёт `ChoiceTargetKind.Damage`; клиент выбирает карту кликом. Counter Blast - принимает `DamageIndex` в `ActivateAbilityIntent`/`ResolveAbilityIntent` - и использует выбранную открытую карту (остаток суммы — авто). + принимает `DamageIndex`/`DamageIndexes` в `ActivateAbilityIntent`; при + multi-target используются выбранные открытые карты. `ResolveAbilityIntent` + пока сохраняет одиночный `DamageIndex`. - Маллиган: клиент выбирает карты в руке, подсвечивает их и отправляет `MulliganIntent`; сервер возвращает выбранные в колоду, перемешивает и добирает столько же. После обмена доступно «Закончить маллиган». -- Протокол `Vanguard.Shared` расширен (`DamageIndex`, `ChoiceTargetKind`), - DLL пересобран и обновлён в `client/Assets/Plugins/Vanguard.Shared`. +- Протокол `Vanguard.Shared` расширен (`DamageIndex`/`DamageIndexes`, + `ChoiceTargetKind`), DLL пересобран и обновлён в + `client/Assets/Plugins/Vanguard.Shared`. - Проверки: сервер **139** правил + **45** серверных; клиент EditMode **4/4** (включая рендер доски с уроном и отдыхающей картой); `SCENE_SMOKE success=True active=Login`; `validate` и @@ -1115,4 +1117,20 @@ Battle; Boot и общие сервисы остаются персистент - `CardMoveAnimator` получил дедлайн активной анимации и `CancelCard`; `ReleaseCard` отменяет движение. Это устраняет вечный `card-move`-лок, из-за которого не открывалась смена и поле выглядело зависшим. -- Клиентские EditMode: **42/42**. \ No newline at end of file +- Клиентские EditMode: **42/42**. + +## 2026-10-24 — аудит Skill и правил + +- Проверены Skill `vanguard-remaster`, `AGENTS.md`, docs и две локальные + сессии OpenCode Vanguard (1259 и 5617 сообщений; история вне OpenCode + недоступна). +- Skill разделён на `SKILL.md`, `game-rules.md`, `architecture.md`, + `card-implementation.md`, `workflow-and-decisions.md`. +- Зафиксировано решение: смешивать кланы в колоде можно; эффекты с клановыми + условиями могут не работать. `DeckValidator` это не ограничивает. +- `CombatStats` strip принят как HUD, world-space Power/Shield — отложено. +- Multi-target cost требует отдельный `CostPaymentOverlay`; текущий CB в + панели «Действия» — промежуточная реализация. +- Исправлен порядок отображения урона в `docs/protocol.md`: сначала открытые, + затем закрытые. +- Код, сцены и префабы в рамках аудита не менялись. \ No newline at end of file diff --git a/docs/SESSION-STATE.md b/docs/SESSION-STATE.md index 1e1c5ac..77a4876 100644 --- a/docs/SESSION-STATE.md +++ b/docs/SESSION-STATE.md @@ -355,8 +355,9 @@ client/Assets/Plugins/Vanguard.Shared + MessagePack 1. 3D UX: P0 (движение, ориентации, камера/триггер) и базовый P1 (стрелки, multi-target Counter Blast) готовы. Осталось: `AttackResolutionQueue`, - multi-target Soul Blast/прочие стоимости, world-space Power/Shield, - world-space dash shader; полировка позиций на конкретном мониторе. + multi-target Soul Blast/прочие стоимости, отдельный `CostPaymentOverlay`, + world-space Power/Shield (верхний strip принят как HUD), world-space + dash shader; полировка позиций на конкретном мониторе. 2. Декбилдер: drag&drop, счётчики лимитов, live-валидация, пресеты. 3. 3D-наполнение: модели карт/стола, prefab-экраны сцен, анимации retire/ghost. 4. Звук и настройки, DOTween. diff --git a/docs/ability-implementation.md b/docs/ability-implementation.md index 5c99043..fb9c8f2 100644 --- a/docs/ability-implementation.md +++ b/docs/ability-implementation.md @@ -1,7 +1,8 @@ # Реализация способностей карт Актуально на **2026-10-24**. Источник правил — `content/sets/*.yaml`, -`textEn`; источник исполнения — `Vanguard.Rules`. +`textEn`; источник исполнения — `Vanguard.Rules`. Рабочие паттерны и чеклист +новой карты — Skill `vanguard-remaster/card-implementation.md`. ## Проверка реестра diff --git a/docs/client-visual-direction.md b/docs/client-visual-direction.md index abb1638..64ca9e9 100644 --- a/docs/client-visual-direction.md +++ b/docs/client-visual-direction.md @@ -64,6 +64,10 @@ перехватывают клики и не дублируют выбор кнопками. - `ActionPrompt` в бою — информационный слой; подсветка на 3D-поле показывает доступные карты/круги/зоны. +- Верхний `CombatStats` strip — допустимый HUD (решение 2026-10-24); + world-space Power/Shield — отдельный отложенный шаг. +- Для multi-target стоимости требуется отдельный `CostPaymentOverlay`; + выбор в панели «Действия» — промежуточная реализация. ## Ограничения и следующие шаги diff --git a/docs/game-rules.md b/docs/game-rules.md index 6d805cf..0bde329 100644 --- a/docs/game-rules.md +++ b/docs/game-rules.md @@ -40,8 +40,10 @@ - до 16 карт; - только воины наследия. -Кланы: без отдельной договорённости игрок не может смешивать карты разных -кланов в одной колоде. +Кланы: **смешивать карты разных кланов в одной колоде разрешено** (решение +проекта от 2026-10-24). Это отклонение от официального ограничения; эффекты, +которые требуют конкретный клан или условие, могут не сработать. `DeckValidator` +единство клана не проверяет. --- @@ -345,9 +347,10 @@ Draw, Stand. | 7 | не проверялось ровно 16 триггеров | `DeckValidator` требует ровно 16; UI декбилдера показывает счётчик | | 8 | зона урона показывала открытые карты как закрытые | открытые карты видны артом, закрытые — рубашкой; всё на боку | -Проверки после исправлений: **138** правил + **45** серверных, клиент -EditMode **4/4**, `SCENE_SMOKE success=True`, `validate` и -`translations check` — без ошибок. +Проверки на 2026-10-24: **151** правил + **45** серверных, клиентские +EditMode **42/42**, `validate` и `translations check` — без ошибок. +`SCENE_SMOKE`/`PLAYMAT_SMOKE` запускаются отдельно; не считать их +пройденными только по факту сборки. Осталось вне Classic: см. ниже. diff --git a/docs/protocol.md b/docs/protocol.md index e5c258f..6b75efe 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -174,8 +174,9 @@ WebSocket-транспорта. Логика handshake, кадров и тайм | 17 | `ConcedeIntent` | — | Индексы кругов: `-1` — Авангард, `0..2` — задний ряд, `3..4` — передний. -`damageIndex` — индекс карты урона в порядке отображения (сначала закрытые, -затем открытые); `-1` — авто. +`damageIndex` — индекс карты урона в порядке отображения (сначала открытые, +затем закрытые); `-1` — авто. `DamageDisplayOrder` в движке и клиентская +зона урона используют тот же порядок. `GuardIntent.discardPrintingId` заполняется только для идеальной защиты: без него Sentinel не отменяет атаку; с ним указывается карта того же клана.