tools(verify): fast/medium/full verification tiers with a compiled-in source stamp (L39); research R6
BuildLinux.sh hashes a sorted manifest of compiled inputs into VOXELFORGE_SOURCE_STAMP, printed first by every executable; Standalone/Tools/verify/verify.py checks it before and after each tier and records binary/input hashes and the resolved recipe, style map and layout. Fast (163 s) runs on the canonical owner world only and asserts FI Style 2 / SW Style 1; medium (8 min) and full (26 min) add the style matrix, GCC, determinism and cost. Controls: stale source, mislabelled style-0 owner, all-black capture, L30/L31, L35/L36 and the L38 step are all rejected. No geometry change; owner hashes identical to L37. Research R6: leak table, generator contract and a 10-step metre-native migration plan. 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
88c54eab44
commit
753cc9c259
22 files changed
+1831
-163
No files matched your search
@@ -30,8 +30,9 @@ DELTA_GRID_M = 0.125
|
||||
SCENES = {
|
||||
"owner-fi": ("owner-fi-ground-mouth.vfmesh", "layout-owner-fi.json", "FloatingIslands", "upper"),
|
||||
"owner-sw": ("owner-sw-ground-mouth.vfmesh", "layout-owner-sw.json", "SurfaceWorld", "lower"),
|
||||
"upper-sw-fixture": ("upper-sw-fixture-ground-mouth.vfmesh",
|
||||
"layout-upper-sw-fixture.json", "SurfaceWorld", "upper"),
|
||||
"style-override-upper-sw-fixture-style0": (
|
||||
"style-override-upper-sw-fixture-style0-ground-mouth.vfmesh",
|
||||
"layout-style-override-upper-sw-fixture-style0.json", "SurfaceWorld", "upper"),
|
||||
}
|
||||
|
||||
|
||||
@@ -456,6 +457,9 @@ def audit_scene(scene, out, baseline_out, mesh_override=None):
|
||||
crop_to_center=False)
|
||||
full_surface = analyze_triangles(corridor, mouth[:2], mouth[2],
|
||||
crop_to_center=False, include_sides=True)
|
||||
proxy_alert = fail(differential, include_planarity=False)
|
||||
corner_gate = (metrics["corners"] == 0 and full_surface["corners"] == 0
|
||||
and differential["corners"] == 0)
|
||||
metrics["all_face_planar_area"] = full_surface["planar_area"]
|
||||
metrics["all_face_straight"] = full_surface["straight"]
|
||||
metrics["all_face_corners"] = full_surface["corners"]
|
||||
@@ -484,10 +488,13 @@ def audit_scene(scene, out, baseline_out, mesh_override=None):
|
||||
f"patch_bounds={metrics['bounds']} plane_normal={metrics['normal']} "
|
||||
f"limits=(plane<{PLANE_LIMIT_M2:.2f}m2 informational,line<={STRAIGHT_LIMIT_M:.2f}m,"
|
||||
f"corner_turn<{CORNER_TURN_LIMIT_DEG:.0f}deg) "
|
||||
f"straight_or_planar_proxy={'ALERT' if proxy_alert else 'PASS'} "
|
||||
f"delta_threshold_m={DELTA_THRESHOLD_M:.2f} status="
|
||||
f"{'FAIL' if fail(differential, include_planarity=False) else 'PASS'}")
|
||||
f"{'PASS' if corner_gate else 'FAIL'}")
|
||||
metrics["differential"] = differential
|
||||
metrics["matched_no_support"] = baseline_metrics
|
||||
metrics["corner_gate"] = corner_gate
|
||||
metrics["proxy_alert"] = proxy_alert
|
||||
return metrics
|
||||
|
||||
|
||||
@@ -519,13 +526,14 @@ def main():
|
||||
f"{control['planar_area']:.4f} straight_run_m={control['straight']:.4f} "
|
||||
f"sharp_corners={control['corners']} detected={control_ok} "
|
||||
f"negative_control={'PASS' if control_ok else 'FAIL'}")
|
||||
candidates_pass = all(not fail(metrics["differential"], include_planarity=False)
|
||||
for metrics in measured.values())
|
||||
candidates_pass = all(metrics["corner_gate"] for metrics in measured.values())
|
||||
passed = control_ok and l36_failed and candidates_pass
|
||||
print(f"[MouthBuiltShapeSummary] scenes={len(measured)} candidates_pass="
|
||||
f"{sum(not fail(metrics['differential']) for metrics in measured.values())}/{len(measured)} "
|
||||
f"{sum(metrics['corner_gate'] for metrics in measured.values())}/{len(measured)} "
|
||||
f"L36_control_fail={'PASS' if l36_failed else 'FAIL'} "
|
||||
f"box_control_fail={'PASS' if control_ok else 'FAIL'} "
|
||||
f"corner_gate={'PASS' if candidates_pass else 'FAIL'} "
|
||||
f"straight_or_planar_proxy_alerts={sum(metrics['proxy_alert'] for metrics in measured.values())} "
|
||||
f"planar_gate=informational_owner_accepted "
|
||||
f"status={'PASS' if passed else 'FAIL'}")
|
||||
raise SystemExit(0 if passed else 1)
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
import math
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = Path(os.environ.get("VOXELFORGE_L37_OUT", ROOT / "Saved/Linux/l37"))
|
||||
OUT = Path(os.environ.get("VOXELFORGE_L37_OUT",
|
||||
os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37")))
|
||||
CAPTURES = Path(os.environ.get("VOXELFORGE_L37_CAPTURE_DIR",
|
||||
Path(__file__).resolve().parent / "captures"))
|
||||
OUT / "captures"))
|
||||
MESH = Path(os.environ.get("VOXELFORGE_L37_MESH",
|
||||
OUT / "owner-fi-ground-mouth.vfmesh"))
|
||||
RENDERER = Path(os.environ.get("VOXELFORGE_L37_RENDERER",
|
||||
@@ -20,6 +22,8 @@ MOUTH = (5.600, 0.445, -55.156)
|
||||
CAMERA = (MOUTH[0] - 9.75, MOUTH[1] - 11.4, MOUTH[2] + 8.0)
|
||||
TARGET = (6.298233, 1.137233, -55.468264)
|
||||
FOV = 80.0
|
||||
sys.path.insert(0, str(ROOT / "Standalone/Tools/verify"))
|
||||
from runner import current_stamp, first_stamp, bounded_run
|
||||
|
||||
|
||||
def normalize(vector):
|
||||
@@ -42,6 +46,11 @@ def run(name, command):
|
||||
log = OUT / f"capture-overview-{name}.log"
|
||||
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1800s", *map(str, command)]
|
||||
expected = current_stamp() if Path(command[0]) == RENDERER else None
|
||||
if expected is not None:
|
||||
probe = bounded_run([str(RENDERER), "--print-source-stamp"], timeout=15)
|
||||
if probe is None or probe.returncode != 0 or first_stamp(probe.stdout or "") != expected:
|
||||
raise SystemExit(f"{name} refused an unstamped or stale renderer: {RENDERER}")
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
|
||||
@@ -49,6 +58,10 @@ def run(name, command):
|
||||
elapsed = time.monotonic() - started
|
||||
print(f"[L37OverviewCaptureRun] step={name} elapsed={elapsed:.1f}s "
|
||||
f"exit={result.returncode} log={log}", flush=True)
|
||||
if expected is not None:
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
if first_stamp(output) != expected or current_stamp() != expected:
|
||||
raise SystemExit(f"{name} source stamp mismatch; see {log}")
|
||||
if result.returncode != 0:
|
||||
raise SystemExit(f"{name} failed; see {log}")
|
||||
|
||||
@@ -67,6 +80,7 @@ def main():
|
||||
"--tile-edges=off", f"--focus-region={fmt((*MOUTH, 20.0))}"])
|
||||
run("jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error",
|
||||
"-y", "-i", PNG, "-frames:v", "1", "-q:v", "4", JPEG])
|
||||
run("quality", [sys.executable, ROOT / "Standalone/Tools/verify/capture_quality.py", JPEG])
|
||||
size = JPEG.stat().st_size if JPEG.exists() else 0
|
||||
distance = math.dist(CAMERA, MOUTH)
|
||||
print(f"[L37OverviewCapture] file={JPEG.relative_to(ROOT)} camera_to_mouth_m="
|
||||
|
||||
@@ -7,26 +7,29 @@ import math
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = Path(os.environ.get("VOXELFORGE_L37_OUT", ROOT / "Saved/Linux/l37"))
|
||||
OUT = Path(os.environ.get("VOXELFORGE_L37_OUT",
|
||||
os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37")))
|
||||
BIN = OUT / "bin"
|
||||
CAPTURES = Path(os.environ.get("VOXELFORGE_L37_CAPTURE_DIR",
|
||||
Path(__file__).resolve().parent / "captures"))
|
||||
OUT / "captures"))
|
||||
HEADLESS = Path(os.environ.get("VOXELFORGE_L37_HEADLESS",
|
||||
BIN / "VoxelForgeCoreHeadless-l37-final-clang"))
|
||||
RENDERER = Path(os.environ.get("VOXELFORGE_L37_RENDERER",
|
||||
BIN / "VoxelForgeOffscreenCapture-l37-final-clang"))
|
||||
PNG_DIR = OUT / "capture-png"
|
||||
RECIPE_INPUTS = (
|
||||
("owner-fi", ROOT / "Saved/Linux/linux25-owner-floating-islands-style0.json",
|
||||
("owner-fi", ROOT / "Standalone/data/owner_world.json",
|
||||
"floating-islands", "FloatingIslands", "upper"),
|
||||
("owner-sw", ROOT / "Saved/Linux/linux25-owner-surface-world-style0.json",
|
||||
("owner-sw", ROOT / "Standalone/data/owner_world.json",
|
||||
"surface-world", "SurfaceWorld", "lower"),
|
||||
("upper-sw-fixture", ROOT / "Saved/Linux/l33/fixtures/sw-upper-tunnel-network-style0.json",
|
||||
("style-override-upper-sw-fixture-style0",
|
||||
OUT / "fixtures/style-override-upper-sw-fixture-style0.json",
|
||||
"floating-islands", "SurfaceWorld", "upper"),
|
||||
)
|
||||
SUBVOXELS_PER_VOXEL = 256.0
|
||||
@@ -34,6 +37,8 @@ RING_AUDIT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerra
|
||||
spec = importlib.util.spec_from_file_location("ring_profile_l32", RING_AUDIT)
|
||||
ring_profile = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(ring_profile)
|
||||
sys.path.insert(0, str(ROOT / "Standalone/Tools/verify"))
|
||||
from runner import current_stamp, first_stamp, bounded_run
|
||||
|
||||
for directory in (OUT, BIN, PNG_DIR, CAPTURES):
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
@@ -51,12 +56,23 @@ def run(name, command):
|
||||
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1800s", *map(str, command)]
|
||||
started = time.monotonic()
|
||||
binary = Path(command[0]) if command else None
|
||||
is_stamped_executable = binary in (HEADLESS, RENDERER)
|
||||
expected = current_stamp() if is_stamped_executable else None
|
||||
if is_stamped_executable:
|
||||
probe = bounded_run([str(binary), "--print-source-stamp"], timeout=15)
|
||||
if probe is None or probe.returncode != 0 or first_stamp(probe.stdout or "") != expected:
|
||||
raise SystemExit(f"{name} refused an unstamped or stale executable: {binary}")
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(wrapped, cwd=ROOT, env=ENV, stdout=stream,
|
||||
stderr=subprocess.STDOUT, timeout=1800, check=False)
|
||||
elapsed = time.monotonic() - started
|
||||
print(f"[L37CaptureRun] step={name} elapsed={elapsed:.1f}s exit={result.returncode} log={log}",
|
||||
flush=True)
|
||||
if is_stamped_executable:
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
if first_stamp(output) != expected or current_stamp() != expected:
|
||||
raise SystemExit(f"{name} source stamp mismatch; see {log}")
|
||||
if result.returncode != 0:
|
||||
raise SystemExit(f"{name} failed; see {log}")
|
||||
return log
|
||||
@@ -163,7 +179,32 @@ def load_layout(name, source, recipe):
|
||||
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=2",
|
||||
"--max-clip-level=0", "--max-level=0", f"--recipe={recipe}",
|
||||
f"--resolved-layout-output={path}", "--resolved-layout-only"])
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
layout = json.loads(path.read_text(encoding="utf-8"))
|
||||
passage_styles = sorted({int(passage["style"]) for passage in layout.get("passages", [])})
|
||||
resolved_styles = {str(index): int(style) for index, style in
|
||||
layout.get("resolved_strate_passage_styles", {}).items()}
|
||||
if name.startswith("owner-"):
|
||||
expected_styles = {"0": 2, "1": 1}
|
||||
if resolved_styles != expected_styles or passage_styles != [2]:
|
||||
raise SystemExit(f"{name} resolved styles={resolved_styles} passage_styles={passage_styles}; "
|
||||
f"expected strata={expected_styles} passage_styles=[2]")
|
||||
expected_style = 2 if name == "owner-fi" else 1
|
||||
print(f"[L37CaptureResolvedOwner] scene={name} recipe={recipe} "
|
||||
f"generator_style={expected_style} strata_styles={resolved_styles} "
|
||||
f"passage_style={passage_styles} layout_id={layout.get('stable_identity')} input={source}",
|
||||
flush=True)
|
||||
return layout
|
||||
expected_style = 0
|
||||
matching_styles = sorted({resolved_styles[str(stratum["strate_index"])]
|
||||
for stratum in layout.get("strates", [])
|
||||
if stratum.get("generator_kind") == "SurfaceWorld"
|
||||
and str(stratum["strate_index"]) in resolved_styles})
|
||||
if expected_style not in passage_styles or matching_styles != [expected_style]:
|
||||
raise SystemExit(f"{name} resolved style={matching_styles} passage styles={passage_styles}; "
|
||||
f"expected [{expected_style}]")
|
||||
print(f"[L37CaptureResolvedOwner] scene={name} recipe={recipe} style={matching_styles} "
|
||||
f"layout_id={layout.get('stable_identity')} input={source}", flush=True)
|
||||
return layout
|
||||
|
||||
|
||||
def select_passage(layout, mouth_generator, preferred_side):
|
||||
@@ -327,7 +368,8 @@ for scene_name, source, recipe, mouth_generator, preferred_side in RECIPE_INPUTS
|
||||
jpg = CAPTURES / f"{stem}.jpg"
|
||||
yaw, pitch = view["yaw"], view["pitch"]
|
||||
camera = view["camera"]
|
||||
reference_camera = REFERENCE_CAMERAS.get(stem)
|
||||
reference_camera = (REFERENCE_CAMERAS.get(stem)
|
||||
if scene_name.startswith("style-override-") else None)
|
||||
if reference_camera is not None:
|
||||
camera, target, reference_clip = reference_camera
|
||||
view["camera"], view["target"], view["clip"] = camera, target, reference_clip
|
||||
@@ -397,6 +439,8 @@ for scene_name, source, recipe, mouth_generator, preferred_side in RECIPE_INPUTS
|
||||
run(f"{stem}-render", render)
|
||||
run(f"{stem}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error",
|
||||
"-y", "-i", png, "-frames:v", "1", "-q:v", "4", jpg])
|
||||
run(f"{stem}-quality", [sys.executable,
|
||||
ROOT / "Standalone/Tools/verify/capture_quality.py", jpg])
|
||||
size = jpg.stat().st_size if jpg.exists() else 0
|
||||
print(f"[L37CaptureJPEG] file={jpg.relative_to(ROOT)} bytes={size} "
|
||||
f"status={'PASS' if 0 < size <= 300_000 else 'FAIL'}", flush=True)
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the L13 route parity and L9 uniform-block proof audits on L37."""
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l37"
|
||||
BINARIES = {
|
||||
"clang": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
|
||||
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l37-final-gcc",
|
||||
}
|
||||
RECIPES = ("floating-islands", "surface-world")
|
||||
rows = []
|
||||
failed = False
|
||||
|
||||
for recipe in RECIPES:
|
||||
for compiler, binary in BINARIES.items():
|
||||
name = f"legacy-route-proof-{recipe}-{compiler}"
|
||||
logfile = OUT / f"{name}.log"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "900s", str(binary), str(ROOT / "Standalone/data/owner_world.json"),
|
||||
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", "--stream-hash", "--force-generate", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=-1",
|
||||
f"--recipe={recipe}", "--audit-uniform-block-proofs", "--audit-route-parity",
|
||||
]
|
||||
started = time.monotonic()
|
||||
try:
|
||||
with logfile.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, timeout=900, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
result = None
|
||||
elapsed = time.monotonic() - started
|
||||
output = logfile.read_text(encoding="utf-8", errors="replace")
|
||||
stamp = re.findall(
|
||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
|
||||
output, re.M)
|
||||
route = re.findall(r"^\[RouteParityAudit\].*$", output, re.M)
|
||||
proofs = re.findall(r"^\[UniformBlockProofAudit\].*$", output, re.M)
|
||||
stamp_ok = stamp == [(recipe, recipe, "PASS")]
|
||||
route_ok = len(route) == 1 and route[0].endswith("status=PASS")
|
||||
proof_ok = len(proofs) == 1 and bool(
|
||||
re.search(r"violations=0(?:\s|$)", proofs[0]))
|
||||
ok = (result is not None and result.returncode == 0 and stamp_ok
|
||||
and route_ok and proof_ok)
|
||||
code = result.returncode if result is not None else "TIMEOUT"
|
||||
rows.append((name, recipe, compiler, elapsed, code, route_ok, bool(proof_ok), ok))
|
||||
failed |= not ok
|
||||
print(f"[L37LegacyAuditRun] case={name} elapsed={elapsed:.1f}s exit={code} "
|
||||
f"recipe_stamp={'PASS' if stamp_ok else 'FAIL'} "
|
||||
f"L13={'PASS' if route_ok else 'FAIL'} L9={'PASS' if proof_ok else 'FAIL'} "
|
||||
f"{'PASS' if ok else 'FAIL'}", flush=True)
|
||||
|
||||
summary = OUT / "legacy-audits.csv"
|
||||
summary.write_text(
|
||||
"case,recipe,compiler,elapsed_seconds,exit_code,l13,l9,status\n" +
|
||||
"".join(f"{name},{recipe},{compiler},{elapsed:.3f},{code},"
|
||||
f"{'PASS' if l13 else 'FAIL'},{'PASS' if l9 else 'FAIL'},"
|
||||
f"{'PASS' if ok else 'FAIL'}\n"
|
||||
for name, recipe, compiler, elapsed, code, l13, l9, ok in rows),
|
||||
encoding="utf-8")
|
||||
print(f"[L37LegacyAuditSummary] 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) != 4 else 0)
|
||||
@@ -4,6 +4,7 @@ import ast
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import argparse
|
||||
@@ -15,10 +16,13 @@ SPEC = importlib.util.spec_from_file_location("mouth_fragment_l37", BASE_PATH)
|
||||
BASE = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(BASE)
|
||||
|
||||
# Frozen before the L37 mouth edit: 20 degrees over the audit's 0.125 m samples is
|
||||
# the visible-crease limit. At the 6.016 m standard camera and 80-degree horizontal
|
||||
# FOV, each sample gap spans multiple pixels at 2560x1440.
|
||||
BASE.NORMAL_LIMIT_DEG = 20.0
|
||||
# Keep the near limit frozen at 20 degrees. The saved L37 owner overview measures
|
||||
# 25.542 degrees, so the overview regression limit is 26 degrees: it admits that
|
||||
# baseline while still rejecting the saved L36 washboard (27.133 degrees).
|
||||
NEAR_NORMAL_LIMIT_DEG = 20.0
|
||||
OVERVIEW_NORMAL_LIMIT_DEG = 26.0
|
||||
MIN_VISIBLE_PAIRS = 20
|
||||
MIN_PROFILE_COVERAGE = 0.05
|
||||
CAMERA = (1.691820, -3.424982, -54.224198)
|
||||
TARGET = (6.298233, 1.137233, -55.468264)
|
||||
FOV_DEG = 80.0
|
||||
@@ -42,11 +46,14 @@ def view_basis(camera, target):
|
||||
return forward, right, up
|
||||
|
||||
|
||||
def visible_crease(path, camera, target):
|
||||
cx, cy = BASE.CENTER
|
||||
bounds = (cx - BASE.OUTER, cx + BASE.OUTER, cy - BASE.OUTER, cy + BASE.OUTER)
|
||||
voxel_um, triangles, _ = BASE.RING.read_scene(
|
||||
str(path), 0, BASE.HEIGHT_MIN, BASE.HEIGHT_MAX, bounds)
|
||||
def visible_crease(scene_data, camera, target, center=None, preferred=None,
|
||||
inner=None, outer=None):
|
||||
cx, cy = center or BASE.CENTER
|
||||
inner = BASE.INNER if inner is None else inner
|
||||
outer = BASE.OUTER if outer is None else outer
|
||||
bounds = (cx - outer, cx + outer, cy - outer, cy + outer)
|
||||
voxel_um, triangles, _ = scene_data
|
||||
preferred = BASE.PREFERRED if preferred is None else preferred
|
||||
cell = max(2.0, 16.0 * voxel_um / 1_000_000.0)
|
||||
buckets = BASE.RING.build_buckets(triangles, True, bounds, cell)
|
||||
forward, right, up = view_basis(camera, target)
|
||||
@@ -54,11 +61,11 @@ def visible_crease(path, camera, target):
|
||||
tan_v = math.tan(math.atan(tan_h * HEIGHT_1440P / WIDTH_1440P))
|
||||
focal = WIDTH_1440P / (2.0 * tan_h)
|
||||
lines = []
|
||||
count = round((BASE.OUTER - BASE.INNER) / BASE.STEP)
|
||||
count = round((outer - inner) / BASE.STEP)
|
||||
for degrees in (135.0, 180.0, 225.0, 270.0, 315.0):
|
||||
angle = math.radians(degrees)
|
||||
lines.append([(cx + radius * math.cos(angle), cy + radius * math.sin(angle))
|
||||
for radius in (BASE.INNER + index * BASE.STEP
|
||||
for radius in (inner + index * BASE.STEP
|
||||
for index in range(count + 1))])
|
||||
tangent_count = round(8.0 / BASE.STEP)
|
||||
for degrees in (225.0, 315.0):
|
||||
@@ -70,6 +77,7 @@ def visible_crease(path, camera, target):
|
||||
for index in range(tangent_count + 1)])
|
||||
|
||||
maximum, largest_pixels, worst_depth, visible_pairs = 0.0, 0.0, 0.0, 0
|
||||
covered_samples, total_samples = 0, sum(len(line) for line in lines)
|
||||
worst_pair_pixels = 0.0
|
||||
worst_pair = None
|
||||
|
||||
@@ -81,8 +89,9 @@ def visible_crease(path, camera, target):
|
||||
return depth, x, y
|
||||
|
||||
for points in lines:
|
||||
samples = [BASE.RING.sample_surface(buckets, x, y, True, cell, BASE.PREFERRED)
|
||||
samples = [BASE.RING.sample_surface(buckets, x, y, True, cell, preferred)
|
||||
for x, y in points]
|
||||
covered_samples += sum(sample is not None for sample in samples)
|
||||
for index in range(1, len(samples)):
|
||||
before, current = samples[index - 1], samples[index]
|
||||
if before is None or current is None:
|
||||
@@ -105,38 +114,74 @@ def visible_crease(path, camera, target):
|
||||
if angle > maximum:
|
||||
maximum, worst_pair, worst_depth = angle, (p0, p1), depth
|
||||
worst_pair_pixels = pixels
|
||||
return maximum, worst_depth, largest_pixels, visible_pairs, worst_pair, worst_pair_pixels
|
||||
coverage = covered_samples / total_samples if total_samples else 0.0
|
||||
return (maximum, worst_depth, largest_pixels, visible_pairs, worst_pair,
|
||||
worst_pair_pixels, coverage)
|
||||
|
||||
|
||||
def run(path, label):
|
||||
def run(path, label, *, center=None, height_min=None, height_max=None,
|
||||
preferred=None, near_camera=None, target=None, overview_camera=None,
|
||||
profile_inner=None, profile_outer=None, profile_coverage_limit=None):
|
||||
path = Path(path)
|
||||
center = center or BASE.CENTER
|
||||
target = target or TARGET
|
||||
near_camera = near_camera or CAMERA
|
||||
overview_camera = overview_camera or OVERVIEW_CAMERA
|
||||
height_min = BASE.HEIGHT_MIN if height_min is None else height_min
|
||||
height_max = BASE.HEIGHT_MAX if height_max is None else height_max
|
||||
profile_inner = BASE.INNER if profile_inner is None else profile_inner
|
||||
profile_outer = BASE.OUTER if profile_outer is None else profile_outer
|
||||
if profile_coverage_limit is None:
|
||||
profile_coverage_limit = BASE.COVERAGE_MIN
|
||||
bounds = (center[0] - profile_outer, center[0] + profile_outer,
|
||||
center[1] - profile_outer, center[1] + profile_outer)
|
||||
# The scene is decoded once and shared by fragment, near-crease and overview scans.
|
||||
scene_data = BASE.RING.read_scene(str(path), 0, height_min, height_max, bounds)
|
||||
stream = io.StringIO()
|
||||
with contextlib.redirect_stdout(stream):
|
||||
passed, maximum = BASE.audit(path)
|
||||
passed, maximum = BASE.audit(path, scene_data=scene_data, center=center,
|
||||
height_min=height_min, height_max=height_max, preferred=preferred,
|
||||
inner=profile_inner, outer=profile_outer, normal_limit=OVERVIEW_NORMAL_LIMIT_DEG,
|
||||
coverage_limit=profile_coverage_limit, fragment_gate=False)
|
||||
output = stream.getvalue()
|
||||
print(output, end="")
|
||||
match = re.search(r"worst_pair=(\([^\n]*\)) status=", output)
|
||||
worst = ast.literal_eval(match.group(1)) if match else None
|
||||
path = Path(path)
|
||||
near = visible_crease(path, CAMERA, TARGET)
|
||||
far = visible_crease(path, OVERVIEW_CAMERA, TARGET)
|
||||
near = visible_crease(scene_data, near_camera, target, center, preferred,
|
||||
profile_inner, profile_outer)
|
||||
far = visible_crease(scene_data, overview_camera, target, center, preferred,
|
||||
profile_inner, profile_outer)
|
||||
print(f"[MouthCreasePixels] build={label} sample_step_m={BASE.STEP:.3f} "
|
||||
f"horizontal_fov_deg={FOV_DEG:.1f} resolution=2560x1440 "
|
||||
f"near_camera_to_mouth_m=6.016 near_visible_pairs={near[3]} "
|
||||
f"near_max_visible_deg={near[0]:.6f} near_depth_m={near[1]:.3f} "
|
||||
f"near_sample_spacing_px={near[2]:.2f} "
|
||||
f"near_camera_to_mouth_m={math.dist(near_camera, (*center, target[2])):.3f} "
|
||||
f"near_visible_pairs={near[3]} near_max_visible_deg={near[0]:.6f} "
|
||||
f"near_profile_coverage={near[6]:.3f} "
|
||||
f"near_depth_m={near[1]:.3f} near_sample_spacing_px={near[2]:.2f} "
|
||||
f"near_worst_pair_spacing_px={near[5]:.2f} "
|
||||
f"overview_camera_to_mouth_m={math.dist(OVERVIEW_CAMERA, MOUTH):.3f} "
|
||||
f"overview_camera_to_mouth_m={math.dist(overview_camera, (*center, target[2])):.3f} "
|
||||
f"overview_visible_pairs={far[3]} overview_max_visible_deg={far[0]:.6f} "
|
||||
f"overview_profile_coverage={far[6]:.3f} "
|
||||
f"overview_depth_m={far[1]:.3f} overview_sample_spacing_px={far[2]:.2f} "
|
||||
f"overview_worst_pair_spacing_px={far[5]:.2f} "
|
||||
f"overall_max_deg={maximum:.6f}")
|
||||
return passed, maximum
|
||||
f"overview_worst_pair_spacing_px={far[5]:.2f} overall_max_deg={maximum:.6f}")
|
||||
near_ok = (near[3] >= MIN_VISIBLE_PAIRS and near[6] >= MIN_PROFILE_COVERAGE
|
||||
and near[0] <= NEAR_NORMAL_LIMIT_DEG)
|
||||
overview_ok = (far[3] >= MIN_VISIBLE_PAIRS and far[6] >= MIN_PROFILE_COVERAGE
|
||||
and far[0] <= OVERVIEW_NORMAL_LIMIT_DEG)
|
||||
views_pass = near_ok and overview_ok
|
||||
print(f"[MouthCreaseViewGate] build={label} near_limit_deg={NEAR_NORMAL_LIMIT_DEG:.1f} "
|
||||
f"overview_limit_deg={OVERVIEW_NORMAL_LIMIT_DEG:.1f} "
|
||||
f"min_visible_pairs={MIN_VISIBLE_PAIRS} "
|
||||
f"min_profile_coverage={MIN_PROFILE_COVERAGE:.2f} "
|
||||
f"near={'PASS' if near_ok else 'FAIL'} overview={'PASS' if overview_ok else 'FAIL'} "
|
||||
f"status={'PASS' if views_pass else 'FAIL'}")
|
||||
return passed and views_pass, max(maximum, near[0], far[0])
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--candidate", type=Path,
|
||||
default=ROOT / "Saved/Linux/l37/owner-fi-ground-mouth.vfmesh")
|
||||
parser.add_argument("--candidate-sw", type=Path)
|
||||
parser.add_argument("--layout-sw", type=Path)
|
||||
parser.add_argument("--capture-log-sw", type=Path)
|
||||
args = parser.parse_args()
|
||||
paths = {
|
||||
"L35": ROOT / "Saved/Linux/l35/owner-fi-ground-mouth.vfmesh",
|
||||
@@ -146,12 +191,38 @@ def main():
|
||||
}
|
||||
results = {label: run(path, label) for label, path in paths.items()}
|
||||
controls = (not results["L35"][0] and not results["L36"][0]
|
||||
and results["L35"][1] > 20.0 and results["L36"][1] > 20.0)
|
||||
final = results["L37"][0] and results["L37"][1] <= 20.0
|
||||
print(f"[MouthCreaseGate] normal_limit_deg=20 frozen_before_fix=true "
|
||||
and results["L35"][1] > OVERVIEW_NORMAL_LIMIT_DEG
|
||||
and results["L36"][1] > OVERVIEW_NORMAL_LIMIT_DEG)
|
||||
final = (results["L37"][0]
|
||||
and results["L37"][1] <= OVERVIEW_NORMAL_LIMIT_DEG)
|
||||
if args.candidate_sw:
|
||||
if not args.layout_sw or not args.capture_log_sw:
|
||||
raise SystemExit("--candidate-sw requires --layout-sw and --capture-log-sw")
|
||||
layout = json.loads(args.layout_sw.read_text(encoding="utf-8"))
|
||||
passage = layout["passages"][0]
|
||||
q_to_m = layout["core_voxel_size_um"] / (256.0 * 1_000_000.0)
|
||||
mouth = tuple(value * q_to_m for value in passage["lower_mouth_q"])
|
||||
capture = args.capture_log_sw.read_text(encoding="utf-8", errors="replace")
|
||||
camera_match = re.search(r"\[L37CaptureCamera\] view=owner-sw-ground-mouth .*?"
|
||||
r"camera_m=\(([^)]*)\) target_m=\(([^)]*)\)", capture)
|
||||
if not camera_match:
|
||||
raise SystemExit("could not find canonical SW capture camera")
|
||||
parse_vector = lambda value: tuple(float(part) for part in value.split(","))
|
||||
near_camera, target = map(parse_vector, camera_match.groups())
|
||||
overview_camera = (mouth[0] + 2.5 * (near_camera[0] - mouth[0]),
|
||||
mouth[1] + 2.5 * (near_camera[1] - mouth[1]),
|
||||
near_camera[2] + 7.0)
|
||||
sw_final, sw_max = run(args.candidate_sw, "owner-sw-style1",
|
||||
center=mouth[:2], height_min=mouth[2] - 24.0,
|
||||
height_max=mouth[2] + 16.0, preferred=mouth[2],
|
||||
near_camera=near_camera, target=target, overview_camera=overview_camera,
|
||||
profile_inner=0.5, profile_outer=4.5, profile_coverage_limit=0.0)
|
||||
final = final and sw_final and sw_max <= OVERVIEW_NORMAL_LIMIT_DEG
|
||||
print(f"[MouthCreaseGate] near_limit_deg={NEAR_NORMAL_LIMIT_DEG:.1f} "
|
||||
f"overview_limit_deg={OVERVIEW_NORMAL_LIMIT_DEG:.1f} "
|
||||
f"L35_42deg_fail_control={'PASS' if controls else 'FAIL'} "
|
||||
f"L36_27deg_fail_control={'PASS' if controls else 'FAIL'} "
|
||||
f"L37_candidate={'PASS' if final else 'FAIL'} "
|
||||
f"real_owner_candidates={'PASS' if final else 'FAIL'} "
|
||||
f"status={'PASS' if controls and final else 'FAIL'}")
|
||||
raise SystemExit(0 if controls and final else 1)
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ FRAGMENT_MAX_AREA_M2 = 0.05
|
||||
FRAGMENT_MAX_DISTANCE_M = 12.0
|
||||
|
||||
|
||||
def small_components(triangles):
|
||||
def small_components(triangles, center=None):
|
||||
parent = list(range(len(triangles)))
|
||||
sizes = [1] * len(triangles)
|
||||
owners = {}
|
||||
@@ -67,7 +67,7 @@ def small_components(triangles):
|
||||
ab[0] * ac[1] - ab[1] * ac[0])
|
||||
item["area"] += 0.5 * math.sqrt(sum(value * value for value in cross))
|
||||
|
||||
cx, cy = CENTER
|
||||
cx, cy = center or CENTER
|
||||
small = []
|
||||
below_floor = []
|
||||
for item in components.values():
|
||||
@@ -81,12 +81,21 @@ def small_components(triangles):
|
||||
return len(components), small, below_floor
|
||||
|
||||
|
||||
def audit(path):
|
||||
cx, cy = CENTER
|
||||
bounds = (cx - OUTER, cx + OUTER, cy - OUTER, cy + OUTER)
|
||||
voxel_um, triangles, tiles = RING.read_scene(
|
||||
str(path), 0, HEIGHT_MIN, HEIGHT_MAX, bounds)
|
||||
component_count, fragments, below_floor = small_components(triangles)
|
||||
def audit(path, scene_data=None, center=None, height_min=None, height_max=None,
|
||||
preferred=None, inner=None, outer=None, normal_limit=None,
|
||||
coverage_limit=None, fragment_gate=True):
|
||||
cx, cy = center or CENTER
|
||||
inner = INNER if inner is None else inner
|
||||
outer = OUTER if outer is None else outer
|
||||
normal_limit = NORMAL_LIMIT_DEG if normal_limit is None else normal_limit
|
||||
coverage_limit = COVERAGE_MIN if coverage_limit is None else coverage_limit
|
||||
bounds = (cx - outer, cx + outer, cy - outer, cy + outer)
|
||||
if scene_data is None:
|
||||
scene_data = RING.read_scene(str(path), 0,
|
||||
HEIGHT_MIN if height_min is None else height_min,
|
||||
HEIGHT_MAX if height_max is None else height_max, bounds)
|
||||
voxel_um, triangles, tiles = scene_data
|
||||
component_count, fragments, below_floor = small_components(triangles, (cx, cy))
|
||||
print(f"[MouthFragmentComponents] mesh={path.name} components={component_count} "
|
||||
f"small_near_mouth={len(fragments)} subthreshold_near_mouth={len(below_floor)} "
|
||||
f"limits=(triangles<={FRAGMENT_MAX_TRIANGLES},"
|
||||
@@ -94,16 +103,18 @@ def audit(path):
|
||||
f"distance<={FRAGMENT_MAX_DISTANCE_M:.1f}m) "
|
||||
f"first={fragments[0] if fragments else None} "
|
||||
f"subthreshold={below_floor[0] if below_floor else None} "
|
||||
f"status={'PASS' if not fragments else 'FAIL'}")
|
||||
f"gate={'on' if fragment_gate else 'diagnostic'} "
|
||||
f"status={'PASS' if not fragments or not fragment_gate else 'FAIL'}")
|
||||
preferred = PREFERRED if preferred is None else preferred
|
||||
cell = max(2.0, 16.0 * voxel_um / 1_000_000.0)
|
||||
buckets = RING.build_buckets(triangles, True, bounds, cell)
|
||||
lines = []
|
||||
count = round((OUTER - INNER) / STEP)
|
||||
count = round((outer - inner) / STEP)
|
||||
for degrees in (135.0, 180.0, 225.0, 270.0, 315.0):
|
||||
angle = math.radians(degrees)
|
||||
lines.append((f"radial-{degrees:.0f}", [
|
||||
(cx + radius * math.cos(angle), cy + radius * math.sin(angle))
|
||||
for radius in (INNER + i * STEP for i in range(count + 1))]))
|
||||
for radius in (inner + i * STEP for i in range(count + 1))]))
|
||||
tangent_count = round(8.0 / STEP)
|
||||
for degrees in (225.0, 315.0):
|
||||
angle = math.radians(degrees)
|
||||
@@ -118,7 +129,7 @@ def audit(path):
|
||||
max_pair = None
|
||||
min_coverage = 1.0
|
||||
for name, points in lines:
|
||||
samples = [RING.sample_surface(buckets, x, y, True, cell, PREFERRED)
|
||||
samples = [RING.sample_surface(buckets, x, y, True, cell, preferred)
|
||||
for x, y in points]
|
||||
coverage = sum(value is not None for value in samples) / len(samples)
|
||||
min_coverage = min(min_coverage, coverage)
|
||||
@@ -136,11 +147,11 @@ def audit(path):
|
||||
max_pair = (name, points[index - 1], prior[0], points[index], current[0])
|
||||
print(f"[MouthFadeLine] mesh={path.name} line={name} coverage={coverage:.3f} "
|
||||
f"max_normal_jump_deg={line_max:.6f}")
|
||||
normal_pass = max_angle <= NORMAL_LIMIT_DEG and min_coverage >= COVERAGE_MIN
|
||||
passed = normal_pass and not fragments
|
||||
normal_pass = max_angle <= normal_limit and min_coverage >= coverage_limit
|
||||
passed = normal_pass and (not fragment_gate or not fragments)
|
||||
print(f"[MouthFadeAudit] mesh={path.name} tiles={tiles} lines={len(lines)} "
|
||||
f"coverage_min={min_coverage:.3f} coverage_limit={COVERAGE_MIN:.2f} "
|
||||
f"max_normal_jump_deg={max_angle:.6f} normal_limit_deg={NORMAL_LIMIT_DEG:.1f} "
|
||||
f"coverage_min={min_coverage:.3f} coverage_limit={coverage_limit:.2f} "
|
||||
f"max_normal_jump_deg={max_angle:.6f} normal_limit_deg={normal_limit:.1f} "
|
||||
f"fragments={len(fragments)} "
|
||||
f"worst_pair={max_pair} status={'PASS' if passed else 'FAIL'}")
|
||||
return passed, max_angle
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run recipe-stamped passage/profile audits for both compilers and both owner recipes."""
|
||||
"""Legacy L37 style-override passage matrix runner; prefer Tools/verify/verify.py."""
|
||||
import re
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
@@ -19,12 +20,19 @@ for recipe, filename in (
|
||||
("surface-world", "surface-world"),
|
||||
):
|
||||
for style in range(4):
|
||||
RUNS.append((f"owner-{filename}-style{style}",
|
||||
ROOT / f"Saved/Linux/linux25-owner-{filename}-style{style}.json", recipe))
|
||||
source = ROOT / f"Saved/Linux/linux25-owner-{filename}-style{style}.json"
|
||||
name = f"style-override-{filename}-style{style}"
|
||||
destination = OUT / "fixtures" / f"{name}.json"
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(source, destination)
|
||||
RUNS.append((name, destination, recipe))
|
||||
for style in range(4):
|
||||
RUNS.append((f"upper-sw-fixture-style{style}",
|
||||
OUT.parent / "l33/fixtures" / f"sw-upper-tunnel-network-style{style}.json",
|
||||
"floating-islands"))
|
||||
source = OUT.parent / "l33/fixtures" / f"sw-upper-tunnel-network-style{style}.json"
|
||||
name = f"style-override-upper-sw-fixture-style{style}"
|
||||
destination = OUT / "fixtures" / f"{name}.json"
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(source, destination)
|
||||
RUNS.append((name, destination, "floating-islands"))
|
||||
|
||||
RECIPE = re.compile(r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=PASS$", re.M)
|
||||
PASSAGE = re.compile(r"^\[ResolvedPassageAudit\].*walkability=PASS$", re.M)
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Export v24 ring meshes at matching LOD windows and retain old controls."""
|
||||
import csv
|
||||
import argparse
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l37"
|
||||
BIN = OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang"
|
||||
OUT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37"))
|
||||
BIN = Path(os.environ.get("VOXELFORGE_VERIFY_HEADLESS",
|
||||
OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang"))
|
||||
OWNER = ROOT / "Standalone/data/owner_world.json"
|
||||
TERRACE = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
|
||||
RING_SET = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingSetL32.py"
|
||||
@@ -19,6 +23,13 @@ TERRACE_RESULT = re.compile(r"^\[RingTerraceAudit\].*status=(PASS|FAIL)$", re.M)
|
||||
SET_RESULT = re.compile(r"^\[RingSetAudit\].*status=(PASS|FAIL)$", re.M)
|
||||
rows = []
|
||||
failed = False
|
||||
PARSER = argparse.ArgumentParser(description=__doc__)
|
||||
PARSER.add_argument("--lod0-only", action="store_true")
|
||||
PARSER.add_argument("--scene", choices=("fi-floor", "sw-ceiling", "all"), default="all")
|
||||
PARSER.add_argument("--skip-controls", action="store_true")
|
||||
OPTIONS = PARSER.parse_args()
|
||||
sys.path.insert(0, str(ROOT / "Standalone/Tools/verify"))
|
||||
from runner import current_stamp, first_stamp, bounded_run
|
||||
|
||||
FI_CENTERS = ((56, -72), (72, -72), (56, -88), (40, -72), (24, 24),
|
||||
(104, 24), (72, -88), (120, 24), (104, 8), (24, -24))
|
||||
@@ -42,6 +53,13 @@ def run(name, command):
|
||||
log = OUT / f"ring-{name}.log"
|
||||
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1800s", *map(str, command)]
|
||||
stamped = bool(command and Path(command[0]) == BIN)
|
||||
expected_stamp = current_stamp() if stamped else None
|
||||
if stamped:
|
||||
probe = bounded_run([str(BIN), "--print-source-stamp"], timeout=15)
|
||||
if (probe is None or probe.returncode != 0
|
||||
or first_stamp(probe.stdout or "") != expected_stamp):
|
||||
raise SystemExit(f"{name} refused an unstamped or stale executable: {BIN}")
|
||||
started = time.monotonic()
|
||||
try:
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
@@ -51,6 +69,9 @@ def run(name, command):
|
||||
result = None
|
||||
elapsed = time.monotonic() - started
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
if stamped and (first_stamp(output) != expected_stamp
|
||||
or current_stamp() != expected_stamp):
|
||||
raise SystemExit(f"{name} source stamp mismatch; see {log}")
|
||||
code = result.returncode if result is not None else "TIMEOUT"
|
||||
print(f"[L37RingStep] name={name} elapsed={elapsed:.1f}s exit={code} log={log}",
|
||||
flush=True)
|
||||
@@ -59,18 +80,32 @@ def run(name, command):
|
||||
|
||||
def export(label, recipe, max_level, camera):
|
||||
mesh = OUT / f"ring-owner-{label}-v24.vfmesh"
|
||||
layout = OUT / "layouts" / f"ring-layout-{label}.json"
|
||||
layout.parent.mkdir(parents=True, exist_ok=True)
|
||||
command = [BIN, OWNER, "--workers=12",
|
||||
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||
"--force-generate", "--clip-radius=3", "--max-clip-level=8",
|
||||
f"--max-level={max_level}", f"--recipe={recipe}"]
|
||||
if camera:
|
||||
command.append(f"--camera={camera}")
|
||||
command.append(f"--mesh-export={mesh}")
|
||||
command.extend((f"--mesh-export={mesh}", f"--resolved-layout-output={layout}"))
|
||||
result, output, elapsed = run(f"export-{label}", command)
|
||||
stamps = STAMP.findall(output)
|
||||
ok = (result is not None and result.returncode == 0
|
||||
and stamps == [(recipe, recipe)] and EXPORT.search(output) and mesh.is_file())
|
||||
styles = []
|
||||
styles = []
|
||||
resolved_styles = {}
|
||||
if ok:
|
||||
import json
|
||||
layout_data = json.loads(layout.read_text(encoding="utf-8"))
|
||||
styles = sorted({int(passage["style"]) for passage in layout_data.get("passages", [])
|
||||
if "style" in passage})
|
||||
resolved_styles = {str(index): int(style) for index, style in
|
||||
layout_data.get("resolved_strate_passage_styles", {}).items()}
|
||||
ok = styles == [2] and resolved_styles == {"0": 2, "1": 1}
|
||||
print(f"[L37RingExport] scene={label} recipe={recipe} stamp={stamps} "
|
||||
f"passage_style={styles if mesh.exists() else []} resolved_strate_styles={resolved_styles} "
|
||||
f"bytes={mesh.stat().st_size if mesh.exists() else 0} "
|
||||
f"status={'PASS' if ok else 'FAIL'}", flush=True)
|
||||
return mesh, elapsed, ok
|
||||
@@ -108,32 +143,36 @@ def lod1_set(label, recipe, surface, mesh, centers, height_min, height_max):
|
||||
return ok
|
||||
|
||||
|
||||
for label, recipe, surface, window_min, window_max, camera0, camera1, centers in (
|
||||
SCENES = (
|
||||
("fi-floor", "floating-islands", "floor", -122.0, -117.0,
|
||||
"64,-32,-115,0,-45", "64,-32,-115,0,-45", FI_CENTERS),
|
||||
("sw-ceiling", "surface-world", "ceiling", -132.0, -124.0,
|
||||
"64,-32,-136,0,45", "-96,128,-136,0,45", SW_CENTERS)):
|
||||
"64,-32,-136,0,45", "-96,128,-136,0,45", SW_CENTERS))
|
||||
selected_scenes = (SCENES if OPTIONS.scene == "all" else
|
||||
tuple(scene for scene in SCENES if scene[0] == OPTIONS.scene))
|
||||
for label, recipe, surface, window_min, window_max, camera0, camera1, centers in selected_scenes:
|
||||
low_mesh, _, low_ok = export(f"{label}-lod0", recipe, 0, camera0)
|
||||
failed |= not low_ok
|
||||
if low_ok:
|
||||
failed |= not terrace(label, recipe, surface, low_mesh, 0, window_min, window_max)
|
||||
|
||||
detail_mesh, _, detail_ok = export(f"{label}-lod1", recipe, 1, camera1)
|
||||
failed |= not detail_ok
|
||||
if detail_ok:
|
||||
set_min, set_max = ((-121.625, -117.375) if label == "fi-floor"
|
||||
else (-128.375, -124.125))
|
||||
failed |= not lod1_set(label, recipe, surface, detail_mesh, centers,
|
||||
set_min, set_max)
|
||||
if not OPTIONS.lod0_only:
|
||||
detail_mesh, _, detail_ok = export(f"{label}-lod1", recipe, 1, camera1)
|
||||
failed |= not detail_ok
|
||||
if detail_ok:
|
||||
set_min, set_max = ((-121.625, -117.375) if label == "fi-floor"
|
||||
else (-128.375, -124.125))
|
||||
failed |= not lod1_set(label, recipe, surface, detail_mesh, centers,
|
||||
set_min, set_max)
|
||||
|
||||
full_mesh, _, full_ok = export(f"{label}-lod2-8", recipe, 8, None)
|
||||
failed |= not full_ok
|
||||
if full_ok:
|
||||
for lod in range(2, 9):
|
||||
center = LOD_CENTERS[label][lod]
|
||||
radius = 4 * (1 << lod)
|
||||
failed |= not terrace(label, recipe, surface, full_mesh, lod,
|
||||
window_min, window_max, center, radius)
|
||||
full_mesh, _, full_ok = export(f"{label}-lod2-8", recipe, 8, None)
|
||||
failed |= not full_ok
|
||||
if full_ok:
|
||||
for lod in range(2, 9):
|
||||
center = LOD_CENTERS[label][lod]
|
||||
radius = 4 * (1 << lod)
|
||||
failed |= not terrace(label, recipe, surface, full_mesh, lod,
|
||||
window_min, window_max, center, radius)
|
||||
|
||||
# Keep the established L30 terrace and L31 flat-surface negative controls live.
|
||||
controls = (
|
||||
@@ -141,7 +180,7 @@ controls = (
|
||||
-130.0, -110.0),
|
||||
("l31-flat", ROOT / "Saved/Linux/l31/fi-floor-v19.vfmesh", -122.0, -117.0),
|
||||
)
|
||||
for name, mesh, height_min, height_max in controls:
|
||||
for name, mesh, height_min, height_max in (() if OPTIONS.skip_controls else controls):
|
||||
result, output, _ = run(f"control-{name}", ["python3", TERRACE, mesh,
|
||||
"--surface=floor", "--center", "64", "-32", "--radius=32", "--lod=0",
|
||||
f"--height-min-m={height_min}", f"--height-max-m={height_max}"])
|
||||
@@ -151,14 +190,18 @@ for name, mesh, height_min, height_max in controls:
|
||||
print(f"[L37RingControl] name={name} detected={detected} "
|
||||
f"status={'PASS' if detected else 'FAIL'}", flush=True)
|
||||
|
||||
summary = OUT / "ring-audits-l37.csv"
|
||||
scene_tag = OPTIONS.scene
|
||||
lod_tag = "lod0" if OPTIONS.lod0_only else "lod0-8"
|
||||
summary = OUT / f"ring-audits-{scene_tag}-{lod_tag}.csv"
|
||||
with summary.open("w", newline="", encoding="utf-8") as stream:
|
||||
writer = csv.writer(stream)
|
||||
writer.writerow(("scene", "recipe", "surface", "lod", "elapsed_seconds", "status"))
|
||||
writer.writerows((label, recipe, surface, lod, f"{elapsed:.3f}",
|
||||
"PASS" if ok else "FAIL")
|
||||
for label, recipe, surface, lod, elapsed, ok in rows)
|
||||
expected_runs = (1 if OPTIONS.lod0_only else 9) * len(selected_scenes)
|
||||
expected_controls = 0 if OPTIONS.skip_controls else len(controls)
|
||||
print(f"[L37RingSummary] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
|
||||
f"fail={sum(not row[-1] for row in rows)} controls=2 "
|
||||
f"fail={sum(not row[-1] for row in rows)} controls={expected_controls} "
|
||||
f"status={'FAIL' if failed else 'PASS'} summary={summary}", flush=True)
|
||||
raise SystemExit(1 if failed or len(rows) != 18 else 0)
|
||||
raise SystemExit(1 if failed or len(rows) != expected_runs else 0)
|
||||
Reference in new issue
Block a user