Files
MVP1/Docs/Modules/Battle.md
T
AlexanderandClaude Opus 5.5 5581b4d2c9 Голод в бою (−50%) и правило усталости (−20%, вместе 40%)
План от 27.10.2026, раздел «Армии и восстановление»:
- BattleCondition (флаги Famine, Fatigue); BattleRequest.AttackerConditions / DefenderConditions
- голод −50%, усталость −20%, применяются последовательно: вместе 40% силы
- порядок: контры и осадные → бонусы → состояния → случайность
- числа в BattleConfig (famineStrengthPercent, fatigueStrengthPercent) и BattleRules, версия правил 2
- BattleOutcome.AttackerConditionPercent / DefenderConditionPercent

Мир:
- голод проверяется на момент боя: WorldSimulation.AttackerConditions спрашивает MarchManager,
  тот берёт IPopulation.IsStarving
- бой в офлайне получает голод на момент входа в игру (истории голода у Населения нет) — записано в Battle.md

Интерфейс карты:
- окно атаки: «Голод: войско слабее, сила 50%. Накормите город до боя», прогноз учитывает голод
- окно итога боя: «голод: сила 50%»

Усталость пока никто не выставляет: не утверждено, как смешивать свежие и уставшие войска.

Тесты: штрафы и их сложение, голод переворачивает бой у нападающего и у защитника, прогноз с голодом,
голод в момент боя у похода. Все 39 тестов ModuleTests проходят вне Unity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:16:25 +01:00

