1 load libraries

2 Load Object & Set RNA Assay

3 Define State-Defining TFs (Confirmed from Heatmap Panel C)

# orig.ident is set automatically during individual Seurat object creation
# It stores the sample name: L1, L2, L3, L4, L5, L6, L7, PBMC, PBMC-10x
seurat_L1 <- subset(seurat_obj, subset = orig.ident == "L1")

cat("Number of L1 cells:", ncol(seurat_L1), "\n")
Number of L1 cells: 5825 
# Sanity check — should show only L1
table(seurat_L1$orig.ident)

      L1       L2       L3       L4       L5       L6       L7 CD4T_lab CD4T_10x 
    5825        0        0        0        0        0        0        0        0 

4 Define 3 States (Exact match to manuscript Figure 8D)

# Inflammatory  → NF-κB/AP-1 driven (clusters 11, 12)
# Proliferative → MYC/E2F1 driven  (clusters 4, 7, 8)
# Th1_Cytotoxic → TBX21 driven     (cluster 5; stem-like via HMGA2 in text)
state_tfs <- list(
  Inflammatory  = c("STAT3", "RELA", "NFKB1"),
  Proliferative = c("MYC",   "E2F1"),
  Th1_Cytotoxic = c("TBX21")
)

5 Extract TF Activity Matrix from dorothea Assay

tf_mat <- GetAssayData(seurat_L1, assay = "dorothea", layer =  "data")

cat("TF matrix dimensions (TFs x cells):", dim(tf_mat), "\n")
TF matrix dimensions (TFs x cells): 271 5825 
# Check which state TFs are present in the dorothea assay
for (state in names(state_tfs)) {
  found   <- state_tfs[[state]][state_tfs[[state]] %in% rownames(tf_mat)]
  missing <- state_tfs[[state]][!state_tfs[[state]] %in% rownames(tf_mat)]
  cat(sprintf("%-15s Found: %-30s Missing: %s\n",
              state,
              paste(found,   collapse = ", "),
              paste(missing, collapse = ", ")))
}
Inflammatory    Found: STAT3, RELA, NFKB1             Missing: 
Proliferative   Found: MYC, E2F1                      Missing: 
Th1_Cytotoxic   Found: TBX21                          Missing: 

6 Compute Mean Activity Score per State per Cell

score_df <- as.data.frame(
  sapply(state_tfs, function(tfs) {
    tfs_found <- tfs[tfs %in% rownames(tf_mat)]
    if (length(tfs_found) == 0) {
      warning("No TFs found for this state — returning NA")
      return(rep(NA_real_, ncol(tf_mat)))
    }
    colMeans(tf_mat[tfs_found, , drop = FALSE])
  })
)

rownames(score_df) <- colnames(tf_mat)

# Preview raw scores
head(round(score_df, 3))

7 Scale Scores Across States

score_df_scaled <- as.data.frame(scale(score_df))
rownames(score_df_scaled) <- rownames(score_df)

# Preview scaled scores
head(round(score_df_scaled, 3))

8 Assign Dominant State per Cell

score_df_scaled$DominantState <- apply(
  score_df_scaled[, names(state_tfs), drop = FALSE], 1,
  function(x) {
    if (all(is.na(x))) return(NA_character_)
    names(which.max(x))
  }
)

seurat_L1$TF_State <- factor(
  score_df_scaled$DominantState,
  levels = names(state_tfs)
)

9 Also Add Raw Scores as Individual Metadata Columns

cat("\n=======================================================\n")

=======================================================
cat("     L1 TF State Distribution (Figure 8D)\n")
     L1 TF State Distribution (Figure 8D)
cat("=======================================================\n")
=======================================================
dist_table <- seurat_L1@meta.data %>%
  filter(!is.na(TF_State)) %>%
  count(TF_State) %>%
  mutate(Percentage = round(n / sum(n) * 100, 1)) %>%
  arrange(factor(TF_State, levels = names(state_tfs)))
print(dist_table)
       TF_State    n Percentage
1  Inflammatory 1917       32.9
2 Proliferative 2219       38.1
3 Th1_Cytotoxic 1689       29.0
cat("All 3 states must be > 0% to confirm intra-clonal heterogeneity\n")
All 3 states must be > 0% to confirm intra-clonal heterogeneity
cat("=======================================================\n")
=======================================================
# ── 12. Define Color Palette ──────────────────────────────────────────────────
state_colors <- c(
  Inflammatory  = "#E63946",    # red   — NF-κB/STAT3 survival program
  Proliferative = "#457B9D",    # blue  — MYC/E2F1 proliferative program
  Th1_Cytotoxic = "#2D6A4F"     # green — TBX21 Th1/cytotoxic program
)

10 Check UMAP is Present

