squad-proto/docs/14-monsters.md

110 lines
7.9 KiB
Markdown
Raw Normal View History

Документация: границы движка, работа через модель, дизайн игры до лейтгейма 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
# 14. Твари: конвейер ассетов
Blender-first конвейер для пяти видов: `shambler`, `rusher`, `spitter`,
`brute`, `lurker`. Отдельный от агентского и намеренно.
## Зачем отдельный конвейер
У агента слои бумажной куклы обязаны совпасть **пиксель в пиксель**: тело,
броня и оружие рисуются на одном скелете, и любое расхождение видно сразу.
Отсюда в `tools/agent_assets.py` калибровка, растровый `ingest` и проверка
«экипировка не вылезла за силуэт тела».
У твари слоёв нет вовсе, склеивать нечего. Зато пять видов обязаны
**различаться силуэтом** с одного взгляда: игрок принимает решение по пятну,
а не по деталям. Поэтому здесь пять независимых скелетов, никакой общей
калибровки — и проверка ровно обратного смысла: `validate` ругается, если два
вида дали похожее пятно.
Общее у двух конвейеров ровно одно и намеренно: **неподвижная
ортографическая изометрическая камера** (`elevation_deg = atan(0.5)`, тот же
угол, под которым в игре лежит пол) и вырезка кадра постоянным
прямоугольником. Начало координат рига проецируется в центр кадра, поэтому
упаковщик режет все кадры одинаково — тварь не «дышит» при повороте.
## Файлы
| Файл | Что делает |
|---|---|
| `assets/monsters/catalog.yaml` | геометрия ячейки, камера, список видов и клипов — единственный источник чисел |
| `tools/blender/monster_rig.py` | анатомия и анимации; запускается ВНУТРИ Blender |
| `tools/monster_assets.py` | CLI: рендер, упаковка листов, манифест, проверки |
| `tools/build_monster_assets.ps1` | полный прогон: init → render-all → validate → sf validate |
| `assets/sources/monsters/*.png` | собранные листы (вход SpriteForge) |
| `assets/manifests/monsters.generated.yaml` | манифест, пишется `sync` |
| `generated/monster_sprites.h` | константы `MONSTER_ASSET_*` и `MonsterAsset(EnemyKind, AnimKind)` |
## Геометрия
Ячейка `48x56`, pivot `(24,47)`, восемь направлений в том же порядке, что у
агентов: юг, юго-запад, запад, ... Лист **direction-major**: сначала все кадры
юга, затем юго-запада.
`pivot_y = 47`, а не `55`: изометрический пол уходит от точки опоры **вниз** по
экрану, и всё, что у твари позади (хвост бегуна, задние лапы затаившегося),
проецируется ниже точки опоры. Девять строк под pivot — запас на глубину
подставки, а не воздух.
Клипы: `walk` 8, `attack` 6, `hit` 4, `death` 8 кадров на направление.
`fps_scale` в каталоге задаёт повадку: бегун частит, громила волочится при
одинаковом числе кадров.
## Морфологии
Не капсулы. Каждый вид — своя анатомия, а не перекрашенный гуманоид:
* **shambler** — вскрытая наружу грудная клетка, рёбра веером, правая рука
доросла до колена, в череп вбита пластина; хромает;
* **rusher** — четвероногий, горизонтальный хребет, пальцеходящие задние ноги,
косы вместо предплечий, вместо головы раскрытый цветок челюстей;
* **spitter** — волочащийся железистый мешок больше грудной клетки, вживлённый
в горло клапан, сопло вместо лица, атрофированные руки;
* **brute** — к плечу приварено второе, наполовину поглощённое тело; грудь
забрана болтами; правая рука — молот из сросшейся кости;
* **lurker** — плоский зашитый скобами панцирь подвешен под четырьмя длинными
выгнутыми лапами, безглазый клин с крюками.
Анимации описаны в **нормированном** времени (список опорных значений на 0..1)
и сэмплируются под число кадров из каталога: `frames` в каталоге можно менять,
не трогая `monster_rig.py`.
`death` — это **осадка в кучу**, а не падение во весь рост. Лежащая во весь
рост туша не помещается в клетку `48x56` при повороте «от камеры», и в игре это
выглядело бы как обрезанный труп.
## Порядок работы
```
python tools/monster_assets.py monster-init # собрать риг .blend
python tools/monster_assets.py monster-render shambler walk # один вид, один клип
python tools/monster_assets.py monster-render-all # всё
python tools/monster_assets.py validate --strict
python tools/monster_assets.py sync
python -m unittest discover -s tools/tests
```
`monster-render` и `monster-render-all` сами вызывают `sync`.
## Что проверяет validate
* холст листа равен `48 x (8 направлений x кадры)`, пустых ячеек нет;
* силуэт **не упирается в кромку** клетки — иначе в игре это отрубленная лапа;
* тварь стоит на общей линии пола (`death` освобождён: труп имеет право осесть);
* «роза» из восьми направлений центрирована на `pivot_x`;
* зеркальные пары (ЮЗ,ЮВ), (З,В), (СЗ,СВ) совпадают при отражении — ловит
перестановку направлений;
* **пять видов различаются пятном** (IoU силуэтов шага ниже 0.90);
* манифест не разошёлся с каталогом по pivot и размеру кадра, и ни один ID не
объявлен вторым манифестом.
## Запреты
* ID ассета — строго `<kind>_<animation>`: ровно это требует
`tools/asset_coverage.py` от группы `enemy`. Переименуешь — упадёт покрытие.
* Строковых ID в C++ нет: только `MONSTER_ASSET_*` из `generated/monster_sprites.h`.
* PNG и contact sheet не открывать: проверка только текстом — `validate`,
`sf list`, `sf describe`, `sf ascii`.
* `assets/manifests/enemies.yaml` — старые односторонние листы-заглушки. Пока
живы оба файла, ID пересекаются, и `validate` про это предупреждает: снимать
предупреждение — это отдельное решение на стороне рантайма, не конвейера.