Решили: порядок pre-damage реакций задаёт число IPreDamageComponent.Priority (свойство кода, не поле ассета), при равных числах раньше идёт своя реакция носителя, а сам проход RunPreDamage обрывается на первой, отменившей удар.

Почему: порядок опроса определялся индексом в ActiveEffects, то есть тем, кто раньше повесил эффект, — при двух реакциях на одном юните это лотерея, а не правило. Отвергнуты два варианта. Жёсткая лестница по «типу вмешательства» без числа — она прячет в код рычаг, который в автобое принадлежит игроку (слова Макса, 2026-08-21/3). Приоритет полем в EffectData — из инспектора можно было бы переставить порядок боя, а это правило симуляции, а не настройка контента.

Грабли:

  • BlockComponent жёг заряд на отменённый удар. Остановка «удар уже отменён» держалась не на системе, а на вежливости: каждый компонент писал if (result.Negated) return; первой строкой. Из двенадцати pre-damage компонентов её не делали два — «Угли» (безвредно, чистый множитель) и «Оплот», который тратил заряд и вешал щит против удара, которого уже нет. Искать это по симптомам было бы тяжело: заряд просто «иногда не срабатывал». Регресс закрыт тестом EvadeNegates_BulwarkKeepsItsChargeAndRaisesNoShield.
  • Сортировка в горячем пути. RunPreDamage зовётся на каждый удар, и List.Sort там не годится: он нестабилен (равные реакции переставлялись бы между кадрами, ломая детерминизм) и боксит компаратор-структуру. Взята сортировка вставками по предвыделенному буферу — реакций на юните единицы, зато проход не аллоцирует и порядок равных сохраняется. Гейт — BattleTapeTests.CaptureTick_DoesNotAllocate_AfterWarmup.
  • Ступени «реакция от предмета» нет, хотя Макс её назвал. Предметы сегодня дают только статы (EffectiveStats.Build) и ни одной реакции не вешают; признака «эффект пришёл из предмета» в RuntimeEffect не существует. Заводить перечисление под несуществующего носителя значит выдумать поле, которое некому заполнять, — место под ступень оставлено числовым зазором между SelfCast и FromAlly.
  • Проверки Negated в компонентах оставлены. Система теперь гарантирует остановку сама, но компонент — публичный API, и его контракт не должен зависеть от того, кто его вызвал (тесты зовут OnPreDamage напрямую).

Владелец правды: Assets/_Project/Scripts/Combat/Effects/ReactionPriority.cs, EffectSystem.RunPreDamage, тест ReactionOrderTests.