Решили: статистику профиля наполняют двое — PlayTimeTracker в корневом скоупе (наигранное) и
RunStatsRecorder в сеансовом (итог забега). Итог пишется по ОБЪЯВЛЕННОМУ шагу узла
(ISessionStageView → Outcome), а не из петли акта.
Почему: петля акта есть только у владельца. Гость проходит тот же акт, и если писать из петли, в его профиле не прибавится ничего — а разницу между ролями никто не заказывал (HARD-правило «равные игроки»). Шаг узла видят обе роли по построению: по нему им и показывается экран исхода. Тем же выбором закрывается брошенный забег — уход в меню отменяет токен занятия, шаг исхода не объявляется вовсе, и в статистику ничего не попадает.
Сказано: «Всё время с запущенной игрой» и «Акт пройден до конца» (21.08.2026, сессия 26182ec4) — про то, что считать наигранным и что считать победой.
Грабли:
- Список профилей читается с диска, а правда активного живёт в памяти. Наигранное копится в
объекте профиля и уезжает в файл раз в минуту; сводки в списке собраны чтением каталога при
запуске. Пока сводка активного не пересчитывалась, экран показывал ноль наигранного у того, кто
играет второй час. Пойман живым прогоном, а не тестом: тест на «пишется ли» был бы зелёным.
Держит теперь
ProfileServiceTests.PlayedTime_ShowsUpInTheListImmediately. - Стартовый узел помечен пройденным при генерации карты. Простой счёт
Clearedзавышал бы длину любого забега на единицу — включая брошенный на первом шаге, где пройдено ноль. Счёт живёт у владельца топологии (MapTraversal.ClearedCount) и отбрасывает нулевой этаж. - Шаг узла — состояние, а не событие. Хозяин объявляет его повторно тому, кто вошёл посреди забега, поэтому запись идёт на ПЕРЕХОДЕ в исход: без этого переподключение на экране исхода добавляло бы профилю ещё один забег на каждый повтор.
Владелец правды: Assets/_Project/Scripts/Game/Session/RunStatsRecorder.cs,
Assets/_Project/Scripts/Game/Services/PlayTimeTracker.cs, тесты ProfileServiceTests,
MapTraversalTests.ClearedCount_IgnoresStart_AndGrowsWithEachStep.