Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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
|
|
|
|
# 03 — Рендер: софтверный буфер, изометрия, спрайты
|
|
|
|
|
|
|
|
|
|
|
|
## Фреймбуфер
|
|
|
|
|
|
|
Документация: границы движка, работа через модель, дизайн игры до лейтгейма
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/framebuffer.h](../engine/framebuffer.h) — `std::vector<uint32_t>` 480×270.
|
Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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
|
|
|
|
|
|
|
|
|
|
Формат — RGBA8, в памяти байты идут `R,G,B,A`, значит little-endian `uint32` это
|
|
|
|
|
|
`A<<24 | B<<16 | G<<8 | R`. Помощник `RGBA(r,g,b,a)` собирает цвет правильно.
|
|
|
|
|
|
|
|
|
|
|
|
Примитивы: `Clear`, `Put`, `Blit`, `BlitTinted`, `FillRect`, `Line` (Брезенхем),
|
|
|
|
|
|
`CircleOutline` (средняя точка), `CircleFilled`. Всё пишет пиксели руками.
|
|
|
|
|
|
|
|
|
|
|
|
Спрайты пишутся **альфа-тестом**: пиксель с `alpha == 0` не пишется, полупрозрачности
|
|
|
|
|
|
нет. Для эффектов есть **аддитивное** смешивание с насыщением (`AddSat`, `AddBeam`,
|
|
|
|
|
|
`AddGlowDisc`) и умножение (`ScaleColor8`, `BlitMul`), а для света — `BlitShaded`,
|
|
|
|
|
|
пропускающий цвет через световую рампу. Подробности — [10-atmosphere.md](10-atmosphere.md).
|
|
|
|
|
|
|
|
|
|
|
|
`Blit` обрезает спрайт по границам буфера ДО циклов и **выходит при пустом
|
|
|
|
|
|
диапазоне**: иначе адрес строки вычисляется за концом вектора. Цикл при этом не
|
|
|
|
|
|
выполняется, разыменования нет, но индекс уже невалиден — Debug-сборка ловит это
|
|
|
|
|
|
как `vector subscript out of range`, Release молча считает мусорный адрес.
|
|
|
|
|
|
|
|
|
|
|
|
Вывод на экран — `ScreenBlitter`: `UpdateTexture` + `DrawTexturePro` с
|
|
|
|
|
|
`TEXTURE_FILTER_POINT`. Это единственное место рендера, знающее про raylib;
|
|
|
|
|
|
`Texture2D` спрятан за `void*`, чтобы `raylib.h` не протёк в заголовок.
|
|
|
|
|
|
|
|
|
|
|
|
## Изометрия
|
|
|
|
|
|
|
Документация: границы движка, работа через модель, дизайн игры до лейтгейма
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/view2d.h](../engine/view2d.h). Тайл 32×16:
|
Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
sx = (wx - wy) * 16
|
|
|
|
|
|
sy = (wx + wy) * 8
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Обратная проекция: `wx = sx/32 + sy/16`, `wy = sy/16 - sx/32`.
|
|
|
|
|
|
|
|
|
|
|
|
`ScreenDirToWorld` переводит направление ввода из экранных осей в мировые —
|
|
|
|
|
|
именно это делает `WASD` интуитивным. Больше изометрия нигде не используется.
|
|
|
|
|
|
|
|
|
|
|
|
**Камера** следует за интерполированным якорем экспоненциальным сглаживанием
|
|
|
|
|
|
(`1 - exp(-smooth*dt)`), смещение округляется до целых пикселей — иначе
|
|
|
|
|
|
при ×3 апскейле картинка дрожит.
|
|
|
|
|
|
|
|
|
|
|
|
## Сортировка по глубине
|
|
|
|
|
|
|
|
|
|
|
|
Ключ — `wx + wy`. Пол рисуется первым сплошным проходом и в сортировке не участвует.
|
|
|
|
|
|
Все стены и все сущности попадают в **один** список `Scene::items` и сортируются
|
|
|
|
|
|
`std::stable_sort` (стабильная — чтобы порядок был детерминирован при равных ключах).
|
|
|
|
|
|
|
|
|
|
|
|
Отсечение — по экранным координатам с запасом на высоту спрайта стены.
|
|
|
|
|
|
|
|
|
|
|
|
## Точка привязки спрайта
|
|
|
|
|
|
|
|
|
|
|
|
У `Sprite` есть поля `ax, ay` — точка внутри спрайта, которая совмещается с
|
|
|
|
|
|
экранной проекцией мировой позиции. Так блит не зависит от размера спрайта:
|
|
|
|
|
|
|
|
|
|
|
|
| Спрайт | Размер | Привязка | Смысл |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| пол | 32×16 | (16, 8) | центр ромба = центр тайла |
|
|
|
|
|
|
| стена | 32×32 | (16, 24) | низ блока садится на ромб пола |
|
|
|
|
|
|
| агент (стрелок) | 10×14 | (5, 12) | «ноги» на земле |
|
|
|
|
|
|
| агент (клинок) | 12×15 | (6, 13) | шире в плечах, класс читается силуэтом |
|
|
|
|
|
|
| мишень | 12×16 | (6, 14) | то же |
|
|
|
|
|
|
|
|
|
|
|
|
У пули спрайта нет: снаряд рисуется светящимся трассером, см. [10-atmosphere.md](10-atmosphere.md).
|
|
|
|
|
|
|
|
|
|
|
|
## Процедурные спрайты
|
|
|
|
|
|
|
|
|
|
|
|
[src/render/sprites.cpp](../src/render/sprites.cpp), файлов с ассетами нет.
|
|
|
|
|
|
|
|
|
|
|
|
- **Пол** — ромб 32×16, два оттенка тёмно-серого металла по шахматке `(x+y)&1`,
|
|
|
|
|
|
затемнённый рант по краю и редкие светлые крапины для фактуры.
|
|
|
|
|
|
- **Стена** — верхняя грань (тот же ромб) + вертикальные грани: для каждого столбца
|
|
|
|
|
|
ищется нижняя кромка ромба и вниз заливается на высоту тайла. Левая грань темнее
|
|
|
|
|
|
правой. Обводка по силуэту — пиксели, у которых есть прозрачный 4-сосед.
|
|
|
|
|
|
- **Агент** — капсула: пиксель внутри, если расстояние до вертикального отрезка
|
|
|
|
|
|
меньше радиуса; внешний пиксель кольца — обводка, слева-сверху блик.
|
|
|
|
|
|
5 различимых цветов, по одному на агента.
|
|
|
|
|
|
- **Агент с клинком** — та же капсула, крупнее, плюс стальная вертикаль с бликом
|
|
|
|
|
|
и гардой у правого края. Цвет уже занят номером агента, поэтому класс обязан
|
|
|
|
|
|
читаться формой. Сам взмах рисуется не спрайтом, а аддитивной дугой
|
|
|
|
|
|
([11-melee.md](11-melee.md)).
|
|
|
|
|
|
- **Мишень** — та же капсула, тускло-красная, 12×16. В темноте тело не видно —
|
|
|
|
|
|
виден только тлеющий красный «глаз», нарисованный аддитивно поверх сцены.
|
|
|
|
|
|
|
|
|
|
|
|
Порядок отрисовки кадра:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Clear(чёрный) → свет → пол (BlitShaded) → список (стены, мишени, агенты)
|
|
|
|
|
|
→ сортировка → блит → глаза мишеней → трассеры, вспышки и дуги клинков
|
|
|
|
|
|
(аддитивно) → ОДИН пост-проход → Present → оверлей (raylib, dev-only)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Три инварианта, нарушение любого ломает картинку молча:
|
|
|
|
|
|
|
|
|
|
|
|
1. альфа фреймбуфера всегда 255;
|
|
|
|
|
|
2. ступень 0 световой рампы совпадает с цветом `Clear` — только поэтому полностью
|
|
|
|
|
|
тёмные тайлы можно не рисовать;
|
|
|
|
|
|
3. стена никогда не темнее самого яркого соседа-пола — иначе сквозь пропущенный
|
|
|
|
|
|
тёмный блок проступит освещённый пол тайла (tx−1, ty−1), который рисуется
|
|
|
|
|
|
раньше по глубине.
|