Разгром дальше 5:1 (решения 31.10, п. 5): 7:1 -- 92%, 10:1 -- 95%, дальше плавно к 99%

Сильная сторона после 5:1 -- 11% (документ не задаёт). Версия правил боя 5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-11-01 13:07:14 +01:00
co-authored by Claude Opus 5.5
parent 20691e0511
commit 5c4ba98fef
7 changed files with 51 additions and 16 deletions
+8 -1
View File
@@ -12,7 +12,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 7264ce138a844adc81fa69b77a27c8a6, type: 3}
m_Name: BattleConfig
m_EditorClassIdentifier:
version: 4
version: 5
counterPercent: 25
wallsPercent: 20
siegeVsWallsPercent: 50
@@ -24,11 +24,18 @@ MonoBehaviour:
- 1000
- 3000
- 5000
- 7000
- 10000
strongLossPermille:
- 450
- 160
- 110
- 110
- 110
weakLossPermille:
- 450
- 740
- 880
- 920
- 950
weakLossMaxPermille: 990
+6 -3
View File
@@ -28,11 +28,13 @@ public class BattleConfig : ScriptableObject
[Header("Кривая потерь (решение №14, регламент 31.10, ТЕСТ)")]
[Tooltip("Опорные точки соотношения сил (сильная / слабая) в десятых долях процента: 1000 = 1:1, 3000 = 3:1. Первая -- обязательно 1000, дальше по возрастанию")]
public int[] lossRatioPermille = { 1000, 3000, 5000 };
public int[] lossRatioPermille = { 1000, 3000, 5000, 7000, 10000 };
[Tooltip("Доля выбывших у сильной стороны в каждой точке, десятые доли процента: 450 = 45%")]
public int[] strongLossPermille = { 450, 160, 110 };
public int[] strongLossPermille = { 450, 160, 110, 110, 110 };
[Tooltip("Доля выбывших у слабой стороны в каждой точке, десятые доли процента: 740 = 74%")]
public int[] weakLossPermille = { 450, 740, 880 };
public int[] weakLossPermille = { 450, 740, 880, 920, 950 };
[Tooltip("Разгром дальше последней точки: слабая сторона плавно стремится к этому пределу, десятые доли процента. Решения 31.10, п. 5: 990 = 99%")]
public int weakLossMaxPermille = 990;
[Tooltip("Голод: сколько силы остаётся, %. Утверждено 27.10.2026: -50% (остаётся 50). Проверяется на момент боя")]
public int famineStrengthPercent = 50;
@@ -67,6 +69,7 @@ public class BattleConfig : ScriptableObject
LossRatioPermille = lossRatioPermille,
StrongLossPermille = strongLossPermille,
WeakLossPermille = weakLossPermille,
WeakLossMaxPermille = weakLossMaxPermille,
RandomPercent = Mathf.Max(0, randomPercent),
FamineStrengthPercent = Mathf.Clamp(famineStrengthPercent, 0, 100),
FatigueStrengthPercent = Mathf.Clamp(fatigueStrengthPercent, 0, 100)
+10 -5
View File
@@ -6,7 +6,8 @@
// итог боя запоминает версию, по которой посчитан.
public sealed class BattleRules
{
public int Version = 4; // 2 -- голод и усталость; 3 -- сила без округления, потери шансом (№12, №13); 4 -- кривая потерь (№14)
public int Version = 5; // 2 -- голод и усталость; 3 -- сила без округления, потери шансом (№12, №13); 4 -- кривая потерь (№14);
// 5 -- разгром дальше 5:1 (решения по 8 вопросам Саши, 31.10, п. 5)
// Сила одного воина каждого типа. В игре -- из GameData.Troops (там же цена и время обучения).
public int[] UnitPower = { 4, 5, 9, 16 };
@@ -19,10 +20,14 @@ public sealed class BattleRules
// Кривая потерь (решение №14, регламент 31.10, ТЕСТ): доля выбывших от каждого типа в зависимости от
// соотношения итоговых сил (сильная / слабая), в десятых долях процента. Между опорными точками -- плавно
// (линейно), без порогов-переключателей. За последней точкой -- значения последней (рост разгрома дальше 5:1 не задан).
public int[] LossRatioPermille = { 1000, 3000, 5000 }; // 1:1, 3:1, 5:1
public int[] StrongLossPermille = { 450, 160, 110 }; // сильная сторона: 45%, 16%, 11%
public int[] WeakLossPermille = { 450, 740, 880 }; // слабая сторона: 45%, 74%, 88%
// (линейно), без порогов-переключателей.
// Разгром (решения 31.10, п. 5): 7:1 -- 92%, 10:1 -- 95%, дальше слабая сторона плавно стремится к WeakLossMaxPermille (99%):
// v = max - (max - v_last) * R_last / R -- при 20:1 97%, при 100:1 98,6%; 100% не достигается.
// Сильная сторона за последней точкой -- значение последней точки (документ числа для неё не задаёт).
public int[] LossRatioPermille = { 1000, 3000, 5000, 7000, 10000 }; // 1:1, 3:1, 5:1, 7:1, 10:1
public int[] StrongLossPermille = { 450, 160, 110, 110, 110 }; // сильная сторона: 45%, 16%, 11%, 11%, 11%
public int[] WeakLossPermille = { 450, 740, 880, 920, 950 }; // слабая сторона: 45%, 74%, 88%, 92%, 95%
public int WeakLossMaxPermille = 990; // верхний предел ожидаемых потерь слабой стороны: 99%
// Состояния (утверждено 27.10.2026): сколько силы остаётся. Применяются последовательно: 50% x 80% = 40%
public int FamineStrengthPercent = 50; // голод: -50%
+14 -3
View File
@@ -61,8 +61,12 @@ public sealed class BattleSystem : IBattleSystem
lossRatio = (int[])src.LossRatioPermille.Clone();
strongLoss = (int[])src.StrongLossPermille.Clone();
weakLoss = (int[])src.WeakLossPermille.Clone();
// Предел не ниже последней точки и не выше 100%
weakLossMax = Math.Min(1000, Math.Max(weakLoss[weakLoss.Length - 1], r.WeakLossMaxPermille));
}
private int weakLossMax;
// Доля выбывших стороны (в десятых долях процента, дробью) при соотношении сил stronger / weaker (решение №14)
void LossShare(Fraction weaker, Fraction stronger, bool isWeak, out BigInteger num, out BigInteger den)
{
@@ -70,8 +74,8 @@ public sealed class BattleSystem : IBattleSystem
int last = lossRatio.Length - 1;
if (weaker.N.IsZero)
{
// У слабой стороны нулевая сила: сильная не теряет никого, слабая -- как при самом большом перевесе
num = isWeak ? values[last] : 0;
// У слабой стороны нулевая сила: сильная не теряет никого, слабая -- предел разгрома
num = isWeak ? weakLossMax : 0;
den = 1;
return;
}
@@ -80,7 +84,14 @@ public sealed class BattleSystem : IBattleSystem
BigInteger rn = 1000 * stronger.N * weaker.D;
BigInteger rd = stronger.D * weaker.N;
if (rn <= lossRatio[0] * rd) { num = values[0]; den = 1; return; }
if (rn >= lossRatio[last] * rd) { num = values[last]; den = 1; return; }
if (rn >= lossRatio[last] * rd)
{
if (!isWeak) { num = values[last]; den = 1; return; }
// Дальше последней точки слабая сторона плавно стремится к пределу: v = max - (max - v_last) * R_last / R
num = (BigInteger)weakLossMax * rn - (BigInteger)(weakLossMax - values[last]) * lossRatio[last] * rd;
den = rn;
return;
}
int i = 0;
while (rn >= lossRatio[i + 1] * rd) i++; // lossRatio[i] <= R < lossRatio[i + 1]
+9 -2
View File
@@ -556,6 +556,13 @@ public class ModuleTests
Assert.AreEqual(550, five.AttackerLosses, "5:1 -- сильная теряет 11%");
Assert.AreEqual(880, five.DefenderLosses, "5:1 -- слабая теряет 88%");
// Разгром дальше 5:1 (решения 31.10, п. 5): 7:1 -- 92%, 10:1 -- 95%, дальше -- плавно к 99%, но не 100%
Assert.AreEqual(920, Fight(7000, 1000).DefenderLosses, "7:1 -- 92%");
Assert.AreEqual(950, Fight(10000, 1000).DefenderLosses, "10:1 -- 95%");
Assert.AreEqual(970, Fight(20000, 1000).DefenderLosses, "20:1 -- 99 - 4 x 10/20 = 97%");
Assert.AreEqual(986, Fight(100000, 1000).DefenderLosses, "100:1 -- 98,6%");
Assert.AreEqual(1100, Fight(10000, 1000).AttackerLosses, "сильная сторона дальше 5:1 -- 11%");
// Симметрия: тот же перевес у защитника -- те же доли (одинаково для PvE, PvP и Круга)
BattleOutcome threeDef = Fight(1000, 3000);
Assert.AreEqual(740, threeDef.AttackerLosses);
@@ -563,7 +570,7 @@ public class ModuleTests
// Плавность: при росте перевеса сильная теряет всё меньше, слабая -- всё больше, без скачков
int prevStrong = 1000, prevWeak = -1;
for (int att = 1010; att <= 6000; att += 10)
for (int att = 1010; att <= 30000; att += 10)
{
BattleOutcome o = Fight(att, 1000);
int strongPermille = (int)((long)o.AttackerLosses * 1000 / att);
@@ -577,7 +584,7 @@ public class ModuleTests
// Кривая из таблицы: другие точки -- другие потери; ошибка в таблице не ломает бой
var custom = BattleRules.NoRandom;
custom.StrongLossPermille = new[] { 300, 100, 50 };
custom.StrongLossPermille = new[] { 300, 100, 50, 50, 50 };
Assert.AreEqual(300, new BattleSystem(custom).Resolve(new BattleRequest { Attackers = 1000, Defenders = 1000 }).DefenderLosses);
var broken = BattleRules.NoRandom;
broken.LossRatioPermille = new[] { 3000, 1000 }; // не по возрастанию и разной длины