Решение принято до 19.06.2026, точная дата не зафиксирована; запись извлечена из замороженного
20-explanation/simulationпри переносе «почему» в журнал.
Решили: внешние воздействия на бой идут командой с целевым тиком (EnqueueCommand в очередь,
отсортированную по TargetTick; ApplyDueCommands в начале тика применяет всё с TargetTick ≤ currentTick), а не прямым вызовом вида sim.Pause().
Почему: команда с тиком означает «применить ровно на этом шаге». Клиент шлёт намерение, хост назначает тик, применение детерминированно — это фундамент кооператива и реплеев. Прямой вызов применился бы «когда дошло», то есть на разном тике у разных машин, и воспроизводимость забега по сиду сломалась бы. Командный слой остался keeper и при выборе host-authoritative — меняется только сетевая обвязка вокруг него, см. Journal - Host-Authoritative, Not Lockstep.
Грабли — пауза, самое тонкое место всей модели. Два неочевидных правила, и оба обязательны:
- Пауза, поданная на тике N, вступает в силу со следующего тика: текущий досимулируется целиком.
- Пока бой на паузе,
currentTickпродолжает идти, если в очереди есть команды. Иначе будущийResumeCommandсо своимTargetTickникогда не наступит и бой зависнет навсегда.
Второе выглядит как баг («на паузе время идёт?») и его тянет «починить». Не чинить.
Владелец правды: Assets/_Project/Scripts/Combat/CombatSimulation.cs — метод Tick(), где эта
дисциплина закомментирована по месту; трогаешь паузу — читаешь те комментарии.