#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; }