|
|
# 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 <id>` (удобно после `go run ./cmd/balanceall -list-enemies`).
|
|
|
- **Один архетип по строке `type`** — `-enemy-type <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.
|