Решение принято до 19.06.2026, точная дата не зафиксирована; запись извлечена из замороженного 20-explanation/simulation при переносе «почему» в журнал.

Решили: внешние воздействия на бой идут командой с целевым тиком (EnqueueCommand в очередь, отсортированную по TargetTick; ApplyDueCommands в начале тика применяет всё с TargetTick ≤ currentTick), а не прямым вызовом вида sim.Pause().

Почему: команда с тиком означает «применить ровно на этом шаге». Клиент шлёт намерение, хост назначает тик, применение детерминированно — это фундамент кооператива и реплеев. Прямой вызов применился бы «когда дошло», то есть на разном тике у разных машин, и воспроизводимость забега по сиду сломалась бы. Командный слой остался keeper и при выборе host-authoritative — меняется только сетевая обвязка вокруг него, см. Journal - Host-Authoritative, Not Lockstep.

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

  1. Пауза, поданная на тике N, вступает в силу со следующего тика: текущий досимулируется целиком.
  2. Пока бой на паузе, currentTick продолжает идти, если в очереди есть команды. Иначе будущий ResumeCommand со своим TargetTick никогда не наступит и бой зависнет навсегда.

Второе выглядит как баг («на паузе время идёт?») и его тянет «починить». Не чинить.

Владелец правды: Assets/_Project/Scripts/Combat/CombatSimulation.cs — метод Tick(), где эта дисциплина закомментирована по месту; трогаешь паузу — читаешь те комментарии.