# 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.4–3.5 мс при бюджете 16.6 мс. ## `--wall`: где обрывается забег ```bash squad_proto.exe --rooms --wall 50 ``` Отвечает на вопрос, который появился вместе с чекпоинтами через пятьдесят этажей: **растёт ли достижимая глубина от вылазки к вылазке**. Отряд из одного, трёх и пяти бойцов, одетый по глубине, ставится в самое скопление тварей и живёт там полторы минуты без управления. ``` первая стена: одиночка D7, трое D30, пятеро D30 ``` Это НИЖНЯЯ оценка: игрок уводит бойцов, выбирает цели и отступает — проверка не умеет ничего из этого. Поэтому «стена» здесь не конец игры, а глубина, с которой игра начинает требовать умения. Смотреть надо на первую стену, а не на «глубже всего дожили»: на глубоких этажах твари разбросаны, и отряд иногда доживает просто потому, что до него дошли не все. Этот же прогон нашёл настоящую ошибку: `PlaceSquad` воскрешал ВСЕ пять слотов, в том числе незанятые. Строка писалась, когда отряд всегда был впятером, и после появления ростера одиночка выходил на этаж с четырьмя невидимыми телохранителями. Из-за них прежний замер «одиночка на первом этаже» показывал уверенную победу — это был бой впятером.