#!/usr/bin/env python3 """Run the deterministic Linux2 seed/compiler/worker matrix under the owner limits.""" import csv import re import subprocess import sys import time from pathlib import Path ROOT = Path.cwd() ROUND = ROOT / "Standalone/Research/WorldAssemblerM3/Linux2" LOG_DIR = ROOT / "Saved/Linux" MEASURE = ROUND / "MeasureProcess.py" DATA = ROOT / "Standalone/data/owner_world.json" BINARIES = { "clang19": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-clang19-l2-off", "gcc14": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-gcc14-l2-off", } CAMERAS = { "surface-world": "0,-35,-487.94,0,-22", "floating-islands": "0,0,-32,0,-22", } HASH_RE = re.compile( r"\[PipelineWholeViewHash\] tiles=(\d+) density=0x([0-9a-f]+) " r"mesh=0x([0-9a-f]+) payload=0x([0-9a-f]+) topology=0x([0-9a-f]+)" ) VOLUME_RE = re.compile( r"\[PipelineVolume\] tiles=(\d+) lod=(\d+)\.\.(\d+) " r"min_voxel=\((-?\d+),(-?\d+),(-?\d+)\) " r"max_voxel=\((-?\d+),(-?\d+),(-?\d+)\)" ) READY_RE = re.compile(r"\[StreamPipeline\].*whole_view_ready=([0-9.]+)s") RESOURCE_RE = re.compile(r"\[ProcessResources\] exit_code=(-?\d+) wall_s=([0-9.]+) peak_rss_kib=(\d+)") def main() -> int: if not (1 <= len(sys.argv) <= 2): raise SystemExit("usage: RunSeedSweep.py [budget-seconds; default 1800]") budget_seconds = int(sys.argv[1]) if len(sys.argv) == 2 else 1800 seeds = [int(line.strip()) for line in (ROUND / "seeds.txt").read_text().splitlines() if line.strip()] if len(seeds) != 48 or len(set(seeds)) != len(seeds): raise SystemExit("seeds.txt must contain 48 unique signed int32 seeds") missing = [str(path) for path in [MEASURE, DATA, *BINARIES.values()] if not path.is_file()] if missing: raise SystemExit("missing sweep input/build: " + ", ".join(missing)) LOG_DIR.mkdir(parents=True, exist_ok=True) output_path = ROUND / "seed_hashes.csv" fields = [ "seed", "recipe", "compiler", "workers", "tiles", "lod_min", "lod_max", "min_x", "min_y", "min_z", "max_x", "max_y", "max_z", "density_hash", "mesh_hash", "payload_hash", "topology_hash", "whole_view_ready_s", "wall_s", "peak_rss_kib", "exit_code", "status", ] started = time.monotonic() total = len(seeds) * len(CAMERAS) * len(BINARIES) * 2 print(f"Sweep: {len(seeds)} seeds × 2 recipes × 2 compilers × W1/W12 = {total} runs; budget={budget_seconds}s", flush=True) with output_path.open("w", newline="") as csv_file: writer = csv.DictWriter(csv_file, fieldnames=fields) writer.writeheader() for seed in seeds: for recipe, camera in CAMERAS.items(): for compiler, binary in BINARIES.items(): for workers in (1, 12): remaining = budget_seconds - (time.monotonic() - started) if remaining <= 0: print("Sweep exceeded its 30 minute budget; stopping.", file=sys.stderr, flush=True) return 124 stem = f"linux2-seed-{seed}-{recipe}-{compiler}-w{workers}" log_path = LOG_DIR / f"{stem}.log" args = [ str(binary), str(DATA), f"--seed={seed}", f"--recipe={recipe}", "--render-distance-chunks=0", f"--workers={workers}", "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline", "--stream-hash", "--force-generate", "--clip-radius=1", "--max-clip-level=0", f"--camera={camera}", "--far-horizon-lod8-step=0", ] command = [ "taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "python3", str(MEASURE), *args, ] with log_path.open("w") as log_file: log_file.write("[SweepCommand] " + " ".join(command) + "\n") log_file.flush() try: result = subprocess.run( command, cwd=ROOT, stdout=log_file, stderr=subprocess.STDOUT, timeout=min(900, max(1, remaining)), check=False, ) exit_code = result.returncode except subprocess.TimeoutExpired: exit_code = 124 log_file.write("\n[SweepTimeout] run reached its allotted limit\n") output = log_path.read_text(errors="replace") volume = VOLUME_RE.search(output) hashes = HASH_RE.search(output) ready = READY_RE.search(output) resources = RESOURCE_RE.search(output) passed = exit_code == 0 and volume and hashes and ready and resources row = { "seed": seed, "recipe": recipe, "compiler": compiler, "workers": workers, "tiles": "", "lod_min": "", "lod_max": "", "min_x": "", "min_y": "", "min_z": "", "max_x": "", "max_y": "", "max_z": "", "density_hash": "", "mesh_hash": "", "payload_hash": "", "topology_hash": "", "whole_view_ready_s": "", "wall_s": "", "peak_rss_kib": "", "exit_code": exit_code, "status": "PASS" if passed else "FAIL", } if passed: row.update({ "tiles": hashes.group(1), "lod_min": volume.group(2), "lod_max": volume.group(3), "min_x": volume.group(4), "min_y": volume.group(5), "min_z": volume.group(6), "max_x": volume.group(7), "max_y": volume.group(8), "max_z": volume.group(9), "density_hash": "0x" + hashes.group(2), "mesh_hash": "0x" + hashes.group(3), "payload_hash": "0x" + hashes.group(4), "topology_hash": "0x" + hashes.group(5), "whole_view_ready_s": ready.group(1), "wall_s": resources.group(2), "peak_rss_kib": resources.group(3), }) writer.writerow(row) csv_file.flush() completed = sum(1 for _ in output_path.open()) - 1 elapsed = time.monotonic() - started print(f"[{completed}/{total}] seed={seed} recipe={recipe} compiler={compiler} W{workers} " f"tiles={row['tiles']} wall={row['wall_s']}s status={row['status']} " f"elapsed={elapsed:.1f}s", flush=True) if not passed: print(f"Run failed; details: {log_path}", file=sys.stderr, flush=True) return exit_code or 1 rows = list(csv.DictReader(output_path.open(newline=""))) keys = ("density_hash", "mesh_hash", "payload_hash", "topology_hash") checks = [] for seed in seeds: for recipe in CAMERAS: for workers in (1, 12): pair = [r for r in rows if int(r["seed"]) == seed and r["recipe"] == recipe and int(r["workers"]) == workers] equal = len(pair) == 2 and all(pair[0][key] == pair[1][key] for key in keys) checks.append((seed, recipe, workers, equal)) worker_checks = [] for seed in seeds: for recipe in CAMERAS: for compiler in BINARIES: pair = [r for r in rows if int(r["seed"]) == seed and r["recipe"] == recipe and r["compiler"] == compiler] equal = len(pair) == 2 and all(pair[0][key] == pair[1][key] for key in keys) worker_checks.append((seed, recipe, compiler, equal)) summary_path = ROUND / "seed_sweep_summary.txt" mismatch_count = sum(not equal for _, _, _, equal in checks) worker_mismatch_count = sum(not equal for _, _, _, equal in worker_checks) summary_path.write_text( f"runs={len(rows)} seeds={len(seeds)} recipes={len(CAMERAS)} compilers={len(BINARIES)} " f"workers=1,12 cross_compiler_comparisons={len(checks)} cross_compiler_mismatches={mismatch_count} " f"cross_worker_comparisons={len(worker_checks)} cross_worker_mismatches={worker_mismatch_count} " f"elapsed_s={time.monotonic()-started:.3f}\n" ) print(summary_path.read_text(), end="", flush=True) return 0 if mismatch_count == 0 and worker_mismatch_count == 0 else 3 if __name__ == "__main__": raise SystemExit(main())