Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (raylib только окно, ввод и финальный блит). Ядро (спек, раздел 6): - LaneClear: проверка линии огня по своим, стенам и дальности; - решатель огневых позиций с гистерезисом и коммитом; - резервирование линий огня и расступание из чужих секторов; - пули-снаряды, дружественный урон физически возможен. Хоррор-слой (render-only): свет и туман войны с памятью карты, светящиеся трассеры, кровь, виньетка, зерно, напряжение. Ближний бой: сектор удара, три фазы, связка из трёх ударов, своя дисциплина «не бить сквозь своего». Проверка: squad_proto.exe --accept прогоняет критерии приёмки раздела 11 плюс блок M7 по ближнему бою — все PASS, FRIENDLY_HITS = 0 за 3 минуты боя. Сборка: cmake -S . -B build && cmake --build build --config Release raylib 6.0 подтягивается через FetchContent. Документация — CLAUDE.md как оглавление, содержание в docs/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
11 KiB
09 — Критерии приёмки
Раздел 11 спека. Воспроизвести: build/bin/squad_proto.exe --accept.
Сценарии — 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), сценарий 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).
M7.2 держится не тюнингом, а конструкцией: урон применяется только к мишеням, а влезший под лезвие союзник обрывает взмах (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): отряд в пятне света,
у каждого своя зелёная линия, ни одна не проходит через своего, трассеры в полёте,
дальние мишени видны только тлеющими глазами. FRIENDLY_HITS 0.
Оговорки, которые стоит знать
- Критерий 1 и метрика
SETTLE TIMEосмысленны только когда в радиусе есть живые мишени. В общем маршруте попадаются значения до 5.7 с — это остановки там, где стрелять не по чему, а не медленное расхождение. - Критерий 8 меряется в стоячем бою рядом с кучкой. При остановке на краю дальности честные значения ниже: часть агентов физически не достаёт цель. После сокращения дальности до 7.2 худшая стоянка маршрута даёт ~30 % — это остановка там, где кучка уже вне радиуса, а не деградация решателя.
FRIENDLY_HITS = 0достигается связкой из четырёх вещей: резерв линий, включая перекрытые своим; резерв шире порога блокировки; учёт времени полёта на спуске; пуля не летит дальше проверенного отрезка. Отключение любой из них возвращает попадания по своим — цифры в 08-decisions.md.FIRE UPTIMEусредняется только по стрелкам,MELEE reach— только по клинкам. Смешивать доли времени двух разных занятий в одно среднее бессмысленно.- «Макс. время в чужой линии» на смешанном отряде доходит до 3.9 с против 1.3 с на чистых стрелках. Это боец с клинком, который стоит вплотную к мишени и тем самым перекрывает стрелку линию: расступиться он не может, не бросив свою работу. Правило при этом не нарушается — стрелок просто не стреляет и перерешивает позицию. Критерий 11.4 меряется на открытой арене чистым отрядом стрелков, где такой ситуации нет по построению.