Fetching the data

Data is grabbed from WarcraftLogs via their API. We get the top 1k parses per spec/encounter/difficulty.

Source is at https://github.com/cheald/warcraftlogs-parses

$ API_KEY=your_key_here ruby grabber.rb death_knight

Once we have the JSON files, we use a small script to boil them down into a CSV:

$ API_KEY=your_key_here ruby distill.rb death_knight

This results in the csv/death_knight.csv that we then consume in R.

Once in R, we load it up:

library(reshape2)
library(ggplot2)
library(dplyr)
library(broom)
data <- read.csv("csv/death_knight.csv")
data$date <- as.Date(data$date)
cutoff_date = "2016-10-15"

Filtering and plotting

Then we define a function that can be used to filter the dataset to a particular encounter and difficulty. It also performs outlier filtering - for each 5-ilvl bucket, for each spec, we compute the 95th percentile of DPS, then remove any parses from the dataset for that ilvl/spec above that cutoff.

get_encounter_data <- function(encounter_name, difficulty_name, dataset, cutoff=c(0.05, 0.95), bucket_size=1, min_ilvl=855, max_ilvl=875) {
  specs <- unique(dataset$spec)
  d <- filter(dataset, ilvl >= min_ilvl, ilvl <= max_ilvl, encounter == encounter_name, difficulty == difficulty_name, date >= cutoff_date)
  for(bucket in seq(800, 900, by=bucket_size)) {
    for(f_spec in specs) {
      q <- quantile(d[d$spec == f_spec & d$ilvl >= bucket & d$ilvl < bucket + bucket_size,]$dps, cutoff)
      if(!is.na(q[1])) {
        d <- filter(d, !(spec == f_spec & ilvl >= bucket & ilvl < bucket + bucket_size & (dps > q[2] | dps < q[1])))
      }
    }
  }
  return(d)
}

Plotting the data is straightforward. Here we plot both boxplots for each spec/ilvl bucket, as well as a smoothed moving average and a linear trendline.

plot_encounter <- function(encounter_name, difficulty_name, dataset=data, bucket_size=2, cutoff=c(0.05, 0.95)) {
  encounter_data <- get_encounter_data(encounter_name, difficulty_name, dataset=dataset, bucket_size=bucket_size, cutoff=cutoff)
  long_data <- melt(encounter_data, id.vars=c("spec", "ilvl"), measure.vars=c("dps"))
  p <- ggplot(data=long_data, aes(x=ilvl, y=value, group=spec, colour=spec)) +
    geom_boxplot(aes(group=interaction(spec, floor(ilvl/bucket_size) * bucket_size))) +
    geom_smooth() + # loess fit
    geom_smooth(linetype = "dashed", method = "lm", se = FALSE) + # linear fit
    labs(y="DPS", x="iLevel", title=paste("Performance:", encounter_name, difficulty_name))
  print(p)
  cat('\r\n\r\n')
  
  p <- ggplot(data=long_data, aes(x=ilvl, group=spec, colour=spec, fill=spec)) + geom_bar(position="dodge") +
    labs(y="Parses", x="iLevel", title=paste("Parses:", encounter_name, difficulty_name))
  print(p)
  cat('\r\n\r\n')
}

Results

plot_encounter("Nythendra", "Normal")

plot_encounter("Nythendra", "Heroic")

plot_encounter("Nythendra", "Mythic")

plot_encounter("Ursoc", "Normal")

plot_encounter("Ursoc", "Heroic")

plot_encounter("Ursoc", "Mythic")

plot_encounter("Elerethe_Renferal", "Normal")

plot_encounter("Elerethe_Renferal", "Heroic")

plot_encounter("Elerethe_Renferal", "Mythic")

plot_encounter("Il_gynoth__Heart_of_Corruption", "Normal")

plot_encounter("Il_gynoth__Heart_of_Corruption", "Heroic")

plot_encounter("Il_gynoth__Heart_of_Corruption", "Mythic")

