OpenQuest: Toward A Ritual Magic System

Posted: 2023-06-26
Word Count: 4936

If anyone besides me got anything out of “OpenQuest: Other Magic” it’s that I need to develop systems for Ritual Magic and Making Magic Stuff (Alchemy/Artificing/Herbology) ASAP. (Why, you ask?)

Luckily both have analogues in Barbarians of Lemuria’s Sorcery and Alchemy systems and Everywhen’s analogous Sorcery and Inventing systems. Problem solved, right?

Well, no.

Bol/EW use a roll-over die system, so it’s easy to express difficulty as a modifier to the roll. Since it’s a 2d6 system, every +1 to the Target Number or -1 to the roll reduces probabilities enormously.

On the other hand, OpenQuest can only reduce probabilities by adjusting the player’s percentile skill, and the rules suggest that only ±20% or ±50% are significant enough to make a difference. (Presumably ±70% or more is tantamount to saying the player has next to no chance.)

So we have to consider other methods.

Essence Dice

The Idea

This mechanic I stole from King Arthur Pendragon 4th Edition, which introduced magician player characters. (They were gone in 5th edition.) Magicians cast spells by summoning enough “life force”, their own and ambient force found more readily in nature and magical power spots. If they had enough life force for the effect they wanted, the spell worked. If they didn’t, either a lesser version of the spell took effect or it fizzled.

Mechanically, magicians collected a number of D20s’ worth of power, then rolled them. What happened depended on whether the total hit the right target number. Too little and their spell fizzled. More than enough might mean making the spell that much stronger.

The Probabilities

Naturally to explore this idea I wrote a Lua program to compute the probabilities of rolling a D21, D6, D10, and, yes, D20s. The result is below.

Lvl TN 1D 2D 3D 4D 5D 6D 7D
D{0,1}
1 1 50.00% 75.00% 87.50% 93.75% 96.88% 98.44% 99.22%
2 2 25.00% 50.00% 68.75% 81.25% 89.06% 93.75%
3 3 12.50% 31.25% 50.00% 65.62% 77.34%
4 4 6.25% 18.75% 34.38% 50.00%
5 5 3.12% 10.94% 22.66%
6 6 1.56% 6.25%
7 7 0.78%
D6
1 4 50.00% 91.67% 99.54% 100.00% 100.00% 100.00% 100.00%
2 7 58.33% 90.74% 98.84% 99.92% 100.00% 100.00%
3 11 8.33% 50.00% 84.10% 96.76% 99.55% 99.96%
4 14 16.20% 55.63% 84.80% 96.41% 99.39%
5 18 0.46% 15.90% 50.00% 79.42% 93.88%
6 21 2.70% 22.15% 54.64% 80.83%
7 25 3.24% 20.58% 50.00%
8 28 0.27% 6.08% 25.72%
9 32 0.45% 6.12%
10 35 0.02% 1.21%
11 39 0.04%
12 42 0.00%
D10
1 6 50.00% 90.00% 99.00% 99.95% 100.00% 100.00% 100.00%
2 11 55.00% 88.00% 97.90% 99.75% 99.98% 100.00%
3 17 10.00% 50.00% 82.40% 95.66% 99.20% 99.89%
4 22 16.50% 53.35% 81.96% 94.85% 98.86%
5 28 1.00% 17.60% 50.00% 77.81% 92.48%
6 33 3.30% 22.37% 52.76% 78.11%
7 39 0.05% 4.34% 22.19% 50.00%
8 44 0.46% 6.91% 25.92%
9 50 0.00% 0.80% 7.52%
10 55 0.05% 1.65%
11 61 0.11%
12 66 0.00%
D20
1 11 50.00% 88.75% 98.50% 99.87% 99.99% 100.00% 100.00%
2 21 52.50% 85.75% 96.97% 99.52% 99.94% 99.99%
3 32 11.25% 50.00% 81.16% 94.76% 98.85% 99.79%
4 42 16.63% 51.67% 79.76% 93.48% 98.31%
5 53 1.50% 18.84% 50.00% 76.66% 91.39%
6 63 3.74% 22.47% 51.38% 76.05%
7 74 0.13% 5.24% 23.34% 50.00%
8 84 0.64% 7.50% 26.00%
9 95 0.01% 1.15% 8.61%
10 105 0.08% 2.01%
11 116 0.00% 0.21%
12 126 0.01%