cat("\nReductions available:", paste(Reductions(seurat_L1), collapse = ", "), "\n")

Reductions available: integrated_dr, ref.umap, pca, umap, harmony 
# Only run if UMAP is absent from the subset
if (!"umap" %in% Reductions(seurat_L1)) {
  cat("UMAP not found — recomputing from Harmony embeddings...\n")
  seurat_L1 <- RunUMAP(seurat_L1,
                        reduction = "harmony",
                        dims      = 1:20,
                        seed.use  = 42)
}

11 Panel A — UMAP Colored by TF State

p_umap <- DimPlot(
  seurat_L1,
  reduction  = "umap",
  group.by   = "TF_State",
  cols       = state_colors,
  pt.size    = 1.0,
  label      = FALSE,
  na.value   = "grey85"
) +
  ggtitle("Cell Line L1 — TF Regulatory State") +
  theme_classic(base_size = 11) +
  theme(
    plot.title      = element_text(face = "bold", size = 11, hjust = 0.5),
    legend.title    = element_text(size = 9,  face = "bold"),
    legend.text     = element_text(size = 8),
    legend.position = "right",
    axis.line       = element_line(color = "black", linewidth = 0.4),
    axis.text       = element_text(size = 7)
  ) +
  labs(color = "TF State")

p_umap

12 Panel B — Stacked Bar Chart (State Proportions)

state_counts <- seurat_L1@meta.data %>%
  filter(!is.na(TF_State)) %>%
  count(TF_State) %>%
  mutate(
    Proportion = n / sum(n) * 100,
    TF_State   = factor(TF_State, levels = names(state_colors))
  )

print(state_counts)
       TF_State    n Proportion
1  Inflammatory 1917   32.90987
2 Proliferative 2219   38.09442
3 Th1_Cytotoxic 1689   28.99571
p_bar <- ggplot(
  state_counts,
  aes(x = "L1", y = Proportion, fill = TF_State)
) +
  geom_bar(
    stat      = "identity",
    width     = 0.45,
    color     = "white",
    linewidth = 0.3
  ) +
  geom_text(
    aes(label = paste0(round(Proportion, 1), "%")),
    position = position_stack(vjust = 0.5),
    size     = 3.5,
    color    = "white",
    fontface = "bold"
  ) +
  scale_fill_manual(values = state_colors, drop = FALSE) +
  scale_y_continuous(
    limits = c(0, 100),
    breaks = seq(0, 100, 25),
    labels = function(x) paste0(x, "%")
  ) +
  labs(
    y     = "Proportion of cells (%)",
    x     = NULL,
    fill  = "TF State",
    title = "State distribution — L1"
  ) +
  theme_classic(base_size = 11) +
  theme(
    axis.text.x     = element_blank(),
    axis.ticks.x    = element_blank(),
    plot.title      = element_text(face = "bold", size = 11, hjust = 0.5),
    legend.position = "none",
    axis.line       = element_line(color = "black", linewidth = 0.4)
  )

p_bar

13 Combine Panels and Save - Figure 8D

final_fig <- p_umap + p_bar +
  plot_layout(widths = c(2.5, 1)) +
  plot_annotation(
    title    = "Intra-clonal regulatory heterogeneity — Cell Line L1",
    subtitle = "Monoclonal population simultaneously occupies Inflammatory, Proliferative, and Th1/Cytotoxic TF states",
    theme    = theme(
      plot.title    = element_text(size = 12, face = "bold",  hjust = 0.5),
      plot.subtitle = element_text(size = 9,  color = "grey40", hjust = 0.5)
    )
  )

final_fig


ggsave("Figure_8D_L1_TFstate_UMAP_Bar.pdf",
       plot = final_fig, width = 9, height = 4.5, dpi = 300, device = "pdf")

ggsave("Figure_8D_L1_TFstate_UMAP_Bar.png",
       plot = final_fig, width = 9, height = 4.5, dpi = 300)

cat("Figure 8D saved successfully.\n")
Figure 8D saved successfully.

14 Validation — Violin Plots (Supplementary Figure SX)

score_features <- paste0("Score_", names(state_tfs))

p_val <- VlnPlot(
  seurat_L1,
  features = score_features,
  group.by = "TF_State",
  pt.size  = 0,
  cols     = state_colors,
  ncol     = 3
) &
  theme_classic(base_size = 9) &
  theme(
    axis.text.x     = element_text(angle = 45, hjust = 1, size = 7),
    legend.position = "none",
    plot.title      = element_text(size = 9)
  )

p_val


ggsave("Supp_SX_TF_validation_violins.pdf",
       plot = p_val, width = 12, height = 4.5, dpi = 300)

ggsave("Supp_SX_TF_validation_violins.png",
       plot = p_val, width = 12, height = 4.5, dpi = 300, bg = "white")

