Пропавшие без вести (решения 01.11, п. 5)

Бой: отдельный статус среди выбывших (доля не утверждена -- 0, выключено), версия правил 8.
Армия: пропавшие из похода -- в группу; окно 6 игровых часов после обратного пути, возвращение 2-4 группами,
70% здоровыми / 30% ранеными, остальные списываются; сохранение без повторного возвращения.
Население: занимают места, но не едят. Окна: сколько пропало и сколько ещё может вернуться.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-11-04 15:16:43 +01:00
co-authored by Claude Opus 5.5
parent cfbdfd9876
commit 6c39c8b102
24 changed files with 492 additions and 21 deletions
+35 -9
View File
@@ -181,15 +181,20 @@ public sealed class BattleSystem : IBattleSystem
int[] attOut = Losses(att, weaker, stronger, isWeak: !victory, request.Seed, side: 1, rounding);
int[] defOut0 = Losses(def, weaker, stronger, isWeak: victory, request.Seed, side: 2, rounding);
// Выбывшие -- погибшие и раненые (решения 31.10, п. 7): доля погибших -- от соотношения сил своей стороны
int[] attDead = Dead(attOut, attPower, defPower, request.Seed, side: 1, rounding);
int[] defDead = Dead(defOut0, defPower, attPower, request.Seed, side: 2, rounding);
// Выбывшие: сначала пропавшие без вести (решения 01.11, п. 5), остальные -- погибшие и раненые (решения 31.10, п. 7):
// доля погибших -- от соотношения сил своей стороны. Солдат -- ровно в одном статусе
int[] attMissing = Missing(attOut, request.Seed, side: 1, rounding);
int[] defMissing = Missing(defOut0, request.Seed, side: 2, rounding);
int[] attRest = new int[TroopType.Count], defRest = new int[TroopType.Count];
for (int i = 0; i < TroopType.Count; i++) { attRest[i] = attOut[i] - attMissing[i]; defRest[i] = defOut0[i] - defMissing[i]; }
int[] attDead = Dead(attRest, attPower, defPower, request.Seed, side: 1, rounding);
int[] defDead = Dead(defRest, defPower, attPower, request.Seed, side: 2, rounding);
int[] attWounded = new int[TroopType.Count];
int[] defWounded = new int[TroopType.Count];
for (int i = 0; i < TroopType.Count; i++)
{
attWounded[i] = attOut[i] - attDead[i];
defWounded[i] = defOut0[i] - defDead[i];
attWounded[i] = attRest[i] - attDead[i];
defWounded[i] = defRest[i] - defDead[i];
}
var attAlive = new int[TroopType.Count];
@@ -197,9 +202,9 @@ public sealed class BattleSystem : IBattleSystem
var defOut = new int[TroopType.Count];
for (int i = 0; i < TroopType.Count; i++)
{
attAlive[i] = att[i] - attDead[i] - attWounded[i];
defAlive[i] = def[i] - defDead[i] - defWounded[i];
defOut[i] = defDead[i] + defWounded[i];
attAlive[i] = att[i] - attOut[i];
defAlive[i] = def[i] - defOut0[i];
defOut[i] = defOut0[i];
}
return new BattleOutcome
@@ -207,7 +212,7 @@ public sealed class BattleSystem : IBattleSystem
Victory = victory,
BattleId = request.Seed,
RulesVersion = rules.Version,
AttackerLosses = Sum(attDead) + Sum(attWounded),
AttackerLosses = Sum(attOut),
DefenderLosses = Sum(defOut),
Survivors = Sum(attAlive),
AttackerPower = attPower.Floor, // округление -- только для показа
@@ -224,6 +229,8 @@ public sealed class BattleSystem : IBattleSystem
DefenderDeadUnits = defDead,
AttackerWoundedUnits = attWounded,
DefenderWoundedUnits = defWounded,
AttackerMissingUnits = attMissing,
DefenderMissingUnits = defMissing,
DefenderLossUnits = defOut
};
}
@@ -340,6 +347,25 @@ public sealed class BattleSystem : IBattleSystem
return lost;
}
// Пропавшие без вести среди выбывших каждого типа: доля из правил (не утверждена; 0 -- никого), дробная часть -- бросок
// из своего потока. Прогноз: Floor -- меньше пропавших, Ceiling -- больше
int[] Missing(int[] outUnits, long seed, int side, Rounding rounding)
{
var missing = new int[TroopType.Count];
int share = Math.Max(0, Math.Min(1000, rules.MissingSharePermille));
if (share == 0) return missing;
for (int i = 0; i < TroopType.Count; i++)
{
if (outUnits[i] <= 0) continue;
long num = (long)outUnits[i] * share;
long whole = num / 1000, rest = num % 1000;
bool extra = rest != 0 && (rounding == Rounding.Ceiling
|| rounding == Rounding.Roll && (LossRoll(seed, side + 24, i) * 1000UL >> 32) < (ulong)rest);
missing[i] = (int)Math.Min(outUnits[i], whole + (extra ? 1 : 0));
}
return missing;
}
// Погибшие среди выбывших каждого типа: доля по соотношению сил стороны (own / enemy), дробная часть -- бросок
// из своего потока (не тот, что у потерь и удачи). Прогноз: Floor -- меньше погибших, Ceiling -- больше.
int[] Dead(int[] outUnits, Fraction own, Fraction enemy, long seed, int side, Rounding rounding)