docs: audit project skill and align rules with code
CI / test (push) Has been cancelled
Details
CI / test (push) Has been cancelled
Details
This commit is contained in:
parent
fd21a969fa
commit
76067da220
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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. Не тестируй геймплей вручную за пользователя; он проверяет сам.
|
||||
|
|
@ -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` не реализована;
|
||||
не описывай её как гарантированное поражение без решения.
|
||||
|
|
@ -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
168
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, если пользователь согласовал новое долговременное правило.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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` и
|
||||
|
|
@ -1116,3 +1118,19 @@ Battle; Boot и общие сервисы остаются персистент
|
|||
`ReleaseCard` отменяет движение. Это устраняет вечный `card-move`-лок,
|
||||
из-за которого не открывалась смена и поле выглядело зависшим.
|
||||
- Клиентские 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`: сначала открытые,
|
||||
затем закрытые.
|
||||
- Код, сцены и префабы в рамках аудита не менялись.
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
# Реализация способностей карт
|
||||
|
||||
Актуально на **2026-10-24**. Источник правил — `content/sets/*.yaml`,
|
||||
`textEn`; источник исполнения — `Vanguard.Rules`.
|
||||
`textEn`; источник исполнения — `Vanguard.Rules`. Рабочие паттерны и чеклист
|
||||
новой карты — Skill `vanguard-remaster/card-implementation.md`.
|
||||
|
||||
## Проверка реестра
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@
|
|||
перехватывают клики и не дублируют выбор кнопками.
|
||||
- `ActionPrompt` в бою — информационный слой; подсветка на 3D-поле
|
||||
показывает доступные карты/круги/зоны.
|
||||
- Верхний `CombatStats` strip — допустимый HUD (решение 2026-10-24);
|
||||
world-space Power/Shield — отдельный отложенный шаг.
|
||||
- Для multi-target стоимости требуется отдельный `CostPaymentOverlay`;
|
||||
выбор в панели «Действия» — промежуточная реализация.
|
||||
|
||||
## Ограничения и следующие шаги
|
||||
|
||||
|
|
|
|||
|
|
@ -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: см. ниже.
|
||||
|
||||
|
|
|
|||
|
|
@ -174,8 +174,9 @@ WebSocket-транспорта. Логика handshake, кадров и тайм
|
|||
| 17 | `ConcedeIntent` | — |
|
||||
|
||||
Индексы кругов: `-1` — Авангард, `0..2` — задний ряд, `3..4` — передний.
|
||||
`damageIndex` — индекс карты урона в порядке отображения (сначала закрытые,
|
||||
затем открытые); `-1` — авто.
|
||||
`damageIndex` — индекс карты урона в порядке отображения (сначала открытые,
|
||||
затем закрытые); `-1` — авто. `DamageDisplayOrder` в движке и клиентская
|
||||
зона урона используют тот же порядок.
|
||||
`GuardIntent.discardPrintingId` заполняется только для идеальной защиты:
|
||||
без него Sentinel не отменяет атаку; с ним указывается карта того же клана.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue