Motif vocabulary v3: version-1 records keyed by (seed, strate, stable ID) with route keepouts, band clearance and pairwise spacing. A rounded capped-cylinder monolith (r 1.4 m, 24 m tall) and a rounded-box plinth, smooth-unioned into a flatField floor with a 0.55 m geometric rounding (measured fillets 0.73-1.17 m). Exact radial/box interval bounds, a BVH, isTileShapeFree aware (0 brute-force misses, false-proof controls rejected). Final-field route passes; whole-surface audit covers foot, cap and platform edges. Cold whole view 0.524 s. Owner world unchanged. fast/medium/full PASS (Clang and GCC). Research R25 (self-checked): FloatingIslands LOD0 column cache conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
214 lines
10 KiB
Python
214 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""Audit LOD0 triangles in the resolved monolith and platform surface ROIs."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import array
|
|
import json
|
|
import math
|
|
from pathlib import Path
|
|
import struct
|
|
import sys
|
|
|
|
|
|
def solve3(matrix, vector):
|
|
rows = [list(matrix[i]) + [vector[i]] for i in range(3)]
|
|
for column in range(3):
|
|
pivot = max(range(column, 3), key=lambda row: abs(rows[row][column]))
|
|
if abs(rows[pivot][column]) < 1.0e-10:
|
|
return None
|
|
rows[column], rows[pivot] = rows[pivot], rows[column]
|
|
scale = rows[column][column]
|
|
rows[column] = [value / scale for value in rows[column]]
|
|
for row in range(3):
|
|
if row == column:
|
|
continue
|
|
scale = rows[row][column]
|
|
rows[row] = [rows[row][i] - scale * rows[column][i] for i in range(4)]
|
|
return [rows[row][3] for row in range(3)]
|
|
|
|
|
|
def fit_radius(points, center_x, center_z, center_y, window=1.05, y_window=1.3):
|
|
selected = sorted({(round(x, 4), round(z, 4)) for x, y, z in points
|
|
if abs(y - center_y) <= y_window
|
|
and abs(x - center_x) <= window and abs(z - center_z) <= window})
|
|
if len(selected) < 8:
|
|
return None, len(selected)
|
|
sums = [0.0] * 9
|
|
rhs = [0.0] * 3
|
|
for x, z in selected:
|
|
row = (x, z, 1.0)
|
|
target = -(x * x + z * z)
|
|
for i in range(3):
|
|
rhs[i] += row[i] * target
|
|
for j in range(3):
|
|
sums[i * 3 + j] += row[i] * row[j]
|
|
solution = solve3([sums[0:3], sums[3:6], [sums[6], sums[7], sums[8]]], rhs)
|
|
if solution is None:
|
|
return None, len(selected)
|
|
a, b, c = solution
|
|
radius_squared = (a * a + b * b) * 0.25 - c
|
|
if radius_squared <= 0.0:
|
|
return None, len(selected)
|
|
return math.sqrt(radius_squared), len(selected)
|
|
|
|
|
|
def read_mesh(path: Path):
|
|
triangles = []
|
|
vertices_all = []
|
|
selected_tiles = 0
|
|
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 header")
|
|
version, voxel_um, tile_count = struct.unpack_from("<3I", prefix, 8)
|
|
if version != 1 or voxel_um <= 0 or tile_count > 1_000_000:
|
|
raise ValueError(f"{path}: invalid mesh version or size")
|
|
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, _aux_count, vertex_count, index_count, mask_count = \
|
|
struct.unpack_from("<5I", header, 16)
|
|
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)
|
|
indices = array.array("I")
|
|
indices.frombytes(index_bytes)
|
|
if sys.byteorder != "little":
|
|
positions.byteswap()
|
|
indices.byteswap()
|
|
points = [tuple(float(positions[index * 3 + axis])
|
|
for axis in range(3)) for index in range(vertex_count)]
|
|
vertices_all.extend(points)
|
|
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(points[index] 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])
|
|
area_twice = math.sqrt(sum(value * value for value in cross))
|
|
if area_twice <= 1.0e-10:
|
|
continue
|
|
center = tuple(sum(vertex[axis] for vertex in verts) / 3.0
|
|
for axis in range(3))
|
|
triangles.append((center, area_twice * 0.5, verts))
|
|
if stream.read(1):
|
|
raise ValueError(f"{path}: trailing mesh data")
|
|
return triangles, vertices_all, selected_tiles
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("meshes", type=Path, nargs="+")
|
|
parser.add_argument("layout", type=Path)
|
|
parser.add_argument("--view", required=True, choices=("combined",))
|
|
args = parser.parse_args()
|
|
layout = json.loads(args.layout.read_text(encoding="utf-8"))
|
|
motifs = layout.get("stamps", [])
|
|
monolith = next(row for row in motifs if row["kind"] == 5)
|
|
platform = next(row for row in motifs if row["kind"] == 6)
|
|
mesh_rows = [read_mesh(path) for path in args.meshes]
|
|
triangles = [triangle for row in mesh_rows for triangle in row[0]]
|
|
vertices = [vertex for row in mesh_rows for vertex in row[1]]
|
|
tiles = sum(row[2] for row in mesh_rows)
|
|
if not triangles:
|
|
raise ValueError("meshes have no LOD0 surface triangles")
|
|
|
|
def metres(point):
|
|
return tuple(value / 1_000_000.0 for value in point)
|
|
|
|
mono = metres(monolith["position_um"])
|
|
floor = monolith["floor_um"] / 1_000_000.0
|
|
radius = monolith["half_extents_um"][0] / 1_000_000.0
|
|
half_height = monolith["half_extents_um"][2] / 1_000_000.0
|
|
mono_round = monolith["rounding_radius_um"] / 1_000_000.0
|
|
plat = metres(platform["position_um"])
|
|
half = tuple(value / 1_000_000.0 for value in platform["half_extents_um"])
|
|
plat_round = platform["rounding_radius_um"] / 1_000_000.0
|
|
top = floor + 2.0 * half_height
|
|
|
|
rois = {
|
|
"monolith_foot": lambda p: math.hypot(p[0] - mono[0], p[1] - mono[1]) <= radius + 1.4
|
|
and floor - 1.0 <= p[2] <= floor + 2.1,
|
|
"monolith_cap": lambda p: math.hypot(p[0] - mono[0], p[1] - mono[1]) <= radius + 1.6
|
|
and top - 2.0 <= p[2] <= top + 1.1,
|
|
"platform_edges": lambda p: abs(p[0] - plat[0]) <= half[0] + 1.2
|
|
and abs(p[1] - plat[1]) <= half[1] + 1.2
|
|
and floor - 0.8 <= p[2] <= floor + 2.4 * half[2] + 0.8,
|
|
}
|
|
roi_counts = {name: sum(1 for center, _area, _verts in triangles if predicate(center))
|
|
for name, predicate in rois.items()}
|
|
bad_area = sum(1 for _center, area, _verts in triangles if not math.isfinite(area) or area <= 0.0)
|
|
foot_radius, foot_points = fit_radius(vertices, mono[0] + radius - mono_round,
|
|
floor + mono_round, mono[1], window=1.1)
|
|
cap_radius, cap_points = fit_radius(vertices, mono[0] + radius - mono_round,
|
|
top - mono_round, mono[1], window=1.1)
|
|
platform_radius, platform_points = fit_radius(vertices,
|
|
plat[0] + half[0] - plat_round, floor + 2.0 * half[2] - plat_round,
|
|
plat[1], window=1.1)
|
|
radii = (foot_radius, cap_radius, platform_radius)
|
|
fit_counts = (foot_points, cap_points, platform_points)
|
|
def surface_pass(tile_count, degenerate_count, rois, measured_radii):
|
|
covered = all(count >= 8 for count in rois.values())
|
|
rounded = all(value is not None and value >= 0.5 for value in measured_radii)
|
|
return tile_count > 0 and degenerate_count == 0 and covered and rounded
|
|
|
|
radius_ok = all(value is not None and value >= 0.5 for value in radii)
|
|
coverage_ok = all(count >= 8 for count in roi_counts.values())
|
|
missing_roi = dict(roi_counts)
|
|
missing_roi["monolith_foot"] = 0
|
|
sharp_edge = list(radii)
|
|
sharp_edge[0] = 0.25
|
|
controls_rejected = (
|
|
not surface_pass(tiles, bad_area, missing_roi, radii)
|
|
and not surface_pass(tiles, bad_area, roi_counts, sharp_edge)
|
|
and not surface_pass(0, 0, roi_counts, radii)
|
|
)
|
|
passed = surface_pass(tiles, bad_area, roi_counts, radii) and controls_rejected
|
|
for name, count in roi_counts.items():
|
|
print(f"[MotifSurfaceROI] view={args.view} name={name} triangles={count} "
|
|
f"whole_surface=LOD0 status={'PASS' if count >= 8 else 'FAIL'}")
|
|
if all(value is not None for value in radii):
|
|
print(f"[MotifFilletMeasure] view={args.view} foot_radius_m={foot_radius:.3f} "
|
|
f"cap_radius_m={cap_radius:.3f} platform_radius_m={platform_radius:.3f} "
|
|
f"fit_points={fit_counts} target_min_m=0.500")
|
|
else:
|
|
print(f"[MotifFilletMeasure] view={args.view} foot_radius_m={foot_radius} "
|
|
f"cap_radius_m={cap_radius} platform_radius_m={platform_radius} "
|
|
f"fit_points={fit_counts} target_min_m=0.500")
|
|
print(f"[MotifSurfaceAudit] view={args.view} meshes={len(mesh_rows)} lod0_tiles={tiles} triangles={len(triangles)} "
|
|
f"degenerate={bad_area} feature_rois={len(roi_counts)} covered={coverage_ok} "
|
|
f"fillets_ge_0.5m={radius_ok} missing_roi_control={'REJECTED' if not surface_pass(tiles, bad_area, missing_roi, radii) else 'FAILED'} "
|
|
f"sharp_fillet_control={'REJECTED' if not surface_pass(tiles, bad_area, roi_counts, sharp_edge) else 'FAILED'} "
|
|
f"empty_mesh_control={'REJECTED' if not surface_pass(0, 0, roi_counts, radii) else 'FAILED'} "
|
|
f"negative_controls={'REJECTED' if controls_rejected else 'FAILED'} "
|
|
f"status={'PASS' if passed else 'FAIL'}")
|
|
return 0 if passed else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except Exception as error:
|
|
print(f"[MotifSurfaceAudit] status=FAIL error={error}", file=sys.stderr)
|
|
raise SystemExit(1)
|