OK here’s the updated numbers, which looks muuuuuuuuch better. I think the consequence is that we need to drop the max bonus a player can get right away from +8 down to +6 or +7, but I’m not 100% sure, and am going to have to think about this some more, I could potentially keep it as-is!
Updated code:
def calc_at_least_dice(bonus, adv, disadv):
# maps the resulting dice roll to the number of times that value is rolled
val_map = {}
total_to_roll = 2+abs(adv-disadv)
bonus_roll = 1 if adv >= disadv else -1
adv_func = max if adv >= disadv else min
exploded_values = [
[i for i in range(1, 7)]
for j in range(total_to_roll)
]
roll_values = [1 for i in range(total_to_roll)]
total_rolls = 0
expected_roll_values = 6 ** total_to_roll
while total_rolls < expected_roll_values:
dice1 = 0
dice2 = 0
accum = 0
seen = set()
for roll in roll_values:
if roll in seen:
accum += bonus_roll
if adv_func(roll, dice1) == roll:
dice2 = dice1
dice1 = roll
elif adv_func(roll, dice2) == roll:
dice2 = roll
seen.add(roll)
roll_total = accum + bonus + dice1 + dice2
val_map[roll_total] = val_map.get(roll_total, 0) + 1
total_rolls += 1
next_die_plus_index = 0
while next_die_plus_index < len(roll_values) and roll_values[next_die_plus_index] == 6:
roll_values[next_die_plus_index] = 1
next_die_plus_index += 1
if next_die_plus_index >= len(roll_values):
break
roll_values[next_die_plus_index] += 1
# print(val_map)
k2 = '' if adv == disadv else 'k2'
print(f'{total_to_roll}d6{k2}+{bonus}')
print("total\t% at least = roll")
percentage = 0.0
probabilities = {}
for roll in reversed(sorted(val_map.keys())):
num_rolled = val_map[roll]
percentage += num_rolled / total_rolls
probabilities[roll] = percentage
for roll in sorted(probabilities.keys()):
print(f'{roll}\t{round(100.0*probabilities[roll], 2)}')
The likelihood to hit the average difficulty (14) when given a single advantage increases from:
- 61% to 86% for +7 (25%)
- 41% to 72% for +6 (31%)
- 30% to 59% for +5 (29%)
- 16% to 38% for +4 (22%)
- 11% to 27% for +3 (16%)

