You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

17 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 без реальных сравнений.

Пример — один шаблон, уровни 515, строгий режим:

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).

После миграций, меняющих gear или множители встреч в runtime_config (enemyEncounterStatMultiplier, enemyStatMultiplierVsUnequippedHero), имеет смысл прогнать -gear-check и при необходимости сетку с -adjust-enemies=false, чтобы увидеть метрики; BuildEnemyInstanceForLevel уже учитывает эти множители в симуляциях.

Калибровка без полного перебора

Выберите типичных монстров (-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 (как в дропе).

Агрегированные цели (как «медиана медиан» по ячейкам):

  1. Длительность — медиана по ячейкам от медиан длительности победных боёв попадает в полосу
    [targetSec × (1 tolerancePct/100), targetSec × (1 + tolerancePct/100)] (по умолчанию при 330 с и 10% — примерно 297363 с).
  2. HP героя — медиана по ячейкам от медиан доли HP после победы попадает в полосу
    hero-hp-mid ± hero-hp-pp процентных пунктов (по умолчанию 60% ± 7 п.п.5367%).

После подбора выполняется смягчение атаки, пока в худшей ячейке есть хотя бы одна победа в выборке (как в прежнем отдельном прототипе сетки).

Логика вынесена в 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 + N1 rolled).
-hero-hp-mid 60 Режим сетки: центр полосы HP героя на победах (%).
-hero-hp-pp 7 Режим сетки: ±п.п. вокруг -hero-hp-mid.
-refine 2 Режим сетки: проходы подгонки длительности после атаки.
-iterations 120 Число боёв на ячейку сетки (grid) или на архетип (legacy). Рекомендуется ≥ 120200.
-seed 20260331 База RNG; на архетип добавляется хеш type.
-target-sec 330 Центр полосы медианы длительности побед (секунды).
-tolerance-pct 10 Полоса вокруг центра; при 330 и 10% → 297363 с.
-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 (1500).
-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%), диапазоны по ячейкам, при -sqlUPDATE.
  • 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.