Skip to content

Combat formulas — damage, defense, energy, encounters

Combat formulas

This page is the canonical math layer for WalkRPG combat. Every equation here is frozen for Phase 11b-2 — the TypeScript combat simulator must reproduce these numbers to the last digit given the same seeded rolls. If 11b-2 has to invent a formula, this page failed.

1. Scope

This page covers single-Walker encounter resolution: the player walks into a quest-bound or region-bound combat trigger, the encounter spins up, both sides exchange actions on a fixed turn order until one side reaches hp = 0 or the Walker retreats, and the outcome resolves back into quest state.

Out of scope, by canon:

  • Multiplayer combat, PvP, raid systems — frozen indefinitely per D-007 (closed-beta vertical slice) / D-008 (full social paused) / D-009 (phasing leaves social out of the 11 series).
  • Party combat, summon mechanics, follower AI — no canon allows them.
  • Real-time combat — combat is turn-based by the encounter shape below.

This page anchors in Pillar 1 (Steps = Energy): combat consumes energy, and energy is replenished only by walking. Combat is therefore a step-paid system, never free.

2. The combat surface

Combat in WalkRPG is a step-paid encounter triggered by a quest beat or a region event. The Walker spends energy to act; the encounter resolves deterministically given the inputs and a seeded random stream. There is no real-time pressure — the Walker decides each action in their own time, and the system never advances the turn without the Walker’s input.

The intent is to keep combat thin enough to live inside a walking-RPG (the player is not staring at a screen for 20 minutes mid-walk) and rich enough to make passive-tree allocation meaningful (every node on cluster.first-steps and beyond has a downstream effect on at least one combat stat).

The encounter loop, at a glance:

  1. Encounter trigger fires (quest beat, region event).
  2. Both sides’ stats snapshot from their respective stat tables.
  3. A seed is drawn from the encounter trigger (deterministic; see §7).
  4. Turns alternate (Walker first) until exit condition: hp = 0 on either side, or Walker chooses to retreat, or Walker energy = 0.
  5. Outcome resolves to quest state — quest progress on win, retreat or defeat on loss.

3. Stat dictionary (canonical)

