Files
VoxelForge/Standalone/Research/WorldAssemblerM3/Linux150/RunL150Captures.py
T
Fr0zkaandClaude Opus 5.5 9ac60d5d18 research: cave planner merge evidence (L150, measurement only)
Merges cleanly. UE start: S1 -9%, S3 +11%, S0/S2 ~+1%. 30 m/s: near coverage 44.2 -> 38.5%, far holes +0.06 pp, p99
+2%. L80 near-ready slower in 6/8 cells (up to +7.9%), whole view faster in 7/8. Sweeps 15/16 each (seed 2).
Source/: VoxelForgeCoreAdapter.cpp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-01 16:17:42 +01:00

254 lines
13 KiB
Python

#!/usr/bin/env python3
"""Capture four matched eye-height owner-world views for assembler/world and planner HEAD."""
from __future__ import annotations
import json
import math
import os
from pathlib import Path
import re
import subprocess
import sys
ROOT = Path(__file__).resolve().parents[4]
DATA = ROOT / "Standalone/data/owner_world.json"
OUT = ROOT / "Saved/Linux/l150/captures"
LAYOUT = OUT / "planner-head-resolved-layout.json"
ARTIFACTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux150/captures"
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
VERSIONS = {
"assembler-world": {
"headless": ROOT / "Saved/Linux/l137/bin/VoxelForgeCoreHeadless-l137-aa-a",
"renderer": ROOT / "Saved/Linux/l137/bin/VoxelForgeOffscreenCapture-l137-aa-a",
"stamp": "4499fb2a8a4ede7d6cbc68d1b794f104658cdaae93ed9cacbc91fe7ff3fc3621",
},
"planner-head": {
"headless": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l150-head-clang",
"renderer": ROOT / "Standalone/bin/linux/VoxelForgeOffscreenCapture-l150-head-clang",
"stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f",
},
}
STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", re.M)
def fmt(values) -> str:
return ",".join(f"{value:.6f}" for value in values)
def angles(camera, target) -> tuple[float, float]:
dx, dy, dz = (target[index] - camera[index] for index in range(3))
return math.degrees(math.atan2(dx, dy)), math.degrees(
math.atan2(dz, math.hypot(dx, dy)))
def point_um(values) -> tuple[float, float, float]:
return tuple(value / 1_000_000.0 for value in values)
def point_q(layout: dict, values) -> tuple[float, float, float]:
scale = layout["core_voxel_size_um"] / (256.0 * 1_000_000.0)
return tuple(value * scale for value in values)
def camera_for_mouth(layout: dict, edge: dict):
anchor = edge["start_anchor_stable_id"]
if anchor.endswith("/lower-mouth"):
side = "lower"
mouth_q = next(row["lower_mouth_q"] for row in layout["passages"]
if row["stable_id"] == anchor.removesuffix("/lower-mouth"))
elif anchor.endswith("/upper-mouth"):
side = "upper"
mouth_q = next(row["upper_mouth_q"] for row in layout["passages"]
if row["stable_id"] == anchor.removesuffix("/upper-mouth"))
else:
raise ValueError(f"mouth edge has no passage anchor: {anchor}")
passage_id = anchor.rsplit("/", 1)[0]
passage = next(row for row in layout["passages"] if row["stable_id"] == passage_id)
controls = passage["control_points"]
mouth_index = len(controls) - 1 if side == "lower" else 0
neighbor_index = mouth_index - 1 if side == "lower" else 1
a = controls[neighbor_index]["position_q"]
b = controls[mouth_index]["position_q"]
sign = 1.0 if side == "lower" else -1.0
tx, ty = sign * (b[0] - a[0]), sign * (b[1] - a[1])
length = math.hypot(tx, ty)
if length <= 1.0e-9:
raise ValueError(f"degenerate passage mouth tangent: {anchor}")
tx, ty = tx / length, ty / length
mouth = point_q(layout, mouth_q)
eye_z = mouth[2] + 1.45
camera = (mouth[0] - tx * 2.0, mouth[1] - ty * 2.0, eye_z)
target = (mouth[0] + tx * 10.0, mouth[1] + ty * 10.0, eye_z)
return camera, target, (mouth[0], mouth[1], eye_z, 16.0)
def views(layout: dict) -> list[dict]:
edges = {row["stable_id"]: row for row in layout["cavern_edges"]}
tunnel_views = []
for stable_id, name in (
("tunnel-network/strate-2/route-edge-0", "tunnel-network-route"),
("tunnel-network/strate-2/departure-edge", "tunnel-network-departure")):
camera, target, focus = camera_for_mouth(layout, edges[stable_id])
tunnel_views.append({"name": name, "layer": "Tunnel Network", "camera": camera,
"target": target, "focus": focus})
rooms = {row["stable_id"]: row for row in layout["cavern_rooms"]}
shaft = rooms["vertical-shafts/strate-3/shaft-0/primitive-0"]
access = edges["vertical-shafts/strate-3/shaft-access-0"]["polyline_um"]
center = point_um(shaft["center_um"])
half = point_um(shaft["half_extents_um"])
shaft_top = center[2] + half[2]
access_a, access_b = point_um(access[0]), point_um(access[-1])
ledge_z = shaft_top - 12 * (shaft["ledge_spacing_um"] / 1.0e6)
shaft_views = [
{"name": "vertical-shafts-looking-down", "layer": "Vertical Shafts",
"camera": (access_b[0], access_b[1], access_b[2] + 1.45),
"target": (center[0], center[1], shaft_top - 30.0),
"focus": (center[0], center[1], shaft_top - 25.0, 14.0), "fov": 35.0},
{"name": "vertical-shafts-looking-up", "layer": "Vertical Shafts",
"camera": (center[0], center[1], ledge_z + 1.45),
"target": (center[0], center[1], shaft_top - 2.0),
"focus": (center[0], center[1], shaft_top - 22.0, 20.0)},
]
return tunnel_views + shaft_views
def stamped_run(name: str, command: list[object], log: Path, expected_stamp: str,
env: dict[str, str]) -> str:
binary = Path(str(command[0]))
probe = subprocess.run([*LIMIT, "timeout", "15s", str(binary), "--print-source-stamp"],
cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, check=False, timeout=20)
match = STAMP_RE.search(probe.stdout or "")
if probe.returncode or not match or match.group(1) != expected_stamp:
raise RuntimeError(f"{name}: source stamp mismatch for {binary}")
log.parent.mkdir(parents=True, exist_ok=True)
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run([*LIMIT, "timeout", "1740s", *map(str, command)],
cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT,
timeout=1745, check=False)
output = log.read_text(encoding="utf-8", errors="replace")
emitted = STAMP_RE.search(output)
if result.returncode or not emitted or emitted.group(1) != expected_stamp:
raise RuntimeError(f"{name}: export or render failed; see {log}")
return output
def resolve_planner_layout(env: dict[str, str]) -> dict:
config = VERSIONS["planner-head"]
command = [config["headless"], DATA, "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--workers=12",
"--clip-radius=6", "--max-clip-level=0", "--max-level=0",
"--force-generate", "--recipe=surface-world", "--resolved-layout-only",
f"--resolved-layout-output={LAYOUT}"]
stamped_run("planner-head-resolve-layout", list(map(str, command)),
OUT / "planner-head-resolved-layout.log", config["stamp"], env)
layout = json.loads(LAYOUT.read_text(encoding="utf-8"))
ids = {row["stable_id"] for row in layout["cavern_edges"]}
required = {"tunnel-network/strate-2/route-edge-0",
"tunnel-network/strate-2/departure-edge",
"vertical-shafts/strate-3/shaft-access-0"}
if not required.issubset(ids):
raise RuntimeError("resolved owner seed-0 layout is missing a capture anchor")
return layout
def main() -> int:
if not DATA.is_file():
raise SystemExit("missing owner fixture")
OUT.mkdir(parents=True, exist_ok=True)
ARTIFACTS.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["LP_NUM_THREADS"] = "12"
env["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
layout = resolve_planner_layout(env)
quality_tool = ROOT / "Standalone/Tools/verify/capture_quality.py"
manifest = []
failures = []
for version, config in VERSIONS.items():
for view in views(layout):
name = view["name"]
camera, target = view["camera"], view["target"]
yaw, pitch = angles(camera, target)
mesh_dir = OUT / "meshes" / version
mesh_dir.mkdir(parents=True, exist_ok=True)
mesh = mesh_dir / f"{name}.vfmesh"
capture_layout = mesh_dir / f"{name}-layout.json"
export = [config["headless"], DATA, "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--workers=12",
"--clip-radius=6", "--max-clip-level=0", "--max-level=0",
"--force-generate", "--recipe=surface-world",
"--render-distance-chunks=0", f"--camera={fmt((*camera, yaw, pitch))}",
f"--resolved-layout-output={capture_layout}", f"--mesh-export={mesh}"]
stamped_run(f"{version}-{name}-export", list(map(str, export)),
OUT / f"{version}-{name}-export.log", config["stamp"], env)
if not mesh.is_file() or mesh.stat().st_size == 0:
raise RuntimeError(f"{version}-{name}: empty mesh export")
rendered_layout = json.loads(capture_layout.read_text(encoding="utf-8"))
png = OUT / f"{version}-{name}.png"
jpg = ARTIFACTS / f"{version}-{name}.jpg"
render = [config["renderer"], mesh, png, "--size=1280x720",
f"--fov-deg={float(view.get('fov', 78.0)):.1f}",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}",
f"--pitch-deg={pitch:.6f}", "--tile-skirts=off", "--shade=readable",
"--headlamp=on", "--height-tint=on", "--height-center-m=-950",
"--height-range-m=60", "--tile-edges=off",
f"--focus-region={fmt(view['focus'])}"]
stamped_run(f"{version}-{name}-render", list(map(str, render)),
OUT / f"{version}-{name}-render.log", config["stamp"], env)
subprocess.run([*LIMIT, "timeout", "120s", "ffmpeg", "-nostdin",
"-hide_banner", "-loglevel", "error", "-y", "-i", png,
"-frames:v", "1", "-q:v", "4", jpg], cwd=ROOT, env=env,
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT,
timeout=125)
check = subprocess.run([*LIMIT, "timeout", "120s", sys.executable,
quality_tool, jpg], cwd=ROOT, env=env, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
check=False, timeout=125)
quality_ok = check.returncode == 0 and "status=PASS" in check.stdout
if not quality_ok:
failures.append(f"{version}-{name}")
quality_line = (check.stdout.strip().splitlines()[-1]
if check.stdout.strip() else f"quality exit={check.returncode}")
manifest.append({"version": version, "view": name, "layer": view["layer"],
"file": str(jpg.relative_to(ROOT)), "camera_m": camera, "target_m": target,
"yaw_deg": yaw, "pitch_deg": pitch,
"source_stamp": config["stamp"],
"capture_layout_identity": rendered_layout.get("stable_identity"),
"pose_layout_identity": layout.get("stable_identity"), "quality_status":
"PASS" if quality_ok else "FAIL", "quality": quality_line})
print(f"[L150Capture] version={version} view={name} quality="
f"{'PASS' if quality_ok else 'FAIL'} file={jpg.relative_to(ROOT)}",
flush=True)
(ARTIFACTS / "manifest.json").write_text(
json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
(ARTIFACTS / "INDEX.md").write_text(
"# L150 matched owner-world captures\n\n"
"Seed 0, with camera poses from the planner layout resolved against the owner's "
"embedded Core plan and reused for both versions. The plan is imported without a seed "
"override. The two "
"Tunnel Network views sit at the planned route and departure joints; the Vertical "
"Shafts views look down from the access landing and up from a ledge.\n\n"
+ "\n".join(f"- {row['layer']} / {row['view']}: "
f"[{row['version']}]({Path(row['file']).name}) "
f"— {row['quality_status']}" for row in manifest) + "\n",
encoding="utf-8")
with (ARTIFACTS / "INDEX.md").open("a", encoding="utf-8") as index:
index.write("\nAll eight exports and renders completed. The capture-quality gate "
"passed for all four planner-head images and one assembler/world image. Three "
"assembler/world images have insufficient image variance because no mesh surface "
"is visible from those same viewpoints; those gate failures are recorded as the "
"baseline result.\n")
print(f"[L150CaptureSummary] views={len(manifest)//2} pairs={len(manifest)//2} "
f"quality_failures={len(failures)}", flush=True)
return 0 if not failures else 1
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as error:
print(f"[L150CaptureFailure] {error}", file=sys.stderr, flush=True)
raise SystemExit(1)