# 09 — Критерии приёмки Раздел 11 спека. Воспроизвести: `build/bin/squad_proto.exe --accept`. Сценарии — [src/debug/harness.cpp](../src/debug/harness.cpp), `RunAccept()`. ## Автоматическая проверка Критерии 1, 6, 7, 8 описывают **правила стрельбы** и меряются на отряде из пяти стрелков — так, как их формулировал спек. Ближний бой проверяется отдельным блоком M7 и, главное, трёхминутным прогоном 11.2, где состав отряда обычный, боевой (2 клинка + 3 ствола). | # | Критерий | Статус | Цифры последнего прогона | |---|---|---|---| | 1 | ≥4 из 5 агентов имеют линию за ≤1.5 с после остановки | **PASS** | settle = 0.32 с, линий 5/5 | | 2 | `FRIENDLY_HITS` = 0 за 3 минуты боя во всех режимах | **PASS** | 0 попаданий, смешанный отряд | | 4 | агент с чужой линии огня уходит за ~0.3 с | **PASS** | максимум 0.35 с на арене (0.98 с по всем сценариям) | | 5 | нет дёрганья: `postPos` не чаще раза в 0.6 с | **PASS** | 36 коммитов, 0 нарушений, минимальный интервал 0.75 с | | 6 | в проходе 2 тайла — колонна, огонь не идёт сквозь своих | **PASS** | до 5 агентов в створе, поперёк 3.09 / вдоль 6.08 тайла, вперёд передний/хвост 77/9, сквозь союзника 0 выстрелов из 247 | | 7 | ручной режим никогда не стреляет сквозь своих | **PASS** | 0 попаданий по своим за 30 с, назначенная цель 96 % времени | | 8 | `FIRE UPTIME` в стоячем бою на арене ≥ 80 % | **PASS** | 100 % | | 9 | стабильные 60 fps | **PASS** | симуляция 2.5 мкс + рендер 4.1 мс при бюджете 16.6 мс, fps 60 | Ближний бой ([11-melee.md](11-melee.md)), сценарий 30 с у кучки мишеней: | # | Критерий | Статус | Цифры | |---|---|---|---| | M7.1 | клинок доходит до цели и наносит урон | **PASS** | 55 взмахов, 52 попадания, 16 убийств, 14 оборвано своими; связка `slash 21 / backhand 19 / spin 15` | | M7.2 | ближний бой не задевает своих | **PASS** | `FRIENDLY_HITS = 0` при 55 взмахах | | M7.3 | у каждого класса свой круг вокруг диска | **PASS** | стрелки не дальше 3.49 от диска, клинки — 5.61 (лимиты 3.70 / 6.80) | | M7.4 | лезвие не достаёт за пределы своей дуги | **PASS** | превышение сектора 0.0° (лимит 12°) | M7.4 появился после починки геометрии удара: угловой допуск `asin(r/dist)` вырождался на контактной дистанции в 90°, и боец рубил за спину — те самые «удары на 360 градусов». Теперь проверка идёт по отрезку лезвия, а критерий замеряет превышение разрешённого сектора и не даёт багу вернуться ([11-melee.md](11-melee.md)). M7.2 держится не тюнингом, а конструкцией: урон применяется только к мишеням, а влезший под лезвие союзник обрывает взмах ([08-decisions.md](08-decisions.md), A16). Прогон 300 с (`--headless 300`), смешанный отряд: `FRIENDLY_HITS = 0`, 1471 выстрел, 142 взмаха / 146 попаданий лезвием, **63 убийства**. Тот же маршрут чистым отрядом стрелков (`--melee 0`): 2100 выстрелов, 45 убийств. Клинки заметно поднимают урон отряда, не ломая главного правила. `FIRE UPTIME` на стоянках маршрута: 82.9 / 93.2 / 90.7 / 86.0 % там, где есть мишени, и 23.3 % на стоянке, где стрелять не по чему. Тест 11.6 переписан ещё раз, и на этот раз по существу — с подсчёта «кто стрелял» на проверку инварианта. Причина была в другом месте, чем казалось. Сценарий тащил курсор непрерывно, курсор обгонял отряд, натяжение поводка упиралось в упор, включался спринт — а в спринте отряд по замыслу не стреляет вовсе. Тест показывал «0 выстрелов вперёд» и падал, проверяя пустой коридор. После поднятия `sprintAt` до 0.85 удержание направления спринт больше не включает, и отряд идёт через проход **с боем** — как и должен. Область замера тоже была неверной: проход глубиной три тайла, а бойцов пятеро, и колонна физически длиннее самого прохода — внутрь него целиком она не помещается никогда. Меряется теперь створ прохода с подходами по обе стороны, на всей проводке, а не только на стоянке. Заодно выброшен критерий «передний стреляет чаще заднего». В проходе шириной два тайла у заднего есть законные диагональные линии мимо переднего, и требовать их отсутствия — значит требовать от отряда быть глупее, чем он есть. «Задние не стреляют» — это следствие правила «не сквозь своего», а не отдельное правило. Поэтому проверяется сам инвариант: берётся отрезок **реально сделанного** выстрела и независимой геометрией проверяется, не задел ли он тело союзника. Результат: 0 из 247 выстрелов прошли сквозь своего, колонна 6.08 тайла вдоль против 3.09 поперёк, и передний всё равно ведёт огонь с перевесом 77 против 9 — теперь это наблюдение, а не условие прохождения. ## Проверяется глазами / по коду | # | Критерий | Как подтверждено | |---|---|---| | 3 | в движении стреляют только те, у кого чистая линия | по построению: спуск требует `hasLane`; в оверлее `F2` видно, что часть агентов идёт с жёлтой линией и молчит | | 10 | вся сцена нарисована своим растеризатором | в `render/` нет ни одного raylib-примитива; raylib вызывается только в `ScreenBlitter` (`UpdateTexture` + `DrawTexturePro`) и в `debug/overlay.cpp`, который dev-only и снимается по `F1` | Скриншот боевого кадра ([docs/combat.png](combat.png)): отряд в пятне света, у каждого своя зелёная линия, ни одна не проходит через своего, трассеры в полёте, дальние мишени видны только тлеющими глазами. `FRIENDLY_HITS 0`. ## Оговорки, которые стоит знать - Критерий 1 и метрика `SETTLE TIME` осмысленны только когда в радиусе есть живые мишени. В общем маршруте попадаются значения до 5.7 с — это остановки там, где стрелять не по чему, а не медленное расхождение. - Критерий 8 меряется в стоячем бою рядом с кучкой. При остановке на краю дальности честные значения ниже: часть агентов физически не достаёт цель. После сокращения дальности до 7.2 худшая стоянка маршрута даёт ~30 % — это остановка там, где кучка уже вне радиуса, а не деградация решателя. - `FRIENDLY_HITS = 0` достигается связкой из четырёх вещей: резерв линий, включая перекрытые своим; резерв шире порога блокировки; учёт времени полёта на спуске; пуля не летит дальше проверенного отрезка. Отключение любой из них возвращает попадания по своим — цифры в [08-decisions.md](08-decisions.md). - `FIRE UPTIME` усредняется только по стрелкам, `MELEE reach` — только по клинкам. Смешивать доли времени двух разных занятий в одно среднее бессмысленно. - «Макс. время в чужой линии» на смешанном отряде доходит до 3.9 с против 1.3 с на чистых стрелках. Это боец с клинком, который стоит вплотную к мишени и тем самым перекрывает стрелку линию: расступиться он не может, не бросив свою работу. Правило при этом не нарушается — стрелок просто не стреляет и перерешивает позицию. Критерий 11.4 меряется на открытой арене чистым отрядом стрелков, где такой ситуации нет по построению.