plot_encounter("Dragons_of_Nightmare", "Normal")

plot_encounter("Dragons_of_Nightmare", "Heroic")

plot_encounter("Dragons_of_Nightmare", "Mythic")

plot_encounter("Cenarius", "Normal")

plot_encounter("Cenarius", "Heroic")

plot_encounter("Cenarius", "Mythic")

plot_encounter("Xavius", "Normal")

plot_encounter("Xavius", "Heroic")

plot_encounter("Xavius", "Mythic")

Scaling

So we have the raw (smoothed) damage progressions, but how about scaling?

plot_dps_gain <- function(encounter, difficulty, dataset=data, bucket_size=1, cutoff=c(0.05, 0.95)) {
  d <- dataset[dataset$encounter == encounter & dataset$difficulty == difficulty & dataset$ilvl <= 870 & dataset$date >= cutoff_date, ]
  d <- aggregate(d$dps, by=list(ilvl=d$ilvl, spec=d$spec), FUN=mean)
  d$dps <- d$x
  d <- d[order(d$ilvl),]
  d$diff <- ave(d$dps, factor(d$spec), FUN=function(x) c(NA, diff(x)))
  d <- filter(d, !is.na(diff))
  ggplot(data=d, aes(x=ilvl, y=diff, group=interaction(spec, encounter), colour=spec)) + geom_smooth(se=FALSE, fullrange=TRUE) +
    labs(y="DPS gain per tier", x="iLevel", title=paste(encounter, difficulty, sep = " ")) + coord_cartesian(ylim = c(0, 15000))
}
# Commented fights have too little data to produce anything useful
plot_dps_gain("Nythendra", "Normal")

plot_dps_gain("Nythendra", "Heroic")

plot_dps_gain("Nythendra", "Mythic")

plot_dps_gain("Ursoc", "Normal")

plot_dps_gain("Ursoc", "Heroic")

plot_dps_gain("Ursoc", "Mythic")

plot_dps_gain("Elerethe_Renferal", "Normal")

plot_dps_gain("Elerethe_Renferal", "Heroic")

plot_dps_gain("Elerethe_Renferal", "Mythic")

plot_dps_gain("Il_gynoth__Heart_of_Corruption", "Normal")

plot_dps_gain("Il_gynoth__Heart_of_Corruption", "Heroic")

# plot_dps_gain("il_gynoth__heart_of_corruption", "Mythic")
plot_dps_gain("Dragons_of_Nightmare", "Normal")

plot_dps_gain("Dragons_of_Nightmare", "Heroic")

# plot_dps_gain("Dragons_of_Nightmare", "Mythic")
plot_dps_gain("Cenarius", "Normal")

plot_dps_gain("Cenarius", "Heroic")

# plot_dps_gain("cenarius", "Mythic")
plot_dps_gain("Xavius", "Normal")

plot_dps_gain("Xavius", "Heroic")

# plot_dps_gain("xavius", "Mythic")

Finally, here’s a really blunt linear regression of each encounter, with the scaling factor (slope of the regression line) plotted:

d <- data
m <- d %>% group_by(spec, encounter) %>% do(tidy(lm(dps ~ ilvl, data=.)))
slopes <- m[m$term == "ilvl",]
ggplot(data=slopes, aes(x=encounter, y=estimate, group=interaction(spec, encounter), reorder=estimate, fill=spec)) + geom_bar(position="dodge", stat="identity") +
  labs(y="DPS gain/ilvl", x="Encounter", title="DPS scaling per ilvl: Overall")

