16 KiB
balanceall — CLI баланса монстров
Утилита backend/cmd/balanceall прогоняет архетипы монстров из PostgreSQL (таблица enemies) через то же ядро боя, что онлайн/оффлайн (game.ResolveCombatToEndWithDuration), на референсном герое (game.NewReferenceHeroForBalance).
Режим метрик без подбора врагов
Если нужно оценивать силу предметов, не меняя таблицу enemies, используйте флаг:
-adjust-enemies=false
В этом режиме утилита не подбирает hp/attack у монстров и не печатает UPDATE SQL. Она только прогоняет сетку и печатает метрики (медианы длительности, HP героя, win rate), что удобно для подгонки формулы ScalePrimary, refGearBase и параметров лута.
Пример:
cd backend
set DATABASE_URL=postgres://...
go run ./cmd/balanceall -adjust-enemies=false
Проверка баланса шмота (-gear-check)
Сравнивает референсного героя в common (ReferenceGearBaseline: меч + средняя броня) и в legendary (ReferenceGearMax) на каждом уровне в полосе min_level..max_level выбранных шаблонов монстров. Для каждого уровня:
- Скорость убийства: медиана длительности победы в лучшем шмоте не должна быть короче медианы в базовом более чем на
-gear-check-max-speedup-pctпроцентов (по умолчанию 20% — то естьdur_max >= dur_baseline × 0.80). - HP после боя: медиана доли HP героя после побед в лучшем шмоте не выше
-gear-check-max-hero-hp-pct(по умолчанию 75%).
При нарушении печатаются строки FAIL, процесс завершаётся с кодом 1. Подбор врагов и SQL не выполняются; сетка -gear-variants в этом режиме не используется.
Дополнительно:
-gear-check-level-min/-gear-check-level-max— пересечь полосу уровней шаблона с нужным диапазоном (по умолчанию0= взятьmin_level/max_levelшаблона). Удобно для калибровки на «типичном» уровне или mid/late без прогона всей полосы.-gear-check-strict— если у baseline нет медианы длительности победы (нет побед), считать FAIL вместоSKIP, и пустое пересечение уровней тоже FAIL. Нужен, чтобы не получать ложныйPASSEDбез реальных сравнений.
Пример — один шаблон, уровни 5–15, строгий режим:
go run ./cmd/balanceall -gear-check -enemy-type wolf_l5_5_meadow -gear-check-level-min 5 -gear-check-level-max 15 -gear-check-strict -iterations 200
Пример — все строки БД с семейством wolf (колонка enemies.archetype):
go run ./cmd/balanceall -gear-check -enemy-archetype wolf -iterations 200
Список значений archetype из БД: go run ./cmd/balanceall -list-archetypes.
Локальный оверлей каталога (-gear-overlay)
Каталог предметов сначала загружается из БД (weapons, armor, equipment_items) или из встроенного набора (-gear-base=code), затем в памяти накладывается JSON с частичными полями GearFamily — по ключу имя предмета или slot:name (например main_hand:Soul Reaver, chest:Chainmail). Поля: basePrimary, speedModifier, baseCrit, agilityBonus, statType. Так можно крутить баланс без записи в БД, по аналогии с -config для монстров.
Пример:
go run ./cmd/balanceall -gear-check -enemy-type wolf_l2_2_forest -gear-check-level-min 2 -gear-check-level-max 2 ^
-gear-overlay ./cmd/balanceall/testdata/gear_overlay_balanced.json -iterations 200
SQL после подбора (-gear-print-sql)
Когда значения в оверлее устраивают (-gear-check проходит), сгенерируйте UPDATE для gear, weapons, armor (и при необходимости equipment_items для не-слотов main_hand/chest):
go run ./cmd/balanceall -gear-overlay ./cmd/balanceall/testdata/gear_overlay_balanced.json -gear-print-sql
Команда завершает работу после вывода SQL. Перенесите результат в миграцию (см. пример backend/migrations/000012_gear_balance_overlay.sql).
Калибровка без полного перебора
Выберите типичных монстров (-enemy-type, -gear-check-level-*) и итерируйте JSON оверлей, пока -gear-check не проходит; затем зафиксируйте изменения миграцией. Глобальные множители редкости остаются в runtime_config / ScalePrimary.
Режим по умолчанию: сетка (-grid, по умолчанию true)
Для каждого архетипа строится сетка сценариев:
- Уровни: для каждого
Lотmin_levelдоmax_levelвключительно — бой герой L против экземпляра монстра L (heroLv == enemyLv), честный тир. - Шмот:
-gear-variantsпрофилей (по умолчанию 4): один с медианным шмотом, остальные с rolled ilvl (как в дропе).
Агрегированные цели (как «медиана медиан» по ячейкам):
- Длительность — медиана по ячейкам от медиан длительности победных боёв попадает в полосу
[targetSec × (1 − tolerancePct/100), targetSec × (1 + tolerancePct/100)](по умолчанию при 330 с и 10% — примерно 297–363 с). - 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:
cd backend
set DATABASE_URL=postgres://...
go run ./cmd/balanceall [флаги]
Сборка:
go build -o balanceall ./cmd/balanceall
./balanceall -iterations 200
Область прогона
- Все шаблоны из БД — по умолчанию: строки из
enemiesв порядкеORDER BY min_level, archetype, type(какListEnemyRows). - Один шаблон по
enemies.id—-enemy-id <id>(удобно после-list-enemies). - Один шаблон по уникальному slug
type—-enemy-type <type>(напримерwolf_l5_5_meadow, не короткое имяwolf). - Все шаблоны с семейством
archetype—-enemy-archetype <name>(колонкаenemies.archetype, напр.wolf— все волки из БД).
Нельзя одновременно задавать -enemy-id и -enemy-type. Если заданы -enemy-type и -enemy-archetype, slug должен принадлежать этому семейству (иначе ошибка). С -enemy-id можно указать -enemy-archetype для проверки согласованности строки в БД.
JSON-оверлей (-config)
Флаг -config path.json задаёт файл с объектом верхнего уровня: ключи — строки type, как в таблице enemies. Значение — объект с любым подмножеством полей шаблона монстра (имена полей как в JSON у model.Enemy: maxHp, attack, hpPerLevel, specialAbilities, …).
После загрузки из БД данные из файла накладываются в памяти: указанное в JSON поле заменяет значение из БД; отсутствующие в JSON поля не трогаются.
Неизвестный ключ верхнего уровня (тип, которого нет среди загруженных шаблонов) пропускается с предупреждением в stderr.
Пример:
{
"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 |
"" |
Только шаблон с этим enemies.type (slug). |
-enemy-archetype |
"" |
Все шаблоны с этим enemies.archetype, если не заданы -enemy-id/-enemy-type. |
-list-archetypes |
false | Список уникальных enemies.archetype и выход. |
-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). |
-adjust-enemies |
true |
Если false — только метрики, без подбора и без SQL. |
-gear-check |
false |
Сравнение baseline vs legendary reference gear по уровням; exit 1 при нарушении порогов. |
-gear-check-max-speedup-pct |
20 |
Макс. ускорение убийства с лучшим шмотом относительно baseline (%). |
-gear-check-max-hero-hp-pct |
75 |
Верхняя граница медианы HP героя на победах с лучшим шмотом (%). |
-gear-check-level-min |
0 |
Нижняя граница уровня для gear-check (0 = min_level шаблона). |
-gear-check-level-max |
0 |
Верхняя граница уровня для gear-check (0 = max_level шаблона). |
-gear-check-strict |
false |
Нет побед у baseline / пустой диапазон уровней → FAIL. |
-gear-base |
db |
db — каталог из БД; code — только встроенный каталог до оверлея. |
-gear-overlay |
"" |
JSON с патчами полей предметов поверх каталога (только память). |
-gear-print-sql |
false |
Напечатать UPDATE для таблиц каталога и выйти (нужен -gear-overlay). |
Примеры:
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/balanceall/gear_check.go. - Одиночная симуляция:
cmd/balancesim+DATABASE_URL. - Краткий снимок для контента:
docs/monster-catalog-balanced-v1.md.
Ограничения
- Сетка не моделирует все пары (герой L5 vs монстр L1): для одной кривой в БД агрегаты по «честному» тиру (
hero == enemy) устойчивее. - Элиты с сильным DoT могут требовать ослабления целей или точечной настройки.
- Вывод SQL — предложение; источник правды в продакшене — таблица
enemiesпосле миграций и reload.