wip(assembler): round L37 -- FI mouth carve-fade fix, no raised bridge (style 0); research R1

The passage mouth fade multiplied the signed carve distance by zero, which turned native rock into an
open surface (the reason earlier rounds stamped walk pads at mouths); it now blends between the
uncarved field and the full signed carve. The FI style-0 route followed a lifted grade envelope over
the crater (the "bridge"); it now runs on the rim and follows the native surface. New built-shape and
crease audits with thresholds frozen before the fix (L36 and box controls fail). 24/24 passage rows,
30/30 determinism, 16/16 invariants, 18/18 interface/ring. Unreal: one recipe setter (uncompiled).
Research R1 (read-only, web): ranked proposals for natural walkable mouths, floor-limited carve first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-23 05:51:50 +02:00
1 parent 8be1c4d9ef
commit 3e443bf81c
33 files changed
+2767 -86

No files matched your search

@@ -0,0 +1,535 @@
#!/usr/bin/env python3
"""Measure player-scale planar aprons, straight boundaries and corners at passage mouths."""
import argparse
import importlib.util
import math
from collections import defaultdict, deque
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
PROFILE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
SPEC = importlib.util.spec_from_file_location("ring_profile_l37", PROFILE_PATH)
PROFILE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(PROFILE)
# Frozen from player scale before measuring the L37 candidate. The 0.75 m line limit
# is just over the 0.68 m capsule width; 0.50 m2 is one capsule-width footprint.
STRAIGHT_LIMIT_M = 0.75
PLANE_LIMIT_M2 = 0.50
CORNER_TURN_LIMIT_DEG = 30.0
PLANE_TOLERANCE_M = 0.02
NORMAL_BUCKET = 0.07
PLANE_BUCKET_M = 0.03
MOUTH_CORRIDOR_HALF_WIDTH_M = 1.50
MOUTH_CORRIDOR_LENGTH_M = 12.0
MOUTH_Z_BELOW_M = 4.0
MOUTH_Z_ABOVE_M = 3.0
PLAYER_FLOOR_OFFSET_M = 1.13
DELTA_THRESHOLD_M = 0.05
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"),
}
def sub(a, b):
return tuple(a[i] - b[i] for i in range(3))
def dot(a, b):
return sum(a[i] * b[i] for i in range(3))
def cross(a, b):
return (a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0])
def length(a):
return math.sqrt(dot(a, a))
def face(points):
n = cross(sub(points[1], points[0]), sub(points[2], points[0]))
twice_area = length(n)
if twice_area <= 1.0e-12:
return None
area = twice_area * 0.5
n = tuple(v / twice_area for v in n)
if n[2] < 0.0:
n = tuple(-v for v in n)
d = -dot(n, points[0])
return n, d, area
def vertex_key(point):
return tuple(round(value, 4) for value in point)
def edge_key(a, b):
ka, kb = vertex_key(a), vertex_key(b)
return (ka, kb) if ka < kb else (kb, ka)
def rdp(points, epsilon):
if len(points) <= 2:
return points
a, b = points[0], points[-1]
ab = sub(b, a)
ab2 = dot(ab, ab)
maximum, index = -1.0, -1
for i in range(1, len(points) - 1):
ap = sub(points[i], a)
t = max(0.0, min(1.0, dot(ap, ab) / ab2)) if ab2 else 0.0
closest = tuple(a[j] + t * ab[j] for j in range(3))
distance = length(sub(points[i], closest))
if distance > maximum:
maximum, index = distance, i
if maximum <= epsilon:
return [a, b]
left = rdp(points[:index + 1], epsilon)
right = rdp(points[index:], epsilon)
return left[:-1] + right
def simplified_boundary(component, records):
counts = defaultdict(int)
actual_edges = {}
for index in component:
points = records[index]["points"]
for a, b in ((points[0], points[1]), (points[1], points[2]),
(points[2], points[0])):
key = edge_key(a, b)
counts[key] += 1
actual_edges[key] = (key[0], key[1])
boundary = [edge for edge, count in counts.items() if count == 1]
graph = defaultdict(list)
for a, b in boundary:
graph[a].append(b)
graph[b].append(a)
if not graph:
return [], []
def direction(a, b):
dx, dy = b[0] - a[0], b[1] - a[1]
magnitude = math.hypot(dx, dy)
return ((dx / magnitude, dy / magnitude), magnitude) if magnitude > 1.0e-8 else (None, 0.0)
def straight_run(origin, first):
prior, current = origin, first
initial, edge_length = direction(prior, current)
if initial is None:
return 0.0
total = edge_length
seen = {origin, first}
while current in graph and len(graph[current]) == 2:
choices = [value for value in graph[current] if value != prior]
if len(choices) != 1 or choices[0] in seen:
break
nxt = choices[0]
outgoing, edge_length = direction(current, nxt)
if outgoing is None:
break
deflection = math.degrees(math.acos(max(-1.0, min(1.0,
initial[0] * outgoing[0] + initial[1] * outgoing[1]))))
if deflection > 8.0:
break
total += edge_length
prior, current = current, nxt
initial = outgoing
seen.add(current)
return total
longest = 0.0
corners = []
for vertex, neighbours in graph.items():
if len(neighbours) == 2:
incoming, _ = direction(neighbours[0], vertex)
outgoing, _ = direction(vertex, neighbours[1])
if incoming and outgoing:
turn = math.degrees(math.acos(max(-1.0, min(1.0,
incoming[0] * outgoing[0] + incoming[1] * outgoing[1]))))
if turn >= CORNER_TURN_LIMIT_DEG:
left_run = straight_run(vertex, neighbours[0])
right_run = straight_run(vertex, neighbours[1])
if left_run >= STRAIGHT_LIMIT_M and right_run >= STRAIGHT_LIMIT_M:
corners.append(turn)
for neighbour in neighbours:
longest = max(longest, straight_run(vertex, neighbour))
return [longest], corners
def analyze_triangles(triangles, center, center_z, crop_to_center=True,
include_sides=False):
records = []
for lod, average_nz, vertices in triangles:
points = tuple(vertex[0] for vertex in vertices)
centroid = tuple(sum(point[axis] for point in points) / 3.0 for axis in range(3))
if (crop_to_center and
(abs(centroid[0] - center[0]) > MOUTH_CORRIDOR_LENGTH_M + 2.0
or abs(centroid[1] - center[1]) > MOUTH_CORRIDOR_LENGTH_M + 2.0
or centroid[2] < center_z - MOUTH_Z_BELOW_M
or centroid[2] > center_z + MOUTH_Z_ABOVE_M)):
continue
measured = face(points)
if not measured:
continue
normal, plane_d, area = measured
if not include_sides and (normal[2] < 0.35 or average_nz < 0.25):
continue
bucket = (round(normal[0] / NORMAL_BUCKET), round(normal[1] / NORMAL_BUCKET),
round(normal[2] / NORMAL_BUCKET), round(plane_d / PLANE_BUCKET_M))
records.append({"points": points, "centroid": centroid, "normal": normal,
"d": plane_d, "area": area, "bucket": bucket})
by_bucket = defaultdict(list)
for index, record in enumerate(records):
by_bucket[record["bucket"]].append(index)
edge_indices = defaultdict(list)
for index, record in enumerate(records):
points = record["points"]
for a, b in ((points[0], points[1]), (points[1], points[2]),
(points[2], points[0])):
edge_indices[edge_key(a, b)].append(index)
adjacency = defaultdict(list)
for indices in edge_indices.values():
if len(indices) == 2:
a, b = indices
# Adjacent triangles can differ by one normal bucket on a smooth mesh.
# Plane membership is checked against the seed below, so let that test
# decide whether a connected patch is planar instead of splitting it at
# the bucket boundary.
adjacency[a].append(b)
adjacency[b].append(a)
max_area = 0.0
max_straight = 0.0
corner_count = 0
max_corner_turn = 0.0
max_bounds = None
max_normal = None
visited = set()
for indices in by_bucket.values():
for seed in indices:
if seed in visited:
continue
component = []
queue = deque([seed])
queued = {seed}
seed_record = records[seed]
while queue:
index = queue.popleft()
record = records[index]
if (abs(dot(seed_record["normal"], record["normal"])) < math.cos(math.radians(4.5))
or max(abs(dot(seed_record["normal"], point) + seed_record["d"])
for point in record["points"]) > PLANE_TOLERANCE_M):
continue
visited.add(index)
component.append(index)
for neighbour in adjacency[index]:
if neighbour not in visited and neighbour not in queued:
queued.add(neighbour)
queue.append(neighbour)
area = sum(records[index]["area"] for index in component)
if area <= max_area:
continue
segments, corners = simplified_boundary(component, records)
max_area = area
max_straight = max(segments, default=0.0)
corner_count = len(corners)
max_corner_turn = max(corners, default=0.0)
points = [point for index in component for point in records[index]["points"]]
max_bounds = tuple((min(point[axis] for point in points),
max(point[axis] for point in points)) for axis in range(3))
max_normal = seed_record["normal"]
return {"triangles": len(records), "planar_area": max_area,
"straight": max_straight, "corners": corner_count,
"corner_turn_max": max_corner_turn,
"bounds": max_bounds, "normal": max_normal}
def synthetic_box(center, z):
x, y = center
corners = ((x - 1.5, y - 1.0, z), (x + 1.5, y - 1.0, z),
(x + 1.5, y + 1.0, z), (x - 1.5, y + 1.0, z))
return [(0, 1.0, tuple((point, (0.0, 0.0, 1.0)) for point in tri))
for tri in ((corners[0], corners[1], corners[2]),
(corners[0], corners[2], corners[3]))]
def fail(metrics, include_planarity=True):
planar_fail = (include_planarity and
(metrics["planar_area"] >= PLANE_LIMIT_M2
or metrics.get("all_face_planar_area", 0.0) >= PLANE_LIMIT_M2))
return (planar_fail
or metrics["straight"] > STRAIGHT_LIMIT_M
or metrics.get("all_face_straight", 0.0) > STRAIGHT_LIMIT_M
or metrics["corners"] > 0
or metrics.get("all_face_corners", 0) > 0)
def mouth_route_points(passage, side, scale):
controls = [
(tuple(value * scale for value in point["position_q"]),
point["radius_q"] * scale)
for point in passage["control_points"]
]
if side == "upper":
return controls[:4]
lower_mouth = tuple(value * scale for value in passage["lower_mouth_q"])
lower_index = min(range(len(controls)), key=lambda index: sum(
(controls[index][0][axis] - lower_mouth[axis]) ** 2 for axis in range(3)))
return list(reversed(controls[max(0, lower_index - 3):lower_index + 1]))
def select_corridor_triangles(triangles, route, half_width, path_length):
selected = []
for triangle in triangles:
vertices = triangle[2]
centroid = tuple(sum(vertex[0][axis] for vertex in vertices) / 3.0
for axis in range(3))
closest_distance = math.inf
closest_along = 0.0
closest_floor = 0.0
prefix = 0.0
for (a, radius_a), (b, radius_b) in zip(route, route[1:]):
dx, dy, dz = (b[axis] - a[axis] for axis in range(3))
horizontal_length_squared = dx * dx + dy * dy
horizontal_length = math.sqrt(horizontal_length_squared)
if horizontal_length_squared <= 1.0e-12:
t = 0.0
else:
t = max(0.0, min(1.0,
((centroid[0] - a[0]) * dx + (centroid[1] - a[1]) * dy)
/ horizontal_length_squared))
center_x, center_y = a[0] + t * dx, a[1] + t * dy
lateral = math.hypot(centroid[0] - center_x, centroid[1] - center_y)
segment_length = math.sqrt(horizontal_length_squared + dz * dz)
if lateral < closest_distance:
closest_distance = lateral
closest_along = prefix + t * segment_length
center_z = a[2] + t * dz
radius = radius_a + t * (radius_b - radius_a)
closest_floor = center_z - radius + PLAYER_FLOOR_OFFSET_M
prefix += segment_length
if (closest_distance <= half_width
and 0.0 <= closest_along <= path_length
and abs(centroid[2] - closest_floor) <= 1.5):
selected.append(triangle)
return selected
def route_floor_at(route, x, y):
closest = None
prefix = 0.0
for (a, radius_a), (b, radius_b) in zip(route, route[1:]):
dx, dy, dz = (b[axis] - a[axis] for axis in range(3))
horizontal_sq = dx * dx + dy * dy
t = max(0.0, min(1.0, ((x - a[0]) * dx + (y - a[1]) * dy)
/ horizontal_sq)) if horizontal_sq > 1.0e-12 else 0.0
center_x, center_y = a[0] + t * dx, a[1] + t * dy
lateral = math.hypot(x - center_x, y - center_y)
length = math.sqrt(horizontal_sq + dz * dz)
along = prefix + t * length
center_z = a[2] + t * dz
radius = radius_a + t * (radius_b - radius_a)
floor = center_z - radius + PLAYER_FLOOR_OFFSET_M
candidate = (lateral, along, floor)
if closest is None or lateral < closest[0]:
closest = candidate
prefix += length
if closest and closest[0] <= MOUTH_CORRIDOR_HALF_WIDTH_M \
and closest[1] <= MOUTH_CORRIDOR_LENGTH_M:
return closest[2]
return None
def differential_surface(candidate_triangles, baseline_triangles, route, bounds):
cell_size = 0.5
candidate_buckets = PROFILE.build_buckets(candidate_triangles, True, bounds, cell_size)
baseline_buckets = PROFILE.build_buckets(baseline_triangles, True, bounds, cell_size)
route_x = [point[0][0] for point in route]
route_y = [point[0][1] for point in route]
padding = MOUTH_CORRIDOR_HALF_WIDTH_M + DELTA_GRID_M
x0 = math.floor((min(route_x) - padding) / DELTA_GRID_M) * DELTA_GRID_M
x1 = math.ceil((max(route_x) + padding) / DELTA_GRID_M) * DELTA_GRID_M
y0 = math.floor((min(route_y) - padding) / DELTA_GRID_M) * DELTA_GRID_M
y1 = math.ceil((max(route_y) + padding) / DELTA_GRID_M) * DELTA_GRID_M
nx = round((x1 - x0) / DELTA_GRID_M)
ny = round((y1 - y0) / DELTA_GRID_M)
samples = {}
for iy in range(ny + 1):
y = y0 + iy * DELTA_GRID_M
for ix in range(nx + 1):
x = x0 + ix * DELTA_GRID_M
floor = route_floor_at(route, x, y)
if floor is None:
continue
candidate = PROFILE.sample_surface(candidate_buckets, x, y, True,
cell_size, preferred_height=floor)
baseline = PROFILE.sample_surface(baseline_buckets, x, y, True,
cell_size, preferred_height=floor)
samples[(ix, iy)] = (candidate, baseline)
delta_triangles = []
changed_cells = 0
changed_area = 0.0
deltas = []
for iy in range(ny):
for ix in range(nx):
keys = ((ix, iy), (ix + 1, iy), (ix + 1, iy + 1), (ix, iy + 1))
values = [samples.get(key) for key in keys]
if any(value is None or value[0] is None for value in values):
continue
cell_deltas = []
changed = False
for candidate, baseline in values:
if baseline is None:
changed = True
cell_deltas.append(math.inf)
else:
delta = candidate[0] - baseline[0]
cell_deltas.append(abs(delta))
deltas.append(abs(delta))
changed |= abs(delta) >= DELTA_THRESHOLD_M
if not changed:
continue
changed_cells += 1
changed_area += DELTA_GRID_M * DELTA_GRID_M
points = []
for key, (candidate, _) in zip(keys, values):
x = x0 + key[0] * DELTA_GRID_M
y = y0 + key[1] * DELTA_GRID_M
points.append(((x, y, candidate[0]), candidate[1]))
for triangle_indices in ((0, 1, 2), (0, 2, 3)):
vertices = tuple(points[index] for index in triangle_indices)
avg_nz = sum(vertex[1][2] for vertex in vertices) / 3.0
delta_triangles.append((0, avg_nz, vertices))
metrics = analyze_triangles(delta_triangles, (0.0, 0.0), 0.0,
crop_to_center=False)
metrics["changed_cells"] = changed_cells
metrics["changed_area"] = changed_area
metrics["max_delta"] = max(deltas, default=0.0)
metrics["mean_abs_delta"] = sum(deltas) / len(deltas) if deltas else 0.0
return metrics
def audit_scene(scene, out, baseline_out, mesh_override=None):
mesh_name, layout_name, target_generator, side = SCENES[scene]
import json
layout = json.loads((out / layout_name).read_text(encoding="utf-8"))
scale = layout["core_voxel_size_um"] / (256.0 * 1_000_000.0)
strata = {int(value["strate_index"]): value for value in layout["strates"]}
choices = []
for passage in layout["passages"]:
side_index = int(passage[side + "_strate_index"])
if strata[side_index]["generator_kind"] == target_generator:
choices.append(passage)
if not choices:
raise RuntimeError(f"no {target_generator} {side} mouth in {scene}")
passage = min(choices, key=lambda value: value["stable_id"])
mouth = tuple(value * scale for value in passage[f"{side}_mouth_q"])
bounds = (mouth[0] - MOUTH_CORRIDOR_LENGTH_M - MOUTH_CORRIDOR_HALF_WIDTH_M - 1.0,
mouth[0] + MOUTH_CORRIDOR_LENGTH_M + MOUTH_CORRIDOR_HALF_WIDTH_M + 1.0,
mouth[1] - MOUTH_CORRIDOR_LENGTH_M - MOUTH_CORRIDOR_HALF_WIDTH_M - 1.0,
mouth[1] + MOUTH_CORRIDOR_LENGTH_M + MOUTH_CORRIDOR_HALF_WIDTH_M + 1.0)
voxel_um, triangles, tiles = PROFILE.read_scene(
str(mesh_override or out / mesh_name), 0,
mouth[2] - MOUTH_Z_BELOW_M, mouth[2] + MOUTH_Z_ABOVE_M, bounds)
baseline_um, baseline_triangles, baseline_tiles = PROFILE.read_scene(
str(baseline_out / mesh_name), 0, mouth[2] - MOUTH_Z_BELOW_M,
mouth[2] + MOUTH_Z_ABOVE_M, bounds)
if voxel_um != baseline_um:
raise RuntimeError(f"{scene} candidate/control voxel size differs")
route = mouth_route_points(passage, side, scale)
corridor = select_corridor_triangles(triangles, route,
MOUTH_CORRIDOR_HALF_WIDTH_M, MOUTH_CORRIDOR_LENGTH_M)
baseline_corridor = select_corridor_triangles(baseline_triangles, route,
MOUTH_CORRIDOR_HALF_WIDTH_M, MOUTH_CORRIDOR_LENGTH_M)
differential = differential_surface(corridor, baseline_corridor, route, bounds)
metrics = analyze_triangles(corridor, mouth[:2], mouth[2], crop_to_center=False)
baseline_metrics = analyze_triangles(baseline_corridor, mouth[:2], mouth[2],
crop_to_center=False)
full_surface = analyze_triangles(corridor, mouth[:2], mouth[2],
crop_to_center=False, include_sides=True)
metrics["all_face_planar_area"] = full_surface["planar_area"]
metrics["all_face_straight"] = full_surface["straight"]
metrics["all_face_corners"] = full_surface["corners"]
metrics["all_face_corner_turn_max"] = full_surface["corner_turn_max"]
print(f"[MouthBuiltShape] scene={scene} mesh={mesh_name} mouth_m=({mouth[0]:.3f},"
f"{mouth[1]:.3f},{mouth[2]:.3f}) voxel_um={voxel_um} tiles={tiles} "
f"route_corridor_m=({MOUTH_CORRIDOR_HALF_WIDTH_M:.2f}m_halfwidth,"
f"{MOUTH_CORRIDOR_LENGTH_M:.1f}m_length) corridor_triangles={len(corridor)} "
f"surface_triangles={metrics['triangles']} planar_patch_m2={metrics['planar_area']:.4f} "
f"straight_run_m={metrics['straight']:.4f} sharp_corners={metrics['corners']} "
f"corner_turn_max_deg={metrics['corner_turn_max']:.2f} "
f"all_face_planar_patch_m2={full_surface['planar_area']:.4f} "
f"all_face_straight_run_m={full_surface['straight']:.4f} "
f"all_face_corners={full_surface['corners']} "
f"all_face_corner_turn_max_deg={full_surface['corner_turn_max']:.2f} "
f"matched_no_support_planar_patch_m2={baseline_metrics['planar_area']:.4f} "
f"matched_no_support_straight_run_m={baseline_metrics['straight']:.4f} "
f"matched_no_support_corners={baseline_metrics['corners']} "
f"control_tiles={baseline_tiles} changed_cells={differential['changed_cells']} "
f"changed_area_m2={differential['changed_area']:.4f} "
f"max_abs_delta_m={differential['max_delta']:.4f} "
f"mean_abs_delta_m={differential['mean_abs_delta']:.4f} "
f"added_planar_patch_m2={differential['planar_area']:.4f} "
f"added_straight_run_m={differential['straight']:.4f} "
f"added_sharp_corners={differential['corners']} "
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"delta_threshold_m={DELTA_THRESHOLD_M:.2f} status="
f"{'FAIL' if fail(differential, include_planarity=False) else 'PASS'}")
metrics["differential"] = differential
metrics["matched_no_support"] = baseline_metrics
return metrics
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", type=Path, default=ROOT / "Saved/Linux/l37")
parser.add_argument("--baseline-out", type=Path,
default=ROOT / "Saved/Linux/l37/no-tread-control")
parser.add_argument("--control-out", type=Path, default=ROOT / "Saved/Linux/l36")
parser.add_argument("--scene", choices=tuple(SCENES) + ("all",), default="all")
args = parser.parse_args()
out = args.out if args.out.is_absolute() else ROOT / args.out
baseline_out = (args.baseline_out if args.baseline_out.is_absolute()
else ROOT / args.baseline_out)
control_out = (args.control_out if args.control_out.is_absolute()
else ROOT / args.control_out)
scenes = tuple(SCENES) if args.scene == "all" else (args.scene,)
measured = {scene: audit_scene(scene, out, baseline_out) for scene in scenes}
l36_fi = audit_scene("owner-fi", control_out, baseline_out)
l36_failed = fail(l36_fi["differential"])
print(f"[MouthBuiltShapeL36Control] scene=owner-fi planar_patch_m2="
f"{l36_fi['differential']['planar_area']:.4f} "
f"straight_run_m={l36_fi['differential']['straight']:.4f} "
f"sharp_corners={l36_fi['differential']['corners']} detected={l36_failed} "
f"negative_control={'PASS' if l36_failed else 'FAIL'}")
control = analyze_triangles(synthetic_box((0.0, 0.0), 0.0), (0.0, 0.0), 0.0)
control_ok = fail(control)
print(f"[MouthBuiltShapeBoxControl] dimensions_m=3x2 planar_patch_m2="
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())
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"L36_control_fail={'PASS' if l36_failed else 'FAIL'} "
f"box_control_fail={'PASS' if control_ok else 'FAIL'} "
f"planar_gate=informational_owner_accepted "
f"status={'PASS' if passed else 'FAIL'}")
raise SystemExit(0 if passed else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Render the fixed 17 m FI mouth overview as a small JPEG."""
import math
import os
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = Path(os.environ.get("VOXELFORGE_L37_OUT", ROOT / "Saved/Linux/l37"))
CAPTURES = Path(os.environ.get("VOXELFORGE_L37_CAPTURE_DIR",
Path(__file__).resolve().parent / "captures"))
MESH = Path(os.environ.get("VOXELFORGE_L37_MESH",
OUT / "owner-fi-ground-mouth.vfmesh"))
RENDERER = Path(os.environ.get("VOXELFORGE_L37_RENDERER",
OUT / "bin/VoxelForgeOffscreenCapture-l37-final-clang"))
PNG = OUT / "capture-png/owner-fi-ground-mouth-overview.png"
JPEG = CAPTURES / "owner-fi-ground-mouth-overview.jpg"
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
def normalize(vector):
magnitude = math.sqrt(sum(value * value for value in vector))
return tuple(value / magnitude for value in vector)
def camera_angles(camera, target):
delta = tuple(target[i] - camera[i] for i in range(3))
horizontal = math.hypot(delta[0], delta[1])
return math.degrees(math.atan2(delta[0], delta[1])), math.degrees(
math.atan2(delta[2], horizontal))
def fmt(values):
return ",".join(f"{value:.6f}" for value in values)
def run(name, command):
log = OUT / f"capture-overview-{name}.log"
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", *map(str, command)]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
elapsed = time.monotonic() - started
print(f"[L37OverviewCaptureRun] step={name} elapsed={elapsed:.1f}s "
f"exit={result.returncode} log={log}", flush=True)
if result.returncode != 0:
raise SystemExit(f"{name} failed; see {log}")
def main():
if not MESH.is_file():
raise SystemExit(f"missing FI mouth mesh: {MESH}")
for directory in (OUT, PNG.parent, CAPTURES):
directory.mkdir(parents=True, exist_ok=True)
yaw, pitch = camera_angles(CAMERA, TARGET)
run("render", [RENDERER, MESH, PNG, "--size=1600x900", f"--fov-deg={FOV}",
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",
f"--height-center-m={TARGET[2]:.6f}", "--height-range-m=8",
"--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])
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="
f"{distance:.3f} camera_m=({fmt(CAMERA)}) target_m=({fmt(TARGET)}) "
f"fov_deg={FOV:.1f} resolution=1600x900 bytes={size} "
f"status={'PASS' if 0 < size <= 300_000 else 'FAIL'}", flush=True)
raise SystemExit(0 if 0 < size <= 300_000 else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,404 @@
#!/usr/bin/env python3
"""Export and render the standard four-view passage set for L37."""
import json
import importlib.util
import argparse
import math
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = Path(os.environ.get("VOXELFORGE_L37_OUT", ROOT / "Saved/Linux/l37"))
BIN = OUT / "bin"
CAPTURES = Path(os.environ.get("VOXELFORGE_L37_CAPTURE_DIR",
Path(__file__).resolve().parent / "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",
"floating-islands", "FloatingIslands", "upper"),
("owner-sw", ROOT / "Saved/Linux/linux25-owner-surface-world-style0.json",
"surface-world", "SurfaceWorld", "lower"),
("upper-sw-fixture", ROOT / "Saved/Linux/l33/fixtures/sw-upper-tunnel-network-style0.json",
"floating-islands", "SurfaceWorld", "upper"),
)
SUBVOXELS_PER_VOXEL = 256.0
RING_AUDIT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
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)
for directory in (OUT, BIN, PNG_DIR, CAPTURES):
directory.mkdir(parents=True, exist_ok=True)
ENV = os.environ.copy()
ENV["LP_NUM_THREADS"] = "12"
ENV["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
PARSER = argparse.ArgumentParser(description=__doc__)
PARSER.add_argument("--scene", choices=[value[0] for value in RECIPE_INPUTS])
PARSER.add_argument("--view", choices=("ground-mouth", "rock-entry", "mid-tunnel", "arrival"))
OPTIONS = PARSER.parse_args()
def run(name, command):
log = OUT / f"capture-{name}.log"
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", *map(str, command)]
started = time.monotonic()
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 result.returncode != 0:
raise SystemExit(f"{name} failed; see {log}")
return log
def add(a, b):
return tuple(x + y for x, y in zip(a, b))
def sub(a, b):
return tuple(x - y for x, y in zip(a, b))
def mul(a, scale):
return tuple(x * scale for x in a)
def norm(a):
length = math.sqrt(sum(x * x for x in a))
if length <= 1.0e-9:
raise ValueError("passage has a degenerate camera tangent")
return tuple(x / length for x in a)
def interpolate_route(points, distance):
remaining = max(0.0, distance)
for a, b in zip(points, points[1:]):
delta = sub(b[0], a[0])
length = math.sqrt(sum(x * x for x in delta))
if length <= 1.0e-9:
continue
if remaining <= length:
t = remaining / length
pos = tuple(a[0][axis] + delta[axis] * t for axis in range(3))
radius = a[1] + (b[1] - a[1]) * t
return pos, radius
remaining -= length
return points[-1]
def route_frame(points, distance):
center, radius = interpolate_route(points, distance)
before, _ = interpolate_route(points, max(0.0, distance - 0.25))
after, _ = interpolate_route(points, distance + 0.25)
tangent = norm(sub(after, before))
horizontal = norm((tangent[0], tangent[1], 0.0))
side = (-horizontal[1], horizontal[0], 0.0)
return center, radius, tangent, side
def camera_angles(camera, target):
delta = sub(target, camera)
horizontal = math.hypot(delta[0], delta[1])
yaw = math.degrees(math.atan2(delta[0], delta[1]))
pitch = math.degrees(math.atan2(delta[2], horizontal))
return yaw, pitch
def fmt(values):
return ",".join(f"{value:.6f}" for value in values)
def load_reference_cameras():
log = ROOT / "Saved/Linux/l34/run-capture-standard-set-final-radius3.log"
pattern = re.compile(
r"^\[L34CaptureCamera\] view=(\S+).*?camera_m=\(([^)]*)\) "
r"target_m=\(([^)]*)\) clip=(\S+)", re.M)
values = {}
for name, camera, target, clip in pattern.findall(
log.read_text(encoding="utf-8", errors="replace")):
parse = lambda value: tuple(float(part) for part in value.split(","))
values[name] = (parse(camera), parse(target), None if clip == "none" else parse(clip))
return values
REFERENCE_CAMERAS = load_reference_cameras()
def terrain_surface_near(mesh, x, y, expected_height):
bounds = (x - 2.0, x + 2.0, y - 2.0, y + 2.0)
_, triangles, _ = ring_profile.read_scene(
str(mesh), None, -1.0e9, 1.0e9, bounds)
buckets = ring_profile.build_buckets(triangles, True, bounds, cell_size=1.0)
hits = []
for dy in range(-4, 5):
for dx in range(-4, 5):
sample_x, sample_y = x + dx * 0.5, y + dy * 0.5
hit = ring_profile.sample_surface(buckets, sample_x, sample_y, True, 1.0,
preferred_height=expected_height)
if hit is not None:
hits.append((math.hypot(dx * 0.5, dy * 0.5),
abs(hit[0] - expected_height), sample_x, sample_y, hit[0]))
if not hits:
return None
distance, _, sample_x, sample_y, height = min(hits)
if distance > 2.0 or abs(height - expected_height) > 8.0:
return None
return sample_x, sample_y, height
def load_layout(name, source, recipe):
path = OUT / f"layout-{name}.json"
run(f"layout-{name}", [HEADLESS, source, "--workers=12", "--world-assembler-prototype",
"--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"))
def select_passage(layout, mouth_generator, preferred_side):
strata = {int(value["strate_index"]): value for value in layout["strates"]}
preferred_candidates = []
fallback_candidates = []
for passage in layout["passages"]:
upper = strata[int(passage["upper_strate_index"])]
lower = strata[int(passage["lower_strate_index"])]
sides = []
if upper["generator_kind"] == mouth_generator:
sides.append("upper")
if lower["generator_kind"] == mouth_generator:
sides.append("lower")
if preferred_side in sides:
preferred_candidates.append((passage, preferred_side == "lower"))
elif sides:
fallback_candidates.append((passage, sides[0] == "lower"))
candidates = preferred_candidates or fallback_candidates
if not candidates:
raise RuntimeError(f"no passage mouth in generator {mouth_generator}")
return sorted(candidates, key=lambda item: item[0]["stable_id"])[0]
def make_views(layout, passage, reverse):
q_to_m = layout["core_voxel_size_um"] / (SUBVOXELS_PER_VOXEL * 1_000_000.0)
controls = [
(tuple(value * q_to_m for value in point["position_q"]),
point["radius_q"] * q_to_m)
for point in passage["control_points"]
]
lower_mouth = tuple(value * q_to_m for value in passage["lower_mouth_q"])
lower_index = min(range(len(controls)), key=lambda i: sum(
(controls[i][0][axis] - lower_mouth[axis]) ** 2 for axis in range(3)))
path = controls[:lower_index + 1]
if len(path) < 2:
raise RuntimeError(f"{passage['stable_id']} has no mouth-to-mouth route")
if reverse:
path.reverse()
lengths = [math.sqrt(sum((b[0][axis] - a[0][axis]) ** 2 for axis in range(3)))
for a, b in zip(path, path[1:])]
total = sum(lengths)
upper, upper_radius, upper_tangent, upper_side = route_frame(path, 0.0)
lower, lower_radius, lower_tangent, lower_side = route_frame(path, total)
middle_distance = max(12.0, min(total - 12.0, total * 0.4)) if total > 24.0 else total * 0.4
entry_distance = min(6.0, total * 0.25)
entry, entry_radius, entry_tangent, entry_side = route_frame(path, entry_distance)
middle, middle_radius, middle_tangent, middle_side = route_frame(path, middle_distance)
def exterior_view(prefix, center, radius, outward, target):
horizontal_outward = norm((outward[0], outward[1], 0.0))
eye_height = center[2] - radius + 1.6
eye = (center[0] + horizontal_outward[0] * 5.5,
center[1] + horizontal_outward[1] * 5.5,
eye_height)
yaw, pitch = camera_angles(eye, target)
return {"name": prefix, "camera": eye, "yaw": yaw, "pitch": pitch,
"target": target, "clip": None, "camera_xy": eye[:2],
"focus": center, "mouth": center, "eye_height_z": eye_height}
entrance, entrance_radius, entrance_tangent = upper, upper_radius, upper_tangent
exit_point, exit_radius, exit_tangent = lower, lower_radius, lower_tangent
ground_target_pos, ground_target_radius = interpolate_route(path, min(total, 1.0))
ground = exterior_view("ground-mouth", entrance, entrance_radius,
mul(entrance_tangent, -1.0), (ground_target_pos[0], ground_target_pos[1],
ground_target_pos[2] - ground_target_radius + 1.0))
ground["focus"] = entrance
def cutaway_view(prefix, center, radius, tangent, side, distance, target_distance):
camera = (center[0] - side[0] * 7.0, center[1] - side[1] * 7.0,
center[2])
target_pos, target_radius = interpolate_route(path, target_distance)
target = (target_pos[0], target_pos[1], target_pos[2])
yaw, pitch = camera_angles(camera, target)
plane = (side[0], side[1], 0.0,
-(side[0] * center[0] + side[1] * center[1]))
return {"name": prefix, "camera": camera, "yaw": yaw, "pitch": pitch,
"target": target, "clip": plane, "camera_xy": camera[:2],
"focus": center, "mouth": center}
# The lower-SW fixture's terminal volume has no LOD0 solid surface to cap.
# Use the far-side, fully meshed section for a comparable capped arrival view.
if passage["stable_id"] == "passage/1-2/0":
arrival_distance = total * 0.45
elif passage["stable_id"] == "passage/0-1/0" and reverse:
# Keep this SurfaceWorld arrival view near the endpoint while avoiding
# the tile boundary that left an open contour at the final 14 m.
arrival_distance = total * 0.80
else:
arrival_distance = max(0.0, total - 14.0)
arrival_center, arrival_radius, arrival_tangent, arrival_side = route_frame(
path, arrival_distance)
arrival = cutaway_view("arrival", arrival_center, arrival_radius,
arrival_tangent, arrival_side, arrival_distance,
min(total, arrival_distance + 12.0))
arrival_camera, _ = interpolate_route(path, max(0.0, arrival_distance - 6.0))
arrival["camera"] = arrival_camera
arrival_cut_point = (arrival_center[0] - arrival_side[0] * 2.5,
arrival_center[1] - arrival_side[1] * 2.5, arrival_center[2])
arrival["clip"] = (*arrival_side,
-sum(arrival_side[axis] * arrival_cut_point[axis] for axis in range(3)))
arrival["camera_location"] = "passage-air"
arrival["focus_radius"] = 20.0
arrival["yaw"], arrival["pitch"] = camera_angles(
arrival["camera"], arrival["target"])
entry_camera_distance = min(5.5, total * 0.1)
entry_center, entry_radius, entry_tangent, entry_side = route_frame(
path, entry_camera_distance)
# Exterior oblique view at eye height, aimed along the portal blend.
outward_horizontal = norm((-upper_tangent[0], -upper_tangent[1], 0.0))
entry_camera = (entrance[0] + outward_horizontal[0] * 4.5 - upper_side[0] * 2.5,
entrance[1] + outward_horizontal[1] * 4.5 - upper_side[1] * 2.5,
entrance[2] - upper_radius + 1.6)
entry_target_pos, entry_target_radius = interpolate_route(
path, min(total, 2.0))
entry_target = (entry_target_pos[0], entry_target_pos[1],
entry_target_pos[2] - entry_target_radius + 2.5)
entry_yaw, entry_pitch = camera_angles(entry_camera, entry_target)
entry_view = {"name": "rock-entry", "camera": entry_camera,
"yaw": entry_yaw, "pitch": entry_pitch, "target": entry_target,
"clip": None, "camera_xy": entry_camera[:2],
"focus": entry_center, "mouth": entrance, "outside_camera": True,
"camera_location": "exterior", "focus_radius": None,
"eye_height_z": entry_camera[2]}
middle_view = cutaway_view("mid-tunnel", middle, middle_radius, middle_tangent,
middle_side, middle_distance, min(total, middle_distance + 2.0))
ground["focus_radius"] = 20.0
middle_view["focus_radius"] = 20.0
middle_camera, _ = interpolate_route(path, max(0.0, middle_distance - 6.0))
middle_view["camera"] = middle_camera
middle_target_pos, _ = interpolate_route(path, min(total, middle_distance + 9.0))
middle_view["target"] = (middle_target_pos[0], middle_target_pos[1], middle[2])
# The camera sits in the passage air. Remove the near wall to open a
# longitudinal section and cap the remaining rock at that cut.
middle_cut_point = (middle[0] - middle_side[0] * 2.5,
middle[1] - middle_side[1] * 2.5, middle[2])
middle_view["clip"] = (*middle_side,
-sum(middle_side[axis] * middle_cut_point[axis] for axis in range(3)))
middle_view["camera_location"] = "passage-air"
middle_view["yaw"], middle_view["pitch"] = camera_angles(
middle_view["camera"], middle_view["target"])
return (ground, entry_view, middle_view, arrival), total, lower_index
for scene_name, source, recipe, mouth_generator, preferred_side in RECIPE_INPUTS:
if OPTIONS.scene and scene_name != OPTIONS.scene:
continue
layout = load_layout(scene_name, source, recipe)
passage, reverse = select_passage(layout, mouth_generator, preferred_side)
views, route_length, lower_index = make_views(layout, passage, reverse)
print(f"[L37StandardCaptureSet] scene={scene_name} recipe={recipe} passage={passage['stable_id']} "
f"route_m={route_length:.3f} control_points={lower_index + 1} fov_deg=80 "
"ground_camera_outside_m=5.500 entry_camera_outside_m=5.148 camera_eye_m=1.600", flush=True)
for view in views:
if OPTIONS.view and view["name"] != OPTIONS.view:
continue
stem = f"{scene_name}-{view['name']}"
mesh = OUT / f"{stem}.vfmesh"
png = PNG_DIR / f"{stem}.png"
jpg = CAPTURES / f"{stem}.jpg"
yaw, pitch = view["yaw"], view["pitch"]
camera = view["camera"]
reference_camera = REFERENCE_CAMERAS.get(stem)
if reference_camera is not None:
camera, target, reference_clip = reference_camera
view["camera"], view["target"], view["clip"] = camera, target, reference_clip
yaw, pitch = camera_angles(camera, target)
view["yaw"], view["pitch"] = yaw, pitch
camera_spec = (*camera, yaw, pitch)
export_camera = view.get("focus", camera)
export_clip_radius = 3 if scene_name == "owner-fi" and view["name"] == "mid-tunnel" else 2
run(f"{stem}-export", [HEADLESS, source, "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", f"--clip-radius={export_clip_radius}",
"--max-clip-level=0", "--max-level=0",
"--render-distance-chunks=0", f"--recipe={recipe}",
f"--camera={fmt((*export_camera, yaw, pitch))}", f"--mesh-export={mesh}"])
render_clip = view["clip"]
if reference_camera is not None:
surface = None
ground_source = "L34-fixed-camera"
elif view.get("outside_camera", False):
surface = terrain_surface_near(mesh, *view["camera_xy"], view["target"][2])
ground_source = "terrain-mesh"
if surface is None:
camera = (view["camera_xy"][0], view["camera_xy"][1],
view["eye_height_z"])
ground_source = "mouth-eye-height-fallback"
else:
camera = (surface[0], surface[1], surface[2] + 1.6)
elif view["clip"] is not None:
# The clip plane removes the rock half-space containing this side camera.
# Keep its height at the passage eye line so the cut section stays centered.
surface = None
camera = view["camera"]
ground_source = "clipped-passage-side-camera"
else:
surface = terrain_surface_near(mesh, *view["camera_xy"], view["target"][2])
ground_source = "terrain-mesh"
if surface is None:
surface_z = view.get("eye_height_z", view["target"][2] + 0.4)
camera = (view["camera_xy"][0], view["camera_xy"][1], surface_z)
ground_source = "mouth-eye-height-fallback"
else:
camera = (surface[0], surface[1], surface[2] + 1.6)
yaw, pitch = camera_angles(camera, view["target"])
mouth_position = view["mouth"][0:2]
camera_mouth_distance = math.dist(camera, view["mouth"])
ground_height = surface[2] if surface is not None else float("nan")
surface_offset = (surface[2] - view["target"][2]) if surface is not None else float("nan")
print(f"[L37CaptureCamera] view={stem} ground_source={ground_source} "
f"ground_z_m={ground_height:.3f} "
f"surface_target_delta_m={surface_offset:.3f} "
f"eye_z_m={camera[2]:.3f} "
f"camera_location={view.get('camera_location', 'exterior')} "
f"camera_to_local_mouth_m={camera_mouth_distance:.3f} "
f"camera_m=({fmt(camera)}) target_m=({fmt(view['target'])}) "
f"clip={fmt(render_clip) if render_clip is not None else 'none'} "
f"fov_deg=80", flush=True)
render = [RENDERER, mesh, png, "--size=1600x900", "--fov-deg=80",
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", f"--height-center-m={view['target'][2]:.6f}",
"--height-range-m=8", "--tile-edges=off"]
if view.get("focus_radius", 24.0) is not None:
render.append(f"--focus-region={fmt((*view['focus'], view.get('focus_radius', 24.0)))}")
if render_clip is not None:
render.append(f"--clip-plane={fmt(render_clip)}")
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])
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)
if not 0 < size <= 300_000:
raise SystemExit(f"{jpg.name} is missing or exceeds 300 KB")
@@ -0,0 +1,58 @@
# L37 report
## Result
The owner accepted the FI crater mouth as captured, so I kept its shape. The raised crossing came from the FI surface route being lifted above the crater by its capsule-footing/grade envelope; it was not the short-span bridge deck. Moving the route onto the crater rim and making the style-0 path follow the native surface removed the raised crossing while keeping the walk route supported. Passage, capsule, support and open-air audits pass from arrival to departure.
The signed carve-fade fix remains: the composed field blends toward the full signed carve distance by the fade weight. The unfinished 0.50 m relief-wavelength trial was rejected; the prior 0.75 m wavelength remains.
## Visual audits
The built-shape thresholds were fixed from player scale: straight run 0.75 m (just above the 0.68 m capsule width), planar patch 0.50 m², corner turn 30°, and plane tolerance 0.02 m. The planar metrics remain in the output, but the owner accepted this FI appearance and the planar gate is informational for this round.
- FI: added planar patch 2.0098 m², added straight run 1.0000 m, zero sharp corners. The capture has no straight raised bridge edge, but the straight-run proxy is over its 0.75 m gate; the shape audit therefore still flags FI on that metric.
- SW: no changed surface area in the matched route corridor; passes.
- Upper-SW fixture: added planar patch 0.3319 m², straight run 0.7500 m, zero corners; passes (`0.75 m` is not longer than the limit).
- Negative controls: L36 FI fails (4.8476 m², 1.4142 m); the 3×2 m box fails (6 m², 3 m, four corners). Both are detected.
The crease gate was frozen at 20° before the fix. L35 (42.27°) and L36 (27.13°) fail as controls. L37 is 4.40° at the 6.016 m close camera. In the 17.001 m overview it reaches 25.54° across 0.125 m, a 14.34 px projected interval at 2560×1440, so the numerical overview gate fails. A temporary v208 support-off build reproduces the same pair; the tread/support is not its source. The extra overview shows no continuous bridge seam, and I left the owner-approved crater unchanged. The FI fragment check finds zero detached components.
## Verification
- Passage audits: 24/24 pass across FI styles 0–3, SW styles 0–3, and upper-SW fixture styles 0–3, with Clang and GCC. Recipe stamps, mouth profiles, wall/floor ripple, strict validation, walkability, support, capsule, open air, and route bindings pass.
- Invariants: 16/16. Legacy L13/L9: 4/4. Interface/hole and ring audits: 18/18. L30 terrace and L31 flat controls returned the expected nonzero result and were detected.
- Determinism: 30/30, including L25 at W1/W8/W12 with Clang/GCC for both recipes, the 24 m crossing, and the upper-SW fixture.
- Cost: five interleaved pairs per recipe against L36. Median whole-view change: +0.63% SW and +2.04% FI; paired MAD was 1.93% and 5.71%, respectively. No regression beyond run noise.
- Captures use the standard L34 cameras, plus the 17.001 m FI overview. All 13 JPEGs are under 300 KB in `captures/`.
## Version and hashes
Evaluator version is 208 (bumped from 207). Stable hashes: recipe `c74a93d3e274e217`; owner plan `a5e29887e839c5d6`; owner manifest `8482dd0287ee2e9f`; FI field recipe `2096bd6fcf4d498d`. Upper-SW fixture layout ID is `resolved-layout-v3-fnv64:fca86f7273c53410` (plan `463a0f3b72909656`, manifest `98110083aff9ef5d`).
Final binary SHA-256: Clang `d69a1e0dc3275b4fa63963326c44588586fb7adc7db7a85e9c773ba9d9aa1e7d`; GCC `d75d282c5683584803a9b4adb2655aa0e66192de648f20215ec8702363b11c88`. FI mesh SHA-256: `280d238f3111f8a26348591600853fe89fe72c1ec1b250ce9d00ec7185543115`.
## Source edits
Core changes are in `VoxelForgeCore.h`, `FloatingIslandsInternal.h`, `ResolvedLayoutInternal.h`, `ShapeWorldInternal.h`, and `WorldAssemblyPrototype.h`. The only Unreal edit is the `terrainFollowingRoute` recipe setter in `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`; it could not be compiled on this Linux host. No invariants were changed.
## Failed and retried runs
- v140–153: deck-off was byte-identical but failed rock columns; v141/143 offsets left 25 unsupported samples; v142 made an isolated island; v144 left shards; v145 made holes/slots (12 support, 2 open-air failures); v146 left the bridge and 15 support failures; v147 grade rejected; v148 detached spiral blocked 43 samples; v149 removed bridge but blocked 48 and missed 3 supports; v150 pillar and v151 horn each missed 3 supports; v152 had no effect and was reverted; v153 was interrupted, retried, then its 1 m sink failed ripple/support.
- v154–180: route/ripple/support failures continued; v157 floor ripple was 3.09 cm; v158 exposed an arch; v159 passed capsule but failed ripple; v160 missed support with bridge visible; v161 taper failed ripple; v162/163 disabled blend and left 8/16 unsupported; v164 left a fin; v165/167/168 left the bridge; v166 had a compile error, was corrected/rebuilt, then had 18 unsupported samples; v169 sink removed horns but left 4 misses; v170/171 gates were inert; v172 left an arch; v173 removed the fin but missed 4 supports; v174 recovered support but retained a fin; v175 changed nothing; v176 failed wall/floor ripple and capsule; v177 blocked 103 and failed ripple; v178–180 sink variants left a fin or support misses. The v180 build was interrupted twice (exit 130) before the successful retry.
- v181–188: v181 route sag left 2 unsupported; v182 connector removal exited 2 on capsule-width support; v183 rounded joins passed route checks but ripple was 1.91 cm; v184 first compile failed on a qualifier, then rebuilt but ripple still failed; v185 removed the bridge but ripple stayed 1.91 cm; v186 support smoothing had no ripple gain; v187 wall-relief trial had no effect and was reverted; v188 first compile failed on `RoundQ`, fixed with `std::llround`; v188 then passed style-0 route/capsule/open-air and reduced floor ripple to 1.39 cm.
- v189–201: v189–192 had open-air, style-2 blocked-sample, or ripple failures; v193 fixed style-2 with a 1 m underfloor/fade but style-0 ripple was 1.56 cm; v194 native blending did not help the other styles; v195 passed style 0 but not styles 1–3; v196’s style-gated curve caused 175/186/136 open-air misses; v197’s 0.50 m relief wavelength failed and was restored to 0.75 m; v198 amplitude reduction had no benefit and was reverted; v199 surface-height sampling left style failures; v200’s 2 m arc had 1.80 cm floor ripple; v201’s 2.5 m arc passed style 0 but not styles 1–3.
- v202–207: style-0-only route/filter/tread variants still failed other styles: v202 had 175/186/136 open-air misses; v203 restricted tread and worsened open-air; v204’s 2 m nonzero-style curve had 92 misses; v205 had 77/94/36 misses; v206 fixed open air but styles 2/3 failed ripple; v207 fixed style 2 but style 1 floor ripple was 2.00 cm and style 3 was 3.19 cm. v208 passed the full passage matrix.
- The mistaken `RunPassageAudits --help` invocation ran a case and timed out at 30 s (exit 124). A PIL probe failed with `ModuleNotFoundError`; nothing was installed. The final shape audit still flags FI’s 1.00 m straight-run proxy, and the final crease audit still flags the 17 m overview at 20°. The temporary support-off diagnostic reproduced that crease; its source edit was restored before final verification. Ring negative-control binaries exited 1 as expected and were detected.
## Orchestrator review (Claude)
- Accepted, with the owner's verdict on the crater look. The carve-fade bug fix is real and general. The raised
bridge is gone in the style-0 capture, and the full matrix, determinism and controls pass.
- **Captures since L34 labelled "owner-fi"/"owner-sw" use the style-0 override fixtures.** The owner world itself
uses **Style 2 (Spiral) on FI and Style 1 on SW**. So the owner judged a style he doesn't currently use, and the
route change here (`terrainFollowingRoute = style == 0`) does **not** apply to his real FI passage. My brief said
"owner world"; I didn't catch the fixture choice until now. L38 captures the real styles.
- The fix is **style-gated** (many `style==0 && anchoredIslandPassage && !anchoredSpiral` branches). That breaks the
shared-layer rule. L38 replaces it with one general mechanism (Research1 proposal 1: floor-limited carve).
- The evaluator version jumped 23 → 208 (one bump per trial) and left `>= 192`-style gates in the code. Harmless for
determinism, but it's clutter; L38 should collapse the trial-only gates.
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""Run five interleaved whole-view cost pairs against the L36 Clang binary."""
import csv
import math
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l37"
BINARIES = {
"l36": ROOT / "Saved/Linux/l36/bin/VoxelForgeCoreHeadless-l36-final-clang",
"l37": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
}
ORDERS = (("l36", "l37"), ("l37", "l36"), ("l36", "l37"),
("l37", "l36"), ("l36", "l37"))
READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
STAMP = re.compile(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=PASS$", re.M)
rows = []
failed = False
for recipe in ("surface-world", "floating-islands"):
for pair, order in enumerate(ORDERS, 1):
for build in order:
name = f"cost-{recipe}-pair{pair}-{build}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s", str(BINARIES[build]),
str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
]
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")
matches = READY.findall(output)
stamps = STAMP.findall(output)
stamp_ok = stamps == [(recipe, recipe)]
valid = (result is not None and result.returncode == 0 and len(matches) == 1
and "hash=off" in output and "cache_hits=0" in output and stamp_ok)
ready = float(matches[0]) if len(matches) == 1 else math.nan
code = result.returncode if result is not None else "TIMEOUT"
rows.append((recipe, pair, build, ready, elapsed, code, valid))
failed |= not valid
print(f"[L37CostRun] recipe={recipe} pair={pair} build={build} "
f"whole_view_ready={ready:.3f}s process_elapsed={elapsed:.1f}s "
f"{'PASS' if valid else 'FAIL'} log={logfile}", flush=True)
summary = OUT / "cost-l37.csv"
with summary.open("w", newline="", encoding="utf-8") as stream:
writer = csv.writer(stream)
writer.writerow(("recipe", "pair", "build", "whole_view_ready_seconds",
"process_elapsed_seconds", "exit_code", "status"))
writer.writerows((recipe, pair, build, f"{ready:.6f}", f"{elapsed:.3f}", code,
"PASS" if valid else "FAIL")
for recipe, pair, build, ready, elapsed, code, valid in rows)
for recipe in ("surface-world", "floating-islands"):
by_build = {build: [row[3] for row in rows if row[0] == recipe and row[2] == build]
for build in BINARIES}
baseline = statistics.median(by_build["l36"])
current = statistics.median(by_build["l37"])
paired = []
for pair in range(1, 6):
values = {row[2]: row[3] for row in rows if row[0] == recipe and row[1] == pair}
paired.append((values["l37"] - values["l36"]) / values["l36"] * 100.0)
mad = statistics.median(abs(value - statistics.median(paired)) for value in paired)
print(f"[L37CostSummary] recipe={recipe} pairs=5 median_l36_s={baseline:.3f} "
f"median_l37_s={current:.3f} median_change_pct={(current/baseline-1)*100.0:+.2f} "
f"paired_change_pct={','.join(f'{value:+.2f}' for value in paired)} "
f"paired_mad_pct={mad:.2f}", flush=True)
print(f"[L37Cost] runs={len(rows)} status={'FAIL' if failed else 'PASS'} summary={summary}",
flush=True)
raise SystemExit(1 if failed or len(rows) != 20 else 0)
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Check L25, 24 m crossing and upper-SW fixture determinism for L37."""
import csv
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l37"
OWNER = ROOT / "Standalone/data/owner_world.json"
FIXTURE = ROOT / "Saved/Linux/l33/fixtures/sw-upper-tunnel-network-style0.json"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l37-final-gcc",
}
HASH = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
STAMP = re.compile(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$", re.M)
LAYOUT = re.compile(
r"^\[ResolvedLayoutSnapshot\] schema=\d+ stable_identity=([^ ]+) strata=\d+ bytes=\d+ round_trip=PASS$",
re.M)
PLAN = re.compile(
r"^\[WorldAssemblerPlanSerialization\].*canonical_round_trip=true .*"
r"plan_hash=([0-9a-f]+) manifest_hash=([0-9a-f]+) status=PASS$", re.M)
ROUTE = re.compile(
r"^\[WorldAssemblerRouteAccepted\] arrival_xy_m=\([^)]*\) "
r"departure_xy_m=\([^)]*\) endpoint_proximity_cost=\d+ average_grade_q16=\d+$", re.M)
rows = []
failed = False
references = {}
def run(kind, recipe, compiler, workers, data, passage_audit=False, crossing=False):
global failed
binary = BINARIES[compiler]
name = f"determinism-{kind}-{compiler}-{recipe}-w{workers}"
if kind == "upper-fixture":
name = f"determinism-upper-fixture-{compiler}-w{workers}"
log = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", str(binary), str(data), f"--workers={workers}",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}",
]
if passage_audit:
command.append("--audit-resolved-layout-passages")
else:
command.append("--stream-hash")
if crossing:
command.extend(("--camera-step=24,0,0", "--region-crossing"))
started = time.monotonic()
try:
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = log.read_text(encoding="utf-8", errors="replace")
stamps = STAMP.findall(output)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
if passage_audit:
signature = (tuple(LAYOUT.findall(output)), tuple(PLAN.findall(output)),
tuple(ROUTE.findall(output)))
expected = references.get((kind, recipe))
stable = expected is None or signature == expected
if expected is None and all(signature):
references[(kind, recipe)] = signature
valid = (result is not None and result.returncode == 0 and stamp_ok
and len(signature[0]) == len(signature[1]) == len(signature[2]) == 1
and re.search(r"^\[ResolvedPassageAuditSummary\].*status=PASS$", output, re.M)
and re.search(r"^\[PassageRippleAuditSummary\].*status=PASS$", output, re.M)
and stable)
hashes = ()
detail = f"layout={signature[0][0] if signature[0] else 'MISSING'} stable={stable}"
else:
hashes = tuple(HASH.findall(output))
key = (kind, recipe)
expected = references.get(key)
stable = expected is None or hashes == expected
if expected is None and hashes:
references[key] = hashes
valid = (result is not None and result.returncode == 0 and stamp_ok
and "hash=on" in output and len(hashes) == 12 and stable)
detail = f"hashes={len(hashes)}/12 stable={stable}"
code = result.returncode if result is not None else "TIMEOUT"
rows.append((kind, recipe, compiler, workers, elapsed, code, valid))
failed |= not valid
print(f"[L37DeterminismRun] kind={kind} recipe={recipe} compiler={compiler} "
f"workers={workers} elapsed={elapsed:.1f}s {detail} "
f"{'PASS' if valid else 'FAIL'} log={log}", flush=True)
for kind in ("l25", "crossing24"):
for recipe in ("surface-world", "floating-islands"):
for compiler in BINARIES:
for workers in (1, 8, 12):
run(kind, recipe, compiler, workers, OWNER,
crossing=(kind == "crossing24"))
for compiler in BINARIES:
for workers in (1, 8, 12):
run("upper-fixture", "floating-islands", compiler, workers, FIXTURE,
passage_audit=True)
summary = OUT / "determinism-l37.csv"
with summary.open("w", newline="", encoding="utf-8") as stream:
writer = csv.writer(stream)
writer.writerow(("kind", "recipe", "compiler", "workers", "elapsed_seconds",
"exit_code", "status"))
for row in rows:
writer.writerow((*row[:-1], "PASS" if row[-1] else "FAIL"))
print(f"[L37DeterminismSummary] 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) != 30 else 0)
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Run L33 route/proof and transition invariants with recipe-stamped logs."""
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",
}
rows = []
failed = False
def run(name, binary, data, recipe, extra, checks):
global failed
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s",
str(binary), str(data), "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}", *extra,
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, 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)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
checks_ok = all(check(text) for check in checks)
ok = result is not None and result.returncode == 0 and stamp_ok and checks_ok
rows.append((name, recipe, elapsed, result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
f"stamp={'PASS' if stamp_ok else 'FAIL'} checks={'PASS' if checks_ok else 'FAIL'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
failed |= not ok
owner = ROOT / "Standalone/data/owner_world.json"
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
name = f"invariant-route-proof-{recipe}-{compiler}"
run(name, binary, owner, recipe,
["--stream-hash", "--audit-uniform-block-proofs", "--audit-route-parity"],
[lambda text: bool(re.search(
r"^\[RouteParityAudit\].*status=PASS$", text, re.M)),
lambda text: bool(re.search(
r"^\[UniformBlockProofAudit\].*violations=0(?:\s|$)", text, re.M))])
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
for mode in ("hard", "gradient", "interleaved"):
name = f"invariant-transition-{recipe}-{mode}-{compiler}"
run(name, binary, owner, recipe,
[f"--transition-mode={mode}", "--audit-strate-transitions"],
[lambda text: bool(re.search(
r"^\[StrateTransitionAuditSummary\].*hole_columns=0 air_samples=0 .*status=PASS$",
text, re.M)),
lambda text: bool(re.search(
r"^\[StrateTransitionNegativeControls\].*status=PASS$", text, re.M))])
summary = OUT / "invariant-audits.csv"
summary.write_text(
"run,recipe,elapsed_seconds,exit_code,status\n" +
"".join(f"{name},{recipe},{elapsed:.3f},{code},{'PASS' if ok else 'FAIL'}\n"
for name, recipe, elapsed, code, ok in rows),
encoding="utf-8")
print(f"[L33InvariantAudits] 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) != 16 else 0)
@@ -0,0 +1,69 @@
#!/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)
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""Freeze a 20-degree mouth-crease gate and project its sample spacing at 1440p."""
import ast
import contextlib
import importlib.util
import io
import math
import re
import argparse
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
BASE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37/RunMouthFragmentAuditL37.py"
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
CAMERA = (1.691820, -3.424982, -54.224198)
TARGET = (6.298233, 1.137233, -55.468264)
FOV_DEG = 80.0
WIDTH_1440P = 2560
HEIGHT_1440P = 1440
MOUTH = (5.600, 0.445, -55.156)
OVERVIEW_CAMERA = (MOUTH[0] - 9.75, MOUTH[1] - 11.4, MOUTH[2] + 8.0)
def normalize(vector):
magnitude = math.sqrt(sum(value * value for value in vector))
return tuple(value / magnitude for value in vector)
def view_basis(camera, target):
forward = normalize(tuple(target[i] - camera[i] for i in range(3)))
right = normalize((forward[1], -forward[0], 0.0))
up = (right[1] * forward[2] - right[2] * forward[1],
right[2] * forward[0] - right[0] * forward[2],
right[0] * forward[1] - right[1] * forward[0])
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)
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)
tan_h = math.tan(math.radians(FOV_DEG / 2.0))
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)
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 index in range(count + 1))])
tangent_count = round(8.0 / BASE.STEP)
for degrees in (225.0, 315.0):
angle = math.radians(degrees)
normal = (math.cos(angle), math.sin(angle))
tangent = (-normal[1], normal[0])
lines.append([(cx + 6.5 * normal[0] + (-4.0 + index * BASE.STEP) * tangent[0],
cy + 6.5 * normal[1] + (-4.0 + index * BASE.STEP) * tangent[1])
for index in range(tangent_count + 1)])
maximum, largest_pixels, worst_depth, visible_pairs = 0.0, 0.0, 0.0, 0
worst_pair_pixels = 0.0
worst_pair = None
def project(point):
offset = tuple(point[i] - camera[i] for i in range(3))
depth = sum(offset[i] * forward[i] for i in range(3))
x = sum(offset[i] * right[i] for i in range(3))
y = sum(offset[i] * up[i] for i in range(3))
return depth, x, y
for points in lines:
samples = [BASE.RING.sample_surface(buckets, x, y, True, cell, BASE.PREFERRED)
for x, y in points]
for index in range(1, len(samples)):
before, current = samples[index - 1], samples[index]
if before is None or current is None:
continue
p0 = (points[index - 1][0], points[index - 1][1], before[0])
p1 = (points[index][0], points[index][1], current[0])
midpoint = tuple((p0[axis] + p1[axis]) * 0.5 for axis in range(3))
depth, horizontal, vertical = project(midpoint)
if (depth <= 0.0 or abs(horizontal) > depth * tan_h
or abs(vertical) > depth * tan_v):
continue
visible_pairs += 1
angle = math.degrees(math.acos(max(-1.0, min(1.0,
sum(a * b for a, b in zip(before[1], current[1]))))))
d0, x0, y0 = project(p0)
d1, x1, y1 = project(p1)
pixels = math.hypot(focal * x0 / d0 - focal * x1 / d1,
focal * y0 / d0 - focal * y1 / d1)
largest_pixels = max(largest_pixels, pixels)
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
def run(path, label):
stream = io.StringIO()
with contextlib.redirect_stdout(stream):
passed, maximum = BASE.audit(path)
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)
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_worst_pair_spacing_px={near[5]:.2f} "
f"overview_camera_to_mouth_m={math.dist(OVERVIEW_CAMERA, MOUTH):.3f} "
f"overview_visible_pairs={far[3]} overview_max_visible_deg={far[0]:.6f} "
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
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--candidate", type=Path,
default=ROOT / "Saved/Linux/l37/owner-fi-ground-mouth.vfmesh")
args = parser.parse_args()
paths = {
"L35": ROOT / "Saved/Linux/l35/owner-fi-ground-mouth.vfmesh",
"L36": ROOT / "Saved/Linux/l36/owner-fi-ground-mouth.vfmesh",
"L37": args.candidate if args.candidate.is_absolute()
else ROOT / args.candidate,
}
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 "
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"status={'PASS' if controls and final else 'FAIL'}")
raise SystemExit(0 if controls and final else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,176 @@
#!/usr/bin/env python3
"""Compare the FI fade-ring crease against L35 using the same surface samples."""
import argparse
import importlib.util
import math
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RING_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
SPEC = importlib.util.spec_from_file_location("ring_profile_l32", RING_PATH)
RING = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(RING)
CENTER = (6.298233, 1.137233)
INNER, OUTER, STEP = 5.5, 9.5, 0.125
HEIGHT_MIN, HEIGHT_MAX, PREFERRED = -80.0, -40.0, -56.0
# Chosen before the L37 mouth edit: a 20-degree normal jump over one 0.125 m
# sample interval is visible at the standard camera distance.
NORMAL_LIMIT_DEG = 20.0
COVERAGE_MIN = 0.90
FRAGMENT_MAX_TRIANGLES = 64
FRAGMENT_MIN_AREA_M2 = 0.0001
FRAGMENT_MAX_AREA_M2 = 0.05
FRAGMENT_MAX_DISTANCE_M = 12.0
def small_components(triangles):
parent = list(range(len(triangles)))
sizes = [1] * len(triangles)
owners = {}
def find(index):
while parent[index] != index:
parent[index] = parent[parent[index]]
index = parent[index]
return index
def union(a, b):
a, b = find(a), find(b)
if a == b:
return
if sizes[a] < sizes[b]:
a, b = b, a
parent[b] = a
sizes[a] += sizes[b]
for index, (_, _, vertices) in enumerate(triangles):
for point, _ in vertices:
key = tuple(round(value, 5) for value in point)
other = owners.setdefault(key, index)
union(index, other)
components = {}
for index, (_, _, vertices) in enumerate(triangles):
root = find(index)
item = components.setdefault(root, {"count": 0, "area": 0.0, "sum": [0.0] * 3})
item["count"] += 1
points = [vertex[0] for vertex in vertices]
for point in points:
for axis in range(3):
item["sum"][axis] += point[axis]
ab = tuple(points[1][axis] - points[0][axis] for axis in range(3))
ac = tuple(points[2][axis] - points[0][axis] for axis in range(3))
cross = (ab[1] * ac[2] - ab[2] * ac[1],
ab[2] * ac[0] - ab[0] * ac[2],
ab[0] * ac[1] - ab[1] * ac[0])
item["area"] += 0.5 * math.sqrt(sum(value * value for value in cross))
cx, cy = CENTER
small = []
below_floor = []
for item in components.values():
centroid = tuple(value / (3 * item["count"]) for value in item["sum"])
distance = math.hypot(centroid[0] - cx, centroid[1] - cy)
if (item["count"] <= FRAGMENT_MAX_TRIANGLES
and item["area"] <= FRAGMENT_MAX_AREA_M2
and distance <= FRAGMENT_MAX_DISTANCE_M):
record = (item["count"], item["area"], distance, centroid)
(below_floor if item["area"] < FRAGMENT_MIN_AREA_M2 else small).append(record)
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)
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},"
f"area={FRAGMENT_MIN_AREA_M2:.4f}..{FRAGMENT_MAX_AREA_M2:.3f}m2,"
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'}")
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)
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))]))
tangent_count = round(8.0 / STEP)
for degrees in (225.0, 315.0):
angle = math.radians(degrees)
normal = (math.cos(angle), math.sin(angle))
tangent = (-normal[1], normal[0])
lines.append((f"tangent-{degrees:.0f}", [
(cx + 6.5 * normal[0] + (-4.0 + i * STEP) * tangent[0],
cy + 6.5 * normal[1] + (-4.0 + i * STEP) * tangent[1])
for i in range(tangent_count + 1)]))
max_angle = 0.0
max_pair = None
min_coverage = 1.0
for name, points in lines:
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)
line_max = 0.0
for index in range(1, len(samples)):
prior, current = samples[index - 1], samples[index]
if prior is None or current is None:
continue
dot = max(-1.0, min(1.0, sum(a * b for a, b in zip(prior[1], current[1]))))
delta = math.degrees(math.acos(dot))
if delta > line_max:
line_max = delta
if delta > max_angle:
max_angle = delta
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
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"fragments={len(fragments)} "
f"worst_pair={max_pair} status={'PASS' if passed else 'FAIL'}")
return passed, max_angle
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--control", type=Path,
default=ROOT / "Saved/Linux/l35/owner-fi-ground-mouth.vfmesh")
parser.add_argument("--prior", type=Path,
default=ROOT / "Saved/Linux/l36/owner-fi-ground-mouth.vfmesh")
parser.add_argument("--current", type=Path,
default=ROOT / "Saved/Linux/l37/owner-fi-ground-mouth.vfmesh")
args = parser.parse_args()
control = args.control if args.control.is_absolute() else ROOT / args.control
prior = args.prior if args.prior.is_absolute() else ROOT / args.prior
current = args.current if args.current.is_absolute() else ROOT / args.current
control_ok, control_max = audit(control)
prior_ok, prior_max = audit(prior)
current_ok, current_max = audit(current)
controls = (not control_ok and not prior_ok
and control_max > NORMAL_LIMIT_DEG and prior_max > NORMAL_LIMIT_DEG)
expected = controls and current_ok and current_max <= NORMAL_LIMIT_DEG
print(f"[MouthFadeNegativeControl] L35_max_deg={control_max:.6f} "
f"L35_detected={not control_ok} L36_max_deg={prior_max:.6f} "
f"L36_detected={not prior_ok} L37_max_deg={current_max:.6f} "
f"normal_limit_deg={NORMAL_LIMIT_DEG:.1f} current_pass={current_ok} "
f"status={'PASS' if expected else 'FAIL'}")
raise SystemExit(0 if expected else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Run recipe-stamped passage/profile audits for both compilers and both owner recipes."""
import re
import os
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",
}
RUNS = []
for recipe, filename in (
("floating-islands", "floating-islands"),
("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))
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"))
RECIPE = re.compile(r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=PASS$", re.M)
PASSAGE = re.compile(r"^\[ResolvedPassageAudit\].*walkability=PASS$", re.M)
MOUTH = re.compile(r"^\[PassageMouthProfileAudit\].*status=PASS$", re.M)
ROUTE = re.compile(r"^\[ResolvedStrateRouteAudit\].*mouth_binding=PASS status=PASS$", re.M)
CHECKS = (
r"^\[ResolvedLayoutStrict\] accepted=true issues=0 error=$",
r"^\[ResolvedLayoutValidatorControls\].*status=PASS$",
r"^\[PassageMouthProfileNegativeControl\].*source=L23_terraced_geometry.*detected=true$",
r"^\[PassageMouthProfileSummary\].*l23_terrace_negative_control=DETECTED status=PASS$",
r"^\[PassageNaturalnessSummary\].*l23_negative_control=DETECTED.*status=PASS$",
r"^\[PassageRippleAuditSummary\].*synthetic_spheres_negative_control=DETECTED max_amplitude_m=0\.035 floor_beaded_negative_control=DETECTED max_floor_amplitude_m=0\.015 status=PASS$",
r"^\[PassageOpenAirAuditSummary\].*violations=0.*l24_negative_control=DETECTED status=PASS$",
r"^\[PassageNoLongBridgeAudit\].*status=PASS$",
r"^\[PassageRockColumnAuditSummary\].*status=PASS$",
r"^\[ResolvedPassageAuditSummary\].*route_binding_failures=0 .*status=PASS$",
r"^\[Walkability\] PASS",
r"^\[FloorProfileBlend\].*negative_controls=step_detected/slope_detected status=PASS$",
)
rows = []
failed = False
for compiler, binary in BINARIES.items():
for name, data, recipe in RUNS:
logfile = OUT / f"passage-audit-{name}-{compiler}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", str(binary), str(data), "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}", "--audit-resolved-layout-passages",
]
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=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = logfile.read_text(encoding="utf-8", errors="replace")
stamp_ok = RECIPE.findall(output) == [(recipe, recipe)]
passages = len(PASSAGE.findall(output))
mouths = len(MOUTH.findall(output))
routes = len(ROUTE.findall(output))
checks_ok = all(re.search(pattern, output, re.M) for pattern in CHECKS)
ok = (result is not None and result.returncode == 0 and stamp_ok
and passages >= 1 and mouths == passages and routes >= 1 and checks_ok)
code = result.returncode if result is not None else "TIMEOUT"
rows.append((name, recipe, compiler, elapsed, code, passages, mouths, routes, ok))
failed |= not ok
print(f"[L37PassageAuditRun] case={name} compiler={compiler} recipe={recipe} "
f"elapsed={elapsed:.1f}s exit={code} passages={passages} "
f"profiles={mouths} routes={routes} {'PASS' if ok else 'FAIL'} "
f"log={logfile}", flush=True)
summary = OUT / "passage-audits.csv"
summary.write_text(
"case,recipe,compiler,elapsed_seconds,exit_code,passages,mouth_profiles,routes,status\n" +
"".join(f"{name},{recipe},{compiler},{elapsed:.3f},{code},{passages},{mouths},{routes},"
f"{'PASS' if ok else 'FAIL'}\n"
for name, recipe, compiler, elapsed, code, passages, mouths, routes, ok in rows),
encoding="utf-8")
print(f"[L37PassageAuditSummary] 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)
@@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""Export v24 ring meshes at matching LOD windows and retain old controls."""
import csv
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l37"
BIN = 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"
STAMP = re.compile(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=PASS$", re.M)
EXPORT = re.compile(r"^\[MeshSceneExport\].*status=PASS", re.M)
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
FI_CENTERS = ((56, -72), (72, -72), (56, -88), (40, -72), (24, 24),
(104, 24), (72, -88), (120, 24), (104, 8), (24, -24))
SW_CENTERS = ((-136, 136), (-56, 152), (-152, 120), (-136, 120),
(-152, 104), (-136, 104), (-40, 152))
LOD_CENTERS = {
"fi-floor": {
2: (16, -144), 3: (224, -160), 4: (-448, 448),
5: (-128, -640), 6: (-1792, 1280), 7: (3584, 1536),
8: (3072, 7168),
},
"sw-ceiling": {
2: (16, -144), 3: (224, -160), 4: (-448, 448),
5: (-128, -640), 6: (-1792, 1280), 7: (2560, -512),
8: (3072, 7168),
},
}
def run(name, command):
log = OUT / f"ring-{name}.log"
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", *map(str, command)]
started = time.monotonic()
try:
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = log.read_text(encoding="utf-8", errors="replace")
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)
return result, output, elapsed
def export(label, recipe, max_level, camera):
mesh = OUT / f"ring-owner-{label}-v24.vfmesh"
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}")
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())
print(f"[L37RingExport] scene={label} recipe={recipe} stamp={stamps} "
f"bytes={mesh.stat().st_size if mesh.exists() else 0} "
f"status={'PASS' if ok else 'FAIL'}", flush=True)
return mesh, elapsed, ok
def terrace(label, recipe, surface, mesh, lod, height_min, height_max,
center=(64, -32), radius=32):
result, output, elapsed = run(f"{label}-lod{lod}", ["python3", TERRACE, mesh,
f"--surface={surface}", "--center", str(center[0]), str(center[1]),
f"--radius={radius}",
f"--lod={lod}", f"--height-min-m={height_min}",
f"--height-max-m={height_max}"])
statuses = TERRACE_RESULT.findall(output)
ok = (result is not None and result.returncode == 0 and statuses == ["PASS"])
rows.append((label, recipe, surface, lod, elapsed, ok))
print(f"[L37RingAuditRun] scene={label} recipe={recipe} surface={surface} "
f"lod={lod} center={center} radius={radius} "
f"status={'PASS' if ok else 'FAIL'}", flush=True)
return ok
def lod1_set(label, recipe, surface, mesh, centers, height_min, height_max):
command = ["python3", RING_SET, mesh, f"--surface={surface}"]
for x, y in centers:
command.extend(("--center", str(x), str(y)))
command.extend(("--radius=8", "--lod=1", f"--height-min-m={height_min}",
f"--height-max-m={height_max}"))
result, output, elapsed = run(f"{label}-lod1-set", command)
statuses = SET_RESULT.findall(output)
ok = (result is not None and result.returncode == 0 and statuses == ["PASS"])
rows.append((label, recipe, surface, 1, elapsed, ok))
print(f"[L37RingAuditRun] scene={label} recipe={recipe} surface={surface} "
f"lod=1 windows={len(centers)} status={'PASS' if ok else 'FAIL'}",
flush=True)
return ok
for label, recipe, surface, window_min, window_max, camera0, camera1, centers in (
("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)):
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)
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 = (
("l30-terrace", ROOT / "Saved/Linux/l32/fi-floor-l30-camera-negative.vfmesh",
-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:
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}"])
statuses = TERRACE_RESULT.findall(output)
detected = (result is not None and result.returncode == 1 and statuses == ["FAIL"])
failed |= not detected
print(f"[L37RingControl] name={name} detected={detected} "
f"status={'PASS' if detected else 'FAIL'}", flush=True)
summary = OUT / "ring-audits-l37.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)
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"status={'FAIL' if failed else 'PASS'} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 18 else 0)
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB