Critical Role: The Almost Impossible (Campaign 1 - Vox Machina)

This is a repost of a previous project originally from 15 June 2024.

1. Introduction

Critical Role is a series in which a star-studded cast of Hollywood voice actors play Dungeons & Dragons. It’s a story known for its length (and you thought catching up on the Marvel Cinematic Universe was bad):

Critical Role Run Time in Context
Campaign Start Date End Date Play Time # of 45' Cable TV Episodes
1 - Vox Machina March 2015 October 2017 373 hours 23 minutes 497.8
2 - The Mighty 9 January 2018 June 2021 483 hours 40 minutes 644.9
3 - Bell's Hells October 2021 Present (as of e95) 351 hours 3 minutes 468.1
Other June 2021 June 2022 56 hours 46 minutes 75.7
Total March 2015 Present 1,265 hours 1 minute 1,686.5

  And that’s without the ads or mid-session break! It was only thanks to the ability to adjust the playback speed to 2.5x that I was able to catch up over the course of a year and a half. It’s a story also known for its deep world building, poignant moments, celebrity guests (including Felicia Day, Joe Manganiello, Stephen Colbert, and Vin Diesel), and extremely dedicated fans. One fan, Stuart Langridge, went so far as to build a website that has searchable transcripts of every episode, linked to the time stamp they occurred in their YouTube upload.

  During the game, players will roll one or more of several different types of dice and then add or subtract values related to their character and the conditions their in. If the roll is equal or greater to an assigned threshold, they are able to perform their intended action, and will sometimes roll again to determine the degree of effect of that action. Dungeons & Dragons lists several standard “Difficulty Classes” depending on how challenging the proposed action is. The values range from “Very Easy” at 5 to “Medium” at 15 to “Very Hard” at 25.

  But it’s the very highest level that I want to focus on today: the “Nearly Impossible.”

  “In the general breakdown of difficulty classes… 30 is the top success level that they list. They don’t list any higher than 30.”

