Боевой цикл, этап 2 плана: таблица баланса боя, случайность ±5%, прогноз риска, полный итог боя

Числа боя (ревью 27.10.2026, п. 2.5):
- BattleRules (модуль Боя, без Unity) — все числа в одном месте, с версией
- таблица BattleConfig (Assets/Resources/BattleConfig.asset) — меняется в Inspector, App/GameModules переводит её в BattleRules
- сила воинов по-прежнему из GameData.Troops

Случайность (план 27.10.2026):
- каждой стороне от −5% до +5% (шаг 0,1%), из номера боя BattleRequest.Seed (SplitMix64)
- тот же номер с теми же войсками — всегда тот же итог; поход получает battleSeed при выступлении и хранит его

Прогноз:
- IBattleSystem.Forecast: «преимущество / исход неясен / высокий риск поражения», диапазоны силы и потерь
- окно атаки показывает прогноз риска вместо точного итога; окно итога боя — силы и удачу сторон

Итог боя (ревью, п. 2.3):
- для обеих сторон по типам: до боя, выжившие, погибшие, раненые (пока нули — доля не утверждена)
- номер боя, версия правил, удача сторон

Тесты: случайность воспроизводима и в пределах ±5%; прогноз не противоречит 300 боям с разными номерами;
итог сходится по типам у обеих сторон; правила из таблицы; тот же поход — тот же бой.
Все 37 тестов ModuleTests прогнаны вне Unity — проходят.

Открыто для команды (Battle.md): показывать ли шанс победы в процентах; порядок бонусов и случайности.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-10-28 19:03:14 +01:00
co-authored by Claude Opus 5.5
parent ebb03489d0
commit eeb90b8cb6
16 changed files with 513 additions and 67 deletions
@@ -131,24 +131,56 @@ public struct BattleRequest
public bool DefenderFortified; // защитники за стенами (город, замок)
public int AttackerBonusPercent; // герой, технологии, голод (минус) -- +10 = на 10% сильнее
public int DefenderBonusPercent;
// Номер боя: из него считается случайность сторон (±5%). Один и тот же Seed с теми же войсками --
// всегда тот же итог, бой можно перепроверить. В мультиплеере Seed выдаёт сервер.
public long Seed;
}
// Итог боя -- одинаково полный для обеих сторон. Все массивы -- по типам войск (TroopType).
// По каждому типу: до боя = выжившие + погибшие + раненые.
public struct BattleOutcome
{
public bool Victory; // победил атакующий
public int AttackerLosses;
public int DefenderLosses;
public int Survivors; // сколько атакующих осталось
public long BattleId; // = Seed запроса
public int RulesVersion; // версия правил боя (баланса), по которой посчитан итог
public long AttackerPower; // сила сторон с учётом контр, стен и бонусов
public int AttackerLosses; // выбыли из строя: погибшие + раненые
public int DefenderLosses;
public int Survivors; // сколько атакующих осталось в строю
public long AttackerPower; // итоговая сила сторон x100: с контрами, стенами, бонусами и случайностью
public long DefenderPower;
public int[] AttackerSurvivorUnits; // выжившие атакующие по типам
public int[] DefenderLossUnits; // потери защитников по типам
public int AttackerLuckPermille; // случайность стороны в десятых долях процента: +23 = +2,3%
public int DefenderLuckPermille;
public int[] AttackerUnitsBefore, DefenderUnitsBefore;
public int[] AttackerSurvivorUnits, DefenderSurvivorUnits;
public int[] AttackerDeadUnits, DefenderDeadUnits;
public int[] AttackerWoundedUnits, DefenderWoundedUnits; // пока нули: доля раненых не утверждена командой
public int[] DefenderLossUnits; // погибшие + раненые защитника (для старого кода)
}
// Прогноз до боя: с учётом случайности, без раскрытия итога (план от 27.10.2026)
public enum BattleChance
{
Advantage, // победа при любой случайности
Uncertain, // исход зависит от случайности
HighRisk // поражение при любой случайности
}
public struct BattleForecast
{
public BattleChance Chance;
public long AttackerPowerMin, AttackerPowerMax; // x100, крайние значения случайности
public long DefenderPowerMin, DefenderPowerMax;
public int AttackerLossesMin, AttackerLossesMax;
}
public interface IBattleSystem
{
BattleOutcome Resolve(BattleRequest request);
BattleForecast Forecast(BattleRequest request); // Seed не нужен: перебираются крайние значения случайности
}
// ---------------- Чат: общий мир и Круг ----------------