LS0tDQp0aXRsZTogIkRlYXRoIEtuaWdodCBzcGVjIHNjYWxpbmciDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IA0KICAgIGZpZ19oZWlnaHQ6IDYNCiAgICBmaWdfd2lkdGg6IDE4DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KLS0tDQoNCiMjIEZldGNoaW5nIHRoZSBkYXRhDQoNCkRhdGEgaXMgZ3JhYmJlZCBmcm9tIFdhcmNyYWZ0TG9ncyB2aWEgdGhlaXIgQVBJLiBXZSBnZXQgdGhlIHRvcCAxayBwYXJzZXMgcGVyIHNwZWMvZW5jb3VudGVyL2RpZmZpY3VsdHkuDQoNClNvdXJjZSBpcyBhdCBodHRwczovL2dpdGh1Yi5jb20vY2hlYWxkL3dhcmNyYWZ0bG9ncy1wYXJzZXMNCg0KYGBgDQokIEFQSV9LRVk9eW91cl9rZXlfaGVyZSBydWJ5IGdyYWJiZXIucmIgZGVhdGhfa25pZ2h0DQpgYGANCg0KT25jZSB3ZSBoYXZlIHRoZSBKU09OIGZpbGVzLCB3ZSB1c2UgYSBzbWFsbCBzY3JpcHQgdG8gYm9pbCB0aGVtIGRvd24gaW50byBhIENTVjoNCg0KYGBgDQokIEFQSV9LRVk9eW91cl9rZXlfaGVyZSBydWJ5IGRpc3RpbGwucmIgZGVhdGhfa25pZ2h0DQpgYGANCg0KVGhpcyByZXN1bHRzIGluIHRoZSBgY3N2L2RlYXRoX2tuaWdodC5jc3ZgIHRoYXQgd2UgdGhlbiBjb25zdW1lIGluIFIuDQoNCk9uY2UgaW4gUiwgd2UgbG9hZCBpdCB1cDoNCg0KYGBge3J9DQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoYnJvb20pDQpkYXRhIDwtIHJlYWQuY3N2KCJjc3YvZGVhdGhfa25pZ2h0LmNzdiIpDQpkYXRhJGRhdGUgPC0gYXMuRGF0ZShkYXRhJGRhdGUpDQpjdXRvZmZfZGF0ZSA9ICIyMDE2LTEwLTE1Ig0KYGBgDQoNCiMjIEZpbHRlcmluZyBhbmQgcGxvdHRpbmcNCg0KVGhlbiB3ZSBkZWZpbmUgYSBmdW5jdGlvbiB0aGF0IGNhbiBiZSB1c2VkIHRvIGZpbHRlciB0aGUgZGF0YXNldCB0byBhIHBhcnRpY3VsYXIgZW5jb3VudGVyIGFuZCBkaWZmaWN1bHR5LiBJdCBhbHNvIHBlcmZvcm1zIG91dGxpZXIgZmlsdGVyaW5nIC0gZm9yIGVhY2ggNS1pbHZsIGJ1Y2tldCwgZm9yIGVhY2ggc3BlYywgd2UgY29tcHV0ZSB0aGUgOTV0aCBwZXJjZW50aWxlIG9mIERQUywgdGhlbiByZW1vdmUgYW55IHBhcnNlcyBmcm9tIHRoZSBkYXRhc2V0IGZvciB0aGF0IGlsdmwvc3BlYyBhYm92ZSB0aGF0IGN1dG9mZi4NCg0KYGBge3J9DQoNCmdldF9lbmNvdW50ZXJfZGF0YSA8LSBmdW5jdGlvbihlbmNvdW50ZXJfbmFtZSwgZGlmZmljdWx0eV9uYW1lLCBkYXRhc2V0LCBjdXRvZmY9YygwLjA1LCAwLjk1KSwgYnVja2V0X3NpemU9MSwgbWluX2lsdmw9ODU1LCBtYXhfaWx2bD04NzUpIHsNCiAgc3BlY3MgPC0gdW5pcXVlKGRhdGFzZXQkc3BlYykNCiAgZCA8LSBmaWx0ZXIoZGF0YXNldCwgaWx2bCA+PSBtaW5faWx2bCwgaWx2bCA8PSBtYXhfaWx2bCwgZW5jb3VudGVyID09IGVuY291bnRlcl9uYW1lLCBkaWZmaWN1bHR5ID09IGRpZmZpY3VsdHlfbmFtZSwgZGF0ZSA+PSBjdXRvZmZfZGF0ZSkNCiAgZm9yKGJ1Y2tldCBpbiBzZXEoODAwLCA5MDAsIGJ5PWJ1Y2tldF9zaXplKSkgew0KICAgIGZvcihmX3NwZWMgaW4gc3BlY3MpIHsNCiAgICAgIHEgPC0gcXVhbnRpbGUoZFtkJHNwZWMgPT0gZl9zcGVjICYgZCRpbHZsID49IGJ1Y2tldCAmIGQkaWx2bCA8IGJ1Y2tldCArIGJ1Y2tldF9zaXplLF0kZHBzLCBjdXRvZmYpDQogICAgICBpZighaXMubmEocVsxXSkpIHsNCiAgICAgICAgZCA8LSBmaWx0ZXIoZCwgIShzcGVjID09IGZfc3BlYyAmIGlsdmwgPj0gYnVja2V0ICYgaWx2bCA8IGJ1Y2tldCArIGJ1Y2tldF9zaXplICYgKGRwcyA+IHFbMl0gfCBkcHMgPCBxWzFdKSkpDQogICAgICB9DQogICAgfQ0KICB9DQogIHJldHVybihkKQ0KfQ0KYGBgDQoNClBsb3R0aW5nIHRoZSBkYXRhIGlzIHN0cmFpZ2h0Zm9yd2FyZC4gSGVyZSB3ZSBwbG90IGJvdGggYm94cGxvdHMgZm9yIGVhY2ggc3BlYy9pbHZsIGJ1Y2tldCwgYXMgd2VsbCBhcyBhIHNtb290aGVkIG1vdmluZyBhdmVyYWdlIGFuZCBhIGxpbmVhciB0cmVuZGxpbmUuDQoNCmBgYHtyfQ0KcGxvdF9lbmNvdW50ZXIgPC0gZnVuY3Rpb24oZW5jb3VudGVyX25hbWUsIGRpZmZpY3VsdHlfbmFtZSwgZGF0YXNldD1kYXRhLCBidWNrZXRfc2l6ZT0yLCBjdXRvZmY9YygwLjA1LCAwLjk1KSkgew0KICBlbmNvdW50ZXJfZGF0YSA8LSBnZXRfZW5jb3VudGVyX2RhdGEoZW5jb3VudGVyX25hbWUsIGRpZmZpY3VsdHlfbmFtZSwgZGF0YXNldD1kYXRhc2V0LCBidWNrZXRfc2l6ZT1idWNrZXRfc2l6ZSwgY3V0b2ZmPWN1dG9mZikNCg0KICBsb25nX2RhdGEgPC0gbWVsdChlbmNvdW50ZXJfZGF0YSwgaWQudmFycz1jKCJzcGVjIiwgImlsdmwiKSwgbWVhc3VyZS52YXJzPWMoImRwcyIpKQ0KDQogIHAgPC0gZ2dwbG90KGRhdGE9bG9uZ19kYXRhLCBhZXMoeD1pbHZsLCB5PXZhbHVlLCBncm91cD1zcGVjLCBjb2xvdXI9c3BlYykpICsNCiAgICBnZW9tX2JveHBsb3QoYWVzKGdyb3VwPWludGVyYWN0aW9uKHNwZWMsIGZsb29yKGlsdmwvYnVja2V0X3NpemUpICogYnVja2V0X3NpemUpKSkgKw0KICAgIGdlb21fc21vb3RoKCkgKyAjIGxvZXNzIGZpdA0KICAgIGdlb21fc21vb3RoKGxpbmV0eXBlID0gImRhc2hlZCIsIG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UpICsgIyBsaW5lYXIgZml0DQogICAgbGFicyh5PSJEUFMiLCB4PSJpTGV2ZWwiLCB0aXRsZT1wYXN0ZSgiUGVyZm9ybWFuY2U6IiwgZW5jb3VudGVyX25hbWUsIGRpZmZpY3VsdHlfbmFtZSkpDQogIHByaW50KHApDQogIGNhdCgnXHJcblxyXG4nKQ0KICANCiAgcCA8LSBnZ3Bsb3QoZGF0YT1sb25nX2RhdGEsIGFlcyh4PWlsdmwsIGdyb3VwPXNwZWMsIGNvbG91cj1zcGVjLCBmaWxsPXNwZWMpKSArIGdlb21fYmFyKHBvc2l0aW9uPSJkb2RnZSIpICsNCiAgICBsYWJzKHk9IlBhcnNlcyIsIHg9ImlMZXZlbCIsIHRpdGxlPXBhc3RlKCJQYXJzZXM6IiwgZW5jb3VudGVyX25hbWUsIGRpZmZpY3VsdHlfbmFtZSkpDQogIHByaW50KHApDQogIGNhdCgnXHJcblxyXG4nKQ0KfQ0KYGBgDQoNCiMjIFJlc3VsdHMNCg0KDQpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTZ9DQoNCnBsb3RfZW5jb3VudGVyKCJOeXRoZW5kcmEiLCAiTm9ybWFsIikNCnBsb3RfZW5jb3VudGVyKCJOeXRoZW5kcmEiLCAiSGVyb2ljIikNCnBsb3RfZW5jb3VudGVyKCJOeXRoZW5kcmEiLCAiTXl0aGljIikNCg0KcGxvdF9lbmNvdW50ZXIoIlVyc29jIiwgIk5vcm1hbCIpDQpwbG90X2VuY291bnRlcigiVXJzb2MiLCAiSGVyb2ljIikNCnBsb3RfZW5jb3VudGVyKCJVcnNvYyIsICJNeXRoaWMiKQ0KDQpwbG90X2VuY291bnRlcigiRWxlcmV0aGVfUmVuZmVyYWwiLCAiTm9ybWFsIikNCnBsb3RfZW5jb3VudGVyKCJFbGVyZXRoZV9SZW5mZXJhbCIsICJIZXJvaWMiKQ0KcGxvdF9lbmNvdW50ZXIoIkVsZXJldGhlX1JlbmZlcmFsIiwgIk15dGhpYyIpDQoNCnBsb3RfZW5jb3VudGVyKCJJbF9neW5vdGhfX0hlYXJ0X29mX0NvcnJ1cHRpb24iLCAiTm9ybWFsIikNCnBsb3RfZW5jb3VudGVyKCJJbF9neW5vdGhfX0hlYXJ0X29mX0NvcnJ1cHRpb24iLCAiSGVyb2ljIikNCnBsb3RfZW5jb3VudGVyKCJJbF9neW5vdGhfX0hlYXJ0X29mX0NvcnJ1cHRpb24iLCAiTXl0aGljIikNCg0KcGxvdF9lbmNvdW50ZXIoIkRyYWdvbnNfb2ZfTmlnaHRtYXJlIiwgIk5vcm1hbCIpDQpwbG90X2VuY291bnRlcigiRHJhZ29uc19vZl9OaWdodG1hcmUiLCAiSGVyb2ljIikNCnBsb3RfZW5jb3VudGVyKCJEcmFnb25zX29mX05pZ2h0bWFyZSIsICJNeXRoaWMiKQ0KDQpwbG90X2VuY291bnRlcigiQ2VuYXJpdXMiLCAiTm9ybWFsIikNCnBsb3RfZW5jb3VudGVyKCJDZW5hcml1cyIsICJIZXJvaWMiKQ0KcGxvdF9lbmNvdW50ZXIoIkNlbmFyaXVzIiwgIk15dGhpYyIpDQoNCnBsb3RfZW5jb3VudGVyKCJYYXZpdXMiLCAiTm9ybWFsIikNCnBsb3RfZW5jb3VudGVyKCJYYXZpdXMiLCAiSGVyb2ljIikNCnBsb3RfZW5jb3VudGVyKCJYYXZpdXMiLCAiTXl0aGljIikNCmBgYA0KDQojIyMgU2NhbGluZw0KDQpTbyB3ZSBoYXZlIHRoZSByYXcgKHNtb290aGVkKSBkYW1hZ2UgcHJvZ3Jlc3Npb25zLCBidXQgaG93IGFib3V0IHNjYWxpbmc/IA0KDQpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTZ9DQpwbG90X2Rwc19nYWluIDwtIGZ1bmN0aW9uKGVuY291bnRlciwgZGlmZmljdWx0eSwgZGF0YXNldD1kYXRhLCBidWNrZXRfc2l6ZT0xLCBjdXRvZmY9YygwLjA1LCAwLjk1KSkgew0KICBkIDwtIGRhdGFzZXRbZGF0YXNldCRlbmNvdW50ZXIgPT0gZW5jb3VudGVyICYgZGF0YXNldCRkaWZmaWN1bHR5ID09IGRpZmZpY3VsdHkgJiBkYXRhc2V0JGlsdmwgPD0gODcwICYgZGF0YXNldCRkYXRlID49IGN1dG9mZl9kYXRlLCBdDQogIGQgPC0gYWdncmVnYXRlKGQkZHBzLCBieT1saXN0KGlsdmw9ZCRpbHZsLCBzcGVjPWQkc3BlYyksIEZVTj1tZWFuKQ0KICBkJGRwcyA8LSBkJHgNCiAgZCA8LSBkW29yZGVyKGQkaWx2bCksXQ0KICBkJGRpZmYgPC0gYXZlKGQkZHBzLCBmYWN0b3IoZCRzcGVjKSwgRlVOPWZ1bmN0aW9uKHgpIGMoTkEsIGRpZmYoeCkpKQ0KICBkIDwtIGZpbHRlcihkLCAhaXMubmEoZGlmZikpDQogIGdncGxvdChkYXRhPWQsIGFlcyh4PWlsdmwsIHk9ZGlmZiwgZ3JvdXA9aW50ZXJhY3Rpb24oc3BlYywgZW5jb3VudGVyKSwgY29sb3VyPXNwZWMpKSArIGdlb21fc21vb3RoKHNlPUZBTFNFLCBmdWxscmFuZ2U9VFJVRSkgKw0KICAgIGxhYnMoeT0iRFBTIGdhaW4gcGVyIHRpZXIiLCB4PSJpTGV2ZWwiLCB0aXRsZT1wYXN0ZShlbmNvdW50ZXIsIGRpZmZpY3VsdHksIHNlcCA9ICIgIikpICsgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDAsIDE1MDAwKSkNCn0NCg0KIyBDb21tZW50ZWQgZmlnaHRzIGhhdmUgdG9vIGxpdHRsZSBkYXRhIHRvIHByb2R1Y2UgYW55dGhpbmcgdXNlZnVsDQoNCnBsb3RfZHBzX2dhaW4oIk55dGhlbmRyYSIsICJOb3JtYWwiKQ0KcGxvdF9kcHNfZ2FpbigiTnl0aGVuZHJhIiwgIkhlcm9pYyIpDQpwbG90X2Rwc19nYWluKCJOeXRoZW5kcmEiLCAiTXl0aGljIikNCg0KcGxvdF9kcHNfZ2FpbigiVXJzb2MiLCAiTm9ybWFsIikNCnBsb3RfZHBzX2dhaW4oIlVyc29jIiwgIkhlcm9pYyIpDQpwbG90X2Rwc19nYWluKCJVcnNvYyIsICJNeXRoaWMiKQ0KDQpwbG90X2Rwc19nYWluKCJFbGVyZXRoZV9SZW5mZXJhbCIsICJOb3JtYWwiKQ0KcGxvdF9kcHNfZ2FpbigiRWxlcmV0aGVfUmVuZmVyYWwiLCAiSGVyb2ljIikNCnBsb3RfZHBzX2dhaW4oIkVsZXJldGhlX1JlbmZlcmFsIiwgIk15dGhpYyIpDQoNCnBsb3RfZHBzX2dhaW4oIklsX2d5bm90aF9fSGVhcnRfb2ZfQ29ycnVwdGlvbiIsICJOb3JtYWwiKQ0KcGxvdF9kcHNfZ2FpbigiSWxfZ3lub3RoX19IZWFydF9vZl9Db3JydXB0aW9uIiwgIkhlcm9pYyIpDQojIHBsb3RfZHBzX2dhaW4oImlsX2d5bm90aF9faGVhcnRfb2ZfY29ycnVwdGlvbiIsICJNeXRoaWMiKQ0KDQpwbG90X2Rwc19nYWluKCJEcmFnb25zX29mX05pZ2h0bWFyZSIsICJOb3JtYWwiKQ0KcGxvdF9kcHNfZ2FpbigiRHJhZ29uc19vZl9OaWdodG1hcmUiLCAiSGVyb2ljIikNCiMgcGxvdF9kcHNfZ2FpbigiRHJhZ29uc19vZl9OaWdodG1hcmUiLCAiTXl0aGljIikNCg0KcGxvdF9kcHNfZ2FpbigiQ2VuYXJpdXMiLCAiTm9ybWFsIikNCnBsb3RfZHBzX2dhaW4oIkNlbmFyaXVzIiwgIkhlcm9pYyIpDQojIHBsb3RfZHBzX2dhaW4oImNlbmFyaXVzIiwgIk15dGhpYyIpDQoNCnBsb3RfZHBzX2dhaW4oIlhhdml1cyIsICJOb3JtYWwiKQ0KcGxvdF9kcHNfZ2FpbigiWGF2aXVzIiwgIkhlcm9pYyIpDQojIHBsb3RfZHBzX2dhaW4oInhhdml1cyIsICJNeXRoaWMiKQ0KDQpgYGANCg0KRmluYWxseSwgaGVyZSdzIGEgcmVhbGx5IGJsdW50IGxpbmVhciByZWdyZXNzaW9uIG9mIGVhY2ggZW5jb3VudGVyLCB3aXRoIHRoZSBzY2FsaW5nIGZhY3RvciAoc2xvcGUgb2YgdGhlIHJlZ3Jlc3Npb24gbGluZSkgcGxvdHRlZDoNCg0KYGBge3IsIGZpZy53aWR0aD0xNn0NCmQgPC0gZGF0YQ0KbSA8LSBkICU+JSBncm91cF9ieShzcGVjLCBlbmNvdW50ZXIpICU+JSBkbyh0aWR5KGxtKGRwcyB+IGlsdmwsIGRhdGE9LikpKQ0Kc2xvcGVzIDwtIG1bbSR0ZXJtID09ICJpbHZsIixdDQpnZ3Bsb3QoZGF0YT1zbG9wZXMsIGFlcyh4PWVuY291bnRlciwgeT1lc3RpbWF0ZSwgZ3JvdXA9aW50ZXJhY3Rpb24oc3BlYywgZW5jb3VudGVyKSwgcmVvcmRlcj1lc3RpbWF0ZSwgZmlsbD1zcGVjKSkgKyBnZW9tX2Jhcihwb3NpdGlvbj0iZG9kZ2UiLCBzdGF0PSJpZGVudGl0eSIpICsNCiAgbGFicyh5PSJEUFMgZ2Fpbi9pbHZsIiwgeD0iRW5jb3VudGVyIiwgdGl0bGU9IkRQUyBzY2FsaW5nIHBlciBpbHZsOiBPdmVyYWxsIikNCmBgYA0K