I chose the target numbers per “level” for all but the D2 so that the roll had a ≥50% of succeeding when the number of dice equalled the level. With D2, the 50% mark lies when the number of successes needed is equal to half the dice rolled rounded up.

An advantage of the D2 is that once can use any dice: d6s, d10s, d12s, d4s (if you like caltrops), even pulling cards from a deck and regarding all the blacks as 1 and all the reds as 0.

The Objections

I can think of a few objections to this mechanic:

1. PC skill becomes irrelevant.
2. That’s a lot of dice to roll (or cards to draw) …
3. That’s a lot of levels …

PC Skill

While I can appreciate that a PC’s abilities have no obvious connection to the number of “life force” dice they roll, the following additional rules may restore the PC’s relevance.

• The GM secretly rolls a check on the appropriate Religion, Lore, or other ritual magic skill to determine if the ritualist remembered the ritual accurately. The ritualist gets a +20% on his skill if he works from a clear, complete written account of the ritual, or +50% if it’s from their own2 grimoire, while another’s messy grimoire could impose -20% or even -50%. Even with their own rituals they still could miss a detail or fail to understand the ritual’s nuances. Failure could mean the ritual doesn’t work as expected, and a Fumble means an unexpected guest …

• Ritual Magic depends on what we’ll call Channelling. The GM may choose Magic Casting as the Channelling skill under the One Magic system, Personal Magic under Personal Magic, Subtle Magic in Rurikania, or a new skill called Channelling equal to POW × 3%. It reflects the ritualist’s ability to channel their own life force. Remaining dice come from advanced planning and situational factors.

The number of Essence Dice the ritualist themselves can contribute is equal to `POW x Channelling / 400`, rounding to the nearest whole number.3 The player may want to write that number down, and update it when they increase POW or Chanelling. (A chart solving for Channelling for values of POW and Essence Dice is left as an exercise for the reader.)

Since the maximum human POW is 21, this means no more than five dice can dome from a human ritualist even if they raise both POW and skill to their maximums. Non-humans, demi-humans, and human subspecies may be better ritualists than baseline humans …

• Certain subtypes of Ritual Magic like Diabolism or Thaumaturgy may have additional skills reflecting their peculiar elaborations on the basic procedure. (I haven’t designed them yet.)

Totalling Dice

Another lingering question is whether players will want to throw handfuls of D6es, D10s, or (gulp) D20s whenever they cast a ritual spell, then total them all up. As my perverse admiration for Tunnels & Trolls demonstates, I have no problem, but others might.

The “D{0,1}” procedure seems ideal, since GMs can use whatever dice they have on hand or even playing cards. They need only count how many came out high, or even, or red (or was it black?) On the other hand, I specifically chose the target numbers for D6 and D10 (and D20) so that the “level” of a ritual equaled the number of dice needed for an approximate 50/50 chance, whereas the 50/50 chance for each level in D{0,1} is twice the level. I wanted as few “levels” as possible, but maybe D{0,1} would leave too few.

And on the third hand, I can’t see anyone but me using this system and I have literal hundreds of d6es and dozens of d10s. Plus I play over Discord a lot lately, so physical dice aren’t a problem.

Number of Levels

As I touched on earlier, I wanted a minimum of four levels corresponding to the four levels of Sorcery in Barbarians of Lemuria:

1. Cantrips, minor “special effects” with no ongoing story effect, e.g. cleaning the dirt off your robes, making obviously fake illusions.
2. Minor Magic, something a trained human could do with the right equipment, e.g. climb a wall, cause a brief distraction for the guards.
3. Major Magic, something beyond human ability, e.g. fly, create realistic illusions.
4. Catastrophic Magic that can change the entire campaign, e.g. cause a flood, permanently warp reality.

Because of the relationship between dice and levels, I thought I’d interpolate levels between the ones above.

However, the system I stole mine from – the ones that used D20s – used the following descriptors for its target numbers:

Descriptor D20 Target Equivalent “Levels”
Low 10 .. 30 1 … 3
Common 35 .. 70 4 … 7
High 90 .. 200 8 … 20?
Grandiose 200+ 20+

The fuzzy border between High and Grandiose is concerning, but assuming the writers actually tested the system and knew what they were doing, perhaps I need to keep about 26 levels or more, in five or six bands, like so:

