#!/usr/bin/env python3 """Run L54's triangle audit over L59's open-shaft lattice and controls.""" from __future__ import annotations import argparse import importlib.util import json import math from pathlib import Path ROOT = Path(__file__).resolve().parents[4] AUDIT54 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux54/AuditWholeSurfaceL54.py" spec = importlib.util.spec_from_file_location("whole_surface_l54", AUDIT54) audit54 = importlib.util.module_from_spec(spec) assert spec and spec.loader spec.loader.exec_module(audit54) original_make_route = audit54.make_route def build_segment_bvh(segments: list[tuple]) -> dict: def build(indices: list[int]) -> dict: minimum = tuple(min(min(segments[index][0][axis], segments[index][1][axis]) for index in indices) for axis in range(3)) maximum = tuple(max(max(segments[index][0][axis], segments[index][1][axis]) for index in indices) for axis in range(3)) if len(indices) <= 8: return {"minimum": minimum, "maximum": maximum, "segments": tuple(sorted(indices)), "left": None, "right": None} axis = max(range(3), key=lambda value: maximum[value] - minimum[value]) indices.sort(key=lambda index: (segments[index][0][axis] + segments[index][1][axis]) * 0.5) middle = len(indices) // 2 return {"minimum": minimum, "maximum": maximum, "segments": None, "left": build(indices[:middle]), "right": build(indices[middle:])} if not segments: raise ValueError("lattice audit route has no measurable segments") return build(list(range(len(segments)))) def make_lattice_route(layout: dict, phase_m: float = 0.0) -> dict: edges = sorted((edge for edge in layout.get("cavern_edges", []) if edge["stable_id"] in ("lattice/spine/00-arrival-portal-v1", "lattice/spine/01-helix-ramp-v1", "lattice/spine/99-departure-portal-v1") or edge["stable_id"].startswith("lattice/spine/maze-edge-")), key=lambda edge: (0 if edge["stable_id"].startswith("lattice/spine/00-") else 1 if edge["stable_id"].startswith("lattice/spine/01-") else 2 if edge["stable_id"].startswith("lattice/spine/maze-edge-") else 3, edge["stable_id"])) if not edges: raise ValueError("lattice audit needs its resolved mandatory spine edges") segments = [] routes = [] for edge_index, edge in enumerate(edges): points = [[value / 1_000_000.0 for value in row] for row in edge.get("polyline_um", [])] if len(points) < 2: raise ValueError(f"{edge['stable_id']}: explicit path is missing") radius = edge["bore_radius_um"] / 1_000_000.0 height = edge["bore_height_um"] / 1_000_000.0 support_depth = edge.get("floor_support_depth_um", 0) / 1_000_000.0 horizontal_s = [0.0] for a, b in zip(points, points[1:]): horizontal_s.append(horizontal_s[-1] + ((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2) ** 0.5) first = points[1] first_dx, first_dy = first[0] - points[0][0], first[1] - points[0][1] first_length = max(1.0e-9, (first_dx * first_dx + first_dy * first_dy) ** 0.5) extension = (points[0][0] - first_dx / first_length * audit54.OPEN_GROUND_EXTENSION_M, points[0][1] - first_dy / first_length * audit54.OPEN_GROUND_EXTENSION_M, points[0][2] - (first[2] - points[0][2]) / first_length * audit54.OPEN_GROUND_EXTENSION_M) route_points = [extension, *points] route_s = [-audit54.OPEN_GROUND_EXTENSION_M, *horizontal_s] for index, (a, b) in enumerate(zip(route_points, route_points[1:])): segments.append((a, b, route_s[index], route_s[index + 1], radius, height, edge_index)) routes.append({"edge": edge, "points": points, "radius": radius, "height": height, "support_depth": support_depth, "length": horizontal_s[-1], "index": edge_index}) # The shaft core is an open cylindrical volume, not a walkable room. Its # axis-aligned bounds include unrelated rock outside the cylinder and # above the ramp headroom; the helix corridor already includes the inner # wall, lip, foot, and underside where those surfaces meet the route. rooms = [{"room": room, "center": tuple(value / 1_000_000.0 for value in room["center_um"]), "half": tuple(value / 1_000_000.0 for value in room["half_extents_um"]), "floor": room["floor_um"] / 1_000_000.0} for room in sorted(layout.get("cavern_rooms", []), key=lambda row: row["stable_id"]) if room.get("stable_id") != "lattice/spine/shaft-core-v2"] return {"segments": segments, "edges": routes, "rooms": rooms, "phase_m": phase_m, "segment_bvh": build_segment_bvh(segments)} def make_route(layout: dict, phase_m: float = 0.0) -> dict: if any(edge.get("stable_id", "").startswith("lattice/") for edge in layout.get("cavern_edges", [])): return make_lattice_route(layout, phase_m) return original_make_route(layout, phase_m) def lattice_route_nearest(point: tuple[float, float, float], route: dict): """Resolve stacked helix turns in 3D, then measure corridor radius in XY.""" best = None def consider(segment_index: int) -> None: nonlocal best a, b, s0, s1, radius, height, edge_index = route["segments"][segment_index] dx, dy, dz = (b[axis] - a[axis] for axis in range(3)) length_squared = dx * dx + dy * dy + dz * dz t = 0.0 if length_squared <= 1.0e-12 else max(0.0, min(1.0, ((point[0] - a[0]) * dx + (point[1] - a[1]) * dy + (point[2] - a[2]) * dz) / length_squared)) x, y, z = (a[axis] + (b[axis] - a[axis]) * t for axis in range(3)) distance3d_squared = ((point[0] - x) ** 2 + (point[1] - y) ** 2 + (point[2] - z) ** 2) if (best is None or distance3d_squared < best[0] or (distance3d_squared == best[0] and segment_index < best[1])): horizontal_length = math.hypot(dx, dy) lateral = ((dx * (point[1] - y) - dy * (point[0] - x)) / horizontal_length if horizontal_length > 1.0e-9 else 0.0) best = (distance3d_squared, segment_index, math.hypot(point[0] - x, point[1] - y), s0 + (s1 - s0) * t, z, lateral, radius, height, edge_index, route["edges"][edge_index]["length"]) def lower_bound_squared(node: dict) -> float: distance_squared = 0.0 for axis in range(3): gap = (node["minimum"][axis] - point[axis] if point[axis] < node["minimum"][axis] else point[axis] - node["maximum"][axis] if point[axis] > node["maximum"][axis] else 0.0) distance_squared += gap * gap return distance_squared def visit(node: dict) -> None: if best is not None and lower_bound_squared(node) > best[0]: return if node["segments"] is not None: for segment_index in node["segments"]: consider(segment_index) return left, right = node["left"], node["right"] left_bound, right_bound = lower_bound_squared(left), lower_bound_squared(right) if left_bound <= right_bound: visit(left) visit(right) else: visit(right) visit(left) visit(route["segment_bvh"]) if best is None: raise ValueError("lattice audit route has no measurable segments") return best[2:] def mark_lattice_region(triangles: list, route: dict) -> None: margin = audit54.CORRIDOR_MARGIN_M extension = audit54.OPEN_GROUND_EXTENSION_M bounds = [] for edge in route["edges"]: points = edge["points"] radius, height = edge["radius"], edge["height"] support_depth = edge["support_depth"] bounds.append(( min(point[0] for point in points) - radius - margin, max(point[0] for point in points) + radius + margin, min(point[1] for point in points) - radius - margin, max(point[1] for point in points) + radius + margin, min(point[2] for point in points) - support_depth - margin, max(point[2] for point in points) + height + margin)) for room in route["rooms"]: center, half, floor = room["center"], room["half"], room["floor"] bounds.append((center[0] - half[0] - margin, center[0] + half[0] + margin, center[1] - half[1] - margin, center[1] + half[1] + margin, floor - margin, floor + 2.0 * half[2] + margin)) for triangle in triangles: x, y, z = triangle.center if not any(low_x <= x <= high_x and low_y <= y <= high_y and low_z <= z <= high_z for low_x, high_x, low_y, high_y, low_z, high_z in bounds): triangle.in_region = False triangle.reference = False continue nearest = lattice_route_nearest(triangle.center, route) distance, distance_s, floor_z, lateral, radius, height, edge_index, edge_length = nearest support_depth = route["edges"][edge_index]["support_depth"] corridor = (-extension <= distance_s <= edge_length and distance <= radius + margin and floor_z - support_depth - margin <= z <= floor_z + height + margin) room_box = any(abs(x - room["center"][0]) <= room["half"][0] + margin and abs(y - room["center"][1]) <= room["half"][1] + margin and room["floor"] - margin <= z <= room["floor"] + 2.0 * room["half"][2] + margin for room in route["rooms"]) triangle.in_region = corridor or room_box inside_room = any(abs(x - room["center"][0]) <= room["half"][0] + 0.45 and abs(y - room["center"][1]) <= room["half"][1] + 0.45 for room in route["rooms"]) outside_bore = distance > radius + 0.45 and not inside_room triangle.reference = (triangle.in_region and outside_bore and triangle.smooth[2] >= 0.45) def main() -> int: 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) parser.add_argument("--phase", type=float, choices=(-0.5, 0.0, 0.5), default=0.0) args = parser.parse_args() audit54.make_route = make_route 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") native_lattice = layout != "-" and any( edge.get("stable_id", "").startswith("lattice/") for edge in json.loads(Path(layout).read_text(encoding="utf-8")).get( "cavern_edges", [])) original_margin = audit54.CORRIDOR_MARGIN_M original_extension = audit54.OPEN_GROUND_EXTENSION_M original_nearest = audit54.route_nearest original_mark_region = audit54.mark_region if native_lattice: # Keep the whole-surface test on native records: one core voxel of # tolerance captures their meshed walls while excluding the separate # legacy transition above the resolved mouth. audit54.CORRIDOR_MARGIN_M = 0.25 audit54.OPEN_GROUND_EXTENSION_M = 0.0 audit54.route_nearest = lattice_route_nearest audit54.mark_region = mark_lattice_region try: results.append(audit54.audit_case(label, Path(mesh), Path(layout), expect, focus, args.svg_dir, args.phase)) finally: audit54.CORRIDOR_MARGIN_M = original_margin audit54.OPEN_GROUND_EXTENSION_M = original_extension audit54.route_nearest = original_nearest audit54.mark_region = original_mark_region if native_lattice: print("[L59LatticeAuditScope] margin_m=0.250 ramp_underside_depth_m=1.000 " "outside_mouth_extension_m=0.000 nearest_centerline=3D " "radius_measure=horizontal status=PASS") args.json.parent.mkdir(parents=True, exist_ok=True) args.json.write_text(json.dumps({"audit": "whole-surface-open-shaft-l59-v1", "cases": results}, indent=2) + "\n", encoding="utf-8") failures = [result for result in results if result["status"] != "PASS"] print(f"[L59WholeSurfaceAuditSummary] 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())