--WALL. Новый прогон отвечает на вопрос, появившийся вместе с чекпоинтами через пятьдесят этажей: растёт ли достижимая глубина от вылазки к вылазке. Отряд из одного, трёх и пяти, одетый по глубине, ставится в скопление тварей и живёт полторы минуты без управления. Первая стена: одиночка D7, трое D30, пятеро D30 - петля работает. И он же нашёл настоящую ошибку: PlaceSquad воскрешал ВСЕ пять слотов, в том числе незанятые. Строка писалась, когда отряд всегда был впятером; после появления ростера одиночка выходил на этаж с четырьмя невидимыми телохранителями. Из-за них прежний замер "одиночка выжил на первом этаже, 100 -> 41" был боем впятером. ЛИЧИНКИ. С исправленным замером выяснилось, что одиночка с ножом проигрывает двум бредущим: 210 здоровья против ножа это девять секунд работы под ответным уроном. Первые этажи населены личинками (55 здоровья, 7 урона, медленные), бредущий уехал на третий этаж, бегун на четвёртый. Новый ВИД, а не ослабленный бредущий с множителем: глубина в этой игре меняет, кто выходит, а не числа у одного и того же. ПАРИРОВАНИЕ. Отдельная характеристика клинка, и чем оружие легче, тем она выше: нож 34%, сабля 20%, тесак 8%. Не выведена из веса намеренно - её двигает аффикс PARRY, и лёгкий тесак обязан быть возможен как находка. Отбить можно, только если удары СОПРИКОСНУЛИСЬ: боец сам должен быть в замахе или в ударе. Иначе клинок стал бы щитом, работающим, пока стоишь. Удавшийся отбив снимает урон, бьёт тварь ударом бойца целиком и запускает повторную атаку с начала фазы, НЕ сбрасывая откат. Последнее и есть суть размена: сбрасывай отбив откат, выгоднее было бы подставляться под удары. Плевок отбивается тоже: летит назад и несёт урон бойца, а не твари. Это его удар, просто по летящей цели. Проверка меряет размен целиком, включая обратную половину правила: "стоя столбом не отбивает". Без неё условие про соприкосновение существовало бы только в комментарии. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
13 KiB
06 — Оверлей, метрики, тюнинг
Оверлей
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)
| Метрика | Как считается | Смысл |
|---|---|---|
| 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).
Как читать цифры
FIRE UPTIMEсчитает «нет живой цели в радиусе» как простой. Поэтому на переходах между комнатами и после зачистки кучки метрика честно падает — это не дефект ИИ. Критерий приёмки 11.8 относится к стоячему бою рядом с мишенями.SETTLE TIMEпо той же причине растёт, если отряд остановился там, где стрелять не по чему. Сценарий--acceptмеряет его в правильной обстановке.
Runtime-тюнинг
Все числа живут в src/tuning.h в структуре Tuning.
Магических констант по коду нет — если нужно новое число, оно добавляется туда.
Часть параметров вынесена в таблицу TUNABLES и крутится на ходу:
[/]— выбрать параметр, текущее имя и значение видно в оверлее;-/=— изменить с шагом параметра (с зажатием работает автоповтор).
Те же параметры можно задать из командной строки без пересборки:
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):
| Параметр | На что влияет |
|---|---|
meleeReach / meleeArc |
размер сектора удара; шире → чаще задевает нескольких, но и своих |
meleeWindup / meleeStrike / meleeRecover |
ритм удара: читаемость замаха против скорострельности |
meleeInterval / meleeDamage |
DPS клинка |
meleeLunge / meleeKnock |
вес удара: рывок бойца и отдача мишени |
meleeCohesion |
СВОЙ, больший круг клинка вокруг диска |
meleeAllyMargin / meleePredict |
строгость дисциплины сектора; выше → меньше оборванных взмахов и меньше взмахов вообще |
meleeReserve |
насколько широко клинок отжимает своих со своей дуги |
meleeShake |
отдача камеры на попадание лезвием |
Хоррор-часть (подробности — 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: где обрывается забег
squad_proto.exe --rooms --wall 50
Отвечает на вопрос, который появился вместе с чекпоинтами через пятьдесят этажей: растёт ли достижимая глубина от вылазки к вылазке. Отряд из одного, трёх и пяти бойцов, одетый по глубине, ставится в самое скопление тварей и живёт там полторы минуты без управления.
первая стена: одиночка D7, трое D30, пятеро D30
Это НИЖНЯЯ оценка: игрок уводит бойцов, выбирает цели и отступает — проверка не умеет ничего из этого. Поэтому «стена» здесь не конец игры, а глубина, с которой игра начинает требовать умения.
Смотреть надо на первую стену, а не на «глубже всего дожили»: на глубоких этажах твари разбросаны, и отряд иногда доживает просто потому, что до него дошли не все.
Этот же прогон нашёл настоящую ошибку: PlaceSquad воскрешал ВСЕ пять слотов, в
том числе незанятые. Строка писалась, когда отряд всегда был впятером, и после
появления ростера одиночка выходил на этаж с четырьмя невидимыми телохранителями.
Из-за них прежний замер «одиночка на первом этаже» показывал уверенную победу —
это был бой впятером.