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
131 lines
6.9 KiB
Python
131 lines
6.9 KiB
Python
#!/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()
|