Descriptor Levels D6 Targets D10 Targets
Low 1 .. 4 4 .. 14 6 .. 22
Common 5 .. 8 18 .. 28 28 .. 44
High 9 .. 12 32 .. 42 50 .. 66
Very High 13 .. 18 46 .. 56 70 .. 88
Grandiose 19 .. 22 60 .. 70 94 .. 110
Catastrophic 23 .. 26 74 .. 84 116 .. 132

If the number of dice goes past 5 I’ll simply add the average of remaining dice, e.g.

Number of Dice: 6 7 8 9 10 11 12 +1
5D6 + … 3 7 10 14 17 21 24 +3.5
5D10 + .. 5 11 16 22 27 33 38 +5.5

The Verdict

As the foregoing indicates I’m really pushing this idea.

1. Success of the ritual depends in part of the training of the player character and in part on the planning of the player.
2. The mechanic can handle subtle changes in difficulty that, for example, OpenQuest doesn’t and that other percentile systems struggle to without lots of math at the table. Here, the dice pool does the math; one just counts them up and compares them to a set of numbers.
3. Rolling lots of dice is fun.

Repeated Skill Rolls

Another idea I’ve seen involves making multiple skill rolls.

The Idea

d20 Incantations presents a system wherein non-spellcasters can cast ceremonial magic. (Sound familiar?) It has the following features:

1. Anyone can cast a spell written in an old book, if they can decipher the author’s prose.
2. The incantation’s description specifies how many skill checks the incantation requires, which skills, and in what order.
3. If someone performing an incantation fails one check, they can save their work by succeeding at a second check of the same skill.
4. If the second skill check fails, the incantation fails and the invoker usually suffers some sort of magical backlash.

Obviously making multiple skill checks would be annoying for ritual magic. The discussion below assumes a single series of checks on a single skill, probably some form of Religion or Lore. However, artificing might interleave checks of Lore (Artificing) and Craft to represent an artificer building magic into an artifact.

The Probabilities

Once again I wrote a Lua program to explore the idea. The table below crossreferences the base probability of a skill with the probability of casting it a number of times with one reroll in the event of failure.

% x1 x2 x3 x4 x5 x6 x7
5 9.75% 0.95% 0.09% 0.01% 0.00% 0.00% 0.00%
10 19.00% 3.61% 0.69% 0.13% 0.02% 0.00% 0.00%
15 27.75% 7.70% 2.14% 0.59% 0.16% 0.05% 0.01%
20 36.00% 12.96% 4.67% 1.68% 0.60% 0.22% 0.08%
25 43.75% 19.14% 8.37% 3.66% 1.60% 0.70% 0.31%
30 51.00% 26.01% 13.27% 6.77% 3.45% 1.76% 0.90%
35 57.75% 33.35% 19.26% 11.12% 6.42% 3.71% 2.14%
40 64.00% 40.96% 26.21% 16.78% 10.74% 6.87% 4.40%
45 69.75% 48.65% 33.93% 23.67% 16.51% 11.52% 8.03%
50 75.00% 56.25% 42.19% 31.64% 23.73% 17.80% 13.35%
55 79.75% 63.60% 50.72% 40.45% 32.26% 25.73% 20.52%
60 84.00% 70.56% 59.27% 49.79% 41.82% 35.13% 29.51%
65 87.75% 77.00% 67.57% 59.29% 52.03% 45.65% 40.06%
70 91.00% 82.81% 75.36% 68.57% 62.40% 56.79% 51.68%
75 93.75% 87.89% 82.40% 77.25% 72.42% 67.89% 63.65%
80 96.00% 92.16% 88.47% 84.93% 81.54% 78.28% 75.14%
85 97.75% 95.55% 93.40% 91.30% 89.24% 87.24% 85.27%
90 99.00% 98.01% 97.03% 96.06% 95.10% 94.15% 93.21%
95 99.75% 99.50% 99.25% 99.00% 98.76% 98.51% 98.26%
100 100.00% 100.00% 100.00% 100.00% 100.00% 100.00% 100.00%

The Verdict

As we see from the table above, the more skill rolls one adds, the less likely success becomes … until one hits a tipping point around 75%, where success percentages remain high despite the number of repetitions. By about 85%, the skill percentage remains as high or higher than the base percentage.

