Feature/3d hud #18
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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%
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 }; // не по возрастанию и разной длины
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
| Голод и усталость вместе | Последовательно: 50% × 80% = **40%** силы (а не 30%). |
|
||||
| Порядок | Контры и осадные → бонусы стороны → состояния (голод, усталость) → случайность. |
|
||||
| Исход | Побеждает тот, у кого итоговая сила больше; ничья — защитнику. |
|
||||
| Потери (решение №14, регламент 31.10) | Доля выбывших каждого типа зависит от соотношения итоговых сил (сильная / слабая). Опорные точки (ТЕСТ): **1:1 — 45% / 45%**, **3:1 — 16% / 74%**, **5:1 — 11% / 88%**. Между точками — плавно (линейно по соотношению), без порогов. Одинаково для PvE, PvP и Круга. За 5:1 — как при 5:1. Точки — в `BattleConfig` (`lossRatioPermille`, `strongLossPermille`, `weakLossPermille`). |
|
||||
| Потери (решение №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). Сильная сторона после 5:1 — 11%. Точки — в `BattleConfig` (`lossRatioPermille`, `strongLossPermille`, `weakLossPermille`, предел `weakLossMaxPermille`). |
|
||||
| Округление потерь | Решение №13. Целая часть гарантирована, дробная — шанс ещё одной потери. Бросок — из отдельного потока случайности (своя «соль» на сторону и тип войск, не пересекается с удачей), воспроизводим по номеру боя. Одиночка-проигравший (потеря 0,5) гибнет примерно в половине боёв; 3 проигравших (1,5) теряют 1 или 2. |
|
||||
|
||||
Примеры (замок Змея Горыныча, гарнизон 20: пехота 8, лучники 6, кавалерия 4, осадные 2, за стенами):
|
||||
@@ -108,7 +108,7 @@
|
||||
- **Командный план и ревью от 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. Раненых пока нет: все выбывшие считаются погибшими.
|
||||
- **Открыто: разгром дальше 5:1.** Регламент даёт точки до 5:1; дальше доли держатся на 11% / 88%. Растёт ли разгром выше (например, до 100% при 10:1) — решает команда.
|
||||
- **Сделано 01.11.2026: разгром дальше 5:1** по решениям 31.10, п. 5. **Открыто:** потери сильной стороны после 5:1 документ не задаёт — пока 11%, как при 5:1.
|
||||
- **Следующий шаг — раненые (№15)** вместе с госпиталем. Доля погибших среди выбывших (ТЕСТ регламента): уверенная победа ~20%, близкий бой ~30%, поражение 2:1 ~45%, разгром 5:1 ~70%. Чтобы раненые не «пропали» из людей города, их должно учитывать Население (Валдемар) — договориться о контракте до реализации.
|
||||
|
||||
- **Утвердить формулу** (контры, стены, осадные, потери) или поправить числа в `BattleSystem.cs`.
|
||||
@@ -120,6 +120,7 @@
|
||||
|
||||
| Дата | Кто | Что |
|
||||
| --- | --- | --- |
|
||||
| 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). Тесты: штрафы, голод переворачивает бой у нападающего и у защитника, прогноз с голодом. |
|
||||
|
||||
@@ -105,6 +105,7 @@ EditMode-тесты: `Assets/Scripts/Modules/Tests/ModuleTests.cs`. Запуск
|
||||
|
||||
| Дата | Кто | Что |
|
||||
| --- | --- | --- |
|
||||
| 01.11.2026 | Claude | Решения 31.10, п. 5: разгром дальше 5:1 (92% / 95% / до 99%). Обновлён Battle. |
|
||||
| 01.11.2026 | Claude | Решения 31.10, п. 4: средняя скорость смешанного войска. Обновлён World. |
|
||||
| 01.11.2026 | Claude | Решения по 8 вопросам Саши (31.10), п. 1: гарнизоны и запасы точек видны только в зоне обзора. Обновлён Intelligence. |
|
||||
| 31.10.2026 | Claude | Окна зданий: подписи над зданиями убраны, у каждого здания (добывающие, Дом, Склад, Амбар, Ратуша, Кристалл) своё окно по тапу — состояние, добыча и склад, улучшение и его цена. Казармы тоже получили окно и уровни (открывают новые войска вместе с Ратушей; уровни 1/2/3/5 — ТЕСТ). Ратуша улучшается кнопкой в окне, а не тапом. Обновлён Settlement. |
|
||||
|
||||
Reference in New Issue
Block a user