Brennan Lee Mulligan, [Exandria Unlimited: Calamity](https://youtu.be/KlIkkeWmVvA?si=WnsJlep0S02dzZTS&t=5976)

  When a player rolls 30 or higher on a 20-sided dice (including any appropriate modifiers) it is a notable occasion. I wanted to know how frequent these rolls were, who was rolling this high, and when these moments were happening during Campaign 1. Because this was a simple exploration of the data, it would only require a few often used packages.

library(tidyverse)
library(stringr)
library(kableExtra) # For the tables

2. Dataset

  The dataset was created by the hard work of the people at CR Stats. Thanks to Andrew K., Singing Badger, Lauren K., and Katherine K. for they work they’ve done compiling this and so much more data. While this team is retired, their work archiving the numbers is being continued by the team at The Omen Archive, though it unfortunately does seem they have not been cataloging every single roll.

  First thing was to load the data. Unlike some other projects I’ve worked on, there was no need to skip any rows in order for the values to find themselves in the right places.

# Load Data
VM_Rolls <- read.csv("Data/Critical_Roll/VM_AllRolls.csv", skip = 0)

Episode, Time, Character, Type.of.Roll, Total.Value, Natural.Value, Crit., Damage, X..Kills, Notes

  Some of the names weren’t the most readable for me, so I changed the ones I would be focusing on to something that would work going forward.

# Renaming Columns
names(VM_Rolls)[4] <- "Roll_Type"
names(VM_Rolls)[5] <- "Total"

  Now that the data frame was workable, it was time to get a sense of what I was working with.

# How Many Rolls Were There?
nrow(VM_Rolls)    # 13,514

  13,514 rolls is a lot of rolls, even for 337 hours 22 minutes and 38 seconds of game time (Thank you again CR Stats Team!)


3. Cleaning

  Throughout the cleaning process I used to the following form to examine where there may have been input errors.

# Looking at what may be wrong
  table(VM_Rolls$TargetColumn) %>% as.data.frame() %>% arrange(Var1)

3.1. Episodes

  Most of the episode names were in a workable format. A few of the episodes were broken into a “Part 1” and a “Part 2.” Thankfully, the {stringr} package made replacing these with a format that could be coerced into numeric for further processing quite simple.

# Episodes 
  # Replacing the "parts" with decimals
  Episode_Clean_1 <- str_replace(VM_Rolls$Episode, "p1", ".1")
  Episode_Clean_2 <- str_replace(Episode_Clean_1, "p2", ".2")

  # Removing the space
  Episode_Clean_3 <- str_replace_all(Episode_Clean_2, pattern = " ", repl = "")

  # Coercing the episodes to numeric
  Episode_Final <- as.numeric(Episode_Clean_3) # 13,514

3.2. Timestamps

  I was worried about letter typos in the Time Stamps that would prevent their sorting. {stringr} allowed for a quick check.

# Time Stamps
  # Testing for non-time values
  Time_Test <- VM_Rolls %>% mutate(Letters = str_count(VM_Rolls$Time, "[:alpha:]"))
  
  # Checking, None Found
  # Time_Test %>% filter(Letters != 0)
  
  # Finalizing
  Time_Final <- VM_Rolls$Time

3.3. Character Attribution

  To accurately determine which characters performed which action, there needed to be accurate counts. There were a couple typos that separated the rolls attributed by each character into two groups. {stringr} saved the day again.

# Which Character Rolled
  # Replacing the Tibierus and PIke typos
  Character_Clean_1 <- str_replace(VM_Rolls$Character, "Tibierus", "Tiberius")
  Character_Final <- str_replace(Character_Clean_1, "PIke", "Pike")

3.4. Roll Type

  Roll type had perhaps the greatest variability. Several entries essentially said the same thing but were worded a little differently. They were combined into one group to allow for more accurate counts.

# Roll Type
  # Replacing Typos
  Roll_Type_Clean_1 <- str_replace_all(VM_Rolls$Roll_Type, "Steath", "Stealth")
  Roll_Type_Clean_2 <- str_replace_all(Roll_Type_Clean_1, "Beard Check", "Beard")
  Roll_Type_Clean_3 <- str_replace_all(Roll_Type_Clean_2, "Dex Save", "Dexterity Save")
  Roll_Type_Clean_4 <- str_replace_all(Roll_Type_Clean_3, "Wisdom Saving", "Wisdom Save")
  Roll_Type_Clean_5 <- str_replace_all(Roll_Type_Clean_4, "d100", "Percentile")
  Roll_Type_Final <- str_replace_all(Roll_Type_Clean_5, "Ressurection Roll", "Resurrection Roll")

3.5. Roll Total

  There were several different ways roll totals were reported. Because the total number was the focus, it wasn’t important whether it was a Natural 20 or whether the value was exact. Using stringr the unnecessary parts were pulled off, anything that wasn’t a number was replaced with an “NA,” and any errant spaces were removed before the column was converted into numeric for later processing.

# Roll Totals
  # Removing what we don't want, then replacing ambiguity with NA
  Total_Clean_1 <- str_replace_all(VM_Rolls$Total, "Natural|Nat|20=|1=|\\+|ish|\\<|\\>|\\?|18,", "")
  Total_Clean_2 <- str_replace_all(Total_Clean_1, "--|Unkknown|Unnknown|unknown|Unknown|N/A|Success|Misfire|Fail", "NA")
  Total_Clean_3 <- str_squish(Total_Clean_2)
  
  # Coercing to numeric
  Total_Final <- as.numeric(Total_Clean_3) # 13,514

3.6. Natural Roll

  The process for the Natural Rolls (the roll on the dice before any modifiers are accounted for) was similar to the processing for the totals.

# Natural Roll Values
  # Removing what we don't want, then replacing ambiguity with NA
  Natural_Clean_1 <- str_replace_all(
                        VM_Rolls$Natural, "--|#REF!|Uknown|unknown|Unknown|Unkown", "NA")
  
  # Coercing to numeric
  Natural_Final <- as.numeric(Natural_Clean_1) # 13,514

3.7. Critical Indicator

  Rolling 20 on a 20 sided die is considered a “Critical Success,” where the action not only succeeds but there is often additional positive effects. Rolling a 1 on a 20 sided die is considered a “Critical Fail,” where the action not only fails but there is often some sort of additional negative consequence. There are other conditions, often in combat, that can end up being “critical.” Whether it was a success, failure, or something else, I wanted to make sure that any ambiguous markings ("–") were replaced with something easier to work with.

# Is It A Critical?
  # Removing what we don't want, then replacing ambiguity with NA
  Crit_Clean_1 <- str_replace_all(VM_Rolls$Crit., "--", "NA")

  # Coercing to numeric
  Crit_Final <- as.numeric(Crit_Clean_1) # 13,514

3.8. Kill Count

  Whether the attack roll resulted in the enemy’s demise. A simple removal of some indications of ambiguity.

# Kill Count
  # Fixing the ?
  Kills_Clean_1 <- str_replace_all(VM_Rolls$Kills, "\\?", "NA")

  # Coercing into numeric
  Kills_Final <- as.numeric(Kills_Clean_1) # 13,514

3.9. Rejoining the Cleaned Data

  Now that all of the data has been cleaned, it was time to reassemble.

# Rejoining the Data
  VM_Clean <- data.frame(Episode_Final, Time_Final, Character_Final, Roll_Type_Final, 
                       Total_Final, Natural_Final, Crit_Final, Kills_Final)

4. Adding Context

  The information contained is critical, but it’s not the full story. For later analysis comparing Campaign 1 and Campaign 2 as well as indicate who was a member of the core party, additional data needed to be included. Further, when exploring the context of these high rolls, it’s important to know not only the Roll Type (Skill, Save, Check, Attack, Damage, etc.) but also what core ability score it’s associated with.

  Each of a creature’s abilities has a score, a number that defines the magnitude of that ability. An ability score is not just a measure of innate capabilities, but also encompasses a creature’s training and competence in activities related to that ability.

[Basic Rules](https://www.dndbeyond.com/sources/basic-rules/using-ability-scores#AbilityScoresandModifiers), Dungeons and Dragons
VM_Campaign <- VM_Clean %>% 
                mutate(Campaign = "1", 
                      Party = "Vox Machina",
                      Main_Party = case_when(Character_Final == "Arkhan" ~ FALSE,
                                             Character_Final == "Doty" ~ FALSE, ... 
                      Ability = case_when(Roll_Type_Final == "Athletics" ~ "Strength",
                                          Roll_Type_Final == "Strength" ~ "Strength", ...
                                    

  There was some ambiguity or it was outright unknown what ability score or skill a certain roll was associated with. The following is my best attempt at aligning roll types with their appropriate ability scores.

4.1 Sorting Skills into their Abilities

# Number of Rolls of each Ability 
Ability_Count <- table(VM_Campaign$Ability) %>% 
                  as.data.frame() %>% 
                  arrange(factor(Var1, levels = c('Strength','Dexterity','Constitution',
                                                  'Intelligence','Wisdom','Charisma','Other'))) %>%
                  mutate("% of Total" = round((Freq/13509)*100,1))
      # Strength = 735
      # Dexterity = 2,792
      # Constitution = 591
      # Intelligence = 1,085
      # Wisdom = 1,871
      # Charisma = 500
      # Other = 5,935
names(Ability_Count) <-c("Ability", "Rolls", "% of Total")

Ability_Count_sOther <- table(VM_Campaign$Ability) %>% 
                          as.data.frame() %>% filter(Var1 != "Other") %>%
                          arrange(factor(Var1, levels = c('Strength','Dexterity','Constitution',
                                                          'Intelligence','Wisdom','Charisma','Other'))) %>%
                          mutate("% of Total" = round((Freq/7574)*100,1))

names(Ability_Count_sOther) <-c("Ability", "Rolls", "% of Total")
Ability_sOther_Final <- rbind(Ability_Count_sOther, c("1", "1", "1"))

# Top Number of Rolls by Type
# table(VM_Campaign$Roll_Type_Final) %>% as.data.frame() %>% arrange(desc(Freq))
      # Attack Rolls = 3,147
      # Damage Rolls = 2,161
      # Combat = 5,308 (of the 5,935)
      # Initiative Rolls = 688 (of the 2,792)

# Skill by Ability Table
Skill_Ability_Table <- VM_Campaign %>% 
                        select(Ability, Roll_Type_Final) %>% 
                        unique() %>% 
                        distinct(.keep_all = T) %>% 
                        filter(!is.na(Ability)) %>%
                        arrange(Ability)
              
# Adding a Counter Column to allow Pivoting Wider
Skill_Ability_Sorted <- Skill_Ability_Table %>% 
                          group_by(Ability) %>% 
                          arrange(Ability, Roll_Type_Final) %>%
                          mutate(Counter = row_number(Ability))

# Pivoting Wider, Replacing NA's, sorting columns to standard DND 5e order
Skill_Ability_Final <- Skill_Ability_Sorted %>% 
                        pivot_wider(names_from = Ability, values_from = Roll_Type_Final) %>% 
                        mutate_all(~replace((.), is.na(.), "--")) %>%
                        select(Counter, Strength, Dexterity, Constitution, 
                               Intelligence, Wisdom, Charisma, Other) %>%
                        rbind(c("Total Rolls",735,2792,591,1085,1871,500,5935)) %>% print()
## # A tibble: 34 × 8
##    Counter Strength    Dexterity Constitution Intelligence Wisdom Charisma Other
##    <chr>   <chr>       <chr>     <chr>        <chr>        <chr>  <chr>    <chr>
##  1 1       Athletics   Acrobati… Concentrati… Alchemy      Anima… Charisma Atta…
##  2 2       Strength    Blacksmi… Constitution Arcana       Insig… Charism… Beard
##  3 3       Strength S… Dexterity Constitutio… History      Medic… Decepti… Coun…
##  4 4       --          Dexterit… --           Intelligence Perce… Disappo… Cutt…
##  5 5       --          Disguise… --           Intelligenc… Survi… Fart.    Dama…
##  6 6       --          Initiati… --           Investigati… Track… Intimid… Deat…
##  7 7       --          Sleight … --           Nature       Wisdom Musical… Dete…
##  8 8       --          Stealth   --           Religion     Wisdo… Perform… Dete…
##  9 9       --          Thieves'… --           --           --     Persuas… Divi…
## 10 10      --          Tinkering --           --           --     --       Fix  
## # ℹ 24 more rows
knitr::kable(Skill_Ability_Final, align = "c") %>% 
          kable_styling(full_width = T, font_size = 12) %>%
          row_spec(seq(from=1, to=34, by=2), background = "#eff1f2") %>%
          row_spec(34, font_size = 14, italic = T) %>%
          column_spec(1, bold=T, italic=T) %>%
          add_header_above(c("Index"=1, "Ability"=7),
                           font_size = 14) %>%
          add_header_above(c("Skills Organized by Associated Abilities"=8), 
                           font_size = 16, color = "white", background = "#000000")
Skills Organized by Associated Abilities
Index
Ability
Counter Strength Dexterity Constitution Intelligence Wisdom Charisma Other
1 Athletics Acrobatics Concentration Alchemy Animal Handling Charisma Attack
2 Strength Blacksmith Tools Constitution Arcana Insight Charisma Save Beard
3 Strength Save Dexterity Constitution Save History Medicine Deception Counterspell
4 -- Dexterity Save -- Intelligence Perception Disappointment Cutting Words
5 -- Disguise Kit -- Intelligence Save Survival Fart. Damage
6 -- Initiative -- Investigation Tracking Intimidation Death Save
7 -- Sleight of Hand -- Nature Wisdom Musical Taste Determine Effect
8 -- Stealth -- Religion Wisdom Save Performance Determine Focus
9 -- Thieves' Tools -- -- -- Persuasion Divine Intervention
10 -- Tinkering -- -- -- -- Fix
11 -- -- -- -- -- -- Gambit of Ord
12 -- -- -- -- -- -- Healing
13 -- -- -- -- -- -- Heroes' Feast
14 -- -- -- -- -- -- Inspiration
15 -- -- -- -- -- -- Missile Snare
16 -- -- -- -- -- -- No reason.
17 -- -- -- -- -- -- Other
18 -- -- -- -- -- -- Panic
19 -- -- -- -- -- -- Parry
20 -- -- -- -- -- -- Percentile
21 -- -- -- -- -- -- Potion Duration
22 -- -- -- -- -- -- Recharge
23 -- -- -- -- -- -- Resurrection Roll
24 -- -- -- -- -- -- Second Wind
25 -- -- -- -- -- -- Skill
26 -- -- -- -- -- -- Sleep Arrow
27 -- -- -- -- -- -- Spell Attack
28 -- -- -- -- -- -- Spell Effect
29 -- -- -- -- -- -- Spellcasting
30 -- -- -- -- -- -- Telekinesis
31 -- -- -- -- -- -- Test Roll
32 -- -- -- -- -- -- Trajectory
33 -- -- -- -- -- -- Unknown
Total Rolls 735 2792 591 1085 1871 500 5935

  Note: Both “Thieves’ Tools” and “Tinkering” were sometimes treated as Dexterity and sometimes treated as Intelligence. In what I could find, both seemed to be predominately treated as Dexterity in this campaign, so it’s what I went with. Attack rolls were placed in the “Other” column because while the appropriate ability score would be known, that information was not included in the data set. 688 of the 2,792 Dexterity rolls were Initiative, meaning 24.6% of the Dexterity rolls were getting into combat. Of the 5,935 “Other” rolls, there were 3,147 “Attack” rolls and 2,161 “Damage rolls. That means at least 89.4% of the “Other” rolls were in combat.

Percent of Total Rolls by Ability
Ability Rolls % of Total
Strength 735 5.4
Dexterity 2792 20.7
Constitution 591 4.4
Intelligence 1085 8.0
Wisdom 1871 13.9
Charisma 500 3.7
Other 5935 43.9
Percent of Total Rolls by Ability (Without Other)
Ability Rolls % of Total
Strength 735 9.7
Dexterity 2792 36.9
Constitution 591 7.8
Intelligence 1085 14.3
Wisdom 1871 24.7
Charisma 500 6.6
NA 1 1

  Examining the demands of the story based on the rolls, it pays to be Dexterous and Wise in Campaign 1. Many of Campaign 2’s characters would have done very well against the obstacles Vox Machina faced just based on the distribution of their ability scores.


5. Success In The Face Of The The Nearly Impossible

  Now that some context has been added, it’s time to zoom in on the rolls of greatest interest, the one’s that exceeded the highest tier of difficulty class.

5.1. Pulling out only those rolls

  First any roll that had no associated character needed to be removed. Next I filtered out rolls that were known to or suspected to not have been done with a d20. Damage has an incredibly high ceiling, so achieving over 30 does not get the same kind of reaction as passing a Difficulty Check with over 30, so while it will be incorporated into a later examination, for now it had to go. If there was no roll type it would have been impossible to determine what the roll was for, so those were removed. Lastly, the main focus, only rolls that totaled (roll on the dice and any modifiers) were kept.

# DC Greater or equal to 30
DC30_Rolls_wStealth <- VM_Campaign %>% 
                        filter(!is.na(Character_Final)) %>%                    
    
                        filter(Roll_Type_Final != "Beard") %>%
                        filter(Roll_Type_Final != "d100") %>%                      
                        filter(Roll_Type_Final != "Damage") %>% 
                        filter(Roll_Type_Final != "Determine Effect") %>%
                        filter(Roll_Type_Final != "Divine Intervention") %>%
                        filter(Roll_Type_Final != "Healing") %>%
                        filter(Roll_Type_Final != "Percentile") %>%
  
                        filter(!is.na(Roll_Type_Final)) %>%
  
                        filter(Total_Final >= 30)
DC30_Rolls_wStealth %>% nrow() # 590

  I debated keeping Stealth rolls. Because of the well-loved spell Pass Without a Trace it is possible to get absurdly high rolls on Stealth checks. However, because the cast and other players still have that “wow” reaction when a high roll is made even the extra magical help, I kept it in.

  Before filtering there were 13,514 rolls recorded in Campaign 1. After filtering all except the total, there were 11,001 rolls (81.4% of the total) still remaining. When the filter for the roll total was added, that number dropped to 590. Only 4.4% of the rolls in Campaign 1 (pre-filtering) were 30 or above. These are truly the cream of the crop, but honestly more frequent than I expected.

5.2. Who Performed These Feats?

  The first thing people like to know when you tell them about a particularly notable roll is who rolled that high? That’s what I looked at first.

# DC 30+ Table by Character
DC30_Character <- DC30_Rolls_wStealth %>% 
                    group_by(Character_Final) %>%
                    count(Total_Final) %>%
                    pivot_wider(names_from = Character_Final, values_from = n) %>%
                    mutate_all(~replace((.), is.na(.), "--")) %>%
                    select(Total_Final,'Grog','Keyleth','Percy','Pike','Scanlan',"Vax'ildan","Vex'ahlia",
                         'Arkhan','Lyra','Tiberius','Tova',"Trinket","Zahra") %>%
                    arrange(Total_Final)

knitr::kable(DC30_Character, align = "c",
                col.names = c("Total", 'Grog','Keyleth','Percy','Pike','Scanlan',"Vax'ildan","Vex'ahlia",
                             'Arkhan','Lyra','Tiberius','Tova',"Trinket","Zahra")) %>% 
          kable_styling(full_width = T, font_size = 12) %>%
          row_spec(seq(from=1, to=18, by=2), background = "#eff1f2") %>%
          pack_rows(index = c("30+ Club"=5,"35+ Club"=5,"40+ Club"=5,"45+ Club"=3)) %>%
          column_spec(1, bold=T, italic=T) %>%
          add_header_above(c(" "=1, "Main Party"=7, "Guests/Inactive Members"=6),
                           font_size = 14) %>%
          add_header_above(c("Characters Who Rolled 30+"=14), 
                           font_size = 16, color = "white", background = "#352b41")
Characters Who Rolled 30+
Main Party
Guests/Inactive Members
Total Grog Keyleth Percy Pike Scanlan Vax'ildan Vex'ahlia Arkhan Lyra Tiberius Tova Trinket Zahra
30+ Club
30 15 12 29 3 15 50 43 1 1 1 1 1 1
31 18 6 22 -- 7 23 27 3 -- 3 -- -- --
32 11 7 14 1 10 29 21 1 -- -- -- -- --
33 9 2 10 -- 2 20 13 1 -- -- -- -- --
34 2 5 8 -- 3 11 11 1 -- -- -- -- --
35+ Club
35 2 1 4 1 1 13 11 -- -- -- -- -- --
36 2 2 2 -- -- 1 5 -- -- -- -- -- --
37 1 1 -- -- 1 15 3 -- -- -- -- -- --
38 -- 2 -- -- -- 5 2 -- -- -- -- -- --
39 -- 1 -- -- -- 5 9 -- -- -- -- -- --
40+ Club
40 -- -- -- -- -- 6 -- -- -- -- -- -- --
41 -- -- -- -- -- 1 -- -- -- -- -- -- --
42 -- -- -- -- -- 2 -- -- -- -- -- -- --
43 -- -- 1 -- -- 3 2 -- -- -- -- -- --
44 -- -- -- -- -- 3 -- -- -- -- -- -- --
45+ Club
45 -- -- -- -- -- 3 -- -- -- -- -- -- --
46 -- -- -- -- -- 2 2 -- -- -- -- -- --
47 -- 1 -- -- -- -- -- -- -- -- -- -- --
Outrageous_Mini <- DC30_Rolls_wStealth %>% filter(Total_Final >= 45) %>% select(Character_Final, Roll_Type_Final, Total_Final) %>%
               knitr::kable(align = "c",
               col.names = c("Character", "Roll Type", "Total")) %>% 
               kable_styling(full_width = F) %>%
               row_spec(1, background = "#a1b556", color = "white") %>%
               row_spec(c(2,7:8), background = "#3b6e89",color = "white") %>%
               row_spec(c(3:6), background = "#352b41",color = "white") %>%
               add_header_above(c("The Truly Outrageous Rolls: 45 and Over" =3), font_size = 16)
Outrageous_Mini
The Truly Outrageous Rolls: 45 and Over
Character Roll Type Total
Keyleth Stealth 47
Vex'ahlia Stealth 46
Vax'ildan Stealth 45
Vax'ildan Stealth 46
Vax'ildan Stealth 45
Vax'ildan Stealth 45
Vex'ahlia Stealth 46
Vax'ildan Stealth 46

  Here’s a closer examination of the atmospherically high rolls. As expected, they’re all for Stealth and were likely magically assisted. Pass Without a Trace adds a +10 modifier to Stealth rolls.


5.3. What Were These Feats?

  Next people ask what was happening when they rolled that high.

# What were the impossible rolls?
DC30_Type <- DC30_Rolls_wStealth %>% 
              group_by(Character_Final) %>%
              count(Roll_Type_Final) %>%
              pivot_wider(names_from = Character_Final, values_from = n) %>%
              replace_na(list('Arkhan'=0,'Grog'=0,'Keyleth'=0,'Lyra'=0,'Percy'=0,'Pike'=0,'Scanlan'=0,
                              'Taryon'=0,'Tiberius'=0,'Tova'=0,"Trinket"=0,"Vax'ildan"=0,"Vex'ahlia"=0,"Zahra"=0)) %>%
              select(Roll_Type_Final,'Grog','Keyleth','Percy','Pike','Scanlan',"Vax'ildan","Vex'ahlia",
                         'Arkhan','Lyra','Tiberius','Tova',"Trinket","Zahra") %>%
              arrange(Roll_Type_Final) %>%
              bind_rows(summarise_all(., ~if(is.numeric(.)) sum(.) else "Total"))

knitr::kable(DC30_Type, align = "c",
             col.names = c("Roll Type", 'Grog','Keyleth','Percy','Pike','Scanlan',"Vax'ildan","Vex'ahlia",
                         'Arkhan','Lyra','Tiberius','Tova',"Trinket","Zahra")) %>% 
          kable_styling(full_width = T, font_size = 12) %>%
          row_spec(seq(from=1, to=23, by=2), background = "#eff1f2") %>%
          row_spec(23, font_size = 14, italic = T) %>%
          column_spec(1, bold=T, italic=T) %>%
          add_header_above(c(" "=1, "Main Party"=7, "Guests / Inactive Members"=6),
                           font_size = 14) %>%
          add_header_above(c("The Skill Types of the 30+ Rolls"=14), 
                           font_size = 16, color = "white", background = "#7f2820")
The Skill Types of the 30+ Rolls
Main Party
Guests / Inactive Members
Roll Type Grog Keyleth Percy Pike Scanlan Vax'ildan Vex'ahlia Arkhan Lyra Tiberius Tova Trinket Zahra
Acrobatics 0 0 0 0 0 15 7 0 0 0 0 0 0
Arcana 0 0 0 0 1 0 0 0 0 0 0 0 0
Athletics 3 0 0 0 0 1 0 1 0 0 0 0 0
Attack 51 1 57 1 0 45 43 5 1 3 1 0 1
Charisma Save 0 0 0 0 1 0 0 0 0 0 0 0 0
Constitution Save 1 0 0 0 0 0 0 0 0 0 0 0 0
Deception 0 0 0 0 13 0 0 0 0 0 0 0 0
Dexterity Save 0 0 0 0 1 4 6 0 0 0 0 0 0
Initiative 0 0 3 0 0 0 0 0 0 0 0 0 0
Intelligence 0 0 1 0 0 0 0 0 0 0 0 0 0
Investigation 0 0 0 0 6 4 0 0 0 0 0 0 0
Nature 0 3 0 0 0 0 0 0 0 0 0 0 0
Perception 0 8 0 0 1 16 36 0 0 0 0 0 0
Performance 0 0 0 0 2 0 0 0 0 0 0 0 0
Persuasion 0 0 0 0 6 0 0 0 0 0 0 0 0
Stealth 4 24 25 4 7 99 55 1 0 1 0 1 0
Strength 1 0 0 0 0 0 0 0 0 0 0 0 0
Survival 0 0 0 0 0 0 1 0 0 0 0 0 0
Thieves' Tools 0 0 0 0 0 8 1 0 0 0 0 0 0
Tinkering 0 0 4 0 0 0 0 0 0 0 0 0 0
Unknown 0 0 0 0 1 0 0 0 0 0 0 0 0
Wisdom Save 0 4 0 0 0 0 0 0 0 0 0 0 0
Total 60 40 90 5 39 192 149 7 1 4 1 1 1

5.4. When Did They Happen?

  Finally, people ask when they happened.

# When were these impossible rolls?
DC30_Timeline <- DC30_Rolls_wStealth %>% 
                  group_by(Episode_Final) %>%                      
                  count(Character_Final) %>%
                  pivot_wider(names_from = Character_Final, values_from = n) %>%
                  replace_na(list('Arkhan'=0,'Grog'=0,'Keyleth'=0,'Lyra'=0,'Percy'=0,'Pike'=0,
                                  'Scanlan'=0,'Taryon'=0,'Tiberius'=0,'Tova'=0,"Trinket"=0,
                                  "Vax'ildan"=0,"Vex'ahlia"=0,"Zahra"=0)) %>%
                  select('Grog','Keyleth','Percy','Pike','Scanlan',"Vax'ildan",
                         "Vex'ahlia",'Arkhan','Lyra','Tiberius','Tova',"Trinket","Zahra")
      
DC30_Timeline_Final <- as.data.frame(DC30_Timeline) %>% 
                        mutate("Total" = rowSums(DC30_Timeline[sapply(DC30_Timeline, is.integer)]), 
                               .after = "Vex'ahlia")
Top30_Episodes<- table(DC30_Rolls_wStealth$Episode_Final) %>% as.data.frame() %>% arrange(desc(Freq))
names(Top30_Episodes)[1] <- "Episode"

Top30_Final <- Top30_Episodes[1:10,] %>% 
                mutate(Arc = c("Vecna","Vecna","Vecna","Taryon Darrington",
                              "Taryon Darrington","Taryon Darrington","The Chroma Conclave",
                              "Vecna","The Chroma Conclave", "The Chroma Conclave"), 
                       .after = Episode)
Top30_Mini <- knitr::kable(Top30_Final, align = "c", 
                           col.names = c("Episode", "Arc", "Number of Rolls 30 and Over")) %>% 
                    kable_styling(full_width = F) %>%
                    row_spec(seq(from=1,to=10,by=2), background = "#eff1f2") %>%
                    add_header_above(c("Episodes with the Most Rolls 30 and Over" =3), font_size = 16)

Top30_Mini
Episodes with the Most Rolls 30 and Over
Episode Arc Number of Rolls 30 and Over
113 Vecna 33
114 Vecna 24
108 Vecna 23
92 Taryon Darrington 21
88 Taryon Darrington 19
93 Taryon Darrington 17
44 The Chroma Conclave 16
112 Vecna 16
68 The Chroma Conclave 15
47 The Chroma Conclave 14

  It makes sense that so many of these episodes with the very highest count of rolls 30 and over were in the later story arcs. Having leveled up so much, the modifiers that are getting added to their rolls helped push the totals they roll higher and higher, reflecting their growth throughout their story.

  Here’s a full breakdown of when all of the 30+ rolls occurred including their episodes and arcs:


knitr::kable(DC30_Timeline_Final, align = "c",
             col.names = c("Episode", 'Grog','Keyleth','Percy','Pike','Scanlan',"Vax'ildan","Vex'ahlia",
                         "Total",'Arkhan','Lyra','Tiberius','Tova',"Trinket","Zahra")) %>% 
          kable_styling(full_width = T, font_size = 12) %>%
          pack_rows(index = c("Arc 1: Kraghammer and Vasselheim (49)" = 19,
                              "Arc 2: The Briarwoods (39)" = 15,
                              "Arc 3: The Chroma Conclave (236)" = 43,
                              "Arc 4: Taryon Darrington (85)" = 14,
                              "Arc 5: Vecna (172)" = 15)) %>%
          row_spec(seq(from=1,to=105,by=2), background = "#eff1f2") %>%
          column_spec(c(1,9), bold=T, italic=T) %>%
          add_header_above(c(" "=1, "Main Party"=7, " "=1, "Guests/Inactive Members"=6),
                           font_size = 14) %>%
          add_header_above(c("Timeline of 30+ Rolls - Arcs Included"=15), 
                           font_size = 16, color = "white", background = "#762e64")
Timeline of 30+ Rolls - Arcs Included
Main Party
Guests/Inactive Members
Episode Grog Keyleth Percy Pike Scanlan Vax'ildan Vex'ahlia Total Arkhan Lyra Tiberius Tova Trinket Zahra
Arc 1: Kraghammer and Vasselheim (49)
1.0 0 0 1 0 0 2 0 3 0 0 0 0 0 0
2.0 0 0 1 0 0 0 0 1 0 0 0 0 0 0
3.0 0 0 0 0 0 1 0 1 0 0 0 0 0 0
4.0 0 0 0 0 1 0 0 1 0 0 0 0 0 0
5.0 0 0 0 0 0 0 1 1 0 0 0 0 0 0
6.0 0 0 1 0 0 2 1 4 0 0 0 0 0 0
7.0 0 0 0 0 0 3 2 5 0 0 0 0 0 0
8.0 0 1 0 0 0 2 0 3 0 0 0 0 0 0
10.0 0 0 0 0 0 1 2 4 0 0 1 0 0 0
11.0 1 1 1 0 0 2 0 5 0 0 0 0 0 0
13.0 0 0 1 0 0 2 0 3 0 0 0 0 0 0
15.0 0 0 0 0 0 0 0 1 0 0 1 0 0 0
16.0 0 0 0 0 1 1 2 4 0 0 0 0 0 0
17.0 0 0 0 0 0 1 0 1 0 0 0 0 0 0
18.0 0 0 0 0 0 0 0 1 0 0 0 0 0 1
19.0 0 0 2 0 0 0 1 4 0 1 0 0 0 0
20.0 0 0 0 0 0 2 0 2 0 0 0 0 0 0
21.0 0 0 0 0 0 1 0 3 0 0 2 0 0 0
22.0 0 0 1 0 0 1 0 2 0 0 0 0 0 0
Arc 2: The Briarwoods (39)
24.0 0 0 0 0 1 1 0 2 0 0 0 0 0 0
25.0 0 0 1 0 0 1 0 2 0 0 0 0 0 0
26.0 0 0 0 0 1 1 0 2 0 0 0 0 0 0
28.0 0 0 0 0 0 1 1 2 0 0 0 0 0 0
29.0 0 2 0 0 2 4 2 10 0 0 0 0 0 0
30.0 0 0 0 0 0 2 1 3 0 0 0 0 0 0
31.1 0 0 0 0 1 1 0 2 0 0 0 0 0 0
31.2 0 0 1 0 0 0 0 1 0 0 0 0 0 0
32.0 0 0 0 0 0 2 0 2 0 0 0 0 0 0
33.2 0 0 0 0 0 2 1 3 0 0 0 0 0 0
34.0 0 0 2 0 0 1 1 4 0 0 0 0 0 0
35.2 0 0 0 0 0 1 0 1 0 0 0 0 0 0
36.0 0 0 0 0 1 0 0 1 0 0 0 0 0 0
37.0 0 0 0 0 2 1 0 3 0 0 0 0 0 0
38.0 0 0 0 0 1 0 0 1 0 0 0 0 0 0
Arc 3: The Chroma Conclave (236)
39.0 0 0 1 0 0 0 0 1 0 0 0 0 0 0
40.0 0 0 0 0 0 1 0 1 0 0 0 0 0 0
41.0 0 0 1 0 0 3 2 6 0 0 0 0 0 0
42.0 1 1 2 0 0 6 3 13 0 0 0 0 0 0
43.0 0 0 0 0 0 3 0 3 0 0 0 0 0 0
44.0 0 0 2 0 2 7 5 16 0 0 0 0 0 0
45.0 0 2 0 0 0 1 1 4 0 0 0 0 0 0
46.0 0 0 3 0 0 3 6 12 0 0 0 0 0 0
47.0 0 3 3 0 0 3 5 14 0 0 0 0 0 0
48.0 0 0 0 0 0 2 1 3 0 0 0 0 0 0
49.0 0 1 1 0 0 4 3 9 0 0 0 0 0 0
50.0 0 2 0 0 0 5 2 9 0 0 0 0 0 0
51.0 0 1 2 0 0 3 4 10 0 0 0 0 0 0
52.0 0 0 0 0 0 1 2 3 0 0 0 0 0 0
53.0 0 0 0 0 0 2 0 2 0 0 0 0 0 0
54.0 0 0 1 0 0 0 1 2 0 0 0 0 0 0
55.0 0 0 0 0 1 2 2 5 0 0 0 0 0 0
56.0 0 0 0 0 0 1 1 2 0 0 0 0 0 0
57.0 0 0 1 0 0 1 1 3 0 0 0 0 0 0
58.0 0 0 0 0 0 0 2 2 0 0 0 0 0 0
59.0 0 1 0 0 0 1 0 2 0 0 0 0 0 0
60.0 0 0 0 0 0 1 0 1 0 0 0 0 0 0
61.0 0 0 1 0 1 1 3 6 0 0 0 0 0 0
62.0 1 1 1 1 1 0 1 6 0 0 0 0 0 0
63.0 3 0 3 0 1 1 3 11 0 0 0 0 0 0
64.0 0 0 0 0 0 2 1 3 0 0 0 0 0 0
65.0 1 0 0 0 4 0 1 6 0 0 0 0 0 0
66.0 0 0 1 0 0 1 1 3 0 0 0 0 0 0
67.0 0 0 0 0 0 0 1 1 0 0 0 0 0 0
68.0 1 1 1 0 0 4 8 15 0 0 0 0 0 0
69.0 0 0 0 0 1 0 0 1 0 0 0 0 0 0
70.0 0 0 0 0 0 0 1 1 0 0 0 0 0 0
71.0 0 0 3 0 0 1 7 11 0 0 0 0 0 0
72.0 0 0 0 0 1 0 0 1 0 0 0 0 0 0
75.0 0 0 0 0 3 1 0 4 0 0 0 0 0 0
76.0 0 0 0 0 1 1 1 3 0 0 0 0 0 0
77.0 0 0 5 0 0 0 3 8 0 0 0 0 0 0
78.0 0 0 1 0 0 3 2 6 0 0 0 0 0 0
79.0 1 0 1 1 0 1 2 6 0 0 0 0 0 0
80.0 0 0 1 0 1 0 0 2 0 0 0 0 0 0
81.0 0 1 0 0 1 0 0 2 0 0 0 0 0 0
82.0 1 1 2 0 1 2 3 10 0 0 0 0 0 0
83.0 3 0 1 0 0 3 3 10 0 0 0 0 0 0
Arc 4: Taryon Darrington (85)
84.0 0 0 0 0 0 3 0 3 0 0 0 0 0 0
85.0 1 0 0 0 0 1 0 2 0 0 0 0 0 0
87.0 1 0 1 0 0 2 1 5 0 0 0 0 0 0
88.0 8 2 2 0 0 4 2 19 0 0 0 0 1 0
90.0 0 1 0 0 0 0 0 1 0 0 0 0 0 0
91.0 0 0 0 0 0 0 1 1 0 0 0 0 0 0
92.0 4 4 1 0 0 10 1 21 0 0 0 1 0 0
93.0 6 0 5 0 0 3 3 17 0 0 0 0 0 0
94.0 0 0 0 0 0 0 3 3 0 0 0 0 0 0
95.0 0 1 0 0 0 0 0 1 0 0 0 0 0 0
96.0 1 0 1 0 0 2 1 5 0 0 0 0 0 0
97.0 0 0 1 0 0 5 2 8 0 0 0 0 0 0
98.0 1 1 1 0 0 2 3 8 0 0 0 0 0 0
99.0 0 0 0 0 1 0 0 1 0 0 0 0 0 0
Arc 5: Vecna (172)
100.0 0 0 1 2 1 5 2 11 0 0 0 0 0 0
101.0 0 0 0 0 0 3 2 5 0 0 0 0 0 0
102.0 0 3 0 0 2 2 1 8 0 0 0 0 0 0
104.0 0 1 1 0 0 0 0 2 0 0 0 0 0 0
105.0 3 0 1 0 0 4 1 9 0 0 0 0 0 0
106.0 0 1 0 0 0 0 1 2 0 0 0 0 0 0
107.0 0 1 1 0 1 2 1 6 0 0 0 0 0 0
108.0 5 1 10 0 0 4 3 23 0 0 0 0 0 0
109.0 0 1 0 0 0 1 0 2 0 0 0 0 0 0
110.0 0 0 2 0 1 8 0 11 0 0 0 0 0 0
111.0 4 0 0 1 1 3 5 14 0 0 0 0 0 0
112.0 0 2 2 0 0 7 5 16 0 0 0 0 0 0
113.0 4 2 6 0 1 5 10 33 5 0 0 0 0 0
114.0 9 0 4 0 1 5 3 24 2 0 0 0 0 0
115.0 0 0 0 0 0 1 1 2 0 0 0 0 0 0

6. Conclusion and Future Examination

  In the face of great adversity, success feels especially good. To not only succeed, but at the same time demonstrate great ability through skill or luck or some combination of the two, creates a moment that people talk about for a long time - whether it happened in a tabletop game or out the sporting field. High rolls are often akin this kind of triumph, and that’s why the elicit such reactions. With the timeline of rolls over 30, fans can revisit those episodes where doing the impossible was the most frequent.

  Knowing when the highest rolls of the campaign occurred is fascinating, but what about before all the modifiers? Who has a tendency of rolling high on the dice themselves? Does Taliesen Jaffe really have a penchant for Natural 20’s? And if so, how much higher than the statistical average does he roll them? These will be some of the areas explored in the next examination of this data set.