tools(audit): whole-surface mesh artifact audit with historical controls (L48, tool only); research R15

AuditWholeSurfaceL48.py scans every LOD0 triangle in the route corridor + 3 m and the chamber box + 3 m:
welded-edge normal variation, high-angle edge density, max jumps and sliver area in 1 m cells, clustered
and located, with thresholds from native patches in the same export, plus a detrended full-width floor
profile. L45 ramp, L46 entrance and L47 wall-foot meshes fail it; plain native terrain passes. Red-triangle
SVG maps included. Not wired into the tiers yet; the attempted Core fix is parked on wip/l48-surface-audit.
Research R15: mesh artifact metrics.

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-24 14:53:11 +02:00
1 parent c091e4434f
commit 3073d1b2fd
16 files changed
+346656

No files matched your search

@@ -0,0 +1,709 @@
#!/usr/bin/env python3
"""Audit every LOD0 mesh triangle in a cavern corridor for clustered defects."""
from __future__ import annotations
import argparse
import array
import json
import math
from pathlib import Path
import struct
import sys
ROOT = Path(__file__).resolve().parents[4]
CELL_M = 1.0
CORRIDOR_MARGIN_M = 3.0
OPEN_GROUND_EXTENSION_M = 5.0
WELD_M = 0.0001
class Triangle:
__slots__ = ("verts", "center", "area", "normal", "smooth", "quality",
"route_distance", "route_lateral", "route_s", "floor_z",
"in_region", "reference")
def __init__(self, verts, area, normal, smooth, quality):
self.verts = verts
self.center = tuple(sum(v[i] for v in verts) / 3.0 for i in range(3))
self.area = area
self.normal = normal
self.smooth = smooth
self.quality = quality
self.route_distance = 0.0
self.route_lateral = 0.0
self.route_s = 0.0
self.floor_z = 0.0
self.in_region = False
self.reference = False
def normalize(value):
length = math.sqrt(sum(item * item for item in value))
return tuple(item / length for item in value) if length > 1.0e-12 else (0.0, 0.0, 0.0)
def dot(a, b):
return sum(a[i] * b[i] for i in range(3))
def angle_degrees(a, b):
return math.degrees(math.acos(max(-1.0, min(1.0, dot(a, b)))))
def quantile(values, fraction):
if not values:
return 0.0
ordered = sorted(values)
return ordered[min(len(ordered) - 1, max(0, int(math.ceil(fraction * len(ordered))) - 1))]
def read_mesh(path: Path):
triangles = []
selected_tiles = 0
lod_counts = {}
with path.open("rb") as stream:
prefix = stream.read(60)
if len(prefix) != 60 or prefix[:8] != b"VFMSH001":
raise ValueError(f"{path}: invalid VFMSH001 mesh header")
version, voxel_um, tile_count = struct.unpack_from("<3I", prefix, 8)
if version != 1 or not voxel_um or tile_count > 1_000_000:
raise ValueError(f"{path}: unsupported mesh version or tile count")
for _ in range(tile_count):
header = stream.read(36)
if len(header) != 36:
raise ValueError(f"{path}: truncated tile header")
key = struct.unpack_from("<4i", header, 0)
surface_count, _surface_aux, vertex_count, index_count, mask_count = \
struct.unpack_from("<5I", header, 16)
lod_counts[key[3]] = lod_counts.get(key[3], 0) + 1
position_bytes = stream.read(vertex_count * 12)
normal_bytes = stream.read(vertex_count * 12)
index_bytes = stream.read(index_count * 4)
masks = stream.read(mask_count)
if (len(position_bytes) != vertex_count * 12
or len(normal_bytes) != vertex_count * 12
or len(index_bytes) != index_count * 4
or len(masks) != mask_count):
raise ValueError(f"{path}: truncated tile payload")
if key[3] != 0:
continue
selected_tiles += 1
positions = array.array("f")
positions.frombytes(position_bytes)
normals = array.array("f")
normals.frombytes(normal_bytes)
indices = array.array("I")
indices.frombytes(index_bytes)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
for offset in range(0, index_count - 2, 3):
if masks and masks[offset // 3]:
continue
ids = indices[offset:offset + 3]
if max(ids) >= surface_count:
continue
verts = tuple(tuple(float(positions[index * 3 + axis]) for axis in range(3))
for index in ids)
edge_a = tuple(verts[1][axis] - verts[0][axis] for axis in range(3))
edge_b = tuple(verts[2][axis] - verts[0][axis] for axis in range(3))
cross = (edge_a[1] * edge_b[2] - edge_a[2] * edge_b[1],
edge_a[2] * edge_b[0] - edge_a[0] * edge_b[2],
edge_a[0] * edge_b[1] - edge_a[1] * edge_b[0])
cross_length = math.sqrt(dot(cross, cross))
if cross_length <= 1.0e-12:
continue
face_normal = tuple(value / cross_length for value in cross)
area = cross_length * 0.5
smooth_normal = normalize(tuple(
sum(float(normals[index * 3 + axis]) for index in ids) for axis in range(3)))
if dot(face_normal, smooth_normal) < 0.0:
face_normal = tuple(-value for value in face_normal)
edge_squares = 0.0
for a, b in ((verts[0], verts[1]), (verts[1], verts[2]),
(verts[2], verts[0])):
edge_squares += sum((a[i] - b[i]) ** 2 for i in range(3))
quality = 4.0 * math.sqrt(3.0) * area / max(edge_squares, 1.0e-15)
triangles.append(Triangle(verts, area, face_normal, smooth_normal, quality))
if stream.read(1):
raise ValueError(f"{path}: trailing bytes after mesh tiles")
if not triangles:
raise ValueError(f"{path}: no LOD0 surface triangles")
return triangles, voxel_um, selected_tiles, lod_counts
def bezier(points, t):
one = 1.0 - t
weights = (one ** 3, 3.0 * one * one * t, 3.0 * one * t * t, t ** 3)
return tuple(sum(weights[i] * points[i][axis] for i in range(4)) for axis in range(3))
def make_route(layout):
edges = layout.get("cavern_edges", [])
rooms = layout.get("cavern_rooms", [])
if len(edges) != 1 or len(rooms) != 1:
raise ValueError("cavern audit expects one resolved room and one edge in its layout")
edge = edges[0]
points = [[value / 1_000_000.0 for value in row]
for row in edge["control_points_um"]]
bore_radius = edge["bore_radius_um"] / 1_000_000.0
bore_height = edge["bore_height_um"] / 1_000_000.0
samples = [bezier(points, index / 160.0) for index in range(161)]
horizontal_s = [0.0]
for a, b in zip(samples, samples[1:]):
horizontal_s.append(horizontal_s[-1] + math.hypot(b[0] - a[0], b[1] - a[1]))
first_dx = samples[1][0] - samples[0][0]
first_dy = samples[1][1] - samples[0][1]
first_len = max(1.0e-9, math.hypot(first_dx, first_dy))
extension = (samples[0][0] - first_dx / first_len * OPEN_GROUND_EXTENSION_M,
samples[0][1] - first_dy / first_len * OPEN_GROUND_EXTENSION_M,
samples[0][2] - (samples[1][2] - samples[0][2]) / first_len
* OPEN_GROUND_EXTENSION_M)
route_points = [extension, *samples]
route_s = [-OPEN_GROUND_EXTENSION_M, *horizontal_s]
segments = []
for i, (a, b) in enumerate(zip(route_points, route_points[1:])):
segments.append((a, b, route_s[i], route_s[i + 1]))
room = rooms[0]
room_center = tuple(value / 1_000_000.0 for value in room["center_um"])
room_half = tuple(value / 1_000_000.0 for value in room["half_extents_um"])
room_floor = room["floor_um"] / 1_000_000.0
return {
"segments": segments,
"edge": edge,
"bore_radius": bore_radius,
"bore_height": bore_height,
"room_center": room_center,
"room_half": room_half,
"room_floor": room_floor,
"route_points": samples,
"route_length": horizontal_s[-1],
}
def route_nearest(point, route):
best = None
for a, b, s0, s1 in route["segments"]:
dx, dy = b[0] - a[0], b[1] - a[1]
denominator = dx * dx + dy * dy
t = 0.0 if denominator <= 1.0e-12 else max(0.0, min(1.0,
((point[0] - a[0]) * dx + (point[1] - a[1]) * dy) / denominator))
x, y = a[0] + dx * t, a[1] + dy * t
distance = math.hypot(point[0] - x, point[1] - y)
if best is None or distance < best[0]:
segment_length = math.sqrt(max(denominator, 1.0e-12))
lateral = (dx * (point[1] - y) - dy * (point[0] - x)) / segment_length
best = (distance, s0 + (s1 - s0) * t,
a[2] + (b[2] - a[2]) * t, lateral)
return best
def mark_region(triangles, route):
room_c = route["room_center"]
room_h = route["room_half"]
radius = route["bore_radius"] + CORRIDOR_MARGIN_M
for tri in triangles:
x, y, z = tri.center
distance, distance_s, floor_z, lateral = route_nearest(tri.center, route)
tri.route_distance = distance
tri.route_lateral = lateral
tri.route_s = distance_s
tri.floor_z = floor_z
corridor = (-OPEN_GROUND_EXTENSION_M <= distance_s <= route["route_length"]
and distance <= radius
and floor_z - CORRIDOR_MARGIN_M <= z
<= floor_z + route["bore_height"] + CORRIDOR_MARGIN_M)
room_box = (abs(x - room_c[0]) <= room_h[0] + CORRIDOR_MARGIN_M
and abs(y - room_c[1]) <= room_h[1] + CORRIDOR_MARGIN_M
and route["room_floor"] - CORRIDOR_MARGIN_M <= z
<= route["room_floor"] + 2.0 * room_h[2] + CORRIDOR_MARGIN_M)
tri.in_region = corridor or room_box
outside_bore = (distance > route["bore_radius"] + 0.45
or abs(x - room_c[0]) > room_h[0] + 0.45
or abs(y - room_c[1]) > room_h[1] + 0.45)
# Keep the reference on native, ground-facing terrain. The outer mesh
# also contains steep cavern walls and room edges, which are features.
tri.reference = tri.in_region and outside_bore and tri.smooth[2] >= 0.45
def vertex_key(point):
return tuple(int(round(value / WELD_M)) for value in point)
def make_edges(triangles, indices):
edge_map = {}
for index in indices:
tri = triangles[index]
points = tri.verts
for a, b in ((0, 1), (1, 2), (2, 0)):
ka, kb = vertex_key(points[a]), vertex_key(points[b])
if ka == kb:
continue
key = (ka, kb) if ka < kb else (kb, ka)
delta = tuple(points[a][axis] - points[b][axis] for axis in range(3))
length = math.sqrt(dot(delta, delta))
midpoint = tuple((points[a][axis] + points[b][axis]) * 0.5 for axis in range(3))
edge_map.setdefault(key, []).append((index, length, midpoint))
pairs = []
for incident in edge_map.values():
unique = {}
for index, length, midpoint in incident:
unique[index] = (length, midpoint)
incident = list(unique.items())
if len(incident) < 2:
continue
if len(incident) > 4:
incident = incident[:4]
for left in range(len(incident)):
for right in range(left + 1, len(incident)):
i, (length, midpoint) = incident[left]
j, _ = incident[right]
angle = max(angle_degrees(triangles[i].normal, triangles[j].normal),
0.75 * angle_degrees(triangles[i].smooth, triangles[j].smooth))
pairs.append((i, j, angle, length, midpoint))
return pairs
def cell_of(point):
return tuple(math.floor(value / CELL_M) for value in point)
def score_surface(triangles, considered, pairs, edge_cutoff, rough_cutoff,
ref_cell_density):
considered_set = set(considered)
variation = {index: 0.0 for index in considered}
max_jump = {index: 0.0 for index in considered}
severe_count = {index: 0.0 for index in considered}
for i, j, angle, length, _midpoint in pairs:
if i not in considered_set or j not in considered_set:
continue
weighted = angle * length * 0.5
variation[i] += weighted
variation[j] += weighted
max_jump[i] = max(max_jump[i], angle)
max_jump[j] = max(max_jump[j], angle)
if angle >= edge_cutoff:
severe_count[i] += 0.5
severe_count[j] += 0.5
cells = {}
for index in considered:
tri = triangles[index]
key = cell_of(tri.center)
cell = cells.setdefault(key, {"area": 0.0, "variation": 0.0, "severe": 0.0,
"sliver_area": 0.0, "slivers": 0, "triangles": 0, "max_jump": 0.0,
"reference_area": 0.0, "reference_variation": 0.0,
"reference_severe": 0.0, "reference_sliver_area": 0.0,
"reference_slivers": 0})
cell["area"] += tri.area
cell["variation"] += variation[index]
cell["severe"] += severe_count[index]
cell["triangles"] += 1
cell["max_jump"] = max(cell["max_jump"], max_jump[index])
sliver = tri.quality < 0.07 and tri.area < 0.12
if sliver:
cell["sliver_area"] += tri.area
cell["slivers"] += 1
if tri.reference:
cell["reference_area"] += tri.area
cell["reference_variation"] += variation[index]
cell["reference_severe"] += severe_count[index]
if sliver:
cell["reference_sliver_area"] += tri.area
cell["reference_slivers"] += 1
ref_rough = []
ref_density = []
ref_sliver_fraction = []
for cell in cells.values():
if cell["reference_area"] >= max(0.15, cell["area"] * 0.65):
ref_rough.append(cell["reference_variation"] / max(cell["reference_area"], 1.0e-9))
ref_density.append(cell["reference_severe"] / max(cell["reference_area"], 1.0e-9))
ref_sliver_fraction.append(cell["reference_sliver_area"]
/ max(cell["reference_area"], 1.0e-9))
reference_median = quantile(ref_rough, 0.5)
reference_mad = quantile([abs(value - reference_median) for value in ref_rough], 0.5)
cell_rough_limit = max(10.0, quantile(ref_rough, 0.995) * 1.5,
reference_median + 8.0 * reference_mad)
edge_density_limit = max(1.5, quantile(ref_density, 0.995) * 1.7,
ref_cell_density * 1.7)
sliver_limit = max(0.22, quantile(ref_sliver_fraction, 0.995) + 0.18)
hot = set()
for key, cell in cells.items():
area = max(cell["area"], 1.0e-9)
roughness = cell["variation"] / area
edge_density = cell["severe"] / area
sliver_fraction = cell["sliver_area"] / area
rough_cluster = roughness > cell_rough_limit and cell["severe"] >= 2.0
edge_cluster = cell["severe"] >= 4.0 and edge_density > edge_density_limit
sliver_cluster = cell["slivers"] >= 5 and sliver_fraction > sliver_limit
if rough_cluster or edge_cluster or sliver_cluster:
hot.add(key)
cell["roughness_deg_per_m"] = roughness
cell["severe_edge_density_m2"] = edge_density
cell["sliver_area_fraction"] = sliver_fraction
cell["reasons"] = [name for name, active in (("normal-variation", rough_cluster),
("edge-density", edge_cluster), ("slivers", sliver_cluster)) if active]
clusters = []
flagged_cells = set()
remaining = set(hot)
while remaining:
stack = [remaining.pop()]
members = []
while stack:
current = stack.pop()
members.append(current)
x, y, z = current
neighbors = [(x + dx, y + dy, z + dz)
for dx in (-1, 0, 1) for dy in (-1, 0, 1) for dz in (-1, 0, 1)
if dx or dy or dz]
for neighbor in neighbors:
if neighbor in remaining:
remaining.remove(neighbor)
stack.append(neighbor)
cells_in_cluster = [cells[key] for key in members]
cluster_area = sum(cell["area"] for cell in cells_in_cluster)
cluster_severe = sum(cell["severe"] for cell in cells_in_cluster)
cluster_slivers = sum(cell["slivers"] for cell in cells_in_cluster)
accepted = len(members) >= 2 and cluster_area >= 0.25 and \
(cluster_severe >= 8.0 or cluster_slivers >= 12)
if accepted:
flagged_cells.update(members)
clusters.append({"cells": len(members), "area_m2": cluster_area,
"severe_edges": cluster_severe, "sliver_triangles": cluster_slivers,
"bounds_m": [[min(key[i] for key in members) * CELL_M,
(max(key[i] for key in members) + 1) * CELL_M]
for i in range(3)],
"reasons": sorted({reason for cell in cells_in_cluster
for reason in cell["reasons"]})})
clusters.sort(key=lambda cluster: (cluster["severe_edges"], cluster["area_m2"]), reverse=True)
flagged_triangles = [index for index in considered
if cell_of(triangles[index].center) in flagged_cells]
return {"cells": cells, "hot_cells": hot, "flagged_cells": flagged_cells,
"clusters": clusters, "hot_triangles": flagged_triangles,
"thresholds": {"edge_angle_deg": edge_cutoff,
"roughness_deg_per_m": cell_rough_limit,
"severe_edge_density_m2": edge_density_limit,
"sliver_area_fraction": sliver_limit,
"reference_cell_roughness_p995_deg_per_m": quantile(ref_rough, 0.995),
"reference_cell_roughness_median_deg_per_m": reference_median}}
def floor_profile_audit(triangles, considered, region_indices, route):
"""Measure the whole floor patch as area-weighted bins, not selected rails."""
if route is None:
return {"present": False, "detected": False, "triangles": 0, "bins": 0,
"lateral_bands": 0, "ripple_amplitude_m": 0.0,
"max_adjacent_step_m": 0.0}
along_step = 0.25
lateral_step = 0.5
bins = {}
floor_triangles = 0
wall_triangles = 0
wall_bins = set()
for index in region_indices:
tri = triangles[index]
height = tri.center[2] - tri.floor_z
if (0.0 <= tri.route_s <= 10.0
and route["bore_radius"] - 0.9 <= tri.route_distance
<= route["bore_radius"] + 1.0
and 0.45 <= height <= route["bore_height"] + 0.5
and abs(tri.smooth[2]) <= 0.45):
wall_triangles += 1
wall_bins.add(int(tri.route_s))
for index in considered:
tri = triangles[index]
if (tri.route_s < 0.0 or tri.route_s > min(10.0, route["route_length"])
or tri.route_distance > min(1.9, route["bore_radius"] - 0.1)
or abs(tri.center[2] - tri.floor_z) > 0.60
or tri.smooth[2] < 0.55):
continue
floor_triangles += 1
along_bin = int(math.floor(tri.route_s / along_step))
lateral_bin = int(math.floor((tri.route_lateral + 2.0) / lateral_step))
key = (lateral_bin, along_bin)
row = bins.setdefault(key, [0.0, 0.0, 0])
residual = tri.center[2] - tri.floor_z
row[0] += residual * tri.area
row[1] += tri.area
row[2] += 1
by_lateral = {}
for (lateral_bin, along_bin), (weighted_residual, area, count) in bins.items():
if area < 0.002:
continue
by_lateral.setdefault(lateral_bin, {})[along_bin] = weighted_residual / area
amplitudes = []
steps = []
valid_bins = 0
for values in by_lateral.values():
ordered = sorted(values.items())
run = []
runs = []
for item in ordered:
if run and item[0] != run[-1][0] + 1:
runs.append(run)
run = []
run.append(item)
if run:
runs.append(run)
for segment in runs:
if len(segment) < 8:
continue
valid_bins += len(segment)
raw = [value for _, value in segment]
highpass = []
for position, value in enumerate(raw):
nearby = raw[max(0, position - 4):min(len(raw), position + 5)]
highpass.append(value - sum(nearby) / len(nearby))
amplitudes.append(max(highpass) - min(highpass))
steps.extend(abs(raw[i] - raw[i - 1]) for i in range(1, len(raw)))
max_ripple = max(amplitudes, default=0.0) * 0.5
max_step = max(steps, default=0.0)
present = (floor_triangles >= 24 and valid_bins >= 8
and wall_triangles >= 24 and len(wall_bins) >= 3)
detected = present and (max_ripple > 0.015 or max_step > 0.050)
return {"present": present, "detected": detected, "triangles": floor_triangles,
"bins": valid_bins, "lateral_bands": len(by_lateral),
"wall_triangles": wall_triangles, "wall_bins": len(wall_bins),
"ripple_amplitude_m": max_ripple, "max_adjacent_step_m": max_step,
"thresholds": {"ripple_amplitude_m": 0.015, "adjacent_step_m": 0.050}}
def wall_band_measure(pairs, route, edge_cutoff):
"""Locate high dihedrals around the bore-foot and the L47 0.25 m band edge."""
if route is None:
return {}
bins = {}
radius = route["bore_radius"]
for _i, _j, angle, _length, midpoint in pairs:
distance, route_s, floor_z, _lateral = route_nearest(midpoint, route)
height = midpoint[2] - floor_z
if (0.0 <= route_s <= 10.0 and abs(distance - radius) <= 0.75
and -0.25 <= height <= 0.875):
index = int(math.floor((height + 0.25) / 0.125))
entry = bins.setdefault(index, [])
entry.append(angle)
result = {}
for index, angles in sorted(bins.items()):
center_height = (index + 0.5) * 0.125 - 0.25
result[f"{center_height:.3f}"] = {
"edges": len(angles),
"over_reference_cutoff": sum(value >= edge_cutoff for value in angles),
"p95_dihedral_deg": quantile(angles, 0.95),
"max_dihedral_deg": max(angles, default=0.0),
}
return result
def radial_wall_measure(pairs, route, edge_cutoff):
"""Measure dihedrals across the hard bore-radius gate at the entrance foot."""
if route is None:
return {}
bins = {}
radius = route["bore_radius"]
for _i, _j, angle, _length, midpoint in pairs:
distance, route_s, floor_z, _lateral = route_nearest(midpoint, route)
height = midpoint[2] - floor_z
if (-5.0 <= route_s <= 3.0 and -1.0 <= height <= 0.75
and abs(distance - radius) <= 1.0):
index = int(math.floor((distance - (radius - 1.0)) / 0.125))
bins.setdefault(index, []).append(angle)
return {f"{radius - 1.0 + (index + 0.5) * 0.125:.3f}": {
"edges": len(angles),
"over_reference_cutoff": sum(value >= edge_cutoff for value in angles),
"p95_dihedral_deg": quantile(angles, 0.95),
"max_dihedral_deg": max(angles, default=0.0),
} for index, angles in sorted(bins.items())}
def svg_for(path, triangles, route, hot_triangles, label):
selected = [triangles[index] for index in hot_triangles]
all_region = [tri for tri in triangles if tri.in_region]
points = [point for tri in all_region for point in tri.verts]
if not points:
return
xmin, xmax = min(p[0] for p in points), max(p[0] for p in points)
ymin, ymax = min(p[1] for p in points), max(p[1] for p in points)
width, height, margin = 1200.0, 900.0, 35.0
sx = (width - 2 * margin) / max(1.0e-6, xmax - xmin)
sy = (height - 2 * margin) / max(1.0e-6, ymax - ymin)
scale = min(sx, sy)
draw_w, draw_h = (xmax - xmin) * scale, (ymax - ymin) * scale
ox, oy = (width - draw_w) * 0.5, (height - draw_h) * 0.5
def project(point):
return ox + (point[0] - xmin) * scale, height - oy - (point[1] - ymin) * scale
lines = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{int(width)}" height="{int(height)}" viewBox="0 0 {int(width)} {int(height)}">',
'<rect width="100%" height="100%" fill="#fbfbfa"/>',
f'<text x="28" y="28" font-family="sans-serif" font-size="19" fill="#222">{label}: whole-surface defect clusters (XY plan)</text>',
'<g fill="#c8ced2" fill-opacity="0.24" stroke="#8b959b" stroke-opacity="0.12" stroke-width="0.3">']
for tri in all_region:
coords = [project(p) for p in tri.verts]
lines.append('<polygon points="' + " ".join(f"{x:.1f},{y:.1f}" for x, y in coords)
+ '"/>')
lines.append('</g><g fill="#d7191c" fill-opacity="0.78" stroke="#7f0000" stroke-width="0.4">')
for tri in selected:
coords = [project(p) for p in tri.verts]
lines.append('<polygon points="' + " ".join(f"{x:.1f},{y:.1f}" for x, y in coords)
+ '"/>')
route_coords = [project(p) for p in route["route_points"]]
lines.append('</g><polyline points="' + " ".join(f"{x:.1f},{y:.1f}" for x, y in route_coords)
+ '" fill="none" stroke="#1769aa" stroke-width="2"/>')
lines.append(f'<text x="28" y="{int(height - 24)}" font-family="sans-serif" font-size="15" fill="#333">Blue: intended route · Red: flagged triangle cells · Gray: remaining ROI triangles · flagged triangles: {len(selected)}</text>')
lines.append('</svg>')
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def audit_case(label, mesh_path, layout_path, expect, focus, svg_dir):
triangles, voxel_um, tile_count, lod_counts = read_mesh(mesh_path)
route = None
if layout_path != Path("-"):
layout = json.loads(layout_path.read_text(encoding="utf-8"))
route = make_route(layout)
mark_region(triangles, route)
region_indices = [i for i, tri in enumerate(triangles) if tri.in_region]
else:
region_indices = list(range(len(triangles)))
for tri in triangles:
tri.in_region = True
tri.reference = True
if len(region_indices) < 24:
raise ValueError(f"{label}: only {len(region_indices)} triangles intersect the ROI")
reference_indices = [i for i in region_indices if triangles[i].reference]
if len(reference_indices) < 24:
reference_indices = region_indices
for index in region_indices:
triangles[index].reference = True
edge_pairs = make_edges(triangles, region_indices)
reference_edges = [edge[2] for edge in edge_pairs
if triangles[edge[0]].reference and triangles[edge[1]].reference]
edge_cutoff = max(35.0, quantile(reference_edges, 0.995))
reference_set = set(reference_indices)
ref_roughness = {index: 0.0 for index in reference_indices}
for i, j, angle, length, _midpoint in edge_pairs:
if i in reference_set and j in reference_set:
ref_roughness[i] += angle * length * 0.5
ref_roughness[j] += angle * length * 0.5
ref_values = [ref_roughness[index] / max(triangles[index].area, 1.0e-9)
for index in reference_indices]
rough_cutoff = 10.0
ref_density = 0.0
ref_area = sum(triangles[index].area for index in reference_indices)
ref_severe = 0.0
for i, j, angle, _length, _midpoint in edge_pairs:
if i in reference_set and j in reference_set and angle >= edge_cutoff:
ref_severe += 1.0
ref_density = ref_severe / max(ref_area, 1.0e-9)
if focus == "floor":
considered = [i for i in region_indices if route is not None
and triangles[i].route_s >= 1.5
and triangles[i].route_s <= 10.0
and triangles[i].route_distance <= 1.25
and abs(triangles[i].center[2] - triangles[i].floor_z) <= 0.24
and triangles[i].smooth[2] >= 0.55]
else:
considered = region_indices
if len(considered) < 24:
raise ValueError(f"{label}: focus={focus} has only {len(considered)} triangles")
scores = score_surface(triangles, considered, edge_pairs, edge_cutoff,
rough_cutoff, ref_density)
floor_profile = floor_profile_audit(triangles, considered, region_indices, route)
wall_band = wall_band_measure(edge_pairs, route, edge_cutoff)
radial_wall = radial_wall_measure(edge_pairs, route, edge_cutoff)
found = bool(scores["clusters"]) or floor_profile["detected"]
passed = expect == "measure" or (expect == "fail" and found) or (expect == "pass" and not found)
if svg_dir and route is not None:
svg_for(svg_dir / f"flagged-regions-{label}.svg", triangles, route,
scores["hot_triangles"], label)
cluster_summary = scores["clusters"][:8]
result = {
"label": label,
"mesh": str(mesh_path),
"layout": None if layout_path == Path("-") else str(layout_path),
"focus": focus,
"expected": expect,
"observed": "fail" if found else "pass",
"status": "PASS" if passed else "FAIL",
"voxel_um": voxel_um,
"lod0_tiles": tile_count,
"lod_tile_counts": lod_counts,
"triangles_loaded_lod0": len(triangles),
"triangles_in_roi": len(region_indices),
"triangles_audited": len(considered),
"adjacent_pairs": len(edge_pairs),
"reference_triangles": len(reference_indices),
"reference_edge_p995_deg": quantile(reference_edges, 0.995),
"reference_p995_normal_variation_deg_per_m": quantile(ref_values, 0.995),
"thresholds": scores["thresholds"],
"flagged_cells": len(scores["hot_cells"]),
"flagged_triangles": len(scores["hot_triangles"]),
"floor_profile": floor_profile,
"wall_band_height_histogram": wall_band,
"radial_wall_histogram": radial_wall,
"clusters": cluster_summary,
}
print(f"[WholeSurfaceAudit] label={label} focus={focus} lod0_tiles={tile_count} "
f"triangles_roi={len(region_indices)} audited={len(considered)} "
f"adjacent_pairs={len(edge_pairs)} reference={len(reference_indices)} "
f"edge_cutoff_deg={edge_cutoff:.2f} "
f"roughness_cutoff_deg_m={scores['thresholds']['roughness_deg_per_m']:.2f} "
f"hot_cells={len(scores['hot_cells'])} flagged_triangles={len(scores['hot_triangles'])} "
f"clusters={len(scores['clusters'])} observed={result['observed'].upper()} "
f"expected={expect.upper()} status={result['status']}")
print(f"[WholeSurfaceFloorProfile] label={label} triangles={floor_profile['triangles']} "
f"bins={floor_profile['bins']} lateral_bands={floor_profile['lateral_bands']} "
f"wall_triangles={floor_profile.get('wall_triangles', 0)} "
f"wall_bins={floor_profile.get('wall_bins', 0)} "
f"ripple_amplitude_m={floor_profile['ripple_amplitude_m']:.4f} "
f"max_adjacent_step_m={floor_profile['max_adjacent_step_m']:.4f} "
f"observed={'FAIL' if floor_profile['detected'] else ('PASS' if floor_profile['present'] else 'NOT_PRESENT')}")
if wall_band:
print(f"[WholeSurfaceWallBandMeasure] label={label} voxel_m={voxel_um / 1_000_000.0:.3f} "
f"recarve_edge_height_m=0.250 radial_band_m={route['bore_radius']:.3f} "
f"height_histogram={json.dumps(wall_band, sort_keys=True, separators=(',', ':'))}")
print(f"[WholeSurfaceRadialWallMeasure] label={label} route_s_m=-5..3 "
f"height_above_floor_m=-1..0.75 bore_radius_m={route['bore_radius']:.3f} "
f"radial_histogram={json.dumps(radial_wall, sort_keys=True, separators=(',', ':'))}")
for index, cluster in enumerate(cluster_summary):
print(f"[WholeSurfaceCluster] label={label} index={index} cells={cluster['cells']} "
f"area_m2={cluster['area_m2']:.3f} severe_edges={cluster['severe_edges']:.1f} "
f"slivers={cluster['sliver_triangles']} bounds_m={cluster['bounds_m']} "
f"reasons={','.join(cluster['reasons'])}")
hot_cells = scores["cells"]
for key in sorted(scores["hot_cells"], key=lambda item: (
hot_cells[item]["severe"], hot_cells[item]["variation"]), reverse=True)[:12]:
cell = hot_cells[key]
local = route_nearest(tuple((axis + 0.5) * CELL_M for axis in key), route) if route else None
print(f"[WholeSurfaceHotCell] label={label} cell_m=({key[0]},{key[1]},{key[2]}) "
f"route_s_m={local[1] if local else 0.0:.2f} radial_m={local[0] if local else 0.0:.2f} "
f"height_above_floor_m={(key[2] + 0.5 - local[2]) if local else 0.0:.2f} "
f"area_m2={cell['area']:.3f} roughness_deg_m={cell['roughness_deg_per_m']:.2f} "
f"severe_edges={cell['severe']:.1f} edge_density_m2={cell['severe_edge_density_m2']:.2f} "
f"slivers={cell['slivers']} max_jump_deg={cell['max_jump']:.2f} "
f"reasons={','.join(cell['reasons'])}")
return result
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--case", nargs=5, action="append", required=True,
metavar=("LABEL", "MESH", "LAYOUT_OR_DASH", "EXPECT", "FOCUS"))
parser.add_argument("--json", type=Path, required=True)
parser.add_argument("--svg-dir", type=Path)
args = parser.parse_args()
results = []
for label, mesh, layout, expect, focus in args.case:
if expect not in ("pass", "fail", "measure") or focus not in ("whole", "floor"):
parser.error("EXPECT must be pass/fail/measure and FOCUS must be whole/floor")
results.append(audit_case(label, Path(mesh), Path(layout), expect, focus, args.svg_dir))
args.json.parent.mkdir(parents=True, exist_ok=True)
args.json.write_text(json.dumps({"audit": "whole-surface-l48-v1", "cases": results},
indent=2) + "\n", encoding="utf-8")
failures = [result for result in results if result["status"] != "PASS"]
print(f"[WholeSurfaceAuditSummary] cases={len(results)} failed={len(failures)} "
f"json={args.json} status={'FAIL' if failures else 'PASS'}")
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,410 @@
#!/usr/bin/env python3
"""Capture L48 cavern entrance views and audit the exported floor profile."""
from __future__ import annotations
import argparse
import importlib.util
import json
import math
import os
from pathlib import Path
import subprocess
import sys
import time
ROOT = Path(__file__).resolve().parents[4]
VERIFY = ROOT / "Standalone/Tools/verify"
LINUX37 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37"
sys.path.insert(0, str(VERIFY))
from runner import bounded_run, current_stamp, first_stamp
def load_module(name: str, path: Path):
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise RuntimeError(f"cannot import {path}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
PROFILE = load_module("l45_ring_profile", ROOT /
"Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py")
SHAPE = load_module("l45_shape_audit", LINUX37 / "AuditMouthShapeL37.py")
PARSER = argparse.ArgumentParser(description=__doc__)
PARSER.add_argument("--layout", type=Path, required=True)
PARSER.add_argument("--artifact-dir", type=Path, required=True)
PARSER.add_argument("--capture-dir", type=Path, required=True)
PARSER.add_argument("--data", type=Path, default=ROOT / "Standalone/data/owner_world.json")
OPTIONS = PARSER.parse_args()
ARTIFACT_DIR = OPTIONS.artifact_dir.resolve()
CAPTURE_DIR = OPTIONS.capture_dir.resolve()
LAYOUT_PATH = OPTIONS.layout.resolve()
ARTIFACT_DIR.mkdir(parents=True, exist_ok=True)
CAPTURE_DIR.mkdir(parents=True, exist_ok=True)
PNG_DIR = ARTIFACT_DIR / "capture-png"
PNG_DIR.mkdir(parents=True, exist_ok=True)
HEADLESS = Path(os.environ["VOXELFORGE_L37_HEADLESS"])
RENDERER = Path(os.environ["VOXELFORGE_L37_RENDERER"])
EXPECTED_STAMP = current_stamp()
def run_logged(name: str, command: list[str], *, capture: bool = False) -> str:
log = ARTIFACT_DIR / f"{name}.log"
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = bounded_run(command, timeout=1800, stdout=stream)
elapsed = time.monotonic() - started
output = log.read_text(encoding="utf-8", errors="replace")
if result is None:
raise RuntimeError(f"{name} timed out; see {log}")
print(f"[L48CaptureRun] step={name} elapsed={elapsed:.1f}s "
f"exit={result.returncode} log={log}", flush=True)
if result.returncode != 0:
raise RuntimeError(f"{name} failed; see {log}")
if capture and (first_stamp(output) != EXPECTED_STAMP
or first_stamp(bounded_run(
[str(command[0]), "--print-source-stamp"], timeout=15).stdout or "")
!= EXPECTED_STAMP):
raise RuntimeError(f"{name} executable source stamp mismatch")
return output
def stamp_by_id(layout: dict, stable_id: str) -> dict:
for stamp in layout.get("stamps", []):
if stamp.get("stable_id") == stable_id:
return stamp
raise RuntimeError(f"missing stamp {stable_id}")
def metres(stamp: dict, key: str = "position_um") -> tuple[float, float, float]:
return tuple(value / 1_000_000.0 for value in stamp[key])
def camera_angles(camera, target):
dx, dy, dz = (target[index] - camera[index] for index in range(3))
return (math.degrees(math.atan2(dx, dy)),
math.degrees(math.atan2(dz, math.hypot(dx, dy))))
def fmt(values) -> str:
return ",".join(f"{value:.6f}" for value in values)
def camera_spec(camera, target):
yaw, pitch = camera_angles(camera, target)
return (*camera, yaw, pitch)
def edge_point(edge: dict, t: float) -> tuple[float, float, float]:
points = edge["control_points_um"]
one = 1.0 - t
weights = (one ** 3, 3 * one ** 2 * t, 3 * one * t ** 2, t ** 3)
return tuple(sum(weights[index] * points[index][axis]
for index in range(4)) / 1_000_000.0
for axis in range(3))
def cavern_features(layout: dict):
if (layout.get("cavern_graph_vocabulary_version") != 1
or len(layout.get("cavern_rooms", [])) != 1
or len(layout.get("cavern_edges", [])) != 1):
raise RuntimeError("L48 capture needs one resolved cavern room and edge")
room = layout["cavern_rooms"][0]
edge = layout["cavern_edges"][0]
return room, edge
layout = json.loads(LAYOUT_PATH.read_text(encoding="utf-8"))
room, edge = cavern_features(layout)
portal_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-mouth-approach-v1")
floor_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-v1-floor")
dais_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-v1-dais")
entrance = edge_point(edge, 0.0)
next_ramp = edge_point(edge, 0.08)
portal = metres(portal_stamp)
chamber = metres(room, "center_um")
floor = metres(floor_stamp)
floor_top = floor[2] + floor_stamp["half_extents_um"][2] / 1_000_000.0
dais = metres(dais_stamp)
dais_top = dais[2] + dais_stamp["half_extents_um"][2] / 1_000_000.0
ground_z = entrance[2]
direction_length = math.hypot(next_ramp[0] - entrance[0], next_ramp[1] - entrance[1])
direction = ((next_ramp[0] - entrance[0]) / direction_length,
(next_ramp[1] - entrance[1]) / direction_length)
outward_length = math.hypot(entrance[0] - chamber[0], entrance[1] - chamber[1])
outward = ((entrance[0] - chamber[0]) / outward_length,
(entrance[1] - chamber[1]) / outward_length)
mouth_normal = (-direction[1], direction[0])
mesh = ARTIFACT_DIR / "landmark-l48.vfmesh"
native_mesh = ARTIFACT_DIR / "control-plain-native.vfmesh"
export_anchor = edge_point(edge, 0.25)
export_camera = (export_anchor[0], export_anchor[1], export_anchor[2] + 1.4)
export_target_floor = edge_point(edge, 0.42)
export_target = (export_target_floor[0], export_target_floor[1],
export_target_floor[2] + 1.2)
run_logged("landmark-export", [str(HEADLESS), str(OPTIONS.data), "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=4", "--max-clip-level=0", "--max-level=0",
"--render-distance-chunks=0", "--recipe=surface-world", "--landmark-chamber-demo",
f"--camera={fmt(camera_spec(export_camera, export_target))}",
f"--mesh-export={mesh}", f"--resolved-layout-output={LAYOUT_PATH}"], capture=True)
run_logged("plain-native-export", [str(HEADLESS), str(OPTIONS.data), "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=4", "--max-clip-level=0", "--max-level=0",
"--render-distance-chunks=0", "--recipe=surface-world",
f"--camera={fmt(camera_spec(export_camera, export_target))}",
f"--mesh-export={native_mesh}",
f"--resolved-layout-output={ARTIFACT_DIR / 'control-plain-native-layout.json'}"],
capture=True)
layout = json.loads(LAYOUT_PATH.read_text(encoding="utf-8"))
room, edge = cavern_features(layout)
portal_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-mouth-approach-v1")
floor_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-v1-floor")
dais_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-v1-dais")
entrance = edge_point(edge, 0.0)
next_ramp = edge_point(edge, 0.08)
portal = metres(portal_stamp)
chamber = metres(room, "center_um")
floor = metres(floor_stamp)
floor_top = floor[2] + floor_stamp["half_extents_um"][2] / 1_000_000.0
dais = metres(dais_stamp)
dais_top = dais[2] + dais_stamp["half_extents_um"][2] / 1_000_000.0
ground_z = entrance[2]
direction_length = math.hypot(next_ramp[0] - entrance[0], next_ramp[1] - entrance[1])
direction = ((next_ramp[0] - entrance[0]) / direction_length,
(next_ramp[1] - entrance[1]) / direction_length)
outward_length = math.hypot(entrance[0] - chamber[0], entrance[1] - chamber[1])
outward = ((entrance[0] - chamber[0]) / outward_length,
(entrance[1] - chamber[1]) / outward_length)
if not mesh.is_file() or mesh.stat().st_size == 0:
raise RuntimeError(f"missing mesh export {mesh}")
entrance_audit = ROOT / "Standalone/Research/WorldAssemblerM3/Linux47/MeasureEntranceL47.py"
run_logged("entrance-floor-control-l46", [sys.executable, str(entrance_audit),
"--mesh", str(ROOT / "Saved/Linux/l46/landmark-l46.vfmesh"),
"--layout", str(ROOT / "Saved/Linux/l46/layouts/landmark-reachability-clang.json"),
"--out", str(ARTIFACT_DIR / "entrance-profile-control-l46.json"), "--expect=fail"])
run_logged("entrance-floor-audit", [sys.executable, str(entrance_audit),
"--mesh", str(mesh), "--layout", str(LAYOUT_PATH),
"--out", str(ARTIFACT_DIR / "entrance-profile-l48.json"), "--expect=pass"])
voxel_um, triangles, tile_count = PROFILE.read_scene(str(mesh), 0,
floor_top - 8.0, entrance[2] + 9.0,
(min(entrance[0], chamber[0]) - 20.0, max(entrance[0], chamber[0]) + 20.0,
min(entrance[1], chamber[1]) - 20.0, max(entrance[1], chamber[1]) + 20.0))
bounds = (min(entrance[0], chamber[0]) - 20.0, max(entrance[0], chamber[0]) + 20.0,
min(entrance[1], chamber[1]) - 20.0, max(entrance[1], chamber[1]) + 20.0)
buckets = PROFILE.build_buckets(triangles, True, bounds, cell_size=1.0)
ground_candidates = []
for ix in range(-20, 21):
for iy in range(-20, 21):
x, y = portal[0] + ix * 0.5, portal[1] + iy * 0.5
dx, dy = x - portal[0], y - portal[1]
distance = math.hypot(dx, dy)
if not 6.0 <= distance <= 10.0:
continue
hit = PROFILE.sample_surface(buckets, x, y, True, 1.0,
preferred_height=ground_z)
if hit is None or abs(hit[0] - ground_z) > 3.0 or hit[1][2] < 0.85:
continue
alignment = max(-1.0, min(1.0,
(dx * mouth_normal[0] + dy * mouth_normal[1]) / distance))
angle = math.acos(alignment)
score = abs(distance - 7.0) + angle * 4.0
ground_candidates.append((score, x, y, hit[0]))
if not ground_candidates:
raise RuntimeError("no walkable mesh ground lies 6 to 10 m from the cave mouth")
_, ground_x, ground_y, outside_ground = min(ground_candidates)
inside_floor = PROFILE.sample_surface(buckets, chamber[0], chamber[1], True, 1.0,
preferred_height=floor_top)
if inside_floor is None or abs(inside_floor[0] - floor_top) > 1.5:
raise RuntimeError(f"chamber floor mesh hit {inside_floor} does not match designed floor {floor_top:.3f} m")
floor_top = inside_floor[0]
ground_camera = (ground_x, ground_y, outside_ground + 1.6)
entry_ground = PROFILE.sample_surface(buckets, entrance[0], entrance[1], True, 1.0,
preferred_height=ground_z)
if entry_ground is None:
raise RuntimeError("cave mouth has no walkable ground surface in the exported mesh")
entry_camera = (entrance[0] + mouth_normal[0] * 3.8,
entrance[1] + mouth_normal[1] * 3.8, ground_z + 1.6)
entry_target_floor = edge_point(edge, 0.30)
entry_target = (entry_target_floor[0], entry_target_floor[1], entry_target_floor[2] + 1.4)
low_camera = (entrance[0] + outward[0] * 1.0,
entrance[1] + outward[1] * 1.0, ground_z + 0.65)
low_target_floor = edge_point(edge, 0.24)
low_target = (low_target_floor[0], low_target_floor[1], low_target_floor[2] + 0.15)
to_dais = (dais[0] - chamber[0], dais[1] - chamber[1])
to_dais_length = math.hypot(*to_dais)
opposite_dais = (-to_dais[0] / to_dais_length, -to_dais[1] / to_dais_length)
inside_x = chamber[0]
inside_y = chamber[1]
inside_floor = PROFILE.sample_surface(buckets, inside_x, inside_y, True, 1.0,
preferred_height=floor_top)
inside_eye_z = inside_floor[0] + 1.6
chamber_camera = (chamber[0] + opposite_dais[0] * 5.5,
chamber[1] + opposite_dais[1] * 5.5, inside_eye_z)
chamber_target = (dais[0], dais[1], dais_top - 0.25)
overview_camera = (entrance[0] + outward[0] * 18.0,
entrance[1] + outward[1] * 18.0, outside_ground + 15.0)
overview_target = ((entrance[0] + chamber[0]) * 0.5,
(entrance[1] + chamber[1]) * 0.5,
(ground_z + floor_top) * 0.5)
inward = (-outward[0], -outward[1])
clip_point = (entrance[0] + outward[0] * 0.5,
entrance[1] + outward[1] * 0.5)
overview_clip = (inward[0], inward[1], 0.0,
-(inward[0] * clip_point[0] + inward[1] * clip_point[1]))
views = (
("ground-entrance", ground_camera, (portal[0], portal[1], portal[2]),
(portal[0], portal[1], ground_z, 20.0), -509.0),
("entrance-looking-in", entry_camera, entry_target, (entrance[0], entrance[1], entrance[2], 16.0), -507.0),
("entrance-floor-low", low_camera, low_target,
(entrance[0], entrance[1], entrance[2], 12.0), ground_z),
("inside-to-dais", chamber_camera, chamber_target,
(chamber[0], chamber[1], chamber[2], 15.0), chamber[2]),
("overview-entrance-and-chamber", overview_camera, overview_target,
((entrance[0] + chamber[0]) * 0.5,
(entrance[1] + chamber[1]) * 0.5,
(ground_z + floor_top) * 0.5, 34.0), -509.0),
)
for name, camera, target, focus, height_center in views:
png = PNG_DIR / f"{name}.png"
jpg = CAPTURE_DIR / f"{name}.jpg"
yaw, pitch = camera_angles(camera, target)
fov = (80.0 if name == "overview-entrance-and-chamber" else
70.0 if name in ("inside-to-dais", "entrance-looking-in") else
58.0 if name == "entrance-floor-low" else 65.0
if name == "ground-entrance" else 60.0)
if name == "ground-entrance":
fov = 55.0
print(f"[L48CaptureCamera] view={name} camera_m=({fmt(camera)}) "
f"target_m=({fmt(target)}) yaw_deg={yaw:.3f} pitch_deg={pitch:.3f} "
f"fov_deg={fov:.1f} entrance_distance_m={math.dist(camera, portal):.3f}", flush=True)
render = [str(RENDERER), str(mesh), str(png), "--size=1600x900", f"--fov-deg={fov:.1f}",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}",
"--shade=readable", "--headlamp=on", "--height-tint=on",
"--light-direction=0.3,-0.4,0.85",
f"--height-center-m={(floor_top if name == 'inside-to-dais' else height_center):.6f}",
f"--height-range-m={4 if name == 'inside-to-dais' else 6 if name == 'entrance-floor-low' else 16}",
"--tile-skirts=off", "--tile-edges=off",
f"--focus-region={fmt(focus)}"]
if name == "overview-entrance-and-chamber":
render.append(f"--clip-plane={fmt(overview_clip)}")
run_logged(f"{name}-render", render, capture=True)
for quality in (4, 6, 8):
run_logged(f"{name}-jpeg-q{quality}", ["ffmpeg", "-nostdin", "-hide_banner",
"-loglevel", "error", "-y", "-i", str(png), "-frames:v", "1",
"-q:v", str(quality), str(jpg)])
if jpg.is_file() and 0 < jpg.stat().st_size <= 300_000:
break
if not jpg.is_file() or not 0 < jpg.stat().st_size <= 300_000:
raise RuntimeError(f"{jpg.name} exceeds the 300 KB capture limit")
quality_log = run_logged(f"{name}-quality", [sys.executable,
str(VERIFY / "capture_quality.py"), str(jpg)])
print(f"[L48CaptureJPEG] file={jpg} bytes={jpg.stat().st_size} "
f"quality_log={quality_log.strip().replace(chr(10), ' ')} status=PASS", flush=True)
def region_triangles(center, radius_xy, z_min, z_max):
selected = []
for triangle in triangles:
points = tuple(vertex[0] for vertex in triangle[2])
centroid = tuple(sum(point[axis] for point in points) / 3.0 for axis in range(3))
if (math.hypot(centroid[0] - center[0], centroid[1] - center[1]) <= radius_xy
and z_min <= centroid[2] <= z_max):
selected.append(triangle)
return selected
def tunnel_wall_triangles(edge, t, height_above_floor):
center = edge_point(edge, t)
before = edge_point(edge, max(0.0, t - 0.01))
after = edge_point(edge, min(1.0, t + 0.01))
tx, ty = after[0] - before[0], after[1] - before[1]
tangent_length = math.hypot(tx, ty)
tx, ty = tx / tangent_length, ty / tangent_length
expected_radius = edge["bore_radius_um"] / 1_000_000.0 * math.sqrt(
1.0 - (height_above_floor / (edge["bore_height_um"] / 1_000_000.0)) ** 2)
selected = []
for triangle in triangles:
points = tuple(vertex[0] for vertex in triangle[2])
centroid = tuple(sum(point[axis] for point in points) / 3.0 for axis in range(3))
dx, dy = centroid[0] - center[0], centroid[1] - center[1]
longitudinal = dx * tx + dy * ty
lateral = abs(-dy * tx + dx * ty)
vertical = centroid[2] - center[2]
if (abs(longitudinal) <= 1.5
and abs(lateral - expected_radius) <= 0.7
and abs(vertical - height_above_floor) <= 0.75):
selected.append(triangle)
print(f"[L47TunnelWallWindow] t={t:.3f} center_m=({center[0]:.3f},"
f"{center[1]:.3f},{center[2]:.3f}) expected_radius_m={expected_radius:.3f} "
f"longitudinal_half_width_m=1.500 vertical_half_width_m=0.750 "
f"triangles={len(selected)}", flush=True)
return selected
def audit_shape(name, region, center):
metrics = SHAPE.analyze_triangles(region, center[:2], center[2],
crop_to_center=False, include_sides=True)
passed = (metrics["triangles"] > 0
and metrics["planar_area"] < SHAPE.PLANE_LIMIT_M2
and metrics["straight"] <= SHAPE.STRAIGHT_LIMIT_M
and metrics["corners"] == 0)
print(f"[LandmarkBuiltShape] region={name} triangles={metrics['triangles']} "
f"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"thresholds=({SHAPE.PLANE_LIMIT_M2:.2f}m2,{SHAPE.STRAIGHT_LIMIT_M:.2f}m,"
f"{SHAPE.CORNER_TURN_LIMIT_DEG:.0f}deg) status={'PASS' if passed else 'FAIL'}",
flush=True)
if not passed:
raise RuntimeError(f"L37 built-shape thresholds failed in {name}")
return metrics
entrance_metrics = audit_shape("cave-mouth", region_triangles(
portal, 5.0, ground_z + 0.8, ground_z + 4.5),
(portal[0], portal[1], ground_z))
tunnel_mid = edge_point(edge, 0.64)
tunnel_metrics = audit_shape("tunnel-wall-edge", tunnel_wall_triangles(
edge, 0.64, 1.2),
(tunnel_mid[0], tunnel_mid[1], tunnel_mid[2] + 1.2))
dais_surface = PROFILE.sample_surface(buckets, dais[0], dais[1], True, 1.0,
preferred_height=dais_top - 0.4)
if dais_surface is None:
raise RuntimeError("dais stamp has no upward-facing surface in the exported mesh")
dais_rise = dais_surface[0] - floor_top
print(f"[LandmarkDaisSurface] center_m=({dais[0]:.3f},{dais[1]:.3f}) "
f"floor_z_m={floor_top:.3f} top_z_m={dais_surface[0]:.3f} "
f"rise_m={dais_rise:.3f} minimum_m=1.000 "
f"status={'PASS' if dais_rise >= 1.0 else 'FAIL'}", flush=True)
if dais_rise < 1.0:
raise RuntimeError(f"dais rises only {dais_rise:.3f} m above the chamber floor")
dais_metrics = audit_shape("dais-platform", region_triangles(
dais, 1.7, floor_top + 0.6, dais_top + 0.4),
(dais[0], dais[1], dais_surface[0]))
metrics_path = ARTIFACT_DIR / "landmark-shape-metrics.json"
metrics_path.write_text(json.dumps({
"graph_vocabulary_version": layout["cavern_graph_vocabulary_version"],
"entry_m": entrance, "portal_m": portal, "entry_ground_m": ground_z,
"chamber_center_m": chamber, "chamber_floor_m": floor_top,
"tunnel_midpoint_m": tunnel_mid,
"dais_center_m": dais, "dais_top_m": dais_top,
"dais_surface_m": dais_surface[0], "dais_rise_m": dais_rise,
"l37_thresholds": {"planar_patch_m2_lt": SHAPE.PLANE_LIMIT_M2,
"straight_run_m_lte": SHAPE.STRAIGHT_LIMIT_M,
"corner_turn_deg_lt": SHAPE.CORNER_TURN_LIMIT_DEG},
"entrance_arch": entrance_metrics, "tunnel_wall": tunnel_metrics,
"dais_platform": dais_metrics,
"mesh_tiles": tile_count, "mesh_voxel_um": voxel_um,
}, indent=2) + "\n", encoding="utf-8")
print(f"[L48CaptureShapeMetrics] file={metrics_path} status=PASS", flush=True)
@@ -0,0 +1,60 @@
# Linux48 report
## Whole-surface audit
`AuditWholeSurfaceL48.py` scans every LOD0 triangle in the route corridor plus 3 m and the chamber box plus 3 m. It welds shared edges, compares geometric face normals, and scores area-weighted normal variation, high-angle edge density, maximum jumps and sliver area in 1 m cells. Connected hot cells become located clusters. A separate floor pass bins the full floor patch at 0.25 m along the route and 0.5 m across it, removes a local moving-mean trend, and checks ripple and adjacent steps. Thresholds use ground-facing native patches from the same export. JSON results and red-triangle XY maps are emitted.
The detector has no general quadric residual or periodic-spectrum metric yet; terraces outside its floor patch rely on the normal-cluster measures. The control results are:
| Mesh | Result | Evidence |
|---|---|---|
| L45 ramp | Expected fail | One normal cluster, 0.669 m² |
| L46 entrance | Expected fail | 84.3 mm floor ripple; 159.1 mm largest step |
| L47 wall-foot | Expected fail | One 5-cell cluster, 5.243 m², 42 severe edges; floor profile 34.5/57.6 mm ripple/step |
| Plain native | Pass | No clusters; floor profile not present |
| L47 smooth floor | Pass | 0.59 mm ripple; 1.15 mm largest step |
| L48 candidate | **Fail** | Two clusters, 7.095 m² total, 65.5 severe edges, 303 flagged triangles; floor profile 34.5/57.6 mm |
The L47 cluster is at approximately `x=[-5,0], y=[22,25], z=[-505,-504] m`. At 0.188 m above the floor its wall-band p95 dihedral is 54.6° with a 160.4° maximum; the selected floor rails did not reach this band. The complete control output is in `Saved/Linux/l48/whole-surface-audit.json`.
## Teeth measurement and attempted fix
The L47 measurements localize the teeth to the floor-cap/bore-wall junction. The post-stamp cap has a hard radial cutoff there, and the floor-adjacent re-carve also uses a non-SDF weighted blend. A re-carve fade alone left the teeth in the capture. The final trial replaced both cavern cap gates with a rounded max of the sloped-floor distance and radial bore distance, restoring the L47 re-carve weight. This reduced the apparent tooth height in the capture but did not clear the whole-surface gate; the final mesh has more flagged area and a second cluster. The hard cap edge is implicated, but the cause is not proven and this is not a validated fix.
## Hashes and cost
Clang and GCC builds used source stamp `05ea28750b2ee89aa61655c386c6d74a6841c4d071374cda03c73a89b5efb578`.
Demo-off whole-view hashes match L47 exactly at W1 and W12:
- floating-islands: density `598e9daaa1744ccb`, mesh `3114e0f4f6dbf734`, payload `c49c4e48bff74089`, topology `cadd4edd10d4d8ef`.
- surface-world: density `7dc75e2c09302258`, mesh `0cb34c40ceec4ce3`, payload `9d23e8c46b515cf1`, topology `2962c9464991eedd`.
Fast took 202.7 s. Medium took 637.6 s; the whole-surface stage took 161.8 s and failed on L48. Full and its five-pair cost comparison were not run because medium did not pass. All build and verification processes used `TMPDIR=Saved/Linux/l48/tmp`; no command wrote to `/tmp`.
## Tiers and captures
| Tier | Result |
|---|---|
| Fast | Pass, 202.7 s |
| Medium | Fail, 637.6 s: L48 whole-surface gate |
| Full | Not run: medium prerequisite failed |
The five captures are `ground-entrance.jpg`, `entrance-looking-in.jpg`, `overview-entrance-and-chamber.jpg`, `inside-to-dais.jpg`, and `entrance-floor-low.jpg` in `captures/`. Flag maps: `flagged-regions-L47-wall-foot-control.svg` and `flagged-regions-L48-final.svg` (red flagged triangles, gray ROI, blue route).
## Failed runs
- The first fast invocation had no built binary; after the Clang/GCC builds, fast passed.
- Bypassing the post-stamp floor cap failed entrance rails. Broad SmoothMin/cap-mask trials either closed the mouth or failed the built-shape check; one planar mouth patch measured 5.575 m².
- Radial re-carve fades at 0.5 m and 1 m preserved the rails but left visible teeth and failed the whole-surface audit. Changing the support inset had no mesh effect.
- The final rounded-cap trial passed the legacy rails and shape checks, but failed medium with the clusters above. The 15-trial budget is exhausted. No full tier, commit, push or stash was performed.
## Orchestrator review (Claude)
Split. **The whole-surface audit is the result of this round,** and it's committed here as a tool, not yet wired into
the tiers. All three historical defects fail it (L45 ramp, L46 entrance, L47 wall foot) and plain native terrain
passes. It also shows that L47's floor near the walls carries 34.5 mm ripple and a 57.6 mm step, which the three
rails never sampled. **The Core change (rounded cap) did not pass** and is parked with the tier wiring on
`wip/l48-surface-audit`. Wiring the gate now would turn medium red on HEAD, because the flagged demo still has the
teeth. `assembler/world` stays on L47 Core. L49 builds the floor and bore as one SDF, with a filleted floor/wall
corner, before stamps, instead of patching with a post-pass cap.
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 3.1 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 2.6 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 2.9 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 2.9 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 2.9 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 2.9 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB