#!/usr/bin/env python3 """Re-run the L25 hash command and 24 m crossing on the L31 compiler matrix.""" import re import subprocess import time from pathlib import Path ROOT = Path(__file__).resolve().parents[4] OUT = ROOT / "Saved/Linux/l31" OUT.mkdir(parents=True, exist_ok=True) BINARIES = { "clang": OUT / "bin/VoxelForgeCoreHeadless-linux31-clang", "gcc": OUT / "bin/VoxelForgeCoreHeadless-linux31-gcc", } HASH_PATTERN = re.compile( r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M) rows = [] failed = False candidate_reference = {} fi_hash_changes = {"l25": False, "crossing24": False} def hashes(path): return HASH_PATTERN.findall(path.read_text(encoding="utf-8", errors="replace")) def run(kind, compiler, recipe, workers): global failed name = f"{kind}-{compiler}-{recipe}-w{workers}" logfile = OUT / f"{name}.log" command = [ str(BINARIES[compiler]), str(ROOT / "Standalone/data/owner_world.json"), "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline", "--stream-hash", "--force-generate", "--clip-radius=3", "--max-clip-level=8", "--far-horizon-lod8-step=0", f"--workers={workers}", f"--recipe={recipe}", ] if kind == "crossing24": command.extend(("--camera-step=24,0,0", "--region-crossing")) started = time.monotonic() try: with logfile.open("w", encoding="utf-8") as stream: result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT, timeout=1800, check=False) except subprocess.TimeoutExpired: result = None elapsed = time.monotonic() - started text = logfile.read_text(encoding="utf-8", errors="replace") stamps = re.findall( r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$", text, re.M) current = hashes(logfile) # The normal L30 log stem is ringfix-hash; crossing uses ringfix-crossing24. baseline = ROOT / "Saved/Linux/l30" / ( f"ringfix-crossing24-{compiler}-{recipe}-w{workers}.log" if kind == "crossing24" else f"ringfix-hash-{compiler}-{recipe}-w{workers}.log") reference = hashes(baseline) if baseline.is_file() else [] key = (kind, recipe) prior = candidate_reference.get(key) stable = prior is None or current == prior if prior is None and current: candidate_reference[key] = current baseline_match = bool(reference) and current == reference baseline_changed = bool(reference) and current != reference if recipe == "surface-world": baseline_ok = baseline_match comparison = "MATCH" if baseline_match else "MISMATCH" else: baseline_ok = baseline_changed comparison = "EXPECTED_CHANGE" if baseline_changed else "UNCHANGED_OR_MISSING" fi_hash_changes[kind] |= baseline_changed ok = (result is not None and result.returncode == 0 and stamps == [(recipe, recipe, "PASS")] and "hash=on" in text and len(current) == 12 and stable and baseline_ok) rows.append((kind, recipe, compiler, workers, elapsed, result.returncode if result else "TIMEOUT", len(current), ok)) print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} " f"hashes={len(current)} " f"L30={comparison} candidate_stable={'yes' if stable else 'no'} " f"{'PASS' if ok else 'FAIL'}", flush=True) if not ok: failed = True for kind in ("l25", "crossing24"): for recipe in ("surface-world", "floating-islands"): for compiler in ("clang", "gcc"): for workers in (1, 8, 12): run("crossing24" if kind == "crossing24" else "l25", compiler, recipe, workers) summary = OUT / "determinism.csv" summary.write_text( "kind,recipe,compiler,workers,elapsed_seconds,exit_code,hash_rows,status\n" + "".join(f"{kind},{recipe},{compiler},{workers},{elapsed:.3f},{code},{count}," f"{'PASS' if ok else 'FAIL'}\n" for kind, recipe, compiler, workers, elapsed, code, count, ok in rows), encoding="utf-8") for kind, changed in fi_hash_changes.items(): if not changed: failed = True print(f"[L31ExpectedGeometryHashChange] kind={kind} detected=false FAIL", flush=True) else: print(f"[L31ExpectedGeometryHashChange] kind={kind} detected=true PASS", flush=True) print(f"[L31Determinism] runs={len(rows)} pass={sum(row[-1] for row in rows)} " f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True) raise SystemExit(1 if failed or len(rows) != 24 else 0)