Files
VoxelForge/Standalone/Research/WorldAssemblerM3/Linux17/BoundaryProbe.py
T
Fr0zkaandClaude Opus 5.5 34ae1d7187 wip(L17, side branch): FloatingIslands seal-cap fix + owner/Core chunk-scale change (breaks SurfaceWorld)
Not for assembler/world: the 8 m -> 4 m strate chunk change moves every stratum, so the SurfaceWorld
spawn camera floats in a void and the L1 mesh export shrank 58.7 -> 31.7 MB. Kept for its
diagnostics, the boundarySeal finding and the readable capture shading. See Linux17/REPORT.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-20 05:52:59 +02:00

90 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Measure upward mesh faces around the FI/SW stratum interface."""
import array
import collections
import math
import struct
import sys
def read_exact(stream, size):
data = stream.read(size)
if len(data) != size:
raise EOFError(f"wanted {size} bytes, got {len(data)}")
return data
path = sys.argv[1]
low_z = float(sys.argv[2]) if len(sys.argv) > 2 else -80.0
high_z = float(sys.argv[3]) if len(sys.argv) > 3 else -45.0
with open(path, "rb") as stream:
if read_exact(stream, 8) != b"VFMSH001":
raise SystemExit("bad VFMSH001 header")
version, voxel_um, tile_count = struct.unpack("<3I", read_exact(stream, 12))
camera = struct.unpack("<3d", read_exact(stream, 24))
yaw, pitch = struct.unpack("<2d", read_exact(stream, 16))
per_lod = collections.defaultdict(lambda: {"tiles": 0, "faces": [], "spans": []})
near_tiles = collections.defaultdict(list)
for _ in range(tile_count):
key = struct.unpack("<4i", read_exact(stream, 16))
surf_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", read_exact(stream, 20))
p = array.array("f")
p.frombytes(read_exact(stream, vertex_count * 12))
n = array.array("f")
n.frombytes(read_exact(stream, vertex_count * 12))
if sys.byteorder != "little":
p.byteswap(); n.byteswap()
indices = array.array("I")
indices.frombytes(read_exact(stream, index_count * 4))
masks = read_exact(stream, mask_count)
if sys.byteorder != "little":
indices.byteswap()
lod = key[3]
z_values = []
for tri in range(index_count // 3):
if masks and masks[tri] != 0:
continue
a, b, c = indices[tri * 3:tri * 3 + 3]
if max(a, b, c) >= surf_vertices:
continue
normal_z = (n[a * 3 + 2] + n[b * 3 + 2] + n[c * 3 + 2]) / 3.0
if normal_z < 0.65:
continue
z = (p[a * 3 + 2] + p[b * 3 + 2] + p[c * 3 + 2]) / 3.0
if low_z <= z <= high_z:
z_values.append(z)
if not z_values:
continue
z_values.sort()
row = per_lod[lod]
row["tiles"] += 1
row["faces"].extend(z_values)
span = z_values[math.floor(.95 * (len(z_values) - 1))] - z_values[math.floor(.05 * (len(z_values) - 1))]
row["spans"].append(span)
extent = 32 * (1 << lod) * voxel_um / 1_000_000.0
cx = (key[0] + 0.5) * extent
cy = (key[1] + 0.5) * extent
near_tiles[lod].append((math.hypot(cx - camera[0], cy - camera[1]), key,
z_values[len(z_values) // 2], span, len(z_values)))
def q(values, fraction):
return values[math.floor(fraction * (len(values) - 1))] if values else math.nan
print(f"camera_m={camera} yaw={yaw} pitch={pitch} boundary_band_m={low_z}..{high_z}")
print("lod tiles_with_boundary_faces faces z_m_p05/median/p95 tile_span_m_p50/p95/max")
for lod in sorted(per_lod):
row = per_lod[lod]
spans = sorted(row["spans"])
print(f"{lod} {row['tiles']} {len(row['faces'])} "
f"{q(sorted(row['faces']), .05):.4f}/{q(sorted(row['faces']), .5):.4f}/"
f"{q(sorted(row['faces']), .95):.4f} "
f"{q(spans, .5):.4f}/{q(spans, .95):.4f}/{max(spans):.4f}")
print("nearest_xy_boundary_tiles_by_lod:")
for lod in sorted(near_tiles):
for distance, key, median, span, faces in sorted(near_tiles[lod])[:3]:
print(f"lod={lod} key={key} xy_distance_m={distance:.2f} "
f"boundary_median_z={median:.4f} within_tile_p05_p95_m={span:.4f} faces={faces}")