Архитектура реализации — Фаза 3 (AI-движок + играбельные реликвии)

2026-06-01. Документ был утерян (пустой файл) и ранее реконструирован вчерне; реконструкция разошлась с кодом (выдуманные IBrain/Decision/BattleSnapshot/LiteHarness, навык «Удар копьём», пассив «Строй копий»). Эта версия выверена по фактическому коду (Assets/_Project/Scripts/Combat/AI/*, тесты Tests/EditMode/Combat/BrainTests.cs) и по канону геймдиз-карточек. Номера секций (§2.1, §2.7, §3.1, §3.2, §3.4, §3.5, §4.1–4.3, §7) совпадают с ссылками в xml-доках кода.

Фаза 3 строит движок ИИ боевых юнитов поверх готового боевого ядра (Фаза 1) и системы эффектов/способностей (Фаза 2), а затем доводит первые играбельные реликвии-юниты до вертикального среза.

Рабочий процесс фазы (как и в прошлых): швы → спайк → стабы+тесты → реализация → ретро. Мозг ИИ — чистый: читает мир через read-only IBattleView, пишет только интент на самого юнита (цель/позиционирование), мир мутирует боевой цикл. «Дорогое» решение «кого/где» принимается с каденсом 10 Гц (стаггер по Id), «дешёвое» решение «кастовать ли» — per-tick в AbilitySystem.


Состояние шагов (§8)

ШагСодержаниеСтатус
1Спайк S1: staggered-детерминизм прото-шва ИИ (Tests/.../StaggeredBrainSpikeTests.cs, throwaway)✅ готово
2Lite визуальный харнесс: самодостаточная BattleScene + UnitView + S4-развязка анимации с симом✅ готово
3 / 3.1Шов ИИ: IUnitBrain/IBattleView/ProfileBrain/BrainSystem на месте удалённого TargetingSystem✅ готово (109/109 тестов)
4Вертикальный срез «Железный копейщик»: линейная авто-атака «Размашистый выпад» + круговой AOE-актив «Стальной вихрь» (×3) с cast-условием «≥X врагов в радиусе» + SO-ассет реликвии + глаз в харнессе✅ готово (135/135; коммит ed9743e)
5Движковые расширения §10 (E1–E10, разобраны в §9): on-hit эффекты АА, хил-АА, pre-damage хук (спайк S5), заряды, реактив-перенос метки, empower-next, Positioning в движении (kite/retreat), атака-в-движении, displacement + knock-up, глобальный/по-тегу таргетинг✅ готово (E1–E10 реализованы по мере срезов §10)
66 Обычных реликвий вертикальными срезами в порядке нарастания движка: Пастырь → Криомант → Защитник → Следопыт → Убийца → Монах (§10)✅ 6/6: §10.1 Пастырь · §10.2 Криомант · §10.3 Защитник · §10.4 Следопыт · §10.5 Убийца · §10.6 Монах (171/171 тестов)
7Закрытие фазы: все 7 срезов в харнессе, отметка в 9. Roadmap §3, ретро + синхрон AI-словаря 0.3 под фактический AIProfile⏳ §11

Скоуп «закрытия Ф3» (решение Макса, 2026-06-01): полный скоуп Roadmap §3все 7 Обычных реликвий играбельны + движковые расширения §10 дока 6, а не только срез копейщика. Шаги 5–7 ниже спроектированы design-first перед кодом (по пайплайну фазы).


§2 — Модель решений ИИ

§2.1 — «Параметризованные пресеты» (выбранная модель)

Решение Макса: НЕ полный data-driven [SerializeReference]-граф, а параметризованные пресеты — фиксированный набор типизированных ручек за стабильным швом IUnitBrain. Одна кодовая реализация (ProfileBrain) их интерпретирует.

  • AIProfile ([Serializable], лежит на RelicData.Ai) — ручки поведения мозга. Не ScriptableObject: сериализуется внутри реликвии, имеет публичный конструктор для кодовых профилей (харнесс/спавн-команды/тесты).
  • Why: сложная AI может не выжить к фестивалю; полиморфная библиотека окупается только при редакторе AI (post-festival). Главное — стабильный шов IUnitBrain (отделяет «как решено» от «как сим исполняет») → апгрейд до графа/хардкода дешёвый, без переписи сим/тестов.

Состав ручек продиктован ГД-моделью блоков A–G (словарь — 1. Контент - Реликвии/0.3. Модель AI-настроек реликвий.md). Реликвия включает свой поднабор:

БлокГде живётЧто задаёт
A фокус авто-атакиAIProfile.AutoAttackTargeting (TargetingMode, один дропдаун, не список) + AutoAttackMode (Damage/Heal) + TargetTag (для PreferTagged)кого бьёт авто-атака
B отступлениеAIProfile.Retreat (Enabled, FleeAtHpPct, ReturnAtHpPct; B > A — гистерезис)бежать при HP ≤ A%, вернуться при ≥ B%
C/D/E цель/условие/отмена ультыпо-способности на AbilityData (приходят с вертикальным срезом, шаг 4)когда и по кому кастовать
F триггер пассивкиAIProfile.PassiveTrigger (None/AnyHit/OnHitAbovePctMaxHp/Always) + PassiveThresholdPctреактив-пассив (щит Защитника и т.п.)
G кайтAIProfile.Kite (Enabled, FleeDist, FallbackDist; T > F)следопыт: удержание дистанции

Режимы таргетинга (TargetingMode, общий для авто-атаки и активок): Nearest, LowestHpFlat, LowestHpPercent, HighestHp, PreferTagged, HighestThreat (оценочный «главный ДПС» = AutoAttackDamage × AttackSpeed × DamageDealtEff, без истории урона — Защитнику), AllyNearest, AllyLowestHpPercent.

Отложено на Ф4+ (решения Макса): время каста/каналинг и «сбить каст» — осмысленны только для стабильных-под-задержкой спеллов (каналы-стойки, телеграф-нюки); реактивные/кластерные ульты Обычных от каст-времени ломаются. «Отмена условия ульты» трактуется как HP% самого юнита (паника/выживание).

§2.6 — Контентные теги эффектов

Сверх базовых (Buff/Debuff/Control/DoT/HoT/Heal/Shield) Фаза 3 завела три контентных тега в EffectTag (уже в коде) — они склеивают пассив-эффект с активкой/AI-приоритетом одной реликвии:

ТегКто ставитКто читает
FrozenКриомант, пассив-АА «Леденящее касание»активка «Ледяные оковы» — станит всех с этим тегом (§9.10, TaggedTargetsCount)
MarkedЛесной следопыт, активка «Метка охотника»AI-приоритет PreferTagged(Marked); реактив-перенос при смерти (§9.5)
KnockUpсемейство контроллеров (Монах)контроль перемещением; не скейлится ReceiveDebuffEff (§4.4)

§2.7 — Хилер и AI-поля юнита

AutoAttackMode.Heal разводит «кого лечу» и «от кого бегу»: авто-атака лечит союзника (AutoAttackTarget), а CurrentTarget держится на ближайшем враге — для позиционирования/отступления.

AI-поля на RuntimeUnit (пишет мозг 10 Гц, читают системы движения/атаки 30 Гц):

ПолеНазначение
IUnitBrain Brainмозг юнита; ставит RuntimeUnitFactory; null → дефолтный мозг BrainSystem
int BrainPhaseфаза стаггера (= Id % AiTickInterval)
bool BrainDirtyсобытийное прерывание → переоценка вне фазы
PositioningIntent PositioningApproach / Kite / Retreat
RuntimeUnit CurrentTargetцель движения/позиционирования
RuntimeUnit AutoAttackTargetцель авто-атаки (для хилера ≠ CurrentTarget)
EffectTag EffectTagMaskмаска тегов активных эффектов — быстрый запрос для PreferTagged

§3 — Контракт шва ИИ (итог шагов 1–3.1)

§3.1 — Шов: IUnitBrain / IBattleView / ProfileBrain

public interface IUnitBrain
{
    // Переоценить решение юнита по текущему срезу боя. Пишет интент на self, НЕ мутирует сим.
    void Decide(RuntimeUnit self, IBattleView view);
}
 
public interface IBattleView          // read-only срез боя (реализует CombatSimulation; мок — FakeBattleView)
{
    IReadOnlyList<RuntimeUnit> Units { get; }   // живые и мёртвые; мозг сам фильтрует по IsDead/Team
    int CurrentTick { get; }                    // для каденса/стаггера
}
  • Мозг не возвращает структуру решения и не мутирует мир — он записывает интент в поля self (CurrentTarget / AutoAttackTarget / Positioning). Это даёт тестируемость (мок-вью) и детерминизм.
  • Cast-решение здесь не принимается — оно дешёвое, проверяется per-tick в AbilitySystem.
  • v1-реализация — единственная (ProfileBrain). Шов оставлен под будущую подмену (граф-примитивы / пер-архетип) без правок интеграции в сим.

§3.2 — Каденс: BrainSystem

BrainSystem.Tick(units, view) заменяет удалённый TargetingSystem в порядке тика. Каденс 10 Гц поверх 30-Гц сим: юнит переоценивает решение на тике, где tick % SimConstants.AiTickInterval == BrainPhase (стаггер по Id — нагрузка распределена), или когда взведён BrainDirty. Между AI-тиками интент «залипает»: движение/атака исполняют прошлое решение. Обход строго по индексу списка ⇒ порядок решений фиксирован ⇒ детерминизм (гейт спайка S1 — StaggeredBrainSpikeTests, оставлен как регрессия каденса).

§3.4 — Событийное прерывание BrainDirty

dirty = u.BrainDirty || (u.CurrentTarget != null && u.CurrentTarget.IsDead) — погибшая цель вызывает немедленную переоценку, не дожидаясь своей фазы. Явный BrainDirty (взводит event-queue) — задел под UnitDied/перенос метки; смерть цели покрыта и проверкой выше. BrainSystem сбрасывает флаг после Decide.

§3.5 — TargetingSystem поглощён, дефолтный мозг

TargetingSystem.cs удалён (поглощён BrainSystem). Юниты без явного профиля получают дефолтный мозг new ProfileBrain(null) = Nearest-враг — ровно прежнее поведение Фазы 1, поэтому тесты Ф1/Ф2 живы.


§4 — Исполнение решения в симуляции

§4.1 — BrainSystem в порядке тика

BrainSystem встаёт там, где раньше был TargetingSystem (детали порядка систем — CombatSimulation). CombatSimulation реализует IBattleView (Units + CurrentTick). Фабрика выставляет BrainPhase = Id % AiTickInterval и Brain = new ProfileBrain(relic.Ai).

Имя SimConstants.AiTickInterval оставлено как есть (не переименовано в AiStaggerWindow) — чтобы не ломать существующие ссылки.

§4.2 — ProfileBrain: Filter → Score → Override

  1. Filter — живой; нужная команда (враги, либо союзники при AllyNearest/AllyLowestHpPercent/Heal); себя исключаем для союзных режимов.
  2. Score (меньше = лучше): Nearest/AllyNearestdist²; LowestHpFlatCurrentHP; LowestHpPercent/AllyLowestHpPercentHP%; HighestHp-CurrentHP; HighestThreat-EstimatedDps; PreferTaggeddist² тегнутым, dist² + штраф нетегнутым (тегнутый всегда выигрывает).
  3. Тай-брейк — при равном скоре дистанция, затем Id (детерминизм, без RNG).
  4. Запись интента — обычный режим: CurrentTarget = AutoAttackTarget = best. Хилер: AutoAttackTarget = лучший союзник, CurrentTarget = ближайший враг (§2.7).

§4.3 — Позиционирование (Override)

ResolvePositioning(self) поверх выбора цели:

  • Retreat (если включён): гистерезис B > A — уже отступаем ⇒ продолжаем, пока HP% < ReturnAtHpPct; иначе уходим в отступление лишь при HP% ≤ FleeAtHpPct. Зазор гасит дёрганье.
  • Kite (если включён) → PositioningIntent.Kite.
  • Иначе → PositioningIntent.Approach.

Состояние исполнения: мозг пишет Positioning, но MovementSystem пока его игнорит (только Approach де-факто). Kite/Retreat-в-движении — шаг 5, расширение E5 (§9.7).

§4.4 — Подкидывание (KnockUp) и контроль перемещением

Смещения (отбрасывание/притягивание/телепорт, §9.9) и «подкидывание» — контроль особого рода: пока юнит летит, он оглушён, и это окно не уменьшается ReceiveDebuffEff/tenacity (реф — knock-up в LoL). Тег EffectTag.KnockUp маркирует такой эффект; EffectSystem при расчёте длительности контроля исключает KnockUp из скейла сопротивлением. Это движковое расширение шага 5 (E8), нужно Монаху вихря.


§7 — Тесты шва (готово, 109/109)

EditMode, Guildmaster.Tests.EditMode.Combat:

  • BrainTests — контракт мозга на продовых типах: Score_LowestHpPercent_PicksWoundedOverNearer, Score_TiesBrokenById_Deterministic, PreferTagged_PicksMarkedEnemy_WhenPresent, Brain_DoesNotMutateSim_OnlyWritesIntent + каденс StaggeredBrain_SameChecksum_AcrossTwoRuns, TargetDeath_SetsBrainDirty_ReevaluatesNextTick, IntentPersistsBetweenAiTicks_UnitKeepsMoving. Мок — FakeBattleView.
  • StaggeredBrainSpikeTests — спайк S1 (throwaway-прото), оставлен как регрессия staggered-детерминизма.

Существующие тесты Ф1/Ф2 (CombatSimulationTests, AbilitySystemTests, EffectSystemTests и др.) зелёные за счёт дефолтного Nearest-мозга (§3.5).


Шаг 4 (§8) — Вертикальный срез «Железный копейщик»

Цель. Провести один реальный контентный юнит сквозь весь шов: мозг выбирает цель, юнит подходит, бьёт линейной авто-атакой, а при выполнении cast-условия пускает круговой AOE-актив. Сначала EditMode (детерминизм), затем глаз в визуальном харнессе.

Дизайн (канон — карточка «Железный копейщик»):

  1. «Размашистый выпад» (пассивная авто-атака) — авто-атака бьёт линией перед юнитом, задевая несколько целей; дальность атаки выше обычного милишника. Роль — фронтлайн-брузер с AOE-паттерном.
  2. «Стальной вихрь» (активная) — круговой AOE вокруг себя, урон ×3. Цель не выбирается (по кругу вокруг себя).

AI-настройки (Шаг 5 карточки):

БлокНастройка
A фокус авто-атакиБлижайший / Максимум целей (линия) / Меньше всего HP
D условие ультыСразу / ≥ X врагов в радиусе AOE (X задать)
E отмена условиякаст при своём HP ≤ P% / выкл
B отступлениебежать при HP ≤ A%, вернуться при ≥ B% / выкл

Статы карточка задаёт словесно (HP выше среднего, урон средний, скорость атаки ниже средней, контроль/зона давления выше средней) — конкретные числа подбираются при сборке SO-ассета, в карточке их нет.

Что уже есть в коде (опора среза)

  • ICombatContext.QueryUnitsInRadius(center, radius, results, filter, team)круговой запрос есть (опора «Стального вихря» и cast-условия «≥X в радиусе»).
  • AbilitySystem — плейсхолдер-каст (первая готовая активка), AbilityData (Id, Effects[], BaseCooldown, ResourceCost, TargetMode∈{Self/NearestEnemy/NearestAlly}).
  • AutoAttackSystem — одиночная авто-атака (Melee/Projectile) по CurrentTarget.

Что добавляет шаг 4 (новое)

ОбластьИзменение
Форма удара (линия)новый AreaShape (Line/Circle) + запрос формы (линия перед юнитом). Круг уже покрыт QueryUnitsInRadius.
Линейная авто-атакаAutoAttackSystem — мульти-цель по форме линии для реликвий с линейной АА; одиночная остаётся дефолтом.
AOE-урон активкиAbilityData — прямой урон с множителем (×3) по кругу, помимо наложения эффектов.
Cast-условие (блок D/E)AbilityData + гейт в AbilitySystem вместо плейсхолдера: Сразу / EnemiesInRadius ≥ X / отмена по своему HP ≤ P%.
КонтентSO-ассет реликвии «Железный копейщик» (AIProfile.Ai + 2 навыка) + глаз в BattleScene-харнессе.

Доп. условия (решения Макса, 2026-06-01)

  • Множитель «Стального вихря» = ×3 от урона авто-атаки (AutoAttackDamage), не от иного стата.
  • Мана: заклинание стоит 30, авто-атака даёт +5 маны. MaxResource = 30, StartResource = 0 (стартует пустым, мана клампится на 30 — 6 авто-атак до полного каста). Прирост маны за удар — общий механизм, но не новый стат (StatType залочен на 30): новое поле ResourceOnHit на RelicData (рядом с AttackType/DamageType; любая мана-реликвия задаёт своё значение, копейщику = 5). Начисляется в AutoAttackSystem при выстреле авто-атаки (× существующий ResourceGainEff), клампится к MaxResource.
  • Визуализация зон удара (dev-оверлей): при касте авто-атаки (линия) и активки (круг) зона рисуется через Shapes в CombatDebugDraw (харнесс/дев, тоггл) — короткая вспышка ~0.25 с. Это инструмент оценки, не финальный VFX.
  • Спрайт копейщика: Fantasy Warrior (Medieval Warrior Pack 2 уже занят тест-юнитом).

Тесты шага 4 (стабы → красный → зелёный)

EditMode, Guildmaster.Tests.EditMode.Combat:

  1. Линейная авто-атака задевает несколько целей в полосе перед юнитом, мимо-полосы — нет.
  2. Дальность линейной АА выше базовой милишной.
  3. «Стальной вихрь» бьёт всех врагов в радиусе на ×3 урона, союзников — нет.
  4. Cast-условие EnemiesInRadius ≥ X: при < X не кастует, при ≥ X — кастует.
  5. Отмена условия (блок E): при своём HP ≤ P% кастует независимо от числа врагов.
  6. Срез в харнессе: подходит → линейно бьёт → при скоплении врагов пускает «Стальной вихрь», далее держит КД.

Критерий готовности шага 4

  • Все EditMode-тесты зелёные (старые + новые шага 4).
  • Плейсхолдер-каст заменён на cast-условие из AbilityData.
  • «Железный копейщик» проходит срез в харнессе от «подойти» до «вихрь по скоплению».

Закрыт (135/135, коммит ed9743e).


§9 — Движковые расширения §10 (шаг 5)

Шаг 5 затаскивает в движок механики, которых нет в библиотеке Ф1/Ф2, но которые нужны 6 оставшимся Обычным реликвиям (6. Боевая модель §10). Каждое расширение проектируется сначала как шов (сигнатура + точка интеграции + имена тестов), реализуется по мере того, как до него доходит первая реликвия в §10. Дефолты всех расширений сохраняют поведение существующего контента (копейщик/тест-юниты не ломаются).

Принцип: реактив-расширения (E3/E6 — на готовом IReactiveComponent + FIFO CombatEvent) — это контент, не движок. Синхронные перехваты (E1 pre-damage, E4 empower) и движение/смещение (E5/E7/E8) — реальные доработки сим. Спайк только там, где есть риск детерминизма (S5 — pre-damage; displacement — порядок мутаций Position).

§9.1 — On-hit эффекты авто-атаки (нужно Криоманту)

Авто-атака сейчас только наносит урон/спавнит снаряд. Добавить наложение эффектов на задетую цель в момент удара.

  • Шов: RelicData.AutoAttackEffects : EffectData[] (рядом с AutoAttackShape). Применяются в AutoAttackSystem.Resolve (мили single/Line — по каждой задетой) и при попадании снаряда (ProjectileSystem).
  • Тесты: AutoAttack_AppliesOnHitEffect_ToHitTargets; Projectile_AppliesOnHitEffect_OnImpact; LineAutoAttack_AppliesOnHitEffect_ToEachTargetInBand.

§9.2 — Хил-автоатака (нужно Пастырю)

AutoAttackMode.Heal уже в AIProfile; мозг уже разводит AutoAttackTarget (союзник) и CurrentTarget (враг для позиционирования, §2.7). Осталось исполнение.

Решения Макса (2026-06-01): (1) лечение скейлится AutoAttackDamage × HealShieldDealtEff кастующего (переиспользуем «выходной» стат, как «Стальной вихрь» реюзал AutoAttackDamage; StatType залочен на 30 — новый стат не вводим; у хилера AutoAttackDamage = «сила лечения»). (2) Доставка — снаряд-хил (Пастырь дальний): хил летит и лечит при попадании, а не мгновенно.

  • Шов (снаряд): Projectile/ProjectileSpawn получают флаг полезной нагрузки bool IsHeal (дефолт false = урон, поведение Ф1). ProjectileSystem.ApplyHit: если p.IsHealctx.Heal(target, p.Amount, p.Source) вместо DealDamage. Хил-снаряд всегда tracking (TargetUnit = союзник) — летит к конкретной цели, командная логика free-flying не мешает.
  • Шов (АА): в AutoAttackSystem.Resolve, если Ai.AutoAttackMode == Heal → спавн tracking-хил-снаряда в self.AutoAttackTarget, amount = AutoAttackDamage × HealShieldDealtEff(self). Вместо DealDamage. Цель-союзник берётся из интента мозга (не CurrentTarget).
  • Шов (приём): ctx.Heal применяет HealShieldTakenEff(target) и клампит к MaxHP (без оверхила). Если в текущем Heal эффективность приёма ещё не учтена — это часть работы §9.2.
  • Тесты: HealAutoAttack_SpawnsHealProjectile_AtAllyAutoAttackTarget; HealProjectile_HealsAlly_OnImpact_NotDamages; HealAutoAttack_AmountScalesWithDealtEff; Heal_NoOverheal_ClampedToMaxHp; HealAutoAttack_TargetsBrainAutoAttackTarget_NotCurrentTarget.

§9.3 — Pre-damage хук (спайк S5) (нужно Защитнику, Убийце)

CombatEvent-очередь — постфактум (срабатывает после применения урона), для «среагировать ДО вычета HP» она не годится. Нужен синхронный перехват в DamagePipeline до изменения CurrentHP.

  • Шов: IPreDamageComponent { void OnPreDamage(ref DamageRequest req, RuntimeUnit self, ICombatContext ctx); } — компонент рантайм-эффекта; DamagePipeline опрашивает компоненты цели до резолва. Может обнулить урон (негейт «Изворотливости»), поднять щит «Оплот» (поглощение перед ударом). Порядок компонентов фиксирован (детерминизм).
  • Решения Макса (применители): (1) «Изворотливость» Убийцы негейтит любой следующий входящий инстанс урона (АА или способность) — флаг типа источника в DamageRequest не нужен, обнуляем ближайший удар. (2) «Оплот» Защитника — реактив с внутренним кулдауном ~6–8с (поле таймера в компоненте), иначе щит блокирует каждый удар.
  • Спайк S5: гейт детерминизма pre-damage перехвата (порядок, повтор-прогон-checksum) — throwaway, как S1.
  • Тесты: PreDamage_Negate_BlocksExactlyOneHit_AnySource_ThenInactive; PreDamage_BulwarkShield_AbsorbsBeforeHp; PreDamage_Bulwark_RespectsInternalCooldown; PreDamage_DeterministicOrder_TwoRunsSameChecksum.

§9.4 — Заряды способности (нужно Убийце)

  • Шов: AbilityData.Charges : int (≥1, дефолт 1 = текущее поведение) + AbilityData.ChargeRechargeSeconds. AbilityRuntime: CurrentCharges, ChargeTimer. AbilitySystem: каст тратит заряд (не весь КД), заряды восполняются независимо по ChargeRechargeSeconds; способность готова, пока есть заряд.
  • Тесты: Charges_TwoCharges_CastTwiceBeforeRecharge; Charges_RechargeOneAtATime_Independent; Charges_DefaultOne_BehavesLikePhase2.

§9.5 — Реактив-перенос метки (нужно Следопыту)

Ложится на готовый IReactiveComponent + CombatEvent.UnitDied (контент, не движок).

  • Шов: компонент на эффекте-метке подписан на UnitDied; если умерла несущая метку цель — перевесить эффект (с остатком длительности) на ближайшего живого врага через ctx.ApplyEffect. Если новых врагов нет — метка гаснет.
  • Тесты: Mark_TransfersToNearestEnemy_OnCarrierDeath; Mark_NoTransfer_WhenNoEnemiesLeft; Mark_Expires_NormallyByDuration.

§9.6 — Empower-next-attack (нужно Монаху, Убийце)

Усиление следующей авто-атаки (×урон и/или доп-эффект), снимается после срабатывания. Нужно до расчёта урона → синхронно, не через FIFO.

  • Шов: рантайм-маркер на юните EmpowerNextAttack { float DamageMult; EffectData[] AddOn; } (ставит реактив-компонент). AutoAttackSystem.Resolve опрашивает маркер до DealDamage, применяет, снимает (однострел).
  • Тесты: Empower_NextAutoAttack_MultipliesDamage_ThenConsumed; Empower_OnlyFirstAttackAffected.

§9.7 — MovementSystem исполняет Positioning (нужно Следопыту + отступлению всех)

Мозг уже пишет PositioningIntent (§4.3), MovementSystem его игнорит. Добавить исполнение.

  • Шов: MovementSystem ветвится по unit.Positioning: Approach (как сейчас); Kite — держать дистанцию между Kite.FleeDist и FallbackDist от CurrentTarget (отходить, если ближе Flee); Retreat — двигаться от ближайшего врага / к центру своих. Детерминизм (целочисленный/фикс-шаг) сохранить.
  • Тесты: Movement_Kite_KeepsDistanceBand; Movement_Retreat_MovesAwayFromNearestEnemy; Movement_Approach_UnchangedFromPhase1.

§9.8 — Атака в движении (нужно Следопыту, Монаху)

  • Шов: RelicData.CanAttackWhileMoving : bool + MovingAttackSpeedPenaltyPct (Следопыт −50%). При флаге авто-атака не обнуляет движение; пока юнит движется и атакует — MoveSpeed × (1 − penalty). Без флага — поведение Ф1 (стоп на атаку).
  • Тесты: AttackInMotion_UnitKeepsMovingDuringWindup; AttackInMotion_AppliesMovePenaltyWhileAttacking; NoFlag_StopsToAttack_LikePhase1.

§9.9 — Displacement + подкидывание (нужно Монаху) — спайк по детерминизму

Детерминированное перемещение чужого/своего Position: knockback (от источника), pull (к источнику), teleport (в точку). На время полёта — контроль-иммунный стан (§4.4, EffectTag.KnockUp).

  • Шов: ICombatContext.Displace(in DisplaceRequest){ DisplaceKind Kind; RuntimeUnit Target; Vector2 Dir; float DistanceX; int Ticks; bool CannonballDamage; }; новая система DisplacementSystem тикает активные смещения, мутирует Position по фикс. шагу, держит контроль-иммунный стан в полёте, снимает в конце.
  • «Ядро» (Монах, решение Макса): при CannonballDamage летящий юнит на пройденном сегменте бьёт только врагов кастующего (TargetFilter.Enemies относительно Монаха — QueryUnitsInLine), своих не задевает; дистанция полёта фиксирована (X), не «до столкновения». «Шквальный толчок»: рывок Монаха к ближайшему врагу с позиционированием под направление (блок C) → толчок «от себя вперёд» на X → урон-ядро по пути.
  • Нотификация для реактива: CombatEvent сейчас не знает про смещение. Добавить сигнал UnitDisplaced (новый флаг события / хук DisplacementSystem), на него подписан «Вихревой заход» Монаха (§9.6, без кулдауна — телепорт-в-спину + ×2 на каждое отталкивание).
  • Спайк: порядок мутаций Position относительно MovementSystem/spatial hash в тике (checksum двух прогонов).
  • Тесты: Knockback_MovesTargetFixedDistanceX_Deterministically; Cannonball_HitsCasterEnemiesOnLine_NotAllies; KnockUp_DurationNotReducedByReceiveDebuffEff; Displace_TargetStunnedDuringFlight_FreedAfter; UnitDisplaced_Signal_FiresForReactive; Displace_TwoRunsSameChecksum.

§9.10 — Глобальный / по-тегу таргетинг активки (нужно Криоманту, Следопыту, Пастырю)

  • Шов: AbilityTargetMode.AllEnemiesWithTag (+ AbilityData-поле TriggerTag : EffectTag) — каст по всем живым врагам с заданным тегом, без ограничения дальности (масс-стан «Ледяные оковы» по Frozen). Для одиночных глобалок (метка, усиленный хил) — флаг AbilityData.NoRangeLimit (снимает проверку дальности в ResolveTarget).
  • Конверсия тега (Криомант): AbilityData.ConsumesTriggerTag : bool — после наложения эффектов снять TriggerTag с цели (ctx.Dispel). Cast-условие CastCondition.EnemiesWithTagCount (≥ X тегнутых) — гейт в AbilitySystem (подсчёт через тот же тег-запрос).
  • Тесты: MassAbility_AffectsAllTaggedEnemies_IgnoresUntagged; MassAbility_NoRangeLimit_HitsAcrossArena; MassAbility_ConsumesTriggerTag_AfterCast; GlobalSingle_NoRangeLimit_PicksByMode.

§10 — 6 Обычных реликвий (шаг 6)

Порядок — от простого к сложному, чтобы движок (§9) наращивался инкрементально и каждый срез опирался на уже закрытые расширения. Каждая реликвия = вертикальный срез как копейщик: SO-ассет (AIProfile + 2 навыка + статы) + спрайт + dev-команда gm_spawn_<relic> + EditMode-тесты среза + ручная проверка в харнессе. Статы — провизорные при сборке SO (карточки задают словесно; баланс — Ф4/Ф8). Канон навыков/AI — карточки в 1. Контент - Реликвии.

#РеликвияТянет из §9Суть
6.1Светлый пастырьE9.2 (хил-АА), E9.10 (глоб.)хилер: АА лечит союзника; ульта «Длань жизни» = X + недостающее HP
6.2КриомантE9.1 (on-hit), E9.10 (по-тегу)АА вешает Frozen (−60% MoveSpeed 4с); ульта «Ледяные оковы» = стан всех Frozen
6.3Надёжный защитникE9.3 (pre-damage S5)«Оплот» = щит (20 + 15% недост. HP) до удара; ульта стан 0.5с + −30% урона цели 3с
6.4Лесной следопытE9.5, E9.7, E9.8, E9.10рейндж-дпс: стрельба в движении (−50%), кайт, «Метка охотника» +25% урона (перенос)
6.5Скрытный убийцаE9.6, E9.4, E9.3empower-«Скрытность» + 2 заряда «Изворотливости» (негейт входящей АА); ресурс = нет
6.6Монах вихряE9.9, E9.6displacement «Шквальный толчок» + телепорт-в-спину ×2 «Вихревой заход»

§10.1 — Светлый пастырь (хилер, дальний) — ✅ СДЕЛАНО (2026-07-09)

Статус: реализовано, 144/144 EditMode зелёные (+8 ShepherdSliceTests). Канон — карточка «Светлый пастырь». Затянуты §9.2 (хил-АА снарядом) и §9.10 (глобальная одиночная активка). Ассет LightShepherd.asset, команда gm_spawn_shepherd.

Поправка к решению по эффективностям: доставка хила кладёт в снаряд/активку сырое значение (AutoAttackDamage / HealFlat + …), а обе эффективности (HealShieldDealtEff источника × HealShieldTakenEff цели) применяет ctx.Heal. Формулировка «amount = AA × DealtEff» из §9.2 была бы двойным учётом dealtEff — не следовать буквально. NoRangeLimit-поле сознательно НЕ введено: в AbilitySystem нет гейта дальности, а LowestHpAlly и так глобален (мёртвое поле не заводим).

Цель среза. Провести хилера сквозь шов: мозг выбирает раненого союзника (не врага), Пастырь пускает по нему хил-снаряд (АА лечит вместо урона), а при падении союзника ниже порога кастует усиленный хил «Длань жизни» (X + недостающее HP, без ограничения дальности). Сначала EditMode (детерминизм), затем глаз в харнессе.

Навыки (канон):

  1. «Целебный свет» (пассив-АА) — авто-атака выбирает союзника и лечит его вместо урона врагу. Реализация — §9.2: AutoAttackMode.Heal → tracking-хил-снаряд в AutoAttackTarget, amount = AutoAttackDamage × HealShieldDealtEff.
  2. «Длань жизни» (актив, глобальная) — усиленный хил X + недостающее HP цели, чтобы вытаскивать приоритетного союзника. Без ограничения дальности.

Новые/затронутые типы (швы):

ГдеИзменение
Projectile, ProjectileSpawn+ bool IsHeal (дефолт false); для хила RawDamage трактуется как Amount лечения (или переименовать в Payload). §9.2
ProjectileSystem.ApplyHitветка if (p.IsHeal) ctx.Heal(target, p.Amount, p.Source); else DealDamage(...). Хил-снаряд всегда tracking (по TargetUnit-союзнику).
ICombatContext.Heal / реализацияприменить HealShieldTakenEff(target), клампить к MaxHP (без оверхила). Если ещё не учтено — доделать.
AutoAttackSystem.Resolveесли unit.Relic.Ai.AutoAttackMode == Heal → спавн tracking-хил-снаряда в unit.AutoAttackTarget (не CurrentTarget), amount = AutoAttackDamage × HealShieldDealtEff; пропуск ветки урона. Если AutoAttackTarget == null (нет раненых союзников) — удар вхолостую (кулдаун потрачен, как у промаха копейщика).
AbilityDataхил-нагрузка активки: + float HealFlat (X) и + float HealPctTargetMissingHp (доля недостающего HP, для «Длани» = 1.0). Итог хила = (HealFlat + HealPctTargetMissingHp × (MaxHP − CurrentHP)цели) × HealShieldDealtEff(caster).
AbilityTargetMode+ LowestHpAlly (ульта целит самого раненого союзника; NearestAlly/Self уже есть).
AbilityData+ bool NoRangeLimit (снимает проверку дальности в ResolveTarget/гейте — глобальная «Длань», §9.10).
CastCondition+ AllyTargetHpBelowPct (блок D: каст, когда выбранный союзник HP% ≤ порога) + поле float CastConditionHpPct.
AbilitySystemблок E для хил-активок: при HpPct(caster) ≤ CastOverrideSelfHpPct — каст идёт и цель = сам кастующий (лечит себя), а не только «каст несмотря на условие». Ветка хила в ApplyToTarget: если `HealFlat>0

Поток (исполнение):

  • Мозг (ProfileBrain, уже готов §4.2): при AutoAttackMode.Heal пишет AutoAttackTarget = лучший союзник по AutoAttackTargeting, CurrentTarget = ближайший враг (для позиционирования/отступления). Режимы союзников AllyNearest/AllyLowestHpPercent уже в TargetingMode; «раненый союзник с макс. уроном» — опциональный новый режим AllyWoundedTopDps (для MVP достаточно AllyLowestHpPercent).
  • АА: AutoAttackSystem (двухфазный windup сохраняется) на кадре удара спавнит хил-снаряд → ProjectileSystem ведёт его к союзнику → ApplyHit лечит.
  • Активка: AbilitySystem per-tick: если союзник-цель (LowestHpAlly) HP% ≤ CastConditionHpPct (блок D) или свой HP% ≤ CastOverrideSelfHpPct (блок E, тогда цель = self) и есть ресурс/КД — каст ctx.Heal.

AI-профиль (AIProfile на ассете): AutoAttackMode = Heal; AutoAttackTargeting = AllyLowestHpPercent; Retreat.Enabled = true (хилер бежит); PassiveTrigger = None. Активка: targetMode = LowestHpAlly, NoRangeLimit = true, castCondition = AllyTargetHpBelowPct (CastConditionHpPct ≈ 0.5), castOverrideSelfHpPct ≈ 0.3 (блок E — лечит себя).

SO-ассет Assets/_Project/ScriptableObjects/Combat/LightShepherd.asset (провизорные статы, баланс позже): AttackType = Projectile; DamageType неважен (хил); ResourceType = Mana, MaxResource ≈ 30, ResourceOnHit ≈ 5; статы — HP/защита ниже среднего, AutoAttackDamage низкий (= слабый базовый хил), AttackRange высокий (дальний), HealShieldDealtEff ≈ 1.0; способность «Длань жизни»: HealFlat ≈ 30, HealPctTargetMissingHp = 1.0, ResourceCost = 30, BaseCooldown ≈ 6. Спрайт — выбрать неиспользуемый пак (Fantasy Warrior занят копейщиком), нарезать как FantasyWarrior.asset.

Харнесс. Команда gm_spawn_shepherd: Пастырь + 2–3 раненых союзника (team 0) против пары болванчиков (team 1), чтобы видеть выбор раненого, хил-снаряды и каст «Длани» при просадке. Привязать визуал-оверрайд (§ шаг 4) + dev-снаряд-хил можно подкрасить позже (логика — EditMode).

Тесты среза (Assets/Tests/EditMode/Combat/ShepherdSliceTests.cs, стабы → красный → зелёный):

  1. HealAutoAttack_SpawnsHealProjectile_AtWoundedAlly — спавнит хил-снаряд в раненого союзника (по AutoAttackTarget мозга), не во врага.
  2. HealProjectile_HealsAllyOnImpact_NotDamage — снаряд при попадании лечит, HP цели растёт, урона нет.
  3. HealAmount_ScalesWith_AutoAttackDamage_And_DealtEff — величина = AutoAttackDamage × HealShieldDealtEff.
  4. Heal_NoOverheal_ClampedToMaxHp — лечение не превышает MaxHP.
  5. HandOfLife_HealsForFlatPlusMissingHp — «Длань» лечит X + недостающее HP.
  6. HandOfLife_CastsWhenAllyBelowThreshold (блок D) / DoesNotCast_WhenAllyHealthy.
  7. HandOfLife_SelfHeal_WhenOwnHpLow (блок E) — при своём низком HP цель = сам.
  8. Shepherd_NeverDamagesEnemies — ни АА, ни активка не наносят урон.

Критерий готовности §10.1:

  • Все EditMode зелёные (135 текущих + §9.2-расширения + срез Пастыря).
  • Хил-снаряд и IsHeal-payload работают, дефолт false не сломал снарядные тесты копейщика/Ф1.
  • Пастырь в харнессе: выбирает раненого, лечит снарядом, кастует «Длань» при просадке союзника, лечит себя при своём низком HP.

§10.2 — Криомант (контроллер, дальний) — ✅ СДЕЛАНО (2026-07-09)

Статус: реализовано, 150/150 EditMode зелёные (+7 CryomancerSliceTests). Затянуты §9.1 (on-hit эффекты АА — на снаряде через RelicData.AutoAttackEffects[]; симметрично реализован и мили single/Line) и §9.10 (масс-каст по тегу). Ассеты Cryomancer.asset + Effects/Frozen.asset + Effects/IceChainsStun.asset, команда gm_spawn_cryomancer, _cryomancerRelic привязан в BattleScene.

Новые швы (сверх плана ниже): AbilityTargetMode.AllEnemiesWithTag, AbilityData.TriggerTag/ConsumesTriggerTag, CastCondition.EnemiesWithTagCount, TargetingMode.PreferUntagged (ветка-зеркало в ProfileBrain.Score), ProjectileSpawn/Projectile.OnHitEffects. Решение по гейту: масс-каст при нуле тегнутых врагов НЕ кастуется (трактуется как «нет валидной цели» — не жжёт КД/ману даже под панику блока E). Провизорные: Frozen = StackRule.Refresh, порог ульты X = 2, статы (HP 120 / урон 12 / MoveSpeed 2.8). Спрайт не назначен.

Тянет §9.1 (on-hit эффект — у дальнего применяется при попадании снаряда) и §9.10 (масс-каст по тегу). Решение Макса: «Ледяные оковы» снимают Frozen (конвертируют в стан), а не оставляют параллельно.

  • Навыки: «Леденящее касание» (пассив-АА) — снаряд при попадании вешает Frozen (StatModifier MoveSpeed −60%, 4с, тег Frozen). «Ледяные оковы» (актив, глобальная)Control-стан 2с всем врагам с Frozen; при касте диспелит Frozen с каждой цели (конверсия).
  • Швы: §9.1 RelicData.AutoAttackEffects[] (применяется в ProjectileSystem при ударе); §9.10 AbilityTargetMode.AllEnemiesWithTag + поле тега; конверсияAbilityData.ConsumesTriggerTag : bool → после наложения эффектов ctx.Dispel этого EffectTag с цели (есть DispelComponent/DispelRequest); инверсия приоритета PreferUntagged(Frozen) — мелкая ветка в ProfileBrain.Score (штраф тегнутым вместо бонуса).
  • AI: A {Nearest, без «Заморозки» (PreferUntagged Frozen), LowestHpPercent}; D Сразу / ≥ X замороженных (новое cast-условие EnemiesWithTagCount(Frozen) ≥ X); E свой HP% ≤ P; B.
  • SO Cryomancer.asset: AttackType=Projectile, ResourceType=Mana; AutoAttackEffects=[Frozen-slow]; способность «Ледяные оковы» targetMode=AllEnemiesWithTag(Frozen), effects=[Control 2с], ConsumesTriggerTag=true, NoRangeLimit, castCondition=EnemiesWithTagCount≥X. Статы: HP ниже среднего, контроль выше.
  • Тесты среза: Cryo_Projectile_AppliesFrozen_OnImpact; Cryo_Ult_StunsAllFrozen_NoRange; Cryo_Ult_ConsumesFrozen_ReplacedByStun; Cryo_Ult_IgnoresUnfrozen; Cryo_CastsAt_FrozenCountThreshold (блок D).

§10.3 — Надёжный защитник (танк-контроллер, ближний)

Тянет §9.3 (pre-damage хук S5). Решение Макса: «Оплот» ограничен внутренним кулдауном ~6–8с (иначе блокирует каждый удар).

  • Навыки: «Оплот» (пассив-реактив) — pre-damage (§9.3): перед входящим уроном, если триггер (блок F) и внутренний КД готов → поднять ShieldComponent величиной 20 + 15% × (MaxHP − CurrentHP), держится 2с, взвести внутренний КД 6–8с. «Решительный удар» (актив)Control-стан 0.5с + StatModifier −30% DamageDealtEff цели 3с.
  • Швы: §9.3 IPreDamageComponent на компоненте «Оплот»: читает входящий DamageRequest, проверяет блок F (любой удар / урон > D%·MaxHP) + поле BulwarkCooldownTimer; поднимает щит, ставит КД. Величина щита (20 + 15% недостающего HP) считается в компоненте (не чистый ScalableValue — недостающее HP юнит-относительно).
  • AI: A; C цель ульты {Nearest, LowestHp, HighestHp, HighestThreat} (главный ДПС — HighestThreat уже в ProfileBrain); D временно «сразу по приоритету» (сбить-каст отложен); E свой HP%; F «Оплот»: при любой атаке / при уроне > D% MaxHP; B.
  • SO Defender.asset: AttackType=Melee, ResourceType=Mana; пассив «Оплот» = GrantedEffects с pre-damage компонентом (КД 6–8с, порог блока F); способность «Решительный удар» effects=[Control 0.5с, StatModifier DamageDealtEff −0.3 / 3с], targetMode по блоку C. Статы: HP/защита выше среднего, СА ниже.
  • Тесты среза: Defender_Bulwark_ShieldsBeforeHit_WhenReady; Defender_Bulwark_ScalesWithMissingHp; Defender_Bulwark_RespectsInternalCooldown; Defender_Ult_StunsAndReducesDamage; Defender_Ult_TargetsHighestThreat.

§10.4 — Лесной следопыт (рейндж-дпс, дальний)

Тянет §9.5 (перенос метки), §9.7 (кайт), §9.8 (атака-в-движении), §9.10 (глоб). Дефолты по карте — без спорных развилок.

  • Навыки: «Стрельба на ходу» (пассив) — атака в движении, MoveSpeed −50% пока стреляет (§9.8). «Метка охотника» (актив, глобальная)Marked + StatModifier DamageTakenEff +0.25, 5с; при смерти носителя переносится на ближайшего врага с обновлением длительности (§9.5).
  • Швы: §9.8 RelicData.CanAttackWhileMoving=true + MovingAttackSpeedPenaltyPct=0.5; §9.7 MovementSystem исполняет Kite; §9.5 reactive-transfer компонент на метке (перенос + рефреш); §9.10 NoRangeLimit.
  • AI: A {Nearest, LowestHp, HighestHp}; C цель метки тем же набором; D «сразу в начале фокуса»; G кайт (FleeDist < FallbackDist); B. Приоритет АА — PreferTagged(Marked) (бьёт отмеченного).
  • SO Ranger.asset: AttackType=Projectile, CanAttackWhileMoving=true, MovingAttackSpeedPenaltyPct=0.5, Kite.Enabled=true; способность-метка targetMode по блоку C + NoRangeLimit, effects=[Marked, StatModifier DamageTakenEff +0.25 / 5с] + reactive-transfer. Статы: HP ниже, урон/мобильность выше.
  • Тесты среза: Ranger_ShootsWhileMoving_WithPenalty; Ranger_Mark_Adds25PctDamageTaken; Ranger_Mark_TransfersToNearest_RefreshesDuration_OnDeath; Ranger_Kite_MaintainsDistanceBand; Ranger_PrioritizesMarkedTarget.

§10.5 — Скрытный убийца (ассасин, ближний, без ресурса)

Тянет §9.6 (empower), §9.4 (заряды), §9.3 (pre-damage негейт). Решения Макса: «Скрытность» держится до первой авто-атаки ассасина; «Изворотливость» негейтит любой следующий входящий удар (флаг типа источника не нужен).

  • Навыки: «Скрытность» (пассив-реактив) — в начале боя и после своего убийства (UnitDied, source=self): StatModifier (DamageTakenEff −X, MoveSpeed +Y) до первой авто-атаки ассасина + empower этой атаки (§9.6). Первая АА: снять баф, применить ×урон/доп-эффект. «Изворотливость» (актив, 2 заряда, §9.4) — негейт следующего входящего инстанса урона (любого) через pre-damage (§9.3), затем заряд на независимую перезарядку. Тратится защитно по AI (блок F), не вручную.
  • Швы: §9.6 маркер empower снимается в AutoAttackSystem.Resolve (он же снимает стелс-StatModifier); §9.4 Charges=2 + ChargeRechargeSeconds; §9.3 pre-damage-компонент «Изворотливость» обнуляет ближайший входящий урон (без проверки типа), тратит заряд.
  • AI: A цель добивания {Nearest, LowestHp, HighestHp}; F «Изворотливость»: при любой атаке / при уроне > D% MaxHP (всегда при смертельном); B. Активной ульта-цели нет; ресурс — None.
  • SO Assassin.asset: AttackType=Melee, ResourceType=None; пассив «Скрытность» (reactive on-start + on-kill); способность «Изворотливость» Charges=2, ChargeRechargeSeconds≈8, pre-damage-негейт. Статы: HP ниже, СА/скорость выше.
  • Тесты среза: Assassin_Stealth_AppliedAtCombatStart; Assassin_Stealth_EmpowersAndEnds_OnFirstAutoAttack; Assassin_Stealth_RetriggersOnOwnKill; Assassin_Dodge_NegatesNextIncomingHit_AnySource_UsesCharge; Assassin_Dodge_TwoChargesThenRecharge.

§10.6 — Монах вихря (мобильный контроллер-дуэлянт, ближний) — последним

Тянет §9.9 (displacement + «ядро») и §9.6 (empower). Решения Макса: «Вихревой заход» без кулдауна (в этом суть — связки телепортов на билдах); «Шквальный толчок» — рывок + отброс на фикс. дистанцию X, летящий враг = «ядро» (урон только врагам Монаха на пути, своих не задевает).

  • Навыки:
    • «Шквальный толчок» (актив): (1) Монах рывком телепортируется к ближайшему врагу, вставая с противоположной стороны от нужного направления броска (блок C); (2) отталкивает цель «от себя вперёд» на фикс. дистанцию X; (3) пока цель летит — она оглушена (контроль-иммунно, §4.4) и наносит урон врагам Монаха на линии смещения (как «ядро»; союзникам Монаха урона нет). Направление выбирается так, чтобы запущенный враг долетел до другой цели (учитывается в AI, блок C — «докуда долетит»).
    • «Вихревой заход» (пассив): когда любого врага отталкивают (любой источник смещения) — Монах телепортируется ему в спину и бьёт ×2 (§9.6). Без кулдауна (комбо-механика). Блок F конфигурирует триггер {Всегда (дефолт) / когда толкают меня / когда я толкаю}.
  • Швы: §9.9 DisplaceRequest{Kind=Knockback, Target, Dir, DistanceX, Ticks} + bool CannonballDamage (летящий юнит бьёт врагов кастующего на пройденном сегменте через QueryUnitsInLine, без friendly-fire); DisplacementSystem мутирует Position, держит стан в полёте — спайк на детерминизм порядка мутаций. Нужна нотификация смещения для реактива (нет CombatEvent.Displaced — добавить флаг события / хук DisplacementSystem), на неё подписан «Вихревой заход» (§9.6 empower + teleport-behind). Рывок-позиционирование (блок C) — расчёт точки телепорта в таргетинге активки.
  • AI: A; C цель ульты {в дальнего (докуда долетит) / в ближнего / себе в тыл (к союзникам)}; D «есть валидная цель (в кого запустить)»; E свой HP% + «игнорировать условие, если остался 1 враг»; F триггер телепорта (см. выше); B.
  • SO WhirlMonk.asset: AttackType=Melee, ResourceType=Mana; способность «Шквальный толчок» (displacement-ядро, DistanceX, урон); пассив «Вихревой заход» (reactive on-displaced, empower ×2, без КД). Статы: HP/урон средние, мобильность/контроль выше.
  • Тесты среза: Monk_Push_DashesToNearestEnemy_PositionsOppositeThrowDir; Monk_Push_KnocksTargetFixedDistanceX; Monk_Push_FlyingTarget_StunnedAndCannonballHitsEnemiesOnly_NotAllies; Monk_VortexEntry_TeleportsBehind_OnAnyDisplace_NoCooldown; Monk_VortexEntry_EmpoweredHit_DoubleDamage.

§11 — Критерий закрытия Фазы 3

  • Все EditMode-тесты зелёные (текущие 135 + расширения §9 + срезы 6 реликвий).
  • Все 7 Обычных реликвий проходят срез в харнессе (gm_spawn_<relic>): от «подойти/занять позицию» до применения активки по условию.
  • Движковые расширения §9 (E1–E10) реализованы, дефолты не сломали существующий контент.
  • doc 13 §8: шаги 5–7 ✅; 9. Roadmap §3: отметка о завершении Ф3.
  • Ретро + синхрон AI-словаря 0.3 под фактический AIProfile (если ручки разошлись с реализацией).