# balanceall — CLI баланса монстров Утилита `backend/cmd/balanceall` прогоняет **архетипы монстров** из **PostgreSQL** (таблица `enemies`) через то же ядро боя, что онлайн/оффлайн (`game.ResolveCombatToEndWithDuration`), на **референсном герое** (`game.NewReferenceHeroForBalance`). ## Режим по умолчанию: сетка (`-grid`, по умолчанию `true`) Для каждого архетипа строится сетка сценариев: - **Уровни:** для каждого `L` от `min_level` до `max_level` включительно — бой **герой L** против **экземпляра монстра L** (`heroLv == enemyLv`), честный тир. - **Шмот:** `-gear-variants` профилей (по умолчанию **4**): один с **медианным** шмотом, остальные с **rolled** ilvl (как в дропе). Агрегированные цели (как «медиана медиан» по ячейкам): 1. **Длительность** — медиана по ячейкам от **медиан длительности победных** боёв попадает в полосу `[targetSec × (1 − tolerancePct/100), targetSec × (1 + tolerancePct/100)]` (по умолчанию при 330 с и **10%** — примерно **297–363 с**). 2. **HP героя** — медиана по ячейкам от **медиан доли HP после победы** попадает в полосу **`hero-hp-mid` ± `hero-hp-pp` процентных пунктов** (по умолчанию **60% ± 7 п.п.** → **53–67%**). После подбора выполняется смягчение атаки, пока в худшей ячейке есть хотя бы одна победа в выборке (как в прежнем отдельном прототипе сетки). Логика вынесена в `backend/cmd/balanceall/grid.go`. ## Режим legacy (`-grid=false`) Один сценарий на архетип: - Герой и монстр: уровень = середина полосы `(min_level + max_level) / 2`, только **медианный** шмот. - **Длительность** — медиана длительности побед в полосе по `-tolerance-pct`. - **Давление** — медиана оставшегося HP **не выше** `-max-hero-hp-pct-on-win` (при необходимости ослабление до 65/70/75%). ## Подключение к БД Нужен DSN (без БД утилита не запускается): - переменная окружения **`DATABASE_URL`**, или - флаг **`-dsn`** (перекрывает env). Шаблоны подгружаются через `storage.ContentStore.LoadEnemyTemplates` (как в `balancesim`). ## Запуск Из каталога модуля Go: ```bash cd backend set DATABASE_URL=postgres://... go run ./cmd/balanceall [флаги] ``` Сборка: ```bash go build -o balanceall ./cmd/balanceall ./balanceall -iterations 200 ``` ## Область прогона - **Все архетипы из БД** — по умолчанию: строки из `enemies` в порядке `ORDER BY min_level, type` (как `ListEnemyRows`). - **Один монстр по `enemies.id`** — `-enemy-id ` (удобно после `go run ./cmd/balanceall -list-enemies`). - **Один архетип по строке `type`** — `-enemy-type ` (в таблице `enemies` это строка вроде `wolf`, не catalog id `enemy.wolf_forest`). Нельзя одновременно задавать `-enemy-id` и `-enemy-type`. ## JSON-оверлей (`-config`) Флаг **`-config path.json`** задаёт файл с объектом верхнего уровня: **ключи — строки `type`**, как в таблице `enemies`. Значение — объект с **любым подмножеством** полей шаблона монстра (имена полей как в JSON у `model.Enemy`: `maxHp`, `attack`, `hpPerLevel`, `specialAbilities`, …). После загрузки из БД данные из файла **накладываются в памяти**: указанное в JSON поле заменяет значение из БД; отсутствующие в JSON поля не трогаются. Неизвестный ключ верхнего уровня (тип, которого нет среди загруженных шаблонов) пропускается с предупреждением в stderr. Пример: ```json { "wolf": { "attack": 12, "attackPerLevel": 1.1 }, "demon_fire": { "maxHp": 800, "hpPerLevel": 45 } } ``` Если в оверлее задан только один из пары `hp` / `maxHp`, второй выравнивается под него для согласованности шаблона. ## Флаги | Флаг | По умолчанию | Смысл | |------|----------------|--------| | `-dsn` | `""` | Postgres DSN; если пусто — берётся `DATABASE_URL`. | | `-enemy-id` | 0 | Только строка с этим `enemies.id`. | | `-enemy-type` | `""` | Только архетип с этим `type`. | | `-config` | `""` | Путь к JSON: частичные шаблоны по ключу `type`, поверх БД. | | `-grid` | `true` | Сетка уровней × шмот; `false` — legacy (один уровень, медианный шмот). | | `-gear-variants` | 4 | Режим сетки: число профилей шмота на уровень (1 median + N−1 rolled). | | `-hero-hp-mid` | 60 | Режим сетки: центр полосы HP героя на победах (%). | | `-hero-hp-pp` | 7 | Режим сетки: ±п.п. вокруг `-hero-hp-mid`. | | `-refine` | 2 | Режим сетки: проходы подгонки длительности после атаки. | | `-iterations` | 120 | Число боёв **на ячейку сетки** (grid) или на архетип (legacy). Рекомендуется ≥ 120–200. | | `-seed` | `20260331` | База RNG; на архетип добавляется хеш `type`. | | `-target-sec` | 330 | Центр полосы медианы длительности побед (секунды). | | `-tolerance-pct` | 10 | Полоса вокруг центра; при 330 и 10% → **297–363 с**. | | `-max-hero-hp-pct-on-win` | 60 | Только **legacy**: верхняя граница медианы HP героя после побед (%). | | `-min-win-rate` | 0.35 | Legacy: планка винрейта при накрутке атаки. Сетка: планка **медианного** винрейта по ячейкам. | | `-sql` | `true` | Печатать предлагаемые `UPDATE enemies ...`. | | `-list-enemies` | false | Список архетипов из БД (с колонкой `id`) и выход. | | `-filter` | `""` | Подстрока для `-list-enemies`. | | `-limit` | 50 | Максимум строк для `-list-enemies` (1–500). | Примеры: ```bash go run ./cmd/balanceall -iterations 200 go run ./cmd/balanceall -enemy-type wolf -iterations 200 # Старый алгоритм (один уровень — середина полосы) go run ./cmd/balanceall -grid=false -enemy-type wolf go run ./cmd/balanceall -list-enemies go run ./cmd/balanceall -sql=false ``` ## Вывод - **Сетка:** для каждого типа — baseline по текущему шаблону, затем `hpScale`/`atkScale`, агрегаты `medOfMed(duration)`, `medOfMed(heroHp%)`, диапазоны по ячейкам, при `-sql` — `UPDATE`. - **Legacy:** как раньше — одна строка метрик и `UPDATE`. ## Связь с репозиторием - Загрузка из БД: `internal/storage/content_store.go` (`LoadEnemyTemplates`, `ListEnemyRows`). - Сетка: `cmd/balanceall/grid.go`. - Одиночная симуляция: `cmd/balancesim` + `DATABASE_URL`. - Краткий снимок для контента: `docs/monster-catalog-balanced-v1.md`. ## Ограничения - Сетка не моделирует все пары (герой L5 vs монстр L1): для одной кривой в БД агрегаты по «честному» тиру (`hero == enemy`) устойчивее. - Элиты с сильным DoT могут требовать ослабления целей или точечной настройки. - Вывод SQL — предложение; источник правды в продакшене — таблица `enemies` после миграций и reload.