passages: smooth 12 m lower-mouth radius taper; all four-strate passages pass (L68, v213)
The FI->TN wall beat (4.77 cm at 7.06 m) was the lower-mouth flare: the full mouth radius was assigned to the penultimate control point, a one-segment radius jump. The shared route builder now blends to the mouth radius over 12 m with a smoothstep. Wall ripple 1-2: 7.27 -> 1.62 cm. All three passages pass support/blocked/grade/ripple/naturalness; controls detected. Fast tier 269/269 incl. the new-plan stage; W1/W8/W12 equal, Clang and GCC. Hash pins: FI style 2 re-pinned at v213. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
087333bf8a
commit
bd7bba994c
10 files changed
+677
-12
No files matched your search
@@ -0,0 +1,395 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render arrival, departure, and mid-tunnel views for the four-strate owner plan."""
|
||||
import json
|
||||
import importlib.util
|
||||
import argparse
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l68/captures-run"
|
||||
BIN = ROOT / "Saved/Linux/l68/bin-final-clang"
|
||||
CAPTURES = Path(__file__).resolve().parent / "captures"
|
||||
HEADLESS = BIN / "VoxelForgeCoreHeadless-l68-final-clang"
|
||||
RENDERER = BIN / "VoxelForgeOffscreenCapture-l68-final-clang"
|
||||
PNG_DIR = OUT / "capture-png"
|
||||
RECIPE_INPUTS = (
|
||||
("passage-0-1", ROOT / "Standalone/data/owner_world.json",
|
||||
"surface-world", "SurfaceWorld", "upper"),
|
||||
("passage-1-2", ROOT / "Standalone/data/owner_world.json",
|
||||
"surface-world", "FloatingIslands", "upper"),
|
||||
("passage-2-3", ROOT / "Standalone/data/owner_world.json",
|
||||
"surface-world", "TunnelNetwork", "upper"),
|
||||
)
|
||||
SUBVOXELS_PER_VOXEL = 256.0
|
||||
RING_AUDIT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
|
||||
spec = importlib.util.spec_from_file_location("ring_profile_l32", RING_AUDIT)
|
||||
ring_profile = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(ring_profile)
|
||||
|
||||
for directory in (OUT, BIN, PNG_DIR, CAPTURES):
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
ENV = os.environ.copy()
|
||||
ENV["LP_NUM_THREADS"] = "12"
|
||||
ENV["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
|
||||
PARSER = argparse.ArgumentParser(description=__doc__)
|
||||
PARSER.add_argument("--scene", choices=[value[0] for value in RECIPE_INPUTS])
|
||||
PARSER.add_argument("--view", choices=("arrival-mouth", "departure-mouth", "mid-tunnel"))
|
||||
OPTIONS = PARSER.parse_args()
|
||||
|
||||
|
||||
def run(name, command):
|
||||
log = OUT / f"capture-{name}.log"
|
||||
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1800s", *map(str, command)]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(wrapped, cwd=ROOT, env=ENV, stdout=stream,
|
||||
stderr=subprocess.STDOUT, timeout=1800, check=False)
|
||||
elapsed = time.monotonic() - started
|
||||
print(f"[L68CaptureRun] step={name} elapsed={elapsed:.1f}s exit={result.returncode} log={log}",
|
||||
flush=True)
|
||||
if result.returncode != 0:
|
||||
raise SystemExit(f"{name} failed; see {log}")
|
||||
return log
|
||||
|
||||
|
||||
def add(a, b):
|
||||
return tuple(x + y for x, y in zip(a, b))
|
||||
|
||||
|
||||
def sub(a, b):
|
||||
return tuple(x - y for x, y in zip(a, b))
|
||||
|
||||
|
||||
def mul(a, scale):
|
||||
return tuple(x * scale for x in a)
|
||||
|
||||
|
||||
def norm(a):
|
||||
length = math.sqrt(sum(x * x for x in a))
|
||||
if length <= 1.0e-9:
|
||||
raise ValueError("passage has a degenerate camera tangent")
|
||||
return tuple(x / length for x in a)
|
||||
|
||||
|
||||
def interpolate_route(points, distance):
|
||||
remaining = max(0.0, distance)
|
||||
for a, b in zip(points, points[1:]):
|
||||
delta = sub(b[0], a[0])
|
||||
length = math.sqrt(sum(x * x for x in delta))
|
||||
if length <= 1.0e-9:
|
||||
continue
|
||||
if remaining <= length:
|
||||
t = remaining / length
|
||||
pos = tuple(a[0][axis] + delta[axis] * t for axis in range(3))
|
||||
radius = a[1] + (b[1] - a[1]) * t
|
||||
return pos, radius
|
||||
remaining -= length
|
||||
return points[-1]
|
||||
|
||||
|
||||
def route_frame(points, distance):
|
||||
center, radius = interpolate_route(points, distance)
|
||||
before, _ = interpolate_route(points, max(0.0, distance - 0.25))
|
||||
after, _ = interpolate_route(points, distance + 0.25)
|
||||
tangent = norm(sub(after, before))
|
||||
horizontal = norm((tangent[0], tangent[1], 0.0))
|
||||
side = (-horizontal[1], horizontal[0], 0.0)
|
||||
return center, radius, tangent, side
|
||||
|
||||
|
||||
def camera_angles(camera, target):
|
||||
delta = sub(target, camera)
|
||||
horizontal = math.hypot(delta[0], delta[1])
|
||||
yaw = math.degrees(math.atan2(delta[0], delta[1]))
|
||||
pitch = math.degrees(math.atan2(delta[2], horizontal))
|
||||
return yaw, pitch
|
||||
|
||||
|
||||
def fmt(values):
|
||||
return ",".join(f"{value:.6f}" for value in values)
|
||||
|
||||
|
||||
def load_reference_cameras():
|
||||
log = ROOT / "Saved/Linux/l34/run-capture-standard-set-final-radius3.log"
|
||||
pattern = re.compile(
|
||||
r"^\[L34CaptureCamera\] view=(\S+).*?camera_m=\(([^)]*)\) "
|
||||
r"target_m=\(([^)]*)\) clip=(\S+)", re.M)
|
||||
values = {}
|
||||
for name, camera, target, clip in pattern.findall(
|
||||
log.read_text(encoding="utf-8", errors="replace")):
|
||||
parse = lambda value: tuple(float(part) for part in value.split(","))
|
||||
values[name] = (parse(camera), parse(target), None if clip == "none" else parse(clip))
|
||||
return values
|
||||
|
||||
|
||||
REFERENCE_CAMERAS = {}
|
||||
|
||||
|
||||
def terrain_surface_near(mesh, x, y, expected_height):
|
||||
bounds = (x - 2.0, x + 2.0, y - 2.0, y + 2.0)
|
||||
_, triangles, _ = ring_profile.read_scene(
|
||||
str(mesh), None, -1.0e9, 1.0e9, bounds)
|
||||
buckets = ring_profile.build_buckets(triangles, True, bounds, cell_size=1.0)
|
||||
hits = []
|
||||
for dy in range(-4, 5):
|
||||
for dx in range(-4, 5):
|
||||
sample_x, sample_y = x + dx * 0.5, y + dy * 0.5
|
||||
hit = ring_profile.sample_surface(buckets, sample_x, sample_y, True, 1.0,
|
||||
preferred_height=expected_height)
|
||||
if hit is not None:
|
||||
hits.append((math.hypot(dx * 0.5, dy * 0.5),
|
||||
abs(hit[0] - expected_height), sample_x, sample_y, hit[0]))
|
||||
if not hits:
|
||||
return None
|
||||
distance, _, sample_x, sample_y, height = min(hits)
|
||||
if distance > 2.0 or abs(height - expected_height) > 8.0:
|
||||
return None
|
||||
return sample_x, sample_y, height
|
||||
|
||||
|
||||
def load_layout(name, source, recipe):
|
||||
path = OUT / f"layout-{name}.json"
|
||||
run(f"layout-{name}", [HEADLESS, source, "--workers=12", "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=2",
|
||||
"--max-clip-level=0", "--max-level=0", f"--recipe={recipe}",
|
||||
f"--resolved-layout-output={path}", "--resolved-layout-only"])
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def select_passage(layout, mouth_generator, preferred_side):
|
||||
strata = {int(value["strate_index"]): value for value in layout["strates"]}
|
||||
preferred_candidates = []
|
||||
fallback_candidates = []
|
||||
for passage in layout["passages"]:
|
||||
upper = strata[int(passage["upper_strate_index"])]
|
||||
lower = strata[int(passage["lower_strate_index"])]
|
||||
sides = []
|
||||
if upper["generator_kind"] == mouth_generator:
|
||||
sides.append("upper")
|
||||
if lower["generator_kind"] == mouth_generator:
|
||||
sides.append("lower")
|
||||
if preferred_side in sides:
|
||||
preferred_candidates.append((passage, preferred_side == "lower"))
|
||||
elif sides:
|
||||
fallback_candidates.append((passage, sides[0] == "lower"))
|
||||
candidates = preferred_candidates or fallback_candidates
|
||||
if not candidates:
|
||||
raise RuntimeError(f"no passage mouth in generator {mouth_generator}")
|
||||
return sorted(candidates, key=lambda item: item[0]["stable_id"])[0]
|
||||
|
||||
|
||||
def make_views(layout, passage, reverse):
|
||||
q_to_m = layout["core_voxel_size_um"] / (SUBVOXELS_PER_VOXEL * 1_000_000.0)
|
||||
controls = [
|
||||
(tuple(value * q_to_m for value in point["position_q"]),
|
||||
point["radius_q"] * q_to_m)
|
||||
for point in passage["control_points"]
|
||||
]
|
||||
lower_mouth = tuple(value * q_to_m for value in passage["lower_mouth_q"])
|
||||
lower_index = min(range(len(controls)), key=lambda i: sum(
|
||||
(controls[i][0][axis] - lower_mouth[axis]) ** 2 for axis in range(3)))
|
||||
path = controls[:lower_index + 1]
|
||||
if len(path) < 2:
|
||||
raise RuntimeError(f"{passage['stable_id']} has no mouth-to-mouth route")
|
||||
if reverse:
|
||||
path.reverse()
|
||||
lengths = [math.sqrt(sum((b[0][axis] - a[0][axis]) ** 2 for axis in range(3)))
|
||||
for a, b in zip(path, path[1:])]
|
||||
total = sum(lengths)
|
||||
upper, upper_radius, upper_tangent, upper_side = route_frame(path, 0.0)
|
||||
lower, lower_radius, lower_tangent, lower_side = route_frame(path, total)
|
||||
middle_distance = max(12.0, min(total - 12.0, total * 0.4)) if total > 24.0 else total * 0.4
|
||||
if (int(passage["upper_strate_index"],) == 1
|
||||
and int(passage["lower_strate_index"]) == 2 and not reverse):
|
||||
# The old 7 m authored-radius beat sat near the middle of this audit window.
|
||||
middle_distance = min(total - 12.0, 90.8)
|
||||
entry_distance = min(6.0, total * 0.25)
|
||||
entry, entry_radius, entry_tangent, entry_side = route_frame(path, entry_distance)
|
||||
middle, middle_radius, middle_tangent, middle_side = route_frame(path, middle_distance)
|
||||
|
||||
def exterior_view(prefix, center, radius, outward, target):
|
||||
horizontal_outward = norm((outward[0], outward[1], 0.0))
|
||||
eye_height = center[2] - radius + 1.6
|
||||
eye = (center[0] + horizontal_outward[0] * 5.5,
|
||||
center[1] + horizontal_outward[1] * 5.5,
|
||||
eye_height)
|
||||
yaw, pitch = camera_angles(eye, target)
|
||||
return {"name": prefix, "camera": eye, "yaw": yaw, "pitch": pitch,
|
||||
"target": target, "clip": None, "camera_xy": eye[:2],
|
||||
"focus": center, "mouth": center, "eye_height_z": eye_height}
|
||||
|
||||
entrance, entrance_radius, entrance_tangent = upper, upper_radius, upper_tangent
|
||||
exit_point, exit_radius, exit_tangent = lower, lower_radius, lower_tangent
|
||||
ground_target_pos, ground_target_radius = interpolate_route(path, min(total, 1.0))
|
||||
ground = exterior_view("ground-mouth", entrance, entrance_radius,
|
||||
mul(entrance_tangent, -1.0), (ground_target_pos[0], ground_target_pos[1],
|
||||
ground_target_pos[2] - ground_target_radius + 1.0))
|
||||
ground["focus"] = entrance
|
||||
ground["outside_camera"] = True
|
||||
|
||||
def cutaway_view(prefix, center, radius, tangent, side, distance, target_distance):
|
||||
camera = (center[0] - side[0] * 7.0, center[1] - side[1] * 7.0,
|
||||
center[2] - radius + 1.6)
|
||||
target_pos, target_radius = interpolate_route(path, target_distance)
|
||||
target = (target_pos[0], target_pos[1],
|
||||
target_pos[2] - target_radius + 1.6)
|
||||
yaw, pitch = camera_angles(camera, target)
|
||||
plane = (side[0], side[1], 0.0,
|
||||
-(side[0] * center[0] + side[1] * center[1]))
|
||||
return {"name": prefix, "camera": camera, "yaw": yaw, "pitch": pitch,
|
||||
"target": target, "clip": plane, "camera_xy": camera[:2],
|
||||
"focus": center, "mouth": center}
|
||||
|
||||
def inside_mouth_view(prefix, at_start):
|
||||
endpoint_distance = 0.0 if at_start else total
|
||||
mouth, _, tangent, mouth_side = route_frame(path, endpoint_distance)
|
||||
camera_distance = min(total, 7.4) if at_start else max(0.0, total - 5.5)
|
||||
center, _, _, side = route_frame(path, camera_distance)
|
||||
camera = (center[0] + side[0] * 2.5,
|
||||
center[1] + side[1] * 2.5, center[2])
|
||||
outward = mul(tangent, -1.0) if at_start else tangent
|
||||
target = (mouth[0] + outward[0] * 8.0 + mouth_side[0] * 2.5,
|
||||
mouth[1] + outward[1] * 8.0 + mouth_side[1] * 2.5,
|
||||
mouth[2])
|
||||
yaw, pitch = camera_angles(camera, target)
|
||||
return {"name": prefix, "camera": camera, "yaw": yaw, "pitch": pitch,
|
||||
"target": target, "clip": None, "camera_xy": camera[:2],
|
||||
"focus": mouth, "mouth": mouth, "outside_camera": False,
|
||||
"camera_location": "passage-air", "focus_radius": 30.0,
|
||||
"eye_height_z": camera[2], "fov_deg": 80.0}
|
||||
|
||||
# Place each interface view at its true route endpoint. The endpoint camera
|
||||
# is a side cutaway so the solid terrain in front of an underground mouth
|
||||
# cannot hide the floor-to-tunnel connection.
|
||||
arrival_distance = 0.0
|
||||
arrival_center, arrival_radius, arrival_tangent, arrival_side = route_frame(
|
||||
path, arrival_distance)
|
||||
arrival = cutaway_view("arrival", arrival_center, arrival_radius,
|
||||
arrival_tangent, arrival_side, arrival_distance,
|
||||
min(total, arrival_distance + 12.0))
|
||||
arrival["camera_location"] = "passage-air"
|
||||
arrival["focus_radius"] = 20.0
|
||||
arrival["yaw"], arrival["pitch"] = camera_angles(
|
||||
arrival["camera"], arrival["target"])
|
||||
|
||||
entry_camera_distance = min(8.0, total * 0.1)
|
||||
entry_center, entry_radius, entry_tangent, entry_side = route_frame(
|
||||
path, entry_camera_distance)
|
||||
# Player-height view from just inside the passage, aimed through its actual
|
||||
# interface mouth so the floor, bore, and opening share one frame.
|
||||
entry_camera = (entry_center[0], entry_center[1],
|
||||
entry_center[2] - entry_radius + 1.6)
|
||||
entry_target_pos, entry_target_radius = interpolate_route(
|
||||
path, min(total, entry_distance + 14.0))
|
||||
entry_target = (entry_target_pos[0], entry_target_pos[1],
|
||||
entry_camera[2] - 2.5)
|
||||
entry_yaw, entry_pitch = camera_angles(entry_camera, entry_target)
|
||||
entry_view = {"name": "rock-entry", "camera": entry_camera,
|
||||
"yaw": entry_yaw, "pitch": entry_pitch, "target": entry_target,
|
||||
"clip": None, "camera_xy": entry_camera[:2],
|
||||
"focus": entry_center, "mouth": entrance, "outside_camera": False,
|
||||
"camera_location": "passage-air", "focus_radius": 20.0,
|
||||
"eye_height_z": entry_camera[2]}
|
||||
middle_view = cutaway_view("mid-tunnel", middle, middle_radius, middle_tangent,
|
||||
middle_side, middle_distance, min(total, middle_distance + 2.0))
|
||||
ground["focus_radius"] = 20.0
|
||||
middle_view["focus_radius"] = 20.0
|
||||
middle_camera_center, _ = interpolate_route(path, middle_distance)
|
||||
middle_view["camera"] = (middle_camera_center[0] + middle_side[0] * 0.75,
|
||||
middle_camera_center[1] + middle_side[1] * 0.75,
|
||||
middle[2] - middle_radius + 1.6)
|
||||
middle_target_pos, middle_target_radius = interpolate_route(
|
||||
path, min(total, middle_distance + 14.0))
|
||||
middle_view["target"] = (middle_target_pos[0] + middle_side[0] * 2.35,
|
||||
middle_target_pos[1] + middle_side[1] * 2.35,
|
||||
middle_target_pos[2] - middle_target_radius + 1.8)
|
||||
# Keep the camera inside the bore, close to one wall, and look down the
|
||||
# route so the old beat location is visible at player height.
|
||||
middle_view["clip"] = None
|
||||
middle_view["camera_location"] = "passage-air"
|
||||
middle_view["yaw"], middle_view["pitch"] = camera_angles(
|
||||
middle_view["camera"], middle_view["target"])
|
||||
return (ground, entry_view, middle_view, arrival,
|
||||
inside_mouth_view("inside-mouth-start", True),
|
||||
inside_mouth_view("inside-mouth-end", False)), total, lower_index
|
||||
|
||||
|
||||
def export_capture(scene_name, source, recipe, passage, endpoint, view):
|
||||
stem = f"{passage['stable_id'].replace('/', '-')}-{endpoint}"
|
||||
view["name"] = stem
|
||||
mesh = OUT / f"{stem}.vfmesh"
|
||||
png = PNG_DIR / f"{stem}.png"
|
||||
jpg = CAPTURES / f"{stem}.jpg"
|
||||
yaw, pitch = view["yaw"], view["pitch"]
|
||||
camera = view["camera"]
|
||||
export_camera = view.get("focus", camera)
|
||||
run(f"{stem}-export", [HEADLESS, source, "--workers=12",
|
||||
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||
"--force-generate", "--clip-radius=3", "--max-clip-level=0", "--max-level=0",
|
||||
"--render-distance-chunks=0", f"--recipe={recipe}",
|
||||
f"--camera={fmt((*export_camera, yaw, pitch))}", f"--mesh-export={mesh}"])
|
||||
render_clip = view["clip"]
|
||||
surface = None
|
||||
if view.get("outside_camera", False):
|
||||
surface = terrain_surface_near(mesh, *view["camera_xy"], view["target"][2])
|
||||
if surface is None:
|
||||
camera = (view["camera_xy"][0], view["camera_xy"][1], view["eye_height_z"])
|
||||
else:
|
||||
camera = (surface[0], surface[1], surface[2] + 1.6)
|
||||
elif render_clip is not None:
|
||||
camera = view["camera"]
|
||||
yaw, pitch = camera_angles(camera, view["target"])
|
||||
fov_deg = view.get("fov_deg", 80.0)
|
||||
print(f"[L68CaptureCamera] view={stem} camera_m=({fmt(camera)}) "
|
||||
f"target_m=({fmt(view['target'])}) "
|
||||
f"clip={fmt(render_clip) if render_clip is not None else 'none'} fov_deg={fov_deg:.1f}",
|
||||
flush=True)
|
||||
render = [RENDERER, mesh, png, "--size=1600x900", f"--fov-deg={fov_deg:.1f}",
|
||||
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}",
|
||||
"--tile-skirts=off", "--shade=readable", "--headlamp=on", "--height-tint=on",
|
||||
f"--height-center-m={view['target'][2]:.6f}", "--height-range-m=8", "--tile-edges=off"]
|
||||
if view.get("focus_radius", 24.0) is not None:
|
||||
render.append(f"--focus-region={fmt((*view['focus'], view.get('focus_radius', 24.0)))}")
|
||||
if render_clip is not None:
|
||||
render.append(f"--clip-plane={fmt(render_clip)}")
|
||||
run(f"{stem}-render", render)
|
||||
run(f"{stem}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error",
|
||||
"-y", "-i", png, "-frames:v", "1", "-q:v", "4", jpg])
|
||||
size = jpg.stat().st_size if jpg.exists() else 0
|
||||
print(f"[L68CaptureJPEG] file={jpg.relative_to(ROOT)} bytes={size} "
|
||||
f"status={'PASS' if 0 < size <= 300_000 else 'FAIL'}", flush=True)
|
||||
if not 0 < size <= 300_000:
|
||||
raise SystemExit(f"{jpg.name} is missing or exceeds 300 KB")
|
||||
|
||||
|
||||
for scene_name, source, recipe, mouth_generator, preferred_side in RECIPE_INPUTS:
|
||||
if OPTIONS.scene and scene_name != OPTIONS.scene:
|
||||
continue
|
||||
layout = load_layout(scene_name, source, recipe)
|
||||
passage, _ = select_passage(layout, mouth_generator, preferred_side)
|
||||
forward_views, route_length, lower_index = make_views(layout, passage, False)
|
||||
reverse_views, _, _ = make_views(layout, passage, True)
|
||||
print(f"[L68CapturePassage] interface={scene_name.removeprefix('passage-')} "
|
||||
f"id={passage['stable_id']} route_m={route_length:.3f} "
|
||||
f"control_points={lower_index + 1}", flush=True)
|
||||
if scene_name == "passage-2-3":
|
||||
endpoint_views = (("arrival-mouth", forward_views[4]),
|
||||
("departure-mouth", forward_views[5]))
|
||||
else:
|
||||
endpoint_views = (
|
||||
("arrival-mouth", forward_views[0] if scene_name == "passage-0-1" else forward_views[1]),
|
||||
("departure-mouth", reverse_views[3]),
|
||||
)
|
||||
for endpoint, view in endpoint_views:
|
||||
if OPTIONS.view and endpoint != OPTIONS.view:
|
||||
continue
|
||||
export_capture(scene_name, source, recipe, passage, endpoint, view)
|
||||
if scene_name == "passage-1-2" and (OPTIONS.view is None or OPTIONS.view == "mid-tunnel"):
|
||||
export_capture(scene_name, source, recipe, passage, "mid-tunnel", forward_views[2])
|
||||
@@ -0,0 +1,48 @@
|
||||
# Linux68 — FI→TN 7 m wall beat
|
||||
|
||||
## Cause and fix
|
||||
|
||||
The 7.06 m beat came from the authored-radius profile. In the failing 32 m window, a radius-only diagnostic reproduces 4.77 cm at 7.0625 m. The route’s interior controls are generally 5.6–6.3 m apart; the last 3.0→4.5 m radius change spans 5.803 m, followed by an 8.8 m constant-radius span. The measured beat does not repeat at either spacing. Removing relief left 7.46 cm, and restoring the earlier carve cutoff left 7.55 cm. The shorter 114.23 m route moved the lower-mouth flare into the failing window; the L67 split rose from 6.78 to 14.52 cm.
|
||||
|
||||
The shared route builder now blends the lower-mouth radius over 12 m with a smooth distance-based taper, replacing the single-segment radius jump. Evaluator version is **v213**. L67 support changes remain in place.
|
||||
|
||||
## Passage audits
|
||||
|
||||
Clang and GCC produced identical results under the authored-radius gauge with cubic detrending per 32 m window. Limits are unchanged: wall ≤3.5 cm, floor ≤2.5 cm.
|
||||
|
||||
| Interface | Wall ripple | Period | Floor ripple | Blocked / unsupported / grade | Result |
|
||||
|---|---:|---:|---:|---:|---|
|
||||
| FI 0–1 | 1.45 cm | 4.562 m | 0.06 cm | 0 / 0 / 0 | PASS |
|
||||
| FI 1–2 | 1.62 cm | 7.250 m | 1.33 cm | 0 / 0 / 0 | PASS |
|
||||
| TN 2–3 | 0.92 cm | 7.375 m | 0.32 cm | 0 / 0 / 0 | PASS |
|
||||
|
||||
Naturalness, mouth profile, and ripple audits passed 3/3. Synthetic sphere, floor bead, floor step, L23 smooth-wall, and L23 terrace controls were detected. Blocked controls were detected 3/3. The new-plan generator/style maps, 3/3 interfaces, and three transition audits passed; width and thickness negative controls were rejected.
|
||||
|
||||
Clang and GCC builds passed with source stamp `33a8215f36abc00fadcdde5d6f3ff2d7054454fc2158e32470fb059e68bbd12e`. W1/W8/W12 hashes matched for all four strata on both compilers (8 compiler/stratum rows, 12 hash rows per worker count). The final fast tier passed **269/269** checks in 265.1 s. Its new-plan stage passed. The first fast attempt exposed the expected v213 floating-islands hash change against the old L52 pin; the current W1/W12 hashes match, and the v213 pin is [here](hash-baselines/owner-hash-floating-islands-style2-v213.log).
|
||||
|
||||
## Captures
|
||||
|
||||
All files are below 300 KB. The 1–2 camera is at the old ripple window. The 2–3 cameras are inside the passage and aimed through the mouths. The upper 2–3 exterior is sparse in the capture mesh, so that view reads as a dark opening.
|
||||
|
||||
| View | File | Bytes | SHA-256 |
|
||||
|---|---|---:|---|
|
||||
| 1–2 mid-tunnel | [JPEG](captures/passage-1-2-0-mid-tunnel.jpg) | 29,282 | `39381b01ae4d1674800391c6d8c65383e0d6866abf3cd70a8330062c677d06fa` |
|
||||
| 2–3 arrival mouth | [JPEG](captures/passage-2-3-0-arrival-mouth.jpg) | 20,123 | `f73ce987932a5e2ba165a3272b18d8786fcce3f815d0c2a1edcf13c99cc3f7b4` |
|
||||
| 2–3 departure mouth | [JPEG](captures/passage-2-3-0-departure-mouth.jpg) | 26,024 | `7665166f8f2e78b86774931e53af32243a5f40d2fc95e1b7ad4bf8cd6d310bd5` |
|
||||
|
||||
## Whole-view cost
|
||||
|
||||
Three interleaved L67/L68 pairs per stratum; all 24 runs exited 0 with 12 hash rows.
|
||||
|
||||
| Stratum | L67 median | L68 median | Change |
|
||||
|---:|---:|---:|---:|
|
||||
| 0 | 1.011 s | 1.074 s | +6.23% |
|
||||
| 1 | 1.494 s | 1.503 s | +0.60% |
|
||||
| 2 | 0.581 s | 0.577 s | −0.69% |
|
||||
| 3 | 0.210 s | 0.210 s | 0.00% |
|
||||
|
||||
## Failed runs and discarded diagnostics
|
||||
|
||||
The first fast attempt failed only on the stale L52 floating-islands hash expectation; the updated v213 pin and final fast run passed. Relief-off and old-cutoff experiments still failed the wall limit. A generic control-point refinement reduced ripple but introduced one unsupported sample; a post-route refinement retained 7.23 cm ripple. Those experiments were reverted. One early audit used the landmark-demo flag on an incompatible passage fixture and exited during route setup; the corrected audit passed.
|
||||
|
||||
Evidence is under `Saved/Linux/l68`: `build-clang.log`, `build-gcc.log`, `logs/final-{clang,gcc}-owner.log`, `logs/transition-clang.log`, `verify/owner-w1-w8-w12-determinism.json`, `cost/cost-summary.json`, and `verify/fast-tier-final.log`.
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Measure three interleaved L67/L68 whole-view pairs per owner strate."""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l68/cost"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"l67": ROOT / "Saved/Linux/l67/bin-final/VoxelForgeCoreHeadless-l67-final-clang",
|
||||
"l68": ROOT / "Saved/Linux/l68/bin-final-clang/VoxelForgeCoreHeadless-l68-final-clang",
|
||||
}
|
||||
CAMERAS = {
|
||||
0: "0.000000,0.000000,-374.939453,0.000000,0.000000",
|
||||
1: "25.836914,3.199219,-737.000000,0.000000,0.000000",
|
||||
2: "19.166016,-14.200195,-753.374023,0.000000,0.000000",
|
||||
3: "-20.062500,-5.068359,-900.353516,0.000000,0.000000",
|
||||
}
|
||||
ORDER = ("l67", "l68", "l68", "l67", "l67", "l68")
|
||||
PREFIXES = (
|
||||
"[PipelineFullDetailHash]", "[PipelineWholeViewHash]",
|
||||
"[PipelineLODHash]", "[PipelineExactQueryHash]",
|
||||
)
|
||||
ELAPSED_RE = re.compile(r"\belapsed_ms=([0-9.]+)")
|
||||
WHOLE_VIEW_RE = re.compile(r"^\[StreamPipeline\].*?whole_view_ready=([0-9.]+)s", re.M)
|
||||
ENV = os.environ.copy()
|
||||
|
||||
|
||||
def run(strate, repetition, variant):
|
||||
name = f"strate{strate}-pair{repetition // 2 + 1}-{variant}"
|
||||
log_path = OUT / f"{name}.log"
|
||||
layout_path = OUT / f"{name}.json"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1740s", str(BINARIES[variant]), str(DATA), "--workers=1",
|
||||
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||
"--force-generate", "--clip-radius=1", "--max-clip-level=8", "--max-level=0",
|
||||
"--recipe=surface-world", "--stream-hash", "--max-clip-level=0",
|
||||
"--render-distance-chunks=0", f"--camera={CAMERAS[strate]}",
|
||||
f"--resolved-layout-output={layout_path}",
|
||||
]
|
||||
start = time.monotonic()
|
||||
with log_path.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, env=ENV, stdout=stream,
|
||||
stderr=subprocess.STDOUT, timeout=1740, check=False)
|
||||
elapsed = time.monotonic() - start
|
||||
lines = log_path.read_text(encoding="utf-8", errors="replace").splitlines()
|
||||
hashes = [line for line in lines if line.startswith(PREFIXES)]
|
||||
pipeline_elapsed = [float(value) for line in lines
|
||||
if line.startswith("[PipelineWholeViewHash]")
|
||||
for value in ELAPSED_RE.findall(line)]
|
||||
whole_view_match = WHOLE_VIEW_RE.search("\n".join(lines))
|
||||
record = {
|
||||
"strate": strate, "pair_order": repetition, "variant": variant,
|
||||
"elapsed_seconds": elapsed, "pipeline_elapsed_ms": pipeline_elapsed,
|
||||
"whole_view_seconds": float(whole_view_match.group(1))
|
||||
if whole_view_match else None,
|
||||
"exit_code": result.returncode, "hash_rows": len(hashes),
|
||||
"log": str(log_path.relative_to(ROOT)),
|
||||
}
|
||||
print(json.dumps(record, sort_keys=True), flush=True)
|
||||
return record
|
||||
|
||||
|
||||
def main():
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
results = []
|
||||
for strate in range(4):
|
||||
for repetition, variant in enumerate(ORDER):
|
||||
record = run(strate, repetition, variant)
|
||||
results.append(record)
|
||||
if record["exit_code"] != 0 or record["hash_rows"] != 12:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
strata = {}
|
||||
for strate in range(4):
|
||||
values = {variant: [row["whole_view_seconds"] for row in results
|
||||
if row["strate"] == strate and row["variant"] == variant
|
||||
and row["whole_view_seconds"] is not None]
|
||||
for variant in ("l67", "l68")}
|
||||
if all(len(values[variant]) == 3 for variant in ("l67", "l68")):
|
||||
l67 = sorted(values["l67"])[1]
|
||||
l68 = sorted(values["l68"])[1]
|
||||
strata[str(strate)] = {"l67_seconds": values["l67"],
|
||||
"l68_seconds": values["l68"], "l67_median_seconds": l67,
|
||||
"l68_median_seconds": l68,
|
||||
"change_percent": (l68 / l67 - 1.0) * 100.0}
|
||||
summary = {"pairs_per_strate": 3, "order": ORDER, "strata": strata,
|
||||
"runs": results, "complete": len(results) == 24
|
||||
and all(x["exit_code"] == 0 and x["hash_rows"] == 12 for x in results)}
|
||||
(OUT / "cost-summary.json").write_text(json.dumps(summary, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
print(f"[L68CostSummary] complete={summary['complete']} runs={len(results)}", flush=True)
|
||||
return 0 if summary["complete"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare owner four-strata hashes at W1/W8/W12 with both compilers."""
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l68"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
LAYOUT = OUT / "layouts/final-clang-owner.json"
|
||||
BINARIES = {
|
||||
"clang": OUT / "bin-final-clang/VoxelForgeCoreHeadless-l68-final-clang",
|
||||
"gcc": OUT / "bin-final-gcc/VoxelForgeCoreHeadless-l68-final-gcc",
|
||||
}
|
||||
HASH_RE = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
|
||||
RESULTS = []
|
||||
|
||||
|
||||
def hashes(path):
|
||||
return HASH_RE.findall(path.read_text(encoding="utf-8", errors="replace"))
|
||||
|
||||
|
||||
def cameras_by_strate():
|
||||
layout = json.loads(LAYOUT.read_text(encoding="utf-8"))
|
||||
scale = float(layout["core_voxel_size_um"]) / 256_000_000.0
|
||||
cameras = {}
|
||||
for passage in layout["passages"]:
|
||||
upper = str(int(passage["upper_strate_index"]))
|
||||
lower = str(int(passage["lower_strate_index"]))
|
||||
cameras.setdefault(upper, tuple(float(v) * scale for v in passage["upper_mouth_q"]))
|
||||
cameras[lower] = tuple(float(v) * scale for v in passage["lower_mouth_q"])
|
||||
if set(cameras) != {"0", "1", "2", "3"}:
|
||||
raise RuntimeError(f"expected a camera for all four strata; found {sorted(cameras)}")
|
||||
return cameras
|
||||
|
||||
|
||||
def main():
|
||||
(OUT / "verify").mkdir(parents=True, exist_ok=True)
|
||||
logs = OUT / "logs/determinism"
|
||||
layouts = OUT / "layouts/determinism"
|
||||
logs.mkdir(parents=True, exist_ok=True)
|
||||
layouts.mkdir(parents=True, exist_ok=True)
|
||||
cameras = cameras_by_strate()
|
||||
for compiler, binary in BINARIES.items():
|
||||
for strate in range(4):
|
||||
camera = ",".join(f"{value:.6f}" for value in (*cameras[str(strate)], 0.0, 0.0))
|
||||
outputs = {}
|
||||
codes = {}
|
||||
for workers in (1, 8, 12):
|
||||
name = f"owner-new-plan-{compiler}-strate{strate}-w{workers}"
|
||||
log_path = logs / f"{name}.log"
|
||||
layout_path = layouts / f"{name}.json"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1740s", str(binary), str(DATA), f"--workers={workers}",
|
||||
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||
"--force-generate", "--clip-radius=1", "--max-clip-level=8",
|
||||
"--max-level=0", "--recipe=surface-world", "--stream-hash",
|
||||
"--max-clip-level=0", "--render-distance-chunks=0", f"--camera={camera}",
|
||||
f"--resolved-layout-output={layout_path}",
|
||||
]
|
||||
with log_path.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, timeout=1740, check=False)
|
||||
outputs[workers] = hashes(log_path)
|
||||
codes[workers] = result.returncode
|
||||
print(json.dumps({"compiler": compiler, "strate": strate,
|
||||
"workers": workers, "exit_code": result.returncode,
|
||||
"hash_rows": len(outputs[workers]), "log": str(log_path.relative_to(ROOT))},
|
||||
sort_keys=True), flush=True)
|
||||
if result.returncode != 0 or len(outputs[workers]) != 12:
|
||||
break
|
||||
passed = (set(codes) == {1, 8, 12} and all(code == 0 for code in codes.values())
|
||||
and outputs[1] == outputs[8] == outputs[12])
|
||||
RESULTS.append({"compiler": compiler, "strate": strate,
|
||||
"workers": {str(k): len(v) for k, v in outputs.items()},
|
||||
"exit_codes": {str(k): v for k, v in codes.items()},
|
||||
"w1_w8_w12_equal": passed})
|
||||
if not passed:
|
||||
break
|
||||
if RESULTS[-1]["compiler"] != compiler or not RESULTS[-1]["w1_w8_w12_equal"]:
|
||||
break
|
||||
passed = len(RESULTS) == 8 and all(row["w1_w8_w12_equal"] for row in RESULTS)
|
||||
summary = {"status": "PASS" if passed else "FAIL", "strata": RESULTS}
|
||||
(OUT / "verify/owner-w1-w8-w12-determinism.json").write_text(
|
||||
json.dumps(summary, indent=2) + "\n", encoding="utf-8")
|
||||
print(f"[L68OwnerW1W8W12] status={summary['status']} rows={len(RESULTS)}", flush=True)
|
||||
return 0 if passed else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Reference in new issue
Block a user