This implies that only a master should attempt anything with more than one or two skill rolls. To me this suggests a craftsman making a complicated object, and the Incantation description even likens Incantations to making a cake from a recipe.

These numbers raise the question of how one gets good at rituals if only a master can succeed. A craftsman can make easier works while learning from a master, or perhaps even perform part of the master’s work themselves. Depending how severe the magical backlash is, a novice might not survive to become a master.

A mechanic that assumes player characters have to be experts in order to succeed means that most players will not use it. (Except for the masters, the crazies, and the masochists.) There’s also something inherently masochistic about rolling the same skill three or four or seven or ten times to cast a spell, particularly on d100. On a d20 one can roll that many d20s simultaneously, then reroll the ones that miss; even one missed reroll means the procedure failed somewhere along the way. One could devise a procedure using multiple d10s but it might get messy. And if we’re rolling multiple d10s

Opposed Skill Rolls

Yet another mechanic could represent some force actively fighting against the spell.

The Idea

When someone attempts ritual magic, the spell fights back. This is represented by an opposed roll between the ritualist’s relevant ritual magic skill and a “skill” percentage attached to the spell. This could be a constant or variable depending on time of year, material components present or absent, place, time of day, etc.

The Probabilities

Yet again I wrote a Lua program to explore the idea. The column marked “Pl.” represents a player character’s skill, and the header row represent’s the resisting or opposing skill.

Pl. 0 10 20 30 40 50 60 70 80 90 100
0 50.0% 49.4% 47.1% 44.1% 40.2% 35.5% 30.0% 23.8% 16.6% 8.7% 0.0%
10 50.4% 50.0% 47.7% 44.6% 40.8% 36.1% 30.6% 24.3% 17.2% 9.3% 0.5%
20 52.7% 52.2% 50.0% 47.0% 43.1% 38.4% 32.9% 26.6% 19.5% 11.6% 2.8%
30 55.7% 55.3% 53.1% 50.1% 46.2% 41.5% 36.0% 29.7% 22.6% 14.6% 5.9%
40 59.6% 59.2% 57.0% 54.0% 50.2% 45.5% 40.0% 33.7% 26.5% 18.6% 9.8%
50 64.3% 63.8% 61.7% 58.7% 54.9% 50.3% 44.8% 38.4% 31.3% 23.3% 14.5%
60 69.8% 69.3% 67.2% 64.2% 60.4% 55.8% 50.3% 44.0% 36.8% 28.9% 20.1%
70 76.1% 75.7% 73.5% 70.5% 66.7% 62.1% 56.7% 50.4% 43.2% 35.2% 26.4%
80 83.2% 82.8% 80.6% 77.6% 73.8% 69.2% 63.8% 57.5% 50.4% 42.4% 33.6%
90 91.2% 90.7% 88.5% 85.6% 81.8% 77.1% 71.7% 65.5% 58.4% 50.5% 41.6%
100 100.0% 99.6% 97.4% 94.4% 90.6% 86.0% 80.5% 74.3% 67.2% 59.3% 50.5%

Note that the diagonal has a ≥50% chance of success. The OpenQuest rules let the player win if there’s an unresolvable tie.

The Verdict

Years ago I created a similar but messier program for Call of Cthulhu 7 when it replaced its venerable “resistance table” with opposed rolls. I also wrote a program for HeroQuest which had the same tiers of success and similar opposed roll rules. So I pretty much knew what to expect.

The tiebreaker when both parties lose surprised me a little; I’m not sure why the lower failed die would win, as that gives the edge to lower skill. Maybe the idea is that it’s further from that dreaded fumble number 00 (100). I was also surprised that, unlike my CoC7 table, the increase of success percentages with higher skill isn’t quite so sharp, although it appears nonlinear to me. (Haven’t tried to graph the curves.)

Still, the method itself implies I should assign resistances based on what skill level should have a 50/50 chance of successfully performing it. (“You must be this skilled to use this ritual.”) That there’s a simple halfway point, like in the first method, is a plus. But even if I use only multiples of 10%, that’s 10 levels of difficulty, which is about three to four more than I really wanted.

This table provides good information when assigning Persistence scores to summoned creatures and resistances in similar situations. As a primary mechanic for rituals, though, opposed rolls just leave me (clatter of two sets of dice) cold.

