Решили: страховки «ассет равен код-дефолтам» перечисляют поля SimTuning рефлексией, а не списком в тесте; связь двух лестниц дальности (AttackRangeBand и CastRangeBand) держит отдельный тест.

Почему: рукописный перечень полей в тесте — сам по себе второй владелец факта «из чего состоит конфиг». Он отстаёт молча: проверялось 14 полей из 32, и восемнадцать позднейших ручек (смещение, овертайм, спринт, рекаст, маскировка, комбо, отступление) могли разъехаться между ассетом и кодом, не потревожив никого. Цена расхождения тут не косметическая: балансный стенд и зеркальные тесты гоняют бой на код-дефолтах, а игра — на ассете. Отвергнутая альтернатива — «дописать восемнадцать строк»: она чинит сегодняшний разрыв и оставляет механизм, который породит следующий.

Лестницы дальности связаны одной строкой приведения в RuntimeUnitFactory ((AttackRangeBand)(ability.CastRange - 1)). Компилятор такого приведения не проверяет, поэтому ступень, добавленная в один enum и забытая во втором, сдвинет дальность каста у всех умений разом — и выглядеть это будет как «лучник почему-то кастует с дистанции метателя».

Грабли: дрейфа на момент правки не было ни в одном из тридцати двух полей, то есть найти это через красный тест было нельзя в принципе — только пересчитав, что именно страховка покрывает. Обе дыры пришли из панели аудита, а не из игры.

Владелец правды: Core/Simulation/SimTuning.cs, Data/Definitions/{AttackRangeBand,CastRangeBand}.cs; тесты ConfigValidationTests.SimTuningConfig_MatchesCodeDefaults, ConfigValidationTests.RangeBands_CastLadderMirrorsAttackLadder.