squad-proto/docs/04-world.md
z.kirill 4382af2322 Документация: границы движка, работа через модель, дизайн игры до лейтгейма
18-engine.md    границы Tile2D, проекции 2D, формат проекта, второй проект
  19-agent.md     инструменты --tool, скиллы, терминал в редакторе
  20-gdd.md       акты, петли, рост отряда, закон масштаба, бюджет 200 часов
  21-damage.md    семь стихий, статусы, десять реакций, формулы, триггеры
  22-loot.md      редкости, аффиксы, артефакты, зачарования, материалы
  23-endgame.md   Бездна, мутации, лидерборды, 262 ачивки

Плюс правки 01/02/07/15/16/17 под переехавшие пути и новые правила забега.

Дизайнерская часть держится на одном законе: угроза растёт квадратично, сила
отряда — линейно с затуханием, разрыв закрывается ЗНАНИЕМ, а не числами. Из
него выведены и экономика опыта (41 млн против 44 млн дохода), и то, почему
реакции считаются от глубины, а не от урона оружия, и почему в Бездне
множитель HP разрешён, а во всём авторском контенте запрещён.

.claude/commands: скиллы /tile2d, /room, /floor — правила этого движка,
которые нельзя вывести из кода. Главное записано первым: не отчитываться об
успехе без прохода --tool validate.

Числа в этих документах — вход для инструментов, а не украшение. Расхождение
между таблицей и выводом инструмента означает, что неправ документ.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 21:07:45 +03:00

5.7 KiB
Raw Blame History

04 — Мир: карта, рейкаст, коллизии, мишени

Тайлмап

engine/tilemap.h. Сетка 48×48, тайл EMPTY или WALL. Тайл = 1.0 юнита, координаты сущностей — float.

Вне карты считается стеной. Это убирает граничные проверки из рейкаста и коллизий.

Планировка уровня

Генерируется кодом, без RNG, полностью детерминированно (Tilemap::Generate).

 y 0..1    сплошная рамка (2 тайла по всему периметру)
 y 2..12   северная комната: кучка из 3 мишеней + колонны
 y 13..15  стена-разделитель на всю ширину
             проход A: x=10..11   проход B: x=30..31   (оба шириной 2 тайла)
 y 16..45  центральная арена: кучка из 5 мишеней, колонны, огрызки стен, блок 2x2
 x 12..14  западная стена-разделитель (y 20..44), проход: y=30..31
 x 38..40  восточная стена-разделитель (y 18..40), проход: y=27..28

Отряд стартует в (24, 26) — открытая арена, кучка из 5 мишеней в радиусе оружия.

Колонны и огрызки стен раскиданы по всей карте намеренно: без них не проверить ни LOS, ни обход. Три прохода шириной 2 тайла — это тест-кейс, где правило «не стрелять сквозь своих» упирается в геометрию.

Северная кучка мишеней стоит напротив прохода B (x=29.5/31.5), так что из коридора до неё есть чистая линия. Без этого проверить критерий «в узком проходе отряд вытягивается в колонну, стреляет передний» было бы не на чем.

Кучка обязана быть в пределах дальности от середины прохода (y≈14), иначе тест-кейс проверяет пустоту. При укорочении дальности до 7.2 её пришлось подтянуть на два тайла к югу (y 5.58.5 → 7.510.5): ближняя мишень теперь в ~6.5 юнита. Меняешь weaponRange — пересчитывай и эти координаты.

DDA-рейкаст

Tilemap::RaycastBlocked(a, b, tHit) — Amanatides & Woo, без аллокаций. Возвращает, пересекает ли отрезок стену, и опционально параметр входа в стену [0..1].

Используется в трёх местах:

  • LaneClear — пункт «геометрия» (спек 6.3);
  • пуля — поиск столкновения со стеной за шаг;
  • оценка кандидатов решателя — там же, через LaneClear.

Коллизии

ResolveCircle(pos, r) — circle vs AABB тайла: для каждой стены в охватывающем прямоугольнике ищется ближайшая точка AABB и круг выталкивается по нормали. Три итерации — из-за углов, где выталкивание одной стеной загоняет в другую. Вырожденный случай (центр внутри тайла) выталкивается по ближайшей грани.

Скольжение вдоль стен делается в IntegrateAgent: после выталкивания из скорости вычитается только нормальная составляющая, касательная остаётся. Плюс к этому WallAvoidForce даёт касательную силу обхода — см. 05-squad-ai.md, слой 4.

CircleHitsWall(pos, r) — быстрая проверка «в стене ли», для отбраковки кандидатов.

Мишени

src/sim/target.h. 12 штук, не атакуют и сами никуда не идут.

  • Расстановка кучками: 5 (арена) + 3 (север) + 3 (запад) + 1 (восток).
  • HP 500, урон пули 12 → 42 попадания. Урон клинка 130 → 4 удара.
  • Умерла → респавн через 3 с на том же месте. Прототип работает бесконечно.

Мёртвая мишень не рисуется, не выбирается целью и не участвует в оценке покрытия.

Отдача. Удар в ближнем бою отбрасывает мишень (meleeKnockback), импульс гасится (targetKnockDamp), и мишень возвращается на своё место home (targetReturnRate). Без отдачи попадание в упор визуально не отличается от промаха. Возврат обязателен: расстановка захардкожена, и уползание мишеней поломало бы все замеры разом — респавн дополнительно ставит pos = home.

Тело. Мишень расталкивает агентов (TargetPushForce, wTargetPush): до ближнего боя агент мог стоять внутри мишени, и удар не читался вообще.