# 02 — Архитектура ## Карта модулей ``` src/ main.cpp окно, ввод, главный цикл, fixed timestep. Игровой логики нет game.h/.cpp склейка: карта + отряд + мишени + пули + метрики, порядок шага tuning.h ВСЕ числа прототипа + таблица runtime-подкрутки core/ math.h Vec2, отрезки, DistancePointToSegment, углы. Header-only input.h InputState — ввод, влияющий на симуляцию render/ view.h/.cpp ВСЁ render-only состояние + RenderGame(const Game&, ...) framebuffer.h/.cpp софтверный буфер RGBA8, блит (в т.ч. со светом), аддитивные примитивы sprites.h/.cpp процедурная генерация всех спрайтов при старте iso.h проекция мир↔экран, камера, ключ сортировки по глубине scene.h/.cpp проход по полу + общий список сущностей и стен с сортировкой lighting.h/.cpp свет и туман войны (см. docs/10-atmosphere.md) fx.h/.cpp трассеры, вспышки дула, искры попаданий post.h/.cpp напряжение (Dread) и слитый пост-проход noise.h единственное место render-only псевдослучайности world/ tilemap.h/.cpp карта, генерация уровня, DDA-рейкаст, circle-vs-AABB sim/ loadout.h класс оружия, параметры ствола и клинка, фазы удара agent.h/.cpp агент, слои движения (seek/separation/targetPush/wallAvoid) squad.h/.cpp якорь и поводок, строй, ADVANCING/HOLDING, сборка слоёв target.h/.cpp мишени, респавн, отдача от удара и возврат на место bullet.h/.cpp снаряды, коллизии с тайлами и капсулами events.h кольцевой журнал боевых событий: единственный канал к эффектам ai/ firing_solver.h/.cpp LaneClear, выбор цели, решатель огневых позиций lane_registry.h/.cpp активные линии огня и секторы клинков, импульс расступания melee.h/.cpp цель ближнего боя, подход вплотную, чистота сектора debug/ overlay.h/.cpp оверлей F1–F4 поверх кадра (единственное место с raylib-примитивами) metrics.h/.cpp FRIENDLY_HITS, FIRE UPTIME, SETTLE TIME, журнал попаданий по своим harness.h/.cpp прогоны --headless и --accept ``` Против структуры из спека добавлены: `game.h/.cpp`, `core/input.h`, `render/scene.h/.cpp`, `debug/harness.h/.cpp`, хоррор-слой (`render/view`, `lighting`, `fx`, `post`, `noise`, `sim/events.h`) и ближний бой (`sim/loadout.h`, `ai/melee.h/.cpp`). Зачем — [08-decisions.md](08-decisions.md), [10-atmosphere.md](10-atmosphere.md) и [11-melee.md](11-melee.md). ## Правила зависимостей 1. **`RenderGame` принимает `Game` по константной ссылке.** Поэтому свет, трассеры и тряска камеры физически не могут изменить симуляцию — это проверяет компилятор, а не внимательность. Всё render-only состояние живёт в `View`, которое headless-прогоны не создают вообще. 2. **`ai/` не знает ничего** про рендер, изометрию и ввод. Только `core/`, `sim/`, `world/`. 3. **`sim/` и `world/` работают в декартовом мире** (top-down), тайл = 1.0 юнита. Изометрия существует только в `render/` и в оверлее. 4. Ввод в экранных осях переводится в мировое направление ровно в одном месте — `Game::Step` через `iso::ScreenDirToWorld`. Дальше по симуляции экранных осей нет. 5. `tuning.h` не зависит ни от чего; его включают все. ## Порядок одного шага симуляции `Game::Step(dt)` — фиксированный порядок, важен именно он: | # | Что | Почему здесь | |---|---|---| | 1 | пресет строя из ввода | до всего остального | | 2 | мишени: респавн, отдача, возврат на место | состояние мира на начало шага | | 3 | якорь + поводок + состояние отряда (`ADVANCING`/`HOLDING`) | движение зависит от состояния | | 4 | **движение агентов** (слои 6.7) | реестр линий берётся с прошлого шага, задержка 1/60 с | | 5 | ручное прицеливание, назначенная цель | до выбора целей | | 6 | **оценка линий огня** и **выбор цели клинка** по новым позициям | целеуказание, `hasLane`, `melee.inReach` | | 7 | пересборка реестра: линии огня + секторы клинков | для расступания и оценки кандидатов | | 8 | решатель огневых позиций / подход клинка вплотную | решатель — только в `HOLDING` и только без линии | | 9 | доворот ствола (у клинка на замахе ось зафиксирована) | визуальная часть | | 10 | **атака**: огонь, затем взмах | позиции те же, что в п.6 | | 11 | пули | коллизии со стенами и капсулами | | 12 | метрики | снимок конца шага | Ключевое: пункты 6 и 10 разделены только доворотом ствола, позиции между ними не меняются. Поэтому отрезок, который проверил `LaneClear`, и отрезок, по которому полетела пуля, — это буквально один и тот же отрезок. Для клинка то же самое верно про сектор: проверенный в п.6/8 и рассечённый в п.10 — один и тот же. ## Детерминизм - Фиксированный шаг 1/60, накопитель времени, максимум 5 шагов за кадр. - Ни одного вызова RNG в симуляции. Уровень и расстановка мишеней захардкожены. - Вырожденные случаи разрешаются по индексу слота, а не случайно (например, расталкивание двух агентов, стоящих в одной точке). - Камера обновляется в рендере по реальному времени кадра и на симуляцию не влияет. Поэтому `--headless` воспроизводит один и тот же прогон и годится как тест. ## Рендер и симуляция `RenderGame(const Game&, View&, ...)` не может изменить симуляцию — это гарантирует подпись. Позиции агентов и пуль интерполируются `Lerp(prevPos, pos, alpha)`, камера сглаживается по `frameDt` и округляется до целого пикселя, чтобы картинка не дрожала. Единственный канал в обратную сторону — кольцевой журнал `sim/events.h`: симуляция пишет в него боевые события, эффекты читают. Правила его не читают, в headless передаётся `nullptr`. Подробности — [10-atmosphere.md](10-atmosphere.md).