17 KiB
17 KiB
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.
Vanguard ReMaster
Рабочие правила для этого репозитория. Сначала прочитать
docs/SESSION-STATE.md, затем docs/HANDOFF.md и
docs/client-visual-direction.md.
1. Структура проекта
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 тесты
Ключевые границы:
Vanguard.Rules— чистая библиотека. Не добавлять в неё сеть, БД, Orleans, Unity или файловый I/O.Vanguard.Shared— контракты.netstandard2.1-сборка используется Unity, поэтому там только DTO/сериализация без ссылки наVanguard.Rules.Vanguard.Serverзависит отRules,Shared,Content; обратной зависимости быть не должно.- Клиент не является источником истины: правила, скрытая информация и случайность остаются на сервере.
2. Сетевой код
Протокол
- Транспорт матча — 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:
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. Новые карты и изменения правил
Порядок:
- Добавить/исправить карту в
content/sets/*.yaml; текст —textEn. - Найти все AUTO/ACT/CONT/ключевые механики в тексте.
- Реализовать в
CardScriptsили общем движке; не оставлять дублей. - Добавить хотя бы один тест на триггер/активацию/условие.
- Проверить
CardScripts.Defaultцеликом: нет ли карты с текстом без скрипта. - Прогнать серверные тесты,
validate,translations check. - Для клиентских изменений — 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. Проверки и окружение
# сервер — только на ssh home / Docker, не локальным dotnet
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"
Клиент: Unity 6000.6.6f1, URP. Не открывать второй Unity с тем же
проектом; batch-проверки — в копии без Library. Тесты:
& '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:
Vanguard.Editor.StandaloneBuild.BuildWindowsCli.
После блока: тесты, validate, translations check, коммит и push.
8. Триггеры и особые случаи
- После выбора цели силы у Draw дополнительный эффект (draw 1) разрешается сразу: второй цели на поле нет.
- Heal после цели силы просит карту только в своей зоне урона и только если серверные условия выполнены (damage ≥ opponent и есть карты). Нет карт/условий — дополнительный эффект не активируется.
- Tejas бьёт задний ряд своей колонки и с RC, и с VC (центральный задний
ряд); проверять
snipeBackRow+SameColumn. - Если у RC доступен только один круг перемещения в колонке, клиент выполняет перемещение сразу, без выбора цели.
- Подсветка урона при выборе карты ограничена зоной урона локального игрока.