#!/usr/bin/env Rscript # analyse_stadium_relaunch.R # StarForth LithosAnanke — EXEC-DOE 3x3 Latin square campaign, # re-run on the post-item-4.6 Stadium substrate (2026-08-20). # # Generates figures + tables for stadium_relaunch_report.tex # # Data source: runs/acl-rwt-20260820/{arch}-seed{seed}.csv # 9 cells: {amd64,aarch64,riscv64} x {12345,67890,13579} # 480 rows/cell (16 L8 configs x 30 reps, Fisher-Yates shuffled) # # Scope note: this campaign validates that the EXEC-DOE mechanism runs # cleanly and completely on the Stadium substrate (item 5.1's own concern: # "a green POST suite is not evidence that determinism holds"). It is NOT # an ACL-RWT overhead measurement -- ACL.4th is not self-activated in this # repo's default init.4th, so these cells ran with ACL inactive. suppressPackageStartupMessages({ library(ggplot2) library(svglite) library(dplyr) library(tidyr) library(scales) library(patchwork) }) SCRIPT_DIR <- tryCatch( dirname(normalizePath(sys.frames()[[1]]$ofile)), error = function(e) getwd() ) BASE_DIR <- normalizePath(file.path(SCRIPT_DIR, "..")) RUNS_DIR <- file.path(BASE_DIR, "runs", "acl-rwt-20260820") OUT_CHARTS <- file.path(SCRIPT_DIR, "charts") OUT_TABLES <- file.path(SCRIPT_DIR, "tables") dir.create(OUT_CHARTS, showWarnings = FALSE, recursive = TRUE) dir.create(OUT_TABLES, showWarnings = FALSE, recursive = TRUE) cat("══════════════════════════════════════════════════════════════════\n") cat(" StarForth LithosAnanke — Stadium-substrate EXEC-DOE relaunch\n") cat(" Item 5.1 / F.3 — campaign-mechanism validation, 2026-08-20\n") cat("══════════════════════════════════════════════════════════════════\n\n") # ── palette (matches analyse_acl_rwt.R) ─────────────────────────────────────── arch_colours <- c(amd64 = "#E07B39", aarch64 = "#4A90D9", riscv64 = "#50C878") theme_light_sf <- function(base = 11) { theme_minimal(base_size = base) %+replace% theme( panel.grid.minor = element_blank(), panel.grid.major = element_line(colour = "grey90"), strip.text = element_text(face = "bold"), plot.title = element_text(face = "bold", size = base + 1), plot.subtitle = element_text(colour = "grey40", size = base - 2), legend.position = "bottom", legend.key.size = unit(0.5, "cm") ) } theme_dark_sf <- function(base = 11) { theme_minimal(base_size = base) %+replace% theme( panel.background = element_rect(fill = "#0d0d0d", colour = NA), plot.background = element_rect(fill = "#0d0d0d", colour = NA), panel.grid.major = element_line(colour = "#1e1e1e"), panel.grid.minor = element_blank(), axis.text = element_text(colour = "#aaaaaa"), axis.title = element_text(colour = "#cccccc"), strip.text = element_text(colour = "white", face = "bold"), plot.title = element_text(colour = "white", face = "bold", size = base + 1), plot.subtitle = element_text(colour = "#666666", size = base - 2), legend.text = element_text(colour = "#aaaaaa"), legend.title = element_text(colour = "#cccccc"), legend.background = element_rect(fill = "#0d0d0d", colour = NA), legend.position = "bottom", legend.key.size = unit(0.5, "cm") ) } save_svg <- function(plot, name, w = 12, h = 7) { path <- file.path(OUT_CHARTS, paste0(name, ".svg")) svglite(path, width = w, height = h) print(plot) dev.off() cat(sprintf(" Saved: %s.svg\n", name)) invisible(path) } # ── load data ────────────────────────────────────────────────────────────── col_names <- c("run_id", "cfg", "rep", "ent_in", "cv_in", "tmp_in", "stb_in", "l8_mode", "win_div", "infer_win", "infer_dec_q", "infer_var_q", "early_exit", "bc_mean_q", "bb_mean_q", "fit_q") archs <- c("amd64", "aarch64", "riscv64") seeds <- c("12345", "67890", "13579") load_cell <- function(arch, seed) { f <- file.path(RUNS_DIR, sprintf("%s-seed%s.csv", arch, seed)) raw <- readLines(f) data_lines <- raw[grepl("^[0-9]", raw)] df <- read.csv(text = paste(data_lines, collapse = "\n"), header = FALSE, col.names = col_names, strip.white = TRUE) df$arch <- arch df$seed <- seed df } cat("Loading 9 cells...\n") all_data <- bind_rows(lapply(archs, function(a) { bind_rows(lapply(seeds, function(s) load_cell(a, s))) })) cat(sprintf(" Total rows loaded: %d (expect 4320)\n", nrow(all_data))) # ── per-cell completeness table ─────────────────────────────────────────── cell_summary <- all_data %>% group_by(arch, seed) %>% summarise( n_rows = n(), n_distinct_run_id = n_distinct(run_id), l8_modes = n_distinct(l8_mode), mean_win_div = mean(win_div), mean_fit_q = mean(fit_q), mean_bc_mean_q = mean(bc_mean_q), mean_bb_mean_q = mean(bb_mean_q), .groups = "drop" ) cell_summary$arch <- factor(cell_summary$arch, levels = archs) write.csv(cell_summary, file.path(OUT_TABLES, "stadium_relaunch_cell_summary.csv"), row.names = FALSE) cat("\nCell summary:\n") print(as.data.frame(cell_summary)) # ── cross-arch / cross-seed invariance tests (Kruskal-Wallis, matches the # original report's own non-parametric methodology for this kind of # campaign-level distributional comparison) ───────────────────────────── kw_arch_fit <- kruskal.test(fit_q ~ arch, data = all_data) kw_arch_win <- kruskal.test(win_div ~ arch, data = all_data) kw_seed_fit <- kruskal.test(fit_q ~ seed, data = all_data) kw_seed_win <- kruskal.test(win_div ~ seed, data = all_data) kw_results <- capture.output({ cat("Kruskal-Wallis: fit_q ~ arch\n"); print(kw_arch_fit); cat("\n") cat("Kruskal-Wallis: win_div ~ arch\n"); print(kw_arch_win); cat("\n") cat("Kruskal-Wallis: fit_q ~ seed\n"); print(kw_seed_fit); cat("\n") cat("Kruskal-Wallis: win_div ~ seed\n"); print(kw_seed_win); cat("\n") }) writeLines(kw_results, file.path(OUT_TABLES, "stadium_relaunch_kw_results.txt")) cat("\n") cat(paste(kw_results, collapse = "\n")) cat("\n\n") # ══════════════════════════════════════════════════════════════════════════ # FIGURE 1: 3x3 Latin square completeness heatmap (rows captured per cell) # ══════════════════════════════════════════════════════════════════════════ cat("[SR-1] Completeness heatmap (light + dark)...\n") df_heat <- cell_summary %>% mutate(seed = factor(seed, levels = seeds)) make_heatmap <- function(dark = FALSE) { thm <- if (dark) theme_dark_sf() else theme_light_sf() txt <- if (dark) "white" else "grey10" ggplot(df_heat, aes(x = seed, y = arch, fill = n_rows)) + geom_tile(colour = if (dark) "#0d0d0d" else "white", linewidth = 1.5) + geom_text(aes(label = sprintf("%d/480", n_rows)), colour = txt, fontface = "bold", size = 4.2) + scale_fill_gradient(low = "#c0392b", high = "#2CA02C", limits = c(0, 480), name = "Rows captured") + labs( title = "EXEC-DOE Campaign Completeness — Stadium Substrate", subtitle = "3x3 Latin square: 3 seeds x 3 ISAs, 30 reps x 16 L8 configs per cell (480 rows/cell)", x = "Seed", y = "Architecture" ) + thm } save_svg(make_heatmap(FALSE), "stadium_relaunch_completeness_light", w = 9, h = 6) save_svg(make_heatmap(TRUE), "stadium_relaunch_completeness_dark", w = 9, h = 6) # ══════════════════════════════════════════════════════════════════════════ # FIGURE 2: fit_q distribution by architecture (all seeds pooled) # ══════════════════════════════════════════════════════════════════════════ cat("[SR-2] fit_q distribution by architecture (light + dark)...\n") all_data$arch <- factor(all_data$arch, levels = archs) make_fit_box <- function(dark = FALSE) { thm <- if (dark) theme_dark_sf() else theme_light_sf() ggplot(all_data, aes(x = arch, y = fit_q, fill = arch)) + geom_boxplot(alpha = 0.85, outlier.size = 0.6, outlier.alpha = 0.4) + scale_fill_manual(values = arch_colours, guide = "none") + scale_x_discrete(labels = c(amd64 = "amd64\n(x86-64)", aarch64 = "aarch64\n(ARMv8-A)", riscv64 = "riscv64\n(RV64GC)")) + labs( title = "Inference-Fit Distribution Across ISAs", subtitle = sprintf( "Kruskal-Wallis fit_q ~ arch: H=%.3f, p=%.4f (all 4,320 rows, 3 seeds pooled per ISA)", kw_arch_fit$statistic, kw_arch_fit$p.value ), x = "Instruction-Set Architecture", y = "fit_q (Q16 fixed-point)" ) + thm } save_svg(make_fit_box(FALSE), "stadium_relaunch_fitq_box_light", w = 9, h = 6) save_svg(make_fit_box(TRUE), "stadium_relaunch_fitq_box_dark", w = 9, h = 6) # ══════════════════════════════════════════════════════════════════════════ # FIGURE 3: mean win_div per (arch, seed) cell — grouped bar # ══════════════════════════════════════════════════════════════════════════ cat("[SR-3] Mean window-diversity per cell (light + dark)...\n") df_bar <- cell_summary %>% mutate(seed = factor(seed, levels = seeds)) make_windiv_bar <- function(dark = FALSE) { thm <- if (dark) theme_dark_sf() else theme_light_sf() ggplot(df_bar, aes(x = seed, y = mean_win_div, fill = arch)) + geom_col(position = position_dodge(width = 0.75), width = 0.65, colour = NA, alpha = 0.92) + scale_fill_manual(values = arch_colours, name = "ISA") + labs( title = "Mean Window-Diversity per Campaign Cell", subtitle = "9 cells, 480 runs each, Stadium substrate (post item-4.6 quota-grant fix)", x = "Seed", y = "Mean win_div" ) + thm } save_svg(make_windiv_bar(FALSE), "stadium_relaunch_windiv_bar_light", w = 9, h = 6) save_svg(make_windiv_bar(TRUE), "stadium_relaunch_windiv_bar_dark", w = 9, h = 6) cat("\nDone. Tables in analysis/tables/, charts in analysis/charts/.\n")