Conclusions

Really all three systems would make for an interesting Ritual Magic system.

1. Much like the gold standard Sorcery system from Barbarians of Lemuria the “Essence Dice” system encourages the player to creatively gather more power. Cast the spell at an intersection of ley lines? Wait for an auspicious day? Quest for a relic that yields Essence Dice? It’s more than just rolling a skill number and seeing what the dice say. It’s rolling a whole lot of dice and seeing what they say.

2. The Incantation-style system creates an incentive to practice with small rituals and study one’s Religion or Lore. Rituals are for priests, “loremasters”, and others with high enough skills to perform them reliably. Which is a shame; why build a system that only matters in the end game (if it ever comes)?

3. The table of opposed rolls in OpenQuest turned out less skewed than I assumed it would. Still, opposed rolls create a system of rituals marked by the skill level required to have a 50/50 chance of performing them correctly. Since skills have a cap at 100% rituals have an upper limit to their power.

Essence Dice have no upper limit, so a good roll could yield more power than the ritualist knows what to do with. Just saying.

Ultimately I’ll have to go with my biases and start with the first system: Essence Dice. The system encourages player ingenuity, has no upper limit, ahd can have unpredictable effects if they yield too much or too little power.

Appendix A: Why Ritual Magic?

Consider the following scenes:

• A bunch of kids find an old book bound in “leather” with a horrific face worked into the front cover. They decide to read it. Aloud. A demon appears.

• A man who left childhood far behind chants in black robes before an octagram. The octagram has been inlaid in the granite floor with blue tile and silver inlay; mystic symbols around and inside the octagram gltter strangely. Suddenly the silver shines with white light … as does the smaller triangle in which the summoner stands. A demon erupts from the octagram and bellows in rage. The summoner smiles grimly and proposes a bargain …

• A wizard stands atop a cliff, arms outstretched. Before him two armies clash. As he finishes his chant a mysterious mist sweeps through the battlefield.

• The magician Arnaud du Lac needs a healing spell for his wife, so he goes to see his good friend Bors Halfhand. “You’ll cast the spell, right?” Arnaud asks anxiously.

Arnaud pulls a huge tome from a dusty shelf. “My researches are in a delicate stage.”

“But –”

“You’ll cast the spell, my friend.” Bors opens the book to a page that’s surprisingly in plain Latin. “The instructions are in here. They’re all in here.”

All four of these vignettes revolve around ritual magic. Rituals are slower and harder to cast than the spells magicians normally cast, but they have a few advantages:

1. One need not have the spell memorized (or burned into one’s brain). Simply read the instructions, understand the assumptions behind the terminology and symbolism, and use the the words, actions, and material components.

2. Unlike scrolls, one can use the same pages again and again. The power rests in the ceremony and performer(s), not the parchment.

3. Rituals can command far more power than ordinary spells … if one chooses the time, location, and circumstances wisely.

4. Nevertheless, anyone who understands the ritual can perform the ritual. Successfully? That’s another matter. Some (like the demon who left his book lying around) are too easy to perform, others like are difficult for a reason, and many are easier once they’re shorn of ciphers, codes, and a mad wizard’s ramblings.

5. From a game perspective, ritual magic needs fewer checks and balances. It’s not something you use in the dungeon to slay orcs; it won’t immediately let you take over the city or rake in gold. It’s more like the magic system in Call of Cthulhu: it’s what player characters indulge in between adventures, and it’s how they can screw themselves over if they’re not careful.

So that’s why I’ve been trying to write a Ritual Magic or Ceremonial Magic system for years, and why I’m using OpenQuest to test out some ideas.

Appendix B: Essence Dice Probability Program

Astute readers will note that I cannibalized several functions from a previous program.

``````#!/usr/bin/env lua

local function target(nsides, level)
if level % 2 == 0 then
return (nsides + 1) * level // 2
else
return (nsides + 1) * (level - 1) // 2 + (nsides // 2) + 1
end
end

local CACHE = {}

--[[
-- Chance that a roll of `ndice`D`nsides` == `target`
]]--
local function p_roll_eq(ndice, nsides, target)
if ndice == 1 then
if target >= 1 and target <= nsides then
return (1/nsides)
end
elseif target >= ndice and target <= ndice * nsides then
local key = string.format("%02d\t%02d\t%02d", ndice, nsides, target)
local result = CACHE[key]
if result then
return result
end

