Combat Round Resolution
One melee swing end to end: swing budget, one attack roll against every affordable defense, three-channel damage
engaged with a target
Combat is driven by the world tick, not by a command. Every 4 second round, handleCombatRound runs once per engaged pair.
how many swings for this weapon?
1 to 4, after stamina and carried weight
calcSwingCount: dexterity, weapon speed and skill set the base, then a stamina multiplier and an over-capacity weight penalty of up to 50 percent scale it down. Hard cap of 4 per weapon.
resolve this swing at attack score S
calcAttackScore builds S from dexterity, combat skill, a dual-wield penalty, stamina, prone position, darkness and flight. It never reads a mitigation field. Armor changes how much a landed hit hurts, never whether it lands.
roll the swing ONCE, spread = StdDevFor(S)
runBestOfAllDefense opens with atkStdDev := dice.StdDevFor(atkScore) and attackRoll := dice.Roll(atkScore, atkStdDev). This is the only attack roll in the whole exchange, no matter how many defenses follow.
one value A, and the spread that made it
Both halves are kept. A is the number every defense must beat, and the attacker's spread becomes the spread every defense rolls with, so the two sides of the contest share one shape of randomness.
which defenses do you have?
dodge, parry, block
Dodge scores dexterity plus unarmed-combat skill. Parry scores dexterity plus weapon-combat skill plus the best equipped weapon's ParryRating. Block scores the mean of strength and dexterity plus weapon-combat skill plus the best shield's BlockRating. Those ratings are ItemSpec fields entirely separate from physical_mitigation.
skipped: cannot pay this one's stamina
Each defense has a stamina cost. If the defender's current stamina is below it the loop simply continues, so that defense is never rolled at all. Low stamina never lowers a defense's score. It removes the defense from the contest entirely, which costs the defender far more.
roll dodge vs A, attacker's spread
roll parry vs A, attacker's spread
Every surviving defense gets its own independent dice.Roll(defenseScore, atkStdDev). They compete with each other over the same attack value rather than each facing a fresh swing, which is what stops a defender with three defenses from getting three separate chances at three different attacks.
roll block vs A, attacker's spread
a margin each: defenseRoll - A
One number per defense. Positive means that defense beat the swing, negative means it lost and by how much. Position, darkness, rally, grapple and mutation modifiers have already been folded into each defense score before this point.
widest margin picks the defense, win or lose; only that defense pays stamina
The largest margin is kept even when every margin is negative, so the closest failure is the one that gets narrated. Stamina is deducted once, for the winning defense only, after all the rolls are done.
MinDefenseChance: save anyway
If the attack won the margin, resolveDefenseOutcome still gives the defender a flat MinDefenseChance roll, default 0.15. An outclassed defender is never a guaranteed target.
MinAttackHitChance: land anyway
The mirror image. If a defense won the margin, a flat MinAttackHitChance roll can still turn it into a hit. The two floors are symmetric, checked after the margin, and either can overturn the contest.
hit, miss, crit or fumble
resolveDefenseOutcome orders the result: double fumble first, then fumbles, then crit versus crit, then a one-sided crit, then the normal margin. Each floor sits inside one branch of that last step, so only ever one can fire.
stat x skill x weapon x channel scale x global
CalcRawDamage is one formula for all three channels: physical uses strength, magical uses willpower, conviction uses charisma. A configured per-channel scale and a global multiplier finish it.
subtract mitigation, capped
ApplyMitigation reduces raw damage by the defender's summed equipment percentage for that channel, clamped to the channel cap (default 75 percent). This is the only place armor is consulted in the entire round. A critical hit bypasses mitigation entirely: armor does not reduce crit damage at all.
roll variance around the mean
damage for this swing
dice.RollStat derives its spread from the mean using the global RollSpread knob, so the shape of every roll in the engine moves with one number.
turn the number into a phrase
GetDamageDescription converts damage to a share of the target's maximum health and returns light wounds, serious wounds, critical injuries and so on.
"causing serious wounds"
The raw integer never leaves the engine. Attacker, defender and every spectator in the room read the same descriptive band.
Swing budget
One attack roll
Every affordable defense, rolled separately
Two floors, either direction
Three-channel damage
Player-facing text
Attacker · player or mob · Sequence participant
Attacker
player or mob
Round handler · handleCombatRound · Sequence participant
Round handler
handleCombatRound
Swing budget · combat.calcSwingCount · Sequence participant
Swing budget
combat.calcSwingCount
Dice · dice.Roll · StdDevFor · Sequence participant
Dice
dice.Roll · StdDevFor
Defender · the target character · Sequence participant
Defender
the target character
Opposed roll · runBestOfAllDefense · Sequence participant
Opposed roll
runBestOfAllDefense
Damage · CalcRawDamage · Sequence participant
Damage
CalcRawDamage
Room output · AttackResult messages · Sequence participant
Room output
AttackResult messages
Legend
request
return
security
async trace
default message
Ready
Chapter 01 / 01
Guided chapter
Diagram guide
Explore this system
×
Inspecting compiled semantics
01
Find any node Search labels, responsibilities, kinds, and stable IDs.
/
02
Trace a route Ask how two semantic nodes connect in authored direction.
R
03
See the whole system Open Semantic Radar with a live viewport and stable nodes.
M
04
Compare semantic kinds Count roles, reveal their traffic, and compare direct authored links.
L
05
Play the guided story Walk the authored chapters and real relationships.
P
06
Enter Presentation Stage Give the live diagram the viewport without changing export.
F
E ExportT ThemeS Style0 Reset+ Zoom in- Zoom outEsc Close
Find a node
×
⌕
/
No matching nodes
Semantic passport
Authored reach
Upstream 0
Downstream 0
×
Copy link
Relations
Route probe
Choose a start node
Find start
Copy link
Clear
Pick two semantic nodes on the diagram
←
▶
Journey
→
Overview
Choose the source, then the destination. Direction matters.
Semantic lens
Compare system roles
×
Choose up to two semantic kinds. One reveals its real traffic; two compare only direct authored relationships.
Choose a kind to inspect its nodes and touching relationships.
Copy link
Clear
Semantic radar
Building overview
×
Click node Drag to pan
PATH
MAP
LENS
⌕
?
−
100%
+