Статус: Ready — отражает код на 2026-06-19


Это гайд по фактическому коду проекта (а не план, как доки «10/12»). Цель — чтобы ты понимал каждое архитектурное решение: что за что отвечает, почему выбран один паттерн и не выбран другой. Читается от простого к глубокому.

Каждый документ раздела начинается с блока «Простыми словами», потом идёт детально. Связан с 02. Архитектура реализации — Фаза 1 (Боевое ядро), 04. Архитектура реализации — Фаза 2 (Эффекты + способности), 5. Технологический стек и архитектура.

Документы раздела

#ДокументО чём
00Обзор и карта кода (этот файл)Слои, поток данных, где что лежит, порядок чтения
0101. Dependency Injection и событияVContainer, скоупы, инъекция, MessagePipe vs C#-события
0202. Детерминированная симуляция и тикТик 30 Гц, аккумулятор, очередь команд, RNG, checksum, пауза
0303. Данные, статы и уронSO-контент, StatType, слоистые модификаторы, пайплайн урона
0404. Эффекты и способностиEffectData + компоненты, стаки, диспел, контроль, реактивность
0505. Презентация и интерполяцияРаздел сим/визуал, UnitView, сглаживание 30→60
0606. Сетевая модель — решение и планHost-authoritative vs lockstep, что запарковано, главная таска MP
0707. Техдолг, решения и changelogОтложенное, исправленное, открытые вопросы

Простыми словами

Игра внутри разделена на две независимые половины:

  1. «Мозг» (симуляция) — чистый C#-код, который считает бой по тикам: кто куда идёт, кто кого бьёт, сколько HP осталось. Он ничего не знает про картинку, звук и Unity-сцену. Его можно запустить «вслепую» (headless) в тесте и проверить, что при одном и том же входе он всегда даёт один и тот же результат.

  2. «Тело» (презентация) — Unity-объекты (MonoBehaviour, спрайты, HP-бары, цифры урона), которые читают состояние мозга и рисуют его. Тело никогда не меняет бой — только показывает.

Между ними — тонкий провод из событий. Мозг говорит «юнит №5 получил 30 урона», тело рисует цифру «30» и трясёт камеру.

Почему так, а не «по-юнитёвому» (каждый юнит — MonoBehaviour, двигает себя в Update)? Потому что игра кооперативная по сети и с паузой. Чтобы у двух игроков бой шёл одинаково (или чтобы пауза/реплей работали), нужна предсказуемая, отделённая от рендера симуляция. «По-юнитёвый» подход здесь упирается в стену синхронизации. Подробно — в 02. Детерминированная симуляция и тик и 06. Сетевая модель — решение и план.


Слои и сборки (Assembly Definitions)

Код разбит на сборки (.asmdef), и зависимости идут строго вниз. Это не косметика: компилятор физически запрещает нижнему слою знать про верхний, поэтому «мозг» не может случайно потянуть за собой Unity-UI.

Core                      — фундамент: RNG, константы симуляции, интерфейсы
 ├─ Data        → Core           — SO-данные, статы (ScriptableObject, структуры)
 ├─ Combat      → Core, Data     — «мозг»: симуляция, системы, эффекты, урон
 ├─ Net         → Core, Combat   — сетевой слой (NGO)
 ├─ Presentation→ Core,Data,Combat — «тело»: визуал, читает сим
 └─ Game        → знает всех     — composition root: DI-скоупы, bootstrap, флоу

Game — единственный, кто видит все слои сразу: он их собирает (composition root). Всё остальное зависит только вниз. Карта сборок целиком — 1. Сборки (Assembly Definitions).

Важный приём — кросс-сборочный шов эффектов. Data лежит ниже Combat, но EffectDataData) должна хранить поведение эффектов, которое оперирует боевым состоянием (в Combat). Прямая ссылка Data → Combat запрещена графом. Решение: в Data объявлен пустой маркер-интерфейс IEffectComponent, а реальные хуки (OnApply/OnTick/OnExpire) — в Combat через производный IRuntimeEffectComponent : IEffectComponent. Поле [SerializeReference] IEffectComponent[] в EffectData хранит Combat-типы, не создавая зависимости вверх. Детали — 04. Эффекты и способности.


Поток данных: от запуска до удара

[Bootstrap]  GameBootstrap (MonoBehaviour в CoreScene)
     │  Start() → GameFlow.BootAsync()
     ▼
[Сцены]      SceneLoader грузит BattleScene аддитивно к persistent CoreScene
     │
     ▼
