squad-proto/src/ai/firing_solver.h

69 lines
3.5 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
// -----------------------------------------------------------------------------
// Ядро прототипа (спек 6.36.5): проверка линии огня, выбор цели,
// решатель огневых позиций с гистерезисом.
//
// Всё здесь — чистые функции над состоянием мира. Модуль ai/ НЕ знает
// ни про рендер, ни про изометрию, ни про ввод.
// -----------------------------------------------------------------------------
#include <vector>
#include "ai/lane_registry.h"
#include "sim/agent.h"
#include "sim/target.h"
#include "world/tilemap.h"
struct SolverContext
{
const Tilemap* map = nullptr;
const Agent* agents = nullptr;
int agentCount = 0;
const std::vector<Target>* targets = nullptr;
const LaneRegistry* lanes = nullptr;
Vec2 anchor{};
int assignedTargetId = -1; // ручной режим (6.8); -1 = авто
};
// --- 6.3 ---------------------------------------------------------------------
// Линия от дула стрелка (в точке from, ствол смотрит на цель) до targetPos.
// Порядок проверок как в спеке: свои -> геометрия -> дистанция.
bool LaneClearFrom(Vec2 from, int shooterIndex, Vec2 targetPos, const SolverContext& ctx);
// Успеет ли союзник влезть под пулю за время её полёта.
//
// 6.3 проверяет линию в МОМЕНТ выстрела, а 6.9 делает пулю настоящим снарядом:
// до 0.4 с полёта, за которые союзник проходит больше метра. Реактивного
// расступания (6.6) тут не хватает — союзник может быть чист в момент нажатия
// и войти в коридор уже потом. Поэтому спуск дополнительно проверяет
// сближение пули и союзника как двух равномерно движущихся точек.
// Само правило 6.3 не ослабляется: см. docs/08-decisions.md.
bool AllyInterceptsShot(Vec2 muzzle, Vec2 targetPos, int shooterIndex, const SolverContext& ctx);
// --- выбор цели (6.4 / 6.8) --------------------------------------------------
// Заполняет currentTargetId, hasLane, priorityLane и диагностику для оверлея.
void EvaluateAgentLane(Agent& a, int index, const SolverContext& ctx);
// Ближайшая живая мишень в радиусе searchRadius (или -1).
int NearestAliveTarget(Vec2 from, const SolverContext& ctx, float searchRadius);
// --- 6.5 ---------------------------------------------------------------------
struct SolveResult
{
Vec2 candidates[SOLVER_CANDIDATES]{};
float scores[SOLVER_CANDIDATES]{};
bool valid[SOLVER_CANDIDATES]{};
int count = 0;
int bestIndex = -1;
float bestScore = 0.0f;
float currentScore = 0.0f;
bool committed = false;
int bestTargetId = -1;
};
float ScoreCandidate(Vec2 cand, const Agent& a, int index, const SolverContext& ctx,
int bestTargetId);
// true, если решатель отработал (нашёл bestTarget и оценил кандидатов).
bool SolveFiringPosition(Agent& a, int index, const SolverContext& ctx, SolveResult& out);