Решили: дальность авто-атаки задаётся ступенью, а не числом. Шесть ступеней (AttackRangeBand: обычный мили, длинное оружие, малая дальность, обычная, большая, без ограничений), дистанция каждой живёт в StatsConfig (1 / 2 / 5 / 8 / 12 / 25), а личное отличие юнита — доля от ступени (_rangeAdjustPct, ±25%). Свой AttackRange в стат-блоке запрещён.

Почему: у дальности не было ни словаря, ни владельца — просто число в каждом ассете. Незаполненное поле молча уезжало на дефолт конфига (единицу), и это ничем не отличалось от осознанного «он ближний»: «дальник в упор» и «ближник» выглядели в данных одинаково. Ступень заставляет ответить «кто он по дистанции» до того, как появится число.

Поправка именно в долях, а не в единицах: ступени — ручка баланса, и правка ступени обязана доезжать до всех её носителей. С плоским «+1» кит с поправкой остался бы на прежней дистанции, и ручка молча перестала бы работать ровно там, где интереснее всего.

Шестая ступень — число (25 при арене 20×12), а не бесконечность: на дальность опираются геометрические гейты — предсказание «замах докрутит», сближение, кайтинг. Бесконечность превратила бы их в сравнение с бесконечностью.

Сказано: «Да, дальность классы не задают, но должны быть уровни свои отдельно … И все юниты привязываются к одному из этих дальностей + могут чуть уменьшить или увеличить от него. Но по сути мы задаем лишь их, и если что через это и балансим» (2026-08-04, сессия 86077da2).

Грабли:

  • Класс дальность не задаёт и не должен — танк бывает и с копьём, и с кулаками. Поэтому ступень живёт в UnitData, а не в ClassBalanceConfig, хотя обе величины «базовые».
  • Миграция сохранила дистанции всем. 48 юнитов разложены по ступеням с компенсирующей поправкой (7 → «обычная» −12.5%, 6 → «малая» +20%), поэтому замеры до и после сравнимы. Единственный, у кого сменилась база, — Кровомант: с молчаливой единицы на восемь.
  • Кровомант при этом дрался в упор НЕ из-за дальности — вопреки тому, что я решила по первому взгляду на его стат-блок. Дистанцию ему задаёт стойка: AttackStanceComponent в BloodStance Override-ит AttackRange на 8 в дальней форме и на 1 в ближней. Стойка — верхний слой каскада и ступень перекрывает законно, так что для таких китов ступень задаёт лишь исходную форму. Это единственный эффект во всём контенте, который трогает дальность.
  • Ступень кладётся Override, а не Flat — иначе она сложилась бы с дефолтом конфига.

Владелец правды: AttackRangeBand, StatsConfig.RangeOf, RangeBaseline.Apply, тест AttackRangeBandTests.