Время как первоклассный ресурс

Время — главный ресурс игры наравне с HP, MP и деньгами. Объясняет, как оно устроено сейчас (дни) и куда движется (тики), и почему per-actor clock меняет всё.

«Вся игра про время. Про то, как время утекает.»

В west-marches формате с асинхронным игровым временем у каждого персонажа свой «сейчас». Один PC ведёт данж в день 5 петли; другой торгует в городе в день 12. Одновременно, в реальном времени. Система должна это поддерживать, а не притворяться, что все находятся в одной точке.


Что есть сейчас: (loop_number, day_in_loop)

В проде временно́й якорь — два целых числа:

ПолеТаблицаСмысл
loop_numbertransactions, chroniclesкакая петля
day_in_looptransactionsдень внутри петли (1..length_days)
day_from, day_tonodes.fields у session-нодыдиапазон дней сессии
length_daysnodes.fields у loop-нодыдлина петли, дефолт 30

Это грубая модель: день — атомарная единица. Нельзя сказать «событие произошло в 3 часа дня». Для текущего use-case — достаточно; для движка с модификаторами времени суток — нет.

Целевая модель: тики

HANDOFF-архитектура вводит Campaign.tick_unit_seconds — длину тика в секундах, задаётся per-кампания. Для «Мать Учения» — 6 секунд (один раунд боёвки D&D 5e). Время в БД хранится как at_tick: int от точки отсчёта кампании.

Зачем целые числа, а не datetime/timestamp?

  • Временны́е сравнения — простые операции над int, без timezone-магии.
  • Один at_tick перекрывает весь масштаб: раунд боёвки (6с), дневной переход (86 400с / 6 = 14 400 тиков), месяц — всё одним числом.
  • Конвертация в человекочитаемый формат («День 5, Час 22») — presentation layer, не хранимые данные. Разные кампании с разными tick_unit_seconds читают один и тот же формат хранения.

Per-actor clock

У каждого персонажа свой current_tick (в коде сейчас — нет явного поля, time_position хранится неявно через последнее событие). В целевой модели это Actor.current_tick — в HANDOFF-терминологии Actor = всё, что имеет clock. В коде сейчас: PC-нода (node_type='pc' / 'character'); NPC — обычная нода без clock.

Это и есть асинхронное время: когда DM записывает транзакцию, он указывает day_in_loop конкретного PC, а не «сегодняшний день кампании». Два персонажа могут существовать в разных «сейчас» одновременно.

Длинные действия

В целевой модели: «тренируюсь магии 3 месяца» — один event со start_tick и duration_ticks, не итерация миллиона тиков. Resolver знает, что это действие занимает диапазон и блокирует actor'а на это время.

Сейчас: длинные действия выражаются хроникой или комментарием к транзакции, без формальной модели duration.

Модификаторы стоимости

Целевая архитектура: стоимость любого действия = base_cost × modifier_stack. Стек применяется в фиксированном порядке: terrain × weather × time-of-day × travel-mode × condition. UI показывает разложенную цену до принятия решения.

Сейчас: стоимости неявные, DM считает в голове. Modifier stack и weather resolver — roadmap.

roadmap/time-and-modifiers.md, roadmap/tick-time-model.md


Переход от (loop_number, day_in_loop) к (loop_id, at_tick) — одна из крупнейших миграций в engine pivot. Затрагивает практически все таблицы. Риски и план — в roadmap/tick-time-model.md.