As with many people across the internet, I play fantasy sports - mostly fantasy football. Friends from college started a league back in the early 2000s. Multiple teams and managers came and went, but in 2010 we started a keeper-league - the Join or Die Fantasy Football League - and solidified a consistent group of 10 managers. The consistency in management and league settings (settings weren’t solidified until 2011) presents a great opportunity to look at how individual managers preform against each other over multiple years, and even gather enough data to simulate seasons and compare the results to actual season rankings.

Here, I’ve coded script that first scrapes data from my fantasy football league, and then analyzes the data in various ways. I look at how trading and player moves affect winning percentage and total points scored in a season, as well as test for differences among managers. Then, I simulate fantasy football seasons based purely on historical performance of the managers (pairwise winning probabilities). I use the league’s initial 3 years of data (2011-2013) to simulate 500 full seasons (regular season and playoffs), and then compare the average end-of-season manager rankings to actual average rankings from 2013-2016.

So to begin, I scrape the data using the ‘rvest’ package. First I need to log into my yahoo fantasy football account and manually find the League IDs that correspond to each year of play - they are a little more difficult to scrape on their own, and make the rest of the process much easier.

# load the 'rvest' package which helps scrape data from urls
library(rvest)
# Log into your yahoo fantasy football page
# Lookup years and  corrosponding league numbers to make a table
# from which to lookup the appropriate info to edit urls later
league.lookup<-data.frame(Years=c(2010:2016),LeagueID=c(30634,651422,37137,100418,16517,1333,59799))
# Enter the number of players in the league
nplayers<-10
# Now enter the base url for archived yahoo fantasy football pages and records, to be used later when building urls to scrape from
ffbsite.1<-"https://football.fantasysports.yahoo.com/archive/nfl/"

Now I can scrap the data and put it all in a table. To determine the correct CSS selector for each item I’d like to scrape, I use a handy online tool called “Selector Gadget” (http://selectorgadget.com).

# Now to scrape the data from yahoo fantasy sports. We setup two for-loops that iterate over our years and league numbers gathered before, scraping information about how each manager managed their team (i.e., number of trades, and moves), and how they faired against each-other team. Data is stored in dateframes along the way. The selection criteria for each scrape was found using SelectorGadget.
# Start the first for-loop to lookup manager info. We don't use the league's first year - 2010 - because the season format was different than the rest (played with divisions).
for (i in 2:nrow(league.lookup)){
# Create a url to direct to the correct year and league id using the lookup table from before
ffbsite.mananger<-read_html(paste0(ffbsite.1,league.lookup[i,1],"/",league.lookup[i,2],"/teams"))
ffbsite.mananger2<-read_html(paste0(ffbsite.1,league.lookup[i,1],"/",league.lookup[i,2],"?lhst=stand#lhststand"))
# Get the team name
  team.names<-ffbsite.mananger %>% 
    html_nodes("#teams .first a") %>%
    html_text()
# Get team rank, but reorder them based on the team names
  team.rank<-ffbsite.mananger2 %>% 
    html_nodes("td.rank") %>%
    html_text()
 team.rank.name<-ffbsite.mananger2 %>% 
    html_nodes("#standingstable a") %>%
    html_text()
  team.rank<-team.rank[order(match(team.rank.name,team.names))]
  
# Scrape the number of moves a manager makes that season
  moves<-ffbsite.mananger %>% 
    html_nodes("td:nth-child(5)") %>%
    html_text()
  moves<-moves[which(!is.na(as.numeric(moves)))]
# Scrape the number of trades a manager makes that season
  trades<-ffbsite.mananger %>% 
    html_nodes("td.last") %>%
    html_text()
  trades<-trades[which(!is.na(as.numeric(trades)))]
# Put all the sata into the data frame
  manager.table<-data.frame(team.names,team.rank,moves,trades)
# Now begin a loop to scrape info on the scores and outcomes of each game for each manager in the year
  for (j in 1:nplayers){
# Create a url to direct to the score data for a manager in this league in a specific year
ffbsite<-read_html(paste0(ffbsite.1,league.lookup[i,1],"/",league.lookup[i,2],"/?lhst=sched&sctype=team&scmid=",j))
# Get the scores of all the manager's games this season - which is a text file you need to separate into two scores (the managers and the opposing teams)
scores<-ffbsite %>% 
  html_nodes("#scheduletable .last:nth-child(4) , tr:nth-child(1) td:nth-child(5)") %>%
  html_text()
scoresMat<-matrix(as.numeric(unlist(strsplit(scores," - "))),ncol=2,byrow=TRUE)
# Get the outcome of the game with respect to the manager
results<-ffbsite %>% 
  html_nodes("#scheduletable td.result") %>%
  html_text()
# Get the name of this manager's team
team<-ffbsite %>% 
  html_nodes("#schedsubnav .selected a") %>%
  html_text()
# Get the names of the teams played
opponents<-ffbsite %>% 
  html_nodes("#scheduletable td.team") %>%
  html_text()
# Put all the info in a data frame
ffb.dat<-data.frame(Year=league.lookup[i,1],PlayerRank=manager.table[which(manager.table$team.names==team),2],Moves=manager.table[which(manager.table$team.names==team),3],Trades=manager.table[which(manager.table$team.names==team),4],TeamName=team,Opponent=opponents,Result=results,Scored=NA,Against=NA)
ffb.dat[,c("Scored","Against")]<-scoresMat
# So that the for-loop keeps extending the data frame, we use an if else argument which creartes a new data frame on the first pass, but adds on to the dataframe on each pass after that
if(i<=2&j==1){ffb.dat.1<-ffb.dat}else{ffb.dat.1<-rbind(ffb.dat.1,ffb.dat)}
  }}

Next, I need to clean the data up a bit, inserting actual manager names (since team names change over the years), and converting ranking data from characters to numbers.

# Now we can fill in the manager names by taking the unique list of team names (since some team names change over the years) and pair it with a manager name in a lookup table
manager.correction<-data.frame(TeamName=unique(ffb.dat.1$TeamName),Manager=c("Owen","Henry","Paul","Jared B","Jared S","Andrew","Scott","Spears","Lisa and PC","Rick","Paul","Jared S","Jared S","Scott","Henry"))
# We use the match function to lookup the correct manager name for each team name
ffb.dat.1$Manager<-manager.correction$Manager[match(ffb.dat.1$TeamName,manager.correction$TeamName)]
ffb.dat.1$OpposingManager<-manager.correction$Manager[match(ffb.dat.1$Opponent,manager.correction$TeamName)]
#Now fix player rank by removing the '.' and the '*'
levels(ffb.dat.1$PlayerRank)<-c(1,2,3,4,10,5,6,7,8,9)
ffb.dat.1$PlayerRank<-as.numeric(paste(ffb.dat.1$PlayerRank))
# Now we create a lookup table for the number of wins and losses for each matchup
# In this case, rows represent the manager to lookup, and the columns represent the number of wins (win.table) or losses (lose.table) against each other team (columns).
win.table<-ffb.dat.1[which(ffb.dat.1$Result=="Win"),c("Manager","OpposingManager")]
win.table<-table(win.table)
lose.table<-ffb.dat.1[which(ffb.dat.1$Result=="Loss"),c("Manager","OpposingManager")]
lose.table<-table(lose.table)
# We now calculate the probability of a victory for each matchup
JOD.MCM<-win.table/(win.table+lose.table)

Now we’re set to glace at the first few lines of data we’ve gathered.

# We can get a quick look at the data we just gatehred
head(ffb.dat.1)

We can also summarize the data by manager.

Let’s start to look at how the continuous variables relate to each other - especially how potential explanitory variables (Moves, Trades) relate to response variables (Winning Percentage, Total Points). It seems as if Trades and Moves are slightly correlated, and Winning Percentage is unsurprisingly correlated with Total Points Scored and a Player’s end of year Rank. It also looks like number of Trades is possibly negatively related to the Winning Percentage and Rank… hold on to that one for later.

# And we can start to ask how some actions taken by the managers, Moves and Trades, relate to potential successes, Wins and Total points. We use the 'cor' function for this.
cor(ffb.dat.sum[,-c(1:2)])
                 Moves      Trades        Wins        Loss  WinPercent       Total
Moves       1.00000000  0.27176946  0.01882931 -0.02853188  0.01882931  0.05975136
Trades      0.27176946  1.00000000 -0.09305728  0.08522559 -0.09305728 -0.01289292
Wins        0.01882931 -0.09305728  1.00000000 -0.99472397  1.00000000  0.82921869
Loss       -0.02853188  0.08522559 -0.99472397  1.00000000 -0.99472397 -0.83186122
WinPercent  0.01882931 -0.09305728  1.00000000 -0.99472397  1.00000000  0.82921869
Total       0.05975136 -0.01289292  0.82921869 -0.83186122  0.82921869  1.00000000
PlayerRank -0.08123194  0.00000000 -0.81452504  0.80441060 -0.81452504 -0.73173142
            PlayerRank
Moves      -0.08123194
Trades      0.00000000
Wins       -0.81452504
Loss        0.80441060
WinPercent -0.81452504
Total      -0.73173142
PlayerRank  1.00000000

There is a moderate amount of variation in Winning Percentage and Total Points scored among managers, which we can visualise in box plots. We can see that Lisa and PC have suprisingly little variaiton i their total points scored from year to year, while Rick has a lot of variaiton in his winning percentage. Although Winning Percentage and Total Points vary among managers, we’d like to know if differences are significant - especially before we start to run simulaitons.

# We can also quickly look at how Wins and Total points varied among manager with box plots
require(ggplot2)
ggplot(ffb.dat.sum,aes(x=Manager,y=WinPercent))+
  scale_y_continuous(name="Winning Percentage")+
  geom_boxplot()+
  theme_bw()+
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

ggplot(ffb.dat.sum,aes(x=Manager,y=Total))+
  scale_y_continuous(name="Total Points Scored in a Season")+
  geom_boxplot()+
  theme_bw()+
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

For a more in-depth analysis, we use a linear model to test how Manager, Moves, and Trades affect Total points scored. First we center and scale the Moves and Trades data, since there was some correlation between them and we want to avoid multicolinearity. After creating the model, we check some diagnostic plots to ensure the model is fitting correctly and that the residuals are well distributed and not biased.

# Lets center and scale the data to minimize the multicolinearity among explanatory variables
ffb.dat.sum.scale<-ffb.dat.sum
ffb.dat.sum.scale[,c(3:4)]<-apply(ffb.dat.sum.scale[,c(3:4)],2,scale)
ffb.dat.total.lm<-lm(Total~Manager+Moves+Trades,ffb.dat.sum.scale)
# Plot the diagnostics, whcih seem fine and the data seem to fit assumtions of normality pretty well
plot(ffb.dat.total.lm,which=c(1:2))

We can now view a summary of the model, which accounts for ~30% of the variation in Total Points scored after accounting for the number of parameters we use (Adjusted R-Squared). We can also see that the model residuals look relatively normal (mostly evenly distributed around the median).

#show the results
summary(ffb.dat.total.lm)

Call:
lm(formula = Total ~ Manager + Moves + Trades, data = ffb.dat.sum.scale)

Residuals:
     Min       1Q   Median       3Q      Max 
-229.434  -64.347   -7.084   75.364  237.341 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)         1495.30      54.05  27.667  < 2e-16 ***
ManagerHenry         248.07      74.77   3.318 0.001737 ** 
ManagerJared B       199.00      77.14   2.580 0.013009 *  
ManagerJared S       299.59      73.49   4.077 0.000171 ***
ManagerLisa and PC    86.37      74.04   1.167 0.249162    
ManagerOwen          285.63      83.22   3.432 0.001242 ** 
ManagerPaul          155.72      73.80   2.110 0.040097 *  
ManagerRick          287.09      82.88   3.464 0.001130 ** 
ManagerScott         231.30      75.70   3.055 0.003663 ** 
ManagerSpears         50.77      77.92   0.651 0.517834    
Moves                 12.83      19.23   0.667 0.507818    
Trades               -30.25      22.50  -1.344 0.185168    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 127.3 on 48 degrees of freedom
Multiple R-squared:  0.4299,    Adjusted R-squared:  0.2993 
F-statistic: 3.291 on 11 and 48 DF,  p-value: 0.002013

A look at the individual effects indicates that Manager accounts for a significant amount of the variation in Total Points scores, but Moves and Trades do not help us explain points scored in a season.

# And test the effects
anova(ffb.dat.total.lm)
Analysis of Variance Table

Response: Total
          Df Sum Sq Mean Sq F value Pr(>F)   
Manager    9 555136   61682  3.8077 0.0011 **
Moves      1   2024    2024  0.1249 0.7253   
Trades     1  29275   29275  1.8072 0.1852   
Residuals 48 777569   16199                  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Next we look at how the actual winning percentage relates to Manager, Moves and Trades using logistic regression. Again, we assess some diagnostic plots and the model seems to fit OK (a little funky at the bottom of the Q-Q plot).

# Now look at the effects on winning
ffb.dat.win.lm<-glm(cbind(Wins,Loss) ~ Manager+Moves+Trades,family=binomial(link='logit'),data=ffb.dat.sum.scale)
# Again, the model seems as though it its well
plot(ffb.dat.win.lm,which=c(1:2))

We take a closer look at the model, and we can see that both Manger and Trades seem to be important to explaining Winning Percentage.

# Now lets look at the model and the effects
summary(ffb.dat.win.lm)

Call:
glm(formula = cbind(Wins, Loss) ~ Manager + Moves + Trades, family = binomial(link = "logit"), 
    data = ffb.dat.sum.scale)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-2.83512  -1.00569   0.02417   0.76216   2.76820  

Coefficients:
                   Estimate Std. Error z value Pr(>|z|)   
(Intercept)        -0.55714    0.23310  -2.390  0.01684 * 
ManagerHenry        0.75013    0.31988   2.345  0.01902 * 
ManagerJared B      0.42989    0.32868   1.308  0.19090   
ManagerJared S      0.87535    0.31648   2.766  0.00568 **
ManagerLisa and PC  0.13484    0.31897   0.423  0.67249   
ManagerOwen         1.08847    0.36022   3.022  0.00251 **
ManagerPaul         0.37717    0.31523   1.196  0.23151   
ManagerRick         1.18550    0.36055   3.288  0.00101 **
ManagerScott        0.61192    0.32288   1.895  0.05806 . 
ManagerSpears       0.10308    0.33762   0.305  0.76012   
Moves               0.03361    0.08268   0.407  0.68436   
Trades             -0.23552    0.09838  -2.394  0.01666 * 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 105.474  on 59  degrees of freedom
Residual deviance:  78.043  on 48  degrees of freedom
AIC: 280.97

Number of Fisher Scoring iterations: 4
anova(ffb.dat.win.lm,test="Chisq")
Analysis of Deviance Table

Model: binomial, link: logit

Response: cbind(Wins, Loss)

