Combat Resolution — "The Clash"
A design proposal for how two units fight in Every Last City. Covers the resolution procedure, the modifier system, morale and retreat, damage carried between turns, and the odds readout the player sees before committing. Everything here is original design; no value, table, or name is derived from any prior game.
Every number in this document was generated by a reference implementation at
C:/Users/jwebb/AppData/Local/Temp/claude/C--Users-jwebb-workspace-ModernEmpire/bb7f92e4-112a-4802-9b4d-78f83191204e/scratchpad/combat-ref.mjs,
with the performance and exactness claims verified by
.../scratchpad/combat-perf.mjs. If you change a constant, re-run those and re-generate the tables
rather than editing numbers by hand.
1. The problem
The classic shape of this genre resolves combat as repeated coin-flips at fixed odds until one side is destroyed. It has four specific failures, and they are worth naming separately because they need different fixes:
- No terrain drama. A mountain and a wheat field are the same battlefield. The map is a connectivity graph, not a landscape.
- No combined-arms payoff. Every unit is a number. Bringing a different kind of unit is worth nothing that bringing another of the same kind is not.
- No tactical texture after commitment. Once the attack is launched you watch. There is no posture, no positioning payoff, no way to be clever.
- Fight-to-the-death erases maneuver. If every engagement ends with one side gone, there are no fronts, no fighting withdrawals, no pockets, no operational shape at all. The map becomes a sequence of duels.
There is a fifth failure that is really a failure of presentation: a repeated-coin-flip system is technically predictable (you can compute the distribution) but practically opaque, so players develop superstition instead of skill. Any replacement has to be predictable in the readout, not just in principle.
The design has to fix all five without becoming fiddly, without breaking integer determinism, and while resolving hundreds of combats per turn on maps thousands of tiles on a side.
2. The two decisions that shape everything else
The brief asks for these explicitly. I am taking both of the "harder" options, and I want to argue for them before describing the machinery, because everything downstream follows.
2.1 Combat admits retreat and rout. It is not fight-to-the-death.
Decision: a combat is a single exchange, after which either side may break and give ground. Most engagements end with both units alive. Destruction happens when a broken unit has nowhere to go, or when it is ground below zero strength over several engagements.
Fight-to-the-death is genuinely simpler and it makes every attack decisive, which has real appeal. I am rejecting it because it makes the map flat. If an attack always ends with a corpse, then the only question is ever "do I have more?", and positioning collapses into arithmetic. Retreat is what creates a front: a line you can push, bend, and break through, where taking ground and destroying an enemy are different achievements requiring different plans.
The decisive payoff is that encirclement becomes the real win condition of tactical play, and it falls out of two independent, individually trivial rules — "a broken unit retreats" and "a unit with no retreat tile is destroyed instead". You do not need a special surrounded-unit rule; you need to have taken away the roads. That is the single best thing in this design and it is nearly free.
2.2 Units carry damage between turns, and recover slowly.
Decision: every unit has a persistent integer strength in 0–100 and a persistent integer
disorder in 0–100. Strength is how much of the unit is left; disorder is how badly shaken it is.
Both persist across turns; both recover, at different rates and under different conditions.
A unit at 60 strength fights at 60% of its power — the same normalized scale for every unit type, so "this unit is at 60%" means the same thing to a rifle company and an armored battalion, and the UI can show one bar. Reduced strength also reduces resolve, so worn units break sooner: attrition compounds, which is what makes a front wear out and rotation a real decision.
This is the expensive choice. It adds per-unit state, a recovery pass, and a whole category of player decision ("pull it out or push it once more"). I think it is worth it because it is what makes bombardment, rotation, depots, and grinding exist at all, and because without it retreat is meaningless — a unit that retreats undamaged has lost nothing and will simply come back.
3. The design in one page
A combat is called a Clash. It resolves in a single exchange, not a loop, and it produces two independent results: losses (strength removed from both sides) and break (whether either side loses its nerve).
ATTACK POWER A ─┐ ┌─ losses to defender (Ld)
├─ one draw, one ratio ──►┤
DEFENCE POWER D ─┘ └─ losses to attacker (La)
Ld + flanking + assault ≥ defender's RESOLVE ? → defender breaks
La + entrenched + works ≥ attacker's RESOLVE ? → attack falters
The odds ratio A:D decides how the losses are split — the stronger side takes proportionally fewer. It does not decide who dies. Whether the tile changes hands is decided by the break check, which is driven by shock (losses taken) against resolve (grade, posture, dig-in, supply, and current condition). Those are two separate levers and the whole texture of the system comes from the fact that they can point in different directions: you can bleed an enemy badly without shifting them, or shift them without killing them.
Five things a player controls, in descending order of importance:
| Lever | What it does | Where it lands |
|---|---|---|
| Where you fight | terrain changes both the odds and how reliable they are | A, D, volatility |
| Who is adjacent | flanking adds shock, and it removes retreat tiles | break check, retreat |
| Posture | Assault / Guarded / Entrenched / Withdraw, standing per unit | A, D, resolve |
| What you bring | mixing roles gives a combined-arms bonus; roles suit different terrain | A |
| Condition | disorder and strength you inflicted earlier this turn or last | D, resolve |
Note what is not on the list: rolling well. That is deliberate, and section 10 makes the case.
4. What a unit carries
| Field | Range | Persists | Meaning |
|---|---|---|---|
strength |
0–100 | yes | proportion of the unit remaining; scales its power linearly |
disorder |
0–100 | yes | accumulated shock; subtracts directly from resolve |
xp |
0–999 | yes | drives grade: Green 0, Trained 100, Veteran 300, Elite 700 |
posture |
enum | yes | Assault / Guarded / Entrenched / Withdraw |
digIn |
0–4 | yes | turns spent stationary in Entrenched; reset on any move |
supply |
enum | derived | Cut / Strained / Adequate / Full, from the supply subsystem |
Six fields, four of which are one byte. On a 5,000-unit late-game state that is under 30 KB, which is nothing for storage and manageable for a player only if the UI composes them into a single condition badge (see §9.4). The bookkeeping cost is real and I return to it in §12.
Static per-unit-type stats used below: atk, def, tough, and a role in {Foot, Armor, Gun,
Recon}. tough divides incoming losses — it is how the roster expresses "hard to grind down"
independently of "hard to hurt". A four-unit sample cast, invented here to make the examples
concrete:
| Unit | Role | atk | def | tough | notes |
|---|---|---|---|---|---|
| Rifle Company | Foot | 30 | 44 | 100 | the baseline; defends far better than it attacks |
| Armored Battalion | Armor | 62 | 38 | 130 | attacks hard, absorbs losses, hates cover |
| Field Battery | Gun | 48 | 18 | 70 | bombards at range 2; nearly helpless if reached |
| Scout Troop | Recon | 18 | 22 | 60 | wide vision; exists to screen and to see |
5. The resolution procedure
All arithmetic is integer. floor means floor toward negative infinity. The rule I follow
throughout is accumulate in a wide integer, divide once — every named quantity below is produced
by a single division, so there is no compounding rounding to reason about and no fixed-point
representation is needed at all. Q16.16 is available but this subsystem does not need it.
Step 0 — Assemble the force
The lead attacker is the unit issuing the order; it must be adjacent to the target tile. Up to two supporters may join: friendly units adjacent to the target tile that have not yet acted or supported this turn. Support costs the supporter its support for the turn and +10 disorder, but not its move. Supporters are opt-in in the attack panel, defaulted on.
The defender is the top unit of the target stack — the one with the highest defence power. Other units in the stack add a defence modifier but do not take losses.
Why support is automatic-by-adjacency rather than a separate order: the interesting decision is where the battery sits, not clicking it. Positioning earns the bonus; the order does not.
Step 1 — Each unit's raw contribution
contribution = floor( base × strength × (1000 + gradePower + supplyPower + roleTerrain) × factor
/ 100 000 000 )
base is atk for attackers and def for the defender. factor is 1000 for the lead and the
defender, 400 for a supporter. roleTerrain is looked up by the unit's role against the defender's
terrain — both sides are fighting in the defender's tile, so one terrain governs everyone.
A_raw is the sum of attacker contributions; D_raw is the defender's contribution alone.
Step 2 — Force-level modifiers
A = max(1, floor( A_raw × (1000 + clamp(attackerMods, −700, +1800)) / 1000 ))
D = max(1, floor( D_raw × (1000 + clamp(defenderMods, −700, +1800)) / 1000 ))
All modifiers are additive per-mille, not multiplicative. This is a deliberate choice against the grain. Multiplicative stacking is more "realistic" and produces unassailable turtles: forest × dug-in × works × stack is 4–5× defence, and the player's marginal bonus is worth more the more they already have. Additive stacking means every bonus is worth the same wherever you find it, which makes the one you are missing worth pursuing and makes the arithmetic something a player can do in their head. The clamps are a safety rail, not a normal operating range — the worst reachable defensive stack (mountain + entrenched + dug-in 4 + full stack) is +1600, inside the cap.
Step 3 — Volatility
volatility = clamp( floor((volAttacker + volDefender) / 2) + terrainChaos, 250, 1500 )
volX = gradeVol[grade] + supplyVol[supply]
Terrain does not only change the odds — it changes how much you can trust them. Open ground is predictable; forest, marsh, and above all city fighting are gambles. This is the cheapest high-value idea in the design: one extra column in the terrain table buys a whole strategic dimension, and it gives experience and recon a job that is not just "bigger number".
Step 4 — Resolve
Resolve = clamp( 40 + gradeResolve + postureResolve + 6×works + (inOwnCity ? 10 : 0)
+ supplyResolve − disorder − floor((100 − strength) / 2)
− (encircled ? 20 : 0),
5, 95 )
Computed for both sides. Note the two condition terms: disorder subtracts point for point, and being worn subtracts half a point per point of missing strength. A unit at 60 strength with 30 disorder has lost 50 resolve before anything else is counted. This is the compounding that makes attrition mean something.
Step 5 — The draw
Exactly one random number per clash:
h = coreHash(seed, turn, attackerSequenceNumber, attackOrdinal) // 64-bit, counter-based
r = ((h & 0xFFFFFFFF) × 1000) >> 32 // uniform in [0, 1000)
The hash is positionally addressed, not drawn from a stream cursor. The value for a given attack
depends only on the identity of that attack, never on how many other combats happened first. This is
not a nicety — it is what makes the pipelined activation model of the direction document safe. Under
overlapping activation windows (direction §4 option B), two players resolving combats concurrently
would corrupt a shared cursor; with positional addressing they cannot. Deriving r by multiply-shift
rather than mod 1000 avoids modulo bias entirely.
Step 6 — The exchange
s = floor((r − 500) × volatility / 2000) // per-mille swing, ±volatility/4
A_eff = max(1, floor(A × (1000 + s) / 1000))
D_eff = max(1, floor(D × (1000 − s) / 1000))
shareA = floor(A_eff × 1000 / (A_eff + D_eff))
shareD = 1000 − shareA
Ld = min(defenderStrength, floor(INTENSITY × shareA × 100 / (1000 × toughDefender)))
La = min(attackerStrength, floor(INTENSITY × shareD × 100 / (1000 × toughAttacker)))
INTENSITY is 60 for an assault, 22 for a bombardment (and La = 0 for a bombardment — the gunners
are not in reach). The single draw pushes the odds ratio both ways at once, so the total heat of the
battle is roughly constant and only the split moves. At volatility 1000 the swing is ±25%, which
moves a 2:1 attack anywhere between about 1.2:1 and 3.3:1.
At even odds each side loses 30 strength, so a unit survives roughly three even fights. That is
the calibration anchor for INTENSITY = 60: long enough that a unit is a persistent asset with a
history, short enough that a contested tile resolves in two or three turns rather than ten.
The saturating shape of A/(A+D) means overwhelming odds have diminishing returns on damage —
going from 4:1 to 10:1 moves the defender's losses from 48 to 54. The payoff for overwhelming force
is that your own losses collapse (12 down to 5) and the break becomes certain. This is deliberate
anti-doom-stack design: massing is rewarded with safety, not with a bigger hammer.
Step 7 — The break checks
shockDefender = Ld + 8 × min(4, flankers) + (attacker is Assault ? 6 : 0)
shockAttacker = La + (defender is Entrenched ? 10 : 0) + min(16, 8 × works)
defender breaks ⟺ shockDefender ≥ resolveDefender
attacker breaks ⟺ shockAttacker ≥ resolveAttacker AND the defender did not break
flankers counts friendly units adjacent to the target tile other than the lead attacker
(supporters count). Flanking contributes shock, never power. Surrounding a unit does not make
your shells hit harder; it makes the unit decide it cannot stay. Keeping flanking out of the power
calculation is what stops "adjacency bonus" from degenerating into "bring more units".
A defender break suppresses an attacker break: you are not thrown back from a position you have just taken.
Step 8 — Consequences
| Outcome | What happens |
|---|---|
| Defender breaks, retreat tile exists | Defender vacates to the chosen tile, +10 disorder, arrives with movement spent. The whole stack goes with it. The lead attacker advances if it has movement left and its posture is Assault or Guarded. |
| Defender breaks, no retreat tile | Defender (and its whole stack) is destroyed. |
| Defender breaks, but cannot retreat by rule (in a city or fortification) | Not destroyed — a break in works means the defender fights on at −20 resolve next time. Cities fall to strength loss, not to rout. |
| Attacker breaks | Losses still apply. The attacker does not advance, gains +25 disorder, and may not attack again this turn. |
| Neither breaks | Losses apply, nobody moves. The attacker may attack again if it has movement. |
Retreat destination, deterministically: among adjacent tiles that are passable, not enemy-occupied, not the attacker's tile, and have stack room, score each as
score = 4000 × (enemy units adjacent to candidate)
− terrainCover(candidate)
− (friendly unit present ? 500 : 0)
− (closer to nearest friendly city than current tile ? 2000 : 0)
and take the lowest, breaking ties by row-major tile index. The chosen tile is shown in the attack preview before you commit, which turns "cut their line of retreat" from a thing that happens to you into a thing you plan.
Step 9 — Bookkeeping
| Event | Disorder change |
|---|---|
| Made an assault | attacker +6 + floor(La/2) |
| Was assaulted | defender +floor(Ld/2) |
| Was bombarded | defender +3 × Ld + 5 |
| Broke and retreated | +10 |
| Attack faltered | +25 |
| Supported an attack | supporter +10 |
| Disengaged (Withdraw) | +15 |
Clamped to 0–100. Experience: xp += 2 + floor(damageDealt / 4) + (enemy broke ? 10 : 0), capped at
25 per turn so units cannot be farmed on a helpless target.
End-of-turn recovery, applied only if the unit did not fight this turn (disorder recovery is halved if it did):
| Disorder | Strength | |
|---|---|---|
| In the field, supply Adequate/Full | −20 | +4 |
| Entrenched, supply Adequate/Full | −35 | +4 |
| In a friendly city | −45 | +12 |
| In a friendly city with a depot | −45 | +20 |
| Supply Strained | half the above | 0 |
| Supply Cut | 0 | 0 |
A unit ground down to 40 strength is 5 turns in a city or 15 turns in the field from full. That asymmetry is the point: rotation is a real decision with a real cost, and it gives cities a purpose beyond production.
6. The tables
Role × terrain (applies to each unit's own contribution, per-mille)
| Terrain | Foot | Armor | Gun | Recon |
|---|---|---|---|---|
| Open | 0 | +150 | 0 | +100 |
| Hills | +100 | −150 | +100 | 0 |
| Forest | +150 | −300 | −150 | −100 |
| Marsh | 0 | −400 | −250 | −150 |
| Mountain | +200 | −500 | −200 | −150 |
| Urban | +200 | −250 | −100 | −200 |
This one table is the whole combined-arms argument. Armor is 150 per-mille better than foot in the open and 700 worse in mountains. There is no terrain where one role is correct everywhere, so a force that is all one thing has a map full of tiles it cannot profitably touch.
Terrain cover and chaos
| Terrain | Cover (defender force mod) | Chaos (volatility) |
|---|---|---|
| Open | 0 | −150 |
| Marsh | +250 | +250 |
| Hills | +300 | +50 |
| Forest | +400 | +200 |
| Urban | +500 | +350 |
| Mountain | +600 | +150 |
Marsh is the odd one: only moderate cover, but the highest chaos short of a city. It is a bad place to be and a worse place to plan.
Postures
| Posture | Attack force mod | Defence force mod | Resolve | Other |
|---|---|---|---|---|
| Assault | +250 | −200 | −10 | advances after a win; +6 shock to the defender |
| Guarded | 0 | 0 | 0 | the default; advances after a win |
| Entrenched | −400 | +300 | +15 | accrues dig-in; +10 shock to the attacker; never advances |
| Withdraw | cannot attack | −200 if caught | 0 | see below |
Dig-in accrues +100 defence per full turn stationary in Entrenched, to a maximum of 4 (+400), and resets on any movement. A fortification of works level W does not add a defence modifier of its own; it sets the occupant's dig-in floor to W (so you get the position instantly instead of waiting four turns), adds +6 resolve per level, and forbids retreat. Keeping works out of the additive defence stack is what stops a city defender pinning at the modifier cap, and it reframes fortification as time saved and stubbornness rather than a fourth multiplier.
Withdraw is resolved before any exchange and involves no dice at all. If a legal retreat tile
exists, the unit disengages immediately: it takes a flat parting loss of floor(1000 / tough) — 16
for a Scout Troop, 14 for a Field Battery, 10 for a Rifle Company, 7 for an Armored Battalion — gains
+15 disorder, and yields the tile. The attacker takes nothing and advances. If there is no retreat
tile the unit is caught: a normal exchange at −200 defence, which is usually fatal.
This makes screening forces mechanically real. A Scout Troop in Withdraw trades one strength point for one enemy attack and one tile, indefinitely, as long as there is room behind it. It costs the attacker tempo, which is the resource a fast advance is actually spending.
Grade and supply
| Grade | Power | Resolve | Volatility |
|---|---|---|---|
| Green | +0 | +0 | 1000 |
| Trained | +80 | +5 | 850 |
| Veteran | +180 | +12 | 700 |
| Elite | +300 | +20 | 550 |
| Supply | Power | Resolve | Volatility |
|---|---|---|---|
| Cut | −400 | −15 | +300 |
| Strained | −150 | −5 | +100 |
| Adequate | 0 | 0 | 0 |
| Full | +50 | 0 | −100 |
Elite is +30% power, not +200%. Veterans should be reliably better, not individually decisive — the volatility drop from 1000 to 550 is arguably the larger effect, and it is the one that matters to a planner.
7. Worked example
The situation. Belvoir Wood, a forest tile. The 3rd Rifles hold it: Green, strength 78, disorder 15, Entrenched, dug in 2 turns, supply Adequate, alone in the tile. Attacking are the 2nd Armored Battalion (Veteran, strength 92, supply Full, posture Assault) from the west, with the 1st Field Battery (Trained, strength 100, supply Full) adjacent to the north, joining as support. Both attackers are adjacent to the target, so the flanker count is 1.
Step 1 — contributions.
| Working | Result | |
|---|---|---|
| 2nd Armored | 62 × 92 × (1000 + 180 + 50 − 300) × 1000 / 1e8 |
53 |
| 1st Field Battery (support ×0.40) | 48 × 100 × (1000 + 80 + 50 − 150) × 400 / 1e8 |
18 |
| A_raw | 71 | |
| 3rd Rifles | 44 × 78 × (1000 + 0 + 0 + 150) × 1000 / 1e8 |
39 |
| D_raw | 39 |
Step 2 — force modifiers.
Attacker: Assault +250, combined arms (Armor + Gun, two roles) +120 → +370.
A = floor(71 × 1370 / 1000) = 97
Defender: forest cover +400, Entrenched +300, dug in 2 → +200 → +900.
D = floor(39 × 1900 / 1000) = 74
Odds 97 : 74 = 1.31 : 1.
Step 3 — volatility. Attacker 700 − 100 = 600; defender 1000 + 0 = 1000; average 800; forest chaos +200 → 1000. Swing range −250 to +249.
Step 4 — resolve.
Defender: 40 + 0 + 15 − 15 − floor(22/2) = 29.
Attacker: 40 + 12 − 10 − floor(8/2) = 38.
Step 5 — the draw. Suppose the hash yields r = 617.
Step 6 — the exchange.
s = floor(117 × 1000 / 2000) = 58
A_eff = floor(97 × 1058 / 1000) = 102, D_eff = floor(74 × 942 / 1000) = 69
shareA = floor(102000 / 171) = 596, shareD = 404
Ld = floor(60 × 596 × 100 / 100000) = 35
La = floor(60 × 404 × 100 / 130000) = 18
Step 7 — break.
Defender shock 35 + 8×1 + 6 = 49 ≥ 29 → the 3rd Rifles break.
Attacker shock 18 + 10 = 28 < 38 → the attack holds (and is suppressed anyway).
Step 8 — outcome. 3rd Rifles: 78 → 43 strength, disorder 15 → 42, retreat to the lowest-scoring adjacent tile. 2nd Armored: 92 → 74 strength, disorder +15, advances into Belvoir Wood if it has a movement point left. 1st Field Battery: disorder +10, spent for the turn.
What the player was shown before committing (the median column is the one they read first):
┌─ ASSAULT ─ 2nd Armored Battalion → Belvoir Wood (Forest) ──────────────────┐
│ │
│ YOUR FORCE attack power 97 │
│ 2nd Armored Assault 92% Veteran 53 base 62 │
│ +18% veteran +5% supply −30% armour in forest │
│ 1st Field Bty support 100% Trained 18 base 48 × 40% │
│ +8% trained +5% supply −15% guns in forest │
│ force +25% assault +12% combined arms (armour + guns) │
│ │
│ THEIR FORCE defence power 74 │
│ 3rd Rifles Entrenched 78% Green 39 base 44 │
│ +15% foot in forest │
│ position +40% forest cover +30% entrenched +20% dug in ×2 │
│ │
│ ODDS 1.31 : 1 RELIABILITY ±25% (broken ground, green) │
│ │
│ THEY LOSE 26 ── 34 ── 41 → 78% drops to 37–52% │
│ YOU LOSE 14 ── 19 ── 25 → 92% drops to 67–78% │
│ │
│ ▸ THEY BREAK — certain. Resolve 29 vs shock 40–55. │
│ They will fall back to Ford Seven. They are not cut off. │
│ ▸ Your attack falters: never. │
│ ▸ You take Belvoir Wood if you have a movement point left. You do. │
│ │
│ [ Attack ] [ Attack without 1st Field Battery ] [ Cancel ] │
└────────────────────────────────────────────────────────────────────────────┘
7.1 The same attack against a fresh defender — and why bombardment exists
Change one thing: the 3rd Rifles are at strength 100 with disorder 0 instead of 78 and 15.
| Worn defender | Fresh defender | |
|---|---|---|
| Defence power | 74 | 95 |
| Odds | 1.31 : 1 | 1.02 : 1 |
| They lose (min/median/max) | 26 / 34 / 41 | 22 / 30 / 37 |
| You lose | 14 / 19 / 25 | 17 / 22 / 28 |
| Their resolve | 29 | 55 |
| They break | 1000‰ (certain) | 0‰ (never) |
The identical attack is decisive against one and completely pointless against the other — you would trade 22 strength for 30 and gain no ground. The readout says so, in those words. This is the design working: what changed was not the odds (1.31 vs 1.02, a modest shift) but the resolve, and resolve is driven by condition, which is driven by what you did to them earlier.
So do it earlier. The Field Battery bombards at range 2 instead of supporting:
bombard: A = 47 D = 95 odds 0.49:1 they lose 4/7/10 you lose 0
defender: strength 100 → 93, disorder 0 → 26
Seven strength. Almost nothing. But 26 disorder, and disorder comes straight off resolve. Now the armor assaults alone:
assault: A = 66 D = 89 odds 0.74:1 they lose 18/25/33 you lose 20/26/32
their resolve 26 vs shock 32–47 → THEY BREAK, certain
At 0.74:1 — worse odds than the failed attack — the defender breaks with certainty, because the bombardment removed 29 points of resolve. Guns do not kill; guns unstick. That is a mechanically distinct job for a third role, expressed entirely through the two-track structure, and it is the answer to "combined arms should pay off" that does not reduce to "artillery is a bigger gun".
It is also honest about the cost: the armor loses a median 26 to do it, more than the 22 it would have lost failing. Winning is more expensive than losing. Good.
7.2 A siege, to show cities work differently
A works-3 city on urban terrain, entrenched Rifle Company, three Field Batteries in range and three units adjacent. City defenders may not retreat, so this ends in a body count.
turn 1 three batteries fire: strength 100 → 81, disorder → 72, recovery −45 → 27
turn 2 three batteries fire: strength 81 → 59, disorder → 108→100, recovery −45 → 55
turn 3 assault 1: A 75 : D 69 resolve 13 → breaks, loses 31 strength 59 → 28
assault 2: A 75 : D 31 resolve 5 → breaks, loses 28 strength 28 → 0 destroyed
Two turns of a three-battery gun line plus two armored assaults to take one entrenched company out of a level-3 fortification. That is the intended shape: cities are sieges, not skirmishes, they are where the game's big decisions land, and they are the one place where fight-to-the-death is the rule.
Note the equilibrium: one battery does +26 disorder per turn against a city's −45 recovery, so a single gun accomplishes nothing at all. Three batteries do about +75 against −45, netting +30. Artillery only works concentrated, which is a real operational decision rather than a per-unit habit.
8. The same attack across three terrains and two postures
A lone Veteran Armored Battalion at full strength, Assault posture, Full supply, attacking a Rifle Company at full strength, Trained, Adequate supply. No supporters, no flankers. Losses shown as min/median/max; break chances are exact, not estimates.
| Terrain | Defender posture | A | D | Odds | Swing | They lose | You lose | Their resolve | They break | You falter |
|---|---|---|---|---|---|---|---|---|---|---|
| Open | Guarded | 106 | 47 | 2.26:1 | ±14% | 37/41/45 | 11/14/17 | 45 | 858‰ | 0‰ |
| Open | Entrenched | 106 | 61 | 1.74:1 | ±14% | 33/38/41 | 13/16/20 | 60 | 0‰ | 0‰ |
| Forest | Guarded | 71 | 75 | 0.95:1 | ±23% | 22/29/36 | 18/23/29 | 45 | 0‰ | 0‰ |
| Forest | Entrenched | 71 | 91 | 0.78:1 | ±23% | 19/26/33 | 20/25/31 | 60 | 0‰ | 0‰ |
| Mountain | Guarded | 56 | 89 | 0.63:1 | ±22% | 17/23/29 | 23/28/33 | 45 | 0‰ | 0‰ |
| Mountain | Entrenched | 56 | 106 | 0.53:1 | ±22% | 15/20/27 | 25/30/34 | 60 | 0‰ | 297‰ |
Read the last two columns. A lone tank can break infantry caught in the open — 86% of the time. It cannot break infantry that has dug in, anywhere, including in the open. And in mountains against an entrenched company it has a 30% chance of simply being thrown back with nothing to show for 30 strength.
The same six cases with a Field Battery in support and two more units adjacent to the target:
| Terrain | Defender posture | A | D | Odds | They lose | You lose | They break |
|---|---|---|---|---|---|---|---|
| Open | Guarded | 145 | 47 | 3.09:1 | 42/45/48 | 9/11/13 | 1000‰ |
| Open | Entrenched | 145 | 61 | 2.38:1 | 38/42/45 | 11/13/16 | 1000‰ |
| Forest | Guarded | 102 | 75 | 1.36:1 | 27/34/41 | 14/19/25 | 1000‰ |
| Forest | Entrenched | 102 | 91 | 1.12:1 | 24/31/38 | 16/21/27 | 48‰ |
| Mountain | Guarded | 84 | 89 | 0.94:1 | 22/29/35 | 18/23/28 | 961‰ |
| Mountain | Entrenched | 84 | 106 | 0.79:1 | 20/26/33 | 20/25/30 | 0‰ |
Adding a battery and two flanking units changes 0‰ into 1000‰ in three of six cases without changing the odds much at all. Positioning is worth more than force. And there remains one cell that resists everything: entrenched infantry in mountains, 0‰ with the whole apparatus applied.
That cell is not a bug. It is the answer to "how do you take a mountain?", and the answer is: you don't assault it. You cut it off. Same defender, same attack, varying only supply:
| Defender supply | D | Odds | They lose | Their resolve | They break |
|---|---|---|---|---|---|
| Full | 86 | 0.95:1 | 23/29/34 | 60 | 0‰ |
| Adequate | 81 | 1.01:1 | 24/30/35 | 60 | 0‰ |
| Strained | 72 | 1.14:1 | 25/31/37 | 55 | 410‰ |
| Cut (and encircled) | 54 | 1.52:1 | 29/36/42 | 25 | 1000‰ |
Cutting the road does more than three extra battalions. That is the operational game this system is trying to create, and it emerges from the interaction of supply, resolve, and the break check rather than from any rule that mentions "siege".
9. The odds readout
The hard constraint is that a player must be able to predict combat well enough to plan. My claim is that this system predicts better than a coin-flip system, and the reason is structural.
9.1 The preview is exact, not sampled
There is exactly one random input, r ∈ [0, 1000), and every downstream quantity is monotone in
it: as r rises, A_eff rises, D_eff falls, shareA rises, Ld rises, La falls. Therefore:
- The loss bands are found by evaluating the exchange at
r = 0,500,999. Three calls. - The break probabilities are found by binary-searching for the threshold draw at which shock first reaches resolve. Ten calls each. The result is not an estimate — it is the exact count of favourable draws out of 1000, reported in per-mille.
I verified monotonicity by exhaustive sweep over 3,456 configurations spanning every terrain, grade, supply state, posture, three strength levels, two disorder levels, and two flanking counts: zero violations. I then verified the binary-search preview against a brute-force oracle over 10,080 (A, D, volatility, resolve) combinations: zero mismatches.
So the number shown to the player is not "roughly 60%". It is 597 per-mille, and it is right.
9.2 It is fast enough that everything can use it
Measured on this machine (Node 22):
| Operation | Cost | Implication |
|---|---|---|
| One exchange (committing an attack) | 0.016 µs | 500 combats per turn = 8 µs |
| Full preview, binary search | 0.45 µs | live hover preview is free |
| Full preview, brute force | 12.05 µs | not needed, but even this is fine |
At 0.45 µs an AI can evaluate 100,000 candidate attacks in 45 ms, which means the AI can use the same exact preview the player sees rather than a cheaper approximation. That is worth stating plainly: there is no separate "AI combat estimator" to keep in sync, and therefore no class of bug where the AI plays a different game from the player.
9.3 What the panel shows, and in what order
- The verdict first. "They break — certain." A player scanning fifty attack options needs the answer, not the arithmetic.
- Both loss bands as min ── median ── max, with the resulting strength percentages. Never a single number: a single expected value is the specific lie that makes players distrust odds displays.
- Odds and reliability as two separate figures. "1.31 : 1, ±25%." Reliability is a first-class number because in this system it is a first-class mechanic.
- Every contributing line item, with its source named — "−30% armour in forest", "+20% dug in ×2". Because support and flanking are automatic, the panel must enumerate what it counted, or the player will not learn the rules from playing.
- The consequence. Where they will retreat to; whether they are cut off; whether you will take the tile.
- The alternative. A one-click "attack without the battery" so the cost of spending a supporter is visible at the decision point.
9.4 Elsewhere in the UI
- One condition badge per unit composing strength and disorder, because six fields per unit is a spreadsheet and players will not read it. Proposal: a strength bar with a disorder overlay, plus a grade pip and a posture glyph.
- A hostile-tile overlay that colours each enemy-adjacent tile by "can I break this", computed with the same function. At 0.45 µs, painting this for every visible tile every frame is free.
- A post-combat line stating what actually happened against what was predicted: "predicted 26–41, actual 35". This builds trust in the readout faster than any tutorial.
10. Where the surprise lives
This is the part I most want to argue for. The dice are deliberately tame. The uncertainty in this game is supposed to come from not knowing what you are attacking, not from the resolution.
The same attack from §7, against a defender at three levels of observation:
| Observation level | Defence power | They break | You lose |
|---|---|---|---|
| L1 — spotted only (type and owner) | 45 – 132 | 0 – 1000‰ | 9 – 30 |
| L2 — adjacent (posture and field works visible; strength ±15) | 66 – 119 | 0 – 1000‰ | 12 – 29 |
| L3 — recon contact (exact strength, grade, dig-in, disorder) | 87 | 597‰ | 15 – 26 |
Two things fall out, and both are good.
First, your own losses are well-known even when the result is not. At L1 you already know this attack costs you somewhere between 9 and 30 — and the true range once you know everything is 15 to 26. The cost of attacking is roughly knowable from a distance; the outcome is not. That is exactly the right asymmetry for a game about committing forces.
Second, the break probability spans the full 0–1000‰ range at both L1 and L2, and it collapses to a single exact number only at L3. Scouting does not shave a few percent off your uncertainty — it is the difference between knowing and not knowing. Which unknown is doing the damage:
| Unknown grade (strength known = 85) | Defence | Resolve | They break |
|---|---|---|---|
| Green | 81 | 48 | 990‰ |
| Trained | 87 | 53 | 597‰ |
| Veteran | 93 | 60 | 0‰ |
| Elite | 102 | 68 | 0‰ |
So the readout under fog should not show a useless 0–1000‰ band. It should show the pivot:
They break — unless they are Veteran or better. (grade unobserved; a Scout Troop adjacent would tell you)
That is a sentence a player can act on. It names the risk, names the threshold, and names the fix. Building the pivot analysis is cheap — it is four more preview calls over the unobserved dimension — and it converts fog from a source of frustration into a source of decisions.
11. Why this beats the classic approach
Point by point against the failures in §1:
- Terrain has two independent effects, cover and chaos, from one table column each. Mountains are not merely harder, they are reliably harder; cities are not merely harder, they are a gamble. One extra number per terrain buys a second strategic dimension.
- Combined arms is structural, not decorative. The role × terrain table means no single role is correct everywhere, the role-diversity bonus (+120 for two roles, +260 for three) rewards mixing over massing, and bombardment has a job — removing resolve — that no amount of extra armor can do.
- The interesting decisions are before and around the attack, not during it. Posture, where the battery sits, which tiles are adjacent, whether to spend a supporter, whether to soften first. Once committed, resolution is one exchange and does not pretend to be interactive.
- Retreat plus no-retreat-when-cut-off produces fronts, pockets, and maneuver from two trivial rules. Encirclement is lethal and it is achieved by removing tiles, not by rolling well.
- The player sees an exact probability, verified against a brute-force oracle, computed by the same function the AI uses. Compare a coin-flip system, where the honest readout is a negative binomial the interface never shows.
- Anti-snowball is visible and in-world. Disorder means a unit that attacks repeatedly gets worse at attacking, and recovery is fast only in cities. That is the brake the direction document asks for in §6 — a logistics-flavoured one a player can see and plan around — rather than an invisible production tax.
- Bounded, tiny cost. 0.016 µs to resolve, 0.45 µs to preview exactly. No Monte Carlo anywhere, no loop whose length depends on the units involved, so worst-case turn time is flat.
12. Honest tradeoffs
Two persistent numbers per unit is real bookkeeping. Strength and disorder must be displayed, explained, recovered, and understood. At 300 units this is a lot of state for a player to hold. The mitigation (one composite badge) is a promise the UI has to keep, and if it does not, this design is worse than the thing it replaces.
Fewer clean kills makes fronts, but it also makes games longer. Most engagements end with both units alive, which is what creates the operational layer — and it directly fights the direction document's goal of dissolving endgame tedium. I am relying on encirclement-destruction and the configurable victory system to carry that load. If they do not, this design is a contributor to the problem, not a fix.
One draw per combat means outcomes within a battle are perfectly correlated. You cannot have "unlucky casualties but they broke anyway" — a good draw is good for everything. Some players will find one-draw resolution flat. I accept that because the exactness of the preview is worth more than the texture of independent sub-rolls, but it is a genuine loss.
Additive modifiers have a dead zone at the clamp. Once your defensive stack reaches +1800, the next bonus is worth nothing, and a player who discovers this feels cheated. I have set the caps so the worst reachable stack (+1600) stays inside, but that is a calibration that any new terrain or posture could break. The clamp needs a CI assertion, not just good intentions.
Encirclement destroying a whole stack is enormous swing. Four units can vanish in one clash. This is the mechanic I am least sure of; it is also the mechanic that makes the whole maneuver game worthwhile, so I would rather ship it and tune the stack rules than soften it pre-emptively.
Worn units cannot attack. The −floor((100 − strength)/2) resolve penalty combined with the
Assault posture's −10 means a unit below about 60% breaks off almost any marginal attack. This is
intentional culmination, but it is a strong restriction that players will feel as "my units stop
working" before they understand it as "rotate your line". Checked against a good attack, a 60%
Veteran still functions fine — so the real rule is worn units can still make good attacks, they just
cannot make bad ones — but that is subtle and the UI has to teach it.
Four postures is one more standing decision per unit per turn. At 300 units this becomes a chore without formation-level posture setting, which I do not own. If the operational-command subsystem does not ship posture-by-formation, cut Withdraw first and Entrenched second.
13. What could go wrong, and how playtesting would catch it
Each of these is a metric I would put on the automated playtest harness, with a target, so a regression is a failing number rather than a vibe.
| Risk | Metric | Target | If it fails |
|---|---|---|---|
| Encirclement swing dominates | share of total unit losses from no-retreat destruction | 20–35% | reduce stack-wipe to top-two units; add a break-out attempt |
| Entrenchment turtling — fronts freeze | median frontline displacement per turn on contested fronts; % of turns with no tile changing hands | < 40% static turns | reduce Entrenched defence, or raise bombardment disorder |
| Artillery dominance | ratio of bombard actions to assault actions | below 2:1 | lower INTENSITY_BOMBARD or raise the disorder-recovery floor |
| Artillery uselessness | same ratio | above 0.5:1 | raise bombard disorder coefficient from 3× |
| Disorder death spiral | distribution of unit-turns spent above 70 disorder | < 10% | raise base recovery from 20 |
| Combat too grindy | mean turns to resolve a contested tile | 2–4 | raise INTENSITY from 60 |
| Roster traps — a role is never worth building | win rate by force composition; build share by role | no role below 15% build share | retune role × terrain |
| Preview mistrust | % of resolutions falling inside the displayed band | 100%, by construction | a violation is a bug, and CI should assert it |
| Fog band too wide to act on | % of previews where the pivot analysis names a single unknown | > 70% | show pivots for the two dominant unknowns |
| Odds illegibility | player-facing: post-game survey on "did combat surprise you unfairly" | qualitative | the readout, not the math |
Two of these deserve emphasis. The preview-band check is a genuine invariant — the actual outcome must lie inside the displayed min–max, because both come from the same monotone function evaluated at the endpoints — so it belongs in CI as an assertion over the golden-master corpus, not in playtesting. And the monotonicity sweep (§9.1) must be re-run whenever a constant changes, because the entire exact-preview claim rests on it; if someone adds a non-monotone modifier the previews silently start lying.
14. What this subsystem needs from others
- Terrain / map: each terrain type needs a
cover(per-mille), achaos(per-mille), and arolerow. River edges and elevation differences, if they exist, want their own attacker-side modifiers (I would suggest −180 across a river, −100 uphill, but that is the terrain designer's call). - Supply: a per-unit supply level in {Cut, Strained, Adequate, Full}, readable at combat time and visible to the player in the preview. Combat does not compute supply; it only reads it.
- Movement / stacking: stack capacity per tile; whether an attack costs one movement point; whether a unit may attack more than once per turn (I assume movement bounds it and no hard cap is needed); zone-of-control rules, which determine whether a retreating unit may pass an enemy.
- Turn model: a stable per-unit global sequence number and per-turn attack ordinal, to key the PRNG positionally. This is a hard requirement, not a preference — a stream cursor breaks under overlapping activation windows.
- Fog: an observation level per enemy unit, ideally the L1/L2/L3 ladder in §10, with Recon units granting L3 on adjacency. Combat needs to know what the viewing player knows, so the preview can be computed from partial information and the pivot analysis can name the unobserved field.
- Operational command: posture assignable at formation level, or the posture system is a chore.
- Cities / production: cities and works grant no-retreat, a dig-in floor, resolve, and fast recovery. Depots give the +20 strength recovery tier.
15. Open questions I could not settle
- Does a screening force in Withdraw posture become an infinite delay? A Scout Troop trades ~16 strength for a tile and an enemy attack, and recovers 4 per turn in the field. With enough depth behind it, a screen may be able to trade space forever at a profit. Candidate fixes: cap disengagements per unit per turn at one, or make disorder from disengaging (+15) exceed field recovery (−20 only if it did not fight, and it did). I lean toward the second but have not modelled it.
- Should city defenders really never retreat? It makes cities meat grinders, which is thematic, but it also means an outnumbered defender is simply feeding units into a hole. A "sortie or surrender" option — a defender below some resolve may abandon the city and retreat, giving it up intact — might be better play and better drama. Untested.
- Two supporters or one? Two makes combined arms easy to reach, which is good for teaching and possibly too generous. I have no data.
- Casualty distribution within a stack. I have the top defender taking all losses and the rest contributing +100 defence each. That makes stacking cheap until the stack dies all at once, which is a violent cliff. Spreading losses is more sensible and much more bookkeeping. This is really the stacking designer's decision.
- Air and naval are not designed here. The same procedure resolves them, but they need their own answers on retreat (an aircraft cannot dig in or fall back to an adjacent tile), on interception, and on basing. I deliberately did not guess.
- Is one draw per clash enough drama? I believe the fog provides the surprise and the dice should not, but this is a taste question that only playtesting answers. The cheap experiment is a rules option that raises the volatility floor from 250 to 600 and asks which version people prefer.
toughmay be doing a job that belongs to the roster designer. I introduced it so a scout and an armored battalion do not both lose 30 points in an even fight. It might be better expressed as a unit size that also affects production cost and supply draw, in which case it is not mine.- Whether the −10 Assault resolve penalty is too harsh in combination with the strength penalty. The interaction produces the "worn units cannot make bad attacks" behaviour I like, but I arrived at it by observation rather than design, and I do not trust a rule I did not intend.