Решили: команда InflictInjury несёт сид ролла отдельным полем; применитель роллит рану от
него, а не из потока IRngService забега.
Почему: ступень раны зависит от занятых слотов, то есть от состояния, — значит каскад обязан считаться у применителя, и ролл вместе с ним. Но возьми применитель общий поток RNG забега, результат зависел бы от того, сколько раз этот поток дёрнули ДО команды: у хозяина и гостя порядок чужих действий разный, и раны разъехались бы молча — поймалось бы это только рассинхроном через десяток узлов. Сид в команде делает применение самодостаточным: лог воспроизводится один в один сам по себе.
Отвергнуто: роллить рану у публикующей стороны и слать готовый id (ступень тогда неизвестна — она
следствие каскада по состоянию); дать применителю сам IRngService (см. выше).
Грабли: связь «павший юнит → слот гильдии» нигде не хранится и выводится из ПОРЯДКА СПАВНА: отряд
ставится строго в порядке RunState.Guild, поэтому порядковый номер юнита среди team-0 и есть индекс
«Сосуда». На том же инварианте уже стоит запись позиций расстановки в сейв. Две ловушки при подсчёте:
нумеровать надо все team-0 подряд, включая живых (иначе после первой потери рана уедет соседу), и
пропускать призванных (иначе скелет некроманта сдвинет нумерацию).
Отсутствие записи в реестре кооп-прав поймал гейт-тест PlayerCapabilityRegistryTests — новая команда
без строки про право не проходит. Работает как задумано.
Владелец правды: InjuryLedger.cs, BattleStartup.FallenGuildIndices, тесты InjuryLedgerTests,
docs/player-capability-registry.md.