Решили: проход блинков в AutoAttackSystem считает точки назначения ВСЕХ блинкующих от одного
снимка мира и только потом записывает позиции — тем же приёмом, что CombatSimulation.ApplyPendingTeleports.
Почему: прежде CombatPositioning.TeleportBehind вызывался прямо в обходе и писал позицию
немедленно. Два Убийцы в стелсе бьют друг друга в одном тике, оба блинкуют: левый прыгает за спину
правому, пока тот на месте, а правый целится за спину телу, которое уже уехало. Порядок обхода
_hits у отражённых сторон одинаковый, а не зеркальный, поэтому сторона, идущая первой,
выигрывала половину радиуса атаки. Mirror_SquadSeries_NeverDiverges(0) краснел на CI ровно этим:
расхождение 0.5 на тике 450, в момент, когда оба Убийцы умирали.
Это четвёртый случай того же класса, что описан в remarks MirrorMatchTests: система читает мир и
пишет в него внутри одного обхода. Лекарство в проекте уже было написано — в
ApplyPendingTeleports, с комментарием «иначе второй телепорт целился бы за спину тела, которое
только что уехало». Один путь знал про грабли, второй на них наступал.
Грабли:
- Комментарий над циклом был правдой и обманом одновременно: «все перемещения происходят ДО того, как хоть один удар начнёт мерить дистанции». До УДАРОВ — да. До других ТЕЛЕПОРТОВ — нет. Читается как гарантия порядка, а гарантирует только половину.
- Метод стоит удержаться от удаления:
CombatPositioning.TeleportBehindпосле правки выглядит осиротевшим, но его зовут тесты и мок боевого контекста (EffectTestSupport), аCombatPositioningTestsпроверяет его напрямую. Одиночный телепорт законен — незеркальным его делала пачка.
Как нашли — метод, который стоит повторять. Сужение составом: прогнать все пары и тройки из
расходящейся четвёрки [Antimage, Arcanist, Assassin, BaseRelic]. Все десять подмножеств оказались
зелёными — расходится только полный состав, потому что в меньшем бою Убийцы не сходятся в центре к
450-му тику. Это сразу сняло подозрение с одиночных механик и указало на взаимодействие. Дальше
хватило полного отчёта MirrorFixture: он печатает слепок обеих сторон и журнал ударов тика, и в нём
было видно, что все, кроме Убийц, зеркальны идеально, а Убийцы разошлись ровно на AttackRange * 0.5 —
то есть на offset из BehindPosition.
Две гипотезы до этого были отвергнуты замером, и обе выглядели убедительно: кувырок уклонения
(Vector2.right в вырожденной ветке) отпал, потому что уклонение срабатывает только от автоатак,
а effect.discord оказался дебаффом магической брони, а не DoT.
Владелец правды: Combat/Systems/AutoAttackSystem.cs (проход 2a и _blinkDestinations),
Combat/CombatSimulation.cs (ApplyPendingTeleports — образец приёма); тест MirrorMatchTests.