- name
- xgaida-x-nixi-balance
- description
- Баланс Guildmaster: петля «прогнал → записал проблемы → предложил правки → Макс выбрал → внёс → перемерил». Владеет стендом SimBench (бенчи, классовые коридоры, сайт отчётов), реестром проблем docs/balance-issues.md и правкой ЗНАЧЕНИЙ контента через ContentEditService. Зови на любую задачу про баланс, силу китов, винрейты, замеры, «кто перекачан», «кто не играбелен», прогон бенчей и разбор отчётов, а также на всё под Assets/_Project/Scripts/Balance, scripts/balance-*.py и BalanceReports. НЕ применять к: созданию НОВОГО контента (content-design), правке боевых механик и AI (combat-sim), новым полям и типам SO (data-authoring), дизайн-доке (gdd-scribe).
# Balance — петля работы с балансом
Скилл — процедура, а не справочник чисел. Числа живут в ассетах и отчётах; здесь — как их
получать, как читать и в каком порядке принимать решения, чтобы правки не превращались в
подкручивание вслепую.
**Роли.** Макс — Стратег баланса: он выносит вердикт, выбирает вариант и закрывает проблему.
Никси крутит петлю: меряет, ставит диагноз, предлагает варианты с числами, вносит утверждённое
и перемеряет. Стенд — доп-информация, а не судья.
## Карта инструментов
| Что | Где |
|---|---|
| Бенчи стенда | `Assets/_Project/Scripts/Balance/Editor/Benches/*.cs` |
| **PvE-линза: отряд против энкаунтеров** | `Benches/EncounterBench.cs` |
| **Разбор одного боя событиями** | `Benches/TraceBench.cs` + `BattleTrace.cs` |
| Состав и порядок полного круга | `Assets/_Project/Scripts/Balance/Editor/BalanceRound.cs` |
| Прогон из командной строки | `BalanceCli.cs` + `scripts/balance-headless.ps1` |
| Сценарии под замысел кита | `Assets/_Project/ScriptableObjects/DevTools/BalanceScenarios/*.asset` |
| Классовые нормы (линейка коридоров) | `Assets/_Project/Scripts/Balance/Editor/BalanceNorms.cs` |
| Карточки контента (имена, описания, способности) | `Assets/_Project/Scripts/Balance/Editor/ContentCards.cs` |
| Числа норм | `Assets/_Project/ScriptableObjects/Configs/ClassBalanceConfig.asset` |
| Сбор метрик из боя | `Assets/_Project/Scripts/Balance/Editor/MetricCollector.cs` |
| Запись отчётов (CSV + MD + JSON) | `Assets/_Project/Scripts/Balance/Editor/ReportWriter.cs` |
| Меню прогонов | `Assets/_Project/Scripts/Balance/Editor/BalanceMenu.cs` → `Alebardium/Balance/*` |
| Сборка сайта отчётов | `scripts/balance-site.py`, шаблоны в `scripts/balance-site/` |
| Маркеры прогонов | `scripts/balance-run.py` → `BalanceReports/runs.json` |
| **Реестр проблем** | `docs/balance-issues.md` |
| Правка значений контента | `Assets/_Project/Scripts/Data/Editor/ContentEditService.cs` |
| Когорты для массовых правок | `Assets/_Project/Scripts/Data/Editor/ContentCohorts.cs` |
| Пакетные правки с откатом | `Assets/_Project/Scripts/Data/Editor/ContentEditBatch.cs` |
| Готовый сайт (открыть в браузере) | `BalanceReports/site/index.html` |
Подробности: [метрики и линзы](references/metrics-and-lenses.md) · [инструмент правок](references/edit-toolkit.md).
## Петля
### 1. Объявить прогон
Прогон — **мини-коммит**: у него есть название и краткое содержание того, что в нём меняли.
Объявляется ДО бенчей, иначе снимки останутся безымянными.
```bash
python scripts/balance-run.py start "Ослабили Друида" "BAL-001, вариант 2: перевод в Дальника, лечение вдвое"
```
Забыла объявить заранее — поставь маркер задним числом (`--at "2026-07-27T20:40:00"`), но НЕ
приписывай уже снятые снимки следующему прогону: это враньё в истории.
### 2. Прогнать полный круг
По умолчанию гоняется **всё**, а не выборочные бенчи: сравнение прогонов честно только когда линзы
одни и те же. Состав и порядок круга держит `BalanceRound` — один владелец для меню и командной строки:
нормы, карточки, аудит, **энкаунтеры (PvE)**, DPS, выживаемость, дуэли 1v1, тройки, отряды, замена,
синергия.
**Круг одной командой, без открытого редактора** — основной способ:
```powershell
./scripts/balance-headless.ps1 -Title "TTK-проход" -Summary "BAL-005, вариант 1"
```
Скрипт сам ставит маркер прогона, гоняет круг и собирает сайт. Редактор открыт (другая сессия) —
уходит в теневой проект автоматически; закрыт — гонит прямо по репозиторию. Выборочно: `-Benches
encounters,dps`. Из редактора то же самое даёт пункт «Полный круг — все бенчи по порядку».
Нормы и карточки идут в начале круга — линейка обязана быть той же версии, что и замеры.
Сайт пересобирается сам после прогона. Руками: `python scripts/balance-site.py`.
### 2а. Когда таблица не отвечает «почему»
Агрегаты говорят «сколько», а не «почему». Один бой, разобранный по событиям, — пункт «Трейс
выделенного»: выдели **реликвию + энкаунтер** (бой PvE), **две реликвии** (дуэль) или
`BalanceScenarioData`. На выходе Markdown-лента: время, кто, что, кому, чем и HP цели сразу после
события. Это дешевле, чем глазами в play-mode, и повторяемо.
Без редактора — тем же скриптом, по именам ассетов:
```powershell
./scripts/balance-headless.ps1 -Trace Ranger,GoblinRaid
```
Маркер прогона трейс не ставит: это диагностика, а не замер.
### 2б. Пересверить ВЕСЬ реестр — не только то, что чинили
**HARD: после полного круга каждая незакрытая запись получает вердикт ревизии.** Не «дописать новые
находки», а пройти список сверху донизу и ответить по каждой: симптом **подтверждается**,
**смягчился**, **не воспроизводится** или **замером не проверяется** (инструментальные и те, что ждут
реализации).
Без этого шага реестр протухает молча и незаметно: записи заводятся на разных прогонах, между ними
ложатся правки соседних китов, тайминги, новые механики — и через неделю половина симптомов описывает
игру, которой уже нет. Прецедент 03.08.2026: из 26 записей две перестали воспроизводиться полностью
(Пастырь, Копейщик), две смягчились до «не дефект» (Мечник 0.95 → 0.77, Следопыт 0.20 → 0.46 в
отрядах), а Криомант, наоборот, просел ещё сильнее — и всё это лежало под статусом «ждёт слова Макса»
с числами месячной давности. Макс открыл сайт и увидел реестр, который врёт.
Форма ревизии — таблица «запись → вердикт» одним блоком, тела записей **не переписываются**: старые
числа это история правок, а не мусор. Что не воспроизводится — уезжает в список «готово к закрытию,
ждёт слова Макса» в шапке; закрыть по-прежнему может только он.
**Реестр правился руками — пересобери сайт:** `python scripts/balance-site.py`. После прогона бенчей
сайт собирается сам, а после правки одного `.md` — нет, и Макс открывает вчерашний реестр (так и
случилось 03.08).
**Число, отличающееся от нормы НА ПОРЯДОК, — сначала подозрение к линзе, потом к киту.** Прецедент
03.08: Геомант дал EHP 56 при норме 1430 и уехал в реестр как сломанный; на деле соло-бенч ставит
кита одного, а он живёт данью с союзников — платить было некому. Тот же капкан раньше сработал на
Друиде. Прежде чем писать «кит сломан», ответь: этот кит вообще может показать себя в этом формате?
### 3. Записать проблемы в реестр
Каждая проблема — запись в `docs/balance-issues.md`. **Что попадает:**
- **выход за классовый коридор** — цифра не совпадает с ролью;
- **провал или доминирование по результату** — крайний винрейт, даже если все числа в коридоре.
«Скучный, но в норме» — вопрос дизайна, не баланса; такое в реестр не идёт.
Формат записи — симптом (числа с прогона), диагноз (что они значат), **один-три варианта правки
с примерными числами**, поле для вердикта Макса. Где числа не спасут — статус «требует дизайна» и
идеи механики, но код не трогается до выбора.
**Порядок работы — сначала доминаторы.** Перекачанный кит обесценивает весь ростер и искажает
замеры соседей; слабых подтягиваем после того, как потолок опущен.
### 4. Дождаться вердикта, внести, перемерить
Утверждённые правки вносятся **пакетом** через `ContentEditBatch` (см. [инструмент правок](references/edit-toolkit.md)),
затем объявляется новый прогон и гоняется полный круг. Смотреть надо **весь ростер**, а не
починенного кита: новый выброс — новая запись в реестре со ссылкой на причину. Откатывать или жить
с побочкой решает Макс.
### 5. Закрыть
Статус ставит Никси до «правка внесена». **Закрывает запись только Макс словом.** Стенд не ловит
сочетания, ощущение от боя и читаемость размена — то, что решает, играбелен кит или нет.
## HARD-правила
0. **Доли пишутся ПРОЦЕНТАМИ, с двумя знаками: `23.59%`.** Всегда — в отчётах стенда, на сайте, в
реестре и в разговоре. «0.24» и «24%» читаются по-разному, и глаз, привыкший к процентам, читает
долю как «почти ноль». Прецедент 03.08.2026: стенд печатал `WinRate` долей, а соседние колонки той
же таблицы — процентами, и я перенесла «0.04» в разговор про кита с винрейтом 3.85%. Правило живёт
в КОДЕ бенчей (колонка называется `WinRate%`, значение уже умножено), а не в моей памяти — иначе
держится ровно до следующего забывания.
0б. **Числа — КРАСИВЫЕ И РОВНЫЕ.** У нас не соревновательное PvP, а PvE-рогалик: баланс приблизительный
по замыслу. Игрок должен считывать и считать в уме — «250 лечения и 10% недостающего» читается,
«237 и 8.4%» нет. Предлагая правку, округляй до того, что произносится вслух: доли — до 25%
(0.25 / 0.5 / 0.75 / 1.0), плоские величины — до десятков и полусотен, время — до целых секунд.
Точность третьего знака здесь не даёт ничего, кроме нечитаемой карточки.
1. **Числа не назначаются самой.** Предлагаю варианты, решает Макс. Это относится и к коридорам
норм: они такой же предмет вердикта, как сила кита.
2. **Норма раньше замера.** Число без коридора роли — не вывод, а справка. Если нормы для метрики
нет, так и говорить, а не додумывать «на глаз».
3. **Правки — только через `ContentEditService`.** Не hand-YAML, не точечный `execute_code` по
одному полю: SerializedObject + Undo + change-log, иначе правка теряется при domain reload и не
попадает в аудит.
4. **Значения живут в ассете, не в коде.** Дефолт в C# не считается: играет то, что в `.asset`.
5. **Проблема не удаляется — закрывается.** Отклонённый вариант остаётся в реестре с причиной,
иначе через месяц он вернётся как «свежая идея».
6. **Прогон без названия — дефект процесса.** Сравнивать безымянные наборы чисел бессмысленно.
## Готчи
- **Теневой проект — только для чтения.** `-Mode Shadow` подключает живые `Assets` junction-ссылкой,
и Unity честно предупреждает («Assets is a symbolic link… may cause your project to become
corrupted») плюс отключает directory monitoring. Для прогона бенчей это безопасно: он читает и пишет
только в `BalanceReports`. Бенч, который начнёт СОХРАНЯТЬ ассеты, в этом режиме гонять нельзя —
два редактора над одной папкой на запись не рассчитаны.
- **PowerShell не ждёт Unity.** `& $unity -batchmode …` возвращает управление сразу (Unity.exe —
GUI-приложение), `$LASTEXITCODE` достаётся от предыдущей команды, и прогон рапортует об успехе, ещё
не начавшись. Так жил прежний `run-tests.ps1`: «tests PASSED» при отсутствующем файле результатов.
Запускать только через `Invoke-UnityBatch` (`Start-Process -Wait -PassThru`), а «зелёный» проверять
по артефакту, а не по коду выхода.
- **Дерево одно на все сессии.** Headless-прогон компилирует ВСЁ, включая чужие незакоммиченные
правки: соседняя сессия на середине рефактора роняет круг ошибкой в файле, которого ты не касалась.
Смотреть на имя файла в ошибке прежде, чем чинить: не своё — подождать, а не «поправить».
- **Бой RNG-free по исходу** → дуэли детерминированы, винрейт дискретный (кратен числу боёв), а
Монте-Карло по сидам вырожден. Отсюда же: два прогона без правок дают побитово одинаковые числа —
сплошные «=» в дельтах это здоровье, а не поломка сайта.
- **`refresh_unity` без `compile: "request"`** компиляцию не просит: тесты и бенчи молча гоняют
старую сборку, а правка выглядит «не сработавшей». Проверять через `unity_reflect`.
- **Собранная метрика ≠ показанная.** Расширяешь `MetricCollector` — сразу дописывай колонки в
отчёт, иначе корзины на сайте пусты и это выглядит багом сайта.
- **Слэш в имени `MenuItem`** Unity считает разделителем подменю: пункт разваливается на вложенные
уровни и не открывается. Разделять точкой.
- **`overrideReferences: true` в asmdef отключает авто-ссылку на Newtonsoft** — в такой сборке
`Newtonsoft.Json.dll` надо дописать в `precompiledReferences` руками.
- **Бенч выживаемости слеп к перелечиванию:** если хил кита сильнее эталонного источника урона,
цифра EHP упирается в потолок прогона, а не в запас прочности. Читать вместе с `focus3`.
- **Замена в отряде часто упирается в timeout** — 4v4 не всегда разрешается за 240 с; `Delta` в
такой строке значит «преимущество на момент остановки», а не исход.
- **Круговые форматы мерят кита вне его условий.** Отряды собираются по ростеру подряд, поэтому кит,
чья сила требует определённого состава (Друид — свалки ближников), выглядит в них провальным при
работающей механике: замер 2026-07-28 дал ему 72 лечения за бой в круговом 4v4 и **600** в сценарии
melee-vs-melee. Прежде чем заводить запись «аутсайдер», собери **сценарий под замысел** — и сравни
с контролем (тот же отряд без него и с китом-конкурентом), иначе диагноз будет о линзе, а не о ките.
## Чему научил первый заход (2026-07-28)
Заход был **пробным** и вскрыл больше про инструмент, чем про ростер. Главное на будущее:
- **Мы мерим не ту игру.** Все бенчи — это PvP реликвии против реликвии, а Guildmaster **PvE**:
игрок дерётся с энкаунтерами, а не с зеркальным ростером. Круговые винрейты отвечают на вопрос,
которого в игре нет. Следующий заход начинать с **энкаунтеров** (`ScriptableObjects/Encounters/`),
а круговые форматы держать как грубую линзу «кто вообще жив».
- **Линза важнее числа.** Кит, чья сила требует состава, в круговых форматах выглядит провальным при
работающей механике (Друид: 72 лечения в круговом 4v4 против 600 в сценарии melee-vs-melee).
Прежде чем писать «аутсайдер» — собрать сценарий под замысел и контроль к нему.
- **Спрашивай «сколько это длится» раньше, чем «кто победил».** Стенд полгода не выводил длительность
боя, и разговор о затягивании шёл без единого числа. Оказалось, бои втрое короче норматива, а
затягивают их **не танки и хилеры, а киты с недобором урона** — они не могут закрыть бой.
- **Проверяй диагноз кодом, а не памятью.** Три записи реестра из четырнадцати оказались неверными
именно потому, что писались по воспоминанию о механике (щит Оплота, ульта Пастыря, «процент чего»
у Мечника). Docstring в коде тоже врёт — он писался человеком и устаревает.
- **Дефект инструмента маскируется под дефект баланса.** Пакет молча писал нули вместо дробных чисел
(локаль в парсере), и это выглядело как «правка не сработала». Если правка «не подействовала» —
сначала проверь, что она вообще применилась, на диске.
## Границы
Скилл владеет **петлёй, стендом и реестром**. Всё, что за ними:
- **Механика и боевое ядро** (поведение эффектов, AI, урон) — правится по `combat-sim`, после того
как Макс выбрал вариант; реестр только формулирует задачу.
- **Новый контент** (кит, реликвия, способность) — `content-design`. Баланс крутит существующее.
- **Новые поля и типы SO** — `data-authoring`. Здесь правятся ЗНАЧЕНИЯ.
- **Запись решений в дизайн-канон** — `gdd-scribe`, когда правка меняет замысел кита, а не число.
- **Тех-дока о стенде** — `tech-scribe` (`docs/wiki/tech/40-planning/simbench.md`).
Границы не церемониальные: если для баланса нужно зайти в соседнюю область, захожу — но дизайн-выбор
всё равно за Максом.
GitHub에서 보기