128 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Боевая система
Урон, потери, исход боя с учётом типов войск. Модуль только считает: кто победил и сколько погибло каждого типа. Кто идёт в бой и что делать с итогом, решают другие модули.
- **Сборка:** `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` — поражение при любой случайности. |
| `AttackerPowerMin/Max`, `DefenderPowerMin/Max` | Сила сторон при крайних значениях случайности. |
| `AttackerLossesMin/Max` | Потери атакующего: от лучшего случая до худшего. |
Регистрирует `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% | Каждой стороне от −5% до +5% (с шагом 0,1%), отдельно. Считается из номера боя (`Seed`, алгоритм SplitMix64) — одинаково на любой платформе. Применяется последней: после контр, стен и бонусов. |
| Голод, −50% | Утверждено 27.10.2026. Сила голодной стороны ×50%. Проверяется на момент боя. Действует и на нападающих, и на защитников. |
| Усталость, −20% | Утверждено 27.10.2026. Сила ×80%. Правило уже в модуле, но **пока никто её не выставляет**: не утверждено, как учитывать смешивание свежих и уставших войск. |
| Голод и усталость вместе | Последовательно: 50% × 80% = **40%** силы (а не 30%). |
| Порядок | Контры и осадные → бонусы стороны → состояния (голод, усталость) → случайность. |
| Исход | Побеждает тот, у кого итоговая сила больше; ничья — защитнику. |
| Потери проигравшего | Половина каждого типа. |
| Потери победителя | Половина × (сила проигравшего / сила победителя): чем ровнее бой, тем больше потерь, но не больше половины. |
Примеры (замок Змея Горыныча, гарнизон 20: пехота 8, лучники 6, кавалерия 4, осадные 2, за стенами):
| Атакующие | Сила | Исход | Вернутся |
| --- | --- | --- | --- |
| 30 пехоты | 126 против 155 | поражение | 15 |
| 40 пехоты | 168 против 155 | победа | 22 |
| 30 смешанных (12 / 8 / 6 / 4) | 248 против 154 | победа | — |
**Прогноз с учётом случайности** (тот же замок, 10 000 боёв с разными номерами):
| Атакующие | Прогноз | Сила | Потери | Побед на деле |
| --- | --- | --- | --- | --- |
| 30 пехоты | высокий риск поражения | 119–132 против 147–163 | 15 | 0% |
| 36 пехоты | исход неясен | 143–158 против 147–163 | 16–18 | 27% |
| 40 пехоты | исход неясен | 159–176 против 147–163 | 16–20 | 97% |
| 45 пехоты | преимущество | 179–198 против 147–163 | 16–20 | 100% |
| 30 смешанных (12 / 8 / 6 / 4) | преимущество | 235–260 против 146–162 | 7–9 | 100% |
Бой без состава (старые походы, `Attackers` и `Defenders` числами) считается так, будто все — пехота. Без стен 30 против 20 — по-прежнему победа и 20 вернувшихся.
## События
Нет: исход возвращается вызвавшему модулю.
## Сохранение
Нет данных.
## Решения и план
- **Сделано 28.10.2026 (этап 2 плана):** числа — в таблицу `BattleConfig` с версией; итог боя симметричный для обеих сторон; случайность ±5% из номера боя; прогноз «преимущество / исход неясен / высокий риск поражения».
- **Открыто: прогноз.** «Исход неясен» охватывает и 27%, и 97% побед (таблица выше). Показывать ли игроку шанс победы в процентах — решает команда (посчитать его можно точно).
- **Сделано 28.10.2026: голод в бою.** Голод игрока берётся из модуля Населения (`IPopulation.IsStarving`) в момент боя. Пример: 40 пехоты берут замок Змея Горыныча (168 против 155), голодные — нет (84 против 155). Числа — `famineStrengthPercent` и `fatigueStrengthPercent` в `BattleConfig` (версия правил 2).
- **Ограничение голода в офлайне:** если бой прошёл, пока игра была закрыта, берётся голод на момент входа в игру — истории голода у модуля Населения нет. Точно — когда бои будет считать сервер или Население будет хранить, когда начался и кончился голод (договориться с экономикой).
- **Голод гарнизона** пока не нужен: на город игрока никто не нападает. Правило защитника уже есть (`DefenderConditions`).
- **Открыто: порядок бонусов и случайности.** Сейчас случайность применяется последней, после бонусов стороны.
- **Номер боя в мультиплеере** должен выдавать сервер: иначе клиент мог бы подбирать удачный.
- **Командный план и ревью от 27.10.2026** (`Bogatyri_Plan_razrabotki.docx`, `Bogatyri_Battle_Army_Review_27-10-2026.docx`): случайность ±5% каждой стороне (воспроизводимая), прогноз риска вместо точного итога, потери от соотношения сил, раненые и погибшие, голод −50%, усталость −20%, числа — в настройки. Делается на этапе 2 плана.
- **Открыто: малые отряды и округление.** Потери округляются вниз по каждому типу, поэтому одиночный проигравший не гибнет никогда. Проверено 28.10.2026:
| Бой | Исход | Потери атакующего | Потери защитника |
| --- | --- | --- | --- |
| 1 против 1 | ничья, защитнику | 0 | 0 |
| 1 против 10 | поражение | 0 | 0 |
| 10 против 1 | победа | 0 | 0 |
Варианты: округлять потери проигравшего вверх; или воспроизводимая случайность для остатка (одиночка гибнет с шансом 50%). Решает команда, тест `Battle_EdgeCases_OutcomeAndArithmetic` правило не фиксирует.
- **Утвердить формулу** (контры, стены, осадные, потери) или поправить числа в `BattleSystem.cs`.
- **Бонус героя** пока не передаётся в поход: `GameProgress.ArmyBonus` и сила героя в слое приложения. Нужен способ передать их в `AttackerBonusPercent` — например, через новый контракт или поле похода.
- **Решение команды от 19.10.2026:** при голоде сильные дебаффы на боевую эффективность, включая гарнизон и походы. Для этого есть `AttackerBonusPercent` / `DefenderBonusPercent` (минус), проценты — из `EconomyConfig`. Голод уже считает модуль Населения (`IsStarving`).
- **Гарнизоны соседей** пока не уменьшаются после боя: соседи — заглушки до сервера.
## Журнал изменений
| Дата | Кто | Что |
| --- | --- | --- |
| 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` перенесён в ядро. Правила не изменились. |