squad-proto/docs/06-debug-and-tuning.md
z.kirill 7880f275bd Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (raylib только окно, ввод и финальный блит).

Ядро (спек, раздел 6):
- LaneClear: проверка линии огня по своим, стенам и дальности;
- решатель огневых позиций с гистерезисом и коммитом;
- резервирование линий огня и расступание из чужих секторов;
- пули-снаряды, дружественный урон физически возможен.

Хоррор-слой (render-only): свет и туман войны с памятью карты,
светящиеся трассеры, кровь, виньетка, зерно, напряжение.

Ближний бой: сектор удара, три фазы, связка из трёх ударов,
своя дисциплина «не бить сквозь своего».

Проверка: squad_proto.exe --accept прогоняет критерии приёмки
раздела 11 плюс блок M7 по ближнему бою — все PASS,
FRIENDLY_HITS = 0 за 3 минуты боя.

Сборка: cmake -S . -B build && cmake --build build --config Release
raylib 6.0 подтягивается через FetchContent.

Документация — CLAUDE.md как оглавление, содержание в docs/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 13:00:49 +03:00

151 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 06 — Оверлей, метрики, тюнинг
## Оверлей
[src/debug/overlay.cpp](../src/debug/overlay.cpp). Рисуется примитивами raylib **поверх**
уже отмасштабированного кадра. Это единственное исключение из «вся сцена софтверная»,
и оно dev-only: `F1` его убирает.
| Клавиша | Что показывает |
|---|---|
| `F1` | оверлей целиком |
| `F2` | линии огня |
| `F3` | кандидаты последнего прогона решателя |
| `F4` | метрики |
| `F5` | выключить хоррор-слой целиком (свет, туман, пост) |
| `F6` | показать диски обзора |
Всегда (при включённом `F1`): якорь отряда, **два** круга когезии (синий —
стрелков, оранжевый — клинков), слоты строя, подпись над каждым агентом
`индекс, класс (R/M), состояние (A/H), id цели` и у клинка фаза удара
(`wind` / `HIT` / `rec`), выбранная огневая позиция `postPos` кружком.
**Сектор клинка**: кольцо досягаемости и две границы дуги, цвет по фазе —
жёлто-оранжевый на замахе, белый на ударе, серый на восстановлении. В покое цвет
говорит, почему не бьём: жёлтый — свой в дуге, синий — просто ждёт.
**Цвета линий огня (`F2`)**:
| Цвет | Значение |
|---|---|
| зелёный | линия чиста, агент стреляет |
| жёлтый | цель есть, линию перекрыл свой |
| красный | линию перекрыла стена |
**Кандидаты (`F3`)**: точками, цвет по оценке (синий низкая → белый высокая),
отбракованные — тусклым красным, выбранный обведён белым кругом.
Показывается последний прогон решателя, поэтому у агентов, у которых линия есть,
там пусто — решатель для них не запускался, так и задумано.
В ручном режиме дополнительно рисуется вектор прицела, границы конуса ±20°
и обводка назначенной цели.
## Метрики (`F4`)
[src/debug/metrics.h](../src/debug/metrics.h).
| Метрика | Как считается | Смысл |
|---|---|---|
| **FRIENDLY_HITS** | накопительный счётчик попаданий пуль по своим | главная метрика корректности, должна быть 0 |
| **FIRE UPTIME** | доля шагов с валидной линией за окно 10 с; среднее — **только по стрелкам** | насколько отряд «в деле» |
| **MELEE reach** | доля шагов с целью в зоне удара, среднее по клинкам | то же самое для ближнего боя |
| `swings / hits / aborted` | взмахов начато, попаданий лезвием, взмахов оборвано своим в дуге | `aborted` — видимая цена скученности, не баг |
| **SETTLE TIME** | от перехода в `HOLDING` до момента, когда ≥4 агента «в бою» | как быстро расходится веером |
| fps, `sim ms/frame` | время симуляции за кадр | бюджет 16.6 мс |
«В бою» для стрелка — валидная линия, для клинка — выбранная цель. Смешивать
доли времени двух разных занятий в одно среднее бессмысленно, поэтому
`FIRE UPTIME` и `MELEE reach` считаются раздельно: у клинка линии огня не бывает
по определению, и его ноль занижал бы метрику стрельбы.
Реализация `FIRE UPTIME` — кольцевой буфер на 600 сэмплов на агента с
инкрементальными счётчиками, пересчёт O(1) за шаг.
**Журнал попаданий по своим**: каждое попадание пишется в `Metrics::hitLog`
(время, стрелок, жертва, координаты, состояние жертвы). Без него `FRIENDLY_HITS != 0`
нечем объяснить — именно журнал показал, что жертва всегда была `ADVANCING`,
и вывел на настоящую причину (см. [08-decisions.md](08-decisions.md)).
### Как читать цифры
- `FIRE UPTIME` считает «нет живой цели в радиусе» как простой. Поэтому на
переходах между комнатами и после зачистки кучки метрика честно падает — это не
дефект ИИ. Критерий приёмки 11.8 относится к **стоячему бою рядом с мишенями**.
- `SETTLE TIME` по той же причине растёт, если отряд остановился там, где стрелять
не по чему. Сценарий `--accept` меряет его в правильной обстановке.
## Runtime-тюнинг
Все числа живут в [src/tuning.h](../src/tuning.h) в структуре `Tuning`.
Магических констант по коду нет — если нужно новое число, оно добавляется туда.
Часть параметров вынесена в таблицу `TUNABLES` и крутится на ходу:
- `[` / `]` — выбрать параметр, текущее имя и значение видно в оверлее;
- `-` / `=` — изменить с шагом параметра (с зажатием работает автоповтор).
Те же параметры можно задать из командной строки без пересборки:
```bash
squad_proto.exe --headless 300 --set safetyMargin=0.25 --set laneReserve=2.0
```
Список параметров смотри прямо в `TUNABLES` — там имя, поле, шаг и границы.
Самые содержательные для ощущения:
| Параметр | На что влияет |
|---|---|
| `safetyMargin` | запас к «телу в линии»; шире → реже стреляют, но безопаснее |
| `laneReserve` | во сколько раз резерв линии шире порога блокировки; отвечает за «текучесть» |
| `wLaneAvoid` | насколько нагло агент выдавливается из чужого сектора |
| `hysteresis` | насколько лучше должна быть новая позиция, чтобы агент переехал |
| `commitTime` | сколько держится решение; меньше → живее и дёрганее |
| `sDistCost` / `sCohesionCost` | готовность отбегать далеко ради выстрела |
| `postLeash` | жёсткая граница огневой позиции от диска; ниже — отряд плотнее |
| `anchorSpeed` / `anchorLeash` | отзывчивость диска и насколько он может оторваться от отряда |
Ближний бой (подробности — [11-melee.md](11-melee.md)):
| Параметр | На что влияет |
|---|---|
| `meleeReach` / `meleeArc` | размер сектора удара; шире → чаще задевает нескольких, но и своих |
| `meleeWindup` / `meleeStrike` / `meleeRecover` | ритм удара: читаемость замаха против скорострельности |
| `meleeInterval` / `meleeDamage` | DPS клинка |
| `meleeLunge` / `meleeKnock` | вес удара: рывок бойца и отдача мишени |
| `meleeCohesion` | СВОЙ, больший круг клинка вокруг диска |
| `meleeAllyMargin` / `meleePredict` | строгость дисциплины сектора; выше → меньше оборванных взмахов и меньше взмахов вообще |
| `meleeReserve` | насколько широко клинок отжимает своих со своей дуги |
| `meleeShake` | отдача камеры на попадание лезвием |
Хоррор-часть (подробности — [10-atmosphere.md](10-atmosphere.md)):
| Параметр | На что влияет |
|---|---|
| `sightRadius` / `sightCone` | как далеко и как широко видит отряд |
| `sightPersonal` | круговой ореол вокруг агента, спасает от клаустрофобии |
| `fogMemory` | насколько тускло помнится уже увиденное |
| `fogReveal` | при каком свете показывается тело мишени |
| `lightGamma` / `lightGain` | форма затухания света; **перепекают таблицу рампы** |
| `flashPower` | насколько вспышка выстрела освещает окружение |
| `fxIntensity` / `tracerLength` | яркость и длина трассеров |
| `grainTense` / `vigDepthTense` | зерно и виньетка на пике напряжения |
| `dreadRadius` | с какой дистанции мишень начинает пугать |
| `shakePerShot` | отдача камеры |
`F5` — аварийный тумблер: мгновенно возвращает прежний кадр без света и тумана.
Полезен, когда непонятно, баг это или просто темно.
## Dev-тумблеры ядра
`--no-solver` и `--no-laneavoid` выключают решатель и расступание. Это A/B-проверка
ценности ядра, цифры за 60 с на одном маршруте:
| Конфигурация | FRIENDLY_HITS | FIRE UPTIME |
|---|---|---|
| ядро выключено | 1 | 13.6 % |
| ядро включено | 0 | 88 % (на стоянке у мишеней) |
`sim`/`render` в оверлее показывают время шага симуляции и время кадра рендера
раздельно: 2 мкс против 1.43.5 мс при бюджете 16.6 мс.