Решения №11–13 (документ 01 комплекта): - №11: прогноз показывает точный шанс победы (BattleForecast.WinChancePermille) — перебор всех 101×101 равновероятных сочетаний удачи; в окне атаки «шанс победы 27%» - №12: сила считается точной дробью (BigInteger), удача — после всех модификаторов, округление одно и только для показа (3 пехоты против 1 кав. и 2 пех.: 13,00 вместо 12,96) - №13: потери — целая часть гарантирована, дробная — шанс ещё одной потери из отдельного потока случайности (своя соль на сторону и тип), воспроизводимо по номеру боя; одиночка-проигравший теперь может погибнуть - версия правил боя 3 (BattleRules, BattleConfig) Голод на момент боя (№9, документ 02 — офлайн в причинно-следственном порядке): - MarchManager спрашивает IPopulation.WasStarvingAt(время прихода войска) - перед боем, прошедшим в прошлом, просит Население досчитать время (публичный Tick); модуль Населения не менялся Формула потерь (№14) и раненые (№15) не тронуты — ждут чисел от команды. Тесты: шанс победы совпадает с 20 000 настоящих боёв (±2%), сила без промежуточного округления, округление потерь (одиночка гибнет ~50%, 1,5 → 1 или 2). Все 42 теста ModuleTests проходят вне Unity. Документы Battle, World, Core, README обновлены (только наши строки). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
127 lines
18 KiB
Markdown
127 lines
18 KiB
Markdown
# Боевая система
|
||
|
||
Урон, потери, исход боя с учётом типов войск. Модуль только считает: кто победил и сколько погибло каждого типа. Кто идёт в бой и что делать с итогом, решают другие модули.
|
||
|
||
- **Сборка:** `Bogatyri.Battle` (без Unity и без float — тот же код будет считать бои на сервере).
|
||
- **Папка:** `Assets/Scripts/Modules/Battle`.
|
||
- **Зависит от:** ядра.
|
||
|
||
## Контракт `IBattleSystem`
|
||
|
||
- `BattleOutcome Resolve(BattleRequest request)` — бой.
|
||
- `BattleForecast Forecast(BattleRequest request)` — прогноз до боя с учётом случайности. Итог конкретного боя не раскрывает.
|
||
|
||
| `BattleRequest` | Что это |
|
||
| --- | --- |
|
||
| `AttackerUnits`, `DefenderUnits` | Состав сторон по типам (`TroopType`: пехота, лучники, кавалерия, осадные). |
|
||
| `Attackers`, `Defenders` | Число воинов, если состав не задан (`null`) — тогда все считаются пехотой. |
|
||
| `DefenderFortified` | Защитники за стенами (город, замок). У лагерей разбойников стен нет — бой в поле. |
|
||
| `AttackerBonusPercent`, `DefenderBonusPercent` | Бонусы сторон в процентах: герой, технологии, голод (минус). |
|
||
| `AttackerConditions`, `DefenderConditions` | Состояния сторон (`BattleCondition`, флаги): `Famine` — голод, `Fatigue` — усталость. |
|
||
| `Seed` | Номер боя. Из него считается случайность сторон: тот же номер с теми же войсками — всегда тот же итог. |
|
||
|
||
| `BattleOutcome` | Что это |
|
||
| --- | --- |
|
||
| `Victory` | Победил атакующий. |
|
||
| `BattleId`, `RulesVersion` | Номер боя (= `Seed`) и версия правил, по которой посчитан итог. |
|
||
| `AttackerPower`, `DefenderPower` | Итоговая сила сторон ×100 (целые числа): с контрами, стенами, бонусами и случайностью. |
|
||
| `AttackerLuckPermille`, `DefenderLuckPermille` | Выпавшая случайность сторон в десятых долях процента: +23 = +2,3%. |
|
||
| `AttackerConditionPercent`, `DefenderConditionPercent` | Сколько силы оставили состояния: 100 — без штрафов, 50 — голод, 40 — голод и усталость. |
|
||
| `AttackerLosses`, `DefenderLosses`, `Survivors` | Выбывшие (погибшие + раненые) и выжившие атакующие — всего. |
|
||
| `…UnitsBefore`, `…SurvivorUnits`, `…DeadUnits`, `…WoundedUnits` | По типам войск и **для обеих сторон**: до боя, выжившие, погибшие, раненые. По каждому типу: до = выжившие + погибшие + раненые. Раненых пока нет (нули): доля не утверждена. |
|
||
| `DefenderLossUnits` | Выбывшие защитники по типам — для старого кода. |
|
||
|
||
| `BattleForecast` | Что это |
|
||
| --- | --- |
|
||
| `Chance` | `Advantage` — победа при любой случайности, `Uncertain` — решает случайность, `HighRisk` — поражение при любой случайности. |
|
||
| `WinChancePermille` | Шанс победы нападающего в десятых долях процента (решение №11). Точный: перебираются все 101 × 101 равновероятных сочетаний удачи сторон. |
|
||
| `AttackerPowerMin/Max`, `DefenderPowerMin/Max` | Сила сторон при крайних значениях случайности. |
|
||
| `AttackerLossesMin/Max` | Потери атакующего: от лучшего случая до худшего, с учётом округления потерь (№13). |
|
||
|
||
Регистрирует `App/GameModules`: числа боя — из таблицы `BattleConfig`, сила воина каждого типа — из `GameData.Troops` (4 / 5 / 9 / 16). Мир получает модуль при создании симуляции, не через реестр — так симуляцию можно запустить на сервере.
|
||
|
||
## Правила
|
||
|
||
Числа — **стартовые для тестирования** (план от 27.10.2026). Все они в одном месте:
|
||
|
||
- **в игре** — таблица `Assets/Resources/BattleConfig.asset`: выдели её в окне Project и меняй числа в Inspector, изменения действуют со следующего запуска. Скрипт таблицы — `App/BattleConfig.cs`;
|
||
- **в модуле** — `BattleRules.cs` (значения по умолчанию для тестов и сервера). `App/GameModules` переводит таблицу в `BattleRules`;
|
||
- **сила воинов** — в `GameData.Troops`, рядом с ценой и временем обучения;
|
||
- **поменял числа — подними `version`**: итог боя запоминает версию правил (`RulesVersion`).
|
||
|
||
| Правило | Значение |
|
||
| --- | --- |
|
||
| Сила стороны | Сумма по типам: воины × сила типа × (100% + контры + осадный бонус) × (100% + бонусы стороны). |
|
||
| Контры, +25% | Пехота сильнее кавалерии, кавалерия сильнее лучников, лучники сильнее пехоты. Бонус взвешивается по составу врага: против армии, где кавалерии 20%, пехота получает +5%. |
|
||
| Осадные | Против стен +50%, в поле −25%. |
|
||
| Стены | Защитники за стенами сильнее на 20%. |
|
||
| Удача, ±5% | Решение №12. Каждой стороне от −5% до +5% (с шагом 0,1%), отдельно. Считается из номера боя (`Seed`, алгоритм SplitMix64) — одинаково на любой платформе. Применяется после всех модификаторов. Не прокачивается. |
|
||
| Без промежуточного округления | Решение №12. Сила считается точной дробью (`BigInteger`): контра по составу врага, бонусы, голод и удача не округляются по дороге. Округляется один раз — только число для показа. Пример: 3 пехоты против 1 кавалерии и 2 пехоты — сила 13,00 (раньше 12,96). |
|
||
| Голод, −50% | Утверждено 27.10.2026. Сила голодной стороны ×50%. Проверяется на момент боя. Действует и на нападающих, и на защитников. |
|
||
| Усталость, −20% | Утверждено 27.10.2026. Сила ×80%. Правило уже в модуле, но **пока никто её не выставляет**: не утверждено, как учитывать смешивание свежих и уставших войск. |
|
||
| Голод и усталость вместе | Последовательно: 50% × 80% = **40%** силы (а не 30%). |
|
||
| Порядок | Контры и осадные → бонусы стороны → состояния (голод, усталость) → случайность. |
|
||
| Исход | Побеждает тот, у кого итоговая сила больше; ничья — защитнику. |
|
||
| Потери проигравшего | Половина каждого типа (не окончательно: №14 — плавная функция и разгром, ждём кривую). |
|
||
| Потери победителя | Половина × (сила проигравшего / сила победителя): чем ровнее бой, тем больше потерь, но не больше половины. |
|
||
| Округление потерь | Решение №13. Целая часть гарантирована, дробная — шанс ещё одной потери. Бросок — из отдельного потока случайности (своя «соль» на сторону и тип войск, не пересекается с удачей), воспроизводим по номеру боя. Одиночка-проигравший (потеря 0,5) гибнет примерно в половине боёв; 3 проигравших (1,5) теряют 1 или 2. |
|
||
|
||
Примеры (замок Змея Горыныча, гарнизон 20: пехота 8, лучники 6, кавалерия 4, осадные 2, за стенами):
|
||
|
||
| Атакующие | Сила | Исход | Вернутся |
|
||
| --- | --- | --- | --- |
|
||
| 30 пехоты | 126 против 155 | поражение | 15 |
|
||
| 40 пехоты | 168 против 155 | победа | 22 |
|
||
| 30 смешанных (12 / 8 / 6 / 4) | 248 против 154 | победа | — |
|
||
|
||
**Прогноз с учётом случайности** (тот же замок, 10 000 боёв с разными номерами):
|
||
|
||
| Атакующие | Прогноз | Шанс победы | Сила | Потери |
|
||
| --- | --- | --- | --- | --- |
|
||
| 30 пехоты | высокий риск поражения | 0% | 119–132 против 147–163 | 15 |
|
||
| 36 пехоты | исход неясен | 26,5% | 143–158 против 147–163 | 16–18 |
|
||
| 40 пехоты | исход неясен | 97,4% | 159–176 против 147–163 | 16–20 |
|
||
| 45 пехоты | преимущество | 100% | 179–198 против 147–163 | 16–21 |
|
||
| 30 смешанных (12 / 8 / 6 / 4) | преимущество | 100% | 236–260 против 147–162 | 7–13 |
|
||
|
||
Шанс проверен тестом `Battle_WinChance_MatchesRealBattles`: совпадает с долей побед в 20 000 настоящих боёв с точностью ±2%.
|
||
|
||
Бой без состава (старые походы, `Attackers` и `Defenders` числами) считается так, будто все — пехота. Без стен 30 против 20 — по-прежнему победа и 20 вернувшихся.
|
||
|
||
## События
|
||
|
||
Нет: исход возвращается вызвавшему модулю.
|
||
|
||
## Сохранение
|
||
|
||
Нет данных.
|
||
|
||
## Решения и план
|
||
|
||
- **Сделано 28.10.2026 (этап 2 плана):** числа — в таблицу `BattleConfig` с версией; итог боя симметричный для обеих сторон; случайность ±5% из номера боя; прогноз «преимущество / исход неясен / высокий риск поражения».
|
||
- **Решено комплектом 29.10.2026 и сделано 30.10.2026:** шанс победы в прогнозе (№11), сила без промежуточного округления (№12), округление потерь шансом (№13), голод на момент боя по истории голода Населения.
|
||
- **Сделано 28.10.2026: голод в бою.** Голод игрока берётся из модуля Населения (`IPopulation.IsStarving`) в момент боя. Пример: 40 пехоты берут замок Змея Горыныча (168 против 155), голодные — нет (84 против 155). Числа — `famineStrengthPercent` и `fatigueStrengthPercent` в `BattleConfig` (версия правил 2).
|
||
- **Голод в офлайн-бою:** Население хранит историю голода (`IPopulation.WasStarvingAt`, 48 часов, точность — шаг расчёта 10 минут). Перед боем, прошедшим в прошлом, мир просит Население досчитать время, затем спрашивает голод на момент прихода войска.
|
||
- **Голод гарнизона** пока не нужен: на город игрока никто не нападает. Правило защитника уже есть (`DefenderConditions`).
|
||
- **Порядок бонусов и случайности** решён (№12): удача — после всех модификаторов.
|
||
- **Номер боя в мультиплеере** должен выдавать сервер: иначе клиент мог бы подбирать удачный.
|
||
- **Командный план и ревью от 27.10.2026** (`Bogatyri_Plan_razrabotki.docx`, `Bogatyri_Battle_Army_Review_27-10-2026.docx`): случайность ±5% каждой стороне (воспроизводимая), прогноз риска вместо точного итога, потери от соотношения сил, раненые и погибшие, голод −50%, усталость −20%, числа — в настройки. Делается на этапе 2 плана.
|
||
- **Малые отряды** решены (№13): одиночка-проигравший гибнет с шансом по дробной части потерь.
|
||
- **Открыто (№14, №15):** кривая потерь и разгром; доля погибших и раненых от тяжести поражения.
|
||
|
||
- **Утвердить формулу** (контры, стены, осадные, потери) или поправить числа в `BattleSystem.cs`.
|
||
- **Бонус героя** пока не передаётся в поход: `GameProgress.ArmyBonus` и сила героя в слое приложения. Нужен способ передать их в `AttackerBonusPercent` — например, через новый контракт или поле похода.
|
||
- **Решение команды от 19.10.2026:** при голоде сильные дебаффы на боевую эффективность, включая гарнизон и походы. Для этого есть `AttackerBonusPercent` / `DefenderBonusPercent` (минус), проценты — из `EconomyConfig`. Голод уже считает модуль Населения (`IsStarving`).
|
||
- **Гарнизоны соседей** пока не уменьшаются после боя: соседи — заглушки до сервера.
|
||
|
||
## Журнал изменений
|
||
|
||
| Дата | Кто | Что |
|
||
| --- | --- | --- |
|
||
| 30.10.2026 | Claude | Комплект требований 29.10, решения №11–13: точный шанс победы в прогнозе (`BattleForecast.WinChancePermille`); сила — точной дробью без промежуточного округления; потери — целая часть гарантирована, дробная — шанс из отдельного потока случайности. Версия правил 3. Тесты: шанс совпадает с настоящими боями, сила без округления, округление потерь. |
|
||
| 28.10.2026 | Claude | Голод и усталость: `BattleCondition` (флаги), `BattleRequest.AttackerConditions` / `DefenderConditions`, `BattleOutcome.…ConditionPercent`; голод −50%, усталость −20%, вместе 40% (последовательно); числа в `BattleConfig` и `BattleRules` (версия 2). Тесты: штрафы, голод переворачивает бой у нападающего и у защитника, прогноз с голодом. |
|
||
| 28.10.2026 | Claude | Этап 2 плана: числа боя — в таблицу `BattleConfig` (`Assets/Resources/BattleConfig.asset`) и `BattleRules` с версией; случайность ±5% каждой стороне из `BattleRequest.Seed`; `Forecast` — прогноз риска; `BattleOutcome` симметричный: до боя, выжившие, погибшие, раненые (пока нули) по типам у обеих сторон, номер боя, версия правил, удача сторон. Тесты: случайность воспроизводима и в пределах ±5%, прогноз не противоречит бою, итог сходится по типам, правила из таблицы. |
|
||
| 28.10.2026 | Claude | Тест крайних случаев (ревью, п. 2.4): 1×1, 1×10, 10×1, 0×0, пустая сторона, смешанный состав, бонусы −100%, −500%, +100000%, бой числом. Проверяются исход и арифметика (нет отрицательных, выжившие + погибшие = ушедшие). Правило округления потерь — открытый вопрос. |
|
||
| 27.10.2026 | Claude | Бой с типами войск: сила по типам из `GameData.Troops`, контры (пехота > кавалерия > лучники > пехота, +25%), стены +20%, осадные +50% против стен и −25% в поле, потери по типам. `BattleRequest` и `BattleOutcome` расширены, старый бой «числом» считается пехотой. Тесты: контры, стены, осадные, потери в пределах состава. |
|
||
| 19.10.2026 | Claude | Модуль выделен: `BattleResolver` из `WorldSimulation.cs` стал `BattleSystem` с контрактом `IBattleSystem`; `BattleOutcome` перенесён в ядро. Правила не изменились. |
|