#pragma once // ----------------------------------------------------------------------------- // Метрики корректности (спек 7): // FRIENDLY_HITS — накопительный счётчик попаданий по своим (главная метрика); // FIRE UPTIME — доля времени с валидной линией за последние 10 сек; // SETTLE TIME — от перехода в HOLDING до момента, когда >=4 агента в бою. // // С появлением ближнего боя два уточнения, иначе цифры врут: // * FIRE UPTIME усредняется ТОЛЬКО по стрелкам. У бойца с клинком линии огня // не бывает по определению, и его ноль занижал бы метрику стрельбы; // * «в бою» (для SETTLE TIME) = стрелок при линии ИЛИ клинок при цели. // Отдельно считается MELEE UPTIME — доля времени в зоне удара. // ----------------------------------------------------------------------------- #include "sim/squad.h" constexpr int UPTIME_SAMPLES = 600; // 10 сек при 60 Гц // Журнал попаданий по своим: без него FRIENDLY_HITS != 0 нечем объяснить. struct FriendlyHitEvent { float time = 0.0f; int shooter = -1; int victim = -1; Vec2 pos{}; bool victimAdvancing = false; }; constexpr int HIT_LOG_MAX = 16; struct Metrics { int friendlyHits = 0; int targetsKilled = 0; int shotsFired = 0; // ближний бой int meleeSwings = 0; // взмахов начато int meleeHits = 0; // попаданий лезвием int meleeAborts = 0; // взмахов оборвано из-за своего в дуге // Сколько взмахов пришлось на каждый удар связки. Если серия не // прокручивается, здесь будет ноль во всех ячейках кроме первой. int meleeComboSwings[MELEE_COMBO_STEPS] = {}; // На сколько градусов попадание лезвия вышло за РАЗРЕШЁННЫЙ сектор этого // взмаха (полусектор + полуширина полосы). Должно быть ~0: если лезвие // достаёт за спину, здесь вырастет десятки градусов. Ровно этим и выглядел // баг «бьёт на 360». float meleeMaxHitExcessDeg = 0.0f; int reloads = 0; // сколько раз стрелки уходили в перезарядку // Сколько расходников потрачено за прогон. Ноль при раненом отряде и полных // сумках означает, что расходники снова стали мёртвым грузом. int itemsUsed = 0; FriendlyHitEvent hitLog[HIT_LOG_MAX]; int hitLogCount = 0; float clock = 0.0f; // время симуляции, для журнала void LogFriendlyHit(int shooter, int victim, Vec2 pos, bool victimAdvancing); // FIRE UPTIME: кольцевой буфер + инкрементальные счётчики unsigned char ring[NUM_AGENTS][UPTIME_SAMPLES] = {}; int ringPos = 0; int ringFill = 0; int laneCount[NUM_AGENTS] = {}; float uptimePct[NUM_AGENTS] = {}; float uptimeAvg = 0.0f; // только по стрелкам float meleeUptimeAvg = 0.0f; // только по клинкам: доля времени в зоне удара // SETTLE TIME bool settleArmed = false; float settleClock = 0.0f; float lastSettle = -1.0f; // производительность (заполняется главным циклом) float simMs = 0.0f; float renderMs = 0.0f; AgentState prevState = AgentState::HOLDING; void Reset(); void Sample(const Squad& squad, float dt); };