cat("Validation violins saved as PDF and PNG.\n")
Validation violins saved as PDF and PNG.

15 # Session Info

sessionInfo()
R version 4.5.2 (2025-10-31)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.3 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0 
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C               LC_TIME=fr_FR.UTF-8       
 [4] LC_COLLATE=en_GB.UTF-8     LC_MONETARY=fr_FR.UTF-8    LC_MESSAGES=en_GB.UTF-8   
 [7] LC_PAPER=fr_FR.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
[10] LC_TELEPHONE=C             LC_MEASUREMENT=fr_FR.UTF-8 LC_IDENTIFICATION=C       

time zone: Europe/Paris
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] RColorBrewer_1.1-3 patchwork_1.3.2    dplyr_1.2.0        ggplot2_4.0.2     
[5] Seurat_5.4.0       SeuratObject_5.3.0 sp_2.2-1          

loaded via a namespace (and not attached):
  [1] deldir_2.0-4           pbapply_1.7-4          gridExtra_2.3         
  [4] rlang_1.1.7            magrittr_2.0.4         RcppAnnoy_0.0.23      
  [7] otel_0.2.0             spatstat.geom_3.7-0    matrixStats_1.5.0     
 [10] ggridges_0.5.7         compiler_4.5.2         systemfonts_1.3.1     
 [13] png_0.1-8              vctrs_0.7.1            reshape2_1.4.5        
 [16] stringr_1.6.0          pkgconfig_2.0.3        fastmap_1.2.0         
 [19] labeling_0.4.3         promises_1.5.0         rmarkdown_2.30        
 [22] ggbeeswarm_0.7.3       ragg_1.5.0             purrr_1.2.1           
 [25] xfun_0.56              cachem_1.1.0           jsonlite_2.0.0        
 [28] goftest_1.2-3          later_1.4.5            spatstat.utils_3.2-1  
 [31] irlba_2.3.7            parallel_4.5.2         cluster_2.1.8.2       
 [34] R6_2.6.1               ica_1.0-3              spatstat.data_3.1-9   
 [37] bslib_0.10.0           stringi_1.8.7          reticulate_1.44.1     
 [40] spatstat.univar_3.1-6  parallelly_1.46.1      lmtest_0.9-40         
 [43] jquerylib_0.1.4        scattermore_1.2        Rcpp_1.1.1            
 [46] knitr_1.51             tensor_1.5.1           future.apply_1.20.1   
 [49] zoo_1.8-15             sctransform_0.4.3      httpuv_1.6.16         
 [52] Matrix_1.7-4           splines_4.5.2          igraph_2.2.2          
 [55] tidyselect_1.2.1       abind_1.4-8            rstudioapi_0.18.0     
 [58] dichromat_2.0-0.1      yaml_2.3.12            spatstat.random_3.4-4 
 [61] codetools_0.2-20       miniUI_0.1.2           spatstat.explore_3.7-0
 [64] listenv_0.10.0         lattice_0.22-9         tibble_3.3.1          
 [67] plyr_1.8.9             withr_3.0.2            shiny_1.12.1          
 [70] S7_0.2.1               ROCR_1.0-12            ggrastr_1.0.2         
 [73] evaluate_1.0.5         Rtsne_0.17             future_1.69.0         
 [76] fastDummies_1.7.5      survival_3.8-3         polyclip_1.10-7       
 [79] fitdistrplus_1.2-6     pillar_1.11.1          rsconnect_1.7.0       
 [82] KernSmooth_2.23-26     plotly_4.12.0          generics_0.1.4        
 [85] RcppHNSW_0.6.0         scales_1.4.0           globals_0.19.0        
 [88] xtable_1.8-4           glue_1.8.0             lazyeval_0.2.2        
 [91] tools_4.5.2            data.table_1.18.2.1    RSpectra_0.16-2       
 [94] RANN_2.6.2             dotCall64_1.2          cowplot_1.2.0         
 [97] grid_4.5.2             tidyr_1.3.2            nlme_3.1-168          