[DI]         RootLifetimeScope (сессия)  ──родитель──►  CombatLifetimeScope (бой)
     │         RNG, Audio, SceneLoader,                  battle-RNG, все системы,
     │         GameFlow, MessagePipe                     CombatSimulation, презентеры
     ▼
[Старт боя]  BattleSetupBuilder создаёт RuntimeUnit'ы из SO → EnqueueUnitSpawn
     │
     ▼
[Пульс]      CombatLoopService (IAsyncStartable) копит Time.deltaTime и
     │         вызывает CombatSimulation.Tick() фиксированными шагами 30 Гц
     ▼
[Тик]        Targeting → Abilities → Movement → SpatialHash → AutoAttack →
     │         Projectiles → Regen → Effects → DrainEvents → Death → CheckOutcome
     ▼
[События]    CombatSimulation шлёт C#-события (OnDamageDealt, OnUnitDied…)
     ▼
[Презентация] CombatPresenter ловит их, двигает UnitView, ретранслирует в
               MessagePipe → Audio/VFX/UI подписываются независимо

Каждую стрелку этого потока подробно разбирают документы 01–05.


Карта классов: кто за что отвечает

Core (фундамент)

КлассОтветственность
IRngService / XorShiftRngДетерминированный генератор случайностей боя. Любой рандом — только через него
SimConstantsЧастоты сим/AI, TickDelta, анти-лавина-кап. Единый источник правды по таймингам
ISimCommand / ISimEventБазовые контракты команд/событий симуляции

Data (контент)

КлассОтветственность
RelicData, VesselDataSO «Чемпион»/«Пилот»: стат-блоки, эффекты, способности (иммутабельный контент)
EffectData, AbilityDataSO определения эффекта/способности + полиморфные компоненты поведения
StatsConfigГлобальные дефолты статов и тюнинг-константы (armor-K, клампы)
StatType, StatModifier, ModifierOp, ScalableValueСтат-система: перечисление статов, модификаторы, скейл от статов

Combat (мозг)

КлассОтветственность
CombatSimulationСердце: тик-цикл, реализует ICombatContext, очередь команд, события, checksum
ICombatContextШов: единственная точка мутаций боя для систем и компонентов эффектов
RuntimeUnitРантайм-юнит на один бой (POCO, без MonoBehaviour)
StatsСлоистые модификаторы + кэш итоговых значений
*System (Targeting/Movement/AutoAttack/Projectile/Death/Regen/Ability)По одной системе на аспект тика
EffectSystemЖизненный цикл эффектов: наложение, стаки, тик, диспел, контроль
DamagePipelineЧистый расчёт урона (статические функции)
SpatialHashБесалокационные пространственные запросы
RuntimeUnitFactoryЕдинственная точка сборки RuntimeUnit из SO
ICombatCommand + Commands/*Мутации боя на границе тика (Pause/Resume/SpawnUnit)

Game (composition root)

КлассОтветственность
GameBootstrapТочка входа: поднимает DI, зовёт GameFlow.BootAsync
RootLifetimeScope / CombatLifetimeScopeDI-скоупы сессии и боя
GameFlowМакро-флоу: Boot → BattleScene → результат
CombatLoopServiceРеалтайм-пульс: Time.deltaTime → фиксированные тики
BattleSetupBuilderНачальная расстановка юнитов

Presentation (тело)

КлассОтветственность
CombatPresenterМост сим→визуал: подписка на события, спавн вью, ретрансляция в MessagePipe
UnitView, HealthBarView, DamageNumber(Spawner)Отображение и интерполяция

Net (сеть)

КлассОтветственность
NetworkCommandRelayРеле команд клиент→хост (концепт keeper при host-authoritative)
FacepunchTransportBootstrapSteam-транспорт под NGO
_Parked/SimSyncProbe⛔ Запаркован (lockstep-инструмент, см. 06. Сетевая модель — решение и план)

В каком порядке читать

  1. Этот обзор — общая карта.
  2. 01. Dependency Injection и события — как части находят друг друга (ты просил DI отдельно).
  3. 02. Детерминированная симуляция и тик — сердце, без которого остальное не имеет смысла.
  4. 03. Данные, статы и урон04. Эффекты и способности — содержимое боя.
  5. 05. Презентация и интерполяция — как это видно.
  6. 06. Сетевая модель — решение и план — почему вся эта строгость, и куда идём по кооперативу.
  7. 07. Техдолг, решения и changelog — что отложено и почему.