In previous posts (1) (2) I’ve been figuring out probability tables for various RPGs by Swedish publisher Fria Ligan, a.k.a. Free League Publishing in English-speaking countries. Their Alien RPG, recently released, has some interesting wrinkles which made analysis a little more difficult, at least for me.
Nearly all1 their RPGs use the same basic resolution mechanic, dubbed the Year Zero System:
-
The player (and sometimes GM) assembles a pool of d6s. For most player actions, it’s the sum of a Base Attribute, a Skill, and modifiers for equipment and circumstances.
-
The player (or GM) rolls their dice. If at least one die shows a 6, the action is a success. Depending on the skill and the characer’s Talent, every 6 after the first enhances the action. Some rules call these “stunts”.
-
In some variations – Mutant Year Zero (the system namer), Forbidden Lands, and the Alien RPG – some dice in the pool, distinguished by color, have a negative effect when they roll a 1.
-
In opposed rolls, both a player and the GM (or another player) roll simultaneously; the one with more 6s wins.
-
A player may choose to Push a roll, either because had no successes or not enough to overcome the result of an opposed roll. The player rerolls all dice that did not roll 6s (or 1s where they have special meaning) but incurs some penalty, depending on the game. For example:
-
In Mutant Year Zero and Forbidden Lands, every 1 rolled on a Base Die, initially or during the Pushed Roll, does one point of damage to the Base Attribute used. Likewise, every 1 rolled on a Gear Die lowers the Gear Bonus of that gear by 1, until it’s repaired by a competent craftsman.
-
In Coriolis, the GM gains a Darkness Point, which they can spend later to mess up the PCs’ lives in specific ways.
-
In Vaesen (and I think Tales from The Loop and Things From The Flood) every Push imposes a Condition on the character, which imposes a penalty on subsequent actions. Too many Conditions and the character is “Broken”, and suffers a random physical or mental affliction.
-
In another previous article I went into a little more detail about the Alien RPG’s Stress and Panic rules. To review:
-
Pushing a roll adds a point of Stress, each of which grants / imposes a Stress die to the character’s subsequent skill checks.
-
A 6 on a Stress Die denotes a success (or extra stunt), just as on a Base Die.
-
If a player rolls a 1 on a Stress Die, their character Panics and must roll 1d6, add that to their current Stress Points, and consult a table.
-
If the total of the Panic Check is 10 or more, the character must take a Panic Action, which supercedes the character’s attempted action in favor of an involuntary freeze, flight, or fight action. The original action fails automatically.
This last point means that I couldn’t use the Binomial Theorem and a few Stupid Probability Tricks. The program I came up with, listed at the end, required a Trinomial Theorem and careful analysis of each possible outcome. I ended up calculating probabilities in two complementary ways, just to make sure I ended up with a single right answer, and even now I’m not quite convinced I got it right.
Anyway, here’s the table I came up with:
Base | No Stress | 1d | 2d | 3d | 4d | 5d | 6d | 7d | 8d | 9d |
---|---|---|---|---|---|---|---|---|---|---|
1d | 16.67% | 30.56% | 42.13% | 51.77% | 55.14% | 54.08% | 49.13% | 40.93% | 30.11% | 17.21% |
2d | 30.56% | 42.13% | 51.77% | 59.81% | 61.18% | 58.41% | 52.07% | 42.77% | 31.10% | 17.57% |
3d | 42.13% | 51.77% | 59.81% | 66.51% | 66.21% | 62.02% | 54.51% | 44.30% | 31.92% | 17.88% |
4d | 51.77% | 59.81% | 66.51% | 72.09% | 70.40% | 65.03% | 56.55% | 45.57% | 32.61% | 18.13% |
5d | 59.81% | 66.51% | 72.09% | 76.74% | 73.90% | 67.53% | 58.25% | 46.63% | 33.18% | 18.34% |
6d | 66.51% | 72.09% | 76.74% | 80.62% | 76.81% | 69.62% | 59.67% | 47.52% | 33.66% | 18.51% |
7d | 72.09% | 76.74% | 80.62% | 83.85% | 79.24% | 71.36% | 60.85% | 48.25% | 34.06% | 18.65% |
8d | 76.74% | 80.62% | 83.85% | 86.54% | 81.26% | 72.81% | 61.83% | 48.87% | 34.39% | 18.78% |
9d | 80.62% | 83.85% | 86.54% | 88.78% | 82.94% | 74.02% | 62.65% | 49.38% | 34.67% | 18.88% |
10d | 83.85% | 86.54% | 88.78% | 90.65% | 84.35% | 75.03% | 63.33% | 49.81% | 34.90% | 18.96% |
panic | 16.67% | 30.56% | 42.13% | 51.77% | 59.81% | 66.51% | 72.09% | 76.74% | 80.62% | |
p-action | 0.00% | 0.00% | 0.00% | 8.63% | 19.94% | 33.26% | 48.06% | 63.95% | 80.62% |
Each row save the last two represents the number of Base Dice the player rolls. Each column represents the number of Stress Dice in the total dice pool.
The numbers confirm one’s intuitive notion of what happens as Stress builds:
-
At low levels (1-3) Stress makes the character sharper. Effects of Panic, if any, are relatively mild: a case of the shakes which impairs subsequent rolls, maybe dropping an object. But the extra dice make the character more effective.
-
At higher levels (4-8) the risk of Panic steadily rises, as shown in the row marked “panic”. The chance of a Panic Action rises sharply, as shown in the row marked “p-action”2, and the effects get much more severe. Stress makes the character a little sharper, although less than before, but far more likely to have a full-blown freak out. Total effectiveness dips.
-
When Stress hits 9 and above, the character is a basket case. Every Panic causes a Panic Action. Sure, they’re rolling 9 extra dice for every action, but they also have a roughly 4 in 5 chance of completely losing it: freezing, running away, running amok, or full catatonia.
Building a table that combines the total success probability of thie initial attempt and the push was a little too complicated. If P(b, s) is the probability of success on one roll, with b Base Dice and s Stress Dice, then the total probility, unless I’m mistaken, would be something like:
P(b, s) + (1 - P(b, s)) * P(b, s+1)
Fit that into a two-dimensional table. (Plus, some Talents in Alien allow characters to push twice in very specific circumstances, accruing another Stress Point each time.)
Program Listing
Hold onto your hats. This code is less commented and more complicated.
#!/usr/bin/env lua
-- Chances of success and failure on a single die
local SUCCESS = 1/6
local BASE_NO_RESULT = 5/6
local STRESS_NO_RESULT = 4/6
local PANIC = 1/6
local NO_PANIC = 5/6
local function panic_action(stress)
if stress <= 3 then
return 0
end
if stress >= 9 then
return 1
end
return (stress - 3) / 6
end
local function base_success(base)
return 1 - (BASE_NO_RESULT ^ base)
end
local function stress_no_panic(stress)
return NO_PANIC^(stress)
end
local function stress_panic(stress)
return 1 - NO_PANIC^(stress)
end
local function factorial(n)
if n <= 1 then
return 1
end
return n * factorial(n - 1)
end
local function exact_stress_result(ndice, nsuccess, npanic)
if (nsuccess + npanic) > ndice then
return 0
end
local nremain = ndice - nsuccess - npanic
return
(factorial(ndice) /
(factorial(npanic) * factorial(nsuccess) * factorial(nremain)))
* SUCCESS^nsuccess
* PANIC^npanic
* STRESS_NO_RESULT^nremain
end
local function stress_success_no_panic(ndice)
-- TODO: simpler way to get same result?
local result = 0
for nsuccess = 1, ndice do
result = result + exact_stress_result(ndice, nsuccess, 0)
end
return result
end
local function stress_success_with_panic(ndice)
-- TODO: simpler way to get same result?
local result = 0
for nsuccess = 1, ndice do
for npanic = 1, (ndice - nsuccess) do
result = result + exact_stress_result(ndice, nsuccess, npanic)
end
end
return result
end
local function no_success(ndice)
return BASE_NO_RESULT ^ ndice
end
local function success_1(base, stress)
local pact = panic_action(stress)
if pact == 0 then
-- No chance of Panic Actions, so strictly based on number of dice
return base_success(base + stress)
end
--[[
The entire roll succeeds in the following conditions:
- Base Dice Succeed, Stress Dice have no Panics
- Base Dice Succeed, Stress Dice have Panic but no Panic Action
- Base Dice Fail, Stress Dice Succeed with no Panics
- Base Dice Fail, Stress Dice Succeed with a Panic but no Panic Action
]]
local bsuccess = base_success(base)
local bfail = 1 - bsuccess
local nopact = 1 - pact
return
bsuccess * stress_no_panic(stress)
+ bsuccess * stress_panic(stress) * nopact
+ bfail * stress_success_no_panic(stress)
+ bfail * stress_success_with_panic(stress) * nopact
end
local function success_2(base, stress)
local pact = panic_action(stress)
if pact == 0 then
-- No chance of Panic Actions, so strictly based on number of dice
return 1 - no_success(base + stress)
end
--[[
The entire roll *fails* in the following conditions
- no succes on Base Dice or Stress Dice, with or without panic
- Base Dice Fail, Stress Dice Succeed but have Panic and Panic Action
- Base Dice Succeed, Stress Dice have Panic and Panic Action
]]
local bsuccess = base_success(base)
local bfail = 1 - bsuccess
local fail = no_success(base + stress)
+ bfail * stress_success_with_panic(stress) * pact
+ bsuccess * stress_panic(stress) * pact
return 1 - fail
end
local function print_table(maxbase, maxstress)
local mb, ms = (maxbase or 10), (maxstress or 3)
local rowbuf
-- print header
rowbuf = { "Base", "No Stress" }
if ms > 0 then
table.insert(rowbuf, " 1d")
end
for n = 2, ms do
table.insert(rowbuf, string.format("%5dd", n))
end
print(table.concat(rowbuf, ' | '))
rowbuf = { ":---:" }
for n = 0, ms do
table.insert(rowbuf, "-------:")
end
print(table.concat(rowbuf, '|'))
-- print each row
for b = 1, mb do
rowbuf = { string.format(" %2dd ", b) }
for s = 0, ms do
local p1, p2 = success_1(b, s), success_2(b, s)
local cellstr
if (p1 - p2) < 0.0001 then
cellstr = string.format(" %5.2f%% ", p1 * 100)
else
cellstr = string.format("%5.2f-%5.2f%%", p1 * 100, p2 * 100)
end
table.insert(rowbuf, cellstr)
end
print(table.concat(rowbuf, '|'))
end
rowbuf = { "**panic** ", " " }
for s = 1, ms do
local p = stress_panic(s)
table.insert(rowbuf, string.format(" %5.2f%% ", p * 100))
end
print(table.concat(rowbuf, '|'))
rowbuf = { "*p-action* ", " " }
for s = 1, ms do
local p = stress_panic(s) * panic_action(s)
table.insert(rowbuf, string.format(" %5.2f%% ", p * 100))
end
print(table.concat(rowbuf, '|'))
end
print_table(10, 9)