[100] beeswarm_0.4.0         vipor_0.4.7            cli_3.6.5             
[103] spatstat.sparse_3.1-0  textshaping_1.0.4      spam_2.11-3           
[106] viridisLite_0.4.3      uwot_0.2.4             gtable_0.3.6          
[109] sass_0.4.10            digest_0.6.39          progressr_0.18.0      
[112] ggrepel_0.9.6          htmlwidgets_1.6.4      farver_2.1.2          
[115] htmltools_0.5.9        lifecycle_1.0.5        httr_1.4.7            
[118] mime_0.13              MASS_7.3-65           
LS0tCnRpdGxlOiAiRmlndXJlIDhELUwxIEludHJhLUNsb25hbCBSZWd1bGF0b3J5IEhldGVyb2dlbmVpdHkg4oCUIFRGIFN0YXRlIFVNQVAiCmF1dGhvcjogTmFzaXIgTWFobW9vZCBBYmJhc2kKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNzczogc3R5bGUuY3NzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHRydWUKICAgIHRoZW1lOiBqb3VybmFsCi0tLQoKPCEtLSAjID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09IC0tPgo8IS0tICMgRmlndXJlIDhEIOKAlCBMMSBJbnRyYS1DbG9uYWwgUmVndWxhdG9yeSBIZXRlcm9nZW5laXR5IC0tPgo8IS0tICMgMyBTdGF0ZXM6IEluZmxhbW1hdG9yeSAoU1RBVDMvUkVMQS9ORktCMSkgfCBQcm9saWZlcmF0aXZlIChNWUMvRTJGMSkgfCAgLS0+CjwhLS0gIyAgICAgICAgICAgVGgxX0N5dG90b3hpYyAoVEJYMjEpIC0tPgo8IS0tICMgU3RlbS1saWtlIHJlZmVyZW5jZWQgaW4gdGV4dCB2aWEgSE1HQTIgKGNsdXN0ZXIgNSwgRmlnLjVCKSAtLT4KPCEtLSAjIFNjcmlwdCBhdXRob3I6IE5hc2lyIE1haG1vb2QgQWJiYXNpIOKAlCBCUklDLCBVbml2ZXJzaXTDqSBkZSBCb3JkZWF1eCAtLT4KPCEtLSAjID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09IC0tPgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBlY2hvICAgICAgID0gVFJVRSwKICB3YXJuaW5nICAgID0gRkFMU0UsCiAgbWVzc2FnZSAgICA9IEZBTFNFLAogIGZpZy53aWR0aCAgPSAxMCwKICBmaWcuaGVpZ2h0ID0gNiwKICBkcGkgICAgICAgID0gMzAwCikKYGBgCgoKCgojIGxvYWQgbGlicmFyaWVzCmBgYHtyICwgaW5jbHVkZT1GQUxTRX0KCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeShzY2FsZXMpCgpgYGAKCgojIExvYWQgT2JqZWN0ICYgU2V0IFJOQSBBc3NheQpgYGB7ciAsIGluY2x1ZGU9RkFMU0V9CgpzZXVyYXRfb2JqIDwtIHJlYWRSRFMoIi9ob21lL2Jpb2luZm8vMS1UaGVzaXNfRmluYWxfWWVhcl8yMDI1LzIwMjUtWWVhcjNfQW5hbHlzaXMvMS1zY1JOQV9SRVNVTFRTLTE5LTExLTIwMjUvMTktVEZfQW5hbHlzaXNfRGVjdXBsZVIrRG9yb3RoZWEtRmViMjAyNi90ZW1wX3NldXJhdF9vYmoucmRzIikKCiMgQ29uZmlybSBhdmFpbGFibGUgYXNzYXlzIOKAlCBkb3JvdGhlYSBzaG91bGQgYXBwZWFyIGhlcmUKQXNzYXlzKHNldXJhdF9vYmopCgojIENvbmZpcm0gbWV0YWRhdGEgY29sdW1ucyDigJQgJ2NlbGxsaW5lJyBzaG91bGQgYXBwZWFyIGhlcmUKY29sbmFtZXMoc2V1cmF0X29iakBtZXRhLmRhdGEpCgojIENoZWNrIGNlbGwgbGluZSBsYWJlbHMKdGFibGUoc2V1cmF0X29iaiRvcmlnLmlkZW50KQpgYGAKCgoKCgoKIyBEZWZpbmUgU3RhdGUtRGVmaW5pbmcgVEZzIChDb25maXJtZWQgZnJvbSBIZWF0bWFwIFBhbmVsIEMpCmBgYHtyIH0KIyBvcmlnLmlkZW50IGlzIHNldCBhdXRvbWF0aWNhbGx5IGR1cmluZyBpbmRpdmlkdWFsIFNldXJhdCBvYmplY3QgY3JlYXRpb24KIyBJdCBzdG9yZXMgdGhlIHNhbXBsZSBuYW1lOiBMMSwgTDIsIEwzLCBMNCwgTDUsIEw2LCBMNywgUEJNQywgUEJNQy0xMHgKc2V1cmF0X0wxIDwtIHN1YnNldChzZXVyYXRfb2JqLCBzdWJzZXQgPSBvcmlnLmlkZW50ID09ICJMMSIpCgpjYXQoIk51bWJlciBvZiBMMSBjZWxsczoiLCBuY29sKHNldXJhdF9MMSksICJcbiIpCgojIFNhbml0eSBjaGVjayDigJQgc2hvdWxkIHNob3cgb25seSBMMQp0YWJsZShzZXVyYXRfTDEkb3JpZy5pZGVudCkKYGBgCgojIERlZmluZSAzIFN0YXRlcyAoRXhhY3QgbWF0Y2ggdG8gbWFudXNjcmlwdCBGaWd1cmUgOEQpCmBgYHtyIH0KIyBJbmZsYW1tYXRvcnkgIOKGkiBORi3OukIvQVAtMSBkcml2ZW4gKGNsdXN0ZXJzIDExLCAxMikKIyBQcm9saWZlcmF0aXZlIOKGkiBNWUMvRTJGMSBkcml2ZW4gIChjbHVzdGVycyA0LCA3LCA4KQojIFRoMV9DeXRvdG94aWMg4oaSIFRCWDIxIGRyaXZlbiAgICAgKGNsdXN0ZXIgNTsgc3RlbS1saWtlIHZpYSBITUdBMiBpbiB0ZXh0KQpzdGF0ZV90ZnMgPC0gbGlzdCgKICBJbmZsYW1tYXRvcnkgID0gYygiU1RBVDMiLCAiUkVMQSIsICJORktCMSIpLAogIFByb2xpZmVyYXRpdmUgPSBjKCJNWUMiLCAgICJFMkYxIiksCiAgVGgxX0N5dG90b3hpYyA9IGMoIlRCWDIxIikKKQpgYGAKCiMgRXh0cmFjdCBURiBBY3Rpdml0eSBNYXRyaXggZnJvbSBkb3JvdGhlYSBBc3NheQpgYGB7ciB9CnRmX21hdCA8LSBHZXRBc3NheURhdGEoc2V1cmF0X0wxLCBhc3NheSA9ICJkb3JvdGhlYSIsIGxheWVyID0gICJkYXRhIikKCmNhdCgiVEYgbWF0cml4IGRpbWVuc2lvbnMgKFRGcyB4IGNlbGxzKToiLCBkaW0odGZfbWF0KSwgIlxuIikKCiMgQ2hlY2sgd2hpY2ggc3RhdGUgVEZzIGFyZSBwcmVzZW50IGluIHRoZSBkb3JvdGhlYSBhc3NheQpmb3IgKHN0YXRlIGluIG5hbWVzKHN0YXRlX3RmcykpIHsKICBmb3VuZCAgIDwtIHN0YXRlX3Rmc1tbc3RhdGVdXVtzdGF0ZV90ZnNbW3N0YXRlXV0gJWluJSByb3duYW1lcyh0Zl9tYXQpXQogIG1pc3NpbmcgPC0gc3RhdGVfdGZzW1tzdGF0ZV1dWyFzdGF0ZV90ZnNbW3N0YXRlXV0gJWluJSByb3duYW1lcyh0Zl9tYXQpXQogIGNhdChzcHJpbnRmKCIlLTE1cyBGb3VuZDogJS0zMHMgTWlzc2luZzogJXNcbiIsCiAgICAgICAgICAgICAgc3RhdGUsCiAgICAgICAgICAgICAgcGFzdGUoZm91bmQsICAgY29sbGFwc2UgPSAiLCAiKSwKICAgICAgICAgICAgICBwYXN0ZShtaXNzaW5nLCBjb2xsYXBzZSA9ICIsICIpKSkKfQpgYGAKIyBDb21wdXRlIE1lYW4gQWN0aXZpdHkgU2NvcmUgcGVyIFN0YXRlIHBlciBDZWxsCmBgYHtyIH0Kc2NvcmVfZGYgPC0gYXMuZGF0YS5mcmFtZSgKICBzYXBwbHkoc3RhdGVfdGZzLCBmdW5jdGlvbih0ZnMpIHsKICAgIHRmc19mb3VuZCA8LSB0ZnNbdGZzICVpbiUgcm93bmFtZXModGZfbWF0KV0KICAgIGlmIChsZW5ndGgodGZzX2ZvdW5kKSA9PSAwKSB7CiAgICAgIHdhcm5pbmcoIk5vIFRGcyBmb3VuZCBmb3IgdGhpcyBzdGF0ZSDigJQgcmV0dXJuaW5nIE5BIikKICAgICAgcmV0dXJuKHJlcChOQV9yZWFsXywgbmNvbCh0Zl9tYXQpKSkKICAgIH0KICAgIGNvbE1lYW5zKHRmX21hdFt0ZnNfZm91bmQsICwgZHJvcCA9IEZBTFNFXSkKICB9KQopCgpyb3duYW1lcyhzY29yZV9kZikgPC0gY29sbmFtZXModGZfbWF0KQoKIyBQcmV2aWV3IHJhdyBzY29yZXMKaGVhZChyb3VuZChzY29yZV9kZiwgMykpCmBgYAojICBTY2FsZSBTY29yZXMgQWNyb3NzIFN0YXRlcwpgYGB7ciB9CnNjb3JlX2RmX3NjYWxlZCA8LSBhcy5kYXRhLmZyYW1lKHNjYWxlKHNjb3JlX2RmKSkKcm93bmFtZXMoc2NvcmVfZGZfc2NhbGVkKSA8LSByb3duYW1lcyhzY29yZV9kZikKCiMgUHJldmlldyBzY2FsZWQgc2NvcmVzCmhlYWQocm91bmQoc2NvcmVfZGZfc2NhbGVkLCAzKSkKYGBgCiMgIEFzc2lnbiBEb21pbmFudCBTdGF0ZSBwZXIgQ2VsbApgYGB7ciB9CnNjb3JlX2RmX3NjYWxlZCREb21pbmFudFN0YXRlIDwtIGFwcGx5KAogIHNjb3JlX2RmX3NjYWxlZFssIG5hbWVzKHN0YXRlX3RmcyksIGRyb3AgPSBGQUxTRV0sIDEsCiAgZnVuY3Rpb24oeCkgewogICAgaWYgKGFsbChpcy5uYSh4KSkpIHJldHVybihOQV9jaGFyYWN0ZXJfKQogICAgbmFtZXMod2hpY2gubWF4KHgpKQogIH0KKQoKc2V1cmF0X0wxJFRGX1N0YXRlIDwtIGZhY3RvcigKICBzY29yZV9kZl9zY2FsZWQkRG9taW5hbnRTdGF0ZSwKICBsZXZlbHMgPSBuYW1lcyhzdGF0ZV90ZnMpCikKYGBgCgojIEFsc28gQWRkIFJhdyBTY29yZXMgYXMgSW5kaXZpZHVhbCBNZXRhZGF0YSBDb2x1bW5zCmBgYHtyIH0KY2F0KCJcbj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiIpCmNhdCgiICAgICBMMSBURiBTdGF0ZSBEaXN0cmlidXRpb24gKEZpZ3VyZSA4RClcbiIpCmNhdCgiPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuIikKZGlzdF90YWJsZSA8LSBzZXVyYXRfTDFAbWV0YS5kYXRhICU+JQogIGZpbHRlcighaXMubmEoVEZfU3RhdGUpKSAlPiUKICBjb3VudChURl9TdGF0ZSkgJT4lCiAgbXV0YXRlKFBlcmNlbnRhZ2UgPSByb3VuZChuIC8gc3VtKG4pICogMTAwLCAxKSkgJT4lCiAgYXJyYW5nZShmYWN0b3IoVEZfU3RhdGUsIGxldmVscyA9IG5hbWVzKHN0YXRlX3RmcykpKQpwcmludChkaXN0X3RhYmxlKQpjYXQoIkFsbCAzIHN0YXRlcyBtdXN0IGJlID4gMCUgdG8gY29uZmlybSBpbnRyYS1jbG9uYWwgaGV0ZXJvZ2VuZWl0eVxuIikKY2F0KCI9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4iKQoKIyBEZWZpbmUgQ29sb3IgUGFsZXR0ZSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKc3RhdGVfY29sb3JzIDwtIGMoCiAgSW5mbGFtbWF0b3J5ICA9ICIjRTYzOTQ2IiwgICAgIyByZWQgICDigJQgTkYtzrpCL1NUQVQzIHN1cnZpdmFsIHByb2dyYW0KICBQcm9saWZlcmF0aXZlID0gIiM0NTdCOUQiLCAgICAjIGJsdWUgIOKAlCBNWUMvRTJGMSBwcm9saWZlcmF0aXZlIHByb2dyYW0KICBUaDFfQ3l0b3RveGljID0gIiMyRDZBNEYiICAgICAjIGdyZWVuIOKAlCBUQlgyMSBUaDEvY3l0b3RveGljIHByb2dyYW0KKQpgYGAKCiMgQ2hlY2sgVU1BUCBpcyBQcmVzZW50CmBgYHtyIH0KY2F0KCJcblJlZHVjdGlvbnMgYXZhaWxhYmxlOiIsIHBhc3RlKFJlZHVjdGlvbnMoc2V1cmF0X0wxKSwgY29sbGFwc2UgPSAiLCAiKSwgIlxuIikKCiMgT25seSBydW4gaWYgVU1BUCBpcyBhYnNlbnQgZnJvbSB0aGUgc3Vic2V0CmlmICghInVtYXAiICVpbiUgUmVkdWN0aW9ucyhzZXVyYXRfTDEpKSB7CiAgY2F0KCJVTUFQIG5vdCBmb3VuZCDigJQgcmVjb21wdXRpbmcgZnJvbSBIYXJtb255IGVtYmVkZGluZ3MuLi5cbiIpCiAgc2V1cmF0X0wxIDwtIFJ1blVNQVAoc2V1cmF0X0wxLAogICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAiaGFybW9ueSIsCiAgICAgICAgICAgICAgICAgICAgICAgIGRpbXMgICAgICA9IDE6MjAsCiAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQudXNlICA9IDQyKQp9CmBgYAoKCiMgIFBhbmVsIEEg4oCUIFVNQVAgQ29sb3JlZCBieSBURiBTdGF0ZQpgYGB7cn0KcF91bWFwIDwtIERpbVBsb3QoCiAgc2V1cmF0X0wxLAogIHJlZHVjdGlvbiAgPSAidW1hcCIsCiAgZ3JvdXAuYnkgICA9ICJURl9TdGF0ZSIsCiAgY29scyAgICAgICA9IHN0YXRlX2NvbG9ycywKICBwdC5zaXplICAgID0gMS4wLAogIGxhYmVsICAgICAgPSBGQUxTRSwKICBuYS52YWx1ZSAgID0gImdyZXk4NSIKKSArCiAgZ2d0aXRsZSgiQ2VsbCBMaW5lIEwxIOKAlCBURiBSZWd1bGF0b3J5IFN0YXRlIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTEpICsKICB0aGVtZSgKICAgIHBsb3QudGl0bGUgICAgICA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gMTEsIGhqdXN0ID0gMC41KSwKICAgIGxlZ2VuZC50aXRsZSAgICA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgIGZhY2UgPSAiYm9sZCIpLAogICAgbGVnZW5kLnRleHQgICAgID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsCiAgICBheGlzLmxpbmUgICAgICAgPSBlbGVtZW50X2xpbmUoY29sb3IgPSAiYmxhY2siLCBsaW5ld2lkdGggPSAwLjQpLAogICAgYXhpcy50ZXh0ICAgICAgID0gZWxlbWVudF90ZXh0KHNpemUgPSA3KQogICkgKwogIGxhYnMoY29sb3IgPSAiVEYgU3RhdGUiKQoKcF91bWFwCmBgYAoKCgoKIyBQYW5lbCBCIOKAlCBTdGFja2VkIEJhciBDaGFydCAoU3RhdGUgUHJvcG9ydGlvbnMpCmBgYHtyIH0Kc3RhdGVfY291bnRzIDwtIHNldXJhdF9MMUBtZXRhLmRhdGEgJT4lCiAgZmlsdGVyKCFpcy5uYShURl9TdGF0ZSkpICU+JQogIGNvdW50KFRGX1N0YXRlKSAlPiUKICBtdXRhdGUoCiAgICBQcm9wb3J0aW9uID0gbiAvIHN1bShuKSAqIDEwMCwKICAgIFRGX1N0YXRlICAgPSBmYWN0b3IoVEZfU3RhdGUsIGxldmVscyA9IG5hbWVzKHN0YXRlX2NvbG9ycykpCiAgKQoKcHJpbnQoc3RhdGVfY291bnRzKQoKcF9iYXIgPC0gZ2dwbG90KAogIHN0YXRlX2NvdW50cywKICBhZXMoeCA9ICJMMSIsIHkgPSBQcm9wb3J0aW9uLCBmaWxsID0gVEZfU3RhdGUpCikgKwogIGdlb21fYmFyKAogICAgc3RhdCAgICAgID0gImlkZW50aXR5IiwKICAgIHdpZHRoICAgICA9IDAuNDUsCiAgICBjb2xvciAgICAgPSAid2hpdGUiLAogICAgbGluZXdpZHRoID0gMC4zCiAgKSArCiAgZ2VvbV90ZXh0KAogICAgYWVzKGxhYmVsID0gcGFzdGUwKHJvdW5kKFByb3BvcnRpb24sIDEpLCAiJSIpKSwKICAgIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpLAogICAgc2l6ZSAgICAgPSAzLjUsCiAgICBjb2xvciAgICA9ICJ3aGl0ZSIsCiAgICBmb250ZmFjZSA9ICJib2xkIgogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHN0YXRlX2NvbG9ycywgZHJvcCA9IEZBTFNFKSArCiAgc2NhbGVfeV9jb250aW51b3VzKAogICAgbGltaXRzID0gYygwLCAxMDApLAogICAgYnJlYWtzID0gc2VxKDAsIDEwMCwgMjUpLAogICAgbGFiZWxzID0gZnVuY3Rpb24oeCkgcGFzdGUwKHgsICIlIikKICApICsKICBsYWJzKAogICAgeSAgICAgPSAiUHJvcG9ydGlvbiBvZiBjZWxscyAoJSkiLAogICAgeCAgICAgPSBOVUxMLAogICAgZmlsbCAgPSAiVEYgU3RhdGUiLAogICAgdGl0bGUgPSAiU3RhdGUgZGlzdHJpYnV0aW9uIOKAlCBMMSIKICApICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDExKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwbG90LnRpdGxlICAgICAgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDExLCBoanVzdCA9IDAuNSksCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBheGlzLmxpbmUgICAgICAgPSBlbGVtZW50X2xpbmUoY29sb3IgPSAiYmxhY2siLCBsaW5ld2lkdGggPSAwLjQpCiAgKQoKcF9iYXIKYGBgCgoKCiMgQ29tYmluZSBQYW5lbHMgYW5kIFNhdmUgLSBGaWd1cmUgOEQKYGBge3IgfQpmaW5hbF9maWcgPC0gcF91bWFwICsgcF9iYXIgKwogIHBsb3RfbGF5b3V0KHdpZHRocyA9IGMoMi41LCAxKSkgKwogIHBsb3RfYW5ub3RhdGlvbigKICAgIHRpdGxlICAgID0gIkludHJhLWNsb25hbCByZWd1bGF0b3J5IGhldGVyb2dlbmVpdHkg4oCUIENlbGwgTGluZSBMMSIsCiAgICBzdWJ0aXRsZSA9ICJNb25vY2xvbmFsIHBvcHVsYXRpb24gc2ltdWx0YW5lb3VzbHkgb2NjdXBpZXMgSW5mbGFtbWF0b3J5LCBQcm9saWZlcmF0aXZlLCBhbmQgVGgxL0N5dG90b3hpYyBURiBzdGF0ZXMiLAogICAgdGhlbWUgICAgPSB0aGVtZSgKICAgICAgcGxvdC50aXRsZSAgICA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIsICBoanVzdCA9IDAuNSksCiAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksICBjb2xvciA9ICJncmV5NDAiLCBoanVzdCA9IDAuNSkKICAgICkKICApCgpmaW5hbF9maWcKCmdnc2F2ZSgiRmlndXJlXzhEX0wxX1RGc3RhdGVfVU1BUF9CYXIucGRmIiwKICAgICAgIHBsb3QgPSBmaW5hbF9maWcsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNC41LCBkcGkgPSAzMDAsIGRldmljZSA9ICJwZGYiKQoKZ2dzYXZlKCJGaWd1cmVfOERfTDFfVEZzdGF0ZV9VTUFQX0Jhci5wbmciLAogICAgICAgcGxvdCA9IGZpbmFsX2ZpZywgd2lkdGggPSA5LCBoZWlnaHQgPSA0LjUsIGRwaSA9IDMwMCkKCmNhdCgiRmlndXJlIDhEIHNhdmVkIHN1Y2Nlc3NmdWxseS5cbiIpCmBgYAoKIyBWYWxpZGF0aW9uIOKAlCBWaW9saW4gUGxvdHMgKFN1cHBsZW1lbnRhcnkgRmlndXJlIFNYKQpgYGB7cn0Kc2NvcmVfZmVhdHVyZXMgPC0gcGFzdGUwKCJTY29yZV8iLCBuYW1lcyhzdGF0ZV90ZnMpKQoKcF92YWwgPC0gVmxuUGxvdCgKICBzZXVyYXRfTDEsCiAgZmVhdHVyZXMgPSBzY29yZV9mZWF0dXJlcywKICBncm91cC5ieSA9ICJURl9TdGF0ZSIsCiAgcHQuc2l6ZSAgPSAwLAogIGNvbHMgICAgID0gc3RhdGVfY29sb3JzLAogIG5jb2wgICAgID0gMwopICYKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDkpICYKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ICAgICA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEsIHNpemUgPSA3KSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgICAgICA9IGVsZW1lbnRfdGV4dChzaXplID0gOSkKICApCgpwX3ZhbAoKZ2dzYXZlKCJTdXBwX1NYX1RGX3ZhbGlkYXRpb25fdmlvbGlucy5wZGYiLAogICAgICAgcGxvdCA9IHBfdmFsLCB3aWR0aCA9IDEyLCBoZWlnaHQgPSA0LjUsIGRwaSA9IDMwMCkKCmdnc2F2ZSgiU3VwcF9TWF9URl92YWxpZGF0aW9uX3Zpb2xpbnMucG5nIiwKICAgICAgIHBsb3QgPSBwX3ZhbCwgd2lkdGggPSAxMiwgaGVpZ2h0ID0gNC41LCBkcGkgPSAzMDAsIGJnID0gIndoaXRlIikKCmNhdCgiVmFsaWRhdGlvbiB2aW9saW5zIHNhdmVkIGFzIFBERiBhbmQgUE5HLlxuIikKCgpgYGAKCgoKIyAjIFNlc3Npb24gSW5mbwpgYGB7cn0Kc2Vzc2lvbkluZm8oKQoKYGBgCgoKCgoK