Решили: предбанник урона (IPreDamageComponent — щиты, негейт, уязвимости) больше не выполняется в
момент заявки удара. Заявка кладёт в TickLedger сырой DamageRequest, а весь счёт идёт на коммите
через новый шов ITickLedgerSink.ResolveIncoming. Заодно события развели на две очереди: заявления
(«каст объявлен») доставляются ДО коммита урона, последствия («по мне ударили», «я убил») — после.
Почему: предбанник не чист — он тратит запас щита эффекта, копит RuntimeUnit.AbsorbedByWard, жжёт
заряды и (у парирования) дёргает RecastAttack носителя. Пока он срабатывал в момент заявки, первый
заявленный удар менял мир посреди фазы решений, и боец, стоящий в обходе позже, решал по другому
состоянию. Замер: два Антимага, обменявшись «Перегрузкой» в один тик, били 48 и 67.2 — второй успевал
поглотить чужой удар вардом и обналичить его своей же «Перегрузкой». Комментарий в DealDamageCore
утверждал обратное («расчёт чист и от порядка не зависит») — неверным было именно это утверждение, а не
формула.
Отвергли: снимок накопителя на начало тика по образцу CanActAtTickStart / StacksAtTickStart —
дешевле и лечит зеркало, но это правило ПЛЮС список полей-исключений: пятое поле заведут без снимка, и
дефект вернётся молча. Выбор Макса — «самый честный вариант, чтобы самим не запутаться»: одно правило без
исключений.
Грабли:
- Порядок «заявления → коммит → последствия» — не деталь, а исполнение карточки
the-aegis(«щит встаёт до попадания»). До этого щит за вражеский каст приходил ПОСЛЕ того, как урон того же каста применён: заявление лежало в общей очереди последствий, а она дренится после коммита. ЛовитсяAegisSliceTests.WardFromEnemyCast_AbsorbsPayloadOfThatSameCast— проверено, тест краснеет при прежнем порядке. - Порог значимости (
MinSignificantAmount) переехал в коммит: до счёта неизвестно, что от удара останется после брони и щитов, а обрезать надо именно хвост отражения. - Покрытие зеркала держалось на удаче состава: Антимаг в ростере первый, поэтому в скользящее окно четвёрок попадает ровно один раз, а его дуэль зелёная лишь потому, что без чужой магии накопитель не наполняется. Сторож класса теперь наливает накопитель руками.
in hits[i].Reqне компилируется (CS1612 — элемент списка не переменная): копировать элемент в локальную, потом передавать поin.
Владелец правды: TickLedger.cs (шов ResolveIncoming, DamageResolution),
CombatSimulation.cs (_announceQueue, DrainAnnouncements, ResolveIncoming), тесты
AegisSliceTests, MirrorAccumulatorTests, ParrySymmetryTests, MirrorMatchTests.