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",]
slopes$spec <- factor(slopes$spec, levels = slopes$spec[order(slopes$estimate)])
duplicated levels in factors are deprecated
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")
LS0tDQp0aXRsZTogIk1hZ2Ugc3BlYyBzY2FsaW5nIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiANCiAgICBmaWdfaGVpZ2h0OiA2DQogICAgZmlnX3dpZHRoOiAxOA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQojIyBGZXRjaGluZyB0aGUgZGF0YQ0KDQpEYXRhIGlzIGdyYWJiZWQgZnJvbSBXYXJjcmFmdExvZ3MgdmlhIHRoZWlyIEFQSS4gV2UgZ2V0IHRoZSB0b3AgMWsgcGFyc2VzIHBlciBzcGVjL2VuY291bnRlci9kaWZmaWN1bHR5Lg0KDQpTb3VyY2UgaXMgYXQgaHR0cHM6Ly9naXRodWIuY29tL2NoZWFsZC93YXJjcmFmdGxvZ3MtcGFyc2VzDQoNCmBgYA0KJCBBUElfS0VZPXlvdXJfa2V5X2hlcmUgcnVieSBncmFiYmVyLnJiIHNoYW1hbg0KYGBgDQoNCk9uY2Ugd2UgaGF2ZSB0aGUgSlNPTiBmaWxlcywgd2UgdXNlIGEgc21hbGwgc2NyaXB0IHRvIGJvaWwgdGhlbSBkb3duIGludG8gYSBDU1Y6DQoNCmBgYA0KJCBBUElfS0VZPXlvdXJfa2V5X2hlcmUgcnVieSBkaXN0aWxsLnJiIG1hZ2UNCmBgYA0KDQpUaGlzIHJlc3VsdHMgaW4gdGhlIGBjc3YvbWFnZS5jc3ZgIHRoYXQgd2UgdGhlbiBjb25zdW1lIGluIFIuDQoNCk9uY2UgaW4gUiwgd2UgbG9hZCBpdCB1cDoNCg0KYGBge3J9DQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoYnJvb20pDQpkYXRhIDwtIHJlYWQuY3N2KCJjc3YvbWFnZS5jc3YiKQ0KZGF0YSRkYXRlIDwtIGFzLkRhdGUoZGF0YSRkYXRlKQ0KY3V0b2ZmX2RhdGUgPSAiMjAxNi0xMC0xNSINCmBgYA0KDQojIyBGaWx0ZXJpbmcgYW5kIHBsb3R0aW5nDQoNClRoZW4gd2UgZGVmaW5lIGEgZnVuY3Rpb24gdGhhdCBjYW4gYmUgdXNlZCB0byBmaWx0ZXIgdGhlIGRhdGFzZXQgdG8gYSBwYXJ0aWN1bGFyIGVuY291bnRlciBhbmQgZGlmZmljdWx0eS4gSXQgYWxzbyBwZXJmb3JtcyBvdXRsaWVyIGZpbHRlcmluZyAtIGZvciBlYWNoIDUtaWx2bCBidWNrZXQsIGZvciBlYWNoIHNwZWMsIHdlIGNvbXB1dGUgdGhlIDk1dGggcGVyY2VudGlsZSBvZiBEUFMsIHRoZW4gcmVtb3ZlIGFueSBwYXJzZXMgZnJvbSB0aGUgZGF0YXNldCBmb3IgdGhhdCBpbHZsL3NwZWMgYWJvdmUgdGhhdCBjdXRvZmYuDQoNCmBgYHtyfQ0KDQpnZXRfZW5jb3VudGVyX2RhdGEgPC0gZnVuY3Rpb24oZW5jb3VudGVyX25hbWUsIGRpZmZpY3VsdHlfbmFtZSwgZGF0YXNldCwgY3V0b2ZmPWMoMC4wNSwgMC45NSksIGJ1Y2tldF9zaXplPTEsIG1pbl9pbHZsPTg1NSwgbWF4X2lsdmw9ODc1KSB7DQogIHNwZWNzIDwtIHVuaXF1ZShkYXRhc2V0JHNwZWMpDQogIGQgPC0gZmlsdGVyKGRhdGFzZXQsIGlsdmwgPj0gbWluX2lsdmwsIGlsdmwgPD0gbWF4X2lsdmwsIGVuY291bnRlciA9PSBlbmNvdW50ZXJfbmFtZSwgZGlmZmljdWx0eSA9PSBkaWZmaWN1bHR5X25hbWUsIGRhdGUgPj0gY3V0b2ZmX2RhdGUpDQogIGZvcihidWNrZXQgaW4gc2VxKDgwMCwgOTAwLCBieT1idWNrZXRfc2l6ZSkpIHsNCiAgICBmb3IoZl9zcGVjIGluIHNwZWNzKSB7DQogICAgICBxIDwtIHF1YW50aWxlKGRbZCRzcGVjID09IGZfc3BlYyAmIGQkaWx2bCA+PSBidWNrZXQgJiBkJGlsdmwgPCBidWNrZXQgKyBidWNrZXRfc2l6ZSxdJGRwcywgY3V0b2ZmKQ0KICAgICAgaWYoIWlzLm5hKHFbMV0pKSB7DQogICAgICAgIGQgPC0gZmlsdGVyKGQsICEoc3BlYyA9PSBmX3NwZWMgJiBpbHZsID49IGJ1Y2tldCAmIGlsdmwgPCBidWNrZXQgKyBidWNrZXRfc2l6ZSAmIChkcHMgPiBxWzJdIHwgZHBzIDwgcVsxXSkpKQ0KICAgICAgfQ0KICAgIH0NCiAgfQ0KICByZXR1cm4oZCkNCn0NCmBgYA0KDQpQbG90dGluZyB0aGUgZGF0YSBpcyBzdHJhaWdodGZvcndhcmQuIEhlcmUgd2UgcGxvdCBib3RoIGJveHBsb3RzIGZvciBlYWNoIHNwZWMvaWx2bCBidWNrZXQsIGFzIHdlbGwgYXMgYSBzbW9vdGhlZCBtb3ZpbmcgYXZlcmFnZSBhbmQgYSBsaW5lYXIgdHJlbmRsaW5lLg0KDQpgYGB7cn0NCnBsb3RfZW5jb3VudGVyIDwtIGZ1bmN0aW9uKGVuY291bnRlcl9uYW1lLCBkaWZmaWN1bHR5X25hbWUsIGRhdGFzZXQ9ZGF0YSwgYnVja2V0X3NpemU9MiwgY3V0b2ZmPWMoMC4wNSwgMC45NSkpIHsNCiAgZW5jb3VudGVyX2RhdGEgPC0gZ2V0X2VuY291bnRlcl9kYXRhKGVuY291bnRlcl9uYW1lLCBkaWZmaWN1bHR5X25hbWUsIGRhdGFzZXQ9ZGF0YXNldCwgYnVja2V0X3NpemU9YnVja2V0X3NpemUsIGN1dG9mZj1jdXRvZmYpDQoNCiAgbG9uZ19kYXRhIDwtIG1lbHQoZW5jb3VudGVyX2RhdGEsIGlkLnZhcnM9Yygic3BlYyIsICJpbHZsIiksIG1lYXN1cmUudmFycz1jKCJkcHMiKSkNCg0KICBwIDwtIGdncGxvdChkYXRhPWxvbmdfZGF0YSwgYWVzKHg9aWx2bCwgeT12YWx1ZSwgZ3JvdXA9c3BlYywgY29sb3VyPXNwZWMpKSArDQogICAgZ2VvbV9ib3hwbG90KGFlcyhncm91cD1pbnRlcmFjdGlvbihzcGVjLCBmbG9vcihpbHZsL2J1Y2tldF9zaXplKSAqIGJ1Y2tldF9zaXplKSkpICsNCiAgICBnZW9tX3Ntb290aCgpICsgIyBsb2VzcyBmaXQNCiAgICBnZW9tX3Ntb290aChsaW5ldHlwZSA9ICJkYXNoZWQiLCBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArICMgbGluZWFyIGZpdA0KICAgIGxhYnMoeT0iRFBTIiwgeD0iaUxldmVsIiwgdGl0bGU9cGFzdGUoIlBlcmZvcm1hbmNlOiIsIGVuY291bnRlcl9uYW1lLCBkaWZmaWN1bHR5X25hbWUpKQ0KICBwcmludChwKQ0KICBjYXQoJ1xyXG5cclxuJykNCiAgDQogIHAgPC0gZ2dwbG90KGRhdGE9bG9uZ19kYXRhLCBhZXMoeD1pbHZsLCBncm91cD1zcGVjLCBjb2xvdXI9c3BlYywgZmlsbD1zcGVjKSkgKyBnZW9tX2Jhcihwb3NpdGlvbj0iZG9kZ2UiKSArDQogICAgbGFicyh5PSJQYXJzZXMiLCB4PSJpTGV2ZWwiLCB0aXRsZT1wYXN0ZSgiUGFyc2VzOiIsIGVuY291bnRlcl9uYW1lLCBkaWZmaWN1bHR5X25hbWUpKQ0KICBwcmludChwKQ0KICBjYXQoJ1xyXG5cclxuJykNCn0NCmBgYA0KDQojIyBSZXN1bHRzDQoNCg0KYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02fQ0KDQpwbG90X2VuY291bnRlcigibnl0aGVuZHJhIiwgIk5vcm1hbCIpDQpwbG90X2VuY291bnRlcigibnl0aGVuZHJhIiwgIkhlcm9pYyIpDQpwbG90X2VuY291bnRlcigibnl0aGVuZHJhIiwgIk15dGhpYyIpDQoNCnBsb3RfZW5jb3VudGVyKCJ1cnNvYyIsICJOb3JtYWwiKQ0KcGxvdF9lbmNvdW50ZXIoInVyc29jIiwgIkhlcm9pYyIpDQpwbG90X2VuY291bnRlcigidXJzb2MiLCAiTXl0aGljIikNCg0KcGxvdF9lbmNvdW50ZXIoImVsZXJldGhlX3JlbmZlcmFsIiwgIk5vcm1hbCIpDQpwbG90X2VuY291bnRlcigiZWxlcmV0aGVfcmVuZmVyYWwiLCAiSGVyb2ljIikNCnBsb3RfZW5jb3VudGVyKCJlbGVyZXRoZV9yZW5mZXJhbCIsICJNeXRoaWMiKQ0KDQpwbG90X2VuY291bnRlcigiaWxfZ3lub3RoX19oZWFydF9vZl9jb3JydXB0aW9uIiwgIk5vcm1hbCIpDQpwbG90X2VuY291bnRlcigiaWxfZ3lub3RoX19oZWFydF9vZl9jb3JydXB0aW9uIiwgIkhlcm9pYyIpDQpwbG90X2VuY291bnRlcigiaWxfZ3lub3RoX19oZWFydF9vZl9jb3JydXB0aW9uIiwgIk15dGhpYyIpDQoNCnBsb3RfZW5jb3VudGVyKCJkcmFnb25zX29mX25pZ2h0bWFyZSIsICJOb3JtYWwiKQ0KcGxvdF9lbmNvdW50ZXIoImRyYWdvbnNfb2ZfbmlnaHRtYXJlIiwgIkhlcm9pYyIpDQpwbG90X2VuY291bnRlcigiZHJhZ29uc19vZl9uaWdodG1hcmUiLCAiTXl0aGljIikNCg0KcGxvdF9lbmNvdW50ZXIoImNlbmFyaXVzIiwgIk5vcm1hbCIpDQpwbG90X2VuY291bnRlcigiY2VuYXJpdXMiLCAiSGVyb2ljIikNCnBsb3RfZW5jb3VudGVyKCJjZW5hcml1cyIsICJNeXRoaWMiKQ0KDQpwbG90X2VuY291bnRlcigieGF2aXVzIiwgIk5vcm1hbCIpDQpwbG90X2VuY291bnRlcigieGF2aXVzIiwgIkhlcm9pYyIpDQpwbG90X2VuY291bnRlcigieGF2aXVzIiwgIk15dGhpYyIpDQpgYGANCg0KIyMjIFNjYWxpbmcNCg0KU28gd2UgaGF2ZSB0aGUgcmF3IChzbW9vdGhlZCkgZGFtYWdlIHByb2dyZXNzaW9ucywgYnV0IGhvdyBhYm91dCBzY2FsaW5nPyANCg0KYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02fQ0KcGxvdF9kcHNfZ2FpbiA8LSBmdW5jdGlvbihlbmNvdW50ZXIsIGRpZmZpY3VsdHksIGRhdGFzZXQ9ZGF0YSwgYnVja2V0X3NpemU9MSwgY3V0b2ZmPWMoMC4wNSwgMC45NSkpIHsNCiAgZCA8LSBkYXRhc2V0W2RhdGFzZXQkZW5jb3VudGVyID09IGVuY291bnRlciAmIGRhdGFzZXQkZGlmZmljdWx0eSA9PSBkaWZmaWN1bHR5ICYgZGF0YXNldCRpbHZsIDw9IDg3MCAmIGRhdGFzZXQkZGF0ZSA+PSBjdXRvZmZfZGF0ZSwgXQ0KICBkIDwtIGFnZ3JlZ2F0ZShkJGRwcywgYnk9bGlzdChpbHZsPWQkaWx2bCwgc3BlYz1kJHNwZWMpLCBGVU49bWVhbikNCiAgZCRkcHMgPC0gZCR4DQogIGQgPC0gZFtvcmRlcihkJGlsdmwpLF0NCiAgZCRkaWZmIDwtIGF2ZShkJGRwcywgZmFjdG9yKGQkc3BlYyksIEZVTj1mdW5jdGlvbih4KSBjKE5BLCBkaWZmKHgpKSkNCiAgZCA8LSBmaWx0ZXIoZCwgIWlzLm5hKGRpZmYpKQ0KICBnZ3Bsb3QoZGF0YT1kLCBhZXMoeD1pbHZsLCB5PWRpZmYsIGdyb3VwPWludGVyYWN0aW9uKHNwZWMsIGVuY291bnRlciksIGNvbG91cj1zcGVjKSkgKyBnZW9tX3Ntb290aChzZT1GQUxTRSwgZnVsbHJhbmdlPVRSVUUpICsNCiAgICBsYWJzKHk9IkRQUyBnYWluIHBlciB0aWVyIiwgeD0iaUxldmVsIiwgdGl0bGU9cGFzdGUoZW5jb3VudGVyLCBkaWZmaWN1bHR5LCBzZXAgPSAiICIpKSArIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCAxNTAwMCkpDQp9DQoNCiMgQ29tbWVudGVkIGZpZ2h0cyBoYXZlIHRvbyBsaXR0bGUgZGF0YSB0byBwcm9kdWNlIGFueXRoaW5nIHVzZWZ1bA0KDQpwbG90X2Rwc19nYWluKCJueXRoZW5kcmEiLCAiTm9ybWFsIikNCnBsb3RfZHBzX2dhaW4oIm55dGhlbmRyYSIsICJIZXJvaWMiKQ0KcGxvdF9kcHNfZ2Fpbigibnl0aGVuZHJhIiwgIk15dGhpYyIpDQoNCnBsb3RfZHBzX2dhaW4oInVyc29jIiwgIk5vcm1hbCIpDQpwbG90X2Rwc19nYWluKCJ1cnNvYyIsICJIZXJvaWMiKQ0KcGxvdF9kcHNfZ2FpbigidXJzb2MiLCAiTXl0aGljIikNCg0KcGxvdF9kcHNfZ2FpbigiZWxlcmV0aGVfcmVuZmVyYWwiLCAiTm9ybWFsIikNCnBsb3RfZHBzX2dhaW4oImVsZXJldGhlX3JlbmZlcmFsIiwgIkhlcm9pYyIpDQpwbG90X2Rwc19nYWluKCJlbGVyZXRoZV9yZW5mZXJhbCIsICJNeXRoaWMiKQ0KDQpwbG90X2Rwc19nYWluKCJpbF9neW5vdGhfX2hlYXJ0X29mX2NvcnJ1cHRpb24iLCAiTm9ybWFsIikNCnBsb3RfZHBzX2dhaW4oImlsX2d5bm90aF9faGVhcnRfb2ZfY29ycnVwdGlvbiIsICJIZXJvaWMiKQ0KIyBwbG90X2Rwc19nYWluKCJpbF9neW5vdGhfX2hlYXJ0X29mX2NvcnJ1cHRpb24iLCAiTXl0aGljIikNCg0KcGxvdF9kcHNfZ2FpbigiZHJhZ29uc19vZl9uaWdodG1hcmUiLCAiTm9ybWFsIikNCnBsb3RfZHBzX2dhaW4oImRyYWdvbnNfb2ZfbmlnaHRtYXJlIiwgIkhlcm9pYyIpDQojIHBsb3RfZHBzX2dhaW4oImRyYWdvbnNfb2ZfbmlnaHRtYXJlIiwgIk15dGhpYyIpDQoNCnBsb3RfZHBzX2dhaW4oImNlbmFyaXVzIiwgIk5vcm1hbCIpDQpwbG90X2Rwc19nYWluKCJjZW5hcml1cyIsICJIZXJvaWMiKQ0KIyBwbG90X2Rwc19nYWluKCJjZW5hcml1cyIsICJNeXRoaWMiKQ0KDQpwbG90X2Rwc19nYWluKCJ4YXZpdXMiLCAiTm9ybWFsIikNCnBsb3RfZHBzX2dhaW4oInhhdml1cyIsICJIZXJvaWMiKQ0KIyBwbG90X2Rwc19nYWluKCJ4YXZpdXMiLCAiTXl0aGljIikNCg0KYGBgDQoNCkZpbmFsbHksIGhlcmUncyBhIHJlYWxseSBibHVudCBsaW5lYXIgcmVncmVzc2lvbiBvZiBlYWNoIGVuY291bnRlciwgd2l0aCB0aGUgc2NhbGluZyBmYWN0b3IgKHNsb3BlIG9mIHRoZSByZWdyZXNzaW9uIGxpbmUpIHBsb3R0ZWQ6DQoNCmBgYHtyLCBmaWcud2lkdGg9MTZ9DQpkIDwtIGRhdGENCm0gPC0gZCAlPiUgZ3JvdXBfYnkoc3BlYywgZW5jb3VudGVyKSAlPiUgZG8odGlkeShsbShkcHMgfiBpbHZsLCBkYXRhPS4pKSkNCnNsb3BlcyA8LSBtW20kdGVybSA9PSAiaWx2bCIsXQ0Kc2xvcGVzJHNwZWMgPC0gZmFjdG9yKHNsb3BlcyRzcGVjLCBsZXZlbHMgPSBzbG9wZXMkc3BlY1tvcmRlcihzbG9wZXMkZXN0aW1hdGUpXSkNCmdncGxvdChkYXRhPXNsb3BlcywgYWVzKHg9ZW5jb3VudGVyLCB5PWVzdGltYXRlLCBncm91cD1pbnRlcmFjdGlvbihzcGVjLCBlbmNvdW50ZXIpLCByZW9yZGVyPWVzdGltYXRlLCBmaWxsPXNwZWMpKSArIGdlb21fYmFyKHBvc2l0aW9uPSJkb2RnZSIsIHN0YXQ9ImlkZW50aXR5IikgKw0KICBsYWJzKHk9IkRQUyBnYWluL2lsdmwiLCB4PSJFbmNvdW50ZXIiLCB0aXRsZT0iRFBTIHNjYWxpbmcgcGVyIGlsdmw6IE92ZXJhbGwiKQ0KDQpgYGANCg==