Архитектура реализации — Фаза 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) | ✅ готово |
| 2 | Lite визуальный харнесс: самодостаточная 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) |
| 6 | 6 Обычных реликвий вертикальными срезами в порядке нарастания движка: Пастырь → Криомант → Защитник → Следопыт → Убийца → Монах (§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 Positioning | Approach / 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
- Filter — живой; нужная команда (враги, либо союзники при
AllyNearest/AllyLowestHpPercent/Heal); себя исключаем для союзных режимов. - Score (меньше = лучше):
Nearest/AllyNearest→dist²;LowestHpFlat→CurrentHP;LowestHpPercent/AllyLowestHpPercent→HP%;HighestHp→-CurrentHP;HighestThreat→-EstimatedDps;PreferTagged→dist²тегнутым,dist² + штрафнетегнутым (тегнутый всегда выигрывает). - Тай-брейк — при равном скоре дистанция, затем
Id(детерминизм, без RNG). - Запись интента — обычный режим:
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 (детерминизм), затем глаз в визуальном харнессе.
Дизайн (канон — карточка «Железный копейщик»):
- «Размашистый выпад» (пассивная авто-атака) — авто-атака бьёт линией перед юнитом, задевая несколько целей; дальность атаки выше обычного милишника. Роль — фронтлайн-брузер с AOE-паттерном.
- «Стальной вихрь» (активная) — круговой 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:
- Линейная авто-атака задевает несколько целей в полосе перед юнитом, мимо-полосы — нет.
- Дальность линейной АА выше базовой милишной.
- «Стальной вихрь» бьёт всех врагов в радиусе на ×3 урона, союзников — нет.
- Cast-условие
EnemiesInRadius ≥ X: при < X не кастует, при ≥ X — кастует. - Отмена условия (блок E): при своём HP ≤ P% кастует независимо от числа врагов.
- Срез в харнессе: подходит → линейно бьёт → при скоплении врагов пускает «Стальной вихрь», далее держит КД.
Критерий готовности шага 4
- Все EditMode-тесты зелёные (старые + новые шага 4).
- Плейсхолдер-каст заменён на cast-условие из
AbilityData. - «Железный копейщик» проходит срез в харнессе от «подойти» до «вихрь по скоплению».
✅ Закрыт (135/135, коммит ed9743e).
§9 — Движковые расширения §10 (шаг 5)
Шаг 5 затаскивает в движок механики, которых нет в библиотеке Ф1/Ф2, но которые нужны 6 оставшимся Обычным реликвиям (6. Боевая модель §10). Каждое расширение проектируется сначала как шов (сигнатура + точка интеграции + имена тестов), реализуется по мере того, как до него доходит первая реликвия в §10. Дефолты всех расширений сохраняют поведение существующего контента (копейщик/тест-юниты не ломаются).
Принцип: реактив-расширения (E3/E6 — на готовом
IReactiveComponent+ FIFOCombatEvent) — это контент, не движок. Синхронные перехваты (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.IsHeal→ctx.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 2с |
| 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.3 | empower-«Скрытность» + 2 заряда «Изворотливости» (негейт входящей АА); ресурс = нет |
| 6.6 | Монах вихря | E9.9, E9.6 | displacement «Шквальный толчок» + телепорт-в-спину ×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 (детерминизм), затем глаз в харнессе.
Навыки (канон):
- «Целебный свет» (пассив-АА) — авто-атака выбирает союзника и лечит его вместо урона врагу. Реализация — §9.2:
AutoAttackMode.Heal→ tracking-хил-снаряд вAutoAttackTarget,amount = AutoAttackDamage × HealShieldDealtEff. - «Длань жизни» (актив, глобальная) — усиленный хил
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лечит. - Активка:
AbilitySystemper-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, стабы → красный → зелёный):
HealAutoAttack_SpawnsHealProjectile_AtWoundedAlly— спавнит хил-снаряд в раненого союзника (поAutoAttackTargetмозга), не во врага.HealProjectile_HealsAllyOnImpact_NotDamage— снаряд при попадании лечит, HP цели растёт, урона нет.HealAmount_ScalesWith_AutoAttackDamage_And_DealtEff— величина =AutoAttackDamage × HealShieldDealtEff.Heal_NoOverheal_ClampedToMaxHp— лечение не превышаетMaxHP.HandOfLife_HealsForFlatPlusMissingHp— «Длань» лечитX + недостающее HP.HandOfLife_CastsWhenAllyBelowThreshold(блок D) /DoesNotCast_WhenAllyHealthy.HandOfLife_SelfHeal_WhenOwnHpLow(блок E) — при своём низком HP цель = сам.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(StatModifierMoveSpeed −60%, 4с, тегFrozen). «Ледяные оковы» (актив, глобальная) —Control-стан 2с всем врагам сFrozen; при касте диспелитFrozenс каждой цели (конверсия). - Швы: §9.1
RelicData.AutoAttackEffects[](применяется вProjectileSystemпри ударе); §9.10AbilityTargetMode.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.7MovementSystemисполняетKite; §9.5 reactive-transfer компонент на метке (перенос + рефреш); §9.10NoRangeLimit. - 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.4Charges=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(если ручки разошлись с реализацией).