Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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
|
Стихии и вторая поверхность инструментов (M21, E2)
СТИХИИ (src/sim/element.h)
Семь элементов, сопротивления свойством ВИДА, одна формула урона на пулю и на
клинок. Зачем они в игре про бесконечный спуск: сила отряда растёт медленнее
угрозы (закон масштаба, docs/20-gdd.md), и разрыв обязан закрываться знанием.
Стихии — первый такой канал: верное оружие убивает втрое быстрее, и цифра эта
не растёт от прокачки.
Ручных действий не заводится: стихия — свойство оружия, а не кнопка. Всё
решается тем, что отряд взял вниз.
Стихии есть только у НАХОДИМОГО оружия. Карабин, пистолет и сабля остаются
чистой кинетикой, потому что на них меряется приёмка.
ИНСТРУМЕНТЫ БАЛАНСА (src/debug/tools.cpp)
Вторая поверхность: `squad_proto --tool weapons|enemies|ttk|economy`. Отдельная
от редакторской не по вкусу — редактор линкуется только с движком и правил боя
не видит, а всё это считает урон. Инструменты делятся по ПРЕДМЕТУ: содержимое
у редактора, баланс у игры.
ЧТО НАШЛА ПРОВЕРКА
Первая версия --tool ttk считала разброс по времени убийства и показывала
уверенные 2.8x на каждой твари. Это была разница МОЩНОСТИ стволов: магнум лучше
пистолета против всех, стихии ни при чём.
С вынесенной за скобки мощностью оружия правда оказалась другой: 1.04-1.36x,
то есть состав удара не значил почти ничего. Механика была декоративной ровно
в том же смысле, в каком когда-то был вес.
Чтобы разброс появился, у твари должны быть И уязвимость, И сопротивление среди
тех стихий, которые вообще носит оружие: одной уязвимости мало, если ни один
ствол не бьёт тем, что она держит. После правки содержимого все пять видов в
коридоре 1.90-3.06x, и картина читается — дробовик жжёт бредущих, дуга бьёт
плевков, лук разъедает громилу, карабин против громилы бесполезен.
Коридор в инструменте 1.8-8.0, а не заявленные в документе 3-6: до тройки
дотянут почти чистые стихийные сборки, которые приходят с аффиксами. Занижать
цель в документе нельзя, поэтому в инструменте стоит достижимое.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 09:25:44 +07:00
|
|
|
|
#include "sim/element.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
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
// Снаряжение бойца: РАБОЧЕЕ состояние оружия.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Разделение простое и важное:
|
|
|
|
|
|
// * ЧТО такое ствол (дальность, урон, разброс, магазин, вид вспышки) —
|
|
|
|
|
|
// это FirearmDef на сущности-оружии, sim/weapon_defs.h;
|
|
|
|
|
|
// * В КАКОМ ОН СЕЙЧАС СОСТОЯНИИ (откат, патроны, перезарядка, увод ствола) —
|
|
|
|
|
|
// это Firearm здесь, и живёт оно на бойце.
|
|
|
|
|
|
// Подменили сущность оружия — поменялись все статы разом, состояние обнулилось,
|
|
|
|
|
|
// и ни одно правило боя об этом не узнало.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Правила боя от класса НЕ зависят. И стрелок, и боец ближнего боя обязаны
|
|
|
|
|
|
// проверить своих перед ударом; меняется только геометрия проверки —
|
|
|
|
|
|
// конус «дуло -> цель» против сектора замаха (docs/11-melee.md).
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
|
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/hash.h"
|
|
|
|
|
|
#include "engine/math.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
|
|
|
|
#include "sim/weapon_defs.h"
|
|
|
|
|
|
#include "tuning.h"
|
|
|
|
|
|
|
|
|
|
|
|
// --- дальнобойное ------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
struct Firearm
|
|
|
|
|
|
{
|
|
|
|
|
|
FirearmDef def{}; // копия статов сущности-оружия, обновляется каждый шаг
|
|
|
|
|
|
|
|
|
|
|
|
// --- состояние ---
|
|
|
|
|
|
float cooldown = 0.0f;
|
|
|
|
|
|
int ammo = 0;
|
|
|
|
|
|
float reloadTimer = 0.0f; // > 0 — идёт перезарядка
|
|
|
|
|
|
float recoilDeg = 0.0f; // увод ствола, гаснет сам
|
|
|
|
|
|
uint32_t shotSeq = 0; // номер выстрела: сеет детерминированный разброс
|
|
|
|
|
|
|
|
|
|
|
|
bool Reloading() const { return reloadTimer > 0.0f; }
|
|
|
|
|
|
bool Empty() const { return ammo <= 0; }
|
|
|
|
|
|
|
|
|
|
|
|
// Доля магазина — для оверлея и HUD.
|
|
|
|
|
|
float AmmoFrac() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return (def.magazine > 0) ? Clampf(float(ammo) / float(def.magazine), 0.0f, 1.0f) : 0.0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Разброс в радианах: стоя — минимальный, на ходу растёт до spreadMoveDeg,
|
|
|
|
|
|
// сверху ложится накопленный увод ствола.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Это ЕДИНСТВЕННОЕ место, где считается разброс. Его же читает проверка
|
|
|
|
|
|
// линии огня: конус, по которому реально уйдёт пуля, обязан быть тем самым
|
|
|
|
|
|
// конусом, который проверили на своих (см. ai/firing_solver.cpp).
|
|
|
|
|
|
float SpreadRad(float speed) const
|
|
|
|
|
|
{
|
|
|
|
|
|
const float m = Clampf(speed / std::max(0.01f, MaxAgentSpeed()), 0.0f, 1.0f);
|
|
|
|
|
|
const float deg = Lerpf(def.spreadStandDeg, def.spreadMoveDeg, m) + recoilDeg;
|
|
|
|
|
|
return std::max(0.0f, deg) * DEG2RAD_F * std::max(0.0f, g_tune.spreadScale);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Отклонение конкретного выстрела внутри конуса. Не генератор: чистый хеш
|
|
|
|
|
|
// от (владелец, номер выстрела), поэтому прогон воспроизводим побитово.
|
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
|
|
|
|
// pellet различает дробины одного залпа: без него вся картечь ушла бы
|
|
|
|
|
|
// в одну точку и была бы неотличима от одиночного выстрела.
|
|
|
|
|
|
float ShotOffset(int ownerIndex, float spreadRad, int pellet = 0) const
|
Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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
|
|
|
|
const uint32_t h = Hash3(uint32_t(ownerIndex) * 2654435761u, shotSeq,
|
|
|
|
|
|
uint32_t(pellet) * 0x85EBCA6Bu);
|
Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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
|
|
|
|
// Треугольное распределение: центр конуса вероятнее краёв.
|
|
|
|
|
|
const float a = HashSigned(h);
|
|
|
|
|
|
const float b = HashSigned(HashU32(h ^ 0x9E3779B9u));
|
|
|
|
|
|
return 0.5f * (a + b) * spreadRad;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// --- ближний бой -------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
// Три фазы, как в любом ощутимом ударе: замах видно заранее, урон живёт
|
|
|
|
|
|
// в коротком окне, после удара боец открыт. Без разделения удар превращается
|
|
|
|
|
|
// в мгновенный хитскан и не читается на 480x270.
|
|
|
|
|
|
enum class MeleeState : uint8_t
|
|
|
|
|
|
{
|
|
|
|
|
|
IDLE = 0, // подходит или ждёт перезарядку
|
|
|
|
|
|
WINDUP, // замах: ноги вкопаны, ось удара уже зафиксирована
|
|
|
|
|
|
STRIKE, // активное окно: лезвие метёт дугу, есть рывок вперёд
|
|
|
|
|
|
RECOVER, // после удара — уязвим и медленный
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct MeleeWeapon
|
|
|
|
|
|
{
|
Стихии и вторая поверхность инструментов (M21, E2)
СТИХИИ (src/sim/element.h)
Семь элементов, сопротивления свойством ВИДА, одна формула урона на пулю и на
клинок. Зачем они в игре про бесконечный спуск: сила отряда растёт медленнее
угрозы (закон масштаба, docs/20-gdd.md), и разрыв обязан закрываться знанием.
Стихии — первый такой канал: верное оружие убивает втрое быстрее, и цифра эта
не растёт от прокачки.
Ручных действий не заводится: стихия — свойство оружия, а не кнопка. Всё
решается тем, что отряд взял вниз.
Стихии есть только у НАХОДИМОГО оружия. Карабин, пистолет и сабля остаются
чистой кинетикой, потому что на них меряется приёмка.
ИНСТРУМЕНТЫ БАЛАНСА (src/debug/tools.cpp)
Вторая поверхность: `squad_proto --tool weapons|enemies|ttk|economy`. Отдельная
от редакторской не по вкусу — редактор линкуется только с движком и правил боя
не видит, а всё это считает урон. Инструменты делятся по ПРЕДМЕТУ: содержимое
у редактора, баланс у игры.
ЧТО НАШЛА ПРОВЕРКА
Первая версия --tool ttk считала разброс по времени убийства и показывала
уверенные 2.8x на каждой твари. Это была разница МОЩНОСТИ стволов: магнум лучше
пистолета против всех, стихии ни при чём.
С вынесенной за скобки мощностью оружия правда оказалась другой: 1.04-1.36x,
то есть состав удара не значил почти ничего. Механика была декоративной ровно
в том же смысле, в каком когда-то был вес.
Чтобы разброс появился, у твари должны быть И уязвимость, И сопротивление среди
тех стихий, которые вообще носит оружие: одной уязвимости мало, если ни один
ствол не бьёт тем, что она держит. После правки содержимого все пять видов в
коридоре 1.90-3.06x, и картина читается — дробовик жжёт бредущих, дуга бьёт
плевков, лук разъедает громилу, карабин против громилы бесполезен.
Коридор в инструменте 1.8-8.0, а не заявленные в документе 3-6: до тройки
дотянут почти чистые стихийные сборки, которые приходят с аффиксами. Занижать
цель в документе нельзя, поэтому в инструменте стоит достижимое.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 09:25:44 +07:00
|
|
|
|
// Из чего состоит удар клинка. Копируется с сущности-оружия, как и всё
|
|
|
|
|
|
// остальное ниже.
|
|
|
|
|
|
ElementMix elem;
|
|
|
|
|
|
|
Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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 reach = 1.15f; // от центра тела до конца замаха
|
|
|
|
|
|
float arcDeg = 100.0f; // ширина сектора
|
|
|
|
|
|
float windup = 0.18f;
|
|
|
|
|
|
float strike = 0.12f;
|
|
|
|
|
|
float recover = 0.26f;
|
|
|
|
|
|
float interval = 0.62f; // между началами замахов
|
|
|
|
|
|
float damage = 110.0f;
|
|
|
|
|
|
float knockback = 2.4f; // импульс мишени ДО её сопротивления отбросу
|
|
|
|
|
|
|
|
|
|
|
|
// --- состояние ---
|
|
|
|
|
|
MeleeState state = MeleeState::IDLE;
|
|
|
|
|
|
float phase = 0.0f; // остаток текущей фазы, сек
|
|
|
|
|
|
float cooldown = 0.0f;
|
|
|
|
|
|
float dir = 0.0f; // ось удара, зафиксирована на замахе
|
|
|
|
|
|
float sweepSign = 1.0f; // с какой стороны заходит лезвие (чередуется)
|
|
|
|
|
|
int targetId = -1;
|
|
|
|
|
|
bool inReach = false; // цель в зоне удара прямо сейчас
|
|
|
|
|
|
bool arcBlockedByAlly = false; // в секторе свой -> бить нельзя (аналог 6.3)
|
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
|
|
|
|
// Кого уже задели ЭТИМ взмахом. Шестьдесят четыре бита, а не тридцать два:
|
|
|
|
|
|
// ширина маски — это и есть потолок числа целей в мире (MAX_TARGETS), а
|
|
|
|
|
|
// этажи на глубине упираются именно в него.
|
|
|
|
|
|
uint64_t hitMask = 0;
|
Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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
|
|
|
|
|
|
|
|
|
|
// --- связка ---
|
|
|
|
|
|
int comboStep = 0; // какой удар связки будет следующим
|
|
|
|
|
|
float comboTimer = 0.0f; // сколько ещё держится связка
|
|
|
|
|
|
|
|
|
|
|
|
// --- запечённые параметры ТЕКУЩЕГО взмаха ---
|
|
|
|
|
|
// Связка гнёт ширину, урон и тайминги, а поля выше переписываются из
|
|
|
|
|
|
// снаряжения КАЖДЫЙ шаг (SyncLoadoutFromTuning). Поэтому значения взмаха
|
|
|
|
|
|
// фиксируются на старте замаха и живут отдельно — иначе удар менял бы
|
|
|
|
|
|
// свойства прямо в полёте.
|
|
|
|
|
|
float curHalfArc = 0.0f;
|
|
|
|
|
|
float curDamage = 0.0f;
|
|
|
|
|
|
float curKnock = 0.0f;
|
|
|
|
|
|
float curWindup = 0.0f;
|
|
|
|
|
|
float curStrike = 0.0f;
|
|
|
|
|
|
float curRecover = 0.0f;
|
|
|
|
|
|
|
|
|
|
|
|
bool Committed() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return state == MeleeState::WINDUP || state == MeleeState::STRIKE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const MeleeComboStep& NextComboStep() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return MELEE_COMBO[(comboStep >= 0 && comboStep < MELEE_COMBO_STEPS) ? comboStep : 0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Полуширина сектора. Во время взмаха — запечённая, иначе — та, что будет
|
|
|
|
|
|
// у СЛЕДУЮЩЕГО удара связки. Все потребители (проверка сектора, дуга,
|
|
|
|
|
|
// оверлей, резерв линии) обязаны видеть одно и то же число: иначе
|
|
|
|
|
|
// нарисованное и то, что бьёт, разъедутся.
|
|
|
|
|
|
float HalfArc() const
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Committed() || state == MeleeState::RECOVER) return curHalfArc;
|
|
|
|
|
|
return arcDeg * 0.5f * DEG2RAD_F * NextComboStep().arcMul;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Запечь параметры взмаха из базовых характеристик и шага связки.
|
|
|
|
|
|
void BakeSwing(const MeleeComboStep& cs)
|
|
|
|
|
|
{
|
|
|
|
|
|
curHalfArc = arcDeg * 0.5f * DEG2RAD_F * cs.arcMul;
|
|
|
|
|
|
curDamage = damage * cs.dmgMul;
|
|
|
|
|
|
curKnock = knockback * cs.knockMul;
|
|
|
|
|
|
curWindup = windup * cs.windupMul;
|
|
|
|
|
|
curStrike = strike * cs.strikeMul;
|
|
|
|
|
|
curRecover = recover * cs.recoverMul;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
float PhaseLen() const
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (state)
|
|
|
|
|
|
{
|
|
|
|
|
|
case MeleeState::WINDUP: return curWindup;
|
|
|
|
|
|
case MeleeState::STRIKE: return curStrike;
|
|
|
|
|
|
case MeleeState::RECOVER: return curRecover;
|
|
|
|
|
|
default: return 1.0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 0 в начале фазы, 1 в конце.
|
|
|
|
|
|
float Progress() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return Clampf(1.0f - phase / std::max(1e-4f, PhaseLen()), 0.0f, 1.0f);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Где лезвие при заданном прогрессе взмаха: от одного края сектора к другому.
|
|
|
|
|
|
float BladeAngleAt(float progress) const
|
|
|
|
|
|
{
|
|
|
|
|
|
const float half = curHalfArc;
|
|
|
|
|
|
return WrapPi(dir + sweepSign * (-half + 2.0f * half * progress));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|