Not for assembler/world: the 8 m -> 4 m strate chunk change moves every stratum, so the SurfaceWorld spawn camera floats in a void and the L1 mesh export shrank 58.7 -> 31.7 MB. Kept for its diagnostics, the boundarySeal finding and the readable capture shading. See Linux17/REPORT.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
121 lines
5.6 KiB
Python
121 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Run the L17 deterministic W1/W8/W12 x5 matrix on both supported Linux builds."""
|
|
import os
|
|
import re
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[4]
|
|
OUT = ROOT / "Saved/Linux/l17-matrix"
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
if not os.sched_getaffinity(0).issubset(set(range(12))):
|
|
raise SystemExit("run under taskset -c 0-11")
|
|
|
|
BINARIES = {
|
|
"clang-off": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux17-final-clang",
|
|
"gcc-default": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux17-final-gcc",
|
|
}
|
|
RECIPES = {
|
|
"floating-islands": "-10,-31,-22,0,-8",
|
|
"surface-world": "0,-35,-75,0,0",
|
|
}
|
|
HASH_PATTERNS = {
|
|
"full": re.compile(r"^\[PipelineFullDetailHash\].*$", re.M),
|
|
"whole": re.compile(r"^\[PipelineWholeViewHash\].*$", re.M),
|
|
"lod": re.compile(r"^\[PipelineLODHash\].*$", re.M),
|
|
"exact": re.compile(r"^\[PipelineExactQueryHash\].*$", re.M),
|
|
}
|
|
rows = []
|
|
all_pass = True
|
|
|
|
for recipe, camera in RECIPES.items():
|
|
for compiler, binary in BINARIES.items():
|
|
if not binary.exists():
|
|
raise SystemExit(f"missing binary: {binary}")
|
|
for workers, repeat in [(1, 1), (8, 1), (12, 1), (12, 2), (12, 3), (12, 4), (12, 5)]:
|
|
name = f"{recipe}-{compiler}-w{workers}-r{repeat}"
|
|
logfile = OUT / f"{name}.log"
|
|
command = ["prlimit", "--as=6442450944", "--", str(binary),
|
|
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", f"--workers={workers}", f"--camera={camera}",
|
|
f"--recipe={recipe}"]
|
|
started = time.monotonic()
|
|
peak_rss_kib = 0
|
|
with logfile.open("w", encoding="utf-8") as stream:
|
|
process = subprocess.Popen(command, stdout=stream, stderr=subprocess.STDOUT,
|
|
start_new_session=True)
|
|
while process.poll() is None:
|
|
pending = [process.pid]
|
|
seen = set()
|
|
while pending:
|
|
pid = pending.pop()
|
|
if pid in seen:
|
|
continue
|
|
seen.add(pid)
|
|
try:
|
|
children = Path(f"/proc/{pid}/task/{pid}/children").read_text().split()
|
|
pending.extend(int(value) for value in children)
|
|
for line in Path(f"/proc/{pid}/status").read_text().splitlines():
|
|
if line.startswith("VmRSS:"):
|
|
peak_rss_kib = max(peak_rss_kib, int(line.split()[1]))
|
|
break
|
|
except (FileNotFoundError, PermissionError, ProcessLookupError, ValueError):
|
|
pass
|
|
if time.monotonic() - started > 900:
|
|
os.killpg(process.pid, signal.SIGTERM)
|
|
try:
|
|
process.wait(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
os.killpg(process.pid, signal.SIGKILL)
|
|
process.wait()
|
|
all_pass = False
|
|
break
|
|
time.sleep(0.1)
|
|
code = process.wait()
|
|
elapsed = time.monotonic() - started
|
|
output = logfile.read_text(encoding="utf-8", errors="replace")
|
|
matched = {key: pattern.findall(output) for key, pattern in HASH_PATTERNS.items()}
|
|
ok = code == 0 and all(matched.values()) and len(matched["lod"]) == 9
|
|
if not ok:
|
|
all_pass = False
|
|
row = {"recipe": recipe, "compiler": compiler, "workers": workers,
|
|
"repeat": repeat, "exit": code, "seconds": elapsed,
|
|
"peak_rss_kib": peak_rss_kib, "ok": ok,
|
|
"full": matched["full"][-1] if matched["full"] else "",
|
|
"whole": matched["whole"][-1] if matched["whole"] else "",
|
|
"lod": " || ".join(matched["lod"]),
|
|
"exact": matched["exact"][-1] if matched["exact"] else ""}
|
|
rows.append(row)
|
|
print(f"[MatrixRun] {name} exit={code} seconds={elapsed:.3f} "
|
|
f"peak_rss_kib={peak_rss_kib} hash_rows={sum(map(len,matched.values()))} "
|
|
f"status={'PASS' if ok else 'FAIL'}", flush=True)
|
|
|
|
summary = OUT / "summary.tsv"
|
|
columns = ["recipe", "compiler", "workers", "repeat", "exit", "seconds", "peak_rss_kib",
|
|
"ok", "full", "whole", "lod", "exact"]
|
|
with summary.open("w", encoding="utf-8") as f:
|
|
f.write("\t".join(columns) + "\n")
|
|
for row in rows:
|
|
f.write("\t".join(str(row[key]) for key in columns) + "\n")
|
|
|
|
for recipe in RECIPES:
|
|
reference = None
|
|
for row in rows:
|
|
if row["recipe"] != recipe:
|
|
continue
|
|
signature = (row["full"], row["whole"], row["lod"], row["exact"])
|
|
if reference is None:
|
|
reference = signature
|
|
elif signature != reference:
|
|
print(f"[MatrixMismatch] recipe={recipe} workers={row['workers']} "
|
|
f"compiler={row['compiler']} repeat={row['repeat']}", flush=True)
|
|
all_pass = False
|
|
print(f"[MatrixSummary] rows={len(rows)} expected=28 result={'PASS' if all_pass else 'FAIL'} "
|
|
f"path={summary}", flush=True)
|
|
raise SystemExit(0 if all_pass and len(rows) == 28 else 1)
|