Terms added sequentially (first to last)

        Df Deviance Resid. Df Resid. Dev Pr(>Chi)  
NULL                       59    105.474           
Manager  9  21.5691        50     83.905  0.01035 *
Moves    1   0.0389        49     83.866  0.84366  
Trades   1   5.8231        48     78.043  0.01582 *
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

We know from the box plots before that winning percentage is relatively variable among managers, so let’s take a closer look at how trades affect winning percentage. First we look at the model coefficient, which seems to indicate that Trading players has a NEGATVIE effect on winning percentage?

# Now we can look at the effect Trades has on winning, which seems to be negative
ffb.dat.win.lm$coefficients["Trades"]
    Trades 
-0.2355176 

This seems odd… or at the very least, I’d like to be certain of the result before rejecting every trade my friends offer me. By plotting winning percentage as a function of number of trades, we can see an outlier with a high number of trades and a low winning percentage that could be driving our pattern.

# Let's plot winning percentage as a function of trades to see whats gooing on.
# It seems like more of a sample size and variance issue than an actual trade effect.
with(ffb.dat.sum,plot(WinPercent~Trades))

We remove this outlier and re-run the model to see if the point was driving most of our results for Trades. It seems to have been, since the Trades effect disappears… good to know for next season.

# If we remove that pone outlier, the effect dissapears
ffb.dat.sum.fix<-ffb.dat.sum[-which(ffb.dat.sum$Trades==4),]
ffb.dat.sum.fix.scale<-ffb.dat.sum.fix
ffb.dat.sum.fix.scale[,c(3,4)]<-apply(ffb.dat.sum.fix[,c(3,4)],2,scale)
ffb.dat.win.lm<-glm(cbind(Wins,Loss) ~ Manager+Moves+Trades,family=binomial(link='logit'),data=ffb.dat.sum.fix.scale)
anova(ffb.dat.win.lm,test="Chisq")
Analysis of Deviance Table

Model: binomial, link: logit

Response: cbind(Wins, Loss)

Terms added sequentially (first to last)

        Df Deviance Resid. Df Resid. Dev Pr(>Chi)   
NULL                       58    103.470            
Manager  9  24.6076        49     78.862 0.003437 **
Moves    1   0.1600        48     78.703 0.689186   
Trades   1   2.4338        47     76.269 0.118748   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

We can also look at the pairwise probability that each manager will beat another manager in a match up. We do so in a table (displayed below as a heat map) where columns are the manager of interest, and the rows are their potential opponents. Each value reflects the probability that the manger will beat each individual opponent (total wins/games played), and each column sums to 1 (i.e., 100%). We can already see that Spears and Andrew have relatively low probabilities of beating any other manager, although Andrew often beats Spears. We also see that while Spears may lose a lot, he beats me (Scott) more often than not… such is life.

# We can use the 'reshape' package to melt down the probability table so we can plot it as a heatmap
require(reshape)
JOD.MCM.table<-melt(JOD.MCM)
# The heatmap can show the probabilty of a manager (columns) beating another team (rows), with higher probabilites reflected in deeper reds. I've also included the actual probability in each cell of the heatmap for easy reference
ggplot(JOD.MCM.table, aes(x = Manager, y = OpposingManager)) +
  geom_tile(aes(fill = value)) +
  scale_y_discrete(name="Opposing Manager")+
  geom_text(aes(label=round(value,2)))+
  scale_fill_gradient(na.value = 'black',name="Probability\nof Winning",low="white",high="dark red")+
  theme_bw()+
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

Now it’s time for a simulation! We first create another probability table as above, except only with data from 2011-2013. We save the remaining data to check the simulation results against.

Then, we create a template for a Fantasy Football regular season schedule (14 games). The easiest way to do this is to take a schedule that already exists, like that from our 2014 Yahoo season, and use it as a template where each team is a number. We can then randomize which team corresponds to each number for a new season’s schedule.

Lastly, we create a string of for-loops that randomly generates a fantasy football schedule, iterativly go through the schedule, and for each match-up/game in the list, look up the probability that either manager beats the opposing one. Then, we choose between the opposing managers, weighting our choice by their respective probabilities of winning the match up. The choice is the winner of the match!

This happens over and over and over… again until the regular season is over. Then, the regular season ranks are calculated, and managers are placed into a playoff bracket with Ranks 1 vs 4, 2 vs 3, 5 vs 8, and 6 vs 7. Teams ranked 9 and 10 stay at those ranks to think about how awful they are and plan for next year. This format is how our league actually operates on Yahoo.

The results from the finals are placed into a data-table, yielding a rank for each manager (columns) for the season (row). The entire loop starts over again for a new season, and repeats until we’re simulated 500 seasons.

# Now we create another probability table just as beofore, but only using the first 3 years of data
win.table.sub<-ffb.dat.1[which(ffb.dat.1$Result=="Win" & ffb.dat.1$Year<=2013),c("Manager","OpposingManager")]
win.table.sub<-table(win.table.sub)
lose.table.sub<-ffb.dat.1[which(ffb.dat.1$Result=="Loss"& ffb.dat.1$Year<=2013),c("Manager","OpposingManager")]
lose.table.sub<-table(lose.table.sub)
JOD.MCM<-win.table.sub/(win.table.sub+lose.table.sub)
# Before we simulate seasons of fantasy football, we need to generate schedules. The easiest way to do this is to take a schedule that already exists, like that from 2014, and use it as a template where we can randomize which team corrosponds to each number for new season schedules
matchup.table.raw<-matrix(match(as.vector(subset(ffb.dat.1,Year==2014)[,c(6)]),as.vector(unique(subset(ffb.dat.1,Year==2014)[,c(5)]))),ncol=14,byrow=TRUE)
# We now setup an array to hold all of the simulation data (end of season team rankings)
# I've chosen to simulate 500 seasons
sim.seasons<-array(NA,c(500,10))
colnames(sim.seasons)<-as.vector(rownames(JOD.MCM))
# Now for 500 simulated seasons using for-loops
for (s in 1:500){
#First, create a seasons schedule by randomly assigning teams/managers to a number, and placeing them into the schedule template for the 14 regular season games
matchup.names<-sample(as.vector(rownames(JOD.MCM)),10)
matchup.table<-matrix(matchup.names[matchup.table.raw],ncol=14)
rownames(matchup.table)<-matchup.names
colnames(matchup.table)<-c(1:14)
#Then, create a data frame to hold the scheduled matchup, and eventually the results of the matchup
matchup.sim<-data.frame(Week=NA,Manager=NA,Opponent=NA,Winner=NA)
# Loop over the matchups in the schedule and only insert unique matchups for the season (since the schedule template gives matchup 'A vs B' as well as 'B vs A')
for (i in 1:ncol(matchup.table)){
for(j in 1:nrow(matchup.table)){
  if(j==1&i==1){matchup.sim[1,]<-c(i,rownames(matchup.table)[j],matchup.table[j,i],NA)}else{
  if(any(rownames(matchup.table)[j]==subset(matchup.sim,Week==i)[,3])==FALSE){
    matchup.sim<-rbind(matchup.sim,data.frame(Week=i,Manager=rownames(matchup.table)[j],Opponent=matchup.table[j,i],Winner=NA))
  }}
}}
# Now to find out the winners of the matchups
for(i in 1:nrow(matchup.sim)){
# For each matchup in the list, lookup the probabability that the manager (home team) beats the opposing manager, and the probability the oposing manager wins (just 1-P, but we look it up anyway)
  mananger.prob<-JOD.MCM[matchup.sim$Manager[i],matchup.sim$Opponent[i]]
  opponent.prob<-JOD.MCM[matchup.sim$Opponent[i],matchup.sim$Manager[i]]
  # Choose between the manager and the opposing manager with probability manager.prob and opponent.prob respectively. The choice in the winner of the match!
  matchup.sim$Winner[i]<-sample(c(matchup.sim$Manager[i],matchup.sim$Opponent[i]),1,prob=c(mananger.prob,opponent.prob))
}
# Summarize the results of the regular season matchups by using the frequency of the managers name in the winner column, then rank the managers by the number of wins for the end of regular season rankings
reg.results<-table(matchup.sim$Winner)
reg.rank<-names(reg.results)[order(reg.results,decreasing = TRUE)]
# Now use the regular season rankings to setup the mathups in the semifinal round
# Creatre a matrix by rank, but switch the 2nd and 4th ranked, and 6th and 8th ranked players to create the appropriate matchups
semifinal<-matrix(reg.rank,ncol=2,byrow=TRUE)
new.match<-c(semifinal[2,2],semifinal[1,2],semifinal[4,2],semifinal[3,2],semifinal[5,2])
semifinal[,2]<-new.match
# Add a column for the winners to be recorded in
semifinal<-cbind(semifinal,rep(NA,5))
# Now simulate the semifinal in the same way as the regular season
for(i in 1:nrow(semifinal)){
  mananger.prob<-JOD.MCM[semifinal[i,1],semifinal[i,2]]
  opponent.prob<-JOD.MCM[semifinal[i,2],semifinal[i,1]]
  semifinal[i,3]<-sample(c(semifinal[i,1],semifinal[i,2]),1,prob=c(mananger.prob,opponent.prob))
}
# The final two spots do not compete in our semifinals or finals, so we automatically place the better ranked individual into the winners spot
semifinal[5,3]<-semifinal[5,1]
# Use the frequency of names to determine which teams/managers move up a spot (winners with more instances of their name) and which move down a spot (losers with fewer instances of their name).
semifinal.results.1<-table(semifinal[c(1:2),])
semifinal.results.2<-table(semifinal[c(3:4),])
semifinal.results.3<-table(semifinal[5,])
semi.rank<-c(names(semifinal.results.1)[order(semifinal.results.1,decreasing = TRUE)],names(semifinal.results.2)[order(semifinal.results.2,decreasing = TRUE)],names(semifinal.results.3)[order(semifinal.results.3,decreasing = TRUE)])
# create the matchups for the final round with a place holder for the winner
final<-matrix(semi.rank,ncol=2,byrow=TRUE)
final<-cbind(final,rep(NA,5))
# Simulate the finals the same way we did the regualr season and semifinals
for(i in 1:nrow(final)){
  mananger.prob<-JOD.MCM[final[i,1],final[i,2]]
  opponent.prob<-JOD.MCM[final[i,2],final[i,1]]
  final[i,3]<-sample(c(final[i,1],final[i,2]),1,prob=c(mananger.prob,opponent.prob))
}
# Again, the last palced teams keep their ranks and do not compete
final[5,3]<-final[5,1]
# Use the frequency of names to determine which teams/managers move up a spot (winners with more instances of their name) and which move down a spot (losers with fewer instances of their name).
final.results.1<-table(final[1,])
final.results.2<-table(final[2,])
final.results.3<-table(final[3,])
final.results.4<-table(final[4,])
final.results.5<-table(final[5,])
final.rank<-c(names(final.results.1)[order(final.results.1,decreasing = TRUE)],names(final.results.2)[order(final.results.2,decreasing = TRUE)],names(final.results.3)[order(final.results.3,decreasing = TRUE)],names(final.results.4)[order(final.results.4,decreasing = TRUE)],names(final.results.5)[order(final.results.5,decreasing = TRUE)])
# Input the final rnakings into the array for season simulation results, and go back to the beginning of the loop for a new season simulation
sim.seasons[s,]<-order(final.rank)
}

Here is a look at the first few lines of data we get.

head(sim.seasons)
     Andrew Henry Jared B Jared S Lisa and PC Owen Paul Rick Scott Spears
[1,]      8     2       1       5           7    4    6    3    10      9
[2,]      7     5       4       2           9    1    6    3     8     10
[3,]      6     3       8       4          10    2    5    1     9      7
[4,]      8     9       7       1           5    4    3    2     6     10
[5,]      8     6       1       3           9    2    7    4     5     10
[6,]      4     1       2       7           9    3    8    6     5     10

We can view the rankings for each manager using a box plot…. poor Spears, but it looks like Rick did pretty well in most simulations.

For a cleaner look, we calculate the mean rank with 95% confidence intervals for each manager across all 500 simulations, and place a dotted line at rank 5 (middle of the road in our 10 team league). One thing to note is how constrained the 95% confidence intervals are. This is likely because we ran 500 simulations, which is sort of excessive, and large sample sizes will naturally decrease any measure of variance in sample mean (like standard error and 95% confidence intervals) - increasing precision. As in the box plot, we can still see how poorly and well Spears and Rick did respectively, but the relative performance of other managers is more clear now. Also, we indicate each managers actual 2011-2013 mean ranking in the figure with color, and see that, for the most part, managers that rank high in the simulation, actually ranked high bertween 2011 and 2013.

We can also look at the correlation between sumulated and actual rankings between 2011 and 2013 and find that the simulaion did a pretty good job.

with(sim.seasons.results,cor(Mean,PlayerRank.2011.2013))
[1] 0.9392253

Since these simulated results came from 2011-2013 fantasy data, we can compare it to actual fantasy data from 2014-2016. We do so by plotting average manager rank from the simulation against average actual rank from the newer (2014-2016) data. We can see that most managers fall along or near the 1:1 line (dotted black), indicating that the simulation from 2011-2013 data captured the ranks in 2014-2016 data well. But, Spears and Rick fall pretty far from the 1:1 line - Rick performing much better during simulation than expected based on his actual rank, and Spears performing worse than expected. This pattern could mean two things: 1) The simulation did not capture Spears and Rick’s performance well because there is a fundamental problem/inaccuracy in how we simulate Spears and Rick (they can definitely be wild-cards in the group). Since the simulation results were well correlated with 2011-2013 data though, it’s more likely that Spears and Rick have actually improved and worsened their 2014-2016 fantasy play respectively, compared to their 2011-2013 play.

Overall, we can conclude that the majority of the variation in winning the Join or Die Fantasy Football League is not captured by what variation we see in Trades and Moves. There are probably other variables inherient in the variation among managers (e.g., draft performance, interest in fantasy games, proclivity to procrastinate at work and research sports stats), that determine the winning percentage, and eventual ranking, of teams. We can, however, say that Spears and his “Chilean Spear Fishers”" are on the up-and-up, while Rick and his “Alexandria Emperors”" have lost some of that pizzazz that made them a powerhouse from 2011-2013.

That’s it!

