104 lines
8.5 KiB
Markdown
104 lines
8.5 KiB
Markdown
|
|
# 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).
|