Skip to content

Checking the Angry GM’s math on the Tension Pool

In the Angry GM’s publication of the PDF version of his Tension Pool mechanic, he includes some probabilities. Can dyce check his work? You bet!

Let’s reproduce his tables (with slightly different names to provide context).

d6s in pool Angry’s probability of at least one 1 showing
1 16.7%
2 30.6%
3 42.1%
4 51.8%
5 59.8%
6 66.5%

How do we do compute these results using dyce? Taking advantage of our counting trick makes this pretty simple:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>>> from dyce import H
>>> one_in_d6 = H(6).eq(1)
>>> one_in_d6
H({False: 5, True: 1})
>>> for n in range(1, 7):  # (1)!
...     ones_appearing_in_nd6 = n @ one_in_d6  # (2)!
...     at_least_one_one_in_nd6 = ones_appearing_in_nd6.ge(1)
...     print(f"{n}: {at_least_one_one_in_nd6[True] / at_least_one_one_in_nd6.total:6.2%}")
1: 16.67%
2: 30.56%
3: 42.13%
4: 51.77%
5: 59.81%
6: 66.51%
  1. Iterates n over the range \([{ {1} .. {6} }]\).
  2. Our counting trick.

So far so good. Let’s keep going.

1d8 + 1d12 Rarity or Severity
2-4 Very Rare or Extreme
5-6 Rare or Major
7-8 Uncommon or Moderate
9-13 Common or Minor
14-15 Uncommon or Moderate
16-17 Rare or Major
18-20 Very Rare or Extreme

We need to map semantic outcomes to numbers (and back again). How can we represent those in dyce? One way is IntEnums. IntEnums have a property that allows them to substitute directly for ints, which, with a little nudging, is very convenient.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
>>> from enum import IntEnum
>>> class Complication(IntEnum):
...     NONE = 0  # this will come in handy later
...     COMMON = 1
...     UNCOMMON = 2
...     RARE = 3
...     VERY_RARE = 4
...
...     def __repr__(self) -> str:
...         return self.name  # makes formatting easier on the eyes
>>> OUTCOME_TO_RARITY_MAP = {
...     2: Complication.VERY_RARE,
...     3: Complication.VERY_RARE,
...     4: Complication.VERY_RARE,
...     5: Complication.RARE,
...     6: Complication.RARE,
...     7: Complication.UNCOMMON,
...     8: Complication.UNCOMMON,
...     9: Complication.COMMON,
...     10: Complication.COMMON,
...     11: Complication.COMMON,
...     12: Complication.COMMON,
...     13: Complication.COMMON,
...     14: Complication.UNCOMMON,
...     15: Complication.UNCOMMON,
...     16: Complication.RARE,
...     17: Complication.RARE,
...     18: Complication.VERY_RARE,
...     19: Complication.VERY_RARE,
...     20: Complication.VERY_RARE,
... }

Now let’s use our map to validate the probabilities of a particular outcome using that d8 and d12.

Rarity or impact Angry’s probability of a Complication arising
Common or Minor 41.7%
Uncommon or Moderate 27.1%
Rare or Major 18.8%
Very Rare or Extreme 12.5%
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
>>> from dyce import H, HResult, expand
>>> from pprint import pprint
>>> d8d12 = H(8) + H(12)
>>> def rarity(h_result: HResult[int]) -> Complication:
...     return OUTCOME_TO_RARITY_MAP[h_result.outcome]
>>> prob_of_complication: H[Complication] = expand(rarity, d8d12)
>>> for outcome, prob in prob_of_complication.probability_items():
...     print(f"{outcome.name:>9}: {float(prob):5.1%}")
   COMMON: 41.7%
 UNCOMMON: 27.1%
     RARE: 18.8%
VERY_RARE: 12.5%

Lookin’ good! Now let’s put everything together.

d6s in pool None Common Uncommon Rare Very Rare
1 83.3% 7.0% 4.5% 3.1% 2.1%
2 69.4% 12.7% 8.3% 5.7% 3.8%
3 57.9% 17.6% 11.4% 7.9% 5.3%
4 48.2% 21.6% 14.0% 9.7% 6.5%
5 40.2% 24.9% 16.2% 11.2% 7.5%
6 33.5% 27.7% 18.0% 12.5% 8.3%
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
>>> for n in range(1, 7):
...     ones_appearing_in_nd6 = n @ one_in_d6
...     at_least_one_one_in_nd6 = ones_appearing_in_nd6.ge(1)
...     prob_complication_in_nd6 = (at_least_one_one_in_nd6 * prob_of_complication).apply(
...         Complication
...     )
...     print(
...         f"{n}: {prob_complication_in_nd6.format_short(include_avg=False, precision=1)}"
...     )
1: {NONE: 83.3%, COMMON:  6.9%, UNCOMMON:  4.5%, RARE:  3.1%, VERY_RARE:  2.1%}
2: {NONE: 69.4%, COMMON: 12.7%, UNCOMMON:  8.3%, RARE:  5.7%, VERY_RARE:  3.8%}
3: {NONE: 57.9%, COMMON: 17.6%, UNCOMMON: 11.4%, RARE:  7.9%, VERY_RARE:  5.3%}
4: {NONE: 48.2%, COMMON: 21.6%, UNCOMMON: 14.0%, RARE:  9.7%, VERY_RARE:  6.5%}
5: {NONE: 40.2%, COMMON: 24.9%, UNCOMMON: 16.2%, RARE: 11.2%, VERY_RARE:  7.5%}
6: {NONE: 33.5%, COMMON: 27.7%, UNCOMMON: 18.0%, RARE: 12.5%, VERY_RARE:  8.3%}

Well butter my butt, and call me a biscuit! 🤠 That Angry guy sure knows his math!