Files
VoxelForge/Standalone/Research/WorldAssemblerM3/Linux31/MeasureNormalsL31.py
T
Fr0zkaandClaude Opus 5.5 28069f0d98 wip(assembler): round L31 -- FI recipe column audit corrected; recipe-stamped audit runner
On the real FloatingIslands recipe, column caps sit inside anchor-island rock and native bedrock;
the old check sampled the route foot. The audit now samples each planned cap against the native
field (negative control: a cap moved into air is detected). The audit runner records requested vs
actual recipe and exits on mismatch. All 16 passage rows (4 styles x 2 recipes x 2 compilers) pass.
The readable shader uses a smooth tint. Interim: FI Gradient displacement capped at width/256
(flat floor, no contour rings); restoring real undulation is L32. Evaluator 19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-21 14:13:18 +02:00

79 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Measure surface heights and normals on an eastward FI floor radial line."""
import array
import math
import struct
import sys
def read_surface_vertices(path):
vertices = []
with open(path, "rb") as stream:
if stream.read(8) != b"VFMSH001":
raise SystemExit(f"{path}: invalid scene header")
version, voxel_um, count = struct.unpack("<3I", stream.read(12))
stream.read(40)
if version != 1 or count > 100000:
raise SystemExit("unsupported scene")
for _ in range(count):
key = struct.unpack("<4i", stream.read(16))
surface_count, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
positions = array.array("f")
positions.frombytes(stream.read(vertex_count * 12))
normals = array.array("f")
normals.frombytes(stream.read(vertex_count * 12))
indices = array.array("I")
indices.frombytes(stream.read(index_count * 4))
masks = stream.read(mask_count)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
used = set()
for tri in range(0, index_count, 3):
if masks and masks[tri // 3]:
continue
for index in indices[tri:tri + 3]:
if index < surface_count:
used.add(index)
for index in used:
offset = index * 3
vertices.append((positions[offset], positions[offset + 1],
positions[offset + 2], normals[offset],
normals[offset + 1], normals[offset + 2]))
return voxel_um, vertices
def main():
scene = sys.argv[1]
center_x, center_y = 64.0, -32.0
voxel_um, vertices = read_surface_vertices(scene)
floor = [v for v in vertices if abs(v[2] + 119.5) < 0.05
and math.hypot(v[0] - center_x, v[1] - center_y) <= 16.5]
if not floor:
raise SystemExit("no floor surface vertices near expected FI bedrock top")
mean = tuple(sum(v[index] for v in floor) / len(floor) for index in (3, 4, 5))
normal_rms = math.sqrt(sum(sum((v[index] - mean[axis]) ** 2
for axis, index in enumerate((3, 4, 5)))
for v in floor) / len(floor))
min_z = min(v[2] for v in floor)
max_z = max(v[2] for v in floor)
print(f"[RingNormalField] voxel_um={voxel_um} center_m=({center_x:.3f},{center_y:.3f}) "
f"vertices={len(floor)} height_min_m={min_z:.6f} height_max_m={max_z:.6f} "
f"mean_normal=({mean[0]:.8f},{mean[1]:.8f},{mean[2]:.8f}) "
f"normal_rms={normal_rms:.9f}")
print("[RingNormalRadialLine] radius_m sampled_radius_m height_m nx ny nz")
for radius in range(1, 17):
x, y = center_x + radius, center_y
vertex = min(floor, key=lambda v: (v[0] - x) ** 2 + (v[1] - y) ** 2)
sampled_radius = math.hypot(vertex[0] - center_x, vertex[1] - center_y)
if math.hypot(vertex[0] - x, vertex[1] - y) > 0.5:
raise SystemExit(f"missing near vertex at radius {radius} m")
print(f"{radius:2d} {sampled_radius:.6f} {vertex[2]:.6f} "
f"{vertex[3]:.8f} {vertex[4]:.8f} {vertex[5]:.8f}")
if __name__ == "__main__":
main()