Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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 17:00:49 +07:00
|
|
|
|
# 04 — Мир: карта, рейкаст, коллизии, мишени
|
|
|
|
|
|
|
|
|
|
|
|
## Тайлмап
|
|
|
|
|
|
|
Документация: границы движка, работа через модель, дизайн игры до лейтгейма
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-14 01:07:45 +07:00
|
|
|
|
[engine/tilemap.h](../engine/tilemap.h). Сетка 48×48, тайл `EMPTY` или `WALL`.
|
Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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 17:00:49 +07:00
|
|
|
|
Тайл = 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.5–8.5 → 7.5–10.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`): до
|
|
|
|
|
|
ближнего боя агент мог стоять внутри мишени, и удар не читался вообще.
|