LS0tCnRpdGxlOiAiUmVzZWFyY2hpbmcgWW91ciBGYW50YXN5IEZvb3RiYWxsIExlYWd1ZSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpBcyB3aXRoIG1hbnkgcGVvcGxlIGFjcm9zcyB0aGUgaW50ZXJuZXQsIEkgcGxheSBmYW50YXN5IHNwb3J0cyAtIG1vc3RseSBmYW50YXN5IGZvb3RiYWxsLiBGcmllbmRzIGZyb20gY29sbGVnZSBzdGFydGVkIGEgbGVhZ3VlIGJhY2sgaW4gdGhlIGVhcmx5IDIwMDBzLiBNdWx0aXBsZSB0ZWFtcyBhbmQgbWFuYWdlcnMgY2FtZSBhbmQgd2VudCwgYnV0IGluIDIwMTAgd2Ugc3RhcnRlZCBhIGtlZXBlci1sZWFndWUgLSB0aGUgSm9pbiBvciBEaWUgRmFudGFzeSBGb290YmFsbCBMZWFndWUgLSBhbmQgc29saWRpZmllZCBhIGNvbnNpc3RlbnQgZ3JvdXAgb2YgMTAgbWFuYWdlcnMuIFRoZSBjb25zaXN0ZW5jeSBpbiBtYW5hZ2VtZW50IGFuZCBsZWFndWUgc2V0dGluZ3MgKHNldHRpbmdzIHdlcmVuJ3Qgc29saWRpZmllZCB1bnRpbCAyMDExKSBwcmVzZW50cyBhIGdyZWF0IG9wcG9ydHVuaXR5IHRvIGxvb2sgYXQgaG93IGluZGl2aWR1YWwgbWFuYWdlcnMgcHJlZm9ybSBhZ2FpbnN0IGVhY2ggb3RoZXIgb3ZlciBtdWx0aXBsZSB5ZWFycywgYW5kIGV2ZW4gZ2F0aGVyIGVub3VnaCBkYXRhIHRvIHNpbXVsYXRlIHNlYXNvbnMgYW5kIGNvbXBhcmUgdGhlIHJlc3VsdHMgdG8gYWN0dWFsIHNlYXNvbiByYW5raW5ncy4KCkhlcmUsIEkndmUgY29kZWQgc2NyaXB0IHRoYXQgZmlyc3Qgc2NyYXBlcyBkYXRhIGZyb20gbXkgZmFudGFzeSBmb290YmFsbCBsZWFndWUsIGFuZCB0aGVuIGFuYWx5emVzIHRoZSBkYXRhIGluIHZhcmlvdXMgd2F5cy4gSSBsb29rIGF0IGhvdyB0cmFkaW5nIGFuZCBwbGF5ZXIgbW92ZXMgYWZmZWN0IHdpbm5pbmcgcGVyY2VudGFnZSBhbmQgdG90YWwgcG9pbnRzIHNjb3JlZCBpbiBhIHNlYXNvbiwgYXMgd2VsbCBhcyB0ZXN0IGZvciBkaWZmZXJlbmNlcyBhbW9uZyBtYW5hZ2Vycy4gVGhlbiwgSSBzaW11bGF0ZSBmYW50YXN5IGZvb3RiYWxsIHNlYXNvbnMgYmFzZWQgcHVyZWx5IG9uIGhpc3RvcmljYWwgcGVyZm9ybWFuY2Ugb2YgdGhlIG1hbmFnZXJzIChwYWlyd2lzZSB3aW5uaW5nIHByb2JhYmlsaXRpZXMpLiBJIHVzZSB0aGUgbGVhZ3VlJ3MgaW5pdGlhbCAzIHllYXJzIG9mIGRhdGEgKDIwMTEtMjAxMykgdG8gc2ltdWxhdGUgNTAwIGZ1bGwgc2Vhc29ucyAocmVndWxhciBzZWFzb24gYW5kIHBsYXlvZmZzKSwgYW5kIHRoZW4gY29tcGFyZSB0aGUgYXZlcmFnZSBlbmQtb2Ytc2Vhc29uIG1hbmFnZXIgcmFua2luZ3MgdG8gYWN0dWFsIGF2ZXJhZ2UgcmFua2luZ3MgZnJvbSAyMDEzLTIwMTYuCgpTbyB0byBiZWdpbiwgSSBzY3JhcGUgdGhlIGRhdGEgdXNpbmcgdGhlICdydmVzdCcgcGFja2FnZS4gRmlyc3QgSSBuZWVkIHRvIGxvZyBpbnRvIG15IHlhaG9vIGZhbnRhc3kgZm9vdGJhbGwgYWNjb3VudCBhbmQgbWFudWFsbHkgZmluZCB0aGUgTGVhZ3VlIElEcyB0aGF0IGNvcnJlc3BvbmQgdG8gZWFjaCB5ZWFyIG9mIHBsYXkgLSB0aGV5IGFyZSBhIGxpdHRsZSBtb3JlIGRpZmZpY3VsdCB0byBzY3JhcGUgb24gdGhlaXIgb3duLCBhbmQgbWFrZSB0aGUgcmVzdCBvZiB0aGUgcHJvY2VzcyBtdWNoIGVhc2llci4KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgbG9hZCB0aGUgJ3J2ZXN0JyBwYWNrYWdlIHdoaWNoIGhlbHBzIHNjcmFwZSBkYXRhIGZyb20gdXJscwpsaWJyYXJ5KHJ2ZXN0KQojIExvZyBpbnRvIHlvdXIgeWFob28gZmFudGFzeSBmb290YmFsbCBwYWdlCiMgTG9va3VwIHllYXJzIGFuZCAgY29ycm9zcG9uZGluZyBsZWFndWUgbnVtYmVycyB0byBtYWtlIGEgdGFibGUKIyBmcm9tIHdoaWNoIHRvIGxvb2t1cCB0aGUgYXBwcm9wcmlhdGUgaW5mbyB0byBlZGl0IHVybHMgbGF0ZXIKCmxlYWd1ZS5sb29rdXA8LWRhdGEuZnJhbWUoWWVhcnM9YygyMDEwOjIwMTYpLExlYWd1ZUlEPWMoMzA2MzQsNjUxNDIyLDM3MTM3LDEwMDQxOCwxNjUxNywxMzMzLDU5Nzk5KSkKCiMgRW50ZXIgdGhlIG51bWJlciBvZiBwbGF5ZXJzIGluIHRoZSBsZWFndWUKbnBsYXllcnM8LTEwCgojIE5vdyBlbnRlciB0aGUgYmFzZSB1cmwgZm9yIGFyY2hpdmVkIHlhaG9vIGZhbnRhc3kgZm9vdGJhbGwgcGFnZXMgYW5kIHJlY29yZHMsIHRvIGJlIHVzZWQgbGF0ZXIgd2hlbiBidWlsZGluZyB1cmxzIHRvIHNjcmFwZSBmcm9tCmZmYnNpdGUuMTwtImh0dHBzOi8vZm9vdGJhbGwuZmFudGFzeXNwb3J0cy55YWhvby5jb20vYXJjaGl2ZS9uZmwvIgoKYGBgCgoKTm93IEkgY2FuIHNjcmFwIHRoZSBkYXRhIGFuZCBwdXQgaXQgYWxsIGluIGEgdGFibGUuIFRvIGRldGVybWluZSB0aGUgY29ycmVjdCBDU1Mgc2VsZWN0b3IgZm9yIGVhY2ggaXRlbSBJJ2QgbGlrZSB0byBzY3JhcGUsIEkgdXNlIGEgaGFuZHkgb25saW5lIHRvb2wgY2FsbGVkICJTZWxlY3RvciBHYWRnZXQiIChodHRwOi8vc2VsZWN0b3JnYWRnZXQuY29tKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKIyBOb3cgdG8gc2NyYXBlIHRoZSBkYXRhIGZyb20geWFob28gZmFudGFzeSBzcG9ydHMuIFdlIHNldHVwIHR3byBmb3ItbG9vcHMgdGhhdCBpdGVyYXRlIG92ZXIgb3VyIHllYXJzIGFuZCBsZWFndWUgbnVtYmVycyBnYXRoZXJlZCBiZWZvcmUsIHNjcmFwaW5nIGluZm9ybWF0aW9uIGFib3V0IGhvdyBlYWNoIG1hbmFnZXIgbWFuYWdlZCB0aGVpciB0ZWFtIChpLmUuLCBudW1iZXIgb2YgdHJhZGVzLCBhbmQgbW92ZXMpLCBhbmQgaG93IHRoZXkgZmFpcmVkIGFnYWluc3QgZWFjaC1vdGhlciB0ZWFtLiBEYXRhIGlzIHN0b3JlZCBpbiBkYXRlZnJhbWVzIGFsb25nIHRoZSB3YXkuIFRoZSBzZWxlY3Rpb24gY3JpdGVyaWEgZm9yIGVhY2ggc2NyYXBlIHdhcyBmb3VuZCB1c2luZyBTZWxlY3RvckdhZGdldC4KCiMgU3RhcnQgdGhlIGZpcnN0IGZvci1sb29wIHRvIGxvb2t1cCBtYW5hZ2VyIGluZm8uIFdlIGRvbid0IHVzZSB0aGUgbGVhZ3VlJ3MgZmlyc3QgeWVhciAtIDIwMTAgLSBiZWNhdXNlIHRoZSBzZWFzb24gZm9ybWF0IHdhcyBkaWZmZXJlbnQgdGhhbiB0aGUgcmVzdCAocGxheWVkIHdpdGggZGl2aXNpb25zKS4KZm9yIChpIGluIDI6bnJvdyhsZWFndWUubG9va3VwKSl7CgojIENyZWF0ZSBhIHVybCB0byBkaXJlY3QgdG8gdGhlIGNvcnJlY3QgeWVhciBhbmQgbGVhZ3VlIGlkIHVzaW5nIHRoZSBsb29rdXAgdGFibGUgZnJvbSBiZWZvcmUKZmZic2l0ZS5tYW5hbmdlcjwtcmVhZF9odG1sKHBhc3RlMChmZmJzaXRlLjEsbGVhZ3VlLmxvb2t1cFtpLDFdLCIvIixsZWFndWUubG9va3VwW2ksMl0sIi90ZWFtcyIpKQpmZmJzaXRlLm1hbmFuZ2VyMjwtcmVhZF9odG1sKHBhc3RlMChmZmJzaXRlLjEsbGVhZ3VlLmxvb2t1cFtpLDFdLCIvIixsZWFndWUubG9va3VwW2ksMl0sIj9saHN0PXN0YW5kI2xoc3RzdGFuZCIpKQoKIyBHZXQgdGhlIHRlYW0gbmFtZQogIHRlYW0ubmFtZXM8LWZmYnNpdGUubWFuYW5nZXIgJT4lIAogICAgaHRtbF9ub2RlcygiI3RlYW1zIC5maXJzdCBhIikgJT4lCiAgICBodG1sX3RleHQoKQoKIyBHZXQgdGVhbSByYW5rLCBidXQgcmVvcmRlciB0aGVtIGJhc2VkIG9uIHRoZSB0ZWFtIG5hbWVzCiAgdGVhbS5yYW5rPC1mZmJzaXRlLm1hbmFuZ2VyMiAlPiUgCiAgICBodG1sX25vZGVzKCJ0ZC5yYW5rIikgJT4lCiAgICBodG1sX3RleHQoKQogdGVhbS5yYW5rLm5hbWU8LWZmYnNpdGUubWFuYW5nZXIyICU+JSAKICAgIGh0bWxfbm9kZXMoIiNzdGFuZGluZ3N0YWJsZSBhIikgJT4lCiAgICBodG1sX3RleHQoKQogIHRlYW0ucmFuazwtdGVhbS5yYW5rW29yZGVyKG1hdGNoKHRlYW0ucmFuay5uYW1lLHRlYW0ubmFtZXMpKV0KICAKIyBTY3JhcGUgdGhlIG51bWJlciBvZiBtb3ZlcyBhIG1hbmFnZXIgbWFrZXMgdGhhdCBzZWFzb24KICBtb3ZlczwtZmZic2l0ZS5tYW5hbmdlciAlPiUgCiAgICBodG1sX25vZGVzKCJ0ZDpudGgtY2hpbGQoNSkiKSAlPiUKICAgIGh0bWxfdGV4dCgpCiAgbW92ZXM8LW1vdmVzW3doaWNoKCFpcy5uYShhcy5udW1lcmljKG1vdmVzKSkpXQoKIyBTY3JhcGUgdGhlIG51bWJlciBvZiB0cmFkZXMgYSBtYW5hZ2VyIG1ha2VzIHRoYXQgc2Vhc29uCiAgdHJhZGVzPC1mZmJzaXRlLm1hbmFuZ2VyICU+JSAKICAgIGh0bWxfbm9kZXMoInRkLmxhc3QiKSAlPiUKICAgIGh0bWxfdGV4dCgpCiAgdHJhZGVzPC10cmFkZXNbd2hpY2goIWlzLm5hKGFzLm51bWVyaWModHJhZGVzKSkpXQoKIyBQdXQgYWxsIHRoZSBzYXRhIGludG8gdGhlIGRhdGEgZnJhbWUKICBtYW5hZ2VyLnRhYmxlPC1kYXRhLmZyYW1lKHRlYW0ubmFtZXMsdGVhbS5yYW5rLG1vdmVzLHRyYWRlcykKCiMgTm93IGJlZ2luIGEgbG9vcCB0byBzY3JhcGUgaW5mbyBvbiB0aGUgc2NvcmVzIGFuZCBvdXRjb21lcyBvZiBlYWNoIGdhbWUgZm9yIGVhY2ggbWFuYWdlciBpbiB0aGUgeWVhcgogIGZvciAoaiBpbiAxOm5wbGF5ZXJzKXsKCiMgQ3JlYXRlIGEgdXJsIHRvIGRpcmVjdCB0byB0aGUgc2NvcmUgZGF0YSBmb3IgYSBtYW5hZ2VyIGluIHRoaXMgbGVhZ3VlIGluIGEgc3BlY2lmaWMgeWVhcgpmZmJzaXRlPC1yZWFkX2h0bWwocGFzdGUwKGZmYnNpdGUuMSxsZWFndWUubG9va3VwW2ksMV0sIi8iLGxlYWd1ZS5sb29rdXBbaSwyXSwiLz9saHN0PXNjaGVkJnNjdHlwZT10ZWFtJnNjbWlkPSIsaikpCgojIEdldCB0aGUgc2NvcmVzIG9mIGFsbCB0aGUgbWFuYWdlcidzIGdhbWVzIHRoaXMgc2Vhc29uIC0gd2hpY2ggaXMgYSB0ZXh0IGZpbGUgeW91IG5lZWQgdG8gc2VwYXJhdGUgaW50byB0d28gc2NvcmVzICh0aGUgbWFuYWdlcnMgYW5kIHRoZSBvcHBvc2luZyB0ZWFtcykKc2NvcmVzPC1mZmJzaXRlICU+JSAKICBodG1sX25vZGVzKCIjc2NoZWR1bGV0YWJsZSAubGFzdDpudGgtY2hpbGQoNCkgLCB0cjpudGgtY2hpbGQoMSkgdGQ6bnRoLWNoaWxkKDUpIikgJT4lCiAgaHRtbF90ZXh0KCkKCnNjb3Jlc01hdDwtbWF0cml4KGFzLm51bWVyaWModW5saXN0KHN0cnNwbGl0KHNjb3JlcywiIC0gIikpKSxuY29sPTIsYnlyb3c9VFJVRSkKCiMgR2V0IHRoZSBvdXRjb21lIG9mIHRoZSBnYW1lIHdpdGggcmVzcGVjdCB0byB0aGUgbWFuYWdlcgpyZXN1bHRzPC1mZmJzaXRlICU+JSAKICBodG1sX25vZGVzKCIjc2NoZWR1bGV0YWJsZSB0ZC5yZXN1bHQiKSAlPiUKICBodG1sX3RleHQoKQoKIyBHZXQgdGhlIG5hbWUgb2YgdGhpcyBtYW5hZ2VyJ3MgdGVhbQp0ZWFtPC1mZmJzaXRlICU+JSAKICBodG1sX25vZGVzKCIjc2NoZWRzdWJuYXYgLnNlbGVjdGVkIGEiKSAlPiUKICBodG1sX3RleHQoKQoKIyBHZXQgdGhlIG5hbWVzIG9mIHRoZSB0ZWFtcyBwbGF5ZWQKb3Bwb25lbnRzPC1mZmJzaXRlICU+JSAKICBodG1sX25vZGVzKCIjc2NoZWR1bGV0YWJsZSB0ZC50ZWFtIikgJT4lCiAgaHRtbF90ZXh0KCkKCiMgUHV0IGFsbCB0aGUgaW5mbyBpbiBhIGRhdGEgZnJhbWUKZmZiLmRhdDwtZGF0YS5mcmFtZShZZWFyPWxlYWd1ZS5sb29rdXBbaSwxXSxQbGF5ZXJSYW5rPW1hbmFnZXIudGFibGVbd2hpY2gobWFuYWdlci50YWJsZSR0ZWFtLm5hbWVzPT10ZWFtKSwyXSxNb3Zlcz1tYW5hZ2VyLnRhYmxlW3doaWNoKG1hbmFnZXIudGFibGUkdGVhbS5uYW1lcz09dGVhbSksM10sVHJhZGVzPW1hbmFnZXIudGFibGVbd2hpY2gobWFuYWdlci50YWJsZSR0ZWFtLm5hbWVzPT10ZWFtKSw0XSxUZWFtTmFtZT10ZWFtLE9wcG9uZW50PW9wcG9uZW50cyxSZXN1bHQ9cmVzdWx0cyxTY29yZWQ9TkEsQWdhaW5zdD1OQSkKZmZiLmRhdFssYygiU2NvcmVkIiwiQWdhaW5zdCIpXTwtc2NvcmVzTWF0CgojIFNvIHRoYXQgdGhlIGZvci1sb29wIGtlZXBzIGV4dGVuZGluZyB0aGUgZGF0YSBmcmFtZSwgd2UgdXNlIGFuIGlmIGVsc2UgYXJndW1lbnQgd2hpY2ggY3JlYXJ0ZXMgYSBuZXcgZGF0YSBmcmFtZSBvbiB0aGUgZmlyc3QgcGFzcywgYnV0IGFkZHMgb24gdG8gdGhlIGRhdGFmcmFtZSBvbiBlYWNoIHBhc3MgYWZ0ZXIgdGhhdAppZihpPD0yJmo9PTEpe2ZmYi5kYXQuMTwtZmZiLmRhdH1lbHNle2ZmYi5kYXQuMTwtcmJpbmQoZmZiLmRhdC4xLGZmYi5kYXQpfQogIH19CmBgYAoKCk5leHQsIEkgbmVlZCB0byBjbGVhbiB0aGUgZGF0YSB1cCBhIGJpdCwgaW5zZXJ0aW5nIGFjdHVhbCBtYW5hZ2VyIG5hbWVzIChzaW5jZSB0ZWFtIG5hbWVzIGNoYW5nZSBvdmVyIHRoZSB5ZWFycyksIGFuZCBjb252ZXJ0aW5nIHJhbmtpbmcgZGF0YSBmcm9tIGNoYXJhY3RlcnMgdG8gbnVtYmVycy4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIE5vdyB3ZSBjYW4gZmlsbCBpbiB0aGUgbWFuYWdlciBuYW1lcyBieSB0YWtpbmcgdGhlIHVuaXF1ZSBsaXN0IG9mIHRlYW0gbmFtZXMgKHNpbmNlIHNvbWUgdGVhbSBuYW1lcyBjaGFuZ2Ugb3ZlciB0aGUgeWVhcnMpIGFuZCBwYWlyIGl0IHdpdGggYSBtYW5hZ2VyIG5hbWUgaW4gYSBsb29rdXAgdGFibGUKbWFuYWdlci5jb3JyZWN0aW9uPC1kYXRhLmZyYW1lKFRlYW1OYW1lPXVuaXF1ZShmZmIuZGF0LjEkVGVhbU5hbWUpLE1hbmFnZXI9YygiT3dlbiIsIkhlbnJ5IiwiUGF1bCIsIkphcmVkIEIiLCJKYXJlZCBTIiwiQW5kcmV3IiwiU2NvdHQiLCJTcGVhcnMiLCJMaXNhIGFuZCBQQyIsIlJpY2siLCJQYXVsIiwiSmFyZWQgUyIsIkphcmVkIFMiLCJTY290dCIsIkhlbnJ5IikpCgojIFdlIHVzZSB0aGUgbWF0Y2ggZnVuY3Rpb24gdG8gbG9va3VwIHRoZSBjb3JyZWN0IG1hbmFnZXIgbmFtZSBmb3IgZWFjaCB0ZWFtIG5hbWUKZmZiLmRhdC4xJE1hbmFnZXI8LW1hbmFnZXIuY29ycmVjdGlvbiRNYW5hZ2VyW21hdGNoKGZmYi5kYXQuMSRUZWFtTmFtZSxtYW5hZ2VyLmNvcnJlY3Rpb24kVGVhbU5hbWUpXQpmZmIuZGF0LjEkT3Bwb3NpbmdNYW5hZ2VyPC1tYW5hZ2VyLmNvcnJlY3Rpb24kTWFuYWdlclttYXRjaChmZmIuZGF0LjEkT3Bwb25lbnQsbWFuYWdlci5jb3JyZWN0aW9uJFRlYW1OYW1lKV0KCiNOb3cgZml4IHBsYXllciByYW5rIGJ5IHJlbW92aW5nIHRoZSAnLicgYW5kIHRoZSAnKicKbGV2ZWxzKGZmYi5kYXQuMSRQbGF5ZXJSYW5rKTwtYygxLDIsMyw0LDEwLDUsNiw3LDgsOSkKZmZiLmRhdC4xJFBsYXllclJhbms8LWFzLm51bWVyaWMocGFzdGUoZmZiLmRhdC4xJFBsYXllclJhbmspKQoKYGBgCgoKTm93IHdlJ3JlIHNldCB0byBnbGFjZSBhdCB0aGUgZmlyc3QgZmV3IGxpbmVzIG9mIGRhdGEgd2UndmUgZ2F0aGVyZWQuCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIFdlIGNhbiBnZXQgYSBxdWljayBsb29rIGF0IHRoZSBkYXRhIHdlIGp1c3QgZ2F0ZWhyZWQKaGVhZChmZmIuZGF0LjEpCmBgYAoKCldlIGNhbiBhbHNvIHN1bW1hcml6ZSB0aGUgZGF0YSBieSBtYW5hZ2VyLgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBOb3cgbGV0cyBzdW1tYXJpemUgdGhlIHdpbm5pbmcgcGVyY2VudGFnZSBhbmQgdG90YWwgcG9pbnRzIHNjb3JlZCBmb3IgZWFjaCB0ZWFtIGluIGVhY2ggeWVhcgpyZXF1aXJlKHBseXIpCiMgV2UgbmVlZCBhIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgd2luIHBlcmNlbnRhZ2UgZm9yIHRoZSBzZWFzb24Kd2lucGVyYy5mdW5jPC1mdW5jdGlvbih4KXtsZW5ndGgod2hpY2goeD09IldpbiIpKS9sZW5ndGgoeCl9Cndpbi5mdW5jPC1mdW5jdGlvbih4KXtsZW5ndGgod2hpY2goeD09IldpbiIpKX0KbG9zcy5mdW5jPC1mdW5jdGlvbih4KXtsZW5ndGgod2hpY2goeD09Ikxvc3MiKSl9CgpmZmIuZGF0LnN1bTwtZGRwbHkoZmZiLmRhdC4xLGMoIlllYXIiLCJNYW5hZ2VyIiksc3VtbWFyaXNlLE1vdmVzPW1lYW4oYXMubnVtZXJpYyhNb3ZlcykpLFRyYWRlcz1tZWFuKGFzLm51bWVyaWMoVHJhZGVzKSksV2lucz13aW4uZnVuYyhSZXN1bHQpLExvc3M9bG9zcy5mdW5jKFJlc3VsdCksV2luUGVyY2VudD13aW5wZXJjLmZ1bmMoUmVzdWx0KSxUb3RhbD1zdW0oU2NvcmVkKSxQbGF5ZXJSYW5rPW1lYW4oUGxheWVyUmFuaykpCgojIENoZWNrIHRvIHNlZSBpZiBhbnkgY29sdW1ucyBoYXZlIE5BIHZhbHVlcwphcHBseShmZmIuZGF0LnN1bSwyLGFueU5BKQoKIyBBbmRzIGxldHMgbG9vayBhdCBhdmVyYWdlIHN0YXRzIGZvciBlYWNoIG1hbmFnZXIKZGRwbHkoZmZiLmRhdC5zdW0sYygiTWFuYWdlciIpLGNvbHdpc2UobWVhbikpWywtMl0KCmBgYAoKCkxldCdzIHN0YXJ0IHRvIGxvb2sgYXQgaG93IHRoZSBjb250aW51b3VzIHZhcmlhYmxlcyByZWxhdGUgdG8gZWFjaCBvdGhlciAtIGVzcGVjaWFsbHkgaG93IHBvdGVudGlhbCBleHBsYW5pdG9yeSB2YXJpYWJsZXMgKE1vdmVzLCBUcmFkZXMpIHJlbGF0ZSB0byByZXNwb25zZSB2YXJpYWJsZXMgKFdpbm5pbmcgUGVyY2VudGFnZSwgVG90YWwgUG9pbnRzKS4gSXQgc2VlbXMgYXMgaWYgVHJhZGVzIGFuZCBNb3ZlcyBhcmUgc2xpZ2h0bHkgY29ycmVsYXRlZCwgYW5kIFdpbm5pbmcgUGVyY2VudGFnZSBpcyB1bnN1cnByaXNpbmdseSBjb3JyZWxhdGVkIHdpdGggVG90YWwgUG9pbnRzIFNjb3JlZCBhbmQgYSBQbGF5ZXIncyBlbmQgb2YgeWVhciBSYW5rLiBJdCBhbHNvIGxvb2tzIGxpa2UgbnVtYmVyIG9mIFRyYWRlcyBpcyBwb3NzaWJseSBuZWdhdGl2ZWx5IHJlbGF0ZWQgdG8gdGhlIFdpbm5pbmcgUGVyY2VudGFnZSBhbmQgUmFuay4uLiBob2xkIG9uIHRvIHRoYXQgb25lIGZvciBsYXRlci4KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgQW5kIHdlIGNhbiBzdGFydCB0byBhc2sgaG93IHNvbWUgYWN0aW9ucyB0YWtlbiBieSB0aGUgbWFuYWdlcnMsIE1vdmVzIGFuZCBUcmFkZXMsIHJlbGF0ZSB0byBwb3RlbnRpYWwgc3VjY2Vzc2VzLCBXaW5zIGFuZCBUb3RhbCBwb2ludHMuIFdlIHVzZSB0aGUgJ2NvcicgZnVuY3Rpb24gZm9yIHRoaXMuCgpjb3IoZmZiLmRhdC5zdW1bLC1jKDE6MildKQpgYGAKClRoZXJlIGlzIGEgbW9kZXJhdGUgYW1vdW50IG9mIHZhcmlhdGlvbiBpbiBXaW5uaW5nIFBlcmNlbnRhZ2UgYW5kIFRvdGFsIFBvaW50cyBzY29yZWQgYW1vbmcgbWFuYWdlcnMsIHdoaWNoIHdlIGNhbiB2aXN1YWxpc2UgaW4gYm94IHBsb3RzLiBXZSBjYW4gc2VlIHRoYXQgTGlzYSBhbmQgUEMgaGF2ZSBzdXByaXNpbmdseSBsaXR0bGUgdmFyaWFpdG9uIGkgdGhlaXIgdG90YWwgcG9pbnRzIHNjb3JlZCBmcm9tIHllYXIgdG8geWVhciwgd2hpbGUgUmljayBoYXMgYSBsb3Qgb2YgdmFyaWFpdG9uIGluIGhpcyB3aW5uaW5nIHBlcmNlbnRhZ2UuIEFsdGhvdWdoIFdpbm5pbmcgUGVyY2VudGFnZSBhbmQgVG90YWwgUG9pbnRzIHZhcnkgYW1vbmcgbWFuYWdlcnMsIHdlJ2QgbGlrZSB0byBrbm93IGlmIGRpZmZlcmVuY2VzIGFyZSBzaWduaWZpY2FudCAtIGVzcGVjaWFsbHkgYmVmb3JlIHdlIHN0YXJ0IHRvIHJ1biBzaW11bGFpdG9ucy4KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgV2UgY2FuIGFsc28gcXVpY2tseSBsb29rIGF0IGhvdyBXaW5zIGFuZCBUb3RhbCBwb2ludHMgdmFyaWVkIGFtb25nIG1hbmFnZXIgd2l0aCBib3ggcGxvdHMKcmVxdWlyZShnZ3Bsb3QyKQpnZ3Bsb3QoZmZiLmRhdC5zdW0sYWVzKHg9TWFuYWdlcix5PVdpblBlcmNlbnQpKSsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZT0iV2lubmluZyBQZXJjZW50YWdlIikrCiAgZ2VvbV9ib3hwbG90KCkrCiAgdGhlbWVfYncoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQoKZ2dwbG90KGZmYi5kYXQuc3VtLGFlcyh4PU1hbmFnZXIseT1Ub3RhbCkpKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lPSJUb3RhbCBQb2ludHMgU2NvcmVkIGluIGEgU2Vhc29uIikrCiAgZ2VvbV9ib3hwbG90KCkrCiAgdGhlbWVfYncoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQoKYGBgCgoKRm9yIGEgbW9yZSBpbi1kZXB0aCBhbmFseXNpcywgd2UgdXNlIGEgbGluZWFyIG1vZGVsIHRvIHRlc3QgaG93IE1hbmFnZXIsIE1vdmVzLCBhbmQgVHJhZGVzIGFmZmVjdCBUb3RhbCBwb2ludHMgc2NvcmVkLiBGaXJzdCB3ZSBjZW50ZXIgYW5kIHNjYWxlIHRoZSBNb3ZlcyBhbmQgVHJhZGVzIGRhdGEsIHNpbmNlIHRoZXJlIHdhcyBzb21lIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlbSBhbmQgd2Ugd2FudCB0byBhdm9pZCBtdWx0aWNvbGluZWFyaXR5LiBBZnRlciBjcmVhdGluZyB0aGUgbW9kZWwsIHdlIGNoZWNrIHNvbWUgZGlhZ25vc3RpYyBwbG90cyB0byBlbnN1cmUgdGhlIG1vZGVsIGlzIGZpdHRpbmcgY29ycmVjdGx5IGFuZCB0aGF0IHRoZSByZXNpZHVhbHMgYXJlIHdlbGwgZGlzdHJpYnV0ZWQgYW5kIG5vdCBiaWFzZWQuCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIExldHMgY2VudGVyIGFuZCBzY2FsZSB0aGUgZGF0YSB0byBtaW5pbWl6ZSB0aGUgbXVsdGljb2xpbmVhcml0eSBhbW9uZyBleHBsYW5hdG9yeSB2YXJpYWJsZXMKZmZiLmRhdC5zdW0uc2NhbGU8LWZmYi5kYXQuc3VtCmZmYi5kYXQuc3VtLnNjYWxlWyxjKDM6NCldPC1hcHBseShmZmIuZGF0LnN1bS5zY2FsZVssYygzOjQpXSwyLHNjYWxlKQpmZmIuZGF0LnRvdGFsLmxtPC1sbShUb3RhbH5NYW5hZ2VyK01vdmVzK1RyYWRlcyxmZmIuZGF0LnN1bS5zY2FsZSkKCiMgUGxvdCB0aGUgZGlhZ25vc3RpY3MsIHdoY2loIHNlZW0gZmluZSBhbmQgdGhlIGRhdGEgc2VlbSB0byBmaXQgYXNzdW10aW9ucyBvZiBub3JtYWxpdHkgcHJldHR5IHdlbGwKcGxvdChmZmIuZGF0LnRvdGFsLmxtLHdoaWNoPWMoMToyKSkKCmBgYAoKV2UgY2FuIG5vdyB2aWV3IGEgc3VtbWFyeSBvZiB0aGUgbW9kZWwsIHdoaWNoIGFjY291bnRzIGZvciB+MzAlIG9mIHRoZSB2YXJpYXRpb24gaW4gVG90YWwgUG9pbnRzIHNjb3JlZCBhZnRlciBhY2NvdW50aW5nIGZvciB0aGUgbnVtYmVyIG9mIHBhcmFtZXRlcnMgd2UgdXNlIChBZGp1c3RlZCBSLVNxdWFyZWQpLiBXZSBjYW4gYWxzbyBzZWUgdGhhdCB0aGUgbW9kZWwgcmVzaWR1YWxzIGxvb2sgcmVsYXRpdmVseSBub3JtYWwgKG1vc3RseSBldmVubHkgZGlzdHJpYnV0ZWQgYXJvdW5kIHRoZSBtZWRpYW4pLgpgYGB7cn0KI3Nob3cgdGhlIHJlc3VsdHMKc3VtbWFyeShmZmIuZGF0LnRvdGFsLmxtKQpgYGAKCkEgbG9vayBhdCB0aGUgaW5kaXZpZHVhbCBlZmZlY3RzIGluZGljYXRlcyB0aGF0IE1hbmFnZXIgYWNjb3VudHMgZm9yIGEgc2lnbmlmaWNhbnQgYW1vdW50IG9mIHRoZSB2YXJpYXRpb24gaW4gVG90YWwgUG9pbnRzIHNjb3JlcywgYnV0IE1vdmVzIGFuZCBUcmFkZXMgZG8gbm90IGhlbHAgdXMgZXhwbGFpbiBwb2ludHMgc2NvcmVkIGluIGEgc2Vhc29uLgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBBbmQgdGVzdCB0aGUgZWZmZWN0cwphbm92YShmZmIuZGF0LnRvdGFsLmxtKQpgYGAKCgpOZXh0IHdlIGxvb2sgYXQgaG93IHRoZSBhY3R1YWwgd2lubmluZyBwZXJjZW50YWdlIHJlbGF0ZXMgdG8gTWFuYWdlciwgTW92ZXMgYW5kIFRyYWRlcyB1c2luZyBsb2dpc3RpYyByZWdyZXNzaW9uLiBBZ2Fpbiwgd2UgYXNzZXNzIHNvbWUgZGlhZ25vc3RpYyBwbG90cyBhbmQgdGhlIG1vZGVsIHNlZW1zIHRvIGZpdCBPSyAoYSBsaXR0bGUgZnVua3kgYXQgdGhlIGJvdHRvbSBvZiB0aGUgUS1RIHBsb3QpLgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBOb3cgbG9vayBhdCB0aGUgZWZmZWN0cyBvbiB3aW5uaW5nCmZmYi5kYXQud2luLmxtPC1nbG0oY2JpbmQoV2lucyxMb3NzKSB+IE1hbmFnZXIrTW92ZXMrVHJhZGVzLGZhbWlseT1iaW5vbWlhbChsaW5rPSdsb2dpdCcpLGRhdGE9ZmZiLmRhdC5zdW0uc2NhbGUpCgojIEFnYWluLCB0aGUgbW9kZWwgc2VlbXMgYXMgdGhvdWdoIGl0IGl0cyB3ZWxsCnBsb3QoZmZiLmRhdC53aW4ubG0sd2hpY2g9YygxOjIpKQoKYGBgCgoKV2UgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBtb2RlbCwgYW5kIHdlIGNhbiBzZWUgdGhhdCBib3RoIE1hbmdlciBhbmQgVHJhZGVzIHNlZW0gdG8gYmUgaW1wb3J0YW50IHRvIGV4cGxhaW5pbmcgV2lubmluZyBQZXJjZW50YWdlLgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBOb3cgbGV0cyBsb29rIGF0IHRoZSBtb2RlbCBhbmQgdGhlIGVmZmVjdHMKc3VtbWFyeShmZmIuZGF0Lndpbi5sbSkKYW5vdmEoZmZiLmRhdC53aW4ubG0sdGVzdD0iQ2hpc3EiKQoKYGBgCgoKV2Uga25vdyBmcm9tIHRoZSBib3ggcGxvdHMgYmVmb3JlIHRoYXQgd2lubmluZyBwZXJjZW50YWdlIGlzIHJlbGF0aXZlbHkgdmFyaWFibGUgYW1vbmcgbWFuYWdlcnMsIHNvIGxldCdzIHRha2UgYSBjbG9zZXIgbG9vayBhdCBob3cgdHJhZGVzIGFmZmVjdCB3aW5uaW5nIHBlcmNlbnRhZ2UuIEZpcnN0IHdlIGxvb2sgYXQgdGhlIG1vZGVsIGNvZWZmaWNpZW50LCB3aGljaCBzZWVtcyB0byBpbmRpY2F0ZSB0aGF0IFRyYWRpbmcgcGxheWVycyBoYXMgYSBORUdBVFZJRSBlZmZlY3Qgb24gd2lubmluZyBwZXJjZW50YWdlPwpgYGB7cn0KIyBOb3cgd2UgY2FuIGxvb2sgYXQgdGhlIGVmZmVjdCBUcmFkZXMgaGFzIG9uIHdpbm5pbmcsIHdoaWNoIHNlZW1zIHRvIGJlIG5lZ2F0aXZlCmZmYi5kYXQud2luLmxtJGNvZWZmaWNpZW50c1siVHJhZGVzIl0KYGBgCgpUaGlzIHNlZW1zIG9kZC4uLiBvciBhdCB0aGUgdmVyeSBsZWFzdCwgSSdkIGxpa2UgdG8gYmUgY2VydGFpbiBvZiB0aGUgcmVzdWx0IGJlZm9yZSByZWplY3RpbmcgZXZlcnkgdHJhZGUgbXkgZnJpZW5kcyBvZmZlciBtZS4gQnkgcGxvdHRpbmcgd2lubmluZyBwZXJjZW50YWdlIGFzIGEgZnVuY3Rpb24gb2YgbnVtYmVyIG9mIHRyYWRlcywgd2UgY2FuIHNlZSBhbiBvdXRsaWVyIHdpdGggYSBoaWdoIG51bWJlciBvZiB0cmFkZXMgYW5kIGEgbG93IHdpbm5pbmcgcGVyY2VudGFnZSB0aGF0IGNvdWxkIGJlIGRyaXZpbmcgb3VyIHBhdHRlcm4uCmBgYHtyfQojIExldCdzIHBsb3Qgd2lubmluZyBwZXJjZW50YWdlIGFzIGEgZnVuY3Rpb24gb2YgdHJhZGVzIHRvIHNlZSB3aGF0cyBnb29pbmcgb24uCiMgSXQgc2VlbXMgbGlrZSBtb3JlIG9mIGEgc2FtcGxlIHNpemUgYW5kIHZhcmlhbmNlIGlzc3VlIHRoYW4gYW4gYWN0dWFsIHRyYWRlIGVmZmVjdC4Kd2l0aChmZmIuZGF0LnN1bSxwbG90KFdpblBlcmNlbnR+VHJhZGVzKSkKYGBgCgpXZSByZW1vdmUgdGhpcyBvdXRsaWVyIGFuZCByZS1ydW4gdGhlIG1vZGVsIHRvIHNlZSBpZiB0aGUgcG9pbnQgd2FzIGRyaXZpbmcgbW9zdCBvZiBvdXIgcmVzdWx0cyBmb3IgVHJhZGVzLiBJdCBzZWVtcyB0byBoYXZlIGJlZW4sIHNpbmNlIHRoZSBUcmFkZXMgZWZmZWN0IGRpc2FwcGVhcnMuLi4gZ29vZCB0byBrbm93IGZvciBuZXh0IHNlYXNvbi4KYGBge3J9CiMgSWYgd2UgcmVtb3ZlIHRoYXQgcG9uZSBvdXRsaWVyLCB0aGUgZWZmZWN0IGRpc3NhcGVhcnMKZmZiLmRhdC5zdW0uZml4PC1mZmIuZGF0LnN1bVstd2hpY2goZmZiLmRhdC5zdW0kVHJhZGVzPT00KSxdCmZmYi5kYXQuc3VtLmZpeC5zY2FsZTwtZmZiLmRhdC5zdW0uZml4CmZmYi5kYXQuc3VtLmZpeC5zY2FsZVssYygzLDQpXTwtYXBwbHkoZmZiLmRhdC5zdW0uZml4WyxjKDMsNCldLDIsc2NhbGUpCmZmYi5kYXQud2luLmxtPC1nbG0oY2JpbmQoV2lucyxMb3NzKSB+IE1hbmFnZXIrTW92ZXMrVHJhZGVzLGZhbWlseT1iaW5vbWlhbChsaW5rPSdsb2dpdCcpLGRhdGE9ZmZiLmRhdC5zdW0uZml4LnNjYWxlKQphbm92YShmZmIuZGF0Lndpbi5sbSx0ZXN0PSJDaGlzcSIpCmBgYAoKCldlIGNhbiBhbHNvIGxvb2sgYXQgdGhlIHBhaXJ3aXNlIHByb2JhYmlsaXR5IHRoYXQgZWFjaCBtYW5hZ2VyIHdpbGwgYmVhdCBhbm90aGVyIG1hbmFnZXIgaW4gYSBtYXRjaCB1cC4gV2UgZG8gc28gaW4gYSB0YWJsZSAoZGlzcGxheWVkIGJlbG93IGFzIGEgaGVhdCBtYXApIHdoZXJlIGNvbHVtbnMgYXJlIHRoZSBtYW5hZ2VyIG9mIGludGVyZXN0LCBhbmQgdGhlIHJvd3MgYXJlIHRoZWlyIHBvdGVudGlhbCBvcHBvbmVudHMuIEVhY2ggdmFsdWUgcmVmbGVjdHMgdGhlIHByb2JhYmlsaXR5IHRoYXQgdGhlIG1hbmdlciB3aWxsIGJlYXQgZWFjaCBpbmRpdmlkdWFsIG9wcG9uZW50ICh0b3RhbCB3aW5zL2dhbWVzIHBsYXllZCksIGFuZCBlYWNoIGNvbHVtbiBzdW1zIHRvIDEgKGkuZS4sIDEwMCUpLiBXZSBjYW4gYWxyZWFkeSBzZWUgdGhhdCBTcGVhcnMgYW5kIEFuZHJldyBoYXZlIHJlbGF0aXZlbHkgbG93IHByb2JhYmlsaXRpZXMgb2YgYmVhdGluZyBhbnkgb3RoZXIgbWFuYWdlciwgYWx0aG91Z2ggQW5kcmV3IG9mdGVuIGJlYXRzIFNwZWFycy4gV2UgYWxzbyBzZWUgdGhhdCB3aGlsZSBTcGVhcnMgbWF5IGxvc2UgYSBsb3QsIGhlIGJlYXRzIG1lIChTY290dCkgbW9yZSBvZnRlbiB0aGFuIG5vdC4uLiBzdWNoIGlzIGxpZmUuCmBgYHtyLCBmaWcuaGVpZ2h0PTIuNSwgZmlnLndpZHRoPTMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgojIE5vdyB3ZSBjcmVhdGUgYSBsb29rdXAgdGFibGUgZm9yIHRoZSBudW1iZXIgb2Ygd2lucyBhbmQgbG9zc2VzIGZvciBlYWNoIG1hdGNodXAKIyBJbiB0aGlzIGNhc2UsIHJvd3MgcmVwcmVzZW50IHRoZSBtYW5hZ2VyIHRvIGxvb2t1cCwgYW5kIHRoZSBjb2x1bW5zIHJlcHJlc2VudCB0aGUgbnVtYmVyIG9mIHdpbnMgKHdpbi50YWJsZSkgb3IgbG9zc2VzIChsb3NlLnRhYmxlKSBhZ2FpbnN0IGVhY2ggb3RoZXIgdGVhbSAoY29sdW1ucykuCndpbi50YWJsZTwtZmZiLmRhdC4xW3doaWNoKGZmYi5kYXQuMSRSZXN1bHQ9PSJXaW4iKSxjKCJNYW5hZ2VyIiwiT3Bwb3NpbmdNYW5hZ2VyIildCndpbi50YWJsZTwtdGFibGUod2luLnRhYmxlKQpsb3NlLnRhYmxlPC1mZmIuZGF0LjFbd2hpY2goZmZiLmRhdC4xJFJlc3VsdD09Ikxvc3MiKSxjKCJNYW5hZ2VyIiwiT3Bwb3NpbmdNYW5hZ2VyIildCmxvc2UudGFibGU8LXRhYmxlKGxvc2UudGFibGUpCgojIFdlIG5vdyBjYWxjdWxhdGUgdGhlIHByb2JhYmlsaXR5IG9mIGEgdmljdG9yeSBmb3IgZWFjaCBtYXRjaHVwCkpPRC5NQ00uZnVsbDwtd2luLnRhYmxlLyh3aW4udGFibGUrbG9zZS50YWJsZSkKCiMgV2UgY2FuIHVzZSB0aGUgJ3Jlc2hhcGUnIHBhY2thZ2UgdG8gbWVsdCBkb3duIHRoZSBwcm9iYWJpbGl0eSB0YWJsZSBzbyB3ZSBjYW4gcGxvdCBpdCBhcyBhIGhlYXRtYXAKcmVxdWlyZShyZXNoYXBlKQpKT0QuTUNNLnRhYmxlPC1tZWx0KEpPRC5NQ00uZnVsbCkKCiMgVGhlIGhlYXRtYXAgY2FuIHNob3cgdGhlIHByb2JhYmlsdHkgb2YgYSBtYW5hZ2VyIChjb2x1bW5zKSBiZWF0aW5nIGFub3RoZXIgdGVhbSAocm93cyksIHdpdGggaGlnaGVyIHByb2JhYmlsaXRlcyByZWZsZWN0ZWQgaW4gZGVlcGVyIHJlZHMuIEkndmUgYWxzbyBpbmNsdWRlZCB0aGUgYWN0dWFsIHByb2JhYmlsaXR5IGluIGVhY2ggY2VsbCBvZiB0aGUgaGVhdG1hcCBmb3IgZWFzeSByZWZlcmVuY2UKZ2dwbG90KEpPRC5NQ00udGFibGUsIGFlcyh4ID0gTWFuYWdlciwgeSA9IE9wcG9zaW5nTWFuYWdlcikpICsKICBnZW9tX3RpbGUoYWVzKGZpbGwgPSB2YWx1ZSkpICsKICBzY2FsZV95X2Rpc2NyZXRlKG5hbWU9Ik9wcG9zaW5nIE1hbmFnZXIiKSsKICBnZW9tX3RleHQoYWVzKGxhYmVsPXJvdW5kKHZhbHVlLDIpKSkrCiAgc2NhbGVfZmlsbF9ncmFkaWVudChuYS52YWx1ZSA9ICdibGFjaycsbmFtZT0iUHJvYmFiaWxpdHlcbm9mIFdpbm5pbmciLGxvdz0id2hpdGUiLGhpZ2g9ImRhcmsgcmVkIikrCiAgdGhlbWVfYncoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQoKYGBgCgoKTm93IGl0J3MgdGltZSBmb3IgYSBzaW11bGF0aW9uISBXZSBmaXJzdCBjcmVhdGUgYW5vdGhlciBwcm9iYWJpbGl0eSB0YWJsZSBhcyBhYm92ZSwgZXhjZXB0IG9ubHkgd2l0aCBkYXRhIGZyb20gMjAxMS0yMDEzLiBXZSBzYXZlIHRoZSByZW1haW5pbmcgZGF0YSB0byBjaGVjayB0aGUgc2ltdWxhdGlvbiByZXN1bHRzIGFnYWluc3QuCgpUaGVuLCB3ZSBjcmVhdGUgYSB0ZW1wbGF0ZSBmb3IgYSBGYW50YXN5IEZvb3RiYWxsIHJlZ3VsYXIgc2Vhc29uIHNjaGVkdWxlICgxNCBnYW1lcykuIFRoZSBlYXNpZXN0IHdheSB0byBkbyB0aGlzIGlzIHRvIHRha2UgYSBzY2hlZHVsZSB0aGF0IGFscmVhZHkgZXhpc3RzLCBsaWtlIHRoYXQgZnJvbSBvdXIgMjAxNCBZYWhvbyBzZWFzb24sIGFuZCB1c2UgaXQgYXMgYSB0ZW1wbGF0ZSB3aGVyZSBlYWNoIHRlYW0gaXMgYSBudW1iZXIuIFdlIGNhbiB0aGVuIHJhbmRvbWl6ZSB3aGljaCB0ZWFtIGNvcnJlc3BvbmRzIHRvIGVhY2ggbnVtYmVyIGZvciBhIG5ldyBzZWFzb24ncyBzY2hlZHVsZS4KCkxhc3RseSwgd2UgY3JlYXRlIGEgc3RyaW5nIG9mIGZvci1sb29wcyB0aGF0IHJhbmRvbWx5IGdlbmVyYXRlcyBhIGZhbnRhc3kgZm9vdGJhbGwgc2NoZWR1bGUsIGl0ZXJhdGl2bHkgZ28gdGhyb3VnaCB0aGUgc2NoZWR1bGUsIGFuZCBmb3IgZWFjaCBtYXRjaC11cC9nYW1lIGluIHRoZSBsaXN0LCBsb29rIHVwIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGVpdGhlciBtYW5hZ2VyIGJlYXRzIHRoZSBvcHBvc2luZyBvbmUuIFRoZW4sIHdlIGNob29zZSBiZXR3ZWVuIHRoZSBvcHBvc2luZyBtYW5hZ2Vycywgd2VpZ2h0aW5nIG91ciBjaG9pY2UgYnkgdGhlaXIgcmVzcGVjdGl2ZSBwcm9iYWJpbGl0aWVzIG9mIHdpbm5pbmcgdGhlIG1hdGNoIHVwLiBUaGUgY2hvaWNlIGlzIHRoZSB3aW5uZXIgb2YgdGhlIG1hdGNoIQoKVGhpcyBoYXBwZW5zIG92ZXIgYW5kIG92ZXIgYW5kIG92ZXIuLi4gYWdhaW4gdW50aWwgdGhlIHJlZ3VsYXIgc2Vhc29uIGlzIG92ZXIuIFRoZW4sIHRoZSByZWd1bGFyIHNlYXNvbiByYW5rcyBhcmUgY2FsY3VsYXRlZCwgYW5kIG1hbmFnZXJzIGFyZSBwbGFjZWQgaW50byBhIHBsYXlvZmYgYnJhY2tldCB3aXRoIFJhbmtzIDEgdnMgNCwgMiB2cyAzLCA1IHZzIDgsIGFuZCA2IHZzIDcuIFRlYW1zIHJhbmtlZCA5IGFuZCAxMCBzdGF5IGF0IHRob3NlIHJhbmtzIHRvIHRoaW5rIGFib3V0IGhvdyBhd2Z1bCB0aGV5IGFyZSBhbmQgcGxhbiBmb3IgbmV4dCB5ZWFyLiBUaGlzIGZvcm1hdCBpcyBob3cgb3VyIGxlYWd1ZSBhY3R1YWxseSBvcGVyYXRlcyBvbiBZYWhvby4KClRoZSByZXN1bHRzIGZyb20gdGhlIGZpbmFscyBhcmUgcGxhY2VkIGludG8gYSBkYXRhLXRhYmxlLCB5aWVsZGluZyBhIHJhbmsgZm9yIGVhY2ggbWFuYWdlciAoY29sdW1ucykgZm9yIHRoZSBzZWFzb24gKHJvdykuIFRoZSBlbnRpcmUgbG9vcCBzdGFydHMgb3ZlciBhZ2FpbiBmb3IgYSBuZXcgc2Vhc29uLCBhbmQgcmVwZWF0cyB1bnRpbCB3ZSdyZSBzaW11bGF0ZWQgNTAwIHNlYXNvbnMuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBOb3cgd2UgY3JlYXRlIGFub3RoZXIgcHJvYmFiaWxpdHkgdGFibGUganVzdCBhcyBiZW9mb3JlLCBidXQgb25seSB1c2luZyB0aGUgZmlyc3QgMyB5ZWFycyBvZiBkYXRhCndpbi50YWJsZS5zdWI8LWZmYi5kYXQuMVt3aGljaChmZmIuZGF0LjEkUmVzdWx0PT0iV2luIiAmIGZmYi5kYXQuMSRZZWFyPD0yMDEzKSxjKCJNYW5hZ2VyIiwiT3Bwb3NpbmdNYW5hZ2VyIildCndpbi50YWJsZS5zdWI8LXRhYmxlKHdpbi50YWJsZS5zdWIpCmxvc2UudGFibGUuc3ViPC1mZmIuZGF0LjFbd2hpY2goZmZiLmRhdC4xJFJlc3VsdD09Ikxvc3MiJiBmZmIuZGF0LjEkWWVhcjw9MjAxMyksYygiTWFuYWdlciIsIk9wcG9zaW5nTWFuYWdlciIpXQpsb3NlLnRhYmxlLnN1YjwtdGFibGUobG9zZS50YWJsZS5zdWIpCgpKT0QuTUNNPC13aW4udGFibGUuc3ViLyh3aW4udGFibGUuc3ViK2xvc2UudGFibGUuc3ViKQoKIyBCZWZvcmUgd2Ugc2ltdWxhdGUgc2Vhc29ucyBvZiBmYW50YXN5IGZvb3RiYWxsLCB3ZSBuZWVkIHRvIGdlbmVyYXRlIHNjaGVkdWxlcy4gVGhlIGVhc2llc3Qgd2F5IHRvIGRvIHRoaXMgaXMgdG8gdGFrZSBhIHNjaGVkdWxlIHRoYXQgYWxyZWFkeSBleGlzdHMsIGxpa2UgdGhhdCBmcm9tIDIwMTQsIGFuZCB1c2UgaXQgYXMgYSB0ZW1wbGF0ZSB3aGVyZSB3ZSBjYW4gcmFuZG9taXplIHdoaWNoIHRlYW0gY29ycm9zcG9uZHMgdG8gZWFjaCBudW1iZXIgZm9yIG5ldyBzZWFzb24gc2NoZWR1bGVzCgptYXRjaHVwLnRhYmxlLnJhdzwtbWF0cml4KG1hdGNoKGFzLnZlY3RvcihzdWJzZXQoZmZiLmRhdC4xLFllYXI9PTIwMTQpWyxjKDYpXSksYXMudmVjdG9yKHVuaXF1ZShzdWJzZXQoZmZiLmRhdC4xLFllYXI9PTIwMTQpWyxjKDUpXSkpKSxuY29sPTE0LGJ5cm93PVRSVUUpCgojIFdlIG5vdyBzZXR1cCBhbiBhcnJheSB0byBob2xkIGFsbCBvZiB0aGUgc2ltdWxhdGlvbiBkYXRhIChlbmQgb2Ygc2Vhc29uIHRlYW0gcmFua2luZ3MpCiMgSSd2ZSBjaG9zZW4gdG8gc2ltdWxhdGUgNTAwIHNlYXNvbnMKc2ltLnNlYXNvbnM8LWFycmF5KE5BLGMoNTAwLDEwKSkKY29sbmFtZXMoc2ltLnNlYXNvbnMpPC1hcy52ZWN0b3Iocm93bmFtZXMoSk9ELk1DTSkpCgojIE5vdyBmb3IgNTAwIHNpbXVsYXRlZCBzZWFzb25zIHVzaW5nIGZvci1sb29wcwpmb3IgKHMgaW4gMTo1MDApewojRmlyc3QsIGNyZWF0ZSBhIHNlYXNvbnMgc2NoZWR1bGUgYnkgcmFuZG9tbHkgYXNzaWduaW5nIHRlYW1zL21hbmFnZXJzIHRvIGEgbnVtYmVyLCBhbmQgcGxhY2VpbmcgdGhlbSBpbnRvIHRoZSBzY2hlZHVsZSB0ZW1wbGF0ZSBmb3IgdGhlIDE0IHJlZ3VsYXIgc2Vhc29uIGdhbWVzCm1hdGNodXAubmFtZXM8LXNhbXBsZShhcy52ZWN0b3Iocm93bmFtZXMoSk9ELk1DTSkpLDEwKQptYXRjaHVwLnRhYmxlPC1tYXRyaXgobWF0Y2h1cC5uYW1lc1ttYXRjaHVwLnRhYmxlLnJhd10sbmNvbD0xNCkKcm93bmFtZXMobWF0Y2h1cC50YWJsZSk8LW1hdGNodXAubmFtZXMKY29sbmFtZXMobWF0Y2h1cC50YWJsZSk8LWMoMToxNCkKCiNUaGVuLCBjcmVhdGUgYSBkYXRhIGZyYW1lIHRvIGhvbGQgdGhlIHNjaGVkdWxlZCBtYXRjaHVwLCBhbmQgZXZlbnR1YWxseSB0aGUgcmVzdWx0cyBvZiB0aGUgbWF0Y2h1cAptYXRjaHVwLnNpbTwtZGF0YS5mcmFtZShXZWVrPU5BLE1hbmFnZXI9TkEsT3Bwb25lbnQ9TkEsV2lubmVyPU5BKQoKIyBMb29wIG92ZXIgdGhlIG1hdGNodXBzIGluIHRoZSBzY2hlZHVsZSBhbmQgb25seSBpbnNlcnQgdW5pcXVlIG1hdGNodXBzIGZvciB0aGUgc2Vhc29uIChzaW5jZSB0aGUgc2NoZWR1bGUgdGVtcGxhdGUgZ2l2ZXMgbWF0Y2h1cCAnQSB2cyBCJyBhcyB3ZWxsIGFzICdCIHZzIEEnKQpmb3IgKGkgaW4gMTpuY29sKG1hdGNodXAudGFibGUpKXsKZm9yKGogaW4gMTpucm93KG1hdGNodXAudGFibGUpKXsKICBpZihqPT0xJmk9PTEpe21hdGNodXAuc2ltWzEsXTwtYyhpLHJvd25hbWVzKG1hdGNodXAudGFibGUpW2pdLG1hdGNodXAudGFibGVbaixpXSxOQSl9ZWxzZXsKICBpZihhbnkocm93bmFtZXMobWF0Y2h1cC50YWJsZSlbal09PXN1YnNldChtYXRjaHVwLnNpbSxXZWVrPT1pKVssM10pPT1GQUxTRSl7CiAgICBtYXRjaHVwLnNpbTwtcmJpbmQobWF0Y2h1cC5zaW0sZGF0YS5mcmFtZShXZWVrPWksTWFuYWdlcj1yb3duYW1lcyhtYXRjaHVwLnRhYmxlKVtqXSxPcHBvbmVudD1tYXRjaHVwLnRhYmxlW2osaV0sV2lubmVyPU5BKSkKICB9fQp9fQoKIyBOb3cgdG8gZmluZCBvdXQgdGhlIHdpbm5lcnMgb2YgdGhlIG1hdGNodXBzCmZvcihpIGluIDE6bnJvdyhtYXRjaHVwLnNpbSkpewojIEZvciBlYWNoIG1hdGNodXAgaW4gdGhlIGxpc3QsIGxvb2t1cCB0aGUgcHJvYmFiYWJpbGl0eSB0aGF0IHRoZSBtYW5hZ2VyIChob21lIHRlYW0pIGJlYXRzIHRoZSBvcHBvc2luZyBtYW5hZ2VyLCBhbmQgdGhlIHByb2JhYmlsaXR5IHRoZSBvcG9zaW5nIG1hbmFnZXIgd2lucyAoanVzdCAxLVAsIGJ1dCB3ZSBsb29rIGl0IHVwIGFueXdheSkKICBtYW5hbmdlci5wcm9iPC1KT0QuTUNNW21hdGNodXAuc2ltJE1hbmFnZXJbaV0sbWF0Y2h1cC5zaW0kT3Bwb25lbnRbaV1dCiAgb3Bwb25lbnQucHJvYjwtSk9ELk1DTVttYXRjaHVwLnNpbSRPcHBvbmVudFtpXSxtYXRjaHVwLnNpbSRNYW5hZ2VyW2ldXQoKICAjIENob29zZSBiZXR3ZWVuIHRoZSBtYW5hZ2VyIGFuZCB0aGUgb3Bwb3NpbmcgbWFuYWdlciB3aXRoIHByb2JhYmlsaXR5IG1hbmFnZXIucHJvYiBhbmQgb3Bwb25lbnQucHJvYiByZXNwZWN0aXZlbHkuIFRoZSBjaG9pY2UgaW4gdGhlIHdpbm5lciBvZiB0aGUgbWF0Y2ghCiAgbWF0Y2h1cC5zaW0kV2lubmVyW2ldPC1zYW1wbGUoYyhtYXRjaHVwLnNpbSRNYW5hZ2VyW2ldLG1hdGNodXAuc2ltJE9wcG9uZW50W2ldKSwxLHByb2I9YyhtYW5hbmdlci5wcm9iLG9wcG9uZW50LnByb2IpKQp9CgojIFN1bW1hcml6ZSB0aGUgcmVzdWx0cyBvZiB0aGUgcmVndWxhciBzZWFzb24gbWF0Y2h1cHMgYnkgdXNpbmcgdGhlIGZyZXF1ZW5jeSBvZiB0aGUgbWFuYWdlcnMgbmFtZSBpbiB0aGUgd2lubmVyIGNvbHVtbiwgdGhlbiByYW5rIHRoZSBtYW5hZ2VycyBieSB0aGUgbnVtYmVyIG9mIHdpbnMgZm9yIHRoZSBlbmQgb2YgcmVndWxhciBzZWFzb24gcmFua2luZ3MKcmVnLnJlc3VsdHM8LXRhYmxlKG1hdGNodXAuc2ltJFdpbm5lcikKcmVnLnJhbms8LW5hbWVzKHJlZy5yZXN1bHRzKVtvcmRlcihyZWcucmVzdWx0cyxkZWNyZWFzaW5nID0gVFJVRSldCgojIE5vdyB1c2UgdGhlIHJlZ3VsYXIgc2Vhc29uIHJhbmtpbmdzIHRvIHNldHVwIHRoZSBtYXRodXBzIGluIHRoZSBzZW1pZmluYWwgcm91bmQKIyBDcmVhdHJlIGEgbWF0cml4IGJ5IHJhbmssIGJ1dCBzd2l0Y2ggdGhlIDJuZCBhbmQgNHRoIHJhbmtlZCwgYW5kIDZ0aCBhbmQgOHRoIHJhbmtlZCBwbGF5ZXJzIHRvIGNyZWF0ZSB0aGUgYXBwcm9wcmlhdGUgbWF0Y2h1cHMKc2VtaWZpbmFsPC1tYXRyaXgocmVnLnJhbmssbmNvbD0yLGJ5cm93PVRSVUUpCm5ldy5tYXRjaDwtYyhzZW1pZmluYWxbMiwyXSxzZW1pZmluYWxbMSwyXSxzZW1pZmluYWxbNCwyXSxzZW1pZmluYWxbMywyXSxzZW1pZmluYWxbNSwyXSkKc2VtaWZpbmFsWywyXTwtbmV3Lm1hdGNoCgojIEFkZCBhIGNvbHVtbiBmb3IgdGhlIHdpbm5lcnMgdG8gYmUgcmVjb3JkZWQgaW4Kc2VtaWZpbmFsPC1jYmluZChzZW1pZmluYWwscmVwKE5BLDUpKQoKIyBOb3cgc2ltdWxhdGUgdGhlIHNlbWlmaW5hbCBpbiB0aGUgc2FtZSB3YXkgYXMgdGhlIHJlZ3VsYXIgc2Vhc29uCmZvcihpIGluIDE6bnJvdyhzZW1pZmluYWwpKXsKICBtYW5hbmdlci5wcm9iPC1KT0QuTUNNW3NlbWlmaW5hbFtpLDFdLHNlbWlmaW5hbFtpLDJdXQogIG9wcG9uZW50LnByb2I8LUpPRC5NQ01bc2VtaWZpbmFsW2ksMl0sc2VtaWZpbmFsW2ksMV1dCiAgc2VtaWZpbmFsW2ksM108LXNhbXBsZShjKHNlbWlmaW5hbFtpLDFdLHNlbWlmaW5hbFtpLDJdKSwxLHByb2I9YyhtYW5hbmdlci5wcm9iLG9wcG9uZW50LnByb2IpKQp9CiMgVGhlIGZpbmFsIHR3byBzcG90cyBkbyBub3QgY29tcGV0ZSBpbiBvdXIgc2VtaWZpbmFscyBvciBmaW5hbHMsIHNvIHdlIGF1dG9tYXRpY2FsbHkgcGxhY2UgdGhlIGJldHRlciByYW5rZWQgaW5kaXZpZHVhbCBpbnRvIHRoZSB3aW5uZXJzIHNwb3QKc2VtaWZpbmFsWzUsM108LXNlbWlmaW5hbFs1LDFdCgojIFVzZSB0aGUgZnJlcXVlbmN5IG9mIG5hbWVzIHRvIGRldGVybWluZSB3aGljaCB0ZWFtcy9tYW5hZ2VycyBtb3ZlIHVwIGEgc3BvdCAod2lubmVycyB3aXRoIG1vcmUgaW5zdGFuY2VzIG9mIHRoZWlyIG5hbWUpIGFuZCB3aGljaCBtb3ZlIGRvd24gYSBzcG90IChsb3NlcnMgd2l0aCBmZXdlciBpbnN0YW5jZXMgb2YgdGhlaXIgbmFtZSkuCnNlbWlmaW5hbC5yZXN1bHRzLjE8LXRhYmxlKHNlbWlmaW5hbFtjKDE6MiksXSkKc2VtaWZpbmFsLnJlc3VsdHMuMjwtdGFibGUoc2VtaWZpbmFsW2MoMzo0KSxdKQpzZW1pZmluYWwucmVzdWx0cy4zPC10YWJsZShzZW1pZmluYWxbNSxdKQpzZW1pLnJhbms8LWMobmFtZXMoc2VtaWZpbmFsLnJlc3VsdHMuMSlbb3JkZXIoc2VtaWZpbmFsLnJlc3VsdHMuMSxkZWNyZWFzaW5nID0gVFJVRSldLG5hbWVzKHNlbWlmaW5hbC5yZXN1bHRzLjIpW29yZGVyKHNlbWlmaW5hbC5yZXN1bHRzLjIsZGVjcmVhc2luZyA9IFRSVUUpXSxuYW1lcyhzZW1pZmluYWwucmVzdWx0cy4zKVtvcmRlcihzZW1pZmluYWwucmVzdWx0cy4zLGRlY3JlYXNpbmcgPSBUUlVFKV0pCgojIGNyZWF0ZSB0aGUgbWF0Y2h1cHMgZm9yIHRoZSBmaW5hbCByb3VuZCB3aXRoIGEgcGxhY2UgaG9sZGVyIGZvciB0aGUgd2lubmVyCmZpbmFsPC1tYXRyaXgoc2VtaS5yYW5rLG5jb2w9MixieXJvdz1UUlVFKQpmaW5hbDwtY2JpbmQoZmluYWwscmVwKE5BLDUpKQoKIyBTaW11bGF0ZSB0aGUgZmluYWxzIHRoZSBzYW1lIHdheSB3ZSBkaWQgdGhlIHJlZ3VhbHIgc2Vhc29uIGFuZCBzZW1pZmluYWxzCmZvcihpIGluIDE6bnJvdyhmaW5hbCkpewogIG1hbmFuZ2VyLnByb2I8LUpPRC5NQ01bZmluYWxbaSwxXSxmaW5hbFtpLDJdXQogIG9wcG9uZW50LnByb2I8LUpPRC5NQ01bZmluYWxbaSwyXSxmaW5hbFtpLDFdXQogIGZpbmFsW2ksM108LXNhbXBsZShjKGZpbmFsW2ksMV0sZmluYWxbaSwyXSksMSxwcm9iPWMobWFuYW5nZXIucHJvYixvcHBvbmVudC5wcm9iKSkKfQojIEFnYWluLCB0aGUgbGFzdCBwYWxjZWQgdGVhbXMga2VlcCB0aGVpciByYW5rcyBhbmQgZG8gbm90IGNvbXBldGUKZmluYWxbNSwzXTwtZmluYWxbNSwxXQoKIyBVc2UgdGhlIGZyZXF1ZW5jeSBvZiBuYW1lcyB0byBkZXRlcm1pbmUgd2hpY2ggdGVhbXMvbWFuYWdlcnMgbW92ZSB1cCBhIHNwb3QgKHdpbm5lcnMgd2l0aCBtb3JlIGluc3RhbmNlcyBvZiB0aGVpciBuYW1lKSBhbmQgd2hpY2ggbW92ZSBkb3duIGEgc3BvdCAobG9zZXJzIHdpdGggZmV3ZXIgaW5zdGFuY2VzIG9mIHRoZWlyIG5hbWUpLgpmaW5hbC5yZXN1bHRzLjE8LXRhYmxlKGZpbmFsWzEsXSkKZmluYWwucmVzdWx0cy4yPC10YWJsZShmaW5hbFsyLF0pCmZpbmFsLnJlc3VsdHMuMzwtdGFibGUoZmluYWxbMyxdKQpmaW5hbC5yZXN1bHRzLjQ8LXRhYmxlKGZpbmFsWzQsXSkKZmluYWwucmVzdWx0cy41PC10YWJsZShmaW5hbFs1LF0pCmZpbmFsLnJhbms8LWMobmFtZXMoZmluYWwucmVzdWx0cy4xKVtvcmRlcihmaW5hbC5yZXN1bHRzLjEsZGVjcmVhc2luZyA9IFRSVUUpXSxuYW1lcyhmaW5hbC5yZXN1bHRzLjIpW29yZGVyKGZpbmFsLnJlc3VsdHMuMixkZWNyZWFzaW5nID0gVFJVRSldLG5hbWVzKGZpbmFsLnJlc3VsdHMuMylbb3JkZXIoZmluYWwucmVzdWx0cy4zLGRlY3JlYXNpbmcgPSBUUlVFKV0sbmFtZXMoZmluYWwucmVzdWx0cy40KVtvcmRlcihmaW5hbC5yZXN1bHRzLjQsZGVjcmVhc2luZyA9IFRSVUUpXSxuYW1lcyhmaW5hbC5yZXN1bHRzLjUpW29yZGVyKGZpbmFsLnJlc3VsdHMuNSxkZWNyZWFzaW5nID0gVFJVRSldKQoKIyBJbnB1dCB0aGUgZmluYWwgcm5ha2luZ3MgaW50byB0aGUgYXJyYXkgZm9yIHNlYXNvbiBzaW11bGF0aW9uIHJlc3VsdHMsIGFuZCBnbyBiYWNrIHRvIHRoZSBiZWdpbm5pbmcgb2YgdGhlIGxvb3AgZm9yIGEgbmV3IHNlYXNvbiBzaW11bGF0aW9uCnNpbS5zZWFzb25zW3MsXTwtb3JkZXIoZmluYWwucmFuaykKfQoKYGBgCgpIZXJlIGlzIGEgbG9vayBhdCB0aGUgZmlyc3QgZmV3IGxpbmVzIG9mIGRhdGEgd2UgZ2V0LgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBHZXQgYSBxdWljayBsb29rIGF0IHRoZSBkYXRhIHdlIGp1c3QgZ2F0ZWhyZWQKaGVhZChzaW0uc2Vhc29ucykKCmBgYAoKV2UgY2FuIHZpZXcgdGhlIHJhbmtpbmdzIGZvciBlYWNoIG1hbmFnZXIgdXNpbmcgYSBib3ggcGxvdC4uLi4gcG9vciBTcGVhcnMsIGJ1dCBpdCBsb29rcyBsaWtlIFJpY2sgZGlkIHByZXR0eSB3ZWxsIGluIG1vc3Qgc2ltdWxhdGlvbnMuCmBgYHtyfQojIEFuZCBldmVuIHBsb3QgYWxsIHRoZSBkYXRhIGluIGEgYm94IHBsb3QKc2ltLnNlYXNvbnMubWVsdDwtbWVsdChzaW0uc2Vhc29ucykKY29sbmFtZXMoc2ltLnNlYXNvbnMubWVsdCk8LWMoIlNpbXVsYXRpb24iLCJNYW5hZ2VyIiwiUmFuayIpCgpnZ3Bsb3Qoc2ltLnNlYXNvbnMubWVsdCxhZXMoeD1NYW5hZ2VyLHk9UmFuaykpKwogIHNjYWxlX3lfcmV2ZXJzZSggbGltPWMoMTAsMSksbmFtZT0iU2ltdWxhdGVkIFJhbmtzIikrCiAgZ2VvbV9ib3hwbG90KCkrCiAgdGhlbWVfYncoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQpgYGAKCgpGb3IgYSBjbGVhbmVyIGxvb2ssIHdlIGNhbGN1bGF0ZSB0aGUgbWVhbiByYW5rIHdpdGggOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIGZvciBlYWNoIG1hbmFnZXIgYWNyb3NzIGFsbCA1MDAgc2ltdWxhdGlvbnMsIGFuZCBwbGFjZSBhIGRvdHRlZCBsaW5lIGF0IHJhbmsgNSAobWlkZGxlIG9mIHRoZSByb2FkIGluIG91ciAxMCB0ZWFtIGxlYWd1ZSkuIE9uZSAgdGhpbmcgdG8gbm90ZSBpcyBob3cgY29uc3RyYWluZWQgdGhlIDk1JSBjb25maWRlbmNlIGludGVydmFscyBhcmUuIFRoaXMgaXMgbGlrZWx5IGJlY2F1c2Ugd2UgcmFuIDUwMCBzaW11bGF0aW9ucywgd2hpY2ggaXMgc29ydCBvZiBleGNlc3NpdmUsIGFuZCBsYXJnZSBzYW1wbGUgc2l6ZXMgd2lsbCBuYXR1cmFsbHkgZGVjcmVhc2UgYW55IG1lYXN1cmUgb2YgdmFyaWFuY2UgaW4gc2FtcGxlIG1lYW4gKGxpa2Ugc3RhbmRhcmQgZXJyb3IgYW5kIDk1JSBjb25maWRlbmNlIGludGVydmFscykgLSBpbmNyZWFzaW5nIHByZWNpc2lvbi4gQXMgaW4gdGhlIGJveCBwbG90LCB3ZSBjYW4gc3RpbGwgc2VlIGhvdyBwb29ybHkgYW5kIHdlbGwgU3BlYXJzIGFuZCBSaWNrIGRpZCByZXNwZWN0aXZlbHksIGJ1dCB0aGUgcmVsYXRpdmUgcGVyZm9ybWFuY2Ugb2Ygb3RoZXIgbWFuYWdlcnMgaXMgbW9yZSBjbGVhciBub3cuIEFsc28sIHdlIGluZGljYXRlIGVhY2ggbWFuYWdlcnMgYWN0dWFsIDIwMTEtMjAxMyBtZWFuIHJhbmtpbmcgaW4gdGhlIGZpZ3VyZSB3aXRoIGNvbG9yLCBhbmQgc2VlIHRoYXQsIGZvciB0aGUgbW9zdCBwYXJ0LCBtYW5hZ2VycyB0aGF0IHJhbmsgaGlnaCBpbiB0aGUgc2ltdWxhdGlvbiwgYWN0dWFsbHkgcmFua2VkIGhpZ2ggYmVydHdlZW4gMjAxMSBhbmQgMjAxMy4KYGBge3IsIGZpZy5oZWlnaHQ9Mi41LCBmaWcud2lkdGg9MywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBXZSBjYW4gdXNlIHRoZSAncGx5cicgbGlicmFyeSB0byBzdW1tYXJpemUgdGhlIG1lYW4gcmFuaywgYW5kIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gaW4gcmFuayBmb3IgZWFjaCBtYW5hZ2VyIG92ZXIgYWxsIDUwMCBzaW11bGF0aW9ucwpyZXF1aXJlKHBseXIpCgpzaW0uc2Vhc29ucy5yZXN1bHRzPC1kYXRhLmZyYW1lKE5hbWU9YXMudmVjdG9yKHJvd25hbWVzKEpPRC5NQ00pKSxNZWFuPWFwcGx5KHNpbS5zZWFzb25zLDIsbWVhbiksU3RkZXY9YXBwbHkoc2ltLnNlYXNvbnMsMixzZCkpCgojIEFsc28sIHdlIGNhbGN1YWx0ZSB0aGUgc3RhbmRhcmQgZXJyb3IsIGFuZCA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbHMgaW4gcmFuawpzaW0uc2Vhc29ucy5yZXN1bHRzJFN0ZEVycjwtc2ltLnNlYXNvbnMucmVzdWx0cyRTdGRldi9zcXJ0KDUwMCkKc2ltLnNlYXNvbnMucmVzdWx0cyRDb25mOTU8LXNpbS5zZWFzb25zLnJlc3VsdHMkU3RkRXJyKjEuOTYKCiMgQWRkIGluIHRoZSBtZWFuIHJhbmsgZnJvbSB0aGUgYWN0dWFsIGRhdGEgZnJvbSB5ZWFycyB3ZSBkaWRuJ3QgdXNlIGluIHRoZSBzaW11bGF0aW9uLCB0byBzZWUgaG93IHRoZSBzaW11bGF0aW9uIGRvZXMsIGFuZCB3aG8gaGFzbid0IHdvbiwgZXZlbiB0aG91Z2ggdGhlIGJlYXQgb3RoZXIgbWFuYWdlcnMgZnJlcXVlbnRseQpmZmIuZGF0LnN1bS4yMDExLjIwMTM8LXN1YnNldChmZmIuZGF0LnN1bSxZZWFyPD0yMDEzKQpmZmIuZGF0LnN1bS4yMDE0LjIwMTY8LXN1YnNldChmZmIuZGF0LnN1bSxZZWFyPjIwMTMpClBsYXllclJhbmsuc3VtMTwtZGRwbHkoZmZiLmRhdC5zdW0uMjAxMS4yMDEzLGMoIk1hbmFnZXIiKSxzdW1tYXJpc2UsUGxheWVyUmFuaz1tZWFuKFBsYXllclJhbmspKQpQbGF5ZXJSYW5rLnN1bTI8LWRkcGx5KGZmYi5kYXQuc3VtLjIwMTQuMjAxNixjKCJNYW5hZ2VyIiksc3VtbWFyaXNlLFBsYXllclJhbms9bWVhbihQbGF5ZXJSYW5rKSkKc2ltLnNlYXNvbnMucmVzdWx0cyRQbGF5ZXJSYW5rLjIwMTEuMjAxMzwtUGxheWVyUmFuay5zdW0xJFBsYXllclJhbmsKc2ltLnNlYXNvbnMucmVzdWx0cyRQbGF5ZXJSYW5rLjIwMTQuMjAxNjwtUGxheWVyUmFuay5zdW0yJFBsYXllclJhbmsKCgojIFRoZW4gd2UgcGxvdCB0aGUgcmVzdWx0cywgYWxvbmcgd2l0aCBhIHJlZCBkb3R0ZWQgbGluZSBhdCBhIG1lYW4gcmFua2luZyBvZiA1LCBpbmRpY2F0aW5nIHRoZSBtaWRwb2ludCBpbiBvdXIgMTAgdGVhbSBsZWFndWUgc28gd2UgY2FuIHNlZSB3aG8gZmFsbHMgYWJvdmUgYW5kIGJlbG93IGl0CmdncGxvdChzaW0uc2Vhc29ucy5yZXN1bHRzLGFlcyh4PWZhY3RvcihOYW1lKSx5PU1lYW4seW1heD1NZWFuK0NvbmY5NSx5bWluPU1lYW4tQ29uZjk1LGNvbG91cj1QbGF5ZXJSYW5rLjIwMTEuMjAxMykpKwogIGdlb21fcG9pbnRyYW5nZSgpKwogIHNjYWxlX3lfcmV2ZXJzZSggbGltPWMoMTAsMSksbmFtZT0iQXZlcmFnZSBSYW5rICgrLy0gOTUlIENvbmZpZGVuY2UgSW50ZXJ2YWwpIikrCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lPSJNYW5hZ2VyIikrCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobmFtZT0iQWN0dWFsXG4yMDExLTIwMTNcbk1lYW4gUmFuayIsbG93PSJyZWQiLGhpZ2g9ImJsYWNrIixndWlkZT1ndWlkZV9jb2xvdXJiYXIocmV2ZXJzZT1UUlVFKSkrCiAgZ2d0aXRsZSgiICBBdmVyYWdlIEZhbnRhc3kgUmFuayAoNTAwIFNpbXVsYXRpb25zKSIpKwogIGdlb21faGxpbmUoeWludGVyY2VwdD01LGNvbG91cj0icmVkIixsaW5ldHlwZSA9ICJkYXNoZWQiKSsKICB0aGVtZV9idygpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNjAsIGhqdXN0ID0gMSkpCgpgYGAKCldlIGNhbiBhbHNvIGxvb2sgYXQgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gc3VtdWxhdGVkIGFuZCBhY3R1YWwgcmFua2luZ3MgYmV0d2VlbiAyMDExIGFuZCAyMDEzIGFuZCBmaW5kIHRoYXQgdGhlIHNpbXVsYWlvbiBkaWQgYSBwcmV0dHkgZ29vZCBqb2IuCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp3aXRoKHNpbS5zZWFzb25zLnJlc3VsdHMsY29yKE1lYW4sUGxheWVyUmFuay4yMDExLjIwMTMpKQpgYGAKCgpTaW5jZSB0aGVzZSBzaW11bGF0ZWQgcmVzdWx0cyBjYW1lIGZyb20gMjAxMS0yMDEzIGZhbnRhc3kgZGF0YSwgd2UgY2FuIGNvbXBhcmUgaXQgdG8gYWN0dWFsIGZhbnRhc3kgZGF0YSBmcm9tIDIwMTQtMjAxNi4gV2UgZG8gc28gYnkgcGxvdHRpbmcgYXZlcmFnZSBtYW5hZ2VyIHJhbmsgZnJvbSB0aGUgc2ltdWxhdGlvbiBhZ2FpbnN0IGF2ZXJhZ2UgYWN0dWFsIHJhbmsgZnJvbSB0aGUgbmV3ZXIgKDIwMTQtMjAxNikgZGF0YS4gV2UgY2FuIHNlZSB0aGF0IG1vc3QgbWFuYWdlcnMgZmFsbCBhbG9uZyBvciBuZWFyIHRoZSAxOjEgbGluZSAoZG90dGVkIGJsYWNrKSwgaW5kaWNhdGluZyB0aGF0IHRoZSBzaW11bGF0aW9uIGZyb20gMjAxMS0yMDEzIGRhdGEgY2FwdHVyZWQgdGhlIHJhbmtzIGluIDIwMTQtMjAxNiBkYXRhIHdlbGwuIEJ1dCwgU3BlYXJzIGFuZCBSaWNrIGZhbGwgcHJldHR5IGZhciBmcm9tIHRoZSAxOjEgbGluZSAtIFJpY2sgcGVyZm9ybWluZyBtdWNoIGJldHRlciBkdXJpbmcgc2ltdWxhdGlvbiB0aGFuIGV4cGVjdGVkIGJhc2VkIG9uIGhpcyBhY3R1YWwgcmFuaywgYW5kIFNwZWFycyBwZXJmb3JtaW5nIHdvcnNlIHRoYW4gZXhwZWN0ZWQuIFRoaXMgcGF0dGVybiBjb3VsZCBtZWFuIHR3byB0aGluZ3M6IDEpIFRoZSBzaW11bGF0aW9uIGRpZCBub3QgY2FwdHVyZSBTcGVhcnMgYW5kIFJpY2sncyBwZXJmb3JtYW5jZSB3ZWxsIGJlY2F1c2UgdGhlcmUgaXMgYSBmdW5kYW1lbnRhbCBwcm9ibGVtL2luYWNjdXJhY3kgaW4gaG93IHdlIHNpbXVsYXRlIFNwZWFycyBhbmQgUmljayAodGhleSBjYW4gZGVmaW5pdGVseSBiZSB3aWxkLWNhcmRzIGluIHRoZSBncm91cCkuIFNpbmNlIHRoZSBzaW11bGF0aW9uIHJlc3VsdHMgd2VyZSB3ZWxsIGNvcnJlbGF0ZWQgd2l0aCAyMDExLTIwMTMgZGF0YSB0aG91Z2gsIGl0J3MgbW9yZSBsaWtlbHkgdGhhdCBTcGVhcnMgYW5kIFJpY2sgaGF2ZSBhY3R1YWxseSBpbXByb3ZlZCBhbmQgd29yc2VuZWQgdGhlaXIgMjAxNC0yMDE2IGZhbnRhc3kgcGxheSByZXNwZWN0aXZlbHksIGNvbXBhcmVkIHRvIHRoZWlyIDIwMTEtMjAxMyBwbGF5LgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgQW5kIG5vdyBsZXRzIGxvb2sgYXQgdGhlIHJlbGF0aW9uIGJldHdlZW4gcHJlZGljdGVkIHJhbmsgYW5kIHRoZSBhY3R1YWwgcmFuayBpbiBhIHBsb3QKIyBZb3UgY2FuIHNlZSB0aGF0LCBiYXNlZCBvbiBzaW11bGF0ZWQgcHJlZGljdGlvbnMgZnJvbSB0aGUgZmlyc3QgdGhyZWUgeWVhcnMgb2YgZGF0YSwgUmljayBoYXMgdW5kZXJwcmVmb3JtZWQgaW4gdGhlIGZvbGx3aW5nIHRocmVlIHllYXJzLCB3aGlsZSBTcGVhcnMgaGFzIGV4Y2VlZGVkIGV4cGVjdGF0aW9ucwpnZ3Bsb3Qoc2ltLnNlYXNvbnMucmVzdWx0cyxhZXMoeD1QbGF5ZXJSYW5rLjIwMTQuMjAxNix5PU1lYW4sc2hhcGU9TmFtZSkpKwogIGdlb21fcG9pbnQoKSsKICB4bGltKDEwLDEpK3lsaW0oMTAsMSkrCiAgbGFicyh4PSJBdmVyYWdlIEFjdHVhbCBSYW5rICgyMDE0LTIwMTYgZGF0YSkiLHk9IkF2ZXJhZ2UgU2ltdWxhdGVkIFJhbmsgKDIwMTEtMjAxMyBkYXRhKSIpKwogIGdlb21fYWJsaW5lKHNsb3BlPTEsaW50ZXJjZXB0PTAsbGluZXR5cGUgPSAiZGFzaGVkIikrCiAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcz0xOm5sZXZlbHMoc2ltLnNlYXNvbnMucmVzdWx0cyROYW1lKSxuYW1lPSJNYW5hZ2VyIikgKwogIHRoZW1lX2J3KCkKYGBgCgpPdmVyYWxsLCB3ZSBjYW4gY29uY2x1ZGUgdGhhdCB0aGUgbWFqb3JpdHkgb2YgdGhlIHZhcmlhdGlvbiBpbiB3aW5uaW5nIHRoZSBKb2luIG9yIERpZSBGYW50YXN5IEZvb3RiYWxsIExlYWd1ZSBpcyBub3QgY2FwdHVyZWQgYnkgd2hhdCB2YXJpYXRpb24gd2Ugc2VlIGluIFRyYWRlcyBhbmQgTW92ZXMuIFRoZXJlIGFyZSBwcm9iYWJseSBvdGhlciB2YXJpYWJsZXMgaW5oZXJpZW50IGluIHRoZSB2YXJpYXRpb24gYW1vbmcgbWFuYWdlcnMgKGUuZy4sIGRyYWZ0IHBlcmZvcm1hbmNlLCBpbnRlcmVzdCBpbiBmYW50YXN5IGdhbWVzLCBwcm9jbGl2aXR5IHRvIHByb2NyYXN0aW5hdGUgYXQgd29yayBhbmQgcmVzZWFyY2ggc3BvcnRzIHN0YXRzKSwgdGhhdCBkZXRlcm1pbmUgdGhlIHdpbm5pbmcgcGVyY2VudGFnZSwgYW5kIGV2ZW50dWFsIHJhbmtpbmcsIG9mIHRlYW1zLiBXZSBjYW4sIGhvd2V2ZXIsIHNheSB0aGF0IFNwZWFycyBhbmQgaGlzICJDaGlsZWFuIFNwZWFyIEZpc2hlcnMiIiBhcmUgb24gdGhlIHVwLWFuZC11cCwgd2hpbGUgUmljayBhbmQgaGlzICJBbGV4YW5kcmlhIEVtcGVyb3JzIiIgaGF2ZSBsb3N0IHNvbWUgb2YgdGhhdCBwaXp6YXp6IHRoYXQgbWFkZSB0aGVtIGEgcG93ZXJob3VzZSBmcm9tIDIwMTEtMjAxMy4KClRoYXQncyBpdCEKCgoK