#!/usr/bin/env python3 """Compare 18 canonical owner4/owner2 hashes with the pre-cleanup L130 binary.""" from __future__ import annotations import json import re import subprocess import time from pathlib import Path ROOT = Path(__file__).resolve().parents[4] OUT = ROOT / "Saved/Linux/l131/canonical-matrix" DATASETS = { "owner4": ROOT / "Standalone/data/owner_world.json", "owner2": ROOT / "Standalone/data/regression/owner_world_2strate_l61.json", } RECIPES = {"owner4": ("surface-world",), "owner2": ("surface-world", "floating-islands")} BINARIES = { "base": ROOT / "Saved/Linux/l130/bin/VoxelForgeCoreHeadless-l130-{compiler}", "l131": ROOT / "Saved/Linux/l131/bin/VoxelForgeCoreHeadless-l131-{compiler}", } HASH_RE = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M) def run(dataset: str, recipe: str, compiler: str, workers: int, version: str) -> tuple[list[str], float]: binary = Path(str(BINARIES[version]).format(compiler=compiler)) label = f"{dataset}-{recipe}-{compiler}-w{workers}-{version}" log = OUT / f"{label}.log" command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout", "1740s", str(binary), str(DATASETS[dataset]), "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline", "--force-generate", f"--workers={workers}", "--clip-radius=3", "--max-clip-level=8", "--max-level=8", f"--recipe={recipe}", "--stream-hash"] started = time.monotonic() with log.open("w", encoding="utf-8") as stream: process = subprocess.run(command, cwd=ROOT, stdout=stream, stderr=subprocess.STDOUT, check=False, timeout=1740) hashes = HASH_RE.findall(log.read_text(encoding="utf-8", errors="replace")) if process.returncode or len(hashes) != 12: raise RuntimeError(f"hash run failed: {log}; exit={process.returncode}; rows={len(hashes)}") return hashes, time.monotonic() - started def main() -> int: OUT.mkdir(parents=True, exist_ok=True) rows = [] groups = {} for dataset, recipes in RECIPES.items(): for recipe in recipes: signatures = [] for workers in (1, 8, 12): for compiler in ("clang", "gcc"): before, before_s = run(dataset, recipe, compiler, workers, "base") after, after_s = run(dataset, recipe, compiler, workers, "l131") row = {"dataset": dataset, "recipe": recipe, "compiler": compiler, "workers": workers, "hash_mismatches": sum(a != b for a, b in zip(before, after)), "rows": len(after), "base_s": round(before_s, 3), "l131_s": round(after_s, 3), "base_log": f"Saved/Linux/l131/canonical-matrix/{dataset}-{recipe}-{compiler}-w{workers}-base.log", "l131_log": f"Saved/Linux/l131/canonical-matrix/{dataset}-{recipe}-{compiler}-w{workers}-l131.log"} rows.append(row) signatures.append((compiler, workers, after)) print("[L131CanonicalHash] " + json.dumps(row, sort_keys=True), flush=True) (OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n") first = signatures[0][2] stable = all(sig == first for _, _, sig in signatures) groups[f"{dataset}/{recipe}"] = {"base_equals_l131": all(row["hash_mismatches"] == 0 for row in rows if row["dataset"] == dataset and row["recipe"] == recipe), "all_clang_gcc_workers_agree": stable} failures = [row for row in rows if row["hash_mismatches"]] (OUT / "summary.json").write_text(json.dumps({"runs": rows, "groups": groups, "passed": len(rows)-len(failures), "failed": len(failures)}, indent=2) + "\n") return 1 if failures or not all(group["all_clang_gcc_workers_agree"] for group in groups.values()) else 0 if __name__ == "__main__": raise SystemExit(main())