Last year Free League Publishing released Twilight 2000. They’re the makers of Coriolis, Vaesen, Mutant Year Zero, and other Year Zero Engine games, about which I’ve probably written a little too much.
T2K is based on a 1984 postWWIII survival RPG from GDW^{1}, with roughly the same setting but different mechanics superficially similar to their YZE games:
 Characters have Attributes and Skills, both rated in base dice: d6, d8, d10, or d12.
 On a skill test, players roll one die for Attribute and one for Skill, plus (if I’m not mistaken) possibly an extra d6 for weapon or other bonuses.
 Any die that shows a 6 or more counts as at least one success; any die that shows 10 or more counts as two successes.
As the die mechanics differ significantly from the usual YZE pool of d6s, I inevitably wrote a Lua program to understand the probabilities.
The probabilities for a single die are pretty straightforward:
die  0 (15)  1 (69)  2 (10+) 

d6  83.33%  16.67%  
d8  62.50%  37.50%  
d10  50.00%  40.00%  10.00% 
d12  41.67%  33.33%  25.00% 
I was slightly surprised that a single success is less common on a d12 than on a d10, but I guess I shouldn’t have been.
Slightly more surprising was the probabilities for rolling two dice. Below are all the unique combinations of dice.^{2}
dice  1+  2+  3+  4 

d6 d6  30.56%  2.78%  
d6 d8  47.92%  6.25%  
d6 d10  58.33%  16.67%  1.67%  
d6 d12  65.28%  30.56%  4.17%  
d8 d8  60.94%  14.06%  
d8 d10  68.75%  25.00%  3.75%  
d8 d12  73.96%  37.50%  9.38%  
d10 d10  75.00%  35.00%  9.00%  1.00% 
d10 d12  79.17%  45.83%  15.83%  2.50% 
d12 d12  82.64%  54.86%  22.92%  6.25% 
Surprisingly, rolling two d8s has a lower probability of any successes than rolling a d6 and a d12. Likewise rolling 2 successes is less probable and rolling 3 successes is literally impossible. We see a similar effect in the 2+ column, where two d10s are less likely to roll 2+ successes than a d8 and a d12.
So I guess the lesson is that you’re better off increasing all your Skills by two grades than trying to improve base Attributes, or something. Which almost makes sense, but the quirks of game mechanics intruding into the setting’s “natural laws” always irks me somehow. Yes, in a game we have to represent complex phenomena with numbers and simple rules. But it always seemed like the bad kind of fourth wall breaking when characters in an RPG discard their mere +1 sword for a +2 sword, or talk about how many HP and MP they have left, or (more to the point) improve their characters solely to minmax their dice rolls.
Program
Note that I’m using a “new” (to me) feature of
Lua 5.4,
relased in … 2020.
<const>
marks a local variable as a constant^{3};
attempting to assign a new value afterwards causes a compiler^{4} error.
It’s probably not necessary in such a short script,
but it’s better to get in the habit of using it.
#!/usr/bin/env lua
local DICE_SIDES <const> = { 6, 8, 10, 12 }
local NDICE <const> = #DICE_SIDES
[[
Chance of exactly `nsuccess` successes on a `nsides`sided die,
where the number of successes depends on the value on the die:
* (1 .. 5) => 0 successes
* (6 .. 9) => 1 success
* (10 .. 12) => 2 successes
]]
local function dprob_exact(nsides, nsuccess)
if nsuccess == 0 then
 1..5
return 5 / nsides
elseif nsuccess == 1 then
if nsides == 6 then
 6
return 1/6
elseif nsides == 8 then
 6, 7, 8
return 3/8
elseif nsides == 10 or nsides == 12 then
 6, 7, 8, 9
return 4/nsides
end
elseif nsuccess == 2 then
if nsides == 10 then
 10
return 1/10
elseif nsides == 12 then
 10, 11, 12
return 3/12
end
end
return 0
end
 Maximum successes on an `nsides`sided die
local function max_success(nsides)
if nsides >= 10 then
return 2
elseif nsides >= 6 then
return 1
else
return 0
end
end
 Chance of at least `nsuccess` successes on two `ns1` and `ns2`sided dice
local function success_atleast(ns1, ns2, nsuccess)
if nsuccess < 0 or nsuccess > 4 then
return 0
elseif nsuccess == 0 then
return 1
end
local result = 0
for i = nsuccess, (max_success(ns1) + max_success(ns2)) do
for j = 0, i do
result = result + dprob_exact(ns1, i  j) * dprob_exact(ns2, j)
end
end
return result
end
local function fmtpct(p)
if p == 0 then
return " "
end
return string.format("%5.2f%%", p * 100)
end
 Print success probabilities for each die
print "die  0 (15)  1 (69)  2 (10+)"
print ":::"
for i = 1, NDICE do
local d = DICE_SIDES[i]
print(string.format("d%2d  %s  %s  %s ",
d,
fmtpct(dprob_exact(d, 0)),
fmtpct(dprob_exact(d, 1)),
fmtpct(dprob_exact(d, 2))
))
end
 Print success probabilities for unique pairs of two dice
print ""
print "dice  1+  2+  3+  4"
print "::::"
for i = 1, NDICE do
for j = i, NDICE do
local low = DICE_SIDES[i]
local high = DICE_SIDES[j]
print(string.format("d%2d d%2d  %s  %s  %s  %s ",
low,
high,
fmtpct(success_atleast(high, low, 1)),
fmtpct(success_atleast(high, low, 2)),
fmtpct(success_atleast(high, low, 3)),
fmtpct(success_atleast(high, low, 4))
))
end
end

A now (mostly?) defunct company that once published RPGs and board games, notably Traveller. Wikipedia has more. ↩︎

I.e. rolling a d8 and a d6, for example, has the same probabilities no matter which is the Attribute and which the Skill. ↩︎

Probably for optimization purposes. Lua was intended as a script engine embedded in larger programs, and some 5.4 changes seem aimed at more efficient use of resources, e.g. garbage collection optimizations, finalizing external resources promptly (e.g. the
<close>
attribute), and moving string to number conversions out of the core interpreter. ↩︎ 
Lua scripts, like most modern scripting languages, are compiled into an internal bytecode before executing. There’s even a standalone program luac that creates binary “chunks” from script text. ↩︎