squad-proto/docs/09-acceptance.md
z.kirill 7880f275bd Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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>
2026-08-03 13:00:49 +03:00

11 KiB
Raw Blame History

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