Решили: порядок 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.