This post is also available in:
In the solo-focused dungeon-crawl TTRPG “Ker Nethalas”,
I wanted “a hit-rate list to use as a build reference,”
so I tried running a simulation in Python.
Simulation conditions
- Based on the attacker’s and defender’s Weapon Skill or Combat values, I ran 1,000,000 simulations each.
- The attacker’s +10 bonus is assumed to be included.
- In the table, the attacker is A and the defender is D, and the numbers show each side’s skill value.
Simulation results
All values are percentages. Truncated after the first decimal place.
| A30 | A40 | A50 | A60 | A70 | A80 | A90 | A100 | |
| D10 | 29.6 | 39.5 | 49.5 | 59.5 | 69.5 | 79.5 | 89.5 | 99.5 |
| D20 | 28.0 | 38.1 | 48.1 | 57.9 | 68.0 | 78.1 | 88.1 | 98.1 |
| D30 | 25.3 | 35.64 | 45.6 | 55.6 | 65.7 | 75.6 | 85.5 | 95.6 |
| D40 | 22.2 | 31.8 | 42.1 | 52.1 | 62.2 | 72.1 | 82.1 | 92.1 |
| D50 | 19.35 | 27.8 | 37.2 | 47.7 | 57.7 | 67.7 | 77.8 | 87.7 |
| D60 | 16.3 | 23.7 | 32.3 | 41.7 | 52.2 | 62.4 | 72.2 | 82.2 |
| D70 | 13.3 | 19.8 | 27.2 | 35.6 | 45.1 | 55.8 | 65.7 | 75.8 |
| D80 | 10.3 | 15.7 | 22.1 | 29.3 | 38.1 | 47.5 | 58.3 | 68.4 |
| D90 | 7.3 | 11.7 | 17.2 | 23.7 | 31.1 | 39.5 | 49.0 | 59.9 |
| D100 | 4.3 | 7.8 | 12.2 | 17.7 | 24.1 | 31.5 | 40.1 | 49.5 |
What you can tell from the simulation
Because individual success checks happen before the opposed check, hit rate drops noticeably—especially when the attacker’s skill is low.
Baseline hit-rate guideline (early game)
With a starting weapon skill of 60 plus the attack bonus, you’ll hit at roughly a 50–60% rate. If you raise your skill with a weapon that has the Simple trait or with Weapon Master passive skills, you get roughly +10% hit rate per +10 skill.
The tougher the enemy, the more value you get from stacking skill
Because skill increases are closer to adding to hit rate than multiplying it, the higher the enemy’s Combat (i.e., the lower your base hit rate), the bigger the relative benefit of stacking hit rate.
Against a Combat 40 enemy, raising weapon skill from 70 to 90 increases hit rate by 1.31×.
Against a Combat 70 enemy, raising weapon skill from 70 to 90 increases hit rate by 1.456×.
Baseline dodge-rate guideline (early game)
Even with Dodge 10 at the start, at level 1 you can avoid nearly half of incoming attacks. The benefit of raising it up to Dodge 40 is, at least from a damage-mitigation standpoint, not that large. (Dodge heavily affects escape success rate and trap avoidance, so those benefits are probably the bigger deal.)
Equipping a parry weapon has about the same expected damage reduction as wearing Armor across your whole body
The expected reduction rate from blocking a 1D6 attack with Armor 1 is:
So, to neutralize attacks from an enemy with attack skill 60 to a similar degree via dodging or parrying, you’d need to raise Dodge from 10 to Dodge 60. That’s tough to do with Dodge, but it’s achievable even on a freshly made character if you use a parry weapon.
If you have a parry weapon and run a Weapon Master + Tracher build, and pick up Hunter’s Mark at level 2, you can avoid over 60% of attacks even from a Combat 70 enemy. Converting that reduction rate into Armor, it’s:
which means it’s about the same as gearing your whole body in Armor 2.
How hard it is to secure hit rate
To maintain an 80%+ hit rate, even if the enemy’s Combat is low, you need an effective weapon skill of 90 including modifiers.
Also, even with an effective weapon skill of 100 including modifiers, against an enemy whose Combat is 80 due to Overseer, your hit rate drops below 70%.
Code used for the simulation (Python)
Attack = 70
Defense =100
Double = [11,22,33,44,55,66,77,88,99,100]
HitCount =0
for i in range(1,1000000):
AttackCritical = False
DefenseCritical = False
AttackFumble = False
DefenseFumble = False
AttackSuccess = False
DefenseSuccess = False
AttackWin =False
DefenseWin =False
Attackroll = random.randint(1,100)
if Attack >= Attackroll:
AttackSuccess = True
if AttackSuccess == True and Attackroll in Double:
AttackCritical =True
if AttackSuccess == False and Attackroll in Double:
AttackFumble = True
Defenseroll = random.randint(1,100)
if Defense >= Defenseroll:
DefenseSuccess =True
if DefenseSuccess == True and Defenseroll in Double:
DefenseCritical =True
if DefenseSuccess == False and Defenseroll in Double:
DefenseFumble = True
if AttackSuccess == True and DefenseSuccess == False:
AttackWin = True
if AttackSuccess == False and DefenseSuccess == True:
DefenseWin = True
if AttackCritical ==True and DefenseCritical == False:
AttackWin = True
DefenseWin = False
if DefenseCritical == True and AttackCritical == False:
AttackWin = False
DefenseWin = True
if AttackSuccess == True and DefenseSuccess == True:
if Attackroll > Defenseroll:
AttackWin = True
DefenseWin = False
elif Defenseroll > Attackroll:
AttackWin = False
Defensewin = True
elif Attackroll == Defenseroll:
if Attack > Defense:
AttackWin = True
DefenseWin = False
elif Defense > Attack:
AttackWin = False
DefenseWin = True
if DefenseFumble ==True and AttackFumble == False:
AttackWin = True
DefenseWin = False
if AttackFumble == True and DefenseFumble == False:
AttackWin = False
DefenseWin = True
if AttackWin == True:
HitCount +=1
HitRate =100 * HitCount /1000000
print(HitRate)
In closing
Ah… simulations are the best!!
It feels like my soul is being cleansed.
So basically, if you skip bathing but run simulations, you get the same effect as taking a bath.
Or so they say.
With that, happy gaming!
Thank you for reading to the end!
On X, I share article updates, impressions while playing, and my attempts at game development.
About 1.3x louder than the blog posts (according to our own metrics).
