Files
Robert Allan JamesandClaude Sonnet 5 7e2fd9f044 Fix SWAP-MTX: Fisher-Yates shuffle was never actually shuffling correctly
Found while building the analysis report for the ACL-RWT relaunch
campaign: cfg=0 was missing from run coverage for 2 of 3 seeds, reproduced
identically across all three architectures. Root-caused rather than
worked around, per Captain Bob's "this is worrisome."

SWAP-MTX (capsules/doe.4th Block 2104) never actually swapped two
RUN-MATRIX cells -- it performed a lossy one-way copy (second MATRIX!
call mis-targeted mat[i] again instead of mat[j]). Confirmed by direct
empirical test on the hosted build: INIT-MATRIX gives mat[0]=0, mat[5]=5;
after 0 5 SWAP-MTX, mat[0]=0 (unchanged, should be 5) and mat[5]=0
(correct), with the original value 5 permanently destroyed. Every
Fisher-Yates shuffle this mechanism has ever run silently duplicated some
values and dropped others -- not a true permutation. Not new, not
introduced by item 4.6/Stadium work; predates this session.

Fixed with explicit temp variables (SW-I/SW-J/SW-VI/SW-VJ), trivially
verifiable by inspection over clever stack juggling. Verified on the
hosted build for all three seeds used by the relaunch campaign: each now
produces all 16 cfg values exactly 30 times, run_id 0-479 fully distinct.
Three-arch QEMU acceptance clean: 1012/0/0 POST on all three, identical
dict_hash (expected -- doe.4th isn't C-registered or auto-loaded at
boot). BLOCK_MAP.md correctly shows only doe.4th's own hash changed.

Also includes the R analysis/chart pipeline (analyse_stadium_relaunch.R)
built for the relaunch campaign report, and the three acceptance boot
logs.

Retroactive caveat: the relaunch campaign's own run-matrix coverage
(experiments/bare_metal/runs/acl-rwt-20260820/) is not a valid uniform
permutation, having run against the buggy shuffle. Whether to re-run it
against the fix is a separate call, not made here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 10:56:12 -04:00

230 lines
11 KiB
R

#!/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")