-- sum(1, nsides, p(one die is i) * p(remaining dice == target - i))
local sum = 0.0
for i = 1, nsides do
sum = sum + p_roll_eq(1, nsides, i)
* p_roll_eq(ndice - 1, nsides, target - i)
end
CACHE[key] = sum
return sum
end
return 0.0
end

--[[
-- Chance that a roll of `ndice` D `nsides` >= `target`
]]--
local function p_roll_ge(ndice, nsides, target)
local sum = 0.0
for t = target, nsides * ndice do
sum = sum + p_roll_eq(ndice, nsides, t)
end
return sum
end

-- choose function
local function choose(n, k)
if n < 1 or k < 0 or k > n then
return 0
end

local result = 1
for i = 1, k do
result = result * (n + 1 - i) / i
end
return result
end

-- binomial probability
local function binomial(n, k, p)
return choose(n, k) * (p)^k * (1-p)^(n-k)
end

-- chance of rolling at least `k` successes on `n` dice
-- with probability `p`
local function success_at_least(n, k, p)
if k <= 0 then
return 1.0
end

if k == 1 then
return 1 - (1 - p)^n
end

local result = 0
for i = k, n do
result = result + binomial(n, i, p)
end
return result
end

local buf = { "", "Lvl", " TN " }
for n = 1, ndice do
table.insert(buf, string.format(" %6dD ", n))
end
table.insert(buf, "")
return table.concat(buf, "|")
end

local function format_bar(high)
local buf = { "", ":-:", ":--:" }
for n = 1, high do
table.insert(buf, "--------:")
end
table.insert(buf, "")
return table.concat(buf, "|")
end

local function format_row(level, target, results)
local buf = { "",
string.format("%2d ", level),
string.format("%3d ", target),
}
for n = 1, #results do
if results[n] == 0.0 then
table.insert(buf, string.rep(' ', 9))
else
table.insert(buf, string.format(" %6.2f%% ", results[n] * 100))
end
end
table.insert(buf, "")
return table.concat(buf, "|")
end

local function print_table(levels, maxdice)
print(format_bar(maxdice))

-- Dice with 2 sides
print("| ***D{0,1}***")
for n = 1, maxdice do
local t = n
local result = {}
for d = 1, maxdice do
result[d] = success_at_least(d, t, 0.5)
end
print(format_row(n, t, result))
end

-- Dice with > 2 sides
local DICE <const> = { 6, 10, 20 }

for i, s in ipairs(DICE) do
print(string.format("| ***D%d***", s))
for n = 1, levels do
local t = target(s, n)
local result = {}
for d = 1, maxdice do
result[d] = p_roll_ge(d, s, t)
end
print(format_row(n, t, result))
end
end
end

print_table(12, 7)

--[[
for k, v in pairs(CACHE) do
print(k, v)
end
]]--
``````

Appendix C: Repeated Skill Rolls Probability Program

Astute readers will note that I cannibalized several functions from a previous program.

``````#!/usr/bin/env lua

-- choose function
local function choose(n, k)
if n < 1 or k < 0 or k > n then
return 0
end

local result = 1
for i = 1, k do
result = result * (n + 1 - i) / i
end
return result
end

-- binomial probability
local function binomial(n, k, p)
return choose(n, k) * (p)^k * (1-p)^(n-k)
end

-- chance of rolling at least `k` successes on `n` dice
-- with probability `p`
local function success_at_least(n, k, p)
if k <= 0 then
return 1.0
end

if k == 1 then
return 1 - (1 - p)^n
end

local result = 0
for i = k, n do
result = result + binomial(n, i, p)
end
return result
end

-- chance of rolling at least `k` successes on `n` dice with reroll
-- with probability `p`
local function reroll_success_at_least(n, k, p)
local result = success_at_least(n, k, p)
-- only `j` successes from first roll
for j = 0, k-1 do
local first = binomial(n, j, p)
result = result + first * success_at_least(n-j, k-j, p)
end
return result
end

local buf = { "", " %  " }
for n = 1, nrolls do
table.insert(buf, string.format(" x%-6d ", n))
end
table.insert(buf, "")
return table.concat(buf, "|")
end

