Решили: состояние юнита заводится ТОЛЬКО после того, как решение о нём принято, и снимается
ТОЛЬКО когда ушёл последний, кто его держал. Проверено на смещениях: Displace теперь отказывает по
тому же условию, что и DisplacementSystem.Add, а конец полёта объявляется, лишь когда у тела не
осталось активных смещений.
Почему: обе половины ломались одинаково тихо. Маркер «в полёте» — постоянный, неснимаемый, с полным контролем — вешался ДО того, как система примет запрос; система молча отказывалась при нулевой дистанции, а снимает маркер только конец полёта, которого уже не будет. Цель оставалась выключенной до конца боя. С другой стороны, флаг полёта у юнита один, а смещений по одному телу может идти несколько (толчок способности и цепное отбрасывание «ядра» в те же тики) — первый закончившийся полёт снимал флаг с тела, которое ещё тащит второй.
Грабли: и то, и другое латентно ровно до одной строки в ассете. «Толкает» и «на сколько» — два независимых поля, и нулевая дистанция при взведённом флаге это не выдумка, а один промах в инспекторе. Тем же заходом нашлись два родственника: у эффекта снимок стаков стартовал единицей, хотя «снимка ещё нет» кодируется нулём (эффект с несколькими стаками влиял как один), и трекинг- снаряд с пробиванием не гас на попадании — встав в цель, он бил её каждый следующий тик. Общее у всех четырёх одно: пара «завести/снять» разъехалась, и ни одна половина об этом не сообщает.
Владелец правды: CombatSimulation.Displace (единое условие приёма),
DisplacementSystem.HasActiveDisplacement, RuntimeEffect.StacksAtTickStart,
ProjectileSystem.ApplyHit.