108 lines
11 KiB
Markdown
108 lines
11 KiB
Markdown
|
|
# 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 меряется на открытой арене чистым отрядом
|
|||
|
|
стрелков, где такой ситуации нет по построению.
|