proto: first liminal fixtures - flat field + monolith, periodic hills, raised road in fog (L141)
Standalone only: fixtures, liminal_road_override (fillDais), --audit-liminal-road-walk, capture-only distance fog. Road 65 m x 6 m, 22 m up, capsule walk 127/127. Owner hashes unchanged (v227), fast tier, Clang/GCC green. Hills repeat every 80 m but the capture reaches 56 m, so only one rise shows. Also R48 (who owns the tunnel mouth): recommends a bounded joint plan of passage ports and layer landing candidates. 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
eb47d72258
commit
9c6d261d01
15 files changed
+823
-12
No files matched your search
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Build three standalone liminal SurfaceWorld fixtures from the owner input."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
|
||||||
|
ROOT / "Standalone/data/owner_world.json"))
|
||||||
|
OUT_DEFAULT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l141"))
|
||||||
|
|
||||||
|
FLAT_FIELD = {
|
||||||
|
"version": 1,
|
||||||
|
"kind": "flatField",
|
||||||
|
"minimum_arena_clearance_m": 0.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--out", type=Path, default=OUT_DEFAULT / "fixtures")
|
||||||
|
args = parser.parse_args()
|
||||||
|
args.out.mkdir(parents=True, exist_ok=True)
|
||||||
|
base = json.loads(OWNER.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
flat = dict(base)
|
||||||
|
flat["surface_profile_override"] = dict(FLAT_FIELD)
|
||||||
|
flat["motif_family_override"] = {
|
||||||
|
"version": 1,
|
||||||
|
"strate_index": 0,
|
||||||
|
"placement_policy": "seed,strate,stable_id",
|
||||||
|
"motifs": [
|
||||||
|
{
|
||||||
|
"stable_id": "liminal/oversized-familiar-monolith-v1",
|
||||||
|
"kind": "cappedCylinder",
|
||||||
|
"radius_m": 1.6,
|
||||||
|
"height_m": 26.0,
|
||||||
|
"route_side_offset_m": 42.0,
|
||||||
|
"placement_radius_m": 0.0,
|
||||||
|
"fillet_radius_m": 0.6,
|
||||||
|
"blend_width_m": 1.2,
|
||||||
|
"minimum_spacing_m": 0.0,
|
||||||
|
"yaw_quarter_turns": 0,
|
||||||
|
"attachment": "seated_floor",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
hills = dict(base)
|
||||||
|
hills["surface_profile_override"] = {
|
||||||
|
"version": 1,
|
||||||
|
"kind": "periodicHills",
|
||||||
|
"amplitude_x_m": 2.2,
|
||||||
|
"amplitude_y_m": 2.2,
|
||||||
|
"wavelength_x_m": 80.0,
|
||||||
|
"wavelength_y_m": 80.0,
|
||||||
|
"minimum_arena_clearance_m": 0.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
road = dict(base)
|
||||||
|
road["surface_profile_override"] = dict(FLAT_FIELD)
|
||||||
|
road["liminal_road_override"] = {
|
||||||
|
"stable_id": "liminal/raised-road-v1",
|
||||||
|
"strate_index": 0,
|
||||||
|
"position_xy_m": [240.0, 180.0],
|
||||||
|
"half_extents_m": [32.5, 3.0, 0.75],
|
||||||
|
"deck_height_above_field_m": 22.0,
|
||||||
|
"yaw_quarter_turns": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, fixture in (("flatField-monolith", flat),
|
||||||
|
("periodicHills", hills),
|
||||||
|
("raised-road", road)):
|
||||||
|
path = args.out / f"liminal-{name}.json"
|
||||||
|
path.write_text(json.dumps(fixture, indent=2) + "\n", encoding="utf-8")
|
||||||
|
print(f"[L141Fixture] name={name} file={path} status=PASS")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run L141 prototype determinism checks and create eye-level/wide captures."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT_DEFAULT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l141"))
|
||||||
|
CAPTURE_DEFAULT = Path(__file__).resolve().parent / "captures"
|
||||||
|
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
|
||||||
|
Q_PER_METRE = 1024.0
|
||||||
|
FOG_COLOR = "0.66,0.73,0.77"
|
||||||
|
|
||||||
|
sys.path.insert(0, str(ROOT / "Standalone/Tools/verify"))
|
||||||
|
from runner import bounded_run, current_stamp, first_stamp
|
||||||
|
|
||||||
|
|
||||||
|
def fmt(values) -> str:
|
||||||
|
return ",".join(f"{value:.6f}" for value in values)
|
||||||
|
|
||||||
|
|
||||||
|
def sha256(path: Path) -> str:
|
||||||
|
digest = hashlib.sha256()
|
||||||
|
with path.open("rb") as stream:
|
||||||
|
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
||||||
|
digest.update(block)
|
||||||
|
return digest.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def run_step(name: str, command: list[object], log: Path, *, env: dict[str, str],
|
||||||
|
stamped: bool = False, timeout_seconds: int = 1740) -> str:
|
||||||
|
expected_stamp = current_stamp() if stamped else None
|
||||||
|
if stamped:
|
||||||
|
binary = Path(str(command[0]))
|
||||||
|
probe = bounded_run([str(binary), "--print-source-stamp"], timeout=15)
|
||||||
|
if probe is None or probe.returncode != 0 or first_stamp(probe.stdout or "") != expected_stamp:
|
||||||
|
raise RuntimeError(f"{name} refused a stale executable: {binary}")
|
||||||
|
started = time.monotonic()
|
||||||
|
log.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with log.open("w", encoding="utf-8") as stream:
|
||||||
|
result = subprocess.run([*LIMIT, f"timeout", f"{timeout_seconds}s",
|
||||||
|
*map(str, command)], cwd=ROOT, env=env,
|
||||||
|
stdout=stream, stderr=subprocess.STDOUT,
|
||||||
|
timeout=timeout_seconds + 5, check=False)
|
||||||
|
elapsed = time.monotonic() - started
|
||||||
|
output = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
print(f"[L141Run] step={name} elapsed_s={elapsed:.3f} exit={result.returncode} log={log}",
|
||||||
|
flush=True)
|
||||||
|
if stamped and (first_stamp(output) != expected_stamp or current_stamp() != expected_stamp):
|
||||||
|
raise RuntimeError(f"{name} source stamp mismatch; see {log}")
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(f"{name} failed; see {log}")
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def route_frame(manifest_path: Path) -> tuple[tuple[float, float, float],
|
||||||
|
tuple[float, float, float]]:
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
strata = [entry for entry in manifest["plan"]["strates"]
|
||||||
|
if entry.get("route") and len(entry["route"]) >= 2]
|
||||||
|
if not strata:
|
||||||
|
raise RuntimeError(f"no planned SurfaceWorld route in {manifest_path}")
|
||||||
|
route = strata[0]["route"]
|
||||||
|
points = [tuple(float(value) / Q_PER_METRE for value in item["position_q"])
|
||||||
|
for item in route]
|
||||||
|
lengths = [math.dist(a, b) for a, b in zip(points, points[1:])]
|
||||||
|
total = sum(lengths)
|
||||||
|
if total <= 1.0e-6:
|
||||||
|
raise RuntimeError(f"degenerate planned route in {manifest_path}")
|
||||||
|
|
||||||
|
def at(distance: float) -> tuple[float, float, float]:
|
||||||
|
remaining = max(0.0, min(total, distance))
|
||||||
|
for index, (a, b, length) in enumerate(zip(points, points[1:], lengths)):
|
||||||
|
if remaining <= length or index == len(lengths) - 1:
|
||||||
|
if length <= 1.0e-9:
|
||||||
|
return b
|
||||||
|
t = remaining / length
|
||||||
|
return tuple(a[axis] + (b[axis] - a[axis]) * t for axis in range(3))
|
||||||
|
remaining -= length
|
||||||
|
return points[-1]
|
||||||
|
|
||||||
|
center = at(total * 0.5)
|
||||||
|
before, after = at(total * 0.5 - 1.0), at(total * 0.5 + 1.0)
|
||||||
|
dx, dy = after[0] - before[0], after[1] - before[1]
|
||||||
|
length = math.hypot(dx, dy)
|
||||||
|
if length <= 1.0e-9:
|
||||||
|
raise RuntimeError(f"degenerate route tangent in {manifest_path}")
|
||||||
|
return center, (dx / length, dy / length, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def camera_angles(camera, target) -> tuple[float, float]:
|
||||||
|
dx, dy, dz = (target[index] - camera[index] for index in range(3))
|
||||||
|
return math.degrees(math.atan2(dx, dy)), math.degrees(math.atan2(dz, math.hypot(dx, dy)))
|
||||||
|
|
||||||
|
|
||||||
|
def point_toward(camera, target) -> tuple[float, ...]:
|
||||||
|
yaw, pitch = camera_angles(camera, target)
|
||||||
|
return (*camera, yaw, pitch)
|
||||||
|
|
||||||
|
|
||||||
|
def stamp(layout: dict, stable_id: str) -> dict:
|
||||||
|
for record in layout.get("stamps", []):
|
||||||
|
if record.get("stable_id") == stable_id:
|
||||||
|
return record
|
||||||
|
raise RuntimeError(f"missing resolved stamp {stable_id}")
|
||||||
|
|
||||||
|
|
||||||
|
def make_views(name: str, manifest_path: Path, layout_path: Path):
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
layout = json.loads(layout_path.read_text(encoding="utf-8"))
|
||||||
|
center, tangent = route_frame(manifest_path)
|
||||||
|
tx, ty, _ = tangent
|
||||||
|
nx, ny = -ty, tx
|
||||||
|
base_height = manifest["recipe"]["surface_profile"]["h0_q"] / Q_PER_METRE
|
||||||
|
if name == "flatField-monolith":
|
||||||
|
monolith = stamp(layout, "liminal/oversized-familiar-monolith-v1")
|
||||||
|
sx, sy, _ = (value / 1.0e6 for value in monolith["position_um"])
|
||||||
|
floor = monolith["floor_um"] / 1.0e6
|
||||||
|
eye_camera = (sx - tx * 36.0, sy - ty * 36.0, floor + 1.6)
|
||||||
|
eye_target = (sx, sy, floor + 10.5)
|
||||||
|
wide_camera = (sx - tx * 42.0 - nx * 18.0,
|
||||||
|
sy - ty * 42.0 - ny * 18.0, floor + 42.0)
|
||||||
|
wide_target = (sx, sy, floor + 8.0)
|
||||||
|
return {
|
||||||
|
"eye": (eye_camera, eye_target, 20),
|
||||||
|
"wide": (wide_camera, wide_target, 24),
|
||||||
|
"height_center": floor,
|
||||||
|
"height_range": 38.0,
|
||||||
|
}
|
||||||
|
if name == "periodicHills":
|
||||||
|
# Route points are capsule centers: +0.72 m places the view at 1.60 m above ground.
|
||||||
|
eye_camera = (center[0], center[1], center[2] + 0.72)
|
||||||
|
eye_target = (center[0] - tx * 45.0, center[1] - ty * 45.0,
|
||||||
|
center[2] + 0.72)
|
||||||
|
wide_camera = (center[0] - tx * 20.0 - nx * 20.0,
|
||||||
|
center[1] - ty * 20.0 - ny * 20.0, center[2] + 34.0)
|
||||||
|
wide_target = (center[0] - tx * 45.0, center[1] - ty * 45.0,
|
||||||
|
center[2] + 0.72)
|
||||||
|
return {
|
||||||
|
"eye": (eye_camera, eye_target, 20),
|
||||||
|
"wide": (wide_camera, wide_target, 28),
|
||||||
|
"height_center": base_height,
|
||||||
|
"height_range": 5.0,
|
||||||
|
}
|
||||||
|
road = stamp(layout, "liminal/raised-road-v1")
|
||||||
|
rx, ry, rz = (value / 1.0e6 for value in road["position_um"])
|
||||||
|
half_length, half_width, half_thickness = (
|
||||||
|
value / 1.0e6 for value in road["half_extents_um"])
|
||||||
|
deck = rz + half_thickness
|
||||||
|
eye_camera = (rx - half_length + 14.0, ry, deck + 1.6)
|
||||||
|
eye_target = (rx + half_length, ry, base_height)
|
||||||
|
wide_camera = (rx, ry - 28.0, deck + 24.0)
|
||||||
|
wide_target = (rx + 4.0, ry, base_height + 4.0)
|
||||||
|
return {
|
||||||
|
"eye": (eye_camera, eye_target, 24),
|
||||||
|
"wide": (wide_camera, wide_target, 26),
|
||||||
|
"height_center": base_height + 11.0,
|
||||||
|
"height_range": 32.0,
|
||||||
|
"road_deck": deck,
|
||||||
|
"road_width": 2.0 * half_width,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def headless_command(binary: Path, fixture: Path, camera, mesh: Path,
|
||||||
|
manifest: Path, layout: Path, workers: int,
|
||||||
|
clip_radius: int, *, hash_tiles: bool = False,
|
||||||
|
audit_road: bool = False, max_level: int = 0) -> list[object]:
|
||||||
|
command: list[object] = [binary, fixture, f"--workers={workers}",
|
||||||
|
"--seed=0", "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||||
|
"--force-generate", f"--clip-radius={clip_radius}",
|
||||||
|
f"--max-clip-level={max_level}",
|
||||||
|
f"--max-level={max_level}", "--render-distance-chunks=0",
|
||||||
|
f"--camera={fmt(point_toward(camera, (camera[0], camera[1] + 1.0, camera[2])))}",
|
||||||
|
f"--mesh-export={mesh}", f"--world-assembler-manifest-output={manifest}",
|
||||||
|
f"--resolved-layout-output={layout}"]
|
||||||
|
if hash_tiles:
|
||||||
|
command.append("--stream-hash-tiles")
|
||||||
|
if audit_road:
|
||||||
|
command.append("--audit-liminal-road-walk")
|
||||||
|
return command
|
||||||
|
|
||||||
|
|
||||||
|
def determinism_digest(output: str, manifest_path: Path,
|
||||||
|
layout_path: Path) -> tuple[str, str, str]:
|
||||||
|
tile_hashes = sorted(line for line in output.splitlines()
|
||||||
|
if line.startswith("[PipelineTileHash] "))
|
||||||
|
if not manifest_path.is_file() or not layout_path.is_file() or not tile_hashes:
|
||||||
|
raise RuntimeError("determinism run omitted manifest, layout or per-tile hashes")
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
layout = json.loads(layout_path.read_text(encoding="utf-8"))
|
||||||
|
manifest_digest = (manifest["content_hash"],manifest["recipe"]["content_hash"],
|
||||||
|
manifest["plan"]["content_hash"])
|
||||||
|
return ("|".join(manifest_digest),layout["stable_identity"],"\n".join(tile_hashes))
|
||||||
|
|
||||||
|
|
||||||
|
def run_determinism(name: str, fixture: Path, view, *, binaries: dict[str, Path],
|
||||||
|
out: Path, env: dict[str, str]) -> None:
|
||||||
|
camera, _, _ = view["eye"]
|
||||||
|
clip_radius = 1
|
||||||
|
max_clip = 0
|
||||||
|
baseline = None
|
||||||
|
cells = 0
|
||||||
|
for compiler, workers in ((compiler, workers)
|
||||||
|
for compiler in ("clang", "gcc")
|
||||||
|
for workers in (1, 8, 12)):
|
||||||
|
tag = f"{name}-{compiler}-w{workers}"
|
||||||
|
run_dir = out / "determinism" / tag
|
||||||
|
run_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
mesh = run_dir / "feature.vfmesh"
|
||||||
|
manifest = run_dir / "manifest.json"
|
||||||
|
layout = run_dir / "layout.json"
|
||||||
|
command = headless_command(binaries[compiler], fixture,camera,mesh,manifest,
|
||||||
|
layout,workers,max_clip,hash_tiles=True)
|
||||||
|
output = run_step(tag,command,out/"logs"/f"determinism-{tag}.log",
|
||||||
|
env=env,stamped=True)
|
||||||
|
digest = determinism_digest(output,manifest,layout)
|
||||||
|
if baseline is None:
|
||||||
|
baseline = digest
|
||||||
|
elif digest != baseline:
|
||||||
|
raise RuntimeError(f"{name} diverged at {compiler} W{workers}; see determinism logs")
|
||||||
|
cells += 1
|
||||||
|
print(f"[L141Determinism] fixture={name} compiler_worker_cells={cells} identical=yes status=PASS",
|
||||||
|
flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def export_and_capture(name: str, fixture: Path, views: dict, *,
|
||||||
|
headless: Path, renderer: Path, out: Path,
|
||||||
|
artifact_dir: Path, env: dict[str, str]) -> None:
|
||||||
|
for view_name in ("eye", "wide"):
|
||||||
|
camera, target, clip_radius = views[view_name]
|
||||||
|
clip_radius = min(clip_radius,7)
|
||||||
|
stem = f"{name}-{view_name}"
|
||||||
|
mesh = out / "capture-meshes" / f"{stem}.vfmesh"
|
||||||
|
manifest = out / "capture-manifests" / f"{stem}.json"
|
||||||
|
layout = out / "capture-layouts" / f"{stem}.json"
|
||||||
|
png = out / "capture-png" / f"{stem}.png"
|
||||||
|
image = artifact_dir / f"{stem}.jpg"
|
||||||
|
for path in (mesh,manifest,layout,png,image):
|
||||||
|
path.parent.mkdir(parents=True,exist_ok=True)
|
||||||
|
command = headless_command(headless,fixture,camera,mesh,manifest,layout,12,
|
||||||
|
clip_radius,max_level=0)
|
||||||
|
output = run_step(f"capture-{stem}-export",command,
|
||||||
|
out/"logs"/f"capture-{stem}-export.log",env=env,stamped=True)
|
||||||
|
if not mesh.is_file() or mesh.stat().st_size == 0:
|
||||||
|
raise RuntimeError(f"{stem} produced an empty mesh")
|
||||||
|
yaw, pitch = camera_angles(camera,target)
|
||||||
|
height_center = views["height_center"]
|
||||||
|
height_range = views["height_range"]
|
||||||
|
renderer_command = [renderer,mesh,png,"--size=1600x900","--fov-deg=72",
|
||||||
|
f"--camera-m={fmt(camera)}",f"--yaw-deg={yaw:.6f}",
|
||||||
|
f"--pitch-deg={pitch:.6f}","--tile-skirts=off","--shade=readable",
|
||||||
|
"--headlamp=off","--height-tint=on",f"--height-center-m={height_center:.6f}",
|
||||||
|
f"--height-range-m={height_range:.6f}","--light-direction=-0.42,-0.58,0.70",
|
||||||
|
"--fog=on","--fog-start-m=30","--fog-end-m=105",f"--fog-color={FOG_COLOR}"]
|
||||||
|
run_step(f"capture-{stem}-render",renderer_command,
|
||||||
|
out/"logs"/f"capture-{stem}-render.log",env=env,stamped=True)
|
||||||
|
run_step(f"capture-{stem}-jpeg",["ffmpeg","-nostdin","-hide_banner","-loglevel",
|
||||||
|
"error","-y","-i",png,"-frames:v","1","-q:v","4",image],
|
||||||
|
out/"logs"/f"capture-{stem}-jpeg.log",env=env)
|
||||||
|
quality = run_step(f"capture-{stem}-quality",
|
||||||
|
[sys.executable,ROOT/"Standalone/Tools/verify/capture_quality.py",image],
|
||||||
|
out/"logs"/f"capture-{stem}-quality.log",env=env)
|
||||||
|
if "[CaptureQuality]" not in quality or not image.is_file() \
|
||||||
|
or image.stat().st_size > 300_000:
|
||||||
|
raise RuntimeError(f"{stem} capture failed quality or size checks")
|
||||||
|
eye_height = "1.600m" if view_name == "eye" else "wide"
|
||||||
|
print(f"[L141Capture] fixture={name} view={view_name} eye_height={eye_height} "
|
||||||
|
f"camera_m=({fmt(camera)}) target_m=({fmt(target)}) bytes={image.stat().st_size} "
|
||||||
|
f"sha256={sha256(image)} status=PASS",flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def run_road_walk_audit(fixture: Path, view, *, headless: Path,
|
||||||
|
out: Path, env: dict[str, str]) -> None:
|
||||||
|
camera = view["eye"][0]
|
||||||
|
run_dir = out / "road-walk-audit"
|
||||||
|
run_dir.mkdir(parents=True,exist_ok=True)
|
||||||
|
output = run_step("road-walk-audit",headless_command(headless,fixture,camera,
|
||||||
|
run_dir/"audit.vfmesh",run_dir/"manifest.json",run_dir/"layout.json",12,1,
|
||||||
|
audit_road=True),out/"logs"/"road-walk-audit.log",env=env,stamped=True)
|
||||||
|
if not re.search(r"\[LiminalRoadWalkAudit\].*status=PASS",output):
|
||||||
|
raise RuntimeError("raised road failed the Core capsule walk audit")
|
||||||
|
|
||||||
|
|
||||||
|
def measure_near_ready(fixture: Path, views: dict, *, headless: Path,
|
||||||
|
out: Path, env: dict[str, str]) -> None:
|
||||||
|
uptime = subprocess.run(["uptime"],cwd=ROOT,capture_output=True,text=True,check=True).stdout.strip()
|
||||||
|
load_match = re.search(r"load average:\s*([0-9.]+)",uptime)
|
||||||
|
if not load_match or float(load_match.group(1)) >= 1.5:
|
||||||
|
raise RuntimeError(f"near-ready timing requires a quiet machine: {uptime}")
|
||||||
|
print(f"[L141TimingEnvironment] window=one W12 run after one warmup uptime='{uptime}' "
|
||||||
|
f"status=PASS",flush=True)
|
||||||
|
camera = views["eye"][0]
|
||||||
|
warm_dir = out / "near-ready" / "warmup"
|
||||||
|
timed_dir = out / "near-ready" / "measured"
|
||||||
|
warm_dir.mkdir(parents=True,exist_ok=True)
|
||||||
|
timed_dir.mkdir(parents=True,exist_ok=True)
|
||||||
|
|
||||||
|
def command(directory: Path) -> list[object]:
|
||||||
|
return [headless,fixture,"--workers=12","--world-assembler-prototype",
|
||||||
|
"--seed=0","--stream-benchmark","--stream-pipeline","--force-generate",
|
||||||
|
"--clip-radius=1","--max-clip-level=2","--max-level=2",
|
||||||
|
"--render-distance-chunks=0",
|
||||||
|
f"--camera={fmt(point_toward(camera,(camera[0],camera[1]+1.0,camera[2])))}",
|
||||||
|
f"--world-assembler-manifest-output={directory/'manifest.json'}",
|
||||||
|
f"--resolved-layout-output={directory/'layout.json'}"]
|
||||||
|
|
||||||
|
run_step("near-ready-warmup",command(warm_dir),
|
||||||
|
out/"logs"/"near-ready-warmup.log",env=env,stamped=True)
|
||||||
|
output = run_step("near-ready-measured",command(timed_dir),
|
||||||
|
out/"logs"/"near-ready-measured.log",env=env,stamped=True)
|
||||||
|
match = re.search(r"^\[WorldAssemblerNearLODPipelineReady\] lods=0-2 "
|
||||||
|
r"tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$",output,re.M)
|
||||||
|
if not match or match.group(1)!=match.group(2):
|
||||||
|
raise RuntimeError("near-ready run did not complete the full LOD 0-2 set")
|
||||||
|
print(f"[L141NearReady] window=one W12 run after one warmup clip_radius=1 "
|
||||||
|
f"tiles={match.group(1)}/{match.group(2)} cold_ready_s={match.group(3)} status=PASS",
|
||||||
|
flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--out", type=Path, default=OUT_DEFAULT)
|
||||||
|
parser.add_argument("--artifact-dir", type=Path, default=CAPTURE_DEFAULT)
|
||||||
|
parser.add_argument("--headless-clang", type=Path, required=True)
|
||||||
|
parser.add_argument("--headless-gcc", type=Path, required=True)
|
||||||
|
parser.add_argument("--renderer", type=Path, required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
out = args.out.resolve()
|
||||||
|
artifact_dir = args.artifact_dir.resolve()
|
||||||
|
fixture_dir = out / "fixtures"
|
||||||
|
for directory in (out,fixture_dir,artifact_dir):
|
||||||
|
directory.mkdir(parents=True,exist_ok=True)
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["VOXELFORGE_VERIFY_OUT"] = str(out)
|
||||||
|
env["VOXELFORGE_OWNER_DATA"] = str(ROOT/"Standalone/data/owner_world.json")
|
||||||
|
env["LP_NUM_THREADS"] = "12"
|
||||||
|
env["XDG_CACHE_HOME"] = str(out/"xdg-cache")
|
||||||
|
run_step("build-fixtures",[sys.executable,
|
||||||
|
ROOT/"Standalone/Research/WorldAssemblerM3/Linux141/BuildL141Fixtures.py",
|
||||||
|
f"--out={fixture_dir}"],out/"logs"/"build-fixtures.log",env=env)
|
||||||
|
|
||||||
|
binaries = {"clang":args.headless_clang.resolve(),"gcc":args.headless_gcc.resolve()}
|
||||||
|
fixtures = {
|
||||||
|
"flatField-monolith":fixture_dir/"liminal-flatField-monolith.json",
|
||||||
|
"periodicHills":fixture_dir/"liminal-periodicHills.json",
|
||||||
|
"raised-road":fixture_dir/"liminal-raised-road.json",
|
||||||
|
}
|
||||||
|
probed = {}
|
||||||
|
for name,fixture in fixtures.items():
|
||||||
|
probe_dir=out/"plan-probes"/name
|
||||||
|
probe_dir.mkdir(parents=True,exist_ok=True)
|
||||||
|
camera=(0.0,0.0,-500.0)
|
||||||
|
output=run_step(f"plan-probe-{name}",headless_command(binaries["clang"],fixture,
|
||||||
|
camera,probe_dir/"probe.vfmesh",probe_dir/"manifest.json",
|
||||||
|
probe_dir/"layout.json",12,1),out/"logs"/f"plan-probe-{name}.log",
|
||||||
|
env=env,stamped=True)
|
||||||
|
if "[WorldAssemblerFullManifest]" not in output:
|
||||||
|
raise RuntimeError(f"{name} probe omitted its full plan manifest")
|
||||||
|
probed[name]=make_views(name,probe_dir/"manifest.json",probe_dir/"layout.json")
|
||||||
|
|
||||||
|
for name,fixture in fixtures.items():
|
||||||
|
run_determinism(name,fixture,probed[name],binaries=binaries,out=out,env=env)
|
||||||
|
run_road_walk_audit(fixtures["raised-road"],probed["raised-road"],
|
||||||
|
headless=binaries["clang"],out=out,env=env)
|
||||||
|
for name,fixture in fixtures.items():
|
||||||
|
export_and_capture(name,fixture,probed[name],headless=binaries["clang"],
|
||||||
|
renderer=args.renderer.resolve(),out=out,artifact_dir=artifact_dir,env=env)
|
||||||
|
measure_near_ready(fixtures["raised-road"],probed["raised-road"],
|
||||||
|
headless=binaries["clang"],out=out,env=env)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
raise SystemExit(main())
|
||||||
|
except Exception as error:
|
||||||
|
print(f"[L141Capture] status=FAIL error={error}",file=sys.stderr,flush=True)
|
||||||
|
raise SystemExit(1)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# L141 — first liminal layer
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
The standalone fixtures and six captures are ready for review. The flat field has one oversized capped-cylinder monolith. The road is a 65 m `fillDais` platform, 6 m wide, with its deck 22 m above a flat field. Fog supplies the road's cloud-sea backdrop in these standalone images.
|
||||||
|
|
||||||
|
The `periodicHills` fixture uses the validated 80 m periods and 2.2 m amplitudes on both axes. Its field repeats exactly, but these captures show one broad rise rather than hills repeating to the horizon. The validated period is longer than the 56 m eye-level capture radius; the wider full-detail capture exceeded Core's 160-million-node interval bound. Smaller periods fail the existing profile validator. A capture extent that trims vertical tiles independently from horizontal tiles is the next useful step; this round leaves the Core profile limits unchanged.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Reused Core's `flatField`, `periodicHills`, `fillMonolith`, and `fillDais` support. Added standalone fixture inputs and runners in `BuildL141Fixtures.py` and `CaptureL141.py`.
|
||||||
|
- Added `liminal_road_override` as a standalone adapter to the existing free-placement `fillDais` stamp, plus `--audit-liminal-road-walk`. The walk audit samples the Core field with the existing capsule sampler and checks width and grade.
|
||||||
|
- Added optional distance fog to `LinuxOffscreenCapture` for the screenshots. The fog is capture-only; Unreal cloud rendering remains Unreal-side.
|
||||||
|
- No evaluator change or Unreal `Source/` change. Owner output hashes match their pinned L130/v227 canonical rows in the fast tier.
|
||||||
|
|
||||||
|
## Measurements
|
||||||
|
|
||||||
|
- Determinism: each fixture matched across Clang and GCC at W1, W8, and W12 (six cells per fixture). The matrix used LOD0, clip radius 1; each cell compares recipe/plan hashes, resolved-layout identity, and per-tile hashes. Individual logs are in `Saved/Linux/l141/logs/determinism-*.log`.
|
||||||
|
- Raised-road capsule walk: 127 stations, 8,001 capsule samples, zero blocked samples, zero unsupported stations, 6.000 m width against a 0.680 m capsule diameter, and 0.000° grade. See `Saved/Linux/l141/logs/road-walk-audit.log`.
|
||||||
|
- Near-ready: one W12 LOD0–2 sample after one warm-up, clip radius 1, 139/139 tiles. The measured `cold_ready_s` was **0.173886 s**. The window was one run after one warm-up; `uptime` showed a 1-minute load average of 1.31 and no other build was running. See `Saved/Linux/l141/logs/near-ready-measured.log`.
|
||||||
|
- Each capture is 1600×900 and passed `capture_quality.py`. Eye views place the camera 1.6 m above the field or road deck. Camera coordinates and image hashes are recorded in the capture runner output; export and render logs are under `Saved/Linux/l141/logs/`.
|
||||||
|
|
||||||
|
## Checks
|
||||||
|
|
||||||
|
- Clang 19 and GCC standalone builds passed with FP contraction off, four jobs, and source stamp `0a2828c398aa62870690667901d44f2daf5b1a5547b9640da6dbb01df721af8b`.
|
||||||
|
- `python3 Standalone/Tools/verify/verify.py fast --out Saved/Linux/l141 --overall-timeout 1740` passed in 285.7 s. Owner SurfaceWorld and FloatingIslands W1/W12 canonical hashes matched the L130/v227 baselines.
|
||||||
|
- Fixture determinism, dedicated road walk audit, six standalone captures, and all six image-quality checks passed.
|
||||||
|
- Python bytecode compilation and `git diff --check` passed.
|
||||||
|
- An exploratory broad `--audit-resolved-layout-passages` invocation also ran unrelated passage ripple checks and reported 2/3 passage ripple records passing, with `passage/1-2/0` failing. The focused liminal road audit passes; no legacy threshold was changed.
|
||||||
|
- No Unreal build was run because no Unreal `Source/` file changed. No timing matrix was requested or run.
|
||||||
|
|
||||||
|
## Remaining risk
|
||||||
|
|
||||||
|
The hill screenshots do not yet show multiple full repeats to the horizon. The profile itself is exactly periodic; the standalone capture volume is too short at the current full-detail safety bound. The raised road is geometric and walk-audited, while its clouds are represented by fog in the capture renderer.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,36 @@
|
|||||||
|
# VoxelForge — research R48 (read-only): who owns the tunnel mouth?
|
||||||
|
|
||||||
|
You are a researcher. **Read only:** don't edit, create or delete any file, and don't build, run or test anything.
|
||||||
|
Read code, reports and design docs. Be thorough, verify by hand, and mark each claim [CODE] file:line, [MEASURED]
|
||||||
|
report, or [INFERENCE]. This checkout (`Saved/Linux/r48-tree`) is branch `wip/l134-cave-planner` at `4f93f3f`.
|
||||||
|
|
||||||
|
## The problem
|
||||||
|
|
||||||
|
Inter-strate passages (tunnels between layers) are planned first, and each layer's content is planned afterwards
|
||||||
|
around their fixed mouths and tubes.
|
||||||
|
- **Tunnel Network** (L134–L138) works, because caves are carved into rock and can bend to the mouths.
|
||||||
|
- **Vertical Shafts** (L139): 15/16 seeds pass; seed 2 fails the shell and seams at the arrival mouth. Three local
|
||||||
|
geometry fixes failed.
|
||||||
|
- **Floating Islands** (L133 twice, then L140): 0/16 seeds pass.
|
||||||
|
- In 14 of 16 seeds, no island arrangement clears the fixed tubes and reaches both mouth heights.
|
||||||
|
- One seed has a composed collision.
|
||||||
|
- Wall ripple is 6–7 cm against the 3.5 cm limit on the passages.
|
||||||
|
|
||||||
|
Read the L133, L139 and L140 reports and patches in `Standalone/Research/WorldAssemblerM3/`.
|
||||||
|
|
||||||
|
## Questions
|
||||||
|
|
||||||
|
1. Where and in what order are passages and layer contents planned (adapter, `ResolvedLayoutInternal.h`, the
|
||||||
|
passage planner, the island and cave planners)? What does each stage fix that later stages must obey?
|
||||||
|
2. Which designs would make mouths solvable by construction for every layer type? For example:
|
||||||
|
- **content first:** the layer plans its landing spots, and passages are routed to target them;
|
||||||
|
- **negotiated:** passages reserve a mouth landing region, and every layer planner must provide support there,
|
||||||
|
a "landing pad" contract;
|
||||||
|
- **a joint plan:** a corridor and the island anchors are solved together.
|
||||||
|
For each: what changes, the risks (determinism, the existing audits, the passage smoothness/ripple work), and the
|
||||||
|
size.
|
||||||
|
3. Why does ripple fail at 6–7 cm on the upper passages with native islands (and in L67)? Is it the passage, or the
|
||||||
|
host field under it?
|
||||||
|
4. Recommend one design, with steps and the evidence that would prove or kill it.
|
||||||
|
|
||||||
|
Write plain Markdown, ≤ ~1,200 words.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# R48 read only — who owns the tunnel mouth?
|
||||||
|
|
||||||
|
**The passage planner currently chooses the mouth.** Floating Islands then has to fit its islands and walk route around fixed tubes; caves can instead carve toward them. L140’s fixed tube layout left no valid island plan for most seeds. [CODE] `Standalone/Core/src/ResolvedLayoutInternal.h:3699–3792`; [MEASURED] `Standalone/Research/WorldAssemblerM3/Linux140/REPORT.md:15–19`
|
||||||
|
|
||||||
|
## 1. Planning order and constraints
|
||||||
|
|
||||||
|
There are two adapter paths, so there isn’t one universal order today:
|
||||||
|
|
||||||
|
- The geometry only adapter builds strata and passage configs, plans passages, then resolves authored Core geometry. [CODE] `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1455–1585`
|
||||||
|
- The validated assembly path can build a manifest, including an imported Floating Islands plan, before creating the resolved layout. It copies plan anchors into that layout, then calls the passage planner. [CODE] `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1621–1685, 1950–2019, 2021–2041`
|
||||||
|
- L140’s kept patch changes the Floating Islands path: first resolve the fixed passage layout, then plan islands around those passages and export the resulting anchors. The patch is not applied in the R48 source: the current adapter still calls `PlanFloatingIslandCluster` without a resolved layout. [CODE] `Standalone/Research/WorldAssemblerM3/Linux140/l140-fi-around-tunnels.patch:102–219`; `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:893–987`
|
||||||
|
|
||||||
|
`PlanResolvedLayoutPassagesV1` sorts the strata, uses existing route anchors where present, otherwise chooses seeded mouths, and builds passage control points with radii. Once resolved, those mouths and tubes are inputs to later planners. [CODE] `Standalone/Core/src/ResolvedLayoutInternal.h:3699–3792, 3820–3874, 4713–4819`
|
||||||
|
|
||||||
|
The generic island planner lays out seeded island centers and a route without seeing the passage layout. L133’s alternate planner bound endpoints, but did not plan against both full passage volumes. Its composed walk still had collisions and unsupported route points. [CODE] `Standalone/Core/src/FloatingIslandsInternal.h:1991–2050, 2166–2233`; historical patch `Linux133/l133-native-fi-v9.patch` in Git commit `f8cba7c:79–134`; [MEASURED] `Linux133/REPORT.md` in that commit:3–7
|
||||||
|
|
||||||
|
The cavern planner reads incoming and outgoing passages and requires them to exist, except for a Vertical Shafts basal terminal. Caves can carve through host rock to reach those mouths; L134’s default composed route passed, while L139 still failed the shell and seam checks at seed 2’s incoming mouth after three local geometry attempts. [CODE] `Standalone/Core/src/ResolvedLayoutInternal.h:2344–2359`; [MEASURED] `Linux134/REPORT.md:3–7`; `Linux139/REPORT.md:5–8, 43–52, 89–91`
|
||||||
|
|
||||||
|
## 2. Design choices
|
||||||
|
|
||||||
|
- **Content first:** each layer produces supported landing candidates and a connected approach; the passage planner chooses compatible candidates and routes to them. This can solve the fixed-height problem by letting the tunnel target viable terrain. It requires every layer type to provide reliable candidates, stable ordering and tie breaks, and passage rerouting; changed routes must still pass the existing grade, shell, seam, and ripple audits. **Size: medium to large.** [INFERENCE]
|
||||||
|
|
||||||
|
- **Negotiated landing pad:** the passage reserves a landing region and the layer guarantees support there. A point or pad contract alone is too weak: L140 checked five support probes, yet 14 seeds had no arrangement clearing the fixed tubes or could not reach both mouth heights. A useful contract must cover the capsule approach and passage exclusion envelope too. That grows toward a joint plan. **Size: medium for a pad; large for a full approach and clearance contract.** [MEASURED] `Linux140/REPORT.md:7, 15–17`; [INFERENCE]
|
||||||
|
|
||||||
|
- **Joint plan:** choose passage geometry and layer anchors together. This gives the solver room to avoid the fixed-tube infeasibility seen in L140, but costs the most: bounded deterministic search, new planner interfaces, and careful preservation of passage smoothness and audit behavior. A bounded search only guarantees feasibility within its candidate space. **Size: large.** [MEASURED] `Linux140/REPORT.md:17`; [INFERENCE]
|
||||||
|
|
||||||
|
## 3. Why ripple reaches 6–7 cm
|
||||||
|
|
||||||
|
The ripple audit samples the **composed field** passed as `Shape`, at `pose.radius − 0.18 m`, then estimates wall radius as that sample radius plus the average signed density. It cubic-detrends each 32 m window, preserving authored radius variation. Thus the result includes both the passage’s radius profile and any host-field effect on the sampled wall; it is not a passage-only measurement. [CODE] `Standalone/Source/StreamBenchmarkPipeline.h:4186–4239, 4414–4432, 4941–4944`
|
||||||
|
|
||||||
|
L67 traced a prior gauge error: omitting `pose.radius` hid an authored-radius beat. The corrected gauge found a 4.77 cm beat at 7.06 m and measured 7.27 cm on passage 1–2. L140 measured 6.22–7.19 cm on its upper passages with native islands. [MEASURED] `Linux67/REPORT.md:64–79`; `Linux140/REPORT.md:15–19`
|
||||||
|
|
||||||
|
The evidence shows that passage-radius variation alone is enough to explain a failure at this scale; it does not isolate how much native island density adds or masks. L140’s shell-preserving composition change still failed the unchanged ripple gate. [INFERENCE] `Linux140/l140-fi-around-tunnels.patch:912–924`; [MEASURED] `Linux140/REPORT.md:15–19`
|
||||||
|
|
||||||
|
## 4. Recommendation
|
||||||
|
|
||||||
|
**Use a bounded pairwise joint plan.** Have each layer planner publish certified landing candidates with capsule support and approach constraints. Let the passage planner choose compatible ports and route geometry together, then check the complete tube and composed layer before finalizing the resolved layout. Start with Floating Islands; preserve cave planners’ ability to carve to selected ports. [INFERENCE]
|
||||||
|
|
||||||
|
Keep candidate generation and tie breaking seed-stable, and finalize the layout identity only after the selected mouths, tubes, and content anchors are known. Keep the existing thresholds unchanged. [CODE] `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:2021–2041`; [INFERENCE]
|
||||||
|
|
||||||
|
The design passes if all 16 seeds clear the existing route, support, shell, seam, grade, mouth-profile, and 3.5 cm ripple gates, with matching Clang/GCC W1/W8/W12 hashes. Separately compare ripple on the same tube in a smooth host field and in the composed field; that isolates passage-profile ripple from host-field contribution. Kill the design if joint movement still leaves seeds infeasible, or if the passage-only profile fails ripple. [INFERENCE]
|
||||||
|
|
||||||
|
L133’s report and patch were absent from the R48 working tree; I read them from Git commit `f8cba7c`. No files were changed, and I did not build or run tests. [MEASURED]
|
||||||
@@ -592,6 +592,11 @@ uniform float uHeightCenter;
|
|||||||
uniform float uHeightRange;
|
uniform float uHeightRange;
|
||||||
uniform float uTileEdgeWidth;
|
uniform float uTileEdgeWidth;
|
||||||
uniform vec3 uLightDirection;
|
uniform vec3 uLightDirection;
|
||||||
|
uniform vec3 uCameraWorld;
|
||||||
|
uniform vec3 uFogColor;
|
||||||
|
uniform float uFogStart;
|
||||||
|
uniform float uFogEnd;
|
||||||
|
uniform int uFogEnabled;
|
||||||
uniform int uHeadlamp;
|
uniform int uHeadlamp;
|
||||||
uniform int uGrazingLight;
|
uniform int uGrazingLight;
|
||||||
uniform int uClipEnabled;
|
uniform int uClipEnabled;
|
||||||
@@ -643,6 +648,11 @@ void main()
|
|||||||
color=mix(color,resourceColor/max(resourceWeight,0.001),
|
color=mix(color,resourceColor/max(resourceWeight,0.001),
|
||||||
clamp(resourceWeight,0.0,0.95))*illumination;
|
clamp(resourceWeight,0.0,0.95))*illumination;
|
||||||
}
|
}
|
||||||
|
if(uFogEnabled!=0)
|
||||||
|
{
|
||||||
|
float fogT=smoothstep(uFogStart,uFogEnd,length(vWorldPosition-uCameraWorld));
|
||||||
|
color=mix(color,uFogColor,fogT);
|
||||||
|
}
|
||||||
if(uTileEdges!=0&&vTileData.z>0.0)
|
if(uTileEdges!=0&&vTileData.z>0.0)
|
||||||
{
|
{
|
||||||
vec2 local=mod(vWorldPosition.xy-vTileData.xy,vTileData.z);
|
vec2 local=mod(vWorldPosition.xy-vTileData.xy,vTileData.z);
|
||||||
@@ -707,7 +717,8 @@ void main()
|
|||||||
bool HeightTint,bool Headlamp,bool TileEdges,
|
bool HeightTint,bool Headlamp,bool TileEdges,
|
||||||
double TileEdgeWidthMeters,bool HasLightDirection,
|
double TileEdgeWidthMeters,bool HasLightDirection,
|
||||||
const std::array<double,3>& LightDirection,bool ClipPlaneEnabled,
|
const std::array<double,3>& LightDirection,bool ClipPlaneEnabled,
|
||||||
const std::array<double,4>& ClipPlane)
|
const std::array<double,4>& ClipPlane,bool FogEnabled,
|
||||||
|
double FogStartMeters,double FogEndMeters,const std::array<double,3>& FogColor)
|
||||||
{
|
{
|
||||||
EGLDisplay Display=MakeDisplay();
|
EGLDisplay Display=MakeDisplay();
|
||||||
EGLint Major=0,Minor=0;
|
EGLint Major=0,Minor=0;
|
||||||
@@ -796,7 +807,10 @@ void main()
|
|||||||
glViewport(0,0,Width,Height);glDisable(GL_CULL_FACE);glEnable(GL_DEPTH_TEST);
|
glViewport(0,0,Width,Height);glDisable(GL_CULL_FACE);glEnable(GL_DEPTH_TEST);
|
||||||
// A covered empty ray represents open air. Keep it visibly blue so it cannot
|
// A covered empty ray represents open air. Keep it visibly blue so it cannot
|
||||||
// be mistaken for the old near-black result from a scene that lacked tiles.
|
// be mistaken for the old near-black result from a scene that lacked tiles.
|
||||||
glDepthFunc(GL_LEQUAL);glClearColor(0.30f,0.48f,0.62f,1.0f);
|
glDepthFunc(GL_LEQUAL);
|
||||||
|
glClearColor(FogEnabled?static_cast<float>(FogColor[0]):0.30f,
|
||||||
|
FogEnabled?static_cast<float>(FogColor[1]):0.48f,
|
||||||
|
FogEnabled?static_cast<float>(FogColor[2]):0.62f,1.0f);
|
||||||
glClearDepthf(1.0f);glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
|
glClearDepthf(1.0f);glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
|
||||||
glUseProgram(Program);
|
glUseProgram(Program);
|
||||||
glUniform1f(glGetUniformLocation(Program,"uTanHalfHorizontal"),static_cast<float>(TanHalfHorizontal));
|
glUniform1f(glGetUniformLocation(Program,"uTanHalfHorizontal"),static_cast<float>(TanHalfHorizontal));
|
||||||
@@ -825,6 +839,18 @@ void main()
|
|||||||
static_cast<float>(ActiveLightDirection[0]),
|
static_cast<float>(ActiveLightDirection[0]),
|
||||||
static_cast<float>(ActiveLightDirection[1]),
|
static_cast<float>(ActiveLightDirection[1]),
|
||||||
static_cast<float>(ActiveLightDirection[2]));
|
static_cast<float>(ActiveLightDirection[2]));
|
||||||
|
glUniform3f(glGetUniformLocation(Program,"uCameraWorld"),
|
||||||
|
static_cast<float>(Scene.CameraMeters[0]),
|
||||||
|
static_cast<float>(Scene.CameraMeters[1]),
|
||||||
|
static_cast<float>(Scene.CameraMeters[2]));
|
||||||
|
glUniform3f(glGetUniformLocation(Program,"uFogColor"),
|
||||||
|
static_cast<float>(FogColor[0]),static_cast<float>(FogColor[1]),
|
||||||
|
static_cast<float>(FogColor[2]));
|
||||||
|
glUniform1f(glGetUniformLocation(Program,"uFogStart"),
|
||||||
|
static_cast<float>(FogStartMeters));
|
||||||
|
glUniform1f(glGetUniformLocation(Program,"uFogEnd"),
|
||||||
|
static_cast<float>(FogEndMeters));
|
||||||
|
glUniform1i(glGetUniformLocation(Program,"uFogEnabled"),FogEnabled?1:0);
|
||||||
glDrawElements(GL_TRIANGLES,static_cast<GLsizei>(Scene.Indices.size()),GL_UNSIGNED_INT,nullptr);
|
glDrawElements(GL_TRIANGLES,static_cast<GLsizei>(Scene.Indices.size()),GL_UNSIGNED_INT,nullptr);
|
||||||
glFinish();
|
glFinish();
|
||||||
std::vector<std::uint8_t> Pixels(static_cast<std::size_t>(Width)*Height*4);
|
std::vector<std::uint8_t> Pixels(static_cast<std::size_t>(Width)*Height*4);
|
||||||
@@ -863,7 +889,7 @@ int main(int argc,char** argv)
|
|||||||
if(argc==2&&std::string_view(argv[1])=="--print-source-stamp")return 0;
|
if(argc==2&&std::string_view(argv[1])=="--print-source-stamp")return 0;
|
||||||
if(argc<3)
|
if(argc<3)
|
||||||
{
|
{
|
||||||
std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--height-range-m=N] [--height-center-m=N] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N] [--shade=readable|normal] [--height-tint=on|off] [--headlamp=on|off] [--light-direction=X,Y,Z] [--tile-edges=on|off] [--tile-skirts=on|off] [--tile-edge-width-m=N] [--clip-plane=NX,NY,NZ,D] [--focus-region=X,Y,Z,R]\n",argv[0]);
|
std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--height-range-m=N] [--height-center-m=N] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N] [--shade=readable|normal] [--height-tint=on|off] [--headlamp=on|off] [--light-direction=X,Y,Z] [--fog=on|off] [--fog-start-m=N] [--fog-end-m=N] [--fog-color=R,G,B] [--tile-edges=on|off] [--tile-skirts=on|off] [--tile-edge-width-m=N] [--clip-plane=NX,NY,NZ,D] [--focus-region=X,Y,Z,R]\n",argv[0]);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
int Width=2560,Height=1440;double Fov=90.0,HeightRangeMeters=64.0;
|
int Width=2560,Height=1440;double Fov=90.0,HeightRangeMeters=64.0;
|
||||||
@@ -871,6 +897,8 @@ int main(int argc,char** argv)
|
|||||||
bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0;
|
bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0;
|
||||||
bool HasCamera=false;std::array<double,3> Camera{};
|
bool HasCamera=false;std::array<double,3> Camera{};
|
||||||
bool HasLightDirection=false;std::array<double,3> LightDirection{};
|
bool HasLightDirection=false;std::array<double,3> LightDirection{};
|
||||||
|
bool FogEnabled=false;double FogStartMeters=50.0,FogEndMeters=200.0;
|
||||||
|
std::array<double,3> FogColor={0.62,0.70,0.75};
|
||||||
bool ReadableMode=true,HeightTint=true,TileEdges=false,TileSkirts=true,Headlamp=false;
|
bool ReadableMode=true,HeightTint=true,TileEdges=false,TileSkirts=true,Headlamp=false;
|
||||||
double TileEdgeWidthMeters=0.15;
|
double TileEdgeWidthMeters=0.15;
|
||||||
bool ClipPlaneEnabled=false;std::array<double,4> ClipPlane{};
|
bool ClipPlaneEnabled=false;std::array<double,4> ClipPlane{};
|
||||||
@@ -967,6 +995,38 @@ int main(int argc,char** argv)
|
|||||||
{std::fprintf(stderr,"Invalid light direction; use X,Y,Z with non-zero length\n");return 2;}
|
{std::fprintf(stderr,"Invalid light direction; use X,Y,Z with non-zero length\n");return 2;}
|
||||||
HasLightDirection=true;
|
HasLightDirection=true;
|
||||||
}
|
}
|
||||||
|
else if(Arg.starts_with("--fog="))
|
||||||
|
{
|
||||||
|
const std::string_view Mode=Arg.substr(6);
|
||||||
|
if(Mode=="on")FogEnabled=true;
|
||||||
|
else if(Mode=="off")FogEnabled=false;
|
||||||
|
else {std::fprintf(stderr,"Invalid fog mode\n");return 2;}
|
||||||
|
}
|
||||||
|
else if(Arg.starts_with("--fog-start-m="))
|
||||||
|
{
|
||||||
|
char* End=nullptr;const std::string Value(Arg.substr(14));
|
||||||
|
FogStartMeters=std::strtod(Value.c_str(),&End);
|
||||||
|
if(End==Value.c_str()||*End!='\0'||!std::isfinite(FogStartMeters)
|
||||||
|
||FogStartMeters<0.0||FogStartMeters>20000.0)
|
||||||
|
{std::fprintf(stderr,"Invalid fog start distance\n");return 2;}
|
||||||
|
}
|
||||||
|
else if(Arg.starts_with("--fog-end-m="))
|
||||||
|
{
|
||||||
|
char* End=nullptr;const std::string Value(Arg.substr(12));
|
||||||
|
FogEndMeters=std::strtod(Value.c_str(),&End);
|
||||||
|
if(End==Value.c_str()||*End!='\0'||!std::isfinite(FogEndMeters)
|
||||||
|
||FogEndMeters<=0.0||FogEndMeters>20000.0)
|
||||||
|
{std::fprintf(stderr,"Invalid fog end distance\n");return 2;}
|
||||||
|
}
|
||||||
|
else if(Arg.starts_with("--fog-color="))
|
||||||
|
{
|
||||||
|
const std::string Value(Arg.substr(12));
|
||||||
|
if(std::sscanf(Value.c_str(),"%lf,%lf,%lf",&FogColor[0],&FogColor[1],
|
||||||
|
&FogColor[2])!=3
|
||||||
|
||std::any_of(FogColor.begin(),FogColor.end(),[](double Channel)
|
||||||
|
{return !std::isfinite(Channel)||Channel<0.0||Channel>1.0;}))
|
||||||
|
{std::fprintf(stderr,"Invalid fog color; use R,G,B in 0..1\n");return 2;}
|
||||||
|
}
|
||||||
else if(Arg.starts_with("--tile-edges="))
|
else if(Arg.starts_with("--tile-edges="))
|
||||||
{
|
{
|
||||||
const std::string_view Mode=Arg.substr(13);
|
const std::string_view Mode=Arg.substr(13);
|
||||||
@@ -1002,6 +1062,8 @@ int main(int argc,char** argv)
|
|||||||
}
|
}
|
||||||
else {std::fprintf(stderr,"Unknown option: %.*s\n",static_cast<int>(Arg.size()),Arg.data());return 2;}
|
else {std::fprintf(stderr,"Unknown option: %.*s\n",static_cast<int>(Arg.size()),Arg.data());return 2;}
|
||||||
}
|
}
|
||||||
|
if(FogEnabled&&FogEndMeters<=FogStartMeters)
|
||||||
|
{std::fprintf(stderr,"Fog end distance must be greater than fog start distance\n");return 2;}
|
||||||
FScene Scene;std::string Error;
|
FScene Scene;std::string Error;
|
||||||
if(!LoadScene(argv[1],Scene,Error))
|
if(!LoadScene(argv[1],Scene,Error))
|
||||||
{std::fprintf(stderr,"[OffscreenCapture] FAIL: %s\n",Error.c_str());return 2;}
|
{std::fprintf(stderr,"[OffscreenCapture] FAIL: %s\n",Error.c_str());return 2;}
|
||||||
@@ -1077,14 +1139,16 @@ int main(int argc,char** argv)
|
|||||||
FocusRadius,static_cast<unsigned long long>(Scene.DrawnTriangles));
|
FocusRadius,static_cast<unsigned long long>(Scene.DrawnTriangles));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::printf("[OffscreenScene] voxels_per_meter=%.6f camera_m=(%.3f,%.3f,%.3f) yaw=%.2f pitch=%.2f height_m=%.3f..%.3f height_center_m=%.3f shade=%s height_tint=%s tile_edges=%s vertices=%zu triangles=%llu\n",
|
std::printf("[OffscreenScene] voxels_per_meter=%.6f camera_m=(%.3f,%.3f,%.3f) yaw=%.2f pitch=%.2f height_m=%.3f..%.3f height_center_m=%.3f shade=%s height_tint=%s tile_edges=%s fog=%s fog_distance_m=%.1f..%.1f fog_color=(%.2f,%.2f,%.2f) vertices=%zu triangles=%llu\n",
|
||||||
1000000.0/Scene.VoxelSizeMicrons,Scene.CameraMeters[0],Scene.CameraMeters[1],Scene.CameraMeters[2],
|
1000000.0/Scene.VoxelSizeMicrons,Scene.CameraMeters[0],Scene.CameraMeters[1],Scene.CameraMeters[2],
|
||||||
Scene.YawDegrees,Scene.PitchDegrees,Scene.MinimumHeight,Scene.MaximumHeight,
|
Scene.YawDegrees,Scene.PitchDegrees,Scene.MinimumHeight,Scene.MaximumHeight,
|
||||||
HeightCenterMeters,
|
HeightCenterMeters,
|
||||||
ReadableMode?"readable":"normal",HeightTint?"on":"off",
|
ReadableMode?"readable":"normal",HeightTint?"on":"off",
|
||||||
TileEdges?"on":"off",Scene.Vertices.size(),
|
TileEdges?"on":"off",FogEnabled?"on":"off",FogStartMeters,FogEndMeters,
|
||||||
|
FogColor[0],FogColor[1],FogColor[2],Scene.Vertices.size(),
|
||||||
static_cast<unsigned long long>(Scene.Triangles));
|
static_cast<unsigned long long>(Scene.Triangles));
|
||||||
return Render(Scene,argv[2],Width,Height,Fov,HeightRangeMeters,HeightCenterMeters,ReadableMode,
|
return Render(Scene,argv[2],Width,Height,Fov,HeightRangeMeters,HeightCenterMeters,ReadableMode,
|
||||||
HeightTint,Headlamp,TileEdges,
|
HeightTint,Headlamp,TileEdges,
|
||||||
TileEdgeWidthMeters,HasLightDirection,LightDirection,ClipPlaneEnabled,ClipPlane)?0:3;
|
TileEdgeWidthMeters,HasLightDirection,LightDirection,ClipPlaneEnabled,ClipPlane,
|
||||||
|
FogEnabled,FogStartMeters,FogEndMeters,FogColor)?0:3;
|
||||||
}
|
}
|
||||||
@@ -108,6 +108,7 @@ namespace
|
|||||||
bool ResolvedLayoutOnly=false;
|
bool ResolvedLayoutOnly=false;
|
||||||
bool AuditResolvedLayoutBoundaries=false;
|
bool AuditResolvedLayoutBoundaries=false;
|
||||||
bool AuditResolvedLayoutPassages=false;
|
bool AuditResolvedLayoutPassages=false;
|
||||||
|
bool AuditLiminalRoadWalk=false;
|
||||||
bool AuditSurfaceClearance=false;
|
bool AuditSurfaceClearance=false;
|
||||||
bool AuditStrateTransitions=false;
|
bool AuditStrateTransitions=false;
|
||||||
int TransitionModeOverride=-1;
|
int TransitionModeOverride=-1;
|
||||||
@@ -1447,6 +1448,7 @@ namespace
|
|||||||
" --audit-strate-transitions (measure interface crossing, band thickness and holes)\n"
|
" --audit-strate-transitions (measure interface crossing, band thickness and holes)\n"
|
||||||
" --transition-mode=hard|gradient|interleaved --transition-width-chunks=N (fixture override)\n"
|
" --transition-mode=hard|gradient|interleaved --transition-width-chunks=N (fixture override)\n"
|
||||||
" --audit-resolved-layout-passages (capsule, support, route-binding and blocked-control audit)\n"
|
" --audit-resolved-layout-passages (capsule, support, route-binding and blocked-control audit)\n"
|
||||||
|
" --audit-liminal-road-walk (capsule, support, width and grade audit for the liminal road)\n"
|
||||||
" --audit-heightfield-profile (brute-force the profiled field intervals and route)\n"
|
" --audit-heightfield-profile (brute-force the profiled field intervals and route)\n"
|
||||||
" --audit-motif-intervals (brute-force motif bounds, BVH and shape-free guards)\n"
|
" --audit-motif-intervals (brute-force motif bounds, BVH and shape-free guards)\n"
|
||||||
" --audit-landmark-reachability (capsule, support, grade and buried-L43 control)\n"
|
" --audit-landmark-reachability (capsule, support, grade and buried-L43 control)\n"
|
||||||
@@ -1494,6 +1496,7 @@ namespace
|
|||||||
else if(arg=="--resolved-layout-only")options.ResolvedLayoutOnly=true;
|
else if(arg=="--resolved-layout-only")options.ResolvedLayoutOnly=true;
|
||||||
else if(arg=="--audit-resolved-layout-boundaries")options.AuditResolvedLayoutBoundaries=true;
|
else if(arg=="--audit-resolved-layout-boundaries")options.AuditResolvedLayoutBoundaries=true;
|
||||||
else if(arg=="--audit-resolved-layout-passages")options.AuditResolvedLayoutPassages=true;
|
else if(arg=="--audit-resolved-layout-passages")options.AuditResolvedLayoutPassages=true;
|
||||||
|
else if(arg=="--audit-liminal-road-walk")options.AuditLiminalRoadWalk=true;
|
||||||
else if(arg=="--native-owner-fi")options.NativeOwnerFloatingIslandsRecipe=true;
|
else if(arg=="--native-owner-fi")options.NativeOwnerFloatingIslandsRecipe=true;
|
||||||
else if(arg=="--density-dispatch-report")
|
else if(arg=="--density-dispatch-report")
|
||||||
{options.DensityDispatchReport=true;options.StreamHash=true;options.StreamHashTiles=true;}
|
{options.DensityDispatchReport=true;options.StreamHash=true;options.StreamHashTiles=true;}
|
||||||
@@ -1875,7 +1878,13 @@ int main(int argc,char** argv)
|
|||||||
&&!StandaloneWorldAssemblyPrototype::ValidateExportedCorePlanMatchesOwner(
|
&&!StandaloneWorldAssemblyPrototype::ValidateExportedCorePlanMatchesOwner(
|
||||||
*ownerWorld,worldAssembly,resolvedLayout,error))
|
*ownerWorld,worldAssembly,resolvedLayout,error))
|
||||||
{std::fprintf(stderr,"Unreal-resolved Core plan/owner slot validation failed: %s\n",error.c_str());return 2;}
|
{std::fprintf(stderr,"Unreal-resolved Core plan/owner slot validation failed: %s\n",error.c_str());return 2;}
|
||||||
if(!std::holds_alternative<std::nullptr_t>(ownerJsonRoot.Get("motif_family_override").Data))
|
const bool hasMotifFamily=!std::holds_alternative<std::nullptr_t>(
|
||||||
|
ownerJsonRoot.Get("motif_family_override").Data);
|
||||||
|
const bool hasLiminalRoad=!std::holds_alternative<std::nullptr_t>(
|
||||||
|
ownerJsonRoot.Get("liminal_road_override").Data);
|
||||||
|
if(hasMotifFamily&&hasLiminalRoad)
|
||||||
|
{std::fprintf(stderr,"motif_family_override and liminal_road_override cannot be combined\n");return 2;}
|
||||||
|
if(hasMotifFamily)
|
||||||
{
|
{
|
||||||
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1> requests;
|
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1> requests;
|
||||||
if(!StandaloneWorldAssemblyPrototype::BuildMotifFamilyRequests(ownerJsonRoot,
|
if(!StandaloneWorldAssemblyPrototype::BuildMotifFamilyRequests(ownerJsonRoot,
|
||||||
@@ -1903,6 +1912,25 @@ int main(int argc,char** argv)
|
|||||||
static_cast<unsigned long long>(motif.irregularitySeed));
|
static_cast<unsigned long long>(motif.irregularitySeed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(hasLiminalRoad)
|
||||||
|
{
|
||||||
|
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1> requests(1);
|
||||||
|
if(!StandaloneWorldAssemblyPrototype::BuildLiminalRoadRequest(ownerJsonRoot,
|
||||||
|
*ownerWorld,worldAssembly,resolvedLayout,requests.front(),error)
|
||||||
|
||!VoxelForgeCore::ResolveResolvedLayoutStampsV1(
|
||||||
|
resolvedLayout,seed,requests,error))
|
||||||
|
{std::fprintf(stderr,"Liminal road resolution failed: %s\n",error.c_str());return 2;}
|
||||||
|
const auto& road=resolvedLayout.stamps.front();
|
||||||
|
std::printf("[LiminalRoadResolved] vocabulary=%u id=%s strate=%d shape=rounded_dais "
|
||||||
|
"center_m=(%.3f,%.3f,%.3f) top_m=%.3f span_m=%.3f width_m=%.3f "
|
||||||
|
"grade_deg=0.000 placement=free route_keepout=PASS band_clearance=PASS status=PASS\n",
|
||||||
|
resolvedLayout.stampVocabularyVersion,road.stableId.c_str(),road.strateIndex,
|
||||||
|
road.positionMicrometres[0]/1.0e6,road.positionMicrometres[1]/1.0e6,
|
||||||
|
road.positionMicrometres[2]/1.0e6,
|
||||||
|
(road.positionMicrometres[2]+road.halfExtentsMicrometres[2])/1.0e6,
|
||||||
|
2.0*road.halfExtentsMicrometres[0]/1.0e6,
|
||||||
|
2.0*road.halfExtentsMicrometres[1]/1.0e6);
|
||||||
|
}
|
||||||
if(options.LandmarkChamberDemo)
|
if(options.LandmarkChamberDemo)
|
||||||
{
|
{
|
||||||
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1> requests;
|
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1> requests;
|
||||||
@@ -2404,5 +2432,5 @@ int main(int argc,char** argv)
|
|||||||
options.MovingCameraSpeedMetersPerSecond,options.MovingCameraDurationSeconds,
|
options.MovingCameraSpeedMetersPerSecond,options.MovingCameraDurationSeconds,
|
||||||
options.MovingCameraDwellSeconds,options.ColdWorkerTimeline,
|
options.MovingCameraDwellSeconds,options.ColdWorkerTimeline,
|
||||||
options.AuditPassageSourceClamp,options.AuditPassageCarveReach,
|
options.AuditPassageSourceClamp,options.AuditPassageCarveReach,
|
||||||
options.AuditPassageTileSeams);
|
options.AuditPassageTileSeams,options.AuditLiminalRoadWalk);
|
||||||
}
|
}
|
||||||
@@ -2384,7 +2384,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
bool bMovingCameraTrace=false,double MovingCameraSpeedMetersPerSecond=0.0,
|
bool bMovingCameraTrace=false,double MovingCameraSpeedMetersPerSecond=0.0,
|
||||||
double MovingCameraDurationSeconds=12.0,double MovingCameraDwellSeconds=5.0,
|
double MovingCameraDurationSeconds=12.0,double MovingCameraDwellSeconds=5.0,
|
||||||
bool bColdWorkerTimeline=false,bool bAuditPassageSourceClamp=false,
|
bool bColdWorkerTimeline=false,bool bAuditPassageSourceClamp=false,
|
||||||
bool bAuditPassageCarveReach=false,bool bAuditPassageTileSeams=false)
|
bool bAuditPassageCarveReach=false,bool bAuditPassageTileSeams=false,
|
||||||
|
bool bAuditLiminalRoadWalk=false)
|
||||||
{
|
{
|
||||||
using Clock=std::chrono::steady_clock;
|
using Clock=std::chrono::steady_clock;
|
||||||
const std::clock_t ColdCpuOrigin=std::clock();
|
const std::clock_t ColdCpuOrigin=std::clock();
|
||||||
@@ -2554,7 +2555,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
||bAuditOreVeinYield||bAuditLandmarkReachability||bAuditStrateTransitions
|
||bAuditOreVeinYield||bAuditLandmarkReachability||bAuditStrateTransitions
|
||||||
||bAuditResolvedLayoutPassages||bAuditResolvedLayoutBoundaries
|
||bAuditResolvedLayoutPassages||bAuditResolvedLayoutBoundaries
|
||||||
||bAuditTileShapeReadiness||bAuditBasePageEquivalence||bAuditRouteParity
|
||bAuditTileShapeReadiness||bAuditBasePageEquivalence||bAuditRouteParity
|
||||||
||bAuditPassageSourceClamp||bAuditPassageCarveReach;
|
||bAuditPassageSourceClamp||bAuditPassageCarveReach||bAuditLiminalRoadWalk;
|
||||||
const bool bDeferFarWorld=bIncludeFarWorld&&!bMovingCameraTrace&&!bFarWorldAudit;
|
const bool bDeferFarWorld=bIncludeFarWorld&&!bMovingCameraTrace&&!bFarWorldAudit;
|
||||||
VoxelForgeCore::ShapeBuildRequest ShapeRequest;
|
VoxelForgeCore::ShapeBuildRequest ShapeRequest;
|
||||||
ShapeRequest.minVoxel={FineMin.X,FineMin.Y,FineMin.Z};
|
ShapeRequest.minVoxel={FineMin.X,FineMin.Y,FineMin.Z};
|
||||||
@@ -2820,10 +2821,10 @@ namespace StandaloneStreamBenchmark
|
|||||||
Streamer.Stop();
|
Streamer.Stop();
|
||||||
return passed?0:11;
|
return passed?0:11;
|
||||||
}
|
}
|
||||||
if(bAuditResolvedLayoutPassages)
|
if(bAuditResolvedLayoutPassages||bAuditLiminalRoadWalk)
|
||||||
{
|
{
|
||||||
if(ResolvedLayout==nullptr||!CoreSnapshot.resolvedLayout())
|
if(ResolvedLayout==nullptr||!CoreSnapshot.resolvedLayout())
|
||||||
{Streamer.Stop();std::fprintf(stderr,"[ResolvedPassageAudit] missing resolved-layout snapshot\n");return 2;}
|
{Streamer.Stop();std::fprintf(stderr,"[ResolvedWalkAudit] missing resolved-layout snapshot\n");return 2;}
|
||||||
struct FWalkAudit
|
struct FWalkAudit
|
||||||
{
|
{
|
||||||
std::uint64_t segments=0,centers=0,capsuleSamples=0,blocked=0;
|
std::uint64_t segments=0,centers=0,capsuleSamples=0,blocked=0;
|
||||||
@@ -2961,6 +2962,67 @@ namespace StandaloneStreamBenchmark
|
|||||||
&&audit.gradeFailures==0;
|
&&audit.gradeFailures==0;
|
||||||
return audit;
|
return audit;
|
||||||
};
|
};
|
||||||
|
const auto auditLiminalRoadWalk=[&](bool required)
|
||||||
|
{
|
||||||
|
bool found=false;
|
||||||
|
bool passed=true;
|
||||||
|
for(const auto& road:ResolvedLayout->stamps)
|
||||||
|
{
|
||||||
|
if(road.stableId!="liminal/raised-road-v1")continue;
|
||||||
|
found=true;
|
||||||
|
const bool longAxisX=(road.yawQuarterTurns&1u)==0;
|
||||||
|
const double centerX=road.positionMicrometres[0]/1.0e6;
|
||||||
|
const double centerY=road.positionMicrometres[1]/1.0e6;
|
||||||
|
const double centerZ=road.positionMicrometres[2]/1.0e6;
|
||||||
|
const double halfLength=road.halfExtentsMicrometres[longAxisX?0:1]/1.0e6;
|
||||||
|
const double halfWidth=road.halfExtentsMicrometres[longAxisX?1:0]/1.0e6;
|
||||||
|
const double deckZ=centerZ+road.halfExtentsMicrometres[2]/1.0e6;
|
||||||
|
constexpr double CapsuleRadiusMetres=0.34;
|
||||||
|
constexpr double CapsuleHeightMetres=1.76;
|
||||||
|
constexpr double HalfCapsuleHeightMetres=CapsuleHeightMetres*0.5;
|
||||||
|
constexpr double EndInsetMetres=1.0;
|
||||||
|
std::vector<std::array<double,3>> roadPath;
|
||||||
|
const double inset=std::min(EndInsetMetres,halfLength*0.25);
|
||||||
|
for(const double along:{-halfLength+inset,halfLength-inset})
|
||||||
|
roadPath.push_back({(centerX+(longAxisX?along:0.0))/coreMetres,
|
||||||
|
(centerY+(longAxisX?0.0:along))/coreMetres,
|
||||||
|
(deckZ+HalfCapsuleHeightMetres)/coreMetres});
|
||||||
|
const FWalkAudit roadAudit=auditPlayerPath(Shape,roadPath,
|
||||||
|
0.125,0.125,1.125,3,0.45);
|
||||||
|
const double widthMetres=2.0*halfWidth;
|
||||||
|
const double horizontal=std::hypot(roadPath[1][0]-roadPath[0][0],
|
||||||
|
roadPath[1][1]-roadPath[0][1])*coreMetres;
|
||||||
|
const double vertical=std::abs(roadPath[1][2]-roadPath[0][2])*coreMetres;
|
||||||
|
const double gradeDegrees=std::atan2(vertical,horizontal)*
|
||||||
|
57.29577951308232;
|
||||||
|
passed=road.kind==VoxelForgeCore::ResolvedSdfStampKindV1::fillDais
|
||||||
|
&&widthMetres>=2.0*CapsuleRadiusMetres
|
||||||
|
&&gradeDegrees<=15.0&&roadAudit.passed;
|
||||||
|
std::printf("[LiminalRoadWalkAudit] id=%s stations=%llu capsule_samples=%llu "
|
||||||
|
"blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu "
|
||||||
|
"width_m=%.3f capsule_diameter_m=%.3f grade_deg=%.3f grade_limit_deg=15.000 "
|
||||||
|
"status=%s\n",road.stableId.c_str(),
|
||||||
|
static_cast<unsigned long long>(roadAudit.centers),
|
||||||
|
static_cast<unsigned long long>(roadAudit.capsuleSamples),
|
||||||
|
static_cast<unsigned long long>(roadAudit.blocked),
|
||||||
|
static_cast<unsigned long long>(roadAudit.supportSamples),
|
||||||
|
static_cast<unsigned long long>(roadAudit.unsupported),
|
||||||
|
static_cast<unsigned long long>(roadAudit.gradeFailures),widthMetres,
|
||||||
|
2.0*CapsuleRadiusMetres,gradeDegrees,passed?"PASS":"FAIL");
|
||||||
|
}
|
||||||
|
if(!found&&required)
|
||||||
|
{
|
||||||
|
std::printf("[LiminalRoadWalkAudit] status=FAIL reason=road_stamp_missing\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return passed;
|
||||||
|
};
|
||||||
|
if(bAuditLiminalRoadWalk&&!bAuditResolvedLayoutPassages)
|
||||||
|
{
|
||||||
|
const bool passed=auditLiminalRoadWalk(true);
|
||||||
|
Streamer.Stop();
|
||||||
|
return passed?0:14;
|
||||||
|
}
|
||||||
const auto qToMetres=[&](std::int64_t q)
|
const auto qToMetres=[&](std::int64_t q)
|
||||||
{return static_cast<double>(q)*World.CoreVoxelSizeMicrons
|
{return static_cast<double>(q)*World.CoreVoxelSizeMicrons
|
||||||
/(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel*1000000.0);};
|
/(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel*1000000.0);};
|
||||||
@@ -4919,6 +4981,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
|
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
|
||||||
routeAudit.firstUnsupported[i][3]);
|
routeAudit.firstUnsupported[i][3]);
|
||||||
}
|
}
|
||||||
|
const bool liminalRoadAuditPassed=auditLiminalRoadWalk(false);
|
||||||
for(std::size_t passageIndex=0;
|
for(std::size_t passageIndex=0;
|
||||||
passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||||
{
|
{
|
||||||
@@ -4984,6 +5047,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
&&shellAuditPassed
|
&&shellAuditPassed
|
||||||
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
||||||
&&routeBindingFailures==0
|
&&routeBindingFailures==0
|
||||||
|
&&liminalRoadAuditPassed
|
||||||
&&blockedCompiled&&blockedControls==passageCount;
|
&&blockedCompiled&&blockedControls==passageCount;
|
||||||
std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu natural=%llu/%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu blocked_controls=%llu/%llu status=%s\n",
|
std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu natural=%llu/%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu blocked_controls=%llu/%llu status=%s\n",
|
||||||
static_cast<unsigned long long>(passageCount),static_cast<unsigned long long>(passagePasses),
|
static_cast<unsigned long long>(passageCount),static_cast<unsigned long long>(passagePasses),
|
||||||
|
|||||||
@@ -587,6 +587,69 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool BuildLiminalRoadRequest(const VFJson::Value& root,
|
||||||
|
const FStandaloneOwnerWorld& owner,
|
||||||
|
const VoxelForgeCore::WorldAssemblyManifest& assembly,
|
||||||
|
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
|
||||||
|
VoxelForgeCore::ResolvedLayoutStampRequestV1& request,std::string& error)
|
||||||
|
{
|
||||||
|
using namespace VoxelForgeCore;
|
||||||
|
const VFJson::Value& overrideValue=root.Get("liminal_road_override");
|
||||||
|
if(!overrideValue.IsObject())
|
||||||
|
{error="liminal_road_override must be an object";return false;}
|
||||||
|
if(assembly.recipe.surfaceProfile.version!=1
|
||||||
|
||assembly.recipe.surfaceProfile.kind!=WorldAssemblySurfaceProfileKindV1::flatField
|
||||||
|
||owner.CoreVoxelSizeMicrons==0)
|
||||||
|
{error="the liminal road requires a valid flatField SurfaceWorld recipe";return false;}
|
||||||
|
|
||||||
|
const VFJson::Value& strateValue=overrideValue.Get("strate_index");
|
||||||
|
const auto* id=std::get_if<std::string>(&overrideValue.Get("stable_id").Data);
|
||||||
|
const auto& position=overrideValue.Get("position_xy_m").AsArray();
|
||||||
|
const auto& extents=overrideValue.Get("half_extents_m").AsArray();
|
||||||
|
const VFJson::Value& heightValue=overrideValue.Get("deck_height_above_field_m");
|
||||||
|
const VFJson::Value& yawValue=overrideValue.Get("yaw_quarter_turns");
|
||||||
|
if(!strateValue.IsNumber()||!id||id->empty()||position.size()!=2||extents.size()!=3
|
||||||
|
||!heightValue.IsNumber()||!std::isfinite(heightValue.Number())
|
||||||
|
||!yawValue.IsNumber()||yawValue.Integer()<0||yawValue.Integer()>3)
|
||||||
|
{error="liminal road requires stable_id, strate_index, position, extents, deck height and yaw";return false;}
|
||||||
|
for(const auto& value:position)
|
||||||
|
if(!value.IsNumber()||!std::isfinite(value.Number()))
|
||||||
|
{error="liminal road position_xy_m must contain finite metre values";return false;}
|
||||||
|
for(const auto& value:extents)
|
||||||
|
if(!value.IsNumber()||!std::isfinite(value.Number())
|
||||||
|
||value.Number()<0.5||value.Number()>100.0)
|
||||||
|
{error="liminal road half_extents_m must contain values from 0.5 to 100 metres";return false;}
|
||||||
|
if(heightValue.Number()<1.0||heightValue.Number()>100.0)
|
||||||
|
{error="liminal road deck height must be from 1 to 100 metres above the field";return false;}
|
||||||
|
const auto strateIndex=static_cast<std::int32_t>(strateValue.Integer());
|
||||||
|
const auto strate=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||||
|
[strateIndex](const auto& value){return value.strateIndex==strateIndex
|
||||||
|
&&value.generatorKind=="SurfaceWorld";});
|
||||||
|
if(strate==layout.strates.end())
|
||||||
|
{error="liminal_road_override must select a resolved SurfaceWorld strate";return false;}
|
||||||
|
if(extents[0].Number()<10.0||extents[1].Number()<0.75
|
||||||
|
||extents[0].Number()<=extents[1].Number()*4.0)
|
||||||
|
{error="liminal road must be long and narrow, with at least 1.5 metres half-width";return false;}
|
||||||
|
|
||||||
|
const auto metresToMicrometres=[](double value)
|
||||||
|
{return static_cast<std::int64_t>(std::llround(value*1000000.0));};
|
||||||
|
const std::int64_t fieldFloorMicrometres=static_cast<std::int64_t>(std::llround(
|
||||||
|
static_cast<double>(assembly.recipe.surfaceProfile.h0Subvoxels)
|
||||||
|
*owner.CoreVoxelSizeMicrons/WorldAssemblySubvoxelsPerVoxel));
|
||||||
|
request={};
|
||||||
|
request.stableId=*id;
|
||||||
|
request.strateIndex=strateIndex;
|
||||||
|
request.kind=ResolvedSdfStampKindV1::fillDais;
|
||||||
|
request.anchorPositionMicrometres={metresToMicrometres(position[0].Number()),
|
||||||
|
metresToMicrometres(position[1].Number()),
|
||||||
|
fieldFloorMicrometres+metresToMicrometres(heightValue.Number())
|
||||||
|
-metresToMicrometres(extents[2].Number())};
|
||||||
|
request.halfExtentsMicrometres={metresToMicrometres(extents[0].Number()),
|
||||||
|
metresToMicrometres(extents[1].Number()),metresToMicrometres(extents[2].Number())};
|
||||||
|
request.yawQuarterTurns=static_cast<std::uint8_t>(yawValue.Integer());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
inline bool AttachRound1WorldAssemblyPrototype(FStandaloneOwnerWorld& owner,VFJson::Value& root,
|
inline bool AttachRound1WorldAssemblyPrototype(FStandaloneOwnerWorld& owner,VFJson::Value& root,
|
||||||
std::string& error,const std::string& manifestOutputPath,
|
std::string& error,const std::string& manifestOutputPath,
|
||||||
VoxelForgeCore::WorldAssemblyManifest* outManifest=nullptr)
|
VoxelForgeCore::WorldAssemblyManifest* outManifest=nullptr)
|
||||||
|
|||||||
Reference in new issue
Block a user