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

81 lines
5.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 04 — Мир: карта, рейкаст, коллизии, мишени
## Тайлмап
[engine/tilemap.h](../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](05-squad-ai.md), слой 4.
`CircleHitsWall(pos, r)` — быстрая проверка «в стене ли», для отбраковки кандидатов.
## Мишени
[src/sim/target.h](../src/sim/target.h). 12 штук, не атакуют и сами никуда не идут.
- Расстановка кучками: 5 (арена) + 3 (север) + 3 (запад) + 1 (восток).
- HP 500, урон пули 12 → 42 попадания. Урон клинка 130 → 4 удара.
- Умерла → респавн через 3 с на том же месте. Прототип работает бесконечно.
Мёртвая мишень не рисуется, не выбирается целью и не участвует в оценке покрытия.
**Отдача.** Удар в ближнем бою отбрасывает мишень (`meleeKnockback`), импульс
гасится (`targetKnockDamp`), и мишень возвращается на своё место `home`
(`targetReturnRate`). Без отдачи попадание в упор визуально не отличается от
промаха. Возврат обязателен: расстановка захардкожена, и уползание мишеней
поломало бы все замеры разом — респавн дополнительно ставит `pos = home`.
**Тело.** Мишень расталкивает агентов (`TargetPushForce`, `wTargetPush`): до
ближнего боя агент мог стоять внутри мишени, и удар не читался вообще.