Files
MVP1/Docs/Modules/Battle.md
T
AlexanderandClaude Opus 5.5 6c39c8b102 Пропавшие без вести (решения 01.11, п. 5)
Бой: отдельный статус среди выбывших (доля не утверждена -- 0, выключено), версия правил 8.
Армия: пропавшие из похода -- в группу; окно 6 игровых часов после обратного пути, возвращение 2-4 группами,
70% здоровыми / 30% ранеными, остальные списываются; сохранение без повторного возвращения.
Население: занимают места, но не едят. Окна: сколько пропало и сколько ещё может вернуться.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 15:16:43 +01:00

135 lines
22 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` — поражение при любой случайности. |
| `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, регламент 31.10) | Доля выбывших каждого типа зависит от соотношения итоговых сил (сильная / слабая). Опорные точки (ТЕСТ): **1:1 — 45% / 45%**, **3:1 — 16% / 74%**, **5:1 — 11% / 88%**. Между точками — плавно (линейно по соотношению), без порогов. Одинаково для PvE, PvP и Круга. Разгром (решения по 8 вопросам Саши, 31.10, п. 5): **7:1 — 92%**, **10:1 — 95%** у слабой стороны, дальше плавно к пределу 99% (`v = 99% − 4% × 10 / R`: 20:1 — 97%, 100:1 — 98,6%), 100% не достигается. Небольшая армия всё равно может погибнуть целиком из-за округления шансом (№13). Сильная сторона (решения по отчёту Саши, 01.11, п. 1): 5:1 — 11%, плавно до **2% при 10:1**, дальше 2%. Точки — в `BattleConfig` (`lossRatioPermille`, `strongLossPermille`, `weakLossPermille`, предел `weakLossMaxPermille`). |
| Округление потерь | Решение №13. Целая часть гарантирована, дробная — шанс ещё одной потери. Бросок — из отдельного потока случайности (своя «соль» на сторону и тип войск, не пересекается с удачей), воспроизводим по номеру боя. Одиночка-проигравший (потеря 0,5) гибнет примерно в половине боёв; 3 проигравших (1,5) теряют 1 или 2. |
Примеры (замок Змея Горыныча, гарнизон 20: пехота 8, лучники 6, кавалерия 4, осадные 2, за стенами):
| Атакующие | Исход | Вернутся (в среднем) | Враг теряет из 20 |
| --- | --- | --- | --- |
| 30 пехоты | поражение | 15,5 | 8,3 |
| 40 пехоты | победа в 97% боёв | 22,5 | 9,2 |
| 30 смешанных (12 / 8 / 6 / 4) | победа | 19,1 | 10,8 |
| 78 смешанных | победа | 67,7 | 16,4 |
**Прогноз с учётом случайности** (тот же замок, 10 000 боёв с разными номерами):
| Атакующие | Прогноз | Шанс победы | Сила | Потери |
| --- | --- | --- | --- | --- |
| 30 пехоты | высокий риск поражения | 0% | 119–132 против 147–163 | 14–16 |
| 36 пехоты | исход неясен | 26,5% | 143–158 против 147–163 | 16–18 |
| 40 пехоты | исход неясен | 97,4% | 159–176 против 147–163 | 16–19 |
| 45 пехоты | преимущество | 100% | 179–198 против 147–163 | 18–20 |
| 30 смешанных (12 / 8 / 6 / 4) | преимущество | 100% | 236–260 против 147–162 | 9–14 |
Шанс проверен тестом `Battle_WinChance_MatchesRealBattles`: совпадает с долей побед в 20 000 настоящих боёв с точностью ±2%.
Бой без состава (старые походы, `Attackers` и `Defenders` числами) считается так, будто все — пехота. Без стен 30 против 20 (1,5:1) — победа, возвращаются 18–19.
## События
Нет: исход возвращается вызвавшему модулю.
## Сохранение
Нет данных.
## Решения и план
- **Сделано 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): одиночка-проигравший гибнет с шансом по дробной части потерь.
- **Сделано 31.10.2026: кривая потерь (№14)** по опорным точкам регламента 31.10.
- **Сделано 01.11.2026: разгром дальше 5:1** по решениям 31.10, п. 5. Потери сильной стороны после 5:1 — решены 01.11 (до 2% при 10:1).
- **Сделано 01.11.2026: погибшие и раненые** (регламент 31.10, решения по 8 вопросам Саши, п. 7). Из выбывших стороны погибает доля по её соотношению сил (своя / чужая): **1:5 — 70%**, **1:2 — 45%**, **1:1 — 30%**, **3:1 и выше — 20%**, между точками плавно; остальные — раненые. Дробная часть — бросок из отдельного потока (не удачи и не потерь). Точки — `BattleConfig` (`deadRatioPermille`, `deadSharePermille`). Версия правил 6. Раненых хранит и лечит Армия (Army.md).
- **Открыто:** «пропавшие без вести» (регламент) — отдельно не считаются.
- **Утвердить формулу** (контры, стены, осадные, потери) или поправить числа в `BattleSystem.cs`.
- **Бонус героя** пока не передаётся в поход: `GameProgress.ArmyBonus` и сила героя в слое приложения. Нужен способ передать их в `AttackerBonusPercent` — например, через новый контракт или поле похода.
- **Решение команды от 19.10.2026:** при голоде сильные дебаффы на боевую эффективность, включая гарнизон и походы. Для этого есть `AttackerBonusPercent` / `DefenderBonusPercent` (минус), проценты — из `EconomyConfig`. Голод уже считает модуль Населения (`IsStarving`).
- **Гарнизоны соседей** пока не уменьшаются после боя: соседи — заглушки до сервера.
## Журнал изменений
| Дата | Кто | Что |
| --- | --- | --- |
| 04.11.2026 | Claude | Пропавшие без вести (решения 01.11, п. 5): из выбывших сначала отделяются пропавшие (`MissingSharePermille`, не утверждено — 0), остальные делятся на погибших и раненых. `BattleOutcome.AttackerMissingUnits` / `DefenderMissingUnits`. Версия правил 8. |
| 04.11.2026 | Claude | Решения по отчёту Саши от 01.11, п. 1: потери сильной стороны 5:1 → 10:1 плавно с 11% до 2%, дальше 2% (`strongLossPermille` 450 / 160 / 110 / 74 / 20). Версия правил 7. |
| 01.11.2026 | Claude | Раненые (решения 31.10, п. 7): выбывшие делятся на погибших и раненых по соотношению сил стороны — 70% / 45% / 30% / 20% погибших при 1:5 / 1:2 / 1:1 / 3:1. Таблица в `BattleConfig`, версия правил 6, тест `Battle_DeadAndWounded_ShareBySideRatio`. |
| 01.11.2026 | Claude | Разгром дальше 5:1 (решения 31.10, п. 5): точки 7:1 — 92% и 10:1 — 95%, дальше слабая сторона плавно стремится к 99% (`weakLossMaxPermille`). Версия правил 5. Тест кривой продлён до 30:1. |
| 31.10.2026 | Claude | Кривая потерь (решение №14, регламент 31.10): доля выбывших от соотношения итоговых сил по опорным точкам 1:1 — 45% / 45%, 3:1 — 16% / 74%, 5:1 — 11% / 88%, между ними плавно; вместо «проигравший 50%». Точки в `BattleConfig`, неверная таблица — значения по умолчанию. Версия правил 4. Тест `Battle_LossCurve_ControlPointsAndSmoothness`; тесты округления пересчитаны под кривую. |
| 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` перенесён в ядро. Правила не изменились. |