local function format_bar(nrolls)
local buf = { "", ":--:" }
for n = 1, nrolls do
table.insert(buf, "--------:")
end
table.insert(buf, "")
return table.concat(buf, "|")
end

local function format_row(percent, results)
local buf = { "", string.format("%3d ", percent), }
for n = 1, #results do
if results[n] == 0.0 then
table.insert(buf, string.rep(' ', 9))
else
table.insert(buf, string.format(" %6.2f%% ", results[n] * 100))
end
end
table.insert(buf, "")
return table.concat(buf, "|")
end

local function print_table(maxrolls)
print(format_bar(maxrolls))

for pct = 5, 100, 5 do
local result = {}
for n = 1, maxrolls do
result[n] = reroll_success_at_least(n, n, pct/100)
end
print(format_row(pct, result))
end
end

print_table(7)
``````

Appendix D: Opposed Skill Probability Program

I meant to calculate probabilities analytically. I really did. However the tie resolution procedure defeated my weak probability-fu, so I had to use brute force methods. At least I only had 10,000 possibilities per probability and Lua is amazingly fast for a scripting language.

``````#!/usr/bin/env lua

--
-- According to OpenQuest 3rd edition (pp 40-41):
-- "If both characters succeed, then whoever rolled the highest
-- in their skill test wins the opposed test.
-- If one character rolls a critical, while the other rolls an
-- ordinary success, then the character that rolled the critical,
-- which is a higher level of success, wins."
-- ... "[If both characters fail,]
-- [w]hoever rolled the lowest in their skill test wins the opposed test.
-- In the case of ties for both the player wins."
--
-- HOWEVER:
-- A chart on page 44 of _SimpleQuest_ implies that he who fumbles
-- (crit-fails) loses.  If both sides fumble, both lose.
--

-- Whether the roll is a critical success or critical failure (fumble).
local function is_crit(r)
return r % 11 == 0 or r == 100
end

-- Whether player wins on opposed rolls `p` vs `r`
-- with skills `player` vs. `resist`
local function is_player_win(p, r, player, resist)
-- TODO: This logic is in no way optimized
-- I was aiming for clarity and completeness
-- at the expense of efficiency.

if p <= player and r > resist then
-- player succeeded, resist failed
return true
elseif p <= player and r <= resist then
-- both succeeded
local pcrit, rcrit = is_crit(p), is_crit(r)
if pcrit and not rcrit then
-- only player has p critical success
return true
elseif pcrit == rcrit then
-- neither or both have critical successes
if p >= r then
-- player's roll is higher
return true
end
end
elseif p > player and r > resist then
-- both failed
local pfumb, rfumb = is_crit(p), is_crit(r)
if not pfumb then
-- player didn't fumble
if rfumb or p <= r then
-- resist fumbled
-- or player's dice are equal or lower
return true
end
end
end -- big if
return false
end

-- Figure out probabilities of opposed rolls through brute force
local function opposed_roll(player, resist)
local sum = 0
for p = 1, 100 do
for r = 1, 100 do
if is_player_win(p, r, player, resist) then
sum = sum + 1
end
end
end
return sum/10000
end

local buf = { "", " Pl." }
for i = min, max, step do
table.insert(buf, string.format(" %5d ", i))
end
table.insert(buf, "")
return table.concat(buf, "|")
end

local function format_bar(min, max, step)
local buf = { "", ":--:" }
for i = min, max, step do
table.insert(buf, "------:")
end
table.insert(buf, "")
return table.concat(buf, "|")
end

local function format_row(percent, results)
local buf = { "", string.format("%3d ", percent), }
for n = 1, #results do
table.insert(buf, string.format("%5.1f%% ", results[n] * 100))
end
table.insert(buf, "")
return table.concat(buf, "|")
end

local function print_table(min, max, step)
print(format_bar(min, max, step))

for p = min, max, step do
local results = {}
for r = min, max, step do
table.insert(results, opposed_roll(p, r))
end
print(format_row(p, results))
end
end

print_table(0, 100, 10)
``````

1. Listed as D{0,1} to represent the sides being interpreted as 0 or 1. An earlier version used {1,2} and ended up being kind of confusing. ↩︎

2. As in a grimoire they wrote recording rituals they’ve cast before, not that they legally bought at a thrift shop. ↩︎

3. I.e. 1.4999999… rounds to 1, but 1.5000000… rounds to 2. ↩︎