research: flat pad stamp only (L159, one attempt; kept as patch)
Clamp-between-cones pad at each tunnel end, last composition step, chamber cases removed, bounds extended (proof and feature samplers 16/16 clean). Open-ground result looks as intended; but pads pass 8/96 (flatness up to 0.75 m, blend up to 81 deg), interior air fails 16/16 (839-1,588 solid capsule samples), route audits fail, FI band check fails at seed 0. Owner asked for one attempt, no tuning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
e5a60da2c3
commit
c5fcedeb5b
19 files changed
+2245
No files matched your search
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Capture matched L151 eye views and L159 wide views of all six owner pads."""
|
||||
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/l159/captures"
|
||||
LAYOUT = ROOT / "Saved/Linux/l151/captures/after-resolved-layout.json"
|
||||
ARTIFACTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux159/captures"
|
||||
HEADLESS = ROOT / "Saved/Linux/l159/bin/VoxelForgeCoreHeadless-l159-clang"
|
||||
RENDERER = ROOT / "Saved/Linux/l159/bin/VoxelForgeOffscreenCapture-l159-clang"
|
||||
STAMP = "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640"
|
||||
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
|
||||
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_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 chamber_views(layout: dict) -> list[dict]:
|
||||
result = []
|
||||
for passage in layout["passages"]:
|
||||
controls = passage["control_points"]
|
||||
for side, endpoint, neighbor in (
|
||||
("upper", controls[0], controls[1]),
|
||||
("lower", controls[-1], controls[-2])):
|
||||
chamber = passage[f"{side}_landing_chamber"]
|
||||
center = point_q(layout, chamber["floor_center_q"])
|
||||
a = point_q(layout, neighbor["position_q"])
|
||||
b = point_q(layout, endpoint["position_q"])
|
||||
dx, dy = a[0] - b[0], a[1] - b[1]
|
||||
length = math.hypot(dx, dy)
|
||||
if length <= 1.0e-9:
|
||||
raise ValueError(f"degenerate {side} tunnel tangent: {passage['stable_id']}")
|
||||
dx, dy = dx / length, dy / length
|
||||
eye_z = center[2] + 1.45
|
||||
distance = 9.0
|
||||
target = (center[0] + dx * distance, center[1] + dy * distance,
|
||||
eye_z + (chamber["floor_gradient_q16"][0] / 65536.0 * dx
|
||||
+ chamber["floor_gradient_q16"][1] / 65536.0 * dy)
|
||||
* distance)
|
||||
camera = (center[0], center[1], eye_z)
|
||||
slug = passage["stable_id"].replace("passage/", "").replace("/", "-")
|
||||
result.append({"name": f"{slug}-{side}-landing", "passage": passage["stable_id"],
|
||||
"side": side, "view_kind": "eye", "camera": camera,
|
||||
"target": target, "focus": (center[0], center[1], eye_z, 18.0)})
|
||||
wide_camera = (center[0] - dx * 14.0, center[1] - dy * 14.0,
|
||||
center[2] + 5.5)
|
||||
wide_target = (center[0] + dx * 2.0, center[1] + dy * 2.0,
|
||||
center[2] + 1.25)
|
||||
result.append({"name": f"{slug}-{side}-wide", "passage": passage["stable_id"],
|
||||
"side": side, "view_kind": "wide", "camera": wide_camera,
|
||||
"target": wide_target,
|
||||
"focus": (center[0], center[1], eye_z, 42.0)})
|
||||
return result
|
||||
|
||||
|
||||
def stamped_run(name: str, command: list[object], log: Path, env: dict[str, str]) -> str:
|
||||
probe = subprocess.run([*LIMIT, "timeout", "15s", str(command[0]), "--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) != STAMP:
|
||||
raise RuntimeError(f"{name}: source stamp mismatch for {command[0]}")
|
||||
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) != STAMP:
|
||||
raise RuntimeError(f"{name}: export or render failed; see {log}")
|
||||
return output
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not DATA.is_file() or not HEADLESS.is_file() or not RENDERER.is_file():
|
||||
raise SystemExit("missing owner fixture or L159 Clang capture binaries")
|
||||
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 = json.loads(LAYOUT.read_text(encoding="utf-8"))
|
||||
if len(layout.get("passages", [])) != 3:
|
||||
raise RuntimeError("owner layout did not resolve the expected three tunnels")
|
||||
|
||||
quality_tool = ROOT / "Standalone/Tools/verify/capture_quality.py"
|
||||
manifest = []
|
||||
failures = []
|
||||
for view in chamber_views(layout):
|
||||
camera, target = view["camera"], view["target"]
|
||||
yaw, pitch = angles(camera, target)
|
||||
mesh_dir = OUT / "meshes"
|
||||
mesh_dir.mkdir(parents=True, exist_ok=True)
|
||||
mesh = mesh_dir / f"{view['name']}.vfmesh"
|
||||
capture_layout = mesh_dir / f"{view['name']}-layout.json"
|
||||
export = [HEADLESS, DATA, "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", "--workers=12", "--clip-radius=6", "--max-clip-level=0",
|
||||
"--max-level=0", "--force-generate", "--seed=0", "--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"l159-{view['name']}-export", export,
|
||||
OUT / f"{view['name']}-export.log", env)
|
||||
if not mesh.is_file() or mesh.stat().st_size == 0:
|
||||
raise RuntimeError(f"{view['name']}: empty mesh export")
|
||||
png = OUT / f"{view['name']}.png"
|
||||
jpg = ARTIFACTS / f"after-l159-{view['name']}.jpg"
|
||||
render = [RENDERER, mesh, png, "--size=1280x720", "--fov-deg=78.0",
|
||||
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"l159-{view['name']}-render", render,
|
||||
OUT / f"{view['name']}-render.log", 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(view["name"])
|
||||
quality_line = (check.stdout.strip().splitlines()[-1]
|
||||
if check.stdout.strip() else f"quality exit={check.returncode}")
|
||||
manifest.append({"version": "after-l159", "view": view["name"],
|
||||
"passage": view["passage"], "side": view["side"],
|
||||
"view_kind": view["view_kind"],
|
||||
"file": str(jpg.relative_to(ROOT)), "camera_m": camera,
|
||||
"target_m": target, "yaw_deg": yaw, "pitch_deg": pitch,
|
||||
"source_stamp": STAMP, "pose_layout_identity": layout.get("stable_identity"),
|
||||
"quality_status": "PASS" if quality_ok else "FAIL", "quality": quality_line})
|
||||
print(f"[L159Capture] view={view['name']} quality={'PASS' if quality_ok else 'FAIL'} "
|
||||
f"file={jpg.relative_to(ROOT)}", flush=True)
|
||||
|
||||
(ARTIFACTS / "manifest.json").write_text(
|
||||
json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
|
||||
with (ARTIFACTS / "INDEX.md").open("w", encoding="utf-8") as index:
|
||||
index.write("# L159 owner landing pad captures\n\n"
|
||||
"All poses use the seed 0 layout resolved for L151. Eye-height after views "
|
||||
"reuse the six L151 cameras; each pad also has one pulled-back wide view.\n\n")
|
||||
for view in chamber_views(layout):
|
||||
image = f"after-l159-{view['name']}.jpg"
|
||||
if view["view_kind"] == "eye":
|
||||
before = f"after-l151-{view['name']}.jpg"
|
||||
index.write(f"## {view['passage']} / {view['side']} landing\n\n"
|
||||
f"- Before: [{before}](../../Linux151/captures/{before})\n"
|
||||
f"- After: [eye view]({image})\n")
|
||||
else:
|
||||
index.write(f"- After: [wide view]({image})\n\n")
|
||||
index.write(f"Capture quality failures: {len(failures)}/{len(manifest)}.\n")
|
||||
print(f"[L159CaptureSummary] views={len(manifest)} "
|
||||
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"[L159CaptureFailure] {error}", file=sys.stderr, flush=True)
|
||||
raise SystemExit(1)
|
||||
Reference in new issue
Block a user