squad-proto/src/tuning.h

570 lines
41 KiB
C
Raw Normal View History

Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
#pragma once
#include <algorithm>
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
#include "engine/config.h"
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
// -----------------------------------------------------------------------------
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
// Единственное место, где живут числа ИГРЫ.
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
// В коде не должно быть магических констант — всё берётся отсюда.
//
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
// * блок COMPILE-TIME — размеры буферов игры, менять только пересборкой;
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
// * struct Tuning — всё, что подкручивается в рантайме ([ ] выбор, - = шаг).
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
//
// Размеры кадра, тайла и карты сюда БОЛЬШЕ НЕ ВХОДЯТ: это хозяйство движка, оно
// в engine/config.h. Имена те же, файл включён выше — по коду игры ничего не
// меняется, но движок теперь собирается без единого файла игры.
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
// -----------------------------------------------------------------------------
// --- compile-time константы --------------------------------------------------
// Точка старта отряда (центральная арена).
constexpr float SQUAD_START_X = 26.5f; // ближе к кучке: дальность стала 7.2
constexpr float SQUAD_START_Y = 26.5f;
constexpr int NUM_AGENTS = 5;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
constexpr int NUM_TARGETS = 12; // мишени приёмки: расстановка захардкожена
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
constexpr int MAX_BULLETS = 512;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
// Твари, которых боевой режим доставляет СВЕРХ мишеней. В песочнице приёмки их
// нет: расстановка мишеней и все замеры на ней остаются прежними.
constexpr int NUM_EXTRA_ENEMIES = 12;
// Разрушаемые объекты (ящики, бочки, шкафчики). Спавнятся ТОЛЬКО в боевом режиме;
// приёмка их не видит. Приоритет — ниже всех: ИИ переключается на них только
// если ни одного живого врага в поле зрения нет.
constexpr int NUM_PROPS = 8;
// Потолок на число целей в мире. Он не «на всякий случай»: MeleeWeapon::hitMask —
// это маска «кого уже задел ЭТИМ взмахом», индексируемая номером цели, и цель
// за её пределами молча получала бы удары дважды за взмах.
//
// Было 32. Игра про бесконечный спуск упёрлась в этот потолок примерно на
// шестнадцатом этаже: бюджет опасности перестал влезать в число тел, и глубже
// этажи переставали становиться страшнее. Маска расширена до 64 бит — это не
// «запас на будущее», а ровно та цифра, которой сейчас не хватало.
constexpr int MAX_TARGETS = 64;
static_assert(NUM_TARGETS + NUM_EXTRA_ENEMIES + NUM_PROPS <= MAX_TARGETS,
"целей больше, чем битов в MeleeWeapon::hitMask");
// У движка потолок населения свой (MAX_LEVEL_SPAWNS в engine/level_gen.h):
// генератор уровней не обязан знать про маску удара клинком. Что одно влезает
// в другое, проверяется там, где видны оба, — в src/game.h.
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
// Сколько слотов строя занимают бойцы ближнего боя (слоты 0..N-1).
// В клине это остриё и левое крыло — те, кто и так идёт первым.
constexpr int DEFAULT_MELEE_COUNT = 2;
// Сколько ПОСЛЕДНИХ слотов носят пистолет вместо карабина.
// Короткий ствол уезжает в хвост клина: ему там и место.
constexpr int DEFAULT_PISTOL_COUNT = 1;
// Сколько тайлов пола могут нести запечённую кровь. Каждый — своя копия
// спрайта 32x16 (2 КБ), то есть потолок ~2.4 МБ. Дальше новые пятна ложатся
// только на уже запачканные тайлы: декали вечные, но не бесконечные.
constexpr int BLOOD_TILE_BUDGET = 1200;
// Решатель огневых позиций: 3 кольца x 12 направлений + текущая позиция.
constexpr int SOLVER_RINGS = 3;
constexpr int SOLVER_DIRS = 12;
constexpr int SOLVER_CANDIDATES = SOLVER_RINGS * SOLVER_DIRS + 1;
// --- runtime-тюнинг ----------------------------------------------------------
struct Tuning
{
// движение агента
Пистолет не пропадает, эвакуация откуда угодно, кнопки в HUD (M36) БАГ С ПИСТОЛЕТОМ - на самом деле ДВА бага. Первый: экран отряда правит ЖИВОЙ МИР, а вылазка собирает бойцов заново из профиля (NewRun пересоздаёт ecs::World и раздаёт снаряжение через ApplyProfileLoadout). Всё, переложенное в меню, стиралось на старте, а вещь, вынутая из схрона, исчезала вместе с миром - схрон её уже отдал. Теперь всякая правка снаряжения пишется в профиль сразу. Второй, рядом: перетаскивание мышью НЕ РАБОТАЛО ВООБЩЕ. Флаг dropOk нигде не выставлялся в true - только сбрасывался, - и сброс молча отменялся. Единственным рабочим способом переодеться оставался ENTER. Теперь флаг считается каждый кадр, и ячейка честно красится в "нельзя". Заодно: вне забега сумка не принимает вещей. Она принадлежит вылазке и очищается при сборке этажа; вещь, положенная в неё из меню, исчезла бы так же молча. Проверка ловит именно цепочку "поменял в меню -> начал вылазку -> вышел с этим": сам обмен работал и раньше, пропадал он на старте. сборы: в профиле после обмена PISTOL, на этаже PISTOL ТВАРИ НЕ ВОСКРЕСАЮТ. Убитая в этой вылазке убита насовсем: с возвратом любая расчищенная комната через полминуты снова была полна, и "пробиться и уйти" не отличалось от "молотить бесконечную очередь". Исключение одно - аура плакальщика, это его роль. ЭВАКУАЦИЯ С ЛЮБОГО ЭТАЖА И ИЗ ЛЮБОЙ ТОЧКИ. Раньше уйти можно было только стоя на кольце: отряд, решивший не рисковать, всё равно шёл через этаж и умирал ровно там, где игрок уже принял решение. Чекпоинты теперь значат только то, чем должны быть: откуда можно НАЧАТЬ следующую вылазку. ОТЗЫВЧИВОСТЬ СТРОЯ. Отряд ходил 1.75 при курсоре 3.3 - он всегда отставал, и управление ощущалось как "тяну за верёвку". Стало 2.6, поводок короче (2.5 против 3.0), доворот строя вдвое резче. Ориентация строя теперь ПОВОРАЧИВАЕТСЯ, а не щёлкает: раньше слоты клина перепрыгивали через весь отряд на каждом повороте, и бойцы бросали дорогу ради перестроения. В тесноте доворот почти мгновенный - там строй и так колонна, а колонна, смотрящая в прошлую сторону, упирается в стену. Автопилот поймал, что первая версия правок сделала хуже: при быстром наборе натяжения (5.2) отряд уходил в вечный спринт, а спринт растягивает строй и выключает огонь. Порог спринта поднят до 0.95, набор до 3.4 - приёмка 11.4 держится (0.13 с при пороге 0.30). КНОПКИ В HUD. Слева внизу четыре: SQUAD, TREE, WORK, MENU. Экраны открывались только клавишами, о которых игра нигде не говорила, - без кнопки мастерская и дерево не открываются никогда. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:24:10 +07:00
// Скорость отряда. Была 1.75 при курсоре 3.3: отряд ВСЕГДА отставал, и
// управление ощущалось как «тяну за верёвку», а не «веду людей». Поднято до
// 2.6 — это по-прежнему медленнее курсора (иначе поводок теряет смысл), но
// разрыв закрывается за доли секунды, а не за метры.
float moveSpeed = 2.60f; // юнитов/сек: хоррор, а не шутер-беготня
float moveSpeedFast = 4.4f; // догоняют курсор быстро — иначе отряд отстаёт
float steerAccel = 26.0f; // тяжелее; НИЖЕ 16 нельзя — ломается критерий 11.4
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
float bodyRadius = 0.28f;
float separationRadius = 0.7f;
float wSeek = 1.0f;
Пистолет не пропадает, эвакуация откуда угодно, кнопки в HUD (M36) БАГ С ПИСТОЛЕТОМ - на самом деле ДВА бага. Первый: экран отряда правит ЖИВОЙ МИР, а вылазка собирает бойцов заново из профиля (NewRun пересоздаёт ecs::World и раздаёт снаряжение через ApplyProfileLoadout). Всё, переложенное в меню, стиралось на старте, а вещь, вынутая из схрона, исчезала вместе с миром - схрон её уже отдал. Теперь всякая правка снаряжения пишется в профиль сразу. Второй, рядом: перетаскивание мышью НЕ РАБОТАЛО ВООБЩЕ. Флаг dropOk нигде не выставлялся в true - только сбрасывался, - и сброс молча отменялся. Единственным рабочим способом переодеться оставался ENTER. Теперь флаг считается каждый кадр, и ячейка честно красится в "нельзя". Заодно: вне забега сумка не принимает вещей. Она принадлежит вылазке и очищается при сборке этажа; вещь, положенная в неё из меню, исчезла бы так же молча. Проверка ловит именно цепочку "поменял в меню -> начал вылазку -> вышел с этим": сам обмен работал и раньше, пропадал он на старте. сборы: в профиле после обмена PISTOL, на этаже PISTOL ТВАРИ НЕ ВОСКРЕСАЮТ. Убитая в этой вылазке убита насовсем: с возвратом любая расчищенная комната через полминуты снова была полна, и "пробиться и уйти" не отличалось от "молотить бесконечную очередь". Исключение одно - аура плакальщика, это его роль. ЭВАКУАЦИЯ С ЛЮБОГО ЭТАЖА И ИЗ ЛЮБОЙ ТОЧКИ. Раньше уйти можно было только стоя на кольце: отряд, решивший не рисковать, всё равно шёл через этаж и умирал ровно там, где игрок уже принял решение. Чекпоинты теперь значат только то, чем должны быть: откуда можно НАЧАТЬ следующую вылазку. ОТЗЫВЧИВОСТЬ СТРОЯ. Отряд ходил 1.75 при курсоре 3.3 - он всегда отставал, и управление ощущалось как "тяну за верёвку". Стало 2.6, поводок короче (2.5 против 3.0), доворот строя вдвое резче. Ориентация строя теперь ПОВОРАЧИВАЕТСЯ, а не щёлкает: раньше слоты клина перепрыгивали через весь отряд на каждом повороте, и бойцы бросали дорогу ради перестроения. В тесноте доворот почти мгновенный - там строй и так колонна, а колонна, смотрящая в прошлую сторону, упирается в стену. Автопилот поймал, что первая версия правок сделала хуже: при быстром наборе натяжения (5.2) отряд уходил в вечный спринт, а спринт растягивает строй и выключает огонь. Порог спринта поднят до 0.95, набор до 3.4 - приёмка 11.4 держится (0.13 с при пороге 0.30). КНОПКИ В HUD. Слева внизу четыре: SQUAD, TREE, WORK, MENU. Экраны открывались только клавишами, о которых игра нигде не говорила, - без кнопки мастерская и дерево не открываются никогда. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:24:10 +07:00
// Как быстро строй доворачивается к направлению хода. Мгновенный доворот
// заставлял бойцов бегать вбок на каждом повороте (sim/squad.cpp).
float formTurnRate = 7.0f;
Поле путей: отставший возвращается в строй (M37) Отряд из пяти не проходил первый этаж ни на одном сиде, а одиночка проходил. Дело было не в ширине коридоров: у бойцов не было поиска пути вообще. Боец, оказавшийся за стеной от своего слота, seek'ом тянулся В СТЕНУ, обход стены толкал его обратно, и он замирал в углу навсегда. Теперь у каждого бойца своё поле путей (BFS от клетки его цели) с натягиванием верёвки: он берёт самую дальнюю точку пути, которую видит. Включается обход по двум условиям сразу — боец упёрся (хочет идти и не едет) И потерял строй. Счётчик упора сбрасывается по факту «цель видно», а не «поехал»: сброс по скорости давал дребезг, и боец полз шаг за две секунды. По дороге вскрылись две неверные МЕРКИ: * критерий 11.6 считал колонной «разброс вдоль больше, чем поперёк» — и потому награждал отряд, у которого двое навсегда остались по ту сторону стены. Теперь колонна меряется самим проёмом: разом в горловине не больше двух, и прошли ВСЕ. * зонд --wall читал «стены не нашлось» (ноль) как «стена на нулевом этаже» и объявлял пятёрку, не сломавшуюся нигде, худшей из трёх. Обе переписаны и проверены на сломанной сборке. Замеры: --play 6 5 на восьми сидах 47/48 (было 0/48), --accept PASS, шаг 6.1 мкс, --wall 30 одиночка D20 / трое D20 / пятеро D31 (не найдена), --descend 8/30, hollow --descend 6, --ui-check, оба проекта --check — без ошибок. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 22:31:34 +07:00
// Насколько боец может отстать от своего слота, прежде чем бросит всё и
// вернётся в строй. Ниже 2 отряд перестаёт разбредаться по огневым
// позициям вообще, выше 4 — снова застревает цепочкой (sim/squad.cpp).
float straggleDist = 2.8f;
// Когда включается поле путей. Не по расстоянию до цели, а по факту упора:
// боец хочет идти и не едет. Расстоянием этот порог не задать — на подходе
// к проходу цель не видна никому, и по «не видно» все пятеро получили бы
// одну путевую точку и вошли бы в горловину шеренгой (критерий 11.6).
float navStuckTime = 0.35f; // секунд упора до обхода
float navStuckSpeed = 0.25f; // «не едет» — это доля от скорости хода
float navGoalReached = 0.35f; // ближе этого цель считается достигнутой
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
float wSeparation = 1.0f;
float wLaneAvoid = 2.4f; // строго выше wSeparation (спек 6.7)
float wWallAvoid = 1.2f;
float wallLookahead = 0.8f;
float wallSlide = 1.4f; // касательная составляющая обхода стен
float arriveRadius = 0.18f;
float turnRateDeg = 300.0f; // ствол тяжёлый
float wTargetPush = 1.0f; // мишень — тело, а не пустое место
float targetPushGap = 0.10f; // зазор поверх суммы радиусов
// оружие (это ПАРАМЕТРЫ КАРАБИНА — оружия по умолчанию, на котором меряются
// критерии приёмки. Остальные стволы — записи каталога sim/weapon_defs.h)
float weaponRange = 7.2f; // -20% от исходных 9.0
float fireInterval = 0.35f;
float laneHalfWidth = 0.10f;
float damage = 12.0f;
float safetyMargin = 0.15f;
float muzzleOffset = 0.3f;
float bulletSpeed = 22.0f;
float bulletRadius = 0.04f;
float bulletKnock = 0.35f; // импульс мишени от попадания пули
float aimToleranceDeg = 5.0f; // ствол должен быть доведён до цели
// --- разброс -------------------------------------------------------------
// Стоя ствол почти не гуляет, на полном ходу гуляет сильно, очередь его
// раскрывает. Тот же конус проверяется на своих перед спуском: правило 6.3
// не ослаблено, оно просто считает по конусу вместо отрезка.
float spreadStandDeg = 0.9f;
float spreadMoveDeg = 4.2f;
float spreadRecoilDeg = 0.55f; // прибавка за выстрел
float spreadRecoilMax = 3.0f;
float spreadRecoilDecay = 3.2f; // градусов/сек оседания
float spreadScale = 1.0f; // общий множитель, ручка для замеров
// --- перезарядка ---------------------------------------------------------
float magazineRifle = 24.0f; // патронов в магазине карабина
float reloadRifle = 1.9f; // сек
// Запас на манёвр союзника за время полёта пули (доля от moveSpeed).
// 0 = верить экстраполяции скорости буквально.
float shotPredictSlack = 0.3f;
// --- ближний бой ---------------------------------------------------------
// Три фазы: замах видно, урон живёт в коротком окне, после удара боец открыт.
float meleeReach = 1.40f; // от центра тела до конца замаха
float meleeArcDeg = 100.0f; // ширина сектора удара
float meleeWindup = 0.17f; // замах — телеграф
float meleeStrike = 0.13f; // активное окно урона
float meleeRecover = 0.24f; // после удара
float meleeInterval = 0.60f; // между началами замахов
float meleeDamage = 130.0f; // 4 удара на мишень против 42 пуль
float meleeBladeDeg = 34.0f; // ширина полосы самого лезвия
float meleeAimToleranceDeg = 30.0f; // допуск разворота на старте замаха
float meleeAllyMargin = 0.14f; // запас к телу союзника в секторе
// На сколько взмахов вперёд смотреть на союзника при старте замаха
// (в долях windup+strike). 0 = верить только текущему положению.
float meleeStartPredict = 1.0f;
// Доля бокового размаха сектора, которую клинок резервирует в реестре линий.
// 1.0 = вся дуга; тогда своим нечем дышать. 0 = резерв шириной с отрезок.
float meleeReserveFrac = 0.55f;
float meleeGap = 0.12f; // зазор при подходе вплотную
float meleeLungeSpeed = 3.4f; // рывок в момент удара, юнитов/сек
float meleeLungeAccel = 120.0f; // рывок обязан успеть за 0.13 с
float meleeWindupScale = 0.20f; // множитель скорости на замахе
float meleeRecoverScale= 0.55f;
float meleeKnockback = 2.4f; // импульс мишени, юнитов/сек
// СВОЙ круг бойца: его отпускают от диска дальше, чем стрелка.
float meleeCohesionRadius = 5.6f;
float meleeSeekRadius = 7.0f; // дальше этого боец за целью не идёт вообще
// Штраф за уже занятую цель, в юнитах дистанции. Двое с клинками, вцепившиеся
// в одного, половину взмахов обрывают друг об друга — пусть расходятся.
float meleeClaimPenalty = 3.0f;
// Сколько держится связка после удара. Не успел продолжить — начинаешь
// серию заново с первого удара.
float meleeComboWindow = 1.10f;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
// --- живучесть отряда и тварей -------------------------------------------
// У мишеней приёмки эти числа не работают: DUMMY не бьёт и не ходит,
// поэтому --accept от них не зависит вообще.
float agentHP = 100.0f; // запас бойца до брони и оберегов
float enemyDamageScale = 1.0f; // общая сложность: во сколько раз больно
float enemyRespawn = 16.0f; // через сколько тварь возвращается
float enemyLightPower = 0.14f; // как ярко тлеют глаза в темноте
// Сколько цель «светится» после попадания. Единственный признак, по которому
// в темноте видно, что отряд по кому-то реально работает, а не мажет.
float targetHurtTime = 0.22f;
// --- расход ------------------------------------------------------------------
// Патроны на вылазку. Не «на этаж»: запас едет вниз вместе с отрядом, и
// решение «ещё глубже или наверх» упирается именно в него.
float startAmmo = 120.0f;
// При какой доле здоровья боец сам возьмётся за бинт и на каком удалении
// от ближайшей твари это ему позволено. Лечиться в бою нельзя: перевязка
// посреди схватки обесценила бы урон.
float healAtFrac = 0.55f;
float healSafeDist = 9.0f;
// --- спуск, находки, опыт ---------------------------------------------------
// Ничего из этого не работает в песочнице приёмки: там нет ни лута, ни
// спуска, и `--accept` этих чисел не касается.
//
// Радиус подбора заметно больше тела бойца: подбор пассивный, и промазать
// мимо лежащего предмета, пройдя вплотную, игрок не должен — он ведь по
// нему не кликал.
float lootPickupRadius = 0.65f;
// На каком расстоянии от точки спуска отряд считается дошедшим. Меряется
// по ЯКОРЮ, а не по бойцам: спуск — это решение игрока, а не то, что
// случается от того, что один боец забрёл не туда.
float descendRadius = 1.10f;
// Сколько опыта даёт единица опасности убитого. Опыт этажа сверх этого
// множится на xpScale из кривой глубины (engine/progression.h).
float xpPerDanger = 10.0f;
// --- разрушаемая обстановка ------------------------------------------------
// Радиус, в котором ОДНО ПРИСУТСТВИЕ живой твари запрещает бойцу работать
// по хламу. Намеренно шире дальности любого ствола: привязать его к дальности
// значило бы «пока не достаю — стреляю по ящику», и боец встречал бы
// подошедшую тварь с пустым магазином. Ящик ломают, когда вокруг ЧИСТО.
float propYieldRadius = 15.0f;
// --- атаки тварей ---------------------------------------------------------
// Форма каждой атаки (сектор, радиус круга, длина рывка) — свойство ВИДА и
// живёт в EnemyDef (sim/target.cpp). Здесь только то, что общее для всех:
// именно этими двумя ручками правится «успеваю ли я среагировать».
//
// enemyWindupScale — множитель ВСЕХ телеграфов сразу. Опускать его ниже
// ~0.5 нечестно: замах становится короче времени реакции, и смерть бойца
// снова выглядит случайной.
float enemyWindupScale = 1.0f;
// Сколько захват держится без подтверждения от твари. Тварь каждый шаг
// продлевает его на эту величину; убили её — боец освобождается сам, и
// ни bullet.cpp, ни клинку не приходится знать про захват вообще.
float enemyGrabHold = 0.15f;
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
// мишени
float targetHP = 500.0f;
float targetRespawn = 3.0f;
float targetRadius = 0.34f;
float targetKnockDamp = 9.0f; // гашение импульса от удара
float targetReturnRate= 5.0f; // возврат на своё место
// Общий множитель сопротивления отбросу. Само сопротивление у каждой мишени
// своё (sim/target.cpp): тяжёлую клинком не сдвинуть, лёгкая улетает.
float knockResistScale = 1.0f;
// отряд
float holdDelay = 0.22f; // сек без ввода -> HOLDING
float cohesionRadius = 2.1f; // стрелки держатся кучнее
float minEngageDist = 1.6f; // отмасштабировано под дальность 7.2
float formationSpacing = 1.0f; // строй плотнее
float cameraSmooth = 9.0f;
float anchorRadius = 0.3f; // якорь тоже скользит вдоль стен
// Диск отзывчивее бойцов: команда читается мгновенно, а догоняют её ногами.
float anchorSpeed = 3.3f;
float anchorSpeedFast = 4.8f;
float anchorAccel = 20.0f; // разгон/торможение курсора, юнитов/сек^2
// Поводок УПРУГИЙ, а не жёсткий: без него быстрый диск просто отрывается от
// отряда и теряет смысл, но жёсткий упор намертво фиксирует дистанцию, и по
// ней уже не отличить «подправил направление» от «тяну изо всех сил».
// anchorLeash — мягкая граница: до неё курсор идёт свободно;
// anchorLeashMax — жёсткий предел: страховка, до неё обычно не доходит;
// leashDrag — насколько тяжелеет курсор между границами (0..1).
Пистолет не пропадает, эвакуация откуда угодно, кнопки в HUD (M36) БАГ С ПИСТОЛЕТОМ - на самом деле ДВА бага. Первый: экран отряда правит ЖИВОЙ МИР, а вылазка собирает бойцов заново из профиля (NewRun пересоздаёт ecs::World и раздаёт снаряжение через ApplyProfileLoadout). Всё, переложенное в меню, стиралось на старте, а вещь, вынутая из схрона, исчезала вместе с миром - схрон её уже отдал. Теперь всякая правка снаряжения пишется в профиль сразу. Второй, рядом: перетаскивание мышью НЕ РАБОТАЛО ВООБЩЕ. Флаг dropOk нигде не выставлялся в true - только сбрасывался, - и сброс молча отменялся. Единственным рабочим способом переодеться оставался ENTER. Теперь флаг считается каждый кадр, и ячейка честно красится в "нельзя". Заодно: вне забега сумка не принимает вещей. Она принадлежит вылазке и очищается при сборке этажа; вещь, положенная в неё из меню, исчезла бы так же молча. Проверка ловит именно цепочку "поменял в меню -> начал вылазку -> вышел с этим": сам обмен работал и раньше, пропадал он на старте. сборы: в профиле после обмена PISTOL, на этаже PISTOL ТВАРИ НЕ ВОСКРЕСАЮТ. Убитая в этой вылазке убита насовсем: с возвратом любая расчищенная комната через полминуты снова была полна, и "пробиться и уйти" не отличалось от "молотить бесконечную очередь". Исключение одно - аура плакальщика, это его роль. ЭВАКУАЦИЯ С ЛЮБОГО ЭТАЖА И ИЗ ЛЮБОЙ ТОЧКИ. Раньше уйти можно было только стоя на кольце: отряд, решивший не рисковать, всё равно шёл через этаж и умирал ровно там, где игрок уже принял решение. Чекпоинты теперь значат только то, чем должны быть: откуда можно НАЧАТЬ следующую вылазку. ОТЗЫВЧИВОСТЬ СТРОЯ. Отряд ходил 1.75 при курсоре 3.3 - он всегда отставал, и управление ощущалось как "тяну за верёвку". Стало 2.6, поводок короче (2.5 против 3.0), доворот строя вдвое резче. Ориентация строя теперь ПОВОРАЧИВАЕТСЯ, а не щёлкает: раньше слоты клина перепрыгивали через весь отряд на каждом повороте, и бойцы бросали дорогу ради перестроения. В тесноте доворот почти мгновенный - там строй и так колонна, а колонна, смотрящая в прошлую сторону, упирается в стену. Автопилот поймал, что первая версия правок сделала хуже: при быстром наборе натяжения (5.2) отряд уходил в вечный спринт, а спринт растягивает строй и выключает огонь. Порог спринта поднят до 0.95, набор до 3.4 - приёмка 11.4 держится (0.13 с при пороге 0.30). КНОПКИ В HUD. Слева внизу четыре: SQUAD, TREE, WORK, MENU. Экраны открывались только клавишами, о которых игра нигде не говорила, - без кнопки мастерская и дерево не открываются никогда. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:24:10 +07:00
float anchorLeash = 2.5f;
float anchorLeashMax = 4.2f;
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
float leashDrag = 0.75f; // на жёсткой границе остаётся 25% скорости
// --- натяжение поводка (см. docs/05-squad-ai.md) --------------------------
// Курсор упёрся в поводок, а игрок продолжает давить -> он хочет идти
// быстрее, чем отряд шагает. Это и есть команда «бегом»: отряд ускоряется,
// строй растягивается, а на верхней трети шкалы огонь прекращается вовсе.
float urgeStartFrac = 0.50f; // с какой доли поводка начинается натяжение
Пистолет не пропадает, эвакуация откуда угодно, кнопки в HUD (M36) БАГ С ПИСТОЛЕТОМ - на самом деле ДВА бага. Первый: экран отряда правит ЖИВОЙ МИР, а вылазка собирает бойцов заново из профиля (NewRun пересоздаёт ecs::World и раздаёт снаряжение через ApplyProfileLoadout). Всё, переложенное в меню, стиралось на старте, а вещь, вынутая из схрона, исчезала вместе с миром - схрон её уже отдал. Теперь всякая правка снаряжения пишется в профиль сразу. Второй, рядом: перетаскивание мышью НЕ РАБОТАЛО ВООБЩЕ. Флаг dropOk нигде не выставлялся в true - только сбрасывался, - и сброс молча отменялся. Единственным рабочим способом переодеться оставался ENTER. Теперь флаг считается каждый кадр, и ячейка честно красится в "нельзя". Заодно: вне забега сумка не принимает вещей. Она принадлежит вылазке и очищается при сборке этажа; вещь, положенная в неё из меню, исчезла бы так же молча. Проверка ловит именно цепочку "поменял в меню -> начал вылазку -> вышел с этим": сам обмен работал и раньше, пропадал он на старте. сборы: в профиле после обмена PISTOL, на этаже PISTOL ТВАРИ НЕ ВОСКРЕСАЮТ. Убитая в этой вылазке убита насовсем: с возвратом любая расчищенная комната через полминуты снова была полна, и "пробиться и уйти" не отличалось от "молотить бесконечную очередь". Исключение одно - аура плакальщика, это его роль. ЭВАКУАЦИЯ С ЛЮБОГО ЭТАЖА И ИЗ ЛЮБОЙ ТОЧКИ. Раньше уйти можно было только стоя на кольце: отряд, решивший не рисковать, всё равно шёл через этаж и умирал ровно там, где игрок уже принял решение. Чекпоинты теперь значат только то, чем должны быть: откуда можно НАЧАТЬ следующую вылазку. ОТЗЫВЧИВОСТЬ СТРОЯ. Отряд ходил 1.75 при курсоре 3.3 - он всегда отставал, и управление ощущалось как "тяну за верёвку". Стало 2.6, поводок короче (2.5 против 3.0), доворот строя вдвое резче. Ориентация строя теперь ПОВОРАЧИВАЕТСЯ, а не щёлкает: раньше слоты клина перепрыгивали через весь отряд на каждом повороте, и бойцы бросали дорогу ради перестроения. В тесноте доворот почти мгновенный - там строй и так колонна, а колонна, смотрящая в прошлую сторону, упирается в стену. Автопилот поймал, что первая версия правок сделала хуже: при быстром наборе натяжения (5.2) отряд уходил в вечный спринт, а спринт растягивает строй и выключает огонь. Порог спринта поднят до 0.95, набор до 3.4 - приёмка 11.4 держится (0.13 с при пороге 0.30). КНОПКИ В HUD. Слева внизу четыре: SQUAD, TREE, WORK, MENU. Экраны открывались только клавишами, о которых игра нигде не говорила, - без кнопки мастерская и дерево не открываются никогда. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:24:10 +07:00
// Натяжение набирается быстро, но НЕ мгновенно, и порог спринта поднят.
// При urgeAttack 5.2 отряд уходил в спринт от любого движения курсора, а
// спринт растягивает строй и выключает огонь: в коридорах это выглядело как
// «отряд застрял». Поймано автопилотом (--play): первый этаж переставал
// проходиться вообще.
float urgeAttack = 3.4f; // как быстро натяжение набирается
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
float urgeRelease = 3.4f; // и как быстро отпускает
Пистолет не пропадает, эвакуация откуда угодно, кнопки в HUD (M36) БАГ С ПИСТОЛЕТОМ - на самом деле ДВА бага. Первый: экран отряда правит ЖИВОЙ МИР, а вылазка собирает бойцов заново из профиля (NewRun пересоздаёт ecs::World и раздаёт снаряжение через ApplyProfileLoadout). Всё, переложенное в меню, стиралось на старте, а вещь, вынутая из схрона, исчезала вместе с миром - схрон её уже отдал. Теперь всякая правка снаряжения пишется в профиль сразу. Второй, рядом: перетаскивание мышью НЕ РАБОТАЛО ВООБЩЕ. Флаг dropOk нигде не выставлялся в true - только сбрасывался, - и сброс молча отменялся. Единственным рабочим способом переодеться оставался ENTER. Теперь флаг считается каждый кадр, и ячейка честно красится в "нельзя". Заодно: вне забега сумка не принимает вещей. Она принадлежит вылазке и очищается при сборке этажа; вещь, положенная в неё из меню, исчезла бы так же молча. Проверка ловит именно цепочку "поменял в меню -> начал вылазку -> вышел с этим": сам обмен работал и раньше, пропадал он на старте. сборы: в профиле после обмена PISTOL, на этаже PISTOL ТВАРИ НЕ ВОСКРЕСАЮТ. Убитая в этой вылазке убита насовсем: с возвратом любая расчищенная комната через полминуты снова была полна, и "пробиться и уйти" не отличалось от "молотить бесконечную очередь". Исключение одно - аура плакальщика, это его роль. ЭВАКУАЦИЯ С ЛЮБОГО ЭТАЖА И ИЗ ЛЮБОЙ ТОЧКИ. Раньше уйти можно было только стоя на кольце: отряд, решивший не рисковать, всё равно шёл через этаж и умирал ровно там, где игрок уже принял решение. Чекпоинты теперь значат только то, чем должны быть: откуда можно НАЧАТЬ следующую вылазку. ОТЗЫВЧИВОСТЬ СТРОЯ. Отряд ходил 1.75 при курсоре 3.3 - он всегда отставал, и управление ощущалось как "тяну за верёвку". Стало 2.6, поводок короче (2.5 против 3.0), доворот строя вдвое резче. Ориентация строя теперь ПОВОРАЧИВАЕТСЯ, а не щёлкает: раньше слоты клина перепрыгивали через весь отряд на каждом повороте, и бойцы бросали дорогу ради перестроения. В тесноте доворот почти мгновенный - там строй и так колонна, а колонна, смотрящая в прошлую сторону, упирается в стену. Автопилот поймал, что первая версия правок сделала хуже: при быстром наборе натяжения (5.2) отряд уходил в вечный спринт, а спринт растягивает строй и выключает огонь. Порог спринта поднят до 0.95, набор до 3.4 - приёмка 11.4 держится (0.13 с при пороге 0.30). КНОПКИ В HUD. Слева внизу четыре: SQUAD, TREE, WORK, MENU. Экраны открывались только клавишами, о которых игра нигде не говорила, - без кнопки мастерская и дерево не открываются никогда. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:24:10 +07:00
float sprintAt = 0.95f; // выше — спринт: бежим, не атакуем.
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
// Порог высокий намеренно: при удержании направления натяжение садится
// примерно на 0.54, то есть отряд идёт быстрым шагом И ПРОДОЛЖАЕТ работать
// по целям. Спринт остаётся тем, чем должен быть, — отдельным решением
// (Shift) или следствием того, что курсор реально уехал.
// --- дыхание строя --------------------------------------------------------
float holdSpacing = 0.90f; // на стоянке отряд собирается
Запуск грузит проект; автопилот --play; строй сжимается в тесноте (M31) ГЛАВНОЕ. "Дольше пяти секунд не продержаться на первом уровне" - это был не баланс, а запуск. По умолчанию useRooms был false: обычный запуск exe собирал ЗАХАРДКОЖЕННУЮ карту прототипа со старой расстановкой тварей и высаживал туда ОДНОГО оперативника с ножом (размер отряда берётся из профиля). Игрок попадал не на первый этаж игры, а вообще не в игру. Теперь проект подключается по умолчанию, а старая карта осталась под флагом --no-rooms. --PLAY. Новый прогон: автопилот ведёт отряд к спуску и дерётся по дороге. Ни --descend, ни --wall не меряли ПРОХОЖДЕНИЕ: первый собирает этажи и ни с кем не воюет, второй ставит отряд в толпу и запрещает двигаться. Жалоба же была именно про проход этажа. Автопилот ходит по полю расстояний до спуска (заливка по проходимым клеткам), целится на несколько клеток вперёд по спуску поля и правит курсором - тем же вводом, каким играет человек. Три версии до рабочей: по прямой упирался в первую стену, по восьми соседям лез в диагональную щель между углами (тело круглое, не пролезает), по соседней клетке дёргался на каждой границе. Одиночка проходит три этажа и гибнет на четвёртом: 1 тварей 3, убил 0, 100% здоровья, света 244 дошёл (78 с) 2 тварей 6, убил 1, 100%, света 155 дошёл (44 с) 3 тварей 6, убил 0, 65%, света 8 дошёл (74 с) 4 тварей 9, убил 5, ВАЙП (37 с) Свет кончается к третьему этажу: расчёт "заряда на четыре пути до спуска" считал прямые пути, а живой отряд ходит петлями. СТРОЙ В ТЕСНОТЕ. Клин шириной в пять тел не проходит в дверь шириной в клетку: бойцы упираются друг в друга, расталкивание держит их на месте, отряд встаёт насмерть. Теперь строй сам сжимается в колонну, когда вокруг тел мало свободных клеток. ЧЕСТНО: это НЕ вылечило проход пятёрки до конца. Автопилот с полным отрядом всё ещё застревает на первом этаже там, где одиночка проходит, и причина не в ширине строя (проверено сжатием в точку и отрядом без клинков). Похоже, нужна нормальная проводка строя по проходам - это отдельная веха, а не патч в конце. Оставляю с числами, а не с обещанием. --accept PASS, все семь проверок зелёные. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 17:20:21 +07:00
// Строй в тесноте: во сколько раз сжимается вбок и вытягивается вдоль хода.
// 0.28 — это колонна: пять тел проходят в дверь шириной в одну клетку.
float tightSpacing = 0.28f;
float tightStretch = 1.60f;
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
float sprintSpacing = 1.30f; // в беге расходится
float sprintStretch = 1.55f; // и вытягивается вдоль движения
float formMorphRate = 4.0f; // скорость перестроения, 1/сек
// решатель огневых позиций (6.5)
float solveInterval = 0.25f;
float commitTime = 0.6f;
float hysteresis = 15.0f;
float ring1 = 0.7f; // кольца решателя сжаты вслед за строем
float ring2 = 1.4f;
float ring3 = 2.1f;
float candMinAlly = 0.60f; // минимум до другого агента (было МЕНЬШЕ диаметра тела)
float sLaneClear = 100.0f;
float sCoverage = 25.0f;
float sBlockAlly = 35.0f;
float sDistCost = 9.0f;
float sCohesionCost = 24.0f; // отбегать от отряда дорого
float sTooClose = 60.0f;
// Жёсткая граница огневой позиции: дальше cohesionRadius + postLeash от диска
// кандидат просто отбраковывается. Одной цены мало — линия огня стоит 100
// очков, и за неё решатель готов уйти сколько угодно далеко. А отряд, из
// которого стрелок ушёл на пять метров, отрядом быть перестаёт.
float postLeash = 1.0f;
// расступание из чужих линий (6.6)
float laneAvoidStrength = 7.0f;
// Ширина РЕЗЕРВА линии относительно порога «тело в линии» из 6.3.
// >1 означает: агента отжимает ДО того, как он влез в чужой сектор.
// Правило стрельбы (6.3) от этого не меняется — см. docs/08-decisions.md.
float laneReserveScale = 2.0f;
// ручное прицеливание (6.8)
float aimConeDeg = 20.0f;
float aimTurnRateDeg = 110.0f; // прицел ведётся тяжелее
// метрики
float uptimeWindow = 10.0f; // окно FIRE UPTIME, сек
float settleQuorum = 4.0f; // сколько агентов нужно для SETTLE TIME
// --- свет и туман войны (render-only, на симуляцию не влияет) -----------
float sightRadius = 7.6f; // где обзор гаснет полностью
float sightFullRadius = 2.2f; // дальше сразу начинается спад: свет должен таять
float sightConeDeg = 100.0f; // конус взгляда агента
float sightConeSoftDeg = 22.0f; // мягкий край конуса
float sightPersonalRadius= 1.9f; // круговой «личный» ореол вокруг агента
float sightBrightness = 0.86f; // яркость видимого; остаток отдан вспышкам
float fogMemoryLevel = 0.13f; // как тускло помнится уже увиденное
float fogWallBleed = 0.92f; // затухание света вглубь блока стен
float fogFadeRate = 9.0f; // сглаживание во времени, чтобы не щёлкало
float fogEntityReveal = 0.30f; // ниже этого света сущность не показывается
float eyeRevealLight = 0.05f; // глаз мишени тлеет почти в полной темноте
float eyeRadiusPx = 3.0f;
float eyeLiftPx = 11.0f; // на уровне «головы» капсулы
float agentMinLight = 0.34f; // свои читаемы всегда: игрок обязан их различать
// вспышки выстрелов как источники света
float flashPower = 0.55f;
float flashRadius = 4.0f;
float flashTime = 0.11f;
// рампа освещения (меняет саму таблицу — требует перепечь)
float lightGamma = 1.45f;
float lightMaxGain = 1.25f;
// --- эффекты стрельбы ---------------------------------------------------
float fxIntensity = 1.0f;
float tracerLength = 1.30f; // длина светящегося штриха, юнитов мира
float bulletLiftPx = 8.0f; // пуля летит на уровне груди, не по земле
float muzzleTime = 0.07f;
float muzzleRadiusPx = 5.0f;
float sparkTime = 0.16f;
float sparkRadiusPx = 4.0f;
float bladeLiftPx = 9.0f; // дуга метёт на уровне груди, не по земле
float bladeTrailFrac = 0.80f; // какая доля пройденной дуги ещё светится
float meleeShake = 1.4f; // отдача камеры на попадание клинком
// --- напряжение и постобработка -----------------------------------------
float dreadRadius = 7.0f; // с какой дистанции мишень начинает пугать
float dreadAttack = 6.0f; // страх набегает быстро
float dreadRelease = 0.5f; // отпускает медленно
float vigStartCalm = 0.42f; // где начинается виньетка
float vigStartTense = 0.20f;
float vigDepthCalm = 0.55f; // насколько темнеет край
float vigDepthTense = 0.85f;
float grainCalm = 6.0f; // амплитуда зерна (макс 127)
float grainTense = 16.0f;
float heartBpmCalm = 58.0f;
float heartBpmTense = 132.0f;
float heartAmp = 0.10f; // насколько сердцебиение давит картинку
float shotFlash = 0.12f; // подсветка кадра на выстрел
float shakePerShot = 0.55f; // отдача камеры, пикселей на выстрел
float shakeMax = 3.0f;
float shakeDecay = 9.0f;
// --- кровь (render-only, декали запекаются в спрайты пола) ---------------
float bloodAmount = 1.0f; // общий множитель размера пятен, 0 = выключить
float bloodRadiusHit = 0.26f; // пуля
float bloodRadiusMelee = 0.50f; // клинок
float bloodRadiusKill = 0.78f; // смерть: лужа
float bloodDrops = 5.0f; // сколько брызг летит по направлению удара
float bloodOpacity = 0.92f; // насколько густо пятно перекрывает пол
// --- дымка и пепел в воздухе (render-only) --------------------------------
float hazeAmount = 0.34f; // сила дымки, 0 = выключить
float hazeScale = 1.0f; // крупность клубов
float hazeDrift = 0.05f; // скорость сноса, экранных ячеек/сек
float hazeGate = 24.0f; // ниже этой яркости дымки нет: тьма остаётся тьмой
float ashDensity = 0.75f; // пылинок на тайл
float ashSpeed = 0.30f; // скорость дрейфа пепла
float ashBrightness = 0.60f;
float ashLiftPx = 13.0f; // на какой высоте висит взвесь
// флаги
bool freeFireSecondary = true; // 6.8: стрелять по вторичным целям в движении
bool friendlyFire = true; // 6.9: всегда true, тумблер только для проверки
};
// --- связка ударов ближнего боя ----------------------------------------------
//
// Чередование «влево-вправо» — это не связка, а метроном: два удара, между
// которыми нет разницы ни на глаз, ни по последствиям. Связка обязана иметь
// ФОРМУ: быстрый вход, злой возврат, тяжёлое завершение.
//
// Множители, а не абсолютные числа: базовые характеристики клинка остаются
// одним набором в Tuning и продолжают крутиться на ходу, а связка их гнёт.
struct MeleeComboStep
{
const char* name;
float arcMul; // ширина сектора
float dmgMul;
float knockMul;
float windupMul; // замах — телеграф; у тяжёлого удара он длиннее
float strikeMul; // активное окно
float recoverMul; // и расплата после
float intervalMul; // пауза до следующего удара связки
bool flipSide; // менять сторону захода лезвия
};
inline const MeleeComboStep MELEE_COMBO[] = {
// 1. Косой заход. Обычный удар, с него всё начинается.
{"slash", 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, true},
// 2. Возврат с обратной стороны. Быстрее и почти без паузы — именно это
// читается как «связка», а не как два отдельных удара.
{"backhand",0.85f, 1.00f, 0.85f, 0.55f, 0.80f, 0.70f, 0.50f, true},
// 3. Раскрутка. Широкая, тяжёлая, с долгим восстановлением: в тесноте её
// просто не дадут начать — сектор не будет чист, и это правильная цена.
{"spin", 1.70f, 1.75f, 2.30f, 1.50f, 1.40f, 1.70f, 1.35f, false},
};
inline constexpr int MELEE_COMBO_STEPS = int(sizeof(MELEE_COMBO) / sizeof(MELEE_COMBO[0]));
inline Tuning g_tune;
// Максимально возможная скорость агента НА ХОДУ. Проверка спуска на время полёта
// пули обязана считать по НЕЙ, иначе рывок союзника не учитывается.
//
// Рывок бойца ближнего боя сюда намеренно не входит: он не «манёвр», а прямой
// отрезок с уже известным направлением, и AllyInterceptsShot проверяет его
// точно — отдельной экстраполяцией (см. ai/firing_solver.cpp).
inline float MaxAgentSpeed() { return std::max(g_tune.moveSpeed, g_tune.moveSpeedFast); }
// --- таблица параметров для подкрутки в оверлее ------------------------------
struct TunableParam
{
const char* name;
float Tuning::* member;
float step;
float minV;
float maxV;
};
inline const TunableParam TUNABLES[] = {
{"safetyMargin", &Tuning::safetyMargin, 0.01f, 0.0f, 1.0f},
{"laneHalfWidth", &Tuning::laneHalfWidth, 0.01f, 0.0f, 0.5f},
{"bodyRadius", &Tuning::bodyRadius, 0.01f, 0.1f, 0.8f},
{"wLaneAvoid", &Tuning::wLaneAvoid, 0.1f, 0.0f, 10.0f},
{"laneAvoidStr", &Tuning::laneAvoidStrength,0.25f, 0.0f, 30.0f},
{"laneReserve", &Tuning::laneReserveScale, 0.05f, 1.0f, 4.0f},
{"predictSlack", &Tuning::shotPredictSlack, 0.05f, 0.0f, 2.0f},
{"wSeparation", &Tuning::wSeparation, 0.1f, 0.0f, 10.0f},
{"steerAccel", &Tuning::steerAccel, 1.0f, 2.0f, 60.0f},
Пистолет не пропадает, эвакуация откуда угодно, кнопки в HUD (M36) БАГ С ПИСТОЛЕТОМ - на самом деле ДВА бага. Первый: экран отряда правит ЖИВОЙ МИР, а вылазка собирает бойцов заново из профиля (NewRun пересоздаёт ecs::World и раздаёт снаряжение через ApplyProfileLoadout). Всё, переложенное в меню, стиралось на старте, а вещь, вынутая из схрона, исчезала вместе с миром - схрон её уже отдал. Теперь всякая правка снаряжения пишется в профиль сразу. Второй, рядом: перетаскивание мышью НЕ РАБОТАЛО ВООБЩЕ. Флаг dropOk нигде не выставлялся в true - только сбрасывался, - и сброс молча отменялся. Единственным рабочим способом переодеться оставался ENTER. Теперь флаг считается каждый кадр, и ячейка честно красится в "нельзя". Заодно: вне забега сумка не принимает вещей. Она принадлежит вылазке и очищается при сборке этажа; вещь, положенная в неё из меню, исчезла бы так же молча. Проверка ловит именно цепочку "поменял в меню -> начал вылазку -> вышел с этим": сам обмен работал и раньше, пропадал он на старте. сборы: в профиле после обмена PISTOL, на этаже PISTOL ТВАРИ НЕ ВОСКРЕСАЮТ. Убитая в этой вылазке убита насовсем: с возвратом любая расчищенная комната через полминуты снова была полна, и "пробиться и уйти" не отличалось от "молотить бесконечную очередь". Исключение одно - аура плакальщика, это его роль. ЭВАКУАЦИЯ С ЛЮБОГО ЭТАЖА И ИЗ ЛЮБОЙ ТОЧКИ. Раньше уйти можно было только стоя на кольце: отряд, решивший не рисковать, всё равно шёл через этаж и умирал ровно там, где игрок уже принял решение. Чекпоинты теперь значат только то, чем должны быть: откуда можно НАЧАТЬ следующую вылазку. ОТЗЫВЧИВОСТЬ СТРОЯ. Отряд ходил 1.75 при курсоре 3.3 - он всегда отставал, и управление ощущалось как "тяну за верёвку". Стало 2.6, поводок короче (2.5 против 3.0), доворот строя вдвое резче. Ориентация строя теперь ПОВОРАЧИВАЕТСЯ, а не щёлкает: раньше слоты клина перепрыгивали через весь отряд на каждом повороте, и бойцы бросали дорогу ради перестроения. В тесноте доворот почти мгновенный - там строй и так колонна, а колонна, смотрящая в прошлую сторону, упирается в стену. Автопилот поймал, что первая версия правок сделала хуже: при быстром наборе натяжения (5.2) отряд уходил в вечный спринт, а спринт растягивает строй и выключает огонь. Порог спринта поднят до 0.95, набор до 3.4 - приёмка 11.4 держится (0.13 с при пороге 0.30). КНОПКИ В HUD. Слева внизу четыре: SQUAD, TREE, WORK, MENU. Экраны открывались только клавишами, о которых игра нигде не говорила, - без кнопки мастерская и дерево не открываются никогда. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:24:10 +07:00
{"formTurnRate", &Tuning::formTurnRate, 0.5f, 0.5f, 30.0f},
Поле путей: отставший возвращается в строй (M37) Отряд из пяти не проходил первый этаж ни на одном сиде, а одиночка проходил. Дело было не в ширине коридоров: у бойцов не было поиска пути вообще. Боец, оказавшийся за стеной от своего слота, seek'ом тянулся В СТЕНУ, обход стены толкал его обратно, и он замирал в углу навсегда. Теперь у каждого бойца своё поле путей (BFS от клетки его цели) с натягиванием верёвки: он берёт самую дальнюю точку пути, которую видит. Включается обход по двум условиям сразу — боец упёрся (хочет идти и не едет) И потерял строй. Счётчик упора сбрасывается по факту «цель видно», а не «поехал»: сброс по скорости давал дребезг, и боец полз шаг за две секунды. По дороге вскрылись две неверные МЕРКИ: * критерий 11.6 считал колонной «разброс вдоль больше, чем поперёк» — и потому награждал отряд, у которого двое навсегда остались по ту сторону стены. Теперь колонна меряется самим проёмом: разом в горловине не больше двух, и прошли ВСЕ. * зонд --wall читал «стены не нашлось» (ноль) как «стена на нулевом этаже» и объявлял пятёрку, не сломавшуюся нигде, худшей из трёх. Обе переписаны и проверены на сломанной сборке. Замеры: --play 6 5 на восьми сидах 47/48 (было 0/48), --accept PASS, шаг 6.1 мкс, --wall 30 одиночка D20 / трое D20 / пятеро D31 (не найдена), --descend 8/30, hollow --descend 6, --ui-check, оба проекта --check — без ошибок. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 22:31:34 +07:00
{"straggleDist", &Tuning::straggleDist, 0.2f, 1.0f, 12.0f},
{"navStuckTime", &Tuning::navStuckTime, 0.05f, 0.05f, 3.0f},
{"navStuckSpeed", &Tuning::navStuckSpeed, 0.05f, 0.02f, 1.0f},
{"navGoalReached", &Tuning::navGoalReached, 0.05f, 0.05f, 2.0f},
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
{"moveSpeed", &Tuning::moveSpeed, 0.1f, 0.5f, 10.0f},
{"moveSpeedFast", &Tuning::moveSpeedFast, 0.1f, 0.5f, 12.0f},
{"weaponRange", &Tuning::weaponRange, 0.25f, 2.0f, 20.0f},
{"fireInterval", &Tuning::fireInterval, 0.01f, 0.05f, 2.0f},
{"bulletSpeed", &Tuning::bulletSpeed, 1.0f, 5.0f, 60.0f},
{"solveInterval", &Tuning::solveInterval, 0.05f, 0.05f, 2.0f},
{"commitTime", &Tuning::commitTime, 0.05f, 0.0f, 3.0f},
{"hysteresis", &Tuning::hysteresis, 1.0f, 0.0f, 100.0f},
{"cohesionRadius", &Tuning::cohesionRadius, 0.1f, 1.0f, 12.0f},
{"minEngageDist", &Tuning::minEngageDist, 0.1f, 0.0f, 8.0f},
{"sLaneClear", &Tuning::sLaneClear, 5.0f, 0.0f, 400.0f},
{"sCoverage", &Tuning::sCoverage, 1.0f, 0.0f, 200.0f},
{"sBlockAlly", &Tuning::sBlockAlly, 1.0f, 0.0f, 200.0f},
{"sDistCost", &Tuning::sDistCost, 0.5f, 0.0f, 100.0f},
{"sCohesionCost", &Tuning::sCohesionCost, 0.5f, 0.0f, 100.0f},
{"sTooClose", &Tuning::sTooClose, 2.0f, 0.0f, 300.0f},
{"postLeash", &Tuning::postLeash, 0.1f, 0.0f, 10.0f},
{"formSpacing", &Tuning::formationSpacing, 0.05f, 0.4f, 4.0f},
Запуск грузит проект; автопилот --play; строй сжимается в тесноте (M31) ГЛАВНОЕ. "Дольше пяти секунд не продержаться на первом уровне" - это был не баланс, а запуск. По умолчанию useRooms был false: обычный запуск exe собирал ЗАХАРДКОЖЕННУЮ карту прототипа со старой расстановкой тварей и высаживал туда ОДНОГО оперативника с ножом (размер отряда берётся из профиля). Игрок попадал не на первый этаж игры, а вообще не в игру. Теперь проект подключается по умолчанию, а старая карта осталась под флагом --no-rooms. --PLAY. Новый прогон: автопилот ведёт отряд к спуску и дерётся по дороге. Ни --descend, ни --wall не меряли ПРОХОЖДЕНИЕ: первый собирает этажи и ни с кем не воюет, второй ставит отряд в толпу и запрещает двигаться. Жалоба же была именно про проход этажа. Автопилот ходит по полю расстояний до спуска (заливка по проходимым клеткам), целится на несколько клеток вперёд по спуску поля и правит курсором - тем же вводом, каким играет человек. Три версии до рабочей: по прямой упирался в первую стену, по восьми соседям лез в диагональную щель между углами (тело круглое, не пролезает), по соседней клетке дёргался на каждой границе. Одиночка проходит три этажа и гибнет на четвёртом: 1 тварей 3, убил 0, 100% здоровья, света 244 дошёл (78 с) 2 тварей 6, убил 1, 100%, света 155 дошёл (44 с) 3 тварей 6, убил 0, 65%, света 8 дошёл (74 с) 4 тварей 9, убил 5, ВАЙП (37 с) Свет кончается к третьему этажу: расчёт "заряда на четыре пути до спуска" считал прямые пути, а живой отряд ходит петлями. СТРОЙ В ТЕСНОТЕ. Клин шириной в пять тел не проходит в дверь шириной в клетку: бойцы упираются друг в друга, расталкивание держит их на месте, отряд встаёт насмерть. Теперь строй сам сжимается в колонну, когда вокруг тел мало свободных клеток. ЧЕСТНО: это НЕ вылечило проход пятёрки до конца. Автопилот с полным отрядом всё ещё застревает на первом этаже там, где одиночка проходит, и причина не в ширине строя (проверено сжатием в точку и отрядом без клинков). Похоже, нужна нормальная проводка строя по проходам - это отдельная веха, а не патч в конце. Оставляю с числами, а не с обещанием. --accept PASS, все семь проверок зелёные. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 17:20:21 +07:00
{"tightSpacing", &Tuning::tightSpacing, 0.02f, 0.1f, 1.0f},
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
{"anchorSpeed", &Tuning::anchorSpeed, 0.1f, 0.5f, 12.0f},
{"anchorSpeedFast",&Tuning::anchorSpeedFast, 0.1f, 0.5f, 16.0f},
{"anchorLeash", &Tuning::anchorLeash, 0.1f, 0.2f, 12.0f},
{"anchorLeashMax", &Tuning::anchorLeashMax, 0.1f, 0.4f, 16.0f},
{"leashDrag", &Tuning::leashDrag, 0.05f, 0.0f, 1.0f},
{"anchorAccel", &Tuning::anchorAccel, 1.0f, 2.0f, 80.0f},
// --- натяжение и строй ---
{"urgeStart", &Tuning::urgeStartFrac, 0.05f, 0.0f, 0.95f},
{"urgeAttack", &Tuning::urgeAttack, 0.2f, 0.1f, 20.0f},
{"urgeRelease", &Tuning::urgeRelease, 0.2f, 0.1f, 20.0f},
{"sprintAt", &Tuning::sprintAt, 0.02f, 0.05f, 1.0f},
{"holdSpacing", &Tuning::holdSpacing, 0.02f, 0.4f, 2.0f},
{"sprintSpacing", &Tuning::sprintSpacing, 0.02f, 0.4f, 3.0f},
{"sprintStretch", &Tuning::sprintStretch, 0.05f, 0.5f, 4.0f},
{"formMorph", &Tuning::formMorphRate, 0.25f, 0.25f, 20.0f},
// --- разброс и перезарядка ---
{"spreadStand", &Tuning::spreadStandDeg, 0.1f, 0.0f, 20.0f},
{"spreadMove", &Tuning::spreadMoveDeg, 0.1f, 0.0f, 30.0f},
{"spreadRecoil", &Tuning::spreadRecoilDeg, 0.05f, 0.0f, 10.0f},
{"spreadRecMax", &Tuning::spreadRecoilMax, 0.1f, 0.0f, 20.0f},
{"spreadDecay", &Tuning::spreadRecoilDecay,0.2f, 0.1f, 40.0f},
{"spreadScale", &Tuning::spreadScale, 0.05f, 0.0f, 4.0f},
{"magazine", &Tuning::magazineRifle, 1.0f, 1.0f, 200.0f},
{"reloadTime", &Tuning::reloadRifle, 0.05f, 0.1f, 8.0f},
{"bulletKnock", &Tuning::bulletKnock, 0.05f, 0.0f, 6.0f},
{"knockResist", &Tuning::knockResistScale, 0.05f, 0.0f, 2.0f},
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
// --- живучесть и сложность ---
{"agentHP", &Tuning::agentHP, 5.0f, 10.0f, 500.0f},
{"enemyDamage", &Tuning::enemyDamageScale, 0.05f, 0.0f, 4.0f},
{"enemyRespawn", &Tuning::enemyRespawn, 1.0f, 1.0f, 300.0f},
{"enemyWindup", &Tuning::enemyWindupScale, 0.05f, 0.25f, 3.0f},
{"enemyGrabHold", &Tuning::enemyGrabHold, 0.01f, 0.02f, 1.0f},
{"propYield", &Tuning::propYieldRadius, 0.5f, 0.0f, 64.0f},
Прототип боевого ядра: отряд, хоррор-слой, ближний бой Тактический отряд с автоматическим огнём в изометрии, собственный софтверный растеризатор (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
// --- ближний бой ---
{"meleeReach", &Tuning::meleeReach, 0.05f, 0.4f, 4.0f},
{"meleeArc", &Tuning::meleeArcDeg, 5.0f, 20.0f, 300.0f},
{"meleeWindup", &Tuning::meleeWindup, 0.01f, 0.02f, 1.0f},
{"meleeStrike", &Tuning::meleeStrike, 0.01f, 0.02f, 1.0f},
{"meleeRecover", &Tuning::meleeRecover, 0.01f, 0.0f, 1.5f},
{"meleeInterval", &Tuning::meleeInterval, 0.02f, 0.1f, 3.0f},
{"meleeDamage", &Tuning::meleeDamage, 5.0f, 1.0f, 999.0f},
{"meleeLunge", &Tuning::meleeLungeSpeed, 0.1f, 0.0f, 12.0f},
{"meleeKnock", &Tuning::meleeKnockback, 0.1f, 0.0f, 12.0f},
{"meleeCohesion", &Tuning::meleeCohesionRadius, 0.2f, 1.0f, 20.0f},
{"meleeAllyMargin",&Tuning::meleeAllyMargin, 0.02f, 0.0f, 1.0f},
{"meleePredict", &Tuning::meleeStartPredict,0.1f, 0.0f, 3.0f},
{"meleeReserve", &Tuning::meleeReserveFrac, 0.05f, 0.0f, 1.5f},
{"meleeShake", &Tuning::meleeShake, 0.1f, 0.0f, 6.0f},
// --- хоррор-часть ---
{"sightRadius", &Tuning::sightRadius, 0.25f, 2.0f, 24.0f},
{"sightCone", &Tuning::sightConeDeg, 5.0f, 20.0f, 360.0f},
{"sightPersonal", &Tuning::sightPersonalRadius, 0.1f, 0.5f, 10.0f},
{"fogMemory", &Tuning::fogMemoryLevel, 0.02f, 0.0f, 1.0f},
{"fogReveal", &Tuning::fogEntityReveal, 0.02f, 0.0f, 1.0f},
{"flashPower", &Tuning::flashPower, 0.05f, 0.0f, 2.0f},
{"lightGamma", &Tuning::lightGamma, 0.05f, 0.3f, 4.0f},
{"lightGain", &Tuning::lightMaxGain, 0.05f, 0.2f, 3.0f},
{"fxIntensity", &Tuning::fxIntensity, 0.1f, 0.0f, 4.0f},
{"tracerLength", &Tuning::tracerLength, 0.1f, 0.1f, 6.0f},
{"grainTense", &Tuning::grainTense, 1.0f, 0.0f, 100.0f},
{"vigDepthTense", &Tuning::vigDepthTense, 0.05f, 0.0f, 1.0f},
{"dreadRadius", &Tuning::dreadRadius, 0.5f, 1.0f, 20.0f},
{"shakePerShot", &Tuning::shakePerShot, 0.05f, 0.0f, 4.0f},
// --- кровь, дымка, пепел ---
{"bloodAmount", &Tuning::bloodAmount, 0.05f, 0.0f, 3.0f},
{"bloodOpacity", &Tuning::bloodOpacity, 0.02f, 0.0f, 1.0f},
{"bloodDrops", &Tuning::bloodDrops, 1.0f, 0.0f, 16.0f},
{"hazeAmount", &Tuning::hazeAmount, 0.02f, 0.0f, 2.0f},
{"hazeScale", &Tuning::hazeScale, 0.05f, 0.2f, 4.0f},
{"hazeDrift", &Tuning::hazeDrift, 0.01f, 0.0f, 1.0f},
{"hazeGate", &Tuning::hazeGate, 1.0f, 0.0f, 128.0f},
{"ashDensity", &Tuning::ashDensity, 0.05f, 0.0f, 4.0f},
{"ashSpeed", &Tuning::ashSpeed, 0.05f, 0.0f, 3.0f},
{"ashBright", &Tuning::ashBrightness, 0.05f, 0.0f, 2.0f},
{"ashLift", &Tuning::ashLiftPx, 1.0f, 0.0f, 40.0f},
};
inline constexpr int TUNABLE_COUNT = int(sizeof(TUNABLES) / sizeof(TUNABLES[0]));