squad-proto/src/debug/metrics.cpp
z.kirill 7880f275bd Прототип боевого ядра: отряд, хоррор-слой, ближний бой
Тактический отряд с автоматическим огнём в изометрии, собственный
софтверный растеризатор (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 13:00:49 +03:00

104 lines
3.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "debug/metrics.h"
void Metrics::LogFriendlyHit(int shooter, int victim, Vec2 pos, bool victimAdvancing)
{
++friendlyHits;
if (hitLogCount < HIT_LOG_MAX)
hitLog[hitLogCount++] = FriendlyHitEvent{clock, shooter, victim, pos, victimAdvancing};
}
void Metrics::Reset()
{
friendlyHits = 0;
hitLogCount = 0;
clock = 0.0f;
targetsKilled = 0;
shotsFired = 0;
meleeSwings = 0;
meleeHits = 0;
meleeAborts = 0;
for (int& v : meleeComboSwings) v = 0;
meleeMaxHitExcessDeg = 0.0f;
reloads = 0;
for (int i = 0; i < NUM_AGENTS; ++i)
{
laneCount[i] = 0;
uptimePct[i] = 0.0f;
for (int k = 0; k < UPTIME_SAMPLES; ++k) ring[i][k] = 0;
}
ringPos = 0;
ringFill = 0;
uptimeAvg = 0.0f;
meleeUptimeAvg = 0.0f;
settleArmed = true; // старт — отряд уже стоит, замеряем первое расхождение
settleClock = 0.0f;
lastSettle = -1.0f;
prevState = AgentState::HOLDING;
simMs = 0.0f;
renderMs = 0.0f;
}
void Metrics::Sample(const Squad& squad, float dt)
{
clock += dt;
// --- FIRE UPTIME (скользящее окно) ---
const int window = std::min(UPTIME_SAMPLES,
std::max(1, int(g_tune.uptimeWindow / FIXED_DT)));
if (ringPos >= window) ringPos = 0;
// «В бою» для стрелка — валидная линия огня, для клинка — цель в зоне удара.
// Считаются они одинаково, а усредняются раздельно: смешивать доли времени
// двух разных занятий в одно число бессмысленно.
int readyNow = 0;
for (int i = 0; i < NUM_AGENTS; ++i)
{
const Agent& a = squad.agents[i];
const bool engaged = a.IsMelee() ? a.melee.inReach : a.hasLane;
const unsigned char v = engaged ? 1u : 0u;
laneCount[i] -= ring[i][ringPos];
ring[i][ringPos] = v;
laneCount[i] += v;
// Для SETTLE TIME клинку достаточно ИМЕТЬ цель: он к ней ещё идёт,
// но отряд уже разобрал обстановку, а именно это метрика и меряет.
if (a.IsMelee() ? (a.melee.targetId >= 0) : a.hasLane) ++readyNow;
}
ringPos = (ringPos + 1) % window;
ringFill = std::min(ringFill + 1, window);
float rangedSum = 0.0f;
float meleeSum = 0.0f;
for (int i = 0; i < NUM_AGENTS; ++i)
{
uptimePct[i] = (ringFill > 0) ? (100.0f * float(laneCount[i]) / float(ringFill)) : 0.0f;
if (squad.agents[i].IsMelee()) meleeSum += uptimePct[i];
else rangedSum += uptimePct[i];
}
const int ranged = squad.RangedCount();
uptimeAvg = (ranged > 0) ? rangedSum / float(ranged) : 0.0f;
meleeUptimeAvg = (squad.meleeCount > 0) ? meleeSum / float(squad.meleeCount) : 0.0f;
// --- SETTLE TIME ---
if (squad.state == AgentState::HOLDING && prevState == AgentState::ADVANCING)
{
settleArmed = true;
settleClock = 0.0f;
}
if (squad.state == AgentState::ADVANCING) settleArmed = false;
if (settleArmed)
{
settleClock += dt;
if (float(readyNow) >= g_tune.settleQuorum)
{
lastSettle = settleClock;
settleArmed = false;
}
}
prevState = squad.state;
}