Решили: канал авто-атаки — это третья ФАЗА существующего ритма (Idle → Windup → Channel → Recovery), а не отдельный ритм рядом с двухфазным. Тик канала есть обычный удар: бьёт
AutoAttackDamage, расстояние между тиками 1 / AttackSpeed. Авторится ровно два числа
(AttackChannel.DurationSeconds и WindupSeconds), и оба — в секундах. Разрыв (контроль, смерть или
уход цели) гасит поток без рефанда кулдауна, с доигрыванием хвоста.
Почему:
- Фаза, а не свой ритм. Прерывание контролем, рут движения (
MovementSystem), выбор анимации показом и будущее парирование уже написаны один раз — черезAttackPhase. Ритм рядом означал бы вторую копию каждого из этих правил, и первая же правка одной копии разошлась бы со второй. - Урон и частота НЕ поля канала. Формулировка Макса — «скорость атаки это расстояние между тиками
урона», — и при уроне тика в
AutoAttackDamageона даёт DPS, равный классовой норме, посчитанный той же формулой, что у любого бойца. Отвергнуто «своё поле DPS канала»: у этой величины уже есть владелец (пара статов, её читают стенд иClassBalanceConfig), а второй владелец разъезжается на первом же балансном прогоне — так уже разошлись нормы, выписанные в карточках реликвий. - Ловушка двойного счёта, из-за которой поля именно такие. Если скорость атаки одновременно сокращает расстояние между тиками (их становится больше) И входит в урон тика, она умножает сама себя: +50% темпа дают +125% урона. Поэтому канал устроен так, что скорость атаки влияет ровно на одно — на частоту.
- Секунды, а не доли интервала атаки. Канал бывает длиннее интервала (в пределе — «пока цель
жива»), поэтому доля от интервала для него невыразима. По той же причине замах канала не проходит
через штатный кламп
intervalTicks − 1: у канального кита интервал отмеряет тик ВНУТРИ атаки, и обычный потолок обрезал бы именно тот длинный занос, ради которого канал существует. - Мгновенный урон даже у дальнобойного кита. Снаряд по природе дискретная посылка со временем полёта; поток из посылок перестаёт быть потоком, а «Кровавый обмен» Десятины считает урон по позиции жертвы в момент попадания — у летящего снаряда она уже другая.
- Разрыв без рефанда. Часть тиков урона прошла, значит атака состоялась частично; обнулить кулдаун значило бы отдать её бесплатно. И наоборот, кулдаун канального кита ставится в ноль на старте замаха (ритм держат фазы): сорванный поток уже наказан потерянными тиками, добавлять к этому ожидание — наказать дважды за одно прерывание.
Грабли: имена ChannelRemaining / ChannelTickRemaining в RuntimeUnit уже заняты каналом
СПОСОБНОСТИ (M3, владелец — AbilitySystem), и компилятор поймал это только на прогоне. Поля
авто-атаки получили префикс Attack* — не ради стиля: совпади имена, счётчик получил бы двух
владельцев, что в этом же файле объявлено дефектом. Снимок канала лежит в RuntimeUnit, а не читается
из кита напрямую, потому что канал бывает свойством СТОЙКИ (у Десятины поток живёт в дальней форме, а
в упор она бьёт выпадами) — чтение кита заставило бы выражать переключение форм вторым китом.
Владелец правды: AttackPhase.Channel, AutoAttackSystem.TickChannel/EnterChannel/BreakChannel,
AttackChannel в UnitData.cs, AttackTiming.WindupTicksFor; тест ChannelledAutoAttackTests
(в нём же живёт инвариант «канал не обходит классовый коридор DPS»).