#pragma once #include #include "engine/config.h" // ----------------------------------------------------------------------------- // Единственное место, где живут числа ИГРЫ. // В коде не должно быть магических констант — всё берётся отсюда. // // * блок COMPILE-TIME — размеры буферов игры, менять только пересборкой; // * struct Tuning — всё, что подкручивается в рантайме ([ ] выбор, - = шаг). // // Размеры кадра, тайла и карты сюда БОЛЬШЕ НЕ ВХОДЯТ: это хозяйство движка, оно // в engine/config.h. Имена те же, файл включён выше — по коду игры ничего не // меняется, но движок теперь собирается без единого файла игры. // ----------------------------------------------------------------------------- // --- compile-time константы -------------------------------------------------- // Точка старта отряда (центральная арена). constexpr float SQUAD_START_X = 26.5f; // ближе к кучке: дальность стала 7.2 constexpr float SQUAD_START_Y = 26.5f; constexpr int NUM_AGENTS = 5; constexpr int NUM_TARGETS = 12; // мишени приёмки: расстановка захардкожена constexpr int MAX_BULLETS = 512; // Твари, которых боевой режим доставляет СВЕРХ мишеней. В песочнице приёмки их // нет: расстановка мишеней и все замеры на ней остаются прежними. constexpr int NUM_EXTRA_ENEMIES = 12; // Разрушаемые объекты (ящики, бочки, шкафчики). Спавнятся ТОЛЬКО в боевом режиме; // приёмка их не видит. Приоритет — ниже всех: ИИ переключается на них только // если ни одного живого врага в поле зрения нет. constexpr int NUM_PROPS = 8; // Потолок на число целей в мире. Он не «на всякий случай»: MeleeWeapon::hitMask — // это маска «кого уже задел ЭТИМ взмахом», индексируемая номером цели, и цель // за её пределами молча получала бы удары дважды за взмах. // // Было 32. Игра про бесконечный спуск упёрлась в этот потолок примерно на // шестнадцатом этаже: бюджет опасности перестал влезать в число тел, и глубже // этажи переставали становиться страшнее. Маска расширена до 64 бит — это не // «запас на будущее», а ровно та цифра, которой сейчас не хватало. constexpr int MAX_TARGETS = 64; static_assert(NUM_TARGETS + NUM_EXTRA_ENEMIES + NUM_PROPS <= MAX_TARGETS, "целей больше, чем битов в MeleeWeapon::hitMask"); // У движка потолок населения свой (MAX_LEVEL_SPAWNS в engine/level_gen.h): // генератор уровней не обязан знать про маску удара клинком. Что одно влезает // в другое, проверяется там, где видны оба, — в src/game.h. // Сколько слотов строя занимают бойцы ближнего боя (слоты 0..N-1). // В клине это остриё и левое крыло — те, кто и так идёт первым. constexpr int DEFAULT_MELEE_COUNT = 2; // Сколько ПОСЛЕДНИХ слотов носят пистолет вместо карабина. // Короткий ствол уезжает в хвост клина: ему там и место. constexpr int DEFAULT_PISTOL_COUNT = 1; // Сколько тайлов пола могут нести запечённую кровь. Каждый — своя копия // спрайта 32x16 (2 КБ), то есть потолок ~2.4 МБ. Дальше новые пятна ложатся // только на уже запачканные тайлы: декали вечные, но не бесконечные. constexpr int BLOOD_TILE_BUDGET = 1200; // Решатель огневых позиций: 3 кольца x 12 направлений + текущая позиция. constexpr int SOLVER_RINGS = 3; constexpr int SOLVER_DIRS = 12; constexpr int SOLVER_CANDIDATES = SOLVER_RINGS * SOLVER_DIRS + 1; // --- runtime-тюнинг ---------------------------------------------------------- struct Tuning { // движение агента // Скорость отряда. Была 1.75 при курсоре 3.3: отряд ВСЕГДА отставал, и // управление ощущалось как «тяну за верёвку», а не «веду людей». Поднято до // 2.6 — это по-прежнему медленнее курсора (иначе поводок теряет смысл), но // разрыв закрывается за доли секунды, а не за метры. float moveSpeed = 2.60f; // юнитов/сек: хоррор, а не шутер-беготня float moveSpeedFast = 4.4f; // догоняют курсор быстро — иначе отряд отстаёт float steerAccel = 26.0f; // тяжелее; НИЖЕ 16 нельзя — ломается критерий 11.4 float bodyRadius = 0.28f; float separationRadius = 0.7f; float wSeek = 1.0f; // Как быстро строй доворачивается к направлению хода. Мгновенный доворот // заставлял бойцов бегать вбок на каждом повороте (sim/squad.cpp). float formTurnRate = 7.0f; float wSeparation = 1.0f; float wLaneAvoid = 2.4f; // строго выше wSeparation (спек 6.7) float wWallAvoid = 1.2f; float wallLookahead = 0.8f; float wallSlide = 1.4f; // касательная составляющая обхода стен float arriveRadius = 0.18f; float turnRateDeg = 300.0f; // ствол тяжёлый float wTargetPush = 1.0f; // мишень — тело, а не пустое место float targetPushGap = 0.10f; // зазор поверх суммы радиусов // оружие (это ПАРАМЕТРЫ КАРАБИНА — оружия по умолчанию, на котором меряются // критерии приёмки. Остальные стволы — записи каталога sim/weapon_defs.h) float weaponRange = 7.2f; // -20% от исходных 9.0 float fireInterval = 0.35f; float laneHalfWidth = 0.10f; float damage = 12.0f; float safetyMargin = 0.15f; float muzzleOffset = 0.3f; float bulletSpeed = 22.0f; float bulletRadius = 0.04f; float bulletKnock = 0.35f; // импульс мишени от попадания пули float aimToleranceDeg = 5.0f; // ствол должен быть доведён до цели // --- разброс ------------------------------------------------------------- // Стоя ствол почти не гуляет, на полном ходу гуляет сильно, очередь его // раскрывает. Тот же конус проверяется на своих перед спуском: правило 6.3 // не ослаблено, оно просто считает по конусу вместо отрезка. float spreadStandDeg = 0.9f; float spreadMoveDeg = 4.2f; float spreadRecoilDeg = 0.55f; // прибавка за выстрел float spreadRecoilMax = 3.0f; float spreadRecoilDecay = 3.2f; // градусов/сек оседания float spreadScale = 1.0f; // общий множитель, ручка для замеров // --- перезарядка --------------------------------------------------------- float magazineRifle = 24.0f; // патронов в магазине карабина float reloadRifle = 1.9f; // сек // Запас на манёвр союзника за время полёта пули (доля от moveSpeed). // 0 = верить экстраполяции скорости буквально. float shotPredictSlack = 0.3f; // --- ближний бой --------------------------------------------------------- // Три фазы: замах видно, урон живёт в коротком окне, после удара боец открыт. float meleeReach = 1.40f; // от центра тела до конца замаха float meleeArcDeg = 100.0f; // ширина сектора удара float meleeWindup = 0.17f; // замах — телеграф float meleeStrike = 0.13f; // активное окно урона float meleeRecover = 0.24f; // после удара float meleeInterval = 0.60f; // между началами замахов float meleeDamage = 130.0f; // 4 удара на мишень против 42 пуль float meleeBladeDeg = 34.0f; // ширина полосы самого лезвия float meleeAimToleranceDeg = 30.0f; // допуск разворота на старте замаха float meleeAllyMargin = 0.14f; // запас к телу союзника в секторе // На сколько взмахов вперёд смотреть на союзника при старте замаха // (в долях windup+strike). 0 = верить только текущему положению. float meleeStartPredict = 1.0f; // Доля бокового размаха сектора, которую клинок резервирует в реестре линий. // 1.0 = вся дуга; тогда своим нечем дышать. 0 = резерв шириной с отрезок. float meleeReserveFrac = 0.55f; float meleeGap = 0.12f; // зазор при подходе вплотную float meleeLungeSpeed = 3.4f; // рывок в момент удара, юнитов/сек float meleeLungeAccel = 120.0f; // рывок обязан успеть за 0.13 с float meleeWindupScale = 0.20f; // множитель скорости на замахе float meleeRecoverScale= 0.55f; float meleeKnockback = 2.4f; // импульс мишени, юнитов/сек // СВОЙ круг бойца: его отпускают от диска дальше, чем стрелка. float meleeCohesionRadius = 5.6f; float meleeSeekRadius = 7.0f; // дальше этого боец за целью не идёт вообще // Штраф за уже занятую цель, в юнитах дистанции. Двое с клинками, вцепившиеся // в одного, половину взмахов обрывают друг об друга — пусть расходятся. float meleeClaimPenalty = 3.0f; // Сколько держится связка после удара. Не успел продолжить — начинаешь // серию заново с первого удара. float meleeComboWindow = 1.10f; // --- живучесть отряда и тварей ------------------------------------------- // У мишеней приёмки эти числа не работают: DUMMY не бьёт и не ходит, // поэтому --accept от них не зависит вообще. float agentHP = 100.0f; // запас бойца до брони и оберегов float enemyDamageScale = 1.0f; // общая сложность: во сколько раз больно float enemyRespawn = 16.0f; // через сколько тварь возвращается float enemyLightPower = 0.14f; // как ярко тлеют глаза в темноте // Сколько цель «светится» после попадания. Единственный признак, по которому // в темноте видно, что отряд по кому-то реально работает, а не мажет. float targetHurtTime = 0.22f; // --- расход ------------------------------------------------------------------ // Патроны на вылазку. Не «на этаж»: запас едет вниз вместе с отрядом, и // решение «ещё глубже или наверх» упирается именно в него. float startAmmo = 120.0f; // При какой доле здоровья боец сам возьмётся за бинт и на каком удалении // от ближайшей твари это ему позволено. Лечиться в бою нельзя: перевязка // посреди схватки обесценила бы урон. float healAtFrac = 0.55f; float healSafeDist = 9.0f; // --- спуск, находки, опыт --------------------------------------------------- // Ничего из этого не работает в песочнице приёмки: там нет ни лута, ни // спуска, и `--accept` этих чисел не касается. // // Радиус подбора заметно больше тела бойца: подбор пассивный, и промазать // мимо лежащего предмета, пройдя вплотную, игрок не должен — он ведь по // нему не кликал. float lootPickupRadius = 0.65f; // На каком расстоянии от точки спуска отряд считается дошедшим. Меряется // по ЯКОРЮ, а не по бойцам: спуск — это решение игрока, а не то, что // случается от того, что один боец забрёл не туда. float descendRadius = 1.10f; // Сколько опыта даёт единица опасности убитого. Опыт этажа сверх этого // множится на xpScale из кривой глубины (engine/progression.h). float xpPerDanger = 10.0f; // --- разрушаемая обстановка ------------------------------------------------ // Радиус, в котором ОДНО ПРИСУТСТВИЕ живой твари запрещает бойцу работать // по хламу. Намеренно шире дальности любого ствола: привязать его к дальности // значило бы «пока не достаю — стреляю по ящику», и боец встречал бы // подошедшую тварь с пустым магазином. Ящик ломают, когда вокруг ЧИСТО. float propYieldRadius = 15.0f; // --- атаки тварей --------------------------------------------------------- // Форма каждой атаки (сектор, радиус круга, длина рывка) — свойство ВИДА и // живёт в EnemyDef (sim/target.cpp). Здесь только то, что общее для всех: // именно этими двумя ручками правится «успеваю ли я среагировать». // // enemyWindupScale — множитель ВСЕХ телеграфов сразу. Опускать его ниже // ~0.5 нечестно: замах становится короче времени реакции, и смерть бойца // снова выглядит случайной. float enemyWindupScale = 1.0f; // Сколько захват держится без подтверждения от твари. Тварь каждый шаг // продлевает его на эту величину; убили её — боец освобождается сам, и // ни bullet.cpp, ни клинку не приходится знать про захват вообще. float enemyGrabHold = 0.15f; // мишени float targetHP = 500.0f; float targetRespawn = 3.0f; float targetRadius = 0.34f; float targetKnockDamp = 9.0f; // гашение импульса от удара float targetReturnRate= 5.0f; // возврат на своё место // Общий множитель сопротивления отбросу. Само сопротивление у каждой мишени // своё (sim/target.cpp): тяжёлую клинком не сдвинуть, лёгкая улетает. float knockResistScale = 1.0f; // отряд float holdDelay = 0.22f; // сек без ввода -> HOLDING float cohesionRadius = 2.1f; // стрелки держатся кучнее float minEngageDist = 1.6f; // отмасштабировано под дальность 7.2 float formationSpacing = 1.0f; // строй плотнее float cameraSmooth = 9.0f; float anchorRadius = 0.3f; // якорь тоже скользит вдоль стен // Диск отзывчивее бойцов: команда читается мгновенно, а догоняют её ногами. float anchorSpeed = 3.3f; float anchorSpeedFast = 4.8f; float anchorAccel = 20.0f; // разгон/торможение курсора, юнитов/сек^2 // Поводок УПРУГИЙ, а не жёсткий: без него быстрый диск просто отрывается от // отряда и теряет смысл, но жёсткий упор намертво фиксирует дистанцию, и по // ней уже не отличить «подправил направление» от «тяну изо всех сил». // anchorLeash — мягкая граница: до неё курсор идёт свободно; // anchorLeashMax — жёсткий предел: страховка, до неё обычно не доходит; // leashDrag — насколько тяжелеет курсор между границами (0..1). float anchorLeash = 2.5f; float anchorLeashMax = 4.2f; float leashDrag = 0.75f; // на жёсткой границе остаётся 25% скорости // --- натяжение поводка (см. docs/05-squad-ai.md) -------------------------- // Курсор упёрся в поводок, а игрок продолжает давить -> он хочет идти // быстрее, чем отряд шагает. Это и есть команда «бегом»: отряд ускоряется, // строй растягивается, а на верхней трети шкалы огонь прекращается вовсе. float urgeStartFrac = 0.50f; // с какой доли поводка начинается натяжение // Натяжение набирается быстро, но НЕ мгновенно, и порог спринта поднят. // При urgeAttack 5.2 отряд уходил в спринт от любого движения курсора, а // спринт растягивает строй и выключает огонь: в коридорах это выглядело как // «отряд застрял». Поймано автопилотом (--play): первый этаж переставал // проходиться вообще. float urgeAttack = 3.4f; // как быстро натяжение набирается float urgeRelease = 3.4f; // и как быстро отпускает float sprintAt = 0.95f; // выше — спринт: бежим, не атакуем. // Порог высокий намеренно: при удержании направления натяжение садится // примерно на 0.54, то есть отряд идёт быстрым шагом И ПРОДОЛЖАЕТ работать // по целям. Спринт остаётся тем, чем должен быть, — отдельным решением // (Shift) или следствием того, что курсор реально уехал. // --- дыхание строя -------------------------------------------------------- float holdSpacing = 0.90f; // на стоянке отряд собирается // Строй в тесноте: во сколько раз сжимается вбок и вытягивается вдоль хода. // 0.28 — это колонна: пять тел проходят в дверь шириной в одну клетку. float tightSpacing = 0.28f; float tightStretch = 1.60f; float sprintSpacing = 1.30f; // в беге расходится float sprintStretch = 1.55f; // и вытягивается вдоль движения float formMorphRate = 4.0f; // скорость перестроения, 1/сек // решатель огневых позиций (6.5) float solveInterval = 0.25f; float commitTime = 0.6f; float hysteresis = 15.0f; float ring1 = 0.7f; // кольца решателя сжаты вслед за строем float ring2 = 1.4f; float ring3 = 2.1f; float candMinAlly = 0.60f; // минимум до другого агента (было МЕНЬШЕ диаметра тела) float sLaneClear = 100.0f; float sCoverage = 25.0f; float sBlockAlly = 35.0f; float sDistCost = 9.0f; float sCohesionCost = 24.0f; // отбегать от отряда дорого float sTooClose = 60.0f; // Жёсткая граница огневой позиции: дальше cohesionRadius + postLeash от диска // кандидат просто отбраковывается. Одной цены мало — линия огня стоит 100 // очков, и за неё решатель готов уйти сколько угодно далеко. А отряд, из // которого стрелок ушёл на пять метров, отрядом быть перестаёт. float postLeash = 1.0f; // расступание из чужих линий (6.6) float laneAvoidStrength = 7.0f; // Ширина РЕЗЕРВА линии относительно порога «тело в линии» из 6.3. // >1 означает: агента отжимает ДО того, как он влез в чужой сектор. // Правило стрельбы (6.3) от этого не меняется — см. docs/08-decisions.md. float laneReserveScale = 2.0f; // ручное прицеливание (6.8) float aimConeDeg = 20.0f; float aimTurnRateDeg = 110.0f; // прицел ведётся тяжелее // метрики float uptimeWindow = 10.0f; // окно FIRE UPTIME, сек float settleQuorum = 4.0f; // сколько агентов нужно для SETTLE TIME // --- свет и туман войны (render-only, на симуляцию не влияет) ----------- float sightRadius = 7.6f; // где обзор гаснет полностью float sightFullRadius = 2.2f; // дальше сразу начинается спад: свет должен таять float sightConeDeg = 100.0f; // конус взгляда агента float sightConeSoftDeg = 22.0f; // мягкий край конуса float sightPersonalRadius= 1.9f; // круговой «личный» ореол вокруг агента float sightBrightness = 0.86f; // яркость видимого; остаток отдан вспышкам float fogMemoryLevel = 0.13f; // как тускло помнится уже увиденное float fogWallBleed = 0.92f; // затухание света вглубь блока стен float fogFadeRate = 9.0f; // сглаживание во времени, чтобы не щёлкало float fogEntityReveal = 0.30f; // ниже этого света сущность не показывается float eyeRevealLight = 0.05f; // глаз мишени тлеет почти в полной темноте float eyeRadiusPx = 3.0f; float eyeLiftPx = 11.0f; // на уровне «головы» капсулы float agentMinLight = 0.34f; // свои читаемы всегда: игрок обязан их различать // вспышки выстрелов как источники света float flashPower = 0.55f; float flashRadius = 4.0f; float flashTime = 0.11f; // рампа освещения (меняет саму таблицу — требует перепечь) float lightGamma = 1.45f; float lightMaxGain = 1.25f; // --- эффекты стрельбы --------------------------------------------------- float fxIntensity = 1.0f; float tracerLength = 1.30f; // длина светящегося штриха, юнитов мира float bulletLiftPx = 8.0f; // пуля летит на уровне груди, не по земле float muzzleTime = 0.07f; float muzzleRadiusPx = 5.0f; float sparkTime = 0.16f; float sparkRadiusPx = 4.0f; float bladeLiftPx = 9.0f; // дуга метёт на уровне груди, не по земле float bladeTrailFrac = 0.80f; // какая доля пройденной дуги ещё светится float meleeShake = 1.4f; // отдача камеры на попадание клинком // --- напряжение и постобработка ----------------------------------------- float dreadRadius = 7.0f; // с какой дистанции мишень начинает пугать float dreadAttack = 6.0f; // страх набегает быстро float dreadRelease = 0.5f; // отпускает медленно float vigStartCalm = 0.42f; // где начинается виньетка float vigStartTense = 0.20f; float vigDepthCalm = 0.55f; // насколько темнеет край float vigDepthTense = 0.85f; float grainCalm = 6.0f; // амплитуда зерна (макс 127) float grainTense = 16.0f; float heartBpmCalm = 58.0f; float heartBpmTense = 132.0f; float heartAmp = 0.10f; // насколько сердцебиение давит картинку float shotFlash = 0.12f; // подсветка кадра на выстрел float shakePerShot = 0.55f; // отдача камеры, пикселей на выстрел float shakeMax = 3.0f; float shakeDecay = 9.0f; // --- кровь (render-only, декали запекаются в спрайты пола) --------------- float bloodAmount = 1.0f; // общий множитель размера пятен, 0 = выключить float bloodRadiusHit = 0.26f; // пуля float bloodRadiusMelee = 0.50f; // клинок float bloodRadiusKill = 0.78f; // смерть: лужа float bloodDrops = 5.0f; // сколько брызг летит по направлению удара float bloodOpacity = 0.92f; // насколько густо пятно перекрывает пол // --- дымка и пепел в воздухе (render-only) -------------------------------- float hazeAmount = 0.34f; // сила дымки, 0 = выключить float hazeScale = 1.0f; // крупность клубов float hazeDrift = 0.05f; // скорость сноса, экранных ячеек/сек float hazeGate = 24.0f; // ниже этой яркости дымки нет: тьма остаётся тьмой float ashDensity = 0.75f; // пылинок на тайл float ashSpeed = 0.30f; // скорость дрейфа пепла float ashBrightness = 0.60f; float ashLiftPx = 13.0f; // на какой высоте висит взвесь // флаги bool freeFireSecondary = true; // 6.8: стрелять по вторичным целям в движении bool friendlyFire = true; // 6.9: всегда true, тумблер только для проверки }; // --- связка ударов ближнего боя ---------------------------------------------- // // Чередование «влево-вправо» — это не связка, а метроном: два удара, между // которыми нет разницы ни на глаз, ни по последствиям. Связка обязана иметь // ФОРМУ: быстрый вход, злой возврат, тяжёлое завершение. // // Множители, а не абсолютные числа: базовые характеристики клинка остаются // одним набором в Tuning и продолжают крутиться на ходу, а связка их гнёт. struct MeleeComboStep { const char* name; float arcMul; // ширина сектора float dmgMul; float knockMul; float windupMul; // замах — телеграф; у тяжёлого удара он длиннее float strikeMul; // активное окно float recoverMul; // и расплата после float intervalMul; // пауза до следующего удара связки bool flipSide; // менять сторону захода лезвия }; inline const MeleeComboStep MELEE_COMBO[] = { // 1. Косой заход. Обычный удар, с него всё начинается. {"slash", 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, true}, // 2. Возврат с обратной стороны. Быстрее и почти без паузы — именно это // читается как «связка», а не как два отдельных удара. {"backhand",0.85f, 1.00f, 0.85f, 0.55f, 0.80f, 0.70f, 0.50f, true}, // 3. Раскрутка. Широкая, тяжёлая, с долгим восстановлением: в тесноте её // просто не дадут начать — сектор не будет чист, и это правильная цена. {"spin", 1.70f, 1.75f, 2.30f, 1.50f, 1.40f, 1.70f, 1.35f, false}, }; inline constexpr int MELEE_COMBO_STEPS = int(sizeof(MELEE_COMBO) / sizeof(MELEE_COMBO[0])); inline Tuning g_tune; // Максимально возможная скорость агента НА ХОДУ. Проверка спуска на время полёта // пули обязана считать по НЕЙ, иначе рывок союзника не учитывается. // // Рывок бойца ближнего боя сюда намеренно не входит: он не «манёвр», а прямой // отрезок с уже известным направлением, и AllyInterceptsShot проверяет его // точно — отдельной экстраполяцией (см. ai/firing_solver.cpp). inline float MaxAgentSpeed() { return std::max(g_tune.moveSpeed, g_tune.moveSpeedFast); } // --- таблица параметров для подкрутки в оверлее ------------------------------ struct TunableParam { const char* name; float Tuning::* member; float step; float minV; float maxV; }; inline const TunableParam TUNABLES[] = { {"safetyMargin", &Tuning::safetyMargin, 0.01f, 0.0f, 1.0f}, {"laneHalfWidth", &Tuning::laneHalfWidth, 0.01f, 0.0f, 0.5f}, {"bodyRadius", &Tuning::bodyRadius, 0.01f, 0.1f, 0.8f}, {"wLaneAvoid", &Tuning::wLaneAvoid, 0.1f, 0.0f, 10.0f}, {"laneAvoidStr", &Tuning::laneAvoidStrength,0.25f, 0.0f, 30.0f}, {"laneReserve", &Tuning::laneReserveScale, 0.05f, 1.0f, 4.0f}, {"predictSlack", &Tuning::shotPredictSlack, 0.05f, 0.0f, 2.0f}, {"wSeparation", &Tuning::wSeparation, 0.1f, 0.0f, 10.0f}, {"steerAccel", &Tuning::steerAccel, 1.0f, 2.0f, 60.0f}, {"formTurnRate", &Tuning::formTurnRate, 0.5f, 0.5f, 30.0f}, {"moveSpeed", &Tuning::moveSpeed, 0.1f, 0.5f, 10.0f}, {"moveSpeedFast", &Tuning::moveSpeedFast, 0.1f, 0.5f, 12.0f}, {"weaponRange", &Tuning::weaponRange, 0.25f, 2.0f, 20.0f}, {"fireInterval", &Tuning::fireInterval, 0.01f, 0.05f, 2.0f}, {"bulletSpeed", &Tuning::bulletSpeed, 1.0f, 5.0f, 60.0f}, {"solveInterval", &Tuning::solveInterval, 0.05f, 0.05f, 2.0f}, {"commitTime", &Tuning::commitTime, 0.05f, 0.0f, 3.0f}, {"hysteresis", &Tuning::hysteresis, 1.0f, 0.0f, 100.0f}, {"cohesionRadius", &Tuning::cohesionRadius, 0.1f, 1.0f, 12.0f}, {"minEngageDist", &Tuning::minEngageDist, 0.1f, 0.0f, 8.0f}, {"sLaneClear", &Tuning::sLaneClear, 5.0f, 0.0f, 400.0f}, {"sCoverage", &Tuning::sCoverage, 1.0f, 0.0f, 200.0f}, {"sBlockAlly", &Tuning::sBlockAlly, 1.0f, 0.0f, 200.0f}, {"sDistCost", &Tuning::sDistCost, 0.5f, 0.0f, 100.0f}, {"sCohesionCost", &Tuning::sCohesionCost, 0.5f, 0.0f, 100.0f}, {"sTooClose", &Tuning::sTooClose, 2.0f, 0.0f, 300.0f}, {"postLeash", &Tuning::postLeash, 0.1f, 0.0f, 10.0f}, {"formSpacing", &Tuning::formationSpacing, 0.05f, 0.4f, 4.0f}, {"tightSpacing", &Tuning::tightSpacing, 0.02f, 0.1f, 1.0f}, {"anchorSpeed", &Tuning::anchorSpeed, 0.1f, 0.5f, 12.0f}, {"anchorSpeedFast",&Tuning::anchorSpeedFast, 0.1f, 0.5f, 16.0f}, {"anchorLeash", &Tuning::anchorLeash, 0.1f, 0.2f, 12.0f}, {"anchorLeashMax", &Tuning::anchorLeashMax, 0.1f, 0.4f, 16.0f}, {"leashDrag", &Tuning::leashDrag, 0.05f, 0.0f, 1.0f}, {"anchorAccel", &Tuning::anchorAccel, 1.0f, 2.0f, 80.0f}, // --- натяжение и строй --- {"urgeStart", &Tuning::urgeStartFrac, 0.05f, 0.0f, 0.95f}, {"urgeAttack", &Tuning::urgeAttack, 0.2f, 0.1f, 20.0f}, {"urgeRelease", &Tuning::urgeRelease, 0.2f, 0.1f, 20.0f}, {"sprintAt", &Tuning::sprintAt, 0.02f, 0.05f, 1.0f}, {"holdSpacing", &Tuning::holdSpacing, 0.02f, 0.4f, 2.0f}, {"sprintSpacing", &Tuning::sprintSpacing, 0.02f, 0.4f, 3.0f}, {"sprintStretch", &Tuning::sprintStretch, 0.05f, 0.5f, 4.0f}, {"formMorph", &Tuning::formMorphRate, 0.25f, 0.25f, 20.0f}, // --- разброс и перезарядка --- {"spreadStand", &Tuning::spreadStandDeg, 0.1f, 0.0f, 20.0f}, {"spreadMove", &Tuning::spreadMoveDeg, 0.1f, 0.0f, 30.0f}, {"spreadRecoil", &Tuning::spreadRecoilDeg, 0.05f, 0.0f, 10.0f}, {"spreadRecMax", &Tuning::spreadRecoilMax, 0.1f, 0.0f, 20.0f}, {"spreadDecay", &Tuning::spreadRecoilDecay,0.2f, 0.1f, 40.0f}, {"spreadScale", &Tuning::spreadScale, 0.05f, 0.0f, 4.0f}, {"magazine", &Tuning::magazineRifle, 1.0f, 1.0f, 200.0f}, {"reloadTime", &Tuning::reloadRifle, 0.05f, 0.1f, 8.0f}, {"bulletKnock", &Tuning::bulletKnock, 0.05f, 0.0f, 6.0f}, {"knockResist", &Tuning::knockResistScale, 0.05f, 0.0f, 2.0f}, // --- живучесть и сложность --- {"agentHP", &Tuning::agentHP, 5.0f, 10.0f, 500.0f}, {"enemyDamage", &Tuning::enemyDamageScale, 0.05f, 0.0f, 4.0f}, {"enemyRespawn", &Tuning::enemyRespawn, 1.0f, 1.0f, 300.0f}, {"enemyWindup", &Tuning::enemyWindupScale, 0.05f, 0.25f, 3.0f}, {"enemyGrabHold", &Tuning::enemyGrabHold, 0.01f, 0.02f, 1.0f}, {"propYield", &Tuning::propYieldRadius, 0.5f, 0.0f, 64.0f}, // --- ближний бой --- {"meleeReach", &Tuning::meleeReach, 0.05f, 0.4f, 4.0f}, {"meleeArc", &Tuning::meleeArcDeg, 5.0f, 20.0f, 300.0f}, {"meleeWindup", &Tuning::meleeWindup, 0.01f, 0.02f, 1.0f}, {"meleeStrike", &Tuning::meleeStrike, 0.01f, 0.02f, 1.0f}, {"meleeRecover", &Tuning::meleeRecover, 0.01f, 0.0f, 1.5f}, {"meleeInterval", &Tuning::meleeInterval, 0.02f, 0.1f, 3.0f}, {"meleeDamage", &Tuning::meleeDamage, 5.0f, 1.0f, 999.0f}, {"meleeLunge", &Tuning::meleeLungeSpeed, 0.1f, 0.0f, 12.0f}, {"meleeKnock", &Tuning::meleeKnockback, 0.1f, 0.0f, 12.0f}, {"meleeCohesion", &Tuning::meleeCohesionRadius, 0.2f, 1.0f, 20.0f}, {"meleeAllyMargin",&Tuning::meleeAllyMargin, 0.02f, 0.0f, 1.0f}, {"meleePredict", &Tuning::meleeStartPredict,0.1f, 0.0f, 3.0f}, {"meleeReserve", &Tuning::meleeReserveFrac, 0.05f, 0.0f, 1.5f}, {"meleeShake", &Tuning::meleeShake, 0.1f, 0.0f, 6.0f}, // --- хоррор-часть --- {"sightRadius", &Tuning::sightRadius, 0.25f, 2.0f, 24.0f}, {"sightCone", &Tuning::sightConeDeg, 5.0f, 20.0f, 360.0f}, {"sightPersonal", &Tuning::sightPersonalRadius, 0.1f, 0.5f, 10.0f}, {"fogMemory", &Tuning::fogMemoryLevel, 0.02f, 0.0f, 1.0f}, {"fogReveal", &Tuning::fogEntityReveal, 0.02f, 0.0f, 1.0f}, {"flashPower", &Tuning::flashPower, 0.05f, 0.0f, 2.0f}, {"lightGamma", &Tuning::lightGamma, 0.05f, 0.3f, 4.0f}, {"lightGain", &Tuning::lightMaxGain, 0.05f, 0.2f, 3.0f}, {"fxIntensity", &Tuning::fxIntensity, 0.1f, 0.0f, 4.0f}, {"tracerLength", &Tuning::tracerLength, 0.1f, 0.1f, 6.0f}, {"grainTense", &Tuning::grainTense, 1.0f, 0.0f, 100.0f}, {"vigDepthTense", &Tuning::vigDepthTense, 0.05f, 0.0f, 1.0f}, {"dreadRadius", &Tuning::dreadRadius, 0.5f, 1.0f, 20.0f}, {"shakePerShot", &Tuning::shakePerShot, 0.05f, 0.0f, 4.0f}, // --- кровь, дымка, пепел --- {"bloodAmount", &Tuning::bloodAmount, 0.05f, 0.0f, 3.0f}, {"bloodOpacity", &Tuning::bloodOpacity, 0.02f, 0.0f, 1.0f}, {"bloodDrops", &Tuning::bloodDrops, 1.0f, 0.0f, 16.0f}, {"hazeAmount", &Tuning::hazeAmount, 0.02f, 0.0f, 2.0f}, {"hazeScale", &Tuning::hazeScale, 0.05f, 0.2f, 4.0f}, {"hazeDrift", &Tuning::hazeDrift, 0.01f, 0.0f, 1.0f}, {"hazeGate", &Tuning::hazeGate, 1.0f, 0.0f, 128.0f}, {"ashDensity", &Tuning::ashDensity, 0.05f, 0.0f, 4.0f}, {"ashSpeed", &Tuning::ashSpeed, 0.05f, 0.0f, 3.0f}, {"ashBright", &Tuning::ashBrightness, 0.05f, 0.0f, 2.0f}, {"ashLift", &Tuning::ashLiftPx, 1.0f, 0.0f, 40.0f}, }; inline constexpr int TUNABLE_COUNT = int(sizeof(TUNABLES) / sizeof(TUNABLES[0]));