feat(core): native lattice generator, slice 1 -- helix-ramp spine + small maze (L58); research R27
Core.Lattice.PrimitiveField v1, opt-in per strate (legacy Maze/VerticalShaft stay legacy): resolves the real arrival and departure mouths first, plans a mandatory spine, then maze cells and two dead ends (9 rooms, 9 edges, stable IDs). The spine descends 20 m through four helix turns (6 m radius, 3 m clear width, 5 m pitch, 2.6 m clearance; max grade 10.0 deg); each feature is one filleted void (0.57 m measured). Final-field reachability passes at 883 stations; an orphaned shaft control is rejected; exact intervals (0 misses) and a whole-surface audit with 0 flags. Whole view 1.69 s (above the 1 s target). fast/medium/full PASS. Research R27 (self-checked): static stream-to-pool water design. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
1e3e04bbac
commit
6c6cc37e1d
25 files changed
+280497
-117
No files matched your search
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run L54's triangle audit over L58's explicit lattice polylines 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
|
||||
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, "length": horizontal_s[-1], "index": edge_index})
|
||||
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"])]
|
||||
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"]
|
||||
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) - 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
|
||||
corridor = (-extension <= distance_s <= edge_length
|
||||
and distance <= radius + margin
|
||||
and floor_z - 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("[L58LatticeAuditScope] margin_m=0.250 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-lattice-l58-v1",
|
||||
"cases": results}, indent=2) + "\n", encoding="utf-8")
|
||||
failures = [result for result in results if result["status"] != "PASS"]
|
||||
print(f"[L58WholeSurfaceAuditSummary] 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())
|
||||
Reference in new issue
Block a user