Решили: «враг применил способность» стало боевым событием CombatEvent.AbilityCast, и оно
единственное в очереди доставляется НЕ одному носителю, а всем живым врагам кастующего (рассылка — в
CombatSimulation.DrainEventQueue). Щит с фильтром по школе живёт компонентом
SchoolShieldComponent со своим пулом в эффекте, а не новым полем в RuntimeUnit.
Почему: реакция на каст висит на противнике, а кастующий про неё ничего не знает — значит либо событие рассылает очередь, либо каждый реактив сам обходит список юнитов. Второе отвергнуто: обход внутри компонента делает порядок юнитов частью исхода боя, а это ровно тот класс дефектов, который ловит зеркальный тест.
Со щитом развилка была между тремя вариантами: (1) общий CurrentShield — школы не различает, щит от
магии съедал бы физические удары; (2) второй и третий пул в RuntimeUnit — плодит поля под каждую
будущую школу, а пайплайн урона придётся учить порядку их трат; (3) поглощение через pre-damage.
Выбран третий: хватило запаса — удар отменяется, не хватило — множителем срезается покрытая доля.
Арифметически тот же вычет, но без нового понятия в ядре.
«Перегрузка» считает ПОГЛОЩЁННЫЙ урон (RuntimeUnit.AbsorbedByWard), а не размер поднятых щитов — в
отличие от взрыва щита, который платит за прочность. Разница смысловая: взрыв награждает за то, что
щит пришлось пробивать, ответ Антимага — за то, что в него реально били магией.
Грабли: удар, съеденный фильтрованным щитом полностью, для реактивов «на удар» выглядит
отменённым — шипы о него не колются, как и о любой негейт. Цена принята. И расширение
ICombatContext чинится в семи тестовых заглушках руками: дефолтная реализация в интерфейсе
спрятала бы шов от читателя.
Владелец правды: CombatSimulation.ReportAbilityCast/DrainEventQueue,
SchoolShieldComponent.cs, OverloadStrikeComponent.cs.