The combat simulator reads exactly these stat names. The convention is snake_case, matching the names already in use in data/src/content/nodes/*.ts (e.g. step_efficiency, energy_max, cartography_yield). New stats are added here, not invented at the call site.

The table below catalogues two strata of canonical stats: combat-resident stats (rows 1-12) that the simulator reads during a turn, and tree-resident-only stats (rows 13-19) that exist on the tree to shape pre-encounter / out-of-combat state and are never read inside the combat loop. The Notes column distinguishes them; the opening invariant — “the combat simulator reads exactly these stat names” — applies to the combat-resident rows only. Both strata share the same ModifierSchema plumbing and the same composition rule (below).

StatTypeDefault (level 1)RangeNotes
hpint500..hp_maxCurrent health. Combat-state only; persists across encounter turns.
hp_maxint501..∞Maximum health. Read from class baseHp + modifiers.
energyint(per class baseEnergy)0..energy_maxCurrent energy. Universal resource per pillar 1. Persists across encounters and walks.
energy_maxint(per class baseEnergy)1..∞Maximum energy. Read from class baseEnergy + modifiers.
damagefloat100..∞Raw outgoing damage per action. Float to allow multiplicative modifiers without rounding drift.
defensefloat40..∞Raw incoming damage reduction. Float, same reason.
accuracypercent0.800.0..1.0Probability factor; combined with defender evasion to produce hit chance (§7).
evasionpercent0.100.0..1.0Probability factor; subtracts from attacker accuracy.
crit_chancepercent0.050.0..1.0Probability of a critical strike per landed hit.
crit_multfloat1.51.0..∞Damage multiplier on a critical strike.
energy_cost_per_actionint10..∞Energy spent by the Walker per action. Enemies do not consume Walker energy.
cold_resistpercent0.00.0..1.0Fractional reduction of incoming damage tagged cold (e.g. frost-thing attacks). Subtracts after defense; applied via (1 - cold_resist) on the tagged-damage portion only. Introduced phase 12b-2 (item.frost-knight-plate).
cartography_yieldpercent0.00.0..∞Tree-resident only. Additive bonus on map / quest cartography output. Consumed by the cartography subsystem as yield = base × (1 + cartography_yield). First granting nodes: node.inked-boots, node.field-notebook, node.west-crossbeam; notable node.cartographers-eye (+0.25).
guild_rep_gainpercent0.00.0..∞Tree-resident only. Additive multiplier on faction reputation deltas (§12 / D-014 §1). Consumer applies delta_final = delta_base × (1 + guild_rep_gain). First granting nodes: node.iron-heel, node.hand-of-habit; notable node.steady-hands (+0.15).
map_drift_resistpercent0.00.0..1.0Tree-resident only. D-012 cosmology-coupled — fractional resistance to the self-rewriting map’s drift effect on the Walker’s recorded path. First granting node: node.compass-habit (+0.10). Consumer (drift simulator) is deferred to phase 13+ region-rollout; the stat is reserved canonical now.
node_discovery_radiuspercent0.00.0..∞Tree-resident only. Additive bonus on the exploration radius for surfacing tree nodes / region waypoints. Stored as a fraction (e.g. 0.05 = +5%); consumer multiplies into its base radius. First granting node: node.surveyors-squint; notable node.cartographers-eye (+0.10).
quest_xp_gainpercent0.00.0..∞Tree-resident only. Additive bonus on quest-completion XP. Consumer applies xp_final = xp_base × (1 + quest_xp_gain). First granting nodes: node.margin-note, node.east-crossbeam; notable node.steady-hands (+0.25).
recovery_ratepercent0.00.0..∞Tree-resident only. Additive bonus on out-of-combat HP / energy recovery between encounters. Does not refund energy mid-encounter (§6 invariant). First granting node: node.steady-grip (+0.05). Per-tick consumer spec deferred to phase 11e-1 (UI surfacing).
step_efficiencypercent1.00.0..∞Tree-resident only. Direct multiplier on the step→energy conversion (D-007 §2 baseline: 1 step = 1 energy at step_efficiency = 1.0). Streak bonus (+20% at 7d, +50% at 30d) composes on top. D-013 explicitly preserves this name (decisions.md §3 of D-013 follow-ups). First granting node: node.even-stride (+0.05); notable node.guild-pace (+0.30).

The encounter does not introduce new stats beyond this table. The remaining out-of-combat names still in informal use on the tree — walk_streak_bonus, energy_regen_idle, crafting — are intentionally left in prose pending promotion as their consumer subsystems firm up. crafting is the harvest/recipe success scalar; formal spec in wiki/src/content/docs/crafting/leak-harvest.md §4. Resist-type stats follow the *_resist prefix-noun convention (cold_resist, map_drift_resist, future fire_resist, etc.).

Modifier composition rule (already canonical via ModifierSchema): for any stat, the final value is ((base + Σ add) × Π (1 + mul)) , with set operations applied last and overriding the chain. The combat simulator never re-implements composition; it reads the resolved value.

4. Damage formula

Per landed hit:

raw = max(1, attacker.damage − defender.defense)
variance = 0.85 + 0.30 × roll_v
crit_factor = (roll_c < attacker.crit_chance) ? attacker.crit_mult : 1.0
hp_delta = round(raw × variance × crit_factor)

Where:

  • roll_v ∈ [0, 1) is the variance roll (one per landed hit, drawn from the encounter seed stream).
  • roll_c ∈ [0, 1) is the crit roll (one per landed hit, separate draw).
  • round is half-up rounding to the nearest integer; the simulator applies Math.round(x) semantics.
  • The max(1, ...) floor guarantees a landed hit always deals at least 1 damage, preventing stall states where high-defense enemies cannot be damaged at all.

Worked example. Attacker damage = 10, defender defense = 4, crit_chance = 0.05, crit_mult = 1.5, roll_v = 0.5, roll_c = 0.8:

  • raw = max(1, 10 − 4) = 6
  • variance = 0.85 + 0.30 × 0.5 = 1.00
  • roll_c (0.8) >= crit_chance (0.05) → not a crit → crit_factor = 1.0
  • hp_delta = round(6 × 1.00 × 1.0) = 6

A critical hit example with the same inputs except roll_c = 0.02:

  • raw = 6, variance = 1.00, crit_factor = 1.5
  • hp_delta = round(6 × 1.00 × 1.5) = 9

5. Defense formula

Defense is linear subtraction with a floor of 1, as folded into the damage formula above:

mitigated = max(1, attacker.damage − defender.defense)

Chosen over percent-mitigation models (e.g. dmg × (1 − def / (def + K))) because: (a) the dictionary in §3 is small and stat values are small integers in the early game, so linear math is legible to the player; (b) the max(1, …) floor neutralises the only failure mode of linear subtraction (defense ≥ damage producing 0-damage stalls); (c) a future percent-mitigation layer can be added as a derived stat (e.g. damage_reduction_pct) without changing this formula — the layered model defers complexity to when it earns its place.

This decision is frozen for 11b-2. Any future percent-mitigation system attaches after the linear pass, never replacing it.

6. Energy cost per encounter

The Walker’s actions consume energy. Enemy actions do not.

  • Per-action cost: energy_cost_per_action, default 1, configurable per action type in a later phase.
  • Encounter entry cost: 0. Triggering an encounter does not by itself consume energy; the first action does.
  • Energy floor: when Walker energy = 0 at the start of their turn, the encounter ends in a forced retreat. The Walker keeps their current hp. The trigger that started the encounter remains; the quest beat does not advance.
  • Energy regen between encounters: none from combat. Energy returns only through walking (Pillar 1). The combat system never refunds energy.
  • Energy regen mid-encounter: none. The Walker cannot rest mid-encounter to top up.

The number of actions in an encounter is therefore upper-bounded by floor(walker.energy / energy_cost_per_action). This is the central balance lever: a Walker with 30 energy can act at most 30 times against a defender; a Walker with 5 energy is in a fast fight by necessity.

7. Hit, miss, and critical

Hit chance per Walker or enemy attack:

hit_chance = clamp(attacker.accuracy − defender.evasion, 0.05, 0.95)
landed = (roll_h < hit_chance)

Where:

  • roll_h ∈ [0, 1) is the hit roll (one per attack, drawn from the encounter seed stream).
  • clamp(x, 0.05, 0.95) enforces a 5%-floor and 95%-ceiling — no fight is ever a guaranteed hit-fest or a guaranteed miss-fest. Even a Walker with 0 accuracy lands 5% of the time; even a Walker with 0 evasion is missed 5% of the time.

A miss deals 0 damage but still consumes energy on the Walker’s side. A miss does not roll the variance or crit dice — both are skipped.

A landed hit then runs the damage formula in §4. Critical strikes are rolled only on landed hits.

Determinism note. The encounter takes a 64-bit unsigned integer seed as input. The simulator draws floats from a deterministic PRNG (xorshift or PCG — 11b-2 chooses, picks one, freezes it). Three draws per Walker turn (roll_h, roll_v, roll_c), three per enemy turn, in that order. Replaying the same seed against the same stat snapshots reproduces the encounter byte-for-byte. This is non-negotiable: anti-cheat (per D-007) reconciles combat outcomes server-side, and reconciliation requires determinism.

8. Encounter shape

The simplest model that is testable:

  • Turn order: Walker always acts first on turn 1. After that, strict alternation — Walker, enemy, Walker, enemy.
  • Actions per turn: exactly one attack action per side. No multi-attacks in this layer; multi-attack effects (e.g. a future keystone granting two attacks every other turn) attach as a modifier on top, not as a change to the base loop.
  • Action types (this layer): attack only. Defensive actions, items, abilities are layered on by later phases; their inclusion does not change the base equations above, only the per-turn modifier resolution.
  • Initiative ties, AoO, OoO: none. The loop is strict alternation.
  • Exit conditions (checked at start of each turn, in this order):
    1. Walker hp = 0 → defeat (see §9).
    2. Walker chooses retreat → retreat (Walker keeps current hp, quest beat does not advance).
    3. Walker energy = 0 → forced retreat (same outcome as voluntary).
    4. Enemy hp = 0 → victory (quest beat advances).

The exit-condition order matters for replay determinism: simultaneous death (both at hp=0) cannot occur because turns alternate, but the order above pins down corner cases (Walker drops to 0 hp on the enemy’s attack ⇒ defeat resolves before the Walker’s next turn even runs).

9. Defeat and retreat

Three exit states, three outcomes:

Exithp on exitenergy on exitQuest statePenalty
Victoryas-isas-isBeat advancesNone.
Voluntary retreatas-isas-isBeat does not advance; trigger remainsNone. The Walker may return and re-trigger the encounter at any time.
Forced retreat (energy = 0)as-is0Beat does not advance; trigger remainsNone mechanical; the Walker simply has to walk before re-attempting.
Defeat (hp = 0)1 (clamped — Walkers do not die in the lore sense)as-isBeat does not advance; trigger remainsThe Walker is “set back” — hp clamps to 1 at exit, and the quest log reflects the failed attempt. No XP loss, no point loss, no reputation loss.

Walkers do not permanently die in this layer. The lore frame (D-006 pillar 2: walkers are cartographers, not action heroes) does not permit perma-death as a combat outcome at any point in the 11-series. A higher-tier punitive system (penalised attempts, reputation hits on repeated defeats) is out of scope here — if it ever lands, it attaches to the quest layer, not to combat.

10. Worked example — Quest 002 first-encounter at the slow vent

The Walker is on the third-day descent toward the Mroza seal-house (Quest 002, beat 3). On the lower scree, a slow vent of noble frost has accreted into a frost-thing: a half-formed creature of the leak, the canonical first combat in the Frostlands. The encounter triggers on path-step.

Stat snapshot (Walker, post-Quest-001 with cluster.first-steps four-point opening):

StatValueSource
hp50class baseHp
hp_max50class baseHp
energy12post-walk, mid-route — 12 left of class baseEnergy 30
energy_max30class baseEnergy (assumed; classes-page TBD)
damage10level-1 base
defense4level-1 base
accuracy0.80base
evasion0.10base
crit_chance0.05base
crit_mult1.5base
energy_cost_per_action1base

Stat snapshot (frost-thing, slow vent):

StatValue
hp18
hp_max18
damage7
defense2
accuracy0.65
evasion0.05
crit_chance0.02
crit_mult1.5

Seed: 0x005C (an example; actual derivation is per the encounter trigger).

Roll stream (xorshift32 output for seed 0x005C; the simulator draws three floats per attack on a hit — roll_h, roll_v, roll_c — and one float on a miss, just roll_h):

TurnSideroll_hroll_vroll_c
1Walker0.00580.44740.0381
1Enemy0.40600.76470.1792
2Walker0.29960.52710.0143

Turn-by-turn:

Turn 1 — Walker. hit_chance = clamp(0.80 − 0.05, 0.05, 0.95) = 0.75. roll_h (0.0058) < 0.75 → hit. raw = max(1, 10 − 2) = 8. variance = 0.85 + 0.30 × 0.4474 = 0.9842. roll_c (0.0381) < 0.05crit. hp_delta = round(8 × 0.9842 × 1.5) = round(11.81) = 12. Frost-thing hp: 18 → 6. Walker energy: 12 → 11.

Turn 1 — Enemy. hit_chance = clamp(0.65 − 0.10, 0.05, 0.95) = 0.55. roll_h (0.4060) < 0.55 → hit. raw = max(1, 7 − 4) = 3. variance = 0.85 + 0.30 × 0.7647 = 1.0794. roll_c (0.1792) >= 0.02 → not a crit. hp_delta = round(3 × 1.0794 × 1.0) = round(3.238) = 3. Walker hp: 50 → 47.

Turn 2 — Walker. roll_h (0.2996) < 0.75 → hit. raw = 8. variance = 0.85 + 0.30 × 0.5271 = 1.0081. roll_c (0.0143) < 0.05crit. hp_delta = round(8 × 1.0081 × 1.5) = round(12.097) = 12. Frost-thing hp: 6 → −6 → clamped to 0. Walker energy: 11 → 10.

Exit check at top of turn 3: enemy hp = 0 → victory.

Outcome: Walker exits with hp = 47, energy = 10. Two crits land — a high-variance run for the seed; the determinism guarantee in §11 ensures the encounter replays identically every time. Quest beat 3 advances; the Walker continues down the scree to the seal-house. The frost-thing dissolves into a residue that, in a later phase (12c, leak harvesting), can be gathered. Combat takes two Walker turns, costs 2 energy.

Replay guarantee. Given seed 0x005C and the snapshot tables above, any future invocation of the simulator must reproduce the outcome (walker_hp=47, walker_energy=10, enemy_hp=0, turns=2) exactly. This is the test 11b-2 must pass — see data/src/sim/combat.test.ts band 2 (worked example) and band 3 (determinism).

11. What Phase 11b-2 implements

The combat simulator must take:

  1. A Walker stat snapshot (resolved values for every stat in §3).
  2. An enemy stat snapshot (same shape; enemies skip energy, energy_max, energy_cost_per_action).
  3. A 64-bit unsigned integer seed.
  4. A retreat policy (for now: “never retreat” is the only policy; player-driven retreat lands when the combat UI does, Phase 11e-1).

…and must produce:

  • A list of turn records {turn, side, action, roll_h, roll_v?, roll_c?, hit, crit, hp_delta, walker_hp_after, walker_energy_after, enemy_hp_after}.
  • A final outcome ∈ {"victory", "voluntary_retreat", "forced_retreat", "defeat"}.
  • A final state record {walker_hp, walker_energy, enemy_hp, turns}.

…using the equations above, the named constants (max(1, …) damage floor, [0.85, 1.15] variance band, [0.05, 0.95] hit-chance clamp, half-up rounding), and a PRNG that 11b-2 picks once and freezes. The worked example in §10 is the simulator’s first canonical regression test.

12. Open conventions for downstream phases

  • 11c (faction rep math): must not write to any combat stat in §3. Reputation is its own track. The rep tier system can read out-of-combat stats (e.g. guild_rep_gain as a multiplier on rep deltas), but the combat layer never sees a reputation value during a turn.
  • 11e-1 (combat UI): must surface, at minimum, Walker hp / hp_max, Walker energy / energy_max, enemy hp / hp_max, last hp_delta (both sides), and a crit indicator on the last action. The hit-chance number is optional surface — recommend showing it on tap, not always-on, to keep the UI legible.
  • 12 (crafting): consumables that affect combat (e.g. a poultice that adds to hp) attach as modifiers via the same ModifierSchema. The combat simulator does not need a special path for them.
  • Future ascendancy / class differentiation: the schema already supports class-specific secondaryResource (mana / stamina / fury / focus / spirit). The combat layer above ignores this field — phase 11 collapses to energy-only fights. When an ascendancy spends mana (or any other secondary resource), §6 expands to a second per-action cost line and §11 adds a corresponding field to the snapshot. The base equations in §4, §5, §7 do not change.

Status

Draft. The dictionary in §3, the damage formula in §4, and the encounter shape in §8 are mechanics-designer autonomous decisions (logged at ops/decisions/2026-05-19-combat-formulas-11b1.md). Phase 11b-2 freezes the implementation against this page; any discrepancy is a bug in 11b-2, not in this page.