docs: audit project skill and align rules with code
CI / test (push) Has been cancelled Details

This commit is contained in:
MpakobecEx 2026-10-24 19:37:24 +05:00
parent fd21a969fa
commit 76067da220
13 changed files with 1120 additions and 325 deletions

View File

@ -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 <copy> `
-runTests -testPlatform EditMode -testResults <copy>\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`.

View File

@ -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<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.

View File

@ -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<AbilityDefinition> 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<ActivatedAbility> 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. Не тестируй геймплей вручную за пользователя; он проверяет сам.

View File

@ -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` не реализована;
не описывай её как гарантированное поражение без решения.

View File

@ -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 недоступна; спорные места не считаются
восстановленными.

168
AGENTS.md
View File

@ -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, если пользователь согласовал новое долговременное правило.

View File

@ -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.

View File

@ -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**.
- Клиентские 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`: сначала открытые,
затем закрытые.
- Код, сцены и префабы в рамках аудита не менялись.

View File

@ -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.

View File

@ -1,7 +1,8 @@
# Реализация способностей карт
Актуально на **2026-10-24**. Источник правил — `content/sets/*.yaml`,
`textEn`; источник исполнения — `Vanguard.Rules`.
`textEn`; источник исполнения — `Vanguard.Rules`. Рабочие паттерны и чеклист
новой карты — Skill `vanguard-remaster/card-implementation.md`.
## Проверка реестра

View File

@ -64,6 +64,10 @@
перехватывают клики и не дублируют выбор кнопками.
- `ActionPrompt` в бою — информационный слой; подсветка на 3D-поле
показывает доступные карты/круги/зоны.
- Верхний `CombatStats` strip — допустимый HUD (решение 2026-10-24);
world-space Power/Shield — отдельный отложенный шаг.
- Для multi-target стоимости требуется отдельный `CostPaymentOverlay`;
выбор в панели «Действия» — промежуточная реализация.
## Ограничения и следующие шаги

View File

@ -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: см. ниже.

View File

@ -174,8 +174,9 @@ WebSocket-транспорта. Логика handshake, кадров и тайм
| 17 | `ConcedeIntent` | — |
Индексы кругов: `-1` — Авангард, `0..2` — задний ряд, `3..4` — передний.
`damageIndex` — индекс карты урона в порядке отображения (сначала закрытые,
затем открытые); `-1` — авто.
`damageIndex` — индекс карты урона в порядке отображения (сначала открытые,
затем закрытые); `-1` — авто. `DamageDisplayOrder` в движке и клиентская
зона урона используют тот же порядок.
`GuardIntent.discardPrintingId` заполняется только для идеальной защиты:
без него Sentinel не отменяет атаку; с ним указывается карта того же клана.