wip(assembler): round L32 -- smooth Gradient interface at full amplitude (no rings, not flat)
Band faces now use signed distance to the displaced solid intervals, continuous across bands, so MC interpolates the interface instead of terracing at density levels. The Gradient amplitude is restored to width/8 (about 4 m peak-to-peak at 16 m). Ring/terrace audit at LOD0-8 on the FI floor and SW ceiling passes; negative controls: L30 (rings) and L31 (flat) both fail. Hole/interface audits, 16 passage rows and 24/24 determinism pass. Evaluator 20. Cost vs L31 SW +1.9%, FI +1.5%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
No files matched your search
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Audit the displaced floor across the passage-mouth fade edge."""
|
||||
import argparse
|
||||
import math
|
||||
import resource
|
||||
import sys
|
||||
|
||||
import AuditRingTerraceL32 as ring
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("scene")
|
||||
parser.add_argument("--center", nargs=2, type=float, required=True)
|
||||
parser.add_argument("--lod", type=int, default=0)
|
||||
parser.add_argument("--inner-radius-m", type=float, default=5.5)
|
||||
parser.add_argument("--outer-radius-m", type=float, default=9.5)
|
||||
parser.add_argument("--step-m", type=float, default=0.125)
|
||||
parser.add_argument("--height-min-m", type=float, default=-122.5)
|
||||
parser.add_argument("--height-max-m", type=float, default=-117.5)
|
||||
parser.add_argument("--preferred-height-m", type=float, default=-119.5)
|
||||
parser.add_argument("--highest-surface", action="store_true",
|
||||
help="choose the topmost upward face inside the height window")
|
||||
parser.add_argument("--plateau-min-m", type=float, default=0.5)
|
||||
parser.add_argument("--step-threshold-m", type=float, default=0.1)
|
||||
parser.add_argument("--normal-jump-max-deg", type=float, default=20.0)
|
||||
args = parser.parse_args()
|
||||
if (args.inner_radius_m < 0.0 or args.outer_radius_m <= args.inner_radius_m
|
||||
or args.step_m <= 0.0):
|
||||
parser.error("invalid radial interval or sample spacing")
|
||||
|
||||
cx, cy = args.center
|
||||
preferred_height = None if args.highest_surface else args.preferred_height_m
|
||||
bounds = (cx - args.outer_radius_m, cx + args.outer_radius_m,
|
||||
cy - args.outer_radius_m, cy + args.outer_radius_m)
|
||||
voxel_um, triangles, selected_tiles = ring.read_scene(
|
||||
args.scene, args.lod, args.height_min_m, args.height_max_m, bounds)
|
||||
bucket_size = max(2.0, 16.0 * voxel_um / 1_000_000.0 * (1 << args.lod))
|
||||
buckets = ring.build_buckets(triangles, True, bounds, bucket_size)
|
||||
|
||||
count = round((args.outer_radius_m - args.inner_radius_m) / args.step_m)
|
||||
lines = []
|
||||
for angle_degrees in (135.0, 180.0, 225.0, 270.0, 315.0):
|
||||
angle = math.radians(angle_degrees)
|
||||
points = [(cx + radius * math.cos(angle), cy + radius * math.sin(angle))
|
||||
for radius in (args.inner_radius_m + i * args.step_m
|
||||
for i in range(count + 1))]
|
||||
lines.append((f"radial-{angle_degrees:.0f}deg", points))
|
||||
|
||||
# Tangential sections stay outside the open mouth while crossing the outer
|
||||
# fade edge on both sides of the 6.5 m center radius.
|
||||
tangent_count = round(8.0 / args.step_m)
|
||||
tangent_values = [-4.0 + i * args.step_m for i in range(tangent_count + 1)]
|
||||
for angle_degrees in (225.0, 315.0):
|
||||
angle = math.radians(angle_degrees)
|
||||
normal = (math.cos(angle), math.sin(angle))
|
||||
tangent = (-normal[1], normal[0])
|
||||
points = [(cx + 6.5 * normal[0] + t * tangent[0],
|
||||
cy + 6.5 * normal[1] + t * tangent[1])
|
||||
for t in tangent_values]
|
||||
lines.append((f"straight-tangent-{angle_degrees:.0f}deg", points))
|
||||
|
||||
failed = False
|
||||
max_plateau = 0.0
|
||||
max_step = 0.0
|
||||
max_normal = 0.0
|
||||
min_coverage = 1.0
|
||||
for name, points in lines:
|
||||
profile = ring.audit_profile(points, buckets, True, args.plateau_min_m,
|
||||
0.0005, args.step_threshold_m, bucket_size, preferred_height)
|
||||
samples = [ring.sample_surface(buckets, x, y, True, bucket_size,
|
||||
preferred_height)
|
||||
for x, y in points]
|
||||
for index in range(1, len(samples)):
|
||||
prior, current = samples[index - 1], samples[index]
|
||||
if prior is None or current is None:
|
||||
continue
|
||||
dot = max(-1.0, min(1.0, sum(a * b for a, b in zip(prior[1], current[1]))))
|
||||
delta = math.degrees(math.acos(dot))
|
||||
if delta > args.normal_jump_max_deg:
|
||||
px, py = points[index - 1]
|
||||
x, y = points[index]
|
||||
print(f"[PassageFadeNormalJump] line={name} "
|
||||
f"radius_m={math.hypot(x-cx,y-cy):.3f} "
|
||||
f"from_m=({px:.3f},{py:.3f},{prior[0]:.3f}) "
|
||||
f"to_m=({x:.3f},{y:.3f},{current[0]:.3f}) "
|
||||
f"normal_delta_deg={delta:.3f}")
|
||||
coverage = profile["coverage"]
|
||||
min_coverage = min(min_coverage, coverage)
|
||||
plateau = max((run[0] for run in profile["plateaus"]), default=0.0)
|
||||
step = max((run[1] for run in profile["plateaus"]), default=0.0)
|
||||
angle = profile["max_angle"]
|
||||
max_plateau = max(max_plateau, plateau)
|
||||
max_step = max(max_step, step)
|
||||
max_normal = max(max_normal, angle)
|
||||
ok = (profile["valid"] and plateau < args.plateau_min_m
|
||||
and angle <= args.normal_jump_max_deg)
|
||||
failed |= not ok
|
||||
print(f"[PassageFadeProfile] line={name} coverage={coverage:.3f} "
|
||||
f"height_range_m={profile.get('height_range', 0.0):.6f} "
|
||||
f"flat_step_runs={len(profile['plateaus'])} "
|
||||
f"max_normal_delta_deg={angle:.6f} status={'PASS' if ok else 'FAIL'}")
|
||||
|
||||
passed = not failed and min_coverage >= 0.95
|
||||
print(f"[PassageFadeRingAudit] scene={args.scene} lod={args.lod} "
|
||||
f"center_m=({cx:.6f},{cy:.6f}) mouth_radius_m=4.500 "
|
||||
f"fade_m=5.500..7.500 radial_m={args.inner_radius_m:.3f}.."
|
||||
f"{args.outer_radius_m:.3f} lines={len(lines)} selected_tiles={selected_tiles} "
|
||||
f"coverage_min={min_coverage:.3f} longest_flat_run_m={max_plateau:.6f} "
|
||||
f"following_step_m={max_step:.6f} max_normal_delta_deg={max_normal:.6f} "
|
||||
f"thresholds=(coverage>=0.950,plateau<0.500,step>=0.100,normal<=20.000) "
|
||||
f"status={'PASS' if passed else 'FAIL'}")
|
||||
print(f"[PassageFadeRSS] peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
|
||||
raise SystemExit(0 if passed else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Audit several bounded face windows and combine their height range."""
|
||||
import argparse
|
||||
import gc
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import resource
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("scene")
|
||||
parser.add_argument("--surface", choices=("floor", "ceiling"), required=True)
|
||||
parser.add_argument("--center", nargs=2, type=float, action="append", required=True,
|
||||
help="repeat for each bounded local ring")
|
||||
parser.add_argument("--radius", type=float, default=8.0)
|
||||
parser.add_argument("--lod", type=int, required=True)
|
||||
parser.add_argument("--height-min-m", type=float, required=True)
|
||||
parser.add_argument("--height-max-m", type=float, required=True)
|
||||
parser.add_argument("--preferred-height-m", type=float,
|
||||
help="lock overlapping-face selection near this expected surface height")
|
||||
parser.add_argument("--step", type=float, default=0.125)
|
||||
parser.add_argument("--plateau-min-m", type=float, default=0.5)
|
||||
parser.add_argument("--plateau-epsilon-m", type=float, default=0.0005)
|
||||
parser.add_argument("--step-threshold-m", type=float, default=0.1)
|
||||
parser.add_argument("--normal-jump-max-deg", type=float, default=20.0)
|
||||
parser.add_argument("--amplitude-min-m", type=float, default=3.0)
|
||||
parser.add_argument("--min-radial-lines-per-center", type=int, default=4)
|
||||
parser.add_argument("--min-straight-lines-per-center", type=int, default=2)
|
||||
args = parser.parse_args()
|
||||
|
||||
audit_path = Path(__file__).with_name("AuditRingTerraceL32.py")
|
||||
spec = importlib.util.spec_from_file_location("ring_audit_l32_set", audit_path)
|
||||
audit = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(audit)
|
||||
|
||||
upward = args.surface == "floor"
|
||||
preferred_height = args.preferred_height_m
|
||||
if preferred_height is None:
|
||||
preferred_height = args.height_max_m if upward else args.height_min_m
|
||||
all_ranges = []
|
||||
overall_max_normal = 0.0
|
||||
overall_max_plateau = 0.0
|
||||
overall_max_step = 0.0
|
||||
overall_min_coverage = 1.0
|
||||
total_valid_lines = 0
|
||||
total_lines = 0
|
||||
failed = False
|
||||
for center in args.center:
|
||||
bounds = (center[0] - args.radius, center[0] + args.radius,
|
||||
center[1] - args.radius, center[1] + args.radius)
|
||||
voxel_um, triangles, selected_tiles = audit.read_scene(
|
||||
args.scene, args.lod, args.height_min_m, args.height_max_m, bounds)
|
||||
max_lod = max((triangle[0] for triangle in triangles), default=0)
|
||||
cell_size = max(2.0, 16.0 * voxel_um / 1_000_000.0 * (1 << max_lod))
|
||||
buckets = audit.build_buckets(triangles, upward, bounds, cell_size)
|
||||
results = []
|
||||
for name, points in audit.make_lines(*center, args.radius, args.step):
|
||||
result = audit.audit_profile(points, buckets, upward,
|
||||
args.plateau_min_m, args.plateau_epsilon_m,
|
||||
args.step_threshold_m, cell_size, preferred_height)
|
||||
results.append((name, points, result))
|
||||
|
||||
radial = [row for row in results if row[0].startswith("radial-") and row[2]["valid"]]
|
||||
straight = [row for row in results if row[0].startswith("straight-") and row[2]["valid"]]
|
||||
valid = [row for row in results if row[2]["valid"]]
|
||||
local_min = min((row[2]["height_min"] for row in valid), default=float("inf"))
|
||||
local_max = max((row[2]["height_max"] for row in valid), default=float("-inf"))
|
||||
local_amplitude = local_max - local_min
|
||||
local_plateau = max((run[0] for row in valid for run in row[2]["plateaus"]), default=0.0)
|
||||
local_step = max((run[1] for row in valid for run in row[2]["plateaus"]), default=0.0)
|
||||
local_normal = max((row[2]["max_angle"] for row in valid), default=float("inf"))
|
||||
local_coverage = min((row[2]["coverage"] for row in results), default=0.0)
|
||||
local_ok = (len(valid) == len(results)
|
||||
and len(radial) >= args.min_radial_lines_per_center
|
||||
and len(straight) >= args.min_straight_lines_per_center
|
||||
and local_plateau < args.plateau_min_m
|
||||
and local_normal <= args.normal_jump_max_deg)
|
||||
all_ranges.extend((row[2]["height_min"], row[2]["height_max"])
|
||||
for row in valid)
|
||||
overall_max_normal = max(overall_max_normal, local_normal)
|
||||
overall_max_plateau = max(overall_max_plateau, local_plateau)
|
||||
overall_max_step = max(overall_max_step, local_step)
|
||||
overall_min_coverage = min(overall_min_coverage, local_coverage)
|
||||
total_valid_lines += len(valid)
|
||||
total_lines += len(results)
|
||||
print(f"[RingSetWindow] center_m=({center[0]:.3f},{center[1]:.3f}) "
|
||||
f"radius_m={args.radius:.3f} tiles={selected_tiles} lines={len(valid)}/"
|
||||
f"{len(results)} radial={len(radial)} straight={len(straight)} "
|
||||
f"coverage_min={local_coverage:.3f} height_m={local_min:.6f}.."
|
||||
f"{local_max:.6f} amplitude_m={local_amplitude:.6f} "
|
||||
f"longest_flat_m={local_plateau:.6f} following_step_m={local_step:.6f} "
|
||||
f"max_normal_delta_deg={local_normal:.6f} "
|
||||
f"status={'PASS' if local_ok else 'FAIL'}")
|
||||
for name, _, result in results:
|
||||
print(f"[RingSetProfile] center_m=({center[0]:.3f},{center[1]:.3f}) "
|
||||
f"line={name} coverage={result['coverage']:.3f} "
|
||||
f"range_m={result.get('height_range', 0.0):.6f} "
|
||||
f"flat_step_runs={len(result['plateaus'])} "
|
||||
f"max_normal_delta_deg={result['max_angle']:.6f}")
|
||||
failed |= not local_ok
|
||||
del triangles, buckets, results, radial, straight, valid
|
||||
gc.collect()
|
||||
|
||||
if all_ranges:
|
||||
sampled_min = min(value[0] for value in all_ranges)
|
||||
sampled_max = max(value[1] for value in all_ranges)
|
||||
amplitude = sampled_max - sampled_min
|
||||
else:
|
||||
sampled_min = sampled_max = amplitude = 0.0
|
||||
passed = not failed and amplitude >= args.amplitude_min_m
|
||||
print(f"[RingSetAudit] scene={args.scene} surface={args.surface} lod={args.lod} "
|
||||
f"windows={len(args.center)} radius_m={args.radius:.3f} "
|
||||
f"height_window_m={args.height_min_m:.3f}..{args.height_max_m:.3f} "
|
||||
f"sampled_height_m={sampled_min:.6f}..{sampled_max:.6f} "
|
||||
f"amplitude_range_m={amplitude:.6f} lines={total_valid_lines}/{total_lines} "
|
||||
f"coverage_min={overall_min_coverage:.3f} "
|
||||
f"longest_flat_run_m={overall_max_plateau:.6f} "
|
||||
f"max_step_m={overall_max_step:.6f} "
|
||||
f"max_normal_delta_deg={overall_max_normal:.6f} "
|
||||
f"thresholds=(amplitude>={args.amplitude_min_m:.3f},"
|
||||
f"plateau<{args.plateau_min_m:.3f},"
|
||||
f"normal<={args.normal_jump_max_deg:.3f}) "
|
||||
f"status={'PASS' if passed else 'FAIL'}")
|
||||
print(f"[RingSetRSS] peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
|
||||
raise SystemExit(0 if passed else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail on contour terraces, stepped profiles, or insufficient Gradient amplitude."""
|
||||
import argparse
|
||||
import array
|
||||
import math
|
||||
import resource
|
||||
import struct
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def read_scene(path, wanted_lod, height_min, height_max, bounds):
|
||||
triangles = []
|
||||
selected_tiles = 0
|
||||
x_min, x_max, y_min, y_max = bounds
|
||||
with open(path, "rb") as stream:
|
||||
if stream.read(8) != b"VFMSH001":
|
||||
raise SystemExit(f"{path}: invalid mesh scene header")
|
||||
version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
|
||||
stream.read(40)
|
||||
if version != 1 or tile_count > 100000:
|
||||
raise SystemExit(f"{path}: unsupported mesh scene")
|
||||
for _ in range(tile_count):
|
||||
key = struct.unpack("<4i", stream.read(16))
|
||||
surface_count, _, vertex_count, index_count, mask_count = struct.unpack(
|
||||
"<5I", stream.read(20))
|
||||
tile_extent = 32.0 * (1 << key[3]) * voxel_um / 1_000_000.0
|
||||
tile_x0, tile_y0 = key[0] * tile_extent, key[1] * tile_extent
|
||||
tile_z0 = key[2] * tile_extent
|
||||
if (wanted_lod is not None and key[3] != wanted_lod) or \
|
||||
tile_x0 > x_max or tile_x0 + tile_extent < x_min or \
|
||||
tile_y0 > y_max or tile_y0 + tile_extent < y_min or \
|
||||
tile_z0 > height_max or tile_z0 + tile_extent < height_min:
|
||||
stream.seek(vertex_count * 24 + index_count * 4 + mask_count, 1)
|
||||
continue
|
||||
selected_tiles += 1
|
||||
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()
|
||||
for offset in range(0, index_count, 3):
|
||||
if masks and masks[offset // 3]:
|
||||
continue
|
||||
ids = indices[offset:offset + 3]
|
||||
if max(ids) >= surface_count:
|
||||
continue
|
||||
verts = []
|
||||
for index in ids:
|
||||
p, n = index * 3, index * 3
|
||||
verts.append(((positions[p], positions[p + 1], positions[p + 2]),
|
||||
(normals[n], normals[n + 1], normals[n + 2])))
|
||||
average_nz = sum(vertex[1][2] for vertex in verts) / 3.0
|
||||
z_values = [vertex[0][2] for vertex in verts]
|
||||
# Select the requested surface branch by its face height. An
|
||||
# overlap or centroid test admits a giant coarse triangle that
|
||||
# merely clips this window and can belong to the neighboring face.
|
||||
mean_z = sum(z_values) / 3.0
|
||||
vertex_margin = voxel_um / 1_000_000.0 * 0.5
|
||||
if (mean_z < height_min or mean_z > height_max or
|
||||
min(z_values) < height_min - vertex_margin or
|
||||
max(z_values) > height_max + vertex_margin):
|
||||
continue
|
||||
if (max(vertex[0][0] for vertex in verts) < x_min or
|
||||
min(vertex[0][0] for vertex in verts) > x_max or
|
||||
max(vertex[0][1] for vertex in verts) < y_min or
|
||||
min(vertex[0][1] for vertex in verts) > y_max):
|
||||
continue
|
||||
triangles.append((key[3], average_nz, tuple(verts)))
|
||||
return voxel_um, triangles, selected_tiles
|
||||
|
||||
|
||||
def build_buckets(triangles, upward, bounds, cell_size=2.0):
|
||||
buckets = defaultdict(list)
|
||||
x_min, x_max, y_min, y_max = bounds
|
||||
for triangle in triangles:
|
||||
_, average_nz, verts = triangle
|
||||
if (upward and average_nz < 0.35) or (not upward and average_nz > -0.35):
|
||||
continue
|
||||
xs = [v[0][0] for v in verts]
|
||||
ys = [v[0][1] for v in verts]
|
||||
x0 = max(math.floor(min(xs) / cell_size), math.floor(x_min / cell_size))
|
||||
x1 = min(math.floor(max(xs) / cell_size), math.floor(x_max / cell_size))
|
||||
y0 = max(math.floor(min(ys) / cell_size), math.floor(y_min / cell_size))
|
||||
y1 = min(math.floor(max(ys) / cell_size), math.floor(y_max / cell_size))
|
||||
for iy in range(y0, y1 + 1):
|
||||
for ix in range(x0, x1 + 1):
|
||||
buckets[(ix, iy)].append(verts)
|
||||
return buckets
|
||||
|
||||
|
||||
def sample_surface(buckets, x, y, upward, cell_size, preferred_height=None):
|
||||
found = None
|
||||
for verts in buckets.get((math.floor(x / cell_size), math.floor(y / cell_size)), ()):
|
||||
(a, na), (b, nb), (c, nc) = verts
|
||||
denominator = (b[1] - c[1]) * (a[0] - c[0]) + (c[0] - b[0]) * (a[1] - c[1])
|
||||
if abs(denominator) < 1.0e-12:
|
||||
continue
|
||||
u = ((b[1] - c[1]) * (x - c[0]) + (c[0] - b[0]) * (y - c[1])) / denominator
|
||||
v = ((c[1] - a[1]) * (x - c[0]) + (a[0] - c[0]) * (y - c[1])) / denominator
|
||||
w = 1.0 - u - v
|
||||
if min(u, v, w) < -1.0e-6:
|
||||
continue
|
||||
z = u * a[2] + v * b[2] + w * c[2]
|
||||
normal = tuple(u * na[axis] + v * nb[axis] + w * nc[axis] for axis in range(3))
|
||||
length = math.sqrt(sum(value * value for value in normal))
|
||||
if length < 1.0e-9:
|
||||
continue
|
||||
normal = tuple(value / length for value in normal)
|
||||
if (normal[2] > 0) != upward:
|
||||
continue
|
||||
if found is None or (abs(z - preferred_height) < abs(found[0] - preferred_height)
|
||||
if preferred_height is not None else z > found[0]):
|
||||
found = (z, normal)
|
||||
return found
|
||||
|
||||
|
||||
def make_lines(cx, cy, radius, step):
|
||||
lines = []
|
||||
for index in range(8):
|
||||
angle = index * math.tau / 8.0
|
||||
count = round(radius / step)
|
||||
points = [(cx + i * step * math.cos(angle), cy + i * step * math.sin(angle))
|
||||
for i in range(count + 1)]
|
||||
lines.append((f"radial-{index}", points))
|
||||
count = round(2.0 * radius / step)
|
||||
t_values = [-radius + i * step for i in range(count + 1)]
|
||||
for name, points in (
|
||||
("straight-x", [(cx + t, cy) for t in t_values]),
|
||||
("straight-y", [(cx, cy + t) for t in t_values]),
|
||||
("straight-diagonal", [(cx + t / math.sqrt(2.0), cy + t / math.sqrt(2.0))
|
||||
for t in t_values]),
|
||||
):
|
||||
lines.append((name, points))
|
||||
return lines
|
||||
|
||||
|
||||
def audit_profile(points, buckets, upward, plateau_min_m, plateau_epsilon_m,
|
||||
step_threshold_m, cell_size, preferred_height=None):
|
||||
samples = [sample_surface(buckets, x, y, upward, cell_size, preferred_height)
|
||||
for x, y in points]
|
||||
valid = [(index, value) for index, value in enumerate(samples) if value is not None]
|
||||
if len(valid) < max(8, len(points) * 0.75):
|
||||
return {"coverage": len(valid) / len(points), "plateaus": [], "max_angle": math.inf,
|
||||
"height_range": 0.0, "valid": False}
|
||||
heights = [value[0] for _, value in valid]
|
||||
plateau_runs = []
|
||||
run_start = valid[0][0]
|
||||
run_start_height = valid[0][1][0]
|
||||
sample_spacing = math.hypot(points[1][0] - points[0][0],
|
||||
points[1][1] - points[0][1])
|
||||
for cursor in range(1, len(valid)):
|
||||
prior_index, prior = valid[cursor - 1]
|
||||
index, current = valid[cursor]
|
||||
if index != prior_index + 1:
|
||||
run_start = index
|
||||
run_start_height = current[0]
|
||||
continue
|
||||
if abs(current[0] - run_start_height) <= plateau_epsilon_m:
|
||||
continue
|
||||
run_length = (prior_index - run_start + 1) * sample_spacing
|
||||
if run_length >= plateau_min_m and abs(current[0] - prior[0]) >= step_threshold_m:
|
||||
plateau_runs.append((run_length, abs(current[0] - prior[0]), run_start,
|
||||
prior_index, index, prior[0], current[0]))
|
||||
run_start = index
|
||||
run_start_height = current[0]
|
||||
max_angle = 0.0
|
||||
for cursor in range(1, len(valid)):
|
||||
prior_index, prior = valid[cursor - 1]
|
||||
index, current = valid[cursor]
|
||||
if index != prior_index + 1:
|
||||
continue
|
||||
dot = max(-1.0, min(1.0, sum(a * b for a, b in zip(prior[1], current[1]))))
|
||||
max_angle = max(max_angle, math.degrees(math.acos(dot)))
|
||||
return {"coverage": len(valid) / len(points), "plateaus": plateau_runs,
|
||||
"max_angle": max_angle, "height_range": max(heights) - min(heights),
|
||||
"height_min": min(heights), "height_max": max(heights),
|
||||
"valid": True}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("scene")
|
||||
parser.add_argument("--surface", choices=("floor", "ceiling"), default="floor")
|
||||
parser.add_argument("--center", nargs=2, type=float, default=(64.0, -32.0))
|
||||
parser.add_argument("--radius", type=float, default=16.0)
|
||||
parser.add_argument("--lod", required=True,
|
||||
help="audit one LOD (0..8) or the streamed composite (`all`)")
|
||||
parser.add_argument("--step", type=float, default=0.125)
|
||||
parser.add_argument("--height-min-m", type=float, default=-130.0)
|
||||
parser.add_argument("--height-max-m", type=float, default=-110.0)
|
||||
parser.add_argument("--plateau-min-m", type=float, default=0.5)
|
||||
parser.add_argument("--plateau-epsilon-m", type=float, default=0.0005)
|
||||
parser.add_argument("--step-threshold-m", type=float, default=0.1)
|
||||
parser.add_argument("--normal-jump-max-deg", type=float, default=20.0)
|
||||
parser.add_argument("--amplitude-min-m", type=float, default=3.0)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
wanted_lod = None if args.lod == "all" else int(args.lod)
|
||||
except ValueError:
|
||||
parser.error("--lod must be 0..8 or all")
|
||||
if wanted_lod is not None and not 0 <= wanted_lod <= 8:
|
||||
parser.error("--lod must be 0..8 or all")
|
||||
bounds = (args.center[0] - args.radius, args.center[0] + args.radius,
|
||||
args.center[1] - args.radius, args.center[1] + args.radius)
|
||||
voxel_um, triangles, selected_tiles = read_scene(args.scene, wanted_lod,
|
||||
args.height_min_m, args.height_max_m, bounds)
|
||||
upward = args.surface == "floor"
|
||||
max_lod = max((triangle[0] for triangle in triangles), default=0)
|
||||
cell_size = max(2.0, 16.0 * voxel_um / 1_000_000.0 * (1 << max_lod))
|
||||
buckets = build_buckets(triangles, upward, bounds, cell_size)
|
||||
results = []
|
||||
# The requested face is the outer boundary of its authored band: highest Z
|
||||
# for a floor, lowest Z for a ceiling. Selecting the band midpoint can jump
|
||||
# between two overlapping downward/upward sheets at a cave edge.
|
||||
preferred_height = args.height_max_m if upward else args.height_min_m
|
||||
for name, points in make_lines(*args.center, args.radius, args.step):
|
||||
results.append((name, points, audit_profile(points, buckets, upward,
|
||||
args.plateau_min_m, args.plateau_epsilon_m,
|
||||
args.step_threshold_m, cell_size, preferred_height)))
|
||||
valid = [result for _, _, result in results if result["valid"]]
|
||||
if not valid:
|
||||
raise SystemExit("no sampled surface profiles; check scene, center, orientation and LOD")
|
||||
amplitude = max(result["height_max"] for result in valid) - min(
|
||||
result["height_min"] for result in valid)
|
||||
sampled_min = min(result["height_min"] for result in valid)
|
||||
sampled_max = max(result["height_max"] for result in valid)
|
||||
longest_plateau = max((run[0] for result in valid for run in result["plateaus"]),
|
||||
default=0.0)
|
||||
largest_step = max((run[1] for result in valid for run in result["plateaus"]),
|
||||
default=0.0)
|
||||
max_normal_jump = max(result["max_angle"] for result in valid)
|
||||
min_coverage = min(result["coverage"] for _, _, result in results)
|
||||
passed = (amplitude >= args.amplitude_min_m and longest_plateau < args.plateau_min_m
|
||||
and max_normal_jump <= args.normal_jump_max_deg and min_coverage >= 0.75)
|
||||
print(f"[RingTerraceAudit] scene={args.scene} surface={args.surface} lod={args.lod} "
|
||||
f"max_lod={max_lod} "
|
||||
f"height_window_m={args.height_min_m:.3f}..{args.height_max_m:.3f} "
|
||||
f"voxel_um={voxel_um} triangles={len(triangles)} lines={len(results)} "
|
||||
f"selected_tiles={selected_tiles} bucket_cell_m={cell_size:.3f} "
|
||||
f"coverage_min={min_coverage:.3f} sampled_height_m={sampled_min:.6f}.."
|
||||
f"{sampled_max:.6f} amplitude_range_m={amplitude:.6f} "
|
||||
f"longest_flat_run_m={longest_plateau:.6f} following_step_m={largest_step:.6f} "
|
||||
f"max_normal_delta_deg={max_normal_jump:.6f} status={'PASS' if passed else 'FAIL'}")
|
||||
for name, points, result in results:
|
||||
print(f"[RingTerraceProfile] line={name} coverage={result['coverage']:.3f} "
|
||||
f"range_m={result['height_range']:.6f} flat_step_runs={len(result['plateaus'])} "
|
||||
f"max_normal_delta_deg={result['max_angle']:.6f}")
|
||||
for run in result["plateaus"]:
|
||||
print(f"[RingTerraceViolation] line={name} flat_start_m=({points[run[2]][0]:.3f},"
|
||||
f"{points[run[2]][1]:.3f}) flat_run_m={run[0]:.6f} "
|
||||
f"step_m={run[1]:.6f} from_height_m={run[5]:.6f} "
|
||||
f"to_height_m={run[6]:.6f}")
|
||||
print(f"[RingTerraceRSS] peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
|
||||
raise SystemExit(0 if passed else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Encode the near/far tint-on/off L32 captures as compact JPEGs."""
|
||||
import resource
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
SOURCE = ROOT / "Saved/Linux/l32"
|
||||
DEST = Path(__file__).resolve().parent / "captures"
|
||||
DEST.mkdir(parents=True, exist_ok=True)
|
||||
captures = {
|
||||
"fi-floor-near-tint-on": "cap-final-fi-near-on",
|
||||
"fi-floor-near-tint-off": "cap-final-fi-near-off",
|
||||
"fi-floor-far-tint-on": "cap-final-fi-far-on",
|
||||
"fi-floor-far-tint-off": "cap-final-fi-far-off",
|
||||
"sw-ceiling-near-tint-on": "cap-final-sw-near-on",
|
||||
"sw-ceiling-near-tint-off": "cap-final-sw-near-off",
|
||||
"sw-ceiling-far-tint-on": "cap-final-sw-far-on",
|
||||
"sw-ceiling-far-tint-off": "cap-final-sw-far-off",
|
||||
}
|
||||
failed = False
|
||||
for output_name, input_name in captures.items():
|
||||
source = SOURCE / f"{input_name}.png"
|
||||
destination = DEST / f"{output_name}.jpg"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "ffmpeg",
|
||||
"-y", "-hide_banner", "-loglevel", "error", "-i", str(source),
|
||||
"-frames:v", "1", "-q:v", "4", str(destination),
|
||||
]
|
||||
result = subprocess.run(command, check=False)
|
||||
size = destination.stat().st_size if destination.exists() else 0
|
||||
ok = source.exists() and result.returncode == 0 and size <= 300_000
|
||||
failed |= not ok
|
||||
print(f"[L32CaptureJPEG] source={source.name} output={destination.name} "
|
||||
f"bytes={size} status={'PASS' if ok else 'FAIL'}", flush=True)
|
||||
print(f"[L32CaptureJPEG] peak_rss_kib="
|
||||
f"{resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}", flush=True)
|
||||
raise SystemExit(1 if failed else 0)
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Copy only tiles for one LOD and spatial window from a VFMSH scene."""
|
||||
import argparse
|
||||
import resource
|
||||
import shutil
|
||||
import struct
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("source")
|
||||
parser.add_argument("destination")
|
||||
parser.add_argument("--lod", type=int, required=True)
|
||||
parser.add_argument("--bounds", nargs=6, type=float, required=True,
|
||||
metavar=("XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"))
|
||||
args = parser.parse_args()
|
||||
if not 0 <= args.lod <= 8:
|
||||
parser.error("--lod must be between 0 and 8")
|
||||
x_min, x_max, y_min, y_max, z_min, z_max = args.bounds
|
||||
if x_max <= x_min or y_max <= y_min or z_max <= z_min:
|
||||
parser.error("bounds must have positive extents")
|
||||
|
||||
selected = []
|
||||
with open(args.source, "rb") as src:
|
||||
prefix = src.read(60)
|
||||
if len(prefix) != 60 or prefix[:8] != b"VFMSH001":
|
||||
raise SystemExit("source has an 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 SystemExit("source has an unsupported scene header")
|
||||
for _ in range(tile_count):
|
||||
header = src.read(36)
|
||||
if len(header) != 36:
|
||||
raise SystemExit("truncated tile header")
|
||||
key = struct.unpack_from("<4i", header)
|
||||
_, _, vertex_count, index_count, mask_count = struct.unpack_from("<5I", header, 16)
|
||||
payload_offset = src.tell()
|
||||
payload_size = vertex_count * 24 + index_count * 4 + mask_count
|
||||
extent = 32.0 * (1 << key[3]) * voxel_um / 1_000_000.0
|
||||
tx0, ty0, tz0 = key[0] * extent, key[1] * extent, key[2] * extent
|
||||
intersects = (key[3] == args.lod and tx0 <= x_max and tx0 + extent >= x_min
|
||||
and ty0 <= y_max and ty0 + extent >= y_min
|
||||
and tz0 <= z_max and tz0 + extent >= z_min)
|
||||
if intersects:
|
||||
selected.append((header, payload_offset, payload_size))
|
||||
src.seek(payload_size, 1)
|
||||
if src.read(1):
|
||||
raise SystemExit("source scene has trailing bytes")
|
||||
|
||||
if not selected:
|
||||
raise SystemExit("no tiles matched the requested LOD and bounds")
|
||||
with open(args.source, "rb") as src, open(args.destination, "wb") as dst:
|
||||
prefix = bytearray(src.read(60))
|
||||
struct.pack_into("<I", prefix, 16, len(selected))
|
||||
dst.write(prefix)
|
||||
for header, payload_offset, payload_size in selected:
|
||||
dst.write(header)
|
||||
src.seek(payload_offset)
|
||||
remaining = payload_size
|
||||
while remaining:
|
||||
block = src.read(min(1 << 20, remaining))
|
||||
if not block:
|
||||
raise SystemExit("truncated tile payload")
|
||||
dst.write(block)
|
||||
remaining -= len(block)
|
||||
print(f"[VFMeshLodWindow] source={args.source} lod={args.lod} "
|
||||
f"selected_tiles={len(selected)} output={args.destination} "
|
||||
f"output_bytes={__import__('os').path.getsize(args.destination)} "
|
||||
f"peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss} status=PASS")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Stream VFMSH tiles and retain a focused, near-horizontal surface patch."""
|
||||
import argparse
|
||||
import math
|
||||
import resource
|
||||
import struct
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("source")
|
||||
parser.add_argument("destination")
|
||||
parser.add_argument("--center", nargs=2, type=float, required=True, metavar=("X", "Y"))
|
||||
parser.add_argument("--radius-m", type=float, required=True)
|
||||
parser.add_argument("--height-m", nargs=2, type=float, required=True, metavar=("MIN", "MAX"))
|
||||
parser.add_argument("--min-abs-normal-z", type=float, default=0.7)
|
||||
args = parser.parse_args()
|
||||
if args.radius_m <= 0 or args.height_m[1] <= args.height_m[0]:
|
||||
parser.error("radius and height interval must be positive")
|
||||
if not 0.0 <= args.min_abs_normal_z <= 1.0:
|
||||
parser.error("--min-abs-normal-z must be in [0,1]")
|
||||
|
||||
cx, cy = args.center
|
||||
r2 = args.radius_m * args.radius_m
|
||||
zmin, zmax = args.height_m
|
||||
input_tiles = kept_tiles = kept_triangles = 0
|
||||
with open(args.source, "rb") as src, open(args.destination, "wb+") as dst:
|
||||
prefix = bytearray(src.read(60))
|
||||
if len(prefix) != 60 or prefix[:8] != b"VFMSH001":
|
||||
raise SystemExit("source has an 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 SystemExit("source has an unsupported scene header")
|
||||
struct.pack_into("<I", prefix, 16, 0)
|
||||
dst.write(prefix)
|
||||
for _ in range(tile_count):
|
||||
header = src.read(36)
|
||||
if len(header) != 36:
|
||||
raise SystemExit("truncated tile header")
|
||||
key = struct.unpack_from("<4i", header)
|
||||
surface_vertices, skirt_count, vertex_count, index_count, mask_count = \
|
||||
struct.unpack_from("<5I", header, 16)
|
||||
if vertex_count > 2_000_000 or index_count > 12_000_000:
|
||||
raise SystemExit("tile exceeds the focused patch memory limit")
|
||||
vertex_bytes = src.read(vertex_count * 24)
|
||||
index_bytes = src.read(index_count * 4)
|
||||
masks = src.read(mask_count)
|
||||
if (len(vertex_bytes) != vertex_count * 24
|
||||
or len(index_bytes) != index_count * 4 or len(masks) != mask_count):
|
||||
raise SystemExit("truncated tile payload")
|
||||
input_tiles += 1
|
||||
positions = list(struct.iter_unpack("<3f", vertex_bytes[:vertex_count * 12]))
|
||||
indices = struct.unpack(f"<{index_count}I", index_bytes) if index_count else ()
|
||||
face_masks = masks if mask_count else bytes(index_count // 12)
|
||||
filtered = []
|
||||
for tri in range(index_count // 3):
|
||||
i0, i1, i2 = indices[tri * 3:tri * 3 + 3]
|
||||
if i0 >= vertex_count or i1 >= vertex_count or i2 >= vertex_count:
|
||||
raise SystemExit("tile index points outside its vertex array")
|
||||
if face_masks[tri] != 0:
|
||||
continue
|
||||
p0, p1, p2 = positions[i0], positions[i1], positions[i2]
|
||||
mx = (p0[0] + p1[0] + p2[0]) / 3.0
|
||||
my = (p0[1] + p1[1] + p2[1]) / 3.0
|
||||
mz = (p0[2] + p1[2] + p2[2]) / 3.0
|
||||
if (mx - cx) ** 2 + (my - cy) ** 2 > r2 or not zmin <= mz <= zmax:
|
||||
continue
|
||||
ab = (p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2])
|
||||
ac = (p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2])
|
||||
nz = ab[0] * ac[1] - ab[1] * ac[0]
|
||||
length = math.sqrt((ab[1] * ac[2] - ab[2] * ac[1]) ** 2
|
||||
+ (ab[2] * ac[0] - ab[0] * ac[2]) ** 2 + nz * nz)
|
||||
if length == 0 or abs(nz) / length < args.min_abs_normal_z:
|
||||
continue
|
||||
filtered.extend((i0, i1, i2))
|
||||
if not filtered:
|
||||
continue
|
||||
tile_header = struct.pack("<4i5I", *key, surface_vertices, 0,
|
||||
vertex_count, len(filtered), 0)
|
||||
dst.write(tile_header)
|
||||
dst.write(vertex_bytes)
|
||||
dst.write(struct.pack(f"<{len(filtered)}I", *filtered))
|
||||
kept_tiles += 1
|
||||
kept_triangles += len(filtered) // 3
|
||||
dst.seek(16)
|
||||
dst.write(struct.pack("<I", kept_tiles))
|
||||
|
||||
if kept_tiles == 0:
|
||||
raise SystemExit("no surface triangles matched the requested patch")
|
||||
print(f"[VFMeshSurfacePatch] source={args.source} input_tiles={input_tiles} "
|
||||
f"tiles={kept_tiles} triangles={kept_triangles} center=({cx:.3f},{cy:.3f}) "
|
||||
f"radius_m={args.radius_m:.3f} height_m={zmin:.3f}..{zmax:.3f} "
|
||||
f"peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss} status=PASS")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Find VFMSH tiles intersecting a small region without reading mesh payloads."""
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import resource
|
||||
import struct
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("directory")
|
||||
parser.add_argument("--lod", type=int, required=True)
|
||||
parser.add_argument("--bounds", nargs=6, type=float, required=True,
|
||||
metavar=("XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"))
|
||||
args = parser.parse_args()
|
||||
xmin, xmax, ymin, ymax, zmin, zmax = args.bounds
|
||||
matches = 0
|
||||
for path in sorted(glob.glob(os.path.join(args.directory, "*.vfmesh"))):
|
||||
with open(path, "rb") as stream:
|
||||
if stream.read(8) != b"VFMSH001":
|
||||
continue
|
||||
version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
|
||||
stream.seek(40, 1)
|
||||
if version != 1:
|
||||
continue
|
||||
for _ in range(tile_count):
|
||||
key = struct.unpack("<4i", stream.read(16))
|
||||
surface_count, _, vertex_count, index_count, mask_count = struct.unpack(
|
||||
"<5I", stream.read(20))
|
||||
extent = 32.0 * (1 << key[3]) * voxel_um / 1_000_000.0
|
||||
x0, y0, z0 = (key[axis] * extent for axis in range(3))
|
||||
intersects = (key[3] == args.lod and index_count > 0
|
||||
and x0 <= xmax and x0 + extent >= xmin
|
||||
and y0 <= ymax and y0 + extent >= ymin
|
||||
and z0 <= zmax and z0 + extent >= zmin)
|
||||
if intersects:
|
||||
matches += 1
|
||||
print(f"[VFMeshTileWindow] scene={os.path.basename(path)} key={key} "
|
||||
f"vertices={vertex_count} indices={index_count} "
|
||||
f"extent_m={extent:.3f}")
|
||||
stream.seek(vertex_count * 24 + index_count * 4 + mask_count, 1)
|
||||
print(f"[VFMeshTileWindowSummary] matching_tiles={matches} "
|
||||
f"peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Stream selected-LOD tiles and find face windows that pass the ring audit."""
|
||||
import argparse
|
||||
import array
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import resource
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("scene")
|
||||
parser.add_argument("--lod", type=int, required=True)
|
||||
parser.add_argument("--surface", choices=("floor", "ceiling"), required=True)
|
||||
parser.add_argument("--height-min-m", type=float, required=True)
|
||||
parser.add_argument("--height-max-m", type=float, required=True)
|
||||
parser.add_argument("--preferred-height-m", type=float,
|
||||
help="lock overlapping-face selection near this expected surface height")
|
||||
parser.add_argument("--step-m", type=float, default=0.5)
|
||||
parser.add_argument("--tile-key", nargs=3, type=int,
|
||||
help="audit only this tile's x/y/z key (LOD is supplied separately)")
|
||||
args = parser.parse_args()
|
||||
audit_path = Path(__file__).with_name("AuditRingTerraceL32.py")
|
||||
spec = importlib.util.spec_from_file_location("ring_audit_l32", audit_path)
|
||||
audit = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(audit)
|
||||
want_up = args.surface == "floor"
|
||||
candidates = []
|
||||
with open(args.scene, "rb") as stream:
|
||||
if stream.read(8) != b"VFMSH001":
|
||||
raise SystemExit("invalid VFMSH001 scene")
|
||||
version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
|
||||
stream.seek(40, 1)
|
||||
if version != 1:
|
||||
raise SystemExit("unsupported mesh scene")
|
||||
for _ in range(tile_count):
|
||||
header = stream.read(36)
|
||||
key = struct.unpack("<4i", header[:16])
|
||||
surface_count, _, vertex_count, index_count, mask_count = struct.unpack(
|
||||
"<5I", header[16:])
|
||||
payload = vertex_count * 24 + index_count * 4 + mask_count
|
||||
if key[3] != args.lod or (args.tile_key and tuple(key[:3]) != tuple(args.tile_key)):
|
||||
stream.seek(payload, 1)
|
||||
continue
|
||||
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()
|
||||
triangles = []
|
||||
vertex_margin = voxel_um / 1_000_000.0 * 0.5
|
||||
for offset in range(0, index_count, 3):
|
||||
if masks and masks[offset // 3]:
|
||||
continue
|
||||
ids = indices[offset:offset + 3]
|
||||
if max(ids) >= surface_count:
|
||||
continue
|
||||
verts = []
|
||||
for index in ids:
|
||||
p = index * 3
|
||||
verts.append(((positions[p], positions[p + 1], positions[p + 2]),
|
||||
(normals[p], normals[p + 1], normals[p + 2])))
|
||||
average_nz = sum(vertex[1][2] for vertex in verts) / 3.0
|
||||
if (want_up and average_nz < 0.35) or (not want_up and average_nz > -0.35):
|
||||
continue
|
||||
z_values = [vertex[0][2] for vertex in verts]
|
||||
mean_z = sum(z_values) / 3.0
|
||||
if (mean_z < args.height_min_m or mean_z > args.height_max_m or
|
||||
min(z_values) < args.height_min_m - vertex_margin or
|
||||
max(z_values) > args.height_max_m + vertex_margin):
|
||||
continue
|
||||
triangles.append((key[3], average_nz, tuple(verts)))
|
||||
if not triangles:
|
||||
continue
|
||||
extent = 32.0 * (1 << key[3]) * voxel_um / 1_000_000.0
|
||||
cx, cy = (key[0] + 0.5) * extent, (key[1] + 0.5) * extent
|
||||
radius = extent * 0.5
|
||||
bounds = (cx - radius, cx + radius, cy - radius, cy + radius)
|
||||
cell_size = max(2.0, 16.0 * voxel_um / 1_000_000.0 * (1 << key[3]))
|
||||
buckets = audit.build_buckets(triangles, want_up, bounds, cell_size)
|
||||
profiles = []
|
||||
preferred_height = args.preferred_height_m
|
||||
if preferred_height is None:
|
||||
preferred_height = args.height_max_m if want_up else args.height_min_m
|
||||
for _, points in audit.make_lines(cx, cy, radius, args.step_m):
|
||||
profiles.append(audit.audit_profile(points, buckets, want_up,
|
||||
0.5, 0.0005, 0.1, cell_size, preferred_height))
|
||||
valid = [profile for profile in profiles if profile["valid"]]
|
||||
coverage = min(profile["coverage"] for profile in profiles)
|
||||
amplitude = max((profile["height_max"] for profile in valid), default=0.0) - \
|
||||
min((profile["height_min"] for profile in valid), default=0.0)
|
||||
longest = max((run[0] for profile in valid for run in profile["plateaus"]),
|
||||
default=0.0)
|
||||
max_normal = max((profile["max_angle"] for profile in valid), default=float("inf"))
|
||||
passed = (len(valid) == len(profiles) and coverage >= 0.75 and amplitude >= 3.0
|
||||
and longest < 0.5 and max_normal <= 20.0)
|
||||
candidates.append((passed, amplitude, -max_normal, key, cx, cy,
|
||||
len(triangles), coverage, longest, max_normal))
|
||||
stream.seek(payload - (vertex_count * 24 + index_count * 4 + mask_count), 1)
|
||||
candidates.sort(reverse=True, key=lambda item: (item[0], item[1], item[2]))
|
||||
print(f"[RingWindowSearch] lod={args.lod} surface={args.surface} tiles={len(candidates)} "
|
||||
f"height_window_m={args.height_min_m:.3f}..{args.height_max_m:.3f} "
|
||||
f"step_m={args.step_m:.3f}")
|
||||
for passed, amplitude, neg_normal, key, cx, cy, count, coverage, longest, normal in candidates[:40]:
|
||||
print(f"[RingWindowCandidate] status={'PASS' if passed else 'FAIL'} key={key} "
|
||||
f"center_m=({cx:.3f},{cy:.3f}) triangles={count} coverage={coverage:.3f} "
|
||||
f"amplitude_m={amplitude:.6f} longest_flat_m={longest:.6f} "
|
||||
f"normal_jump_deg={normal:.6f}")
|
||||
print(f"[RingWindowRSS] peak_rss_kib="
|
||||
f"{resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Find open SurfaceWorld columns below the resolved transition ceiling."""
|
||||
import argparse
|
||||
import csv
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("probe")
|
||||
parser.add_argument("owner")
|
||||
parser.add_argument("output")
|
||||
parser.add_argument("--z", type=float, default=-130.0)
|
||||
parser.add_argument("--minimum", type=float, default=-96.0)
|
||||
parser.add_argument("--maximum", type=float, default=96.0)
|
||||
parser.add_argument("--spacing", type=float, default=16.0)
|
||||
args = parser.parse_args()
|
||||
probe = str(Path(args.probe).resolve())
|
||||
owner = str(Path(args.owner).resolve())
|
||||
output = Path(args.output)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
rows = []
|
||||
count = round((args.maximum - args.minimum) / args.spacing)
|
||||
for iy in range(count + 1):
|
||||
y = args.minimum + iy * args.spacing
|
||||
for ix in range(count + 1):
|
||||
x = args.minimum + ix * args.spacing
|
||||
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
probe, owner, "surface-world", str(x), str(y),
|
||||
str(args.z - 2.0), str(args.z + 2.0), "0.25"]
|
||||
result = subprocess.run(command, capture_output=True, text=True, timeout=1800,
|
||||
check=False)
|
||||
samples = [line for line in result.stdout.splitlines()
|
||||
if line.startswith(f"{args.z:.6f},")]
|
||||
density = float(samples[0].split(",")[1]) if result.returncode == 0 and samples else float("nan")
|
||||
rows.append((x, y, args.z, density, result.returncode))
|
||||
print(f"[SurfaceWorldAirScan] x_m={x:.1f} y_m={y:.1f} z_m={args.z:.2f} "
|
||||
f"density_vox={density:.6f} exit={result.returncode}", flush=True)
|
||||
with output.open("w", newline="", encoding="utf-8") as stream:
|
||||
writer = csv.writer(stream)
|
||||
writer.writerow(("x_m", "y_m", "z_m", "density_vox", "exit_code"))
|
||||
writer.writerows(rows)
|
||||
open_samples = [row for row in rows if row[3] > 0.0]
|
||||
print(f"[SurfaceWorldAirScanSummary] samples={len(rows)} open={len(open_samples)} "
|
||||
f"output={output} status={'PASS' if open_samples else 'FAIL'}")
|
||||
return 0 if open_samples else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,118 @@
|
||||
#include "Json.h"
|
||||
#include "OwnerWorldLoader.h"
|
||||
#include "StandaloneCoreScheduler.h"
|
||||
#include "WorldAssemblyPrototype.h"
|
||||
#include "StandaloneSnapshotAdapter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
int main(int argc,char** argv)
|
||||
{
|
||||
if(argc!=8)
|
||||
{
|
||||
std::fprintf(stderr,"usage: %s owner.json surface-world|floating-islands x_m y_m zmin_m zmax_m step_m\n",argv[0]);
|
||||
return 2;
|
||||
}
|
||||
const std::string recipe=argv[2];
|
||||
if(recipe!="surface-world"&&recipe!="floating-islands")return 2;
|
||||
const double x=std::strtod(argv[3],nullptr),y=std::strtod(argv[4],nullptr);
|
||||
const double zmin=std::strtod(argv[5],nullptr),zmax=std::strtod(argv[6],nullptr);
|
||||
const double step=std::strtod(argv[7],nullptr);
|
||||
if(!std::isfinite(x)||!std::isfinite(y)||!std::isfinite(zmin)||!std::isfinite(zmax)
|
||||
||!std::isfinite(step)||zmax<=zmin||step<=0.0)return 2;
|
||||
|
||||
VFJson::Value root;
|
||||
std::string error;
|
||||
{
|
||||
std::ifstream input(argv[1],std::ios::binary);
|
||||
if(!input){std::fprintf(stderr,"owner JSON open failed\n");return 2;}
|
||||
std::string text{std::istreambuf_iterator<char>(input),std::istreambuf_iterator<char>{}};
|
||||
if(!VFJson::Parser(std::move(text)).Parse(root,error))
|
||||
{std::fprintf(stderr,"owner JSON parse failed: %s\n",error.c_str());return 2;}
|
||||
}
|
||||
if(recipe=="floating-islands"
|
||||
&&!StandaloneWorldAssemblyPrototype::SelectTopStrateAsFloatingIslands(root,error))
|
||||
{std::fprintf(stderr,"FI selection failed: %s\n",error.c_str());return 2;}
|
||||
auto owner=std::make_shared<FStandaloneOwnerWorld>();
|
||||
if(!StandaloneOwnerLoader::Resolve(root,*owner,error))
|
||||
{std::fprintf(stderr,"owner resolve failed: %s\n",error.c_str());return 2;}
|
||||
VoxelForgeCore::WorldAssemblyManifest assembly;
|
||||
if(!StandaloneWorldAssemblyPrototype::AttachRound1WorldAssemblyPrototype(
|
||||
*owner,root,error,{},&assembly))
|
||||
{std::fprintf(stderr,"assembly setup failed: %s\n",error.c_str());return 2;}
|
||||
if(recipe=="floating-islands"
|
||||
&&!StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly(
|
||||
*owner,root,assembly,error))
|
||||
{std::fprintf(stderr,"FI setup failed: %s\n",error.c_str());return 2;}
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV3 resolvedLayout;
|
||||
if(!StandaloneWorldAssemblyPrototype::BuildResolvedLayoutSnapshot(
|
||||
*owner,assembly,recipe,resolvedLayout,error))
|
||||
{std::fprintf(stderr,"resolved layout build failed: %s\n",error.c_str());return 2;}
|
||||
auto* rootObject=std::get_if<VFJson::Value::Object>(&root.Data);
|
||||
if(!rootObject)return 2;
|
||||
VFJson::Value layoutJson;
|
||||
layoutJson.Data=resolvedLayout.ToJson();
|
||||
(*rootObject)["resolved_layout_json"]=std::move(layoutJson);
|
||||
auto snapshot=VoxelForgeCore::MakeStandaloneSnapshotFromParsedJson(
|
||||
root,owner->Seed,3,8,false,false,error);
|
||||
if(!snapshot)
|
||||
{std::fprintf(stderr,"snapshot resolve failed: %s\n",error.c_str());return 2;}
|
||||
const auto* layout=snapshot.resolvedLayout();
|
||||
if(!layout){std::fprintf(stderr,"resolved layout missing\n");return 2;}
|
||||
for(const auto& stratum:layout->strates)
|
||||
std::printf("[DensityStratum] order=%d strate=%d generator=%s mode=%s width_um=%lld bedrock_um=%lld..%lld ceiling_um=%lld..%lld\n",
|
||||
stratum.order,stratum.strateIndex,stratum.generatorKind.c_str(),
|
||||
stratum.transitionMode.c_str(),static_cast<long long>(stratum.transitionWidthMicrometres),
|
||||
static_cast<long long>(stratum.bedrock.bottomMicrometres),
|
||||
static_cast<long long>(stratum.bedrock.topMicrometres),
|
||||
static_cast<long long>(stratum.ceiling.bottomMicrometres),
|
||||
static_cast<long long>(stratum.ceiling.topMicrometres));
|
||||
const auto interface=std::find_if(layout->strates.begin(),layout->strates.end(),
|
||||
[](const auto& stratum){return stratum.order==0;});
|
||||
if(interface==layout->strates.end())return 2;
|
||||
|
||||
VoxelForgeCore::World world(std::move(snapshot));
|
||||
VoxelForgeCore::ShapeBuildRequest request;
|
||||
request.minVoxel={static_cast<std::int32_t>(std::floor((x-32.0)*4.0)),
|
||||
static_cast<std::int32_t>(std::floor((y-32.0)*4.0)),
|
||||
static_cast<std::int32_t>(std::floor((zmin-4.0)*4.0))};
|
||||
request.maxVoxel={static_cast<std::int32_t>(std::ceil((x+32.0)*4.0)),
|
||||
static_cast<std::int32_t>(std::ceil((y+32.0)*4.0)),
|
||||
static_cast<std::int32_t>(std::ceil((zmax+4.0)*4.0))};
|
||||
request.clipMinVoxel=request.minVoxel;
|
||||
request.clipMaxVoxel=request.maxVoxel;
|
||||
request.workerLimit=12;
|
||||
request.includeFarWorld=false;
|
||||
FStandaloneCoreScheduler scheduler;
|
||||
if(!world.compile(request,scheduler,error))
|
||||
{std::fprintf(stderr,"shape compile failed: %s\n",error.c_str());return 2;}
|
||||
|
||||
std::printf("[DensityLine] recipe=%s mode=%s width_um=%lld seam_um=%lld band_top_um=%lld band_bottom_um=%lld xy_m=%.6f,%.6f core_voxel_m=0.25 status=PASS\n",
|
||||
recipe.c_str(),interface->transitionMode.c_str(),
|
||||
static_cast<long long>(interface->transitionWidthMicrometres),
|
||||
static_cast<long long>(interface->bedrock.bottomMicrometres),
|
||||
static_cast<long long>(interface->bedrock.topMicrometres),
|
||||
static_cast<long long>(interface->bedrock.bottomMicrometres),x,y);
|
||||
std::printf("z_m,density_vox,lattice_vox,unclamped_vox,dispatch\n");
|
||||
const auto count=static_cast<std::size_t>(std::floor((zmax-zmin)/step+1.0e-9));
|
||||
for(std::size_t i=0;i<=count;++i)
|
||||
{
|
||||
const double z=zmin+static_cast<double>(i)*step;
|
||||
const VoxelForgeCore::Vec3d p{x*4.0,y*4.0,z*4.0};
|
||||
const VoxelForgeCore::Vec3i lattice{
|
||||
static_cast<std::int32_t>(std::llround(x*4.0)),
|
||||
static_cast<std::int32_t>(std::llround(y*4.0)),
|
||||
static_cast<std::int32_t>(std::llround(z*4.0))};
|
||||
std::printf("%.6f,%.9f,%.9f,%.9f,%s\n",z,world.densityAt(p),
|
||||
world.densityAtLattice(lattice),world.unclampedDensityAt(p),
|
||||
world.densityDispatchPathAt(p).c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Dump a bounded surface line from a VFMSH scene using the ring-audit sampler."""
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import resource
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("scene")
|
||||
parser.add_argument("--surface", choices=("floor", "ceiling"), required=True)
|
||||
parser.add_argument("--center", nargs=2, type=float, required=True)
|
||||
parser.add_argument("--radius", type=float, required=True)
|
||||
parser.add_argument("--lod", type=int, required=True)
|
||||
parser.add_argument("--height-min-m", type=float, required=True)
|
||||
parser.add_argument("--height-max-m", type=float, required=True)
|
||||
parser.add_argument("--line", choices=("x", "y", "diagonal"), default="x")
|
||||
parser.add_argument("--stride-m", type=float, default=8.0)
|
||||
args = parser.parse_args()
|
||||
audit_path = Path(__file__).with_name("AuditRingTerraceL32.py")
|
||||
spec = importlib.util.spec_from_file_location("ring_audit_l32", audit_path)
|
||||
audit = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(audit)
|
||||
cx, cy = args.center
|
||||
bounds = (cx - args.radius, cx + args.radius,
|
||||
cy - args.radius, cy + args.radius)
|
||||
voxel_um, triangles, tiles = audit.read_scene(args.scene, args.lod,
|
||||
args.height_min_m, args.height_max_m, bounds)
|
||||
cell_size = max(2.0, 16.0 * voxel_um / 1_000_000.0 * (1 << args.lod))
|
||||
buckets = audit.build_buckets(triangles, args.surface == "floor", bounds, cell_size)
|
||||
count = round(2.0 * args.radius / args.stride_m)
|
||||
print(f"[SurfaceProfile] lod={args.lod} surface={args.surface} tiles={tiles} "
|
||||
f"triangles={len(triangles)} line={args.line} stride_m={args.stride_m}")
|
||||
print("index,x_m,y_m,z_m,nx,ny,nz")
|
||||
for index in range(count + 1):
|
||||
t = -args.radius + index * args.stride_m
|
||||
if args.line == "x":
|
||||
x, y = cx + t, cy
|
||||
elif args.line == "y":
|
||||
x, y = cx, cy + t
|
||||
else:
|
||||
x, y = cx + t / 2.0**0.5, cy + t / 2.0**0.5
|
||||
preferred_height = args.height_max_m if args.surface == "floor" else args.height_min_m
|
||||
hit = audit.sample_surface(buckets, x, y, args.surface == "floor", cell_size,
|
||||
preferred_height)
|
||||
if hit is None:
|
||||
print(f"{index},{x:.3f},{y:.3f},nan,nan,nan,nan")
|
||||
else:
|
||||
z, normal = hit
|
||||
print(f"{index},{x:.3f},{y:.3f},{z:.6f}," +
|
||||
",".join(f"{value:.7f}" for value in normal))
|
||||
print(f"[SurfaceProfileRSS] peak_rss_kib="
|
||||
f"{resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare the v20 transition-column cache with the uncached v20 binary."""
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l32"
|
||||
BINARIES = {
|
||||
"base": OUT / "bin/VoxelForgeCoreHeadless-l32-clang-v20",
|
||||
"cache": OUT / "bin/transition-cache/VoxelForgeCoreHeadless-l32-transition-cache",
|
||||
}
|
||||
ORDER = (("base", "cache"), ("cache", "base"), ("base", "cache"),
|
||||
("cache", "base"), ("base", "cache"))
|
||||
READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
|
||||
rows = []
|
||||
failed = False
|
||||
|
||||
for pair, builds in enumerate(ORDER, 1):
|
||||
for build in builds:
|
||||
name = f"cache-compare-sw-pair{pair}-{build}"
|
||||
logfile = OUT / f"{name}.log"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
str(BINARIES[build]), str(ROOT / "Standalone/data/owner_world.json"),
|
||||
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", "--force-generate", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=-1", "--recipe=surface-world",
|
||||
]
|
||||
started = time.monotonic()
|
||||
try:
|
||||
with logfile.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=900, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
result = None
|
||||
elapsed = time.monotonic() - started
|
||||
text = logfile.read_text(encoding="utf-8", errors="replace")
|
||||
matches = READY.findall(text)
|
||||
valid = result is not None and result.returncode == 0 and len(matches) == 1
|
||||
ready = float(matches[0]) if len(matches) == 1 else float("nan")
|
||||
rows.append((pair, build, ready, elapsed,
|
||||
result.returncode if result else "TIMEOUT", valid))
|
||||
print(f"pair={pair} build={build} whole_view_ready={ready:.3f}s "
|
||||
f"process_elapsed={elapsed:.1f}s {'PASS' if valid else 'FAIL'}", flush=True)
|
||||
failed |= not valid
|
||||
|
||||
csv = OUT / "transition-cache-compare.csv"
|
||||
csv.write_text(
|
||||
"pair,build,whole_view_ready_seconds,process_elapsed_seconds,exit_code,status\n" +
|
||||
"".join(f"{pair},{build},{ready:.6f},{elapsed:.3f},{code},"
|
||||
f"{'PASS' if ok else 'FAIL'}\n"
|
||||
for pair, build, ready, elapsed, code, ok in rows), encoding="utf-8")
|
||||
for build in BINARIES:
|
||||
values = [row[2] for row in rows if row[1] == build]
|
||||
values.sort()
|
||||
median = values[len(values) // 2]
|
||||
print(f"[TransitionCacheCost] build={build} median_whole_view_ready_s={median:.3f}")
|
||||
print(f"[TransitionCacheCost] runs={len(rows)} status={'FAIL' if failed else 'PASS'}")
|
||||
raise SystemExit(1 if failed or len(rows) != 10 else 0)
|
||||
@@ -0,0 +1,68 @@
|
||||
# VoxelForge L32 report
|
||||
|
||||
## Cause and fix
|
||||
|
||||
The old Gradient transition wrote constant solid-density values per band. Those values are not signed distance to the displaced boundary, so Marching Cubes crossed band levels as contour-like terraces. The hash surface quantization was not the cause: sampling across the final FI floor gives density -0.53345 at z=-121.50 m and +0.46655 at -121.25 m; the SW ceiling gives +0.01514 at -126.50 m and -0.98486 at -126.25 m. Both cross linearly at about 4 density units/m.
|
||||
|
||||
L32 restores the width/8 Gradient displacement (2 m each side, about 4 m peak-to-peak at 16 m owner width). Density is now signed distance to the union of the displaced solid intervals, continuous across band boundaries. Cap samples follow the same displaced faces. Passage-mouth fades use cubic smoothstep across a 2 m fade from the protected mouth radius; this preserves the face while blending the column flare. Version/evaluator is v20.
|
||||
|
||||
## Surface and interface audits
|
||||
|
||||
Branch-locked radial and straight profiles pass on both faces at LOD0 through LOD8. Every tested LOD has at least 3.15 m height range; no repeated-height plateau or following step was found. LOD0/1 profiles use 0.125 m samples (SW LOD1 also checked at 0.0625 m); LOD2–8 use 0.5 m samples. The largest adjacent-normal change was 17.50° on the fine SW LOD1 scan, below the 20° audit limit. Peak RSS for the final Python ring scans was 373,596 KiB (FI LOD0); the passage-fade scans peaked at 52,128 KiB. Mesh scans use tile/chunk processing.
|
||||
|
||||
Interleaved also passes 11 radial/straight profiles at each LOD0–8: all selected windows have full coverage, no flats or steps, and at most 0.367° adjacent-normal change. Their local height ranges are 0.032–3.174 m; the 3 m minimum is the Gradient amplitude check, not an Interleaved requirement. Peak RSS for this tile-streamed sweep was 24,468 KiB.
|
||||
|
||||
Both ring negative controls fail as intended: L30 has 0.742 m range, a 1.875 m flat run followed by a 0.241 m step, and a 27.34° normal jump; L31's flat fix has only 0.000046 m range. The passage-mouth audit passes on both overlapping faces: upper max normal change 15.06°, lower 7.96°, with full coverage and no flats or steps.
|
||||
|
||||
Hole/interface and minimum-band audits pass for Hard, Gradient and Interleaved, for both recipes and compilers: zero hole columns and zero air samples. Wider-than-declared and thinner-than-minimum controls are rejected. Minimum bands are 0.5 m FI, 1.0 m SW and 6.25 m lower band; maximum Interleaved crossing is 6.487 m against the 16 m bound. Passage audit passes all 16 recipe-stamped rows; recipe-mismatch and disconnected-end controls are detected.
|
||||
|
||||
Determinism passes 24/24: the L25 command and 24 m crossing, both recipes, Clang and GCC, workers 1/8/12.
|
||||
|
||||
## Captures and hashes
|
||||
|
||||
Tint-on and tint-off JPEGs are in `captures/`; all are under 30 KB (limit 300 KB):
|
||||
|
||||
- `fi-floor-near-tint-on.jpg`, `fi-floor-near-tint-off.jpg`
|
||||
- `fi-floor-far-tint-on.jpg`, `fi-floor-far-tint-off.jpg`
|
||||
- `sw-ceiling-near-tint-on.jpg`, `sw-ceiling-near-tint-off.jpg`
|
||||
- `sw-ceiling-far-tint-on.jpg`, `sw-ceiling-far-tint-off.jpg`
|
||||
|
||||
They show the FI floor and SW ceiling from inside each stratum, with grazing light, at LOD0 and LOD8. Capture SHA256 values:
|
||||
|
||||
| Capture | SHA256 |
|
||||
|---|---|
|
||||
| fi-floor-far-tint-off.jpg | `239b6be70c177e267d2db95f206fdcdc8834779a7948fdaaddd136b4163d1f34` |
|
||||
| fi-floor-far-tint-on.jpg | `3ecefd555275036a7045a541f02bb1873e22302495009dbd80b4ac58ce716b58` |
|
||||
| fi-floor-near-tint-off.jpg | `847f10acfb420c2bbded61bdee7f578cde19469777b2ef4c5a0eba08b406611c` |
|
||||
| fi-floor-near-tint-on.jpg | `4a5d3e3f8e8254639fa1840a2fb4497f2ab19f64bb4734e13abe3c3cfcfb4008` |
|
||||
| sw-ceiling-far-tint-off.jpg | `f5ec099076c507bed5a703302d89b665e139804a8f8895a68c1d5705aabf9f61` |
|
||||
| sw-ceiling-far-tint-on.jpg | `b82618814c8320be2de1ac53ba10e9ab4469299a8b53b776be32c6ca9a659988` |
|
||||
| sw-ceiling-near-tint-off.jpg | `5789ce59a7748aa278c1b95edae45aec87721721260c1a3aefe6c4cc1e47cd7a` |
|
||||
| sw-ceiling-near-tint-on.jpg | `aa4351c5effc5e93fc0879257fe056b3f4625e7842f5118f28588fb2c5c1afe2` |
|
||||
|
||||
Final Core binary SHA256:
|
||||
|
||||
- Clang: `052694a569c92ef407c25bd9a534c7123ef800f46733e5c428e965c32d905fdc`
|
||||
- GCC: `c8656901ce18861f12168849165785937a2495233604314df9e005ed1e92afdd`
|
||||
|
||||
## Cost and edits
|
||||
|
||||
Five interleaved pairs per recipe against L31. Median whole-view-ready time was SW 1.723 s → 1.756 s (+1.92%) and FI 16.963 s → 17.221 s (+1.52%); paired results were mixed and within observed run noise. No Unreal files changed. The five edited files are the Core public header, `FarShellsInternal.h`, `ResolvedLayoutInternal.h`, `ShapeWorldInternal.h` and `LinuxOffscreenCapture.cpp`; Unreal compilation was not available here.
|
||||
|
||||
## Failed and retried runs
|
||||
|
||||
- The initial multi-LOD Python audit was stopped by system RAM pressure (reported peak unavailable). Subsequent mesh audits ran with CPU/address-space caps and streamed or selected tiles; peak RSS is recorded above. The first whole-mesh FI LOD8 candidate scan was interrupted and replaced by a targeted tile scan.
|
||||
- Early ring-window searches and narrow-height audits selected the wrong one of two overlapping faces. The FI LOD3/4 branch probes measured 1.423 m amplitude / a 1.0 m flat run; the SW LOD1 low-face probe reached 20.19° normal change. Face-locked, broad-window scans replaced them and pass. The exploratory `ring-*`, `ringset-*`, `search-*` and `window-search-*` logs preserve rejected window candidates. The L30/L31 failures above are intentional negative controls.
|
||||
- The first Interleaved layout/export probe omitted the runner's required streaming flags and exited with usage; the retry produced the LOD0–8 export. Its broad 0.5 m candidate scan applied the Gradient 3 m window heuristic and rejected LOD0–4 (one LOD1 candidate switched overlapping faces at 62.92°); selected branch-locked profiles at those LODs and passing LOD5–8 windows show no plateaus or steps. An unsupported `--help` probe also returned usage with exit 2.
|
||||
- The exact-source passage audit at midpoint preference switched faces and reported a 47.79° jump; upper- and lower-face locked reruns pass. An early wrong-camera export selected zero tiles; the exact camera export was rerun. Initial multi-LOD capture meshes mixed neighboring shells, so the final captures use focused LOD0/LOD8 surface patches.
|
||||
- One fine-normal probe had a tuple-index error and was corrected. A SW far-mesh filter had an argument/layout parsing error and was corrected. The density CSV was overwritten by verbose output; the probe was rebuilt from final Core objects and rerun.
|
||||
- The transition-cache build failed on a nested `Shape` initializer; the retry built, but the cache showed no gain and was removed. `/usr/bin/time` was unavailable for one capture attempt; the capped renderer was rerun directly. The first determinism run was interrupted and resumed; the final matrix passes 24/24. One log-search command used unavailable `rg`; a capped streaming-Python scan replaced it.
|
||||
- Performance experiments were rejected or superseded: initial transition path +36.11% SW/+7.18% FI, planar query +14.89%/+2.28%, 3x3 coverage +6.06%/+1.65%, 5-point coverage +4.04%/+2.03%, single-transition fast path +3.52%/+1.39%; cache trial was slower (2.188 s vs 2.171 s). The retained hash-prefix version is the five-pair result above. Logs and exports are under `Saved/Linux/l32/`.
|
||||
|
||||
## Orchestrator review (Claude)
|
||||
|
||||
- Merged. Captures checked by me: the FI floor (near, tint off) is a smooth rolling surface with no rings or terraces;
|
||||
the SW ceiling and far views are smooth. Gradient is restored at width/8 (about 4 m peak-to-peak) with signed-distance
|
||||
density; the ring audit rejects both L30 (rings) and L31 (flat). The memory-pressure kill mid-round was resumed;
|
||||
audits now run capped and tile-streamed.
|
||||
- Cumulative SW cost vs L24 is roughly +5% after L28–L32 (features: columns, transitions, smooth faces). FI is ≈ +2%.
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the recipe-stamped L31 passage audit matrix and reject mislabeled runs."""
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l32"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
BINARIES = {
|
||||
"clang": OUT / "bin/VoxelForgeCoreHeadless-l32-clang-v20",
|
||||
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l32-gcc-v20",
|
||||
}
|
||||
MARKERS = (
|
||||
"[ResolvedPassageAuditSummary]",
|
||||
"[PassageOpenAirAuditSummary]",
|
||||
"[PassageNaturalnessSummary]",
|
||||
"[PassageNoLongBridgeAudit]",
|
||||
"[PassageRockColumnAuditSummary]",
|
||||
)
|
||||
rows = []
|
||||
failed = False
|
||||
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for style in range(4):
|
||||
data = ROOT / f"Saved/Linux/linux25-owner-{recipe}-style{style}.json"
|
||||
for compiler, binary in BINARIES.items():
|
||||
name = f"passage-{recipe}-style{style}-{compiler}"
|
||||
logfile = OUT / f"{name}.log"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", str(binary), str(data), "--workers=12", "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--force-generate",
|
||||
"--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
|
||||
f"--recipe={recipe}", "--audit-resolved-layout-passages",
|
||||
]
|
||||
started = time.monotonic()
|
||||
try:
|
||||
with logfile.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=900, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
result = None
|
||||
elapsed = time.monotonic() - started
|
||||
text = logfile.read_text(encoding="utf-8", errors="replace")
|
||||
stamps = re.findall(
|
||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
|
||||
text, re.M)
|
||||
recipe_ok = stamps == [(recipe, recipe, "PASS")]
|
||||
markers_ok = all(re.search(re.escape(marker) + r".*status=PASS", text)
|
||||
for marker in MARKERS)
|
||||
capsule_ok = bool(re.search(
|
||||
r"^\[ResolvedPassageAudit\].* blocked=0 support_samples=\d+ "
|
||||
r"unsupported=0 grade_failures=0 mouth_content=2/2 mouth_radius=2/2 "
|
||||
r"walkability=PASS$", text, re.M))
|
||||
island_route_ok = (recipe != "floating-islands" or bool(re.search(
|
||||
r"^\[FloatingIslandRouteAudit\].* blocked=0 support_samples=\d+ "
|
||||
r"unsupported=0 grade_failures=0 status=PASS$", text, re.M)))
|
||||
binding_ok = bool(re.search(
|
||||
r"\[ResolvedPassageAuditSummary\].*route_bindings=[1-9][0-9]* "
|
||||
r"route_binding_failures=0 .*status=PASS", text))
|
||||
walk_ok = "[Walkability] PASS" in text
|
||||
if recipe == "floating-islands":
|
||||
control_ok = "[PassageColumnDisconnectedEndControl]" in text and bool(
|
||||
re.search(r"\[PassageColumnDisconnectedEndControl\].*detected=true", text))
|
||||
else:
|
||||
control_ok = True
|
||||
ok = (result is not None and result.returncode == 0 and recipe_ok
|
||||
and markers_ok and capsule_ok and island_route_ok and binding_ok
|
||||
and walk_ok and control_ok)
|
||||
actual_recipe = stamps[0][1] if len(stamps) == 1 else "MISSING"
|
||||
rows.append((recipe, actual_recipe, style, compiler, elapsed,
|
||||
result.returncode if result else "TIMEOUT", ok))
|
||||
print(f"{name} elapsed={elapsed:.1f}s exit={result.returncode if result else 'TIMEOUT'} "
|
||||
f"recipe={stamps[0][1] if stamps else 'MISSING'} "
|
||||
f"capsule={'PASS' if capsule_ok and island_route_ok else 'FAIL'} "
|
||||
f"column_control={'PASS' if control_ok else 'FAIL'} {'PASS' if ok else 'FAIL'}",
|
||||
flush=True)
|
||||
if not ok:
|
||||
failed = True
|
||||
|
||||
summary = OUT / "passage-audit-matrix.csv"
|
||||
summary.write_text(
|
||||
"requested_recipe,actual_recipe,style,compiler,elapsed_seconds,exit_code,status\n" +
|
||||
"".join(f"{requested},{actual},{style},{compiler},{elapsed:.3f},{code},"
|
||||
f"{'PASS' if ok else 'FAIL'}\n"
|
||||
for requested, actual, style, compiler, elapsed, code, ok in rows), encoding="utf-8")
|
||||
|
||||
control_log = OUT / "recipe-mismatch-control.log"
|
||||
control_owner = OUT / "recipe-mismatch-control-owner.json"
|
||||
control_owner_data = json.loads(
|
||||
(ROOT / "Saved/Linux/linux25-owner-surface-world-style0.json").read_text(encoding="utf-8"))
|
||||
top_owner_stratum = max(control_owner_data["strates"], key=lambda stratum: stratum["top_chunk_z"])
|
||||
top_owner_stratum["asset"]["GeneratorType"] = 6 # ECaveGeneratorType::FloatingIslands
|
||||
control_owner.write_text(json.dumps(control_owner_data, separators=(",", ":")), encoding="utf-8")
|
||||
control_command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", str(BINARIES["clang"]),
|
||||
str(control_owner),
|
||||
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", "--force-generate", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=-1", "--recipe=surface-world",
|
||||
"--audit-resolved-layout-passages",
|
||||
]
|
||||
with control_log.open("w", encoding="utf-8") as stream:
|
||||
control = subprocess.run(control_command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=900, check=False)
|
||||
control_text = control_log.read_text(encoding="utf-8", errors="replace")
|
||||
control_ok = (control.returncode == 2 and
|
||||
"[WorldAssemblerRecipeAudit] requested=surface-world actual=floating-islands "
|
||||
"top_generator=FloatingIslands " in control_text and
|
||||
"floating_islands_stratum=present status=FAIL" in control_text)
|
||||
print(f"[L32RecipeMismatchControl] requested=surface-world actual=floating-islands "
|
||||
f"exit={control.returncode} detected={'true' if control_ok else 'false'} "
|
||||
f"{'PASS' if control_ok else 'FAIL'}", flush=True)
|
||||
|
||||
print(f"[L32PassageMatrix] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
|
||||
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
|
||||
raise SystemExit(1 if failed or not control_ok or len(rows) != 16 else 0)
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run five interleaved whole-view pairs against the L31 binary."""
|
||||
import os
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l32"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
RUN_TAG = os.environ.get("VOXELFORGE_COST_TAG", "").strip()
|
||||
BINARIES = {
|
||||
"l31": ROOT / "Saved/Linux/l31/bin/VoxelForgeCoreHeadless-linux31-clang",
|
||||
"l32": Path(os.environ.get("VOXELFORGE_L32_COST_BINARY",
|
||||
str(OUT / "bin/VoxelForgeCoreHeadless-l32-clang-v20"))),
|
||||
}
|
||||
ORDERS = (("l31", "l32"), ("l32", "l31"), ("l31", "l32"),
|
||||
("l32", "l31"), ("l31", "l32"))
|
||||
READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
|
||||
rows = []
|
||||
failed = False
|
||||
|
||||
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for pair, order in enumerate(ORDERS, 1):
|
||||
for build in order:
|
||||
tag = f"-{RUN_TAG}" if RUN_TAG else ""
|
||||
name = f"cost{tag}-{recipe}-pair{pair}-{build}"
|
||||
logfile = OUT / f"{name}.log"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
str(BINARIES[build]), str(ROOT / "Standalone/data/owner_world.json"),
|
||||
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", "--force-generate", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
|
||||
]
|
||||
started = time.monotonic()
|
||||
try:
|
||||
with logfile.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=900, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
result = None
|
||||
elapsed = time.monotonic() - started
|
||||
text = logfile.read_text(encoding="utf-8", errors="replace")
|
||||
matches = READY.findall(text)
|
||||
if build == "l32":
|
||||
stamps = re.findall(
|
||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
|
||||
text, re.M)
|
||||
actual_recipe = stamps[0][1] if len(stamps) == 1 else "missing"
|
||||
stamp_ok = stamps == [(recipe, recipe, "PASS")]
|
||||
else:
|
||||
configured = re.findall(r"^\[HeadlessConfig\].*\brecipe=([^\s]+)", text, re.M)
|
||||
actual_recipe = configured[0] if len(configured) == 1 else "missing"
|
||||
floating_plan = "[FloatingIslandWorldAssembly]" in text
|
||||
surface_plan = "[WorldAssemblerPrototype]" in text
|
||||
plan_matches = ((actual_recipe == "floating-islands" and floating_plan)
|
||||
or (actual_recipe == "surface-world" and not floating_plan
|
||||
and surface_plan))
|
||||
marker = (f"[L32BaselineRecipeAudit] requested={recipe} actual={actual_recipe} "
|
||||
f"headless_recipe={configured[0] if len(configured) == 1 else 'missing'} "
|
||||
f"floating_island_plan={'present' if floating_plan else 'absent'} "
|
||||
f"surface_world_plan={'present' if surface_plan else 'absent'} "
|
||||
f"status={'PASS' if actual_recipe == recipe and plan_matches else 'FAIL'}\n")
|
||||
with logfile.open("a", encoding="utf-8") as stream:
|
||||
stream.write(marker)
|
||||
text += marker
|
||||
stamp_ok = actual_recipe == recipe and plan_matches and len(configured) == 1
|
||||
valid = (result is not None and result.returncode == 0 and len(matches) == 1
|
||||
and "hash=off" in text and "cache_hits=0" in text and stamp_ok)
|
||||
ready = float(matches[0]) if len(matches) == 1 else float("nan")
|
||||
rows.append((recipe, pair, build, ready, elapsed,
|
||||
result.returncode if result else "TIMEOUT", valid))
|
||||
print(f"{recipe} pair={pair} build={build} whole_view_ready={ready:.3f}s "
|
||||
f"process_elapsed={elapsed:.1f}s {'PASS' if valid else 'FAIL'}", flush=True)
|
||||
failed |= not valid
|
||||
|
||||
summary = OUT / (f"cost-runs-{RUN_TAG}.csv" if RUN_TAG else "cost-runs.csv")
|
||||
summary.write_text(
|
||||
"recipe,pair,build,whole_view_ready_seconds,process_elapsed_seconds,exit_code,status\n" +
|
||||
"".join(f"{recipe},{pair},{build},{ready:.6f},{elapsed:.3f},{code},"
|
||||
f"{'PASS' if ok else 'FAIL'}\n"
|
||||
for recipe, pair, build, ready, elapsed, code, ok in rows), encoding="utf-8")
|
||||
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
by_build = {build: [row[3] for row in rows if row[0] == recipe and row[2] == build]
|
||||
for build in BINARIES}
|
||||
l31 = statistics.median(by_build["l31"])
|
||||
l32 = statistics.median(by_build["l32"])
|
||||
paired = []
|
||||
for pair in range(1, 6):
|
||||
values = {row[2]: row[3] for row in rows if row[0] == recipe and row[1] == pair}
|
||||
paired.append((values["l32"] - values["l31"]) / values["l31"] * 100.0)
|
||||
print(f"[L32CostSummary] recipe={recipe} pairs=5 median_l31_s={l31:.3f} "
|
||||
f"median_l32_s={l32:.3f} median_change_pct={(l32/l31-1)*100.0:+.2f} "
|
||||
f"paired_change_pct={','.join(f'{value:+.2f}' for value in paired)}")
|
||||
|
||||
print(f"[L32Cost] runs={len(rows)} status={'FAIL' if failed else 'PASS'} summary={summary}",
|
||||
flush=True)
|
||||
raise SystemExit(1 if failed or len(rows) != 20 else 0)
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check the L25 hash command and 24 m crossing across compilers and workers."""
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l32"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
RUN_TAG = os.environ.get("VOXELFORGE_DETERMINISM_TAG", "").strip()
|
||||
RUN_RESUME = os.environ.get("VOXELFORGE_DETERMINISM_RESUME", "0") == "1"
|
||||
BINARIES = {
|
||||
"clang": Path(os.environ.get("VOXELFORGE_L32_CLANG_BINARY",
|
||||
str(OUT / "bin/VoxelForgeCoreHeadless-l32-clang-v20"))),
|
||||
"gcc": Path(os.environ.get("VOXELFORGE_L32_GCC_BINARY",
|
||||
str(OUT / "bin/VoxelForgeCoreHeadless-l32-gcc-v20"))),
|
||||
}
|
||||
HASH_PATTERN = re.compile(
|
||||
r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
|
||||
candidate_reference = {}
|
||||
rows = []
|
||||
failed = False
|
||||
prior_elapsed = {}
|
||||
if RUN_RESUME and RUN_TAG:
|
||||
prior_log = OUT / f"determinism-{RUN_TAG}-runner.log"
|
||||
if prior_log.exists():
|
||||
for line in prior_log.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
match = re.match(r"^(\S+) elapsed=([0-9.]+)s .* PASS$", line)
|
||||
if match:
|
||||
prior_elapsed[match.group(1)] = float(match.group(2))
|
||||
|
||||
|
||||
def run(kind, compiler, recipe, workers):
|
||||
global failed
|
||||
tag = f"-{RUN_TAG}" if RUN_TAG else ""
|
||||
name = f"{kind}{tag}-{compiler}-{recipe}-w{workers}"
|
||||
logfile = OUT / f"{name}.log"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
str(BINARIES[compiler]), str(ROOT / "Standalone/data/owner_world.json"),
|
||||
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||
"--stream-hash", "--force-generate", "--clip-radius=3", "--max-clip-level=8",
|
||||
"--far-horizon-lod8-step=0", f"--workers={workers}", f"--recipe={recipe}",
|
||||
]
|
||||
if kind == "crossing24":
|
||||
command.extend(("--camera-step=24,0,0", "--region-crossing"))
|
||||
if RUN_RESUME and name in prior_elapsed and logfile.exists():
|
||||
result = subprocess.CompletedProcess(command, 0)
|
||||
elapsed = prior_elapsed[name]
|
||||
else:
|
||||
started = time.monotonic()
|
||||
try:
|
||||
with logfile.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=900, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
result = None
|
||||
elapsed = time.monotonic() - started
|
||||
text = logfile.read_text(encoding="utf-8", errors="replace")
|
||||
stamps = re.findall(
|
||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
|
||||
text, re.M)
|
||||
hashes = HASH_PATTERN.findall(text)
|
||||
key = (kind, recipe)
|
||||
reference = candidate_reference.get(key)
|
||||
stable = reference is None or hashes == reference
|
||||
if reference is None and hashes:
|
||||
candidate_reference[key] = hashes
|
||||
ok = (result is not None and result.returncode == 0
|
||||
and stamps == [(recipe, recipe, "PASS")]
|
||||
and "hash=on" in text and len(hashes) == 12 and stable)
|
||||
rows.append((kind, recipe, compiler, workers, elapsed,
|
||||
result.returncode if result else "TIMEOUT", len(hashes), ok))
|
||||
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
|
||||
f"hashes={len(hashes)} stable_across_matrix={'yes' if stable else 'no'} "
|
||||
f"{'PASS' if ok else 'FAIL'}", flush=True)
|
||||
failed |= not ok
|
||||
|
||||
|
||||
for kind in ("l25", "crossing24"):
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for compiler in ("clang", "gcc"):
|
||||
for workers in (1, 8, 12):
|
||||
run(kind, compiler, recipe, workers)
|
||||
|
||||
summary = OUT / (f"determinism-{RUN_TAG}.csv" if RUN_TAG else "determinism.csv")
|
||||
summary.write_text(
|
||||
"kind,recipe,compiler,workers,elapsed_seconds,exit_code,hash_rows,status\n" +
|
||||
"".join(f"{kind},{recipe},{compiler},{workers},{elapsed:.3f},{code},{count},"
|
||||
f"{'PASS' if ok else 'FAIL'}\n"
|
||||
for kind, recipe, compiler, workers, elapsed, code, count, ok in rows),
|
||||
encoding="utf-8")
|
||||
print(f"[L32Determinism] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
|
||||
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
|
||||
raise SystemExit(1 if failed or len(rows) != 24 else 0)
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run L31 route/proof and transition invariants with recipe-stamped logs."""
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l32"
|
||||
BINARIES = {
|
||||
"clang": OUT / "bin/VoxelForgeCoreHeadless-l32-clang-v20",
|
||||
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l32-gcc-v20",
|
||||
}
|
||||
rows = []
|
||||
failed = False
|
||||
|
||||
|
||||
def run(name, binary, data, recipe, extra, checks):
|
||||
global failed
|
||||
logfile = OUT / f"{name}.log"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", str(binary), str(data), "--workers=12", "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--force-generate",
|
||||
"--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
|
||||
f"--recipe={recipe}", *extra,
|
||||
]
|
||||
started = time.monotonic()
|
||||
try:
|
||||
with logfile.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=900, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
result = None
|
||||
elapsed = time.monotonic() - started
|
||||
text = logfile.read_text(encoding="utf-8", errors="replace")
|
||||
stamps = re.findall(
|
||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
|
||||
text, re.M)
|
||||
stamp_ok = stamps == [(recipe, recipe, "PASS")]
|
||||
checks_ok = all(check(text) for check in checks)
|
||||
ok = result is not None and result.returncode == 0 and stamp_ok and checks_ok
|
||||
rows.append((name, recipe, elapsed, result.returncode if result else "TIMEOUT", ok))
|
||||
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
|
||||
f"stamp={'PASS' if stamp_ok else 'FAIL'} checks={'PASS' if checks_ok else 'FAIL'} "
|
||||
f"{'PASS' if ok else 'FAIL'}", flush=True)
|
||||
failed |= not ok
|
||||
|
||||
|
||||
owner = ROOT / "Standalone/data/owner_world.json"
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for compiler, binary in BINARIES.items():
|
||||
name = f"invariant-route-proof-{recipe}-{compiler}"
|
||||
run(name, binary, owner, recipe,
|
||||
["--stream-hash", "--audit-uniform-block-proofs", "--audit-route-parity"],
|
||||
[lambda text: bool(re.search(
|
||||
r"^\[RouteParityAudit\].*status=PASS$", text, re.M)),
|
||||
lambda text: bool(re.search(
|
||||
r"^\[UniformBlockProofAudit\].*violations=0(?:\s|$)", text, re.M))])
|
||||
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for compiler, binary in BINARIES.items():
|
||||
for mode in ("hard", "gradient", "interleaved"):
|
||||
name = f"invariant-transition-{recipe}-{mode}-{compiler}"
|
||||
run(name, binary, owner, recipe,
|
||||
[f"--transition-mode={mode}", "--audit-strate-transitions"],
|
||||
[lambda text: bool(re.search(
|
||||
r"^\[StrateTransitionAuditSummary\].*hole_columns=0 air_samples=0 .*status=PASS$",
|
||||
text, re.M)),
|
||||
lambda text: bool(re.search(
|
||||
r"^\[StrateTransitionNegativeControls\].*status=PASS$", text, re.M))])
|
||||
|
||||
summary = OUT / "invariant-audits.csv"
|
||||
summary.write_text(
|
||||
"run,recipe,elapsed_seconds,exit_code,status\n" +
|
||||
"".join(f"{name},{recipe},{elapsed:.3f},{code},{'PASS' if ok else 'FAIL'}\n"
|
||||
for name, recipe, elapsed, code, ok in rows),
|
||||
encoding="utf-8")
|
||||
print(f"[L32InvariantAudits] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
|
||||
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
|
||||
raise SystemExit(1 if failed or len(rows) != 16 else 0)
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Stream one VFMSH tile at a time to locate LOD/interface patches."""
|
||||
import argparse
|
||||
import array
|
||||
import resource
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("scene")
|
||||
parser.add_argument("--lod", type=int, required=True)
|
||||
parser.add_argument("--surface", choices=("floor", "ceiling"), required=True)
|
||||
parser.add_argument("--height-min-m", type=float, required=True)
|
||||
parser.add_argument("--height-max-m", type=float, required=True)
|
||||
args = parser.parse_args()
|
||||
candidates = []
|
||||
scanned = 0
|
||||
with open(args.scene, "rb") as stream:
|
||||
if stream.read(8) != b"VFMSH001":
|
||||
raise SystemExit("invalid VFMSH001 scene")
|
||||
version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
|
||||
stream.seek(40, 1)
|
||||
if version != 1:
|
||||
raise SystemExit("unsupported mesh scene")
|
||||
for _ in range(tile_count):
|
||||
key = struct.unpack("<4i", stream.read(16))
|
||||
surface_count, _, vertex_count, index_count, mask_count = struct.unpack(
|
||||
"<5I", stream.read(20))
|
||||
payload = vertex_count * 24 + index_count * 4 + mask_count
|
||||
if key[3] != args.lod:
|
||||
stream.seek(payload, 1)
|
||||
continue
|
||||
scanned += 1
|
||||
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()
|
||||
hits = 0
|
||||
sum_x = sum_y = sum_z = 0.0
|
||||
min_z, max_z = float("inf"), float("-inf")
|
||||
want_up = args.surface == "floor"
|
||||
for offset in range(0, index_count, 3):
|
||||
if masks and masks[offset // 3]:
|
||||
continue
|
||||
a, b, c = indices[offset:offset + 3]
|
||||
if max(a, b, c) >= surface_count:
|
||||
continue
|
||||
nz = (normals[a * 3 + 2] + normals[b * 3 + 2] + normals[c * 3 + 2]) / 3.0
|
||||
if (nz > 0) != want_up:
|
||||
continue
|
||||
za, zb, zc = positions[a * 3 + 2], positions[b * 3 + 2], positions[c * 3 + 2]
|
||||
if ((za + zb + zc) / 3.0 < args.height_min_m
|
||||
or (za + zb + zc) / 3.0 > args.height_max_m):
|
||||
continue
|
||||
hits += 1
|
||||
sum_x += (positions[a * 3] + positions[b * 3] + positions[c * 3]) / 3.0
|
||||
sum_y += (positions[a * 3 + 1] + positions[b * 3 + 1] + positions[c * 3 + 1]) / 3.0
|
||||
z = (za + zb + zc) / 3.0
|
||||
sum_z += z
|
||||
min_z, max_z = min(min_z, za, zb, zc), max(max_z, za, zb, zc)
|
||||
if hits:
|
||||
extent = 32.0 * (1 << key[3]) * voxel_um / 1_000_000.0
|
||||
candidates.append((hits, key, sum_x / hits, sum_y / hits,
|
||||
sum_z / hits, min_z, max_z, extent))
|
||||
candidates.sort(reverse=True, key=lambda item: item[0])
|
||||
print(f"[VFMeshTileScan] lod={args.lod} surface={args.surface} scanned_tiles={scanned} "
|
||||
f"candidate_tiles={len(candidates)} peak_rss_kib="
|
||||
f"{resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
|
||||
for hits, key, x, y, z, low, high, extent in candidates[:40]:
|
||||
print(f"[VFMeshTile] key={key} triangles={hits} centroid_m=({x:.3f},{y:.3f},{z:.3f}) "
|
||||
f"z_m={low:.3f}..{high:.3f} extent_m={extent:.3f}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 27 KiB |