squad-proto/docs/06-debug-and-tuning.md
z.kirill e3294cffd4 Личинки, парирование и инструмент --wall (M30)
--WALL. Новый прогон отвечает на вопрос, появившийся вместе с чекпоинтами
через пятьдесят этажей: растёт ли достижимая глубина от вылазки к вылазке.
Отряд из одного, трёх и пяти, одетый по глубине, ставится в скопление
тварей и живёт полторы минуты без управления. Первая стена: одиночка D7,
трое D30, пятеро D30 - петля работает.

И он же нашёл настоящую ошибку: PlaceSquad воскрешал ВСЕ пять слотов, в
том числе незанятые. Строка писалась, когда отряд всегда был впятером;
после появления ростера одиночка выходил на этаж с четырьмя невидимыми
телохранителями. Из-за них прежний замер "одиночка выжил на первом этаже,
100 -> 41" был боем впятером.

ЛИЧИНКИ. С исправленным замером выяснилось, что одиночка с ножом
проигрывает двум бредущим: 210 здоровья против ножа это девять секунд
работы под ответным уроном. Первые этажи населены личинками (55 здоровья,
7 урона, медленные), бредущий уехал на третий этаж, бегун на четвёртый.
Новый ВИД, а не ослабленный бредущий с множителем: глубина в этой игре
меняет, кто выходит, а не числа у одного и того же.

ПАРИРОВАНИЕ. Отдельная характеристика клинка, и чем оружие легче, тем она
выше: нож 34%, сабля 20%, тесак 8%. Не выведена из веса намеренно - её
двигает аффикс PARRY, и лёгкий тесак обязан быть возможен как находка.

Отбить можно, только если удары СОПРИКОСНУЛИСЬ: боец сам должен быть в
замахе или в ударе. Иначе клинок стал бы щитом, работающим, пока стоишь.

Удавшийся отбив снимает урон, бьёт тварь ударом бойца целиком и запускает
повторную атаку с начала фазы, НЕ сбрасывая откат. Последнее и есть суть
размена: сбрасывай отбив откат, выгоднее было бы подставляться под удары.

Плевок отбивается тоже: летит назад и несёт урон бойца, а не твари. Это
его удар, просто по летящей цели.

Проверка меряет размен целиком, включая обратную половину правила:
"стоя столбом не отбивает". Без неё условие про соприкосновение
существовало бы только в комментарии.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 05:04:35 +03:00

13 KiB
Raw Permalink Blame History

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)

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).

Как читать цифры

  • 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.43.5 мс при бюджете 16.6 мс.

--wall: где обрывается забег

squad_proto.exe --rooms --wall 50

Отвечает на вопрос, который появился вместе с чекпоинтами через пятьдесят этажей: растёт ли достижимая глубина от вылазки к вылазке. Отряд из одного, трёх и пяти бойцов, одетый по глубине, ставится в самое скопление тварей и живёт там полторы минуты без управления.

первая стена: одиночка D7, трое D30, пятеро D30

Это НИЖНЯЯ оценка: игрок уводит бойцов, выбирает цели и отступает — проверка не умеет ничего из этого. Поэтому «стена» здесь не конец игры, а глубина, с которой игра начинает требовать умения.

Смотреть надо на первую стену, а не на «глубже всего дожили»: на глубоких этажах твари разбросаны, и отряд иногда доживает просто потому, что до него дошли не все.

Этот же прогон нашёл настоящую ошибку: PlaceSquad воскрешал ВСЕ пять слотов, в том числе незанятые. Строка писалась, когда отряд всегда был впятером, и после появления ростера одиночка выходил на этаж с четырьмя невидимыми телохранителями. Из-за них прежний замер «одиночка на первом этаже» показывал уверенную победу — это был бой впятером.