diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux140/REPORT.md new file mode 100644 index 0000000..449e31e --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux140/REPORT.md @@ -0,0 +1,34 @@ +# L140 — Floating Islands planned around passages + +**Not accepted: 0/16 composed seeds passed.** The new Core recipe reaches native S1 dispatch and meets its dedicated route, passage-clearance, and mouth-support checks on seeds 0 and 10. Seed 10 still has one blocked capsule in the composed owner route; both seeds fail the unchanged passage-ripple gate. Fourteen seeds cannot produce a corridor-aware plan. + +## Change + +Core evaluator version is 239 and Floating Islands geometry version is 10. The owner adapter resolves the fixed passage layout first, then plans nine native islands and a walk route around the 0–1 arrival and 1–2 departure tubes. The plan records route and island anchors in the resolved layout. The clearance check reserves the 1.053 m passage influence reach plus the 0.880 m capsule half-height and checks five support probes at each mouth. Shape composition preserves native landing support and passage shell distance for this recipe. Direct legacy recipe fixtures retain their earlier attach-before-layout order. + +S1 binds to `FloatingIslands-native` in the explicit Core owner mode. `bUseVoxelForgeCore` remains opt-in; the fast-tier default owner pose still reports the legacy analytic S1 path. + +## Composed sweep + +The final-source Clang run covered seeds 0–15. Results and per-seed logs are in [`summary.json`](../../../../Saved/Linux/l140/seeds/summary.json) and `Saved/Linux/l140/seeds/` (source stamp `6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da`). + +- **Seed 0:** the planner tried 11 detours; its 94.5 m route has 1.47 m conservative tube clearance. Native route audit: 128 points, 0 blocked capsule samples, 0 unsupported points, 0 grade failures. Passage check: 5,325 tube samples, 0 island conflicts, 676,275 route checks, 0 route conflicts, arrival and departure support 5/5, 2.824 m minimum excess clearance. The composed S1 route and seams pass. Ripple is 1/3: wall ripple is 6.85 cm on 0–1 and 7.10 cm on 1–2 against the unchanged 3.50 cm limit. +- **Seed 10:** the native route and passage checks pass, with 5/5 mouth support and 0 isolated route conflicts. The composed S1 capsule audit finds one blocked sample at `(-6.246, -28.615, -737.982) m`, inside the resolved 0–1 passage; composed density is `-0.002062` while native island density is `+1.730469`. Ripple is again 1/3 (6.22 cm on 0–1 and 7.19 cm on 1–2). Seams and negative controls pass. +- **Seeds 1, 2, 7, 8, 9, 12, 13, 15:** no candidate clears the tubes; best conservative clearances range from −5.994 m to −0.406 m. **Seeds 3, 5, 6, 14:** candidates clear the tubes but the supported island profile cannot reach both fixed mouth heights. **Seeds 4 and 11:** candidate islands escape the sealed vertical layout. + +The dedicated gate is therefore 0/16. No geometry variant was attempted after these results. The repeated 0–1/1–2 ripple failure is also consistent with L67's closed authored-radius experiment: it measured 7.27 cm on 1–2 against the same 3.50 cm limit ([L67 report](../../Linux67/REPORT.md)). + +## Other checks + +- **Fast tier:** PASS, 287.8 s; negative controls and existing worker-hash checks passed. It continues to verify the default legacy S1 dispatch. +- **Determinism:** PASS, six runs (Clang/GCC × W1/W8/W12). All produced the same 512-tile density, mesh, payload, topology, material, and exact-query hashes, with layout identity `resolved-layout-v3-fnv64:08da20d55ff6820f`. Native recipe hash: `0b6a23bc73a3b867`. See the [baseline](baselines/native-owner-fi-seed0.json) and [`summary.json`](../../../../Saved/Linux/l140/determinism/summary.json). +- **Captures:** ten before/after JPEGs passed the existing image-quality gate. The [manifest](captures/manifest.json) records eye-height arrival, island edge, route between islands, departure, and wide silhouette views for both versions. See [before](captures/before-arrival-mouth.jpg), [before edge](captures/before-island-edge.jpg), [before route](captures/before-walk-between-islands.jpg), [before departure](captures/before-departure-mouth.jpg), [before wide](captures/before-wide-silhouette.jpg); [after](captures/after-arrival-mouth.jpg), [after edge](captures/after-island-edge.jpg), [after route](captures/after-walk-between-islands.jpg), [after departure](captures/after-departure-mouth.jpg), [after wide](captures/after-wide-silhouette.jpg). +- **Linux VFHostEditor:** PASS in 73.58 s. Build log: [`ubt.log`](../../../../Saved/Linux/l140/ubt.log). +- **L80 timing:** not completed. The machine gate passed (load 0.97, 2-second CPU idle 98.5%), but the candidate warm-up could not construct native S1 around the checked-in Unreal-exported passage layout: 0 detours, best clearance −3.509 m. No measured L80 pairs were recorded; the ±1% A/A floor remains the L137 reference. A separate 30-second setup probe with a regenerated seed-0 owner plan timed out during passage-route planning, so I did not run a mismatched or incomplete timing matrix. +- `git diff --check`: PASS. + +## Remaining issues + +The fixed exported owner layout and many generated seeds leave no route-clear island arrangement. On seed 10, the native-only clearance check also misses a collision that the final composed owner route catches. Ripple remains above the existing limit on both upper passages. Native S1 is still opt-in and is not promoted by this round. + +Changed Unreal source: [`VoxelForgeCoreAdapter.cpp`](../../../../Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp). Windows compilation remains for the owner. diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/RunL140Captures.py b/Standalone/Research/WorldAssemblerM3/Linux140/RunL140Captures.py new file mode 100644 index 0000000..1e300ba --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux140/RunL140Captures.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +"""Capture comparable player-height and wide views of owner S1 before and after L140.""" +from __future__ import annotations + +import json +import argparse +import math +import os +import re +import subprocess +import sys +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[4] +DATA = ROOT / "Standalone/data/owner_world.json" +LAYOUT = ROOT / "Saved/Linux/l140/seeds/seed0-composed-layout.json" +OUT = ROOT / "Saved/Linux/l140/captures" +ARTIFACTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux140/captures" +LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"] +STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", re.M) +VERSIONS = { + "before-head": { + "headless": ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang", + "renderer": ROOT / "Saved/Linux/l139/bin/VoxelForgeOffscreenCapture-l139-clang", + "stamp": "b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1", + "native": False, + }, + "after-l140": { + "headless": ROOT / "Saved/Linux/l140/bin/VoxelForgeCoreHeadless-l140-clang", + "renderer": ROOT / "Saved/Linux/l140/bin/VoxelForgeOffscreenCapture-l140-clang", + "stamp": "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da", + "native": True, + }, +} + + +def fmt(values: tuple[float, ...]) -> str: + return ",".join(f"{value:.6f}" for value in values) + + +def angles(camera: tuple[float, float, float], target: tuple[float, float, float]) -> tuple[float, float]: + dx, dy, dz = (target[i] - camera[i] for i in range(3)) + return math.degrees(math.atan2(dx, dy)), math.degrees(math.atan2(dz, math.hypot(dx, dy))) + + +def stamped_run(name: str, command: list[str], log: Path, expected: str, + env: dict[str, str]) -> str: + binary = Path(command[0]) + probe = subprocess.run([*LIMIT, "timeout", "15s", str(binary), "--print-source-stamp"], + cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, check=False, timeout=20) + match = STAMP_RE.search(probe.stdout or "") + if probe.returncode != 0 or not match or match.group(1) != expected: + raise RuntimeError(f"{name}: executable stamp mismatch for {binary}") + log.parent.mkdir(parents=True, exist_ok=True) + started = time.monotonic() + with log.open("w", encoding="utf-8") as stream: + result = subprocess.run([*LIMIT, "timeout", "1740s", *map(str, command)], + cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT, + timeout=1745, check=False) + output = log.read_text(encoding="utf-8", errors="replace") + emitted = STAMP_RE.search(output) + if result.returncode != 0 or not emitted or emitted.group(1) != expected: + raise RuntimeError(f"{name}: process failed or source changed; see {log}") + print(f"[L140CaptureRun] step={name} elapsed_s={time.monotonic()-started:.1f} " + f"log={log.relative_to(ROOT)}", flush=True) + return output + + +def qpoint(values: list[int], scale_m: float) -> tuple[float, float, float]: + return tuple(value * scale_m for value in values[:3]) + + +def make_views(layout: dict) -> list[dict[str, object]]: + scale_m = layout["core_voxel_size_um"] / 1_000_000.0 / 256.0 + strate = next(row for row in layout["strates"] if row["strate_index"] == 1) + anchors = strate["anchors"] + route = [qpoint(row["values"], scale_m) for row in anchors if row["kind"] == "route_point"] + islands = [row for row in anchors if row["kind"] == "island_anchor"] + if len(route) < 8 or len(islands) < 5: + raise ValueError("seed-0 resolved layout lacks a complete S1 route or island chain") + + def tangent(index: int) -> tuple[float, float]: + a, b = route[max(0, index - 1)], route[min(len(route) - 1, index + 1)] + dx, dy = b[0] - a[0], b[1] - a[1] + length = max(1e-9, math.hypot(dx, dy)) + return dx / length, dy / length + + def eye(point: tuple[float, float, float]) -> tuple[float, float, float]: + return point[0], point[1], point[2] + 1.45 + + i = len(route) // 3 + tx, ty = tangent(i) + arrival_t = tangent(0) + departure_t = tangent(len(route) - 1) + arrival, departure = route[0], route[-1] + arrival_camera = eye((arrival[0] - 2.5 * arrival_t[0], arrival[1] - 2.5 * arrival_t[1], arrival[2])) + arrival_target = eye((arrival[0] + 8.0 * arrival_t[0], arrival[1] + 8.0 * arrival_t[1], arrival[2])) + departure_camera = eye((departure[0] - 2.5 * departure_t[0], departure[1] - 2.5 * departure_t[1], departure[2])) + departure_target = eye((departure[0] + 8.0 * departure_t[0], departure[1] + 8.0 * departure_t[1], departure[2])) + edge_point = route[i] + edge_camera = eye((edge_point[0] + 6.0 * ty, edge_point[1] - 6.0 * tx, edge_point[2])) + edge_target = (edge_point[0] + 4.0 * tx, edge_point[1] + 4.0 * ty, edge_point[2] + 0.35) + centers = [qpoint(row["values"], scale_m) for row in islands] + middle_index = len(route) // 2 + midpoint = route[middle_index] + mid_t = tangent(middle_index) + walk_camera = eye((midpoint[0] - 2.0 * mid_t[0], midpoint[1] - 2.0 * mid_t[1], midpoint[2])) + next_island_index = min(len(centers) - 1, 5) + next_island_top = centers[next_island_index][2] + islands[next_island_index]["values"][4] * scale_m + walk_target = (centers[next_island_index][0], centers[next_island_index][1], next_island_top + 0.5) + walk_focus = tuple((centers[4][axis] + centers[5][axis]) * 0.5 for axis in range(3)) + (18.0,) + cx = sum(p[0] for p in centers) / len(centers) + cy = sum(p[1] for p in centers) / len(centers) + cz = sum(p[2] + row["values"][4] * scale_m for p, row in zip(centers, islands)) / len(centers) + wide_camera = (cx, cy + 23.0, cz + 10.0) + wide_target = (cx, cy, cz) + return [ + {"name": "arrival-mouth", "camera": arrival_camera, "target": arrival_target, + "focus": (*arrival, 14.0), "note": "player eye at the fixed 0-1 arrival mouth"}, + {"name": "island-edge", "camera": edge_camera, "target": edge_target, + "focus": (*edge_point, 18.0), "note": "player eye beside the route on an island edge"}, + {"name": "walk-between-islands", "camera": walk_camera, "target": walk_target, + "focus": walk_focus, "note": "player eye looking from one route landing toward the next island"}, + {"name": "departure-mouth", "camera": departure_camera, "target": departure_target, + "focus": (*departure, 14.0), "note": "player eye approaching the fixed 1-2 departure mouth"}, + {"name": "wide-silhouette", "camera": wide_camera, "target": wide_target, + "focus": (cx, cy, cz, 48.0), "fov": 72.0, + "note": "wide side view showing the island chain silhouette"}, + ] + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--views", nargs="*", default=None, + help="optional view names to rerender in both phases") + args = parser.parse_args() + if not DATA.is_file() or not LAYOUT.is_file(): + raise SystemExit("missing owner fixture or final seed-0 resolved layout") + layout = json.loads(LAYOUT.read_text(encoding="utf-8")) + views = make_views(layout) + if args.views: + unknown = set(args.views) - {str(view["name"]) for view in views} + if unknown: + raise SystemExit(f"unknown capture views: {sorted(unknown)}") + views = [view for view in views if view["name"] in set(args.views)] + OUT.mkdir(parents=True, exist_ok=True) + ARTIFACTS.mkdir(parents=True, exist_ok=True) + env = os.environ.copy() + env["LP_NUM_THREADS"] = "12" + env["XDG_CACHE_HOME"] = str(OUT / "xdg-cache") + quality = ROOT / "Standalone/Tools/verify/capture_quality.py" + manifest: list[dict[str, object]] = [] + old_manifest_path = ARTIFACTS / "manifest.json" + existing = json.loads(old_manifest_path.read_text(encoding="utf-8")) \ + if old_manifest_path.is_file() else {"captures": []} + rerendered = {str(view["name"]) for view in views} + for row in existing.get("captures", []): + if row.get("view") not in rerendered: + manifest.append(row) + for version, config in VERSIONS.items(): + phase = "before" if version.startswith("before") else "after" + for view in views: + name = str(view["name"]) + camera, target = view["camera"], view["target"] + assert isinstance(camera, tuple) and isinstance(target, tuple) + yaw, pitch = angles(camera, target) + mesh_dir = OUT / "meshes" / phase + mesh_dir.mkdir(parents=True, exist_ok=True) + mesh = mesh_dir / f"{name}.vfmesh" + capture_layout = mesh_dir / f"{name}-layout.json" + flags = ["--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline", + "--workers=12", "--clip-radius=6", "--max-clip-level=0", "--max-level=0", + "--force-generate", "--seed=0", "--recipe=surface-world", + "--render-distance-chunks=0", f"--camera={fmt((*camera,yaw,pitch))}", + f"--resolved-layout-output={capture_layout}", f"--mesh-export={mesh}"] + if config["native"]: + flags.extend(["--native-owner-fi", "--density-dispatch-report"]) + export = [config["headless"], DATA, *flags] + stamped_run(f"{phase}-{name}-export", list(map(str, export)), + OUT / f"{phase}-{name}-export.log", config["stamp"], env) + if not mesh.is_file() or mesh.stat().st_size == 0: + raise RuntimeError(f"{phase}-{name}: empty mesh export") + png = OUT / f"{phase}-{name}.png" + jpg = ARTIFACTS / f"{phase}-{name}.jpg" + focus = view["focus"] + assert isinstance(focus, tuple) + fov = float(view.get("fov", 78.0)) + render = [config["renderer"], mesh, png, "--size=1280x720", + f"--fov-deg={fov:.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", "--height-center-m=-740", + "--height-range-m=70", "--tile-edges=off", f"--focus-region={fmt(focus)}"] + stamped_run(f"{phase}-{name}-render", list(map(str, render)), + OUT / f"{phase}-{name}-render.log", config["stamp"], env) + subprocess.run([*LIMIT, "timeout", "120s", "ffmpeg", "-nostdin", "-hide_banner", + "-loglevel", "error", "-y", "-i", png, "-frames:v", "1", "-q:v", "4", jpg], + cwd=ROOT, env=env, check=True, stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, timeout=125) + check = subprocess.run([*LIMIT, "timeout", "120s", sys.executable, + quality, jpg], cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, check=False, timeout=125) + result = check.stdout.strip().splitlines()[-1] if check.stdout.strip() else f"exit={check.returncode}" + ok = check.returncode == 0 and "status=PASS" in check.stdout + manifest.append({"phase": phase, "view": name, "file": str(jpg.relative_to(ROOT)), + "bytes": jpg.stat().st_size, "camera_m": camera, "target_m": target, + "yaw_deg": yaw, "pitch_deg": pitch, "fov_deg": fov, + "note": view["note"], "quality_status": "PASS" if ok else "FAIL", "quality": result}) + print(f"[L140Capture] phase={phase} view={name} file={jpg.relative_to(ROOT)} " + f"quality={'PASS' if ok else 'FAIL'} {result}", flush=True) + manifest.sort(key=lambda row: (str(row["phase"]), str(row["view"]))) + summary = {"seed": 0, "source_stamp": VERSIONS["after-l140"]["stamp"], + "before_stamp": VERSIONS["before-head"]["stamp"], "views_per_phase": 5, + "all_quality_pass": all(row["quality_status"] == "PASS" for row in manifest), + "captures": manifest} + (ARTIFACTS / "manifest.json").write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8") + print(f"[L140CaptureSummary] views_per_phase={len(views)} captures={len(manifest)} " + f"all_quality_pass={summary['all_quality_pass']}", flush=True) + return 0 if summary["all_quality_pass"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/RunL140Determinism.py b/Standalone/Research/WorldAssemblerM3/Linux140/RunL140Determinism.py new file mode 100644 index 0000000..0e9a438 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux140/RunL140Determinism.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Check native Floating Islands owner identity and hashes at W1/W8/W12.""" +from __future__ import annotations + +import json +import re +import subprocess +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[4] +OUT = ROOT / "Saved/Linux/l140/determinism" +DATA = ROOT / "Standalone/data/owner_world.json" +STAMP = "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da" +BINARIES = { + "clang": ROOT / "Saved/Linux/l140/bin/VoxelForgeCoreHeadless-l140-clang", + "gcc": ROOT / "Saved/Linux/l140/bin/VoxelForgeCoreHeadless-l140-gcc", +} +CAMERA = "25.836914,3.199219,-737.000000,0,0" +STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]+)$", re.M) +HASH_RE = re.compile( + r"^\[Pipeline(?:FullDetailHash|WholeViewHash|MaterialHash|LODHash|ExactQueryHash)\].*$", + re.M) +NATIVE_FI = re.compile( + r"^\[DensityDispatch\] strate=1 generator=FloatingIslands " + r"recipe=FloatingIslands\.ClusterField version=10 " + r"bound_path=FloatingIslands-native adapter=FFloatingIslandFieldAdapter$", re.M) +FI_RECIPE_HASH = re.compile(r"^\[FloatingIslandWorldAssembly\].*recipe_hash=([0-9a-f]+) status=PASS$", re.M) +FI_CLEARANCE = re.compile(r"^\[FloatingIslandPassageClearance\].*status=PASS$", re.M) +FI_ROUTE_AUDIT = re.compile(r"^\[FloatingIslandRouteAudit\].*status=PASS$", re.M) + + +def main() -> int: + if not DATA.is_file() or any(not path.is_file() for path in BINARIES.values()): + raise SystemExit("missing L140 Clang/GCC determinism binary or owner fixture") + OUT.mkdir(parents=True, exist_ok=True) + rows: list[dict[str, object]] = [] + reference_hashes: list[str] | None = None + reference_identity: str | None = None + for compiler, binary in BINARIES.items(): + for workers in (1, 8, 12): + name = f"native-owner-fi-{compiler}-w{workers}" + log, layout = OUT / f"{name}.log", OUT / f"{name}.json" + command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", + "timeout", "1740s", str(binary), str(DATA), "--world-assembler-prototype", + "--stream-benchmark", "--stream-pipeline", f"--workers={workers}", + "--clip-radius=3", "--max-clip-level=8", "--max-level=0", + f"--camera={CAMERA}", "--stream-hash", "--force-generate", "--seed=0", + "--recipe=surface-world", "--native-owner-fi", "--render-distance-chunks=0", + "--density-dispatch-report", f"--resolved-layout-output={layout}"] + started = time.monotonic() + with log.open("w", encoding="utf-8") as output: + proc = subprocess.run(command, cwd=ROOT, stdout=output, + stderr=subprocess.STDOUT, check=False, timeout=1740) + content = log.read_text(encoding="utf-8", errors="replace") + stamp = STAMP_RE.search(content) + hashes = HASH_RE.findall(content) + current_layout = json.loads(layout.read_text(encoding="utf-8")) if layout.is_file() else {} + identity = current_layout.get("stable_identity") + native_ok = bool(NATIVE_FI.search(content)) + plan_ok = ("[FloatingIslandRouteAudit]" in content + and "[FloatingIslandPassageClearance]" in content) + recipe_hash = FI_RECIPE_HASH.search(content) + clearance = FI_CLEARANCE.search(content) + route_audit = FI_ROUTE_AUDIT.search(content) + passed = (proc.returncode == 0 and stamp is not None and stamp.group(1) == STAMP + and len(hashes) >= 4 and native_ok and plan_ok and recipe_hash is not None + and clearance is not None and route_audit is not None and bool(identity)) + if reference_hashes is None and passed: + reference_hashes, reference_identity = hashes, identity + else: + passed = passed and hashes == reference_hashes and identity == reference_identity + row = {"compiler": compiler, "workers": workers, "exit": proc.returncode, + "source_stamp": stamp.group(1) if stamp else None, "hash_lines": hashes, + "layout_identity": identity, "native_fi_dispatch": native_ok, + "native_plan_audits": plan_ok, "floating_island_recipe_hash": + recipe_hash.group(1) if recipe_hash else None, + "passage_clearance_line": clearance.group(0) if clearance else None, + "route_audit_line": route_audit.group(0) if route_audit else None, + "passed": bool(passed), + "elapsed_s": round(time.monotonic() - started, 3), + "log": str(log.relative_to(ROOT)), "layout": str(layout.relative_to(ROOT))} + rows.append(row) + print("[L140DeterminismRun] " + json.dumps(row, sort_keys=True), flush=True) + (OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n") + if not passed: + (OUT / "summary.json").write_text(json.dumps({"status": "FAIL", "runs": rows}, indent=2) + "\n") + raise SystemExit(f"native owner determinism mismatch; see {log}") + summary = {"status": "PASS", "source_stamp": STAMP, + "layout_identity": reference_identity, "runs": rows} + (OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n") + (ROOT / "Standalone/Research/WorldAssemblerM3/Linux140/baselines").mkdir(parents=True, exist_ok=True) + (ROOT / "Standalone/Research/WorldAssemblerM3/Linux140/baselines/native-owner-fi-seed0.json").write_text( + json.dumps(summary, indent=2) + "\n", encoding="utf-8") + print(f"[L140DeterminismSummary] runs={len(rows)} source_stamp={STAMP} status=PASS") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/RunL140SeedSweep.py b/Standalone/Research/WorldAssemblerM3/Linux140/RunL140SeedSweep.py new file mode 100644 index 0000000..d94d91e --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux140/RunL140SeedSweep.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Run the composed L140 owner gates for all requested seeds.""" +from __future__ import annotations + +import json +import re +import subprocess +import time +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[4] +OUT = ROOT / "Saved/Linux/l140/seeds" +BIN = ROOT / "Saved/Linux/l140/bin/VoxelForgeCoreHeadless-l140-clang" +DATA = ROOT / "Standalone/data/owner_world.json" +STAMP = "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da" +STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]+)$", re.M) +CHECKS = { + "native_dispatch": re.compile( + r"^\[DensityDispatch\] strate=1 generator=FloatingIslands " + r"recipe=FloatingIslands\.ClusterField version=10 " + r"bound_path=FloatingIslands-native adapter=FFloatingIslandFieldAdapter$", re.M), + "island_route": re.compile(r"^\[FloatingIslandRouteAudit\].*status=PASS$", re.M), + "passage_clearance": re.compile(r"^\[FloatingIslandPassageClearance\].*status=PASS$", re.M), + "strate1_route": re.compile( + r"^\[ResolvedStrateRouteAudit\] strate=1 .*blocked=0 .*unsupported=0 " + r"grade_failures=0 mouth_binding=PASS status=PASS$", re.M), + "route_summary": re.compile( + r"^\[ResolvedPassageAuditSummary\].*route_walkable=4 .*route_bindings=4 " + r"route_binding_failures=0 .*blocked_controls=3/3", re.M), + "passages": re.compile(r"^\[ResolvedPassageAuditSummary\].*status=PASS$", re.M), + "shell": re.compile(r"^\[PassageShellAuditSummary\].*status=PASS$", re.M), + "naturalness": re.compile(r"^\[PassageNaturalnessSummary\].*status=PASS$", re.M), + "mouth_profile": re.compile(r"^\[PassageMouthProfileSummary\].*status=PASS$", re.M), + "ripple": re.compile(r"^\[PassageRippleAuditSummary\].*status=PASS$", re.M), +} + + +def run(seed: int, label: str, seams_only: bool) -> tuple[int, str, float]: + log = OUT / f"seed{seed}-{label}.log" + layout = OUT / f"seed{seed}-{label}-layout.json" + command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", + "timeout", "1740s", str(BIN), str(DATA), "--world-assembler-prototype", + "--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3", + "--max-clip-level=8", "--max-level=0", "--force-generate", f"--seed={seed}", + "--recipe=surface-world", "--native-owner-fi", "--density-dispatch-report"] + command.append("--audit-passage-tile-seams" if seams_only else "--audit-resolved-layout-passages") + if not seams_only: + command.append("--audit-passage-tile-seams") + command.append(f"--resolved-layout-output={layout}") + started = time.monotonic() + with log.open("w", encoding="utf-8") as output: + proc = subprocess.run(command, cwd=ROOT, stdout=output, + stderr=subprocess.STDOUT, check=False, timeout=1740) + return proc.returncode, str(log.relative_to(ROOT)), round(time.monotonic() - started, 3) + + +def main() -> int: + if not BIN.is_file() or not DATA.is_file(): + raise SystemExit("missing L140 Clang binary or composed owner fixture") + OUT.mkdir(parents=True, exist_ok=True) + results = [] + for seed in range(16): + exit_code, log_name, elapsed = run(seed, "composed", False) + content = (ROOT / log_name).read_text(encoding="utf-8", errors="replace") + stamp = STAMP_RE.search(content) + checks = {name: bool(pattern.search(content)) for name, pattern in CHECKS.items()} + checks["source_stamp"] = stamp is not None and stamp.group(1) == STAMP + checks["blocked_controls"] = len(re.findall( + r"^\[ResolvedPassageBlockedControl\].*detected=true$", content, re.M)) == 3 + checks["negative_controls"] = ( + "[ResolvedLayoutValidatorControls] rejected=10/10 status=PASS" in content + and len(re.findall(r"^\[PassageNaturalnessNegativeControl\].*detected=true$", + content, re.M)) == 1 + and len(re.findall(r"^\[PassageMouthProfileNegativeControl\].*detected=true$", + content, re.M)) == 1 + and len(re.findall(r"^\[PassageFloorStepNegativeControl\].*detected=true$", + content, re.M)) == 3 + and "synthetic_spheres_negative_control=DETECTED" in content + and "floor_beaded_negative_control=DETECTED" in content) + seam_match = re.search(r"^\[PassageTileSeamAudit\].*status=(PASS|FAIL)$", content, re.M) + seam_exit = exit_code + seam_log = log_name + seam_elapsed = 0.0 + if seam_match is None: + seam_exit, seam_log, seam_elapsed = run(seed, "seams", True) + seam_content = (ROOT / seam_log).read_text(encoding="utf-8", errors="replace") + stamp = STAMP_RE.search(seam_content) + checks["source_stamp"] &= stamp is not None and stamp.group(1) == STAMP + seam_match = re.search( + r"^\[PassageTileSeamAudit\].*status=(PASS|FAIL)$", seam_content, re.M) + content += "\n" + seam_content + checks["seams"] = seam_match is not None and seam_match.group(1) == "PASS" + all_gates = all(checks.values()) and exit_code == 0 and seam_exit == 0 + row = {"seed": seed, "exit": exit_code, "seam_exit": seam_exit, + "passed": all_gates, "checks": checks, + "elapsed_s": elapsed + seam_elapsed, "log": log_name, "seam_log": seam_log} + results.append(row) + print("[L140SeedSweep] " + json.dumps(row, sort_keys=True), flush=True) + (OUT / "runs-partial.json").write_text(json.dumps(results, indent=2) + "\n") + passed = sum(row["passed"] for row in results) + summary = {"source_stamp": STAMP, "seeds": list(range(16)), + "passed": passed, "failed": 16 - passed, "results": results} + (OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n") + print(f"[L140SeedSweepSummary] passed={passed}/16 failed={16-passed} status={'PASS' if passed==16 else 'FAIL'}", + flush=True) + return 0 if passed == 16 else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/RunL140Timing.py b/Standalone/Research/WorldAssemblerM3/Linux140/RunL140Timing.py new file mode 100644 index 0000000..a9da171 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux140/RunL140Timing.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +"""L80 timing: native L140 vs L139 HEAD and assembler/world, all owner strata.""" +from __future__ import annotations + +import json +import os +import re +import statistics +import subprocess +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[4] +OUT = ROOT / "Saved/Linux/l140/timing" +DATA = ROOT / "Standalone/data/owner_world.json" +BINARIES = { + "assembler-world": ( + ROOT / "Saved/Linux/l137/bin/VoxelForgeCoreHeadless-l137-aa-a", + "4499fb2a8a4ede7d6cbc68d1b794f104658cdaae93ed9cacbc91fe7ff3fc3621"), + "head": ( + ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang", + "b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1"), + "l140-native-fi": ( + ROOT / "Saved/Linux/l140/bin/VoxelForgeCoreHeadless-l140-clang", + "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da"), +} +CAMERAS = { + # The camera override also selects the owner SurfaceWorld departure mouth. + # Hold XY to the seed-0 native-FI valid mouth so every layer/version shares one layout. + 0: (25.8369140625, 3.19921875, -372.2626953125), + 1: (25.8369140625, 3.19921875, -737.0), + 2: (25.8369140625, 3.19921875, -753.3740234375), + 3: (25.8369140625, 3.19921875, -900.353515625), +} +PIPELINE_RE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M) +FIELDS_RE = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)") +NEAR_RE = re.compile( + r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$", + re.M) +STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", re.M) +L140_AA_FLOOR_PCT = 1.0 # L137's same-binary A/A median control. + + +def quiet_machine() -> tuple[float, float]: + load_1m = os.getloadavg()[0] + + def totals() -> tuple[int, int]: + fields = Path("/proc/stat").read_text().splitlines()[0].split() + values = [int(value) for value in fields[1:]] + return sum(values), values[3] + values[4] + + before = totals() + time.sleep(2.0) + after = totals() + idle = 100.0 * (after[1] - before[1]) / max(1, after[0] - before[0]) + return load_1m, idle + + +def run(strate: int, workers: int, version: str, phase: str, replicate: int) -> dict[str, object]: + binary, expected_stamp = BINARIES[version] + camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0" + label = f"s{strate}-w{workers}-{version}-{phase}{replicate}" + log = OUT / f"{label}.log" + command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", + "timeout", "1740s", str(binary), str(DATA), "--world-assembler-prototype", + "--stream-benchmark", "--stream-pipeline", f"--workers={workers}", + "--clip-radius=3", "--max-clip-level=8", "--max-level=8", + f"--camera={camera}", "--profile-lod-work", "--stream-scheduler-policy=3", + "--recipe=surface-world"] + if version == "l140-native-fi": + command.append("--native-owner-fi") + started = time.monotonic() + with log.open("w", encoding="utf-8") as output: + proc = subprocess.run(command, cwd=ROOT, stdout=output, + stderr=subprocess.STDOUT, check=False, timeout=1740) + content = log.read_text(encoding="utf-8", errors="replace") + pipeline, near, stamp = PIPELINE_RE.search(content), NEAR_RE.search(content), STAMP_RE.search(content) + if (proc.returncode or pipeline is None or near is None or stamp is None + or stamp.group(1) != expected_stamp): + raise SystemExit(f"L80 run failed or stamp mismatch: {log}; exit={proc.returncode}") + if version == "l140-native-fi" and not re.search( + r"^\[FloatingIslandWorldAssembly\].*dispatch=FloatingIslands-native .*status=PASS$", + content, re.M): + raise SystemExit(f"candidate timing did not bind native FloatingIslands: {log}") + fields = dict(FIELDS_RE.findall(pipeline.group(1))) + row = {"strate": strate, "workers": workers, "version": version, + "phase": phase, "replicate": replicate, "source_stamp": stamp.group(1), + "lod02_ready_s": float(near.group(3)), + "whole_view_ready_s": float(fields["whole_view_ready"].removesuffix("s")), + "whole_tiles": int(fields["whole_tiles"].split("/")[0]), + "wall_s": round(time.monotonic() - started, 3), "log": str(log.relative_to(ROOT))} + print("[L140TimingRun] " + json.dumps(row, sort_keys=True), flush=True) + return row + + +def main() -> int: + if any(not path.is_file() for path, _ in BINARIES.values()) or not DATA.is_file(): + raise SystemExit("missing L80 timing binaries or owner fixture") + OUT.mkdir(parents=True, exist_ok=True) + load_1m, idle = quiet_machine() + (OUT / "machine-load-before.txt").write_text( + f"1-minute load: {load_1m:.2f}\n2-second CPU idle: {idle:.1f}%\n") + print(f"[L140TimingGate] load_1m={load_1m:.2f} cpu_idle_2s={idle:.1f}%", flush=True) + if load_1m >= 1.5 or idle < 97.0: + raise SystemExit("timing refused: machine is above the L80 quiet-load gate") + rows: list[dict[str, object]] = [] + for strate in range(4): + for workers in (8, 12): + for version in BINARIES: + run(strate, workers, version, "warmup", 0) + for replicate in range(1, 6): + order = tuple(BINARIES) + order = order[replicate % len(order):] + order[:replicate % len(order)] + if replicate % 2 == 0: + order = tuple(reversed(order)) + for version in order: + rows.append(run(strate, workers, version, "measured", replicate)) + (OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n") + measured = [row for row in rows if row["phase"] == "measured"] + summary = [] + for strate in range(4): + for workers in (8, 12): + group = [row for row in measured if row["strate"] == strate and row["workers"] == workers] + medians = {version: {key: statistics.median( + row[key] for row in group if row["version"] == version) + for key in ("lod02_ready_s", "whole_view_ready_s")} for version in BINARIES} + entry: dict[str, object] = {"strate": strate, "workers": workers, + "replicates_per_version": 5, "medians": medians} + for key in ("lod02_ready_s", "whole_view_ready_s"): + for baseline in ("head", "assembler-world"): + entry[f"l140_vs_{baseline}_{key}_delta_pct"] = round( + 100.0 * (medians["l140-native-fi"][key] / medians[baseline][key] - 1.0), 2) + entry[f"l140_vs_{baseline}_{key}_paired_deltas_pct"] = [round(100.0 * ( + next(row[key] for row in group if row["version"] == "l140-native-fi" + and row["replicate"] == replicate) + / next(row[key] for row in group if row["version"] == baseline + and row["replicate"] == replicate) - 1.0), 3) + for replicate in range(1, 6)] + summary.append(entry) + result = {"a_a_noise_floor_pct": L140_AA_FLOOR_PCT, + "a_a_reference": "L137 same-binary median control; assembler/world source stamp", + "timing_protocol": "quiet machine; one warm-up per binary per cell; five interleaved measured runs; W8/W12; all four owner layers", + "machine": {"load_1m": load_1m, "cpu_idle_2s_pct": idle}, + "source_stamps": {name: stamp for name, (_path, stamp) in BINARIES.items()}, + "runs": rows, "summary": summary} + (OUT / "summary.json").write_text(json.dumps(result, indent=2) + "\n") + print(f"[L140TimingSummary] measured_runs={len(measured)} warmups=24 aa_floor_pct=1.0 status=PASS", + flush=True) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/__pycache__/RunL140SeedSweep.cpython-313.pyc b/Standalone/Research/WorldAssemblerM3/Linux140/__pycache__/RunL140SeedSweep.cpython-313.pyc new file mode 100644 index 0000000..2e2fe0d Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux140/__pycache__/RunL140SeedSweep.cpython-313.pyc differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/baselines/native-owner-fi-seed0.json b/Standalone/Research/WorldAssemblerM3/Linux140/baselines/native-owner-fi-seed0.json new file mode 100644 index 0000000..1d6d810 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux140/baselines/native-owner-fi-seed0.json @@ -0,0 +1,201 @@ +{ + "status": "PASS", + "source_stamp": "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da", + "layout_identity": "resolved-layout-v3-fnv64:08da20d55ff6820f", + "runs": [ + { + "compiler": "clang", + "workers": 1, + "exit": 0, + "source_stamp": "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da", + "hash_lines": [ + "[PipelineFullDetailHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineWholeViewHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineMaterialHash] tiles=512 material=0xc3cb5fcccf78f4ee", + "[PipelineLODHash] lod=0 tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineLODHash] lod=1 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=2 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=3 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=4 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=5 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=6 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=7 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=8 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineExactQueryHash] probes=9 density=0x74eac0457ddc2026 unclamped=0x74eac0457ddc2026 detail=0xefcba20c0c70c415 collision=0xdd5b083a1fa82cc7 path=direct-Core-queries" + ], + "layout_identity": "resolved-layout-v3-fnv64:08da20d55ff6820f", + "native_fi_dispatch": true, + "native_plan_audits": true, + "passed": true, + "elapsed_s": 101.92, + "log": "Saved/Linux/l140/determinism/native-owner-fi-clang-w1.log", + "layout": "Saved/Linux/l140/determinism/native-owner-fi-clang-w1.json", + "floating_island_recipe_hash": "0b6a23bc73a3b867", + "passage_clearance_line": "[FloatingIslandPassageClearance] strate=1 tube_samples=5325 island_blocked=0 route_checks=676275 route_blocked=0 arrival_support=5/5 departure_support=5/5 clearance_excess_m=2.824 portal_exclusion_m=13.0 influence_reach_m=1.053 capsule_bound_m=0.880 status=PASS", + "route_audit_line": "[FloatingIslandRouteAudit] points=128 centers=911 capsule_samples=247792 blocked=0 support_samples=6961 unsupported=0 grade_failures=0 status=PASS" + }, + { + "compiler": "clang", + "workers": 8, + "exit": 0, + "source_stamp": "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da", + "hash_lines": [ + "[PipelineFullDetailHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineWholeViewHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineMaterialHash] tiles=512 material=0xc3cb5fcccf78f4ee", + "[PipelineLODHash] lod=0 tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineLODHash] lod=1 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=2 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=3 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=4 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=5 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=6 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=7 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=8 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineExactQueryHash] probes=9 density=0x74eac0457ddc2026 unclamped=0x74eac0457ddc2026 detail=0xefcba20c0c70c415 collision=0xdd5b083a1fa82cc7 path=direct-Core-queries" + ], + "layout_identity": "resolved-layout-v3-fnv64:08da20d55ff6820f", + "native_fi_dispatch": true, + "native_plan_audits": true, + "passed": true, + "elapsed_s": 68.892, + "log": "Saved/Linux/l140/determinism/native-owner-fi-clang-w8.log", + "layout": "Saved/Linux/l140/determinism/native-owner-fi-clang-w8.json", + "floating_island_recipe_hash": "0b6a23bc73a3b867", + "passage_clearance_line": "[FloatingIslandPassageClearance] strate=1 tube_samples=5325 island_blocked=0 route_checks=676275 route_blocked=0 arrival_support=5/5 departure_support=5/5 clearance_excess_m=2.824 portal_exclusion_m=13.0 influence_reach_m=1.053 capsule_bound_m=0.880 status=PASS", + "route_audit_line": "[FloatingIslandRouteAudit] points=128 centers=911 capsule_samples=247792 blocked=0 support_samples=6961 unsupported=0 grade_failures=0 status=PASS" + }, + { + "compiler": "clang", + "workers": 12, + "exit": 0, + "source_stamp": "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da", + "hash_lines": [ + "[PipelineFullDetailHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineWholeViewHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineMaterialHash] tiles=512 material=0xc3cb5fcccf78f4ee", + "[PipelineLODHash] lod=0 tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineLODHash] lod=1 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=2 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=3 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=4 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=5 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=6 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=7 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=8 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineExactQueryHash] probes=9 density=0x74eac0457ddc2026 unclamped=0x74eac0457ddc2026 detail=0xefcba20c0c70c415 collision=0xdd5b083a1fa82cc7 path=direct-Core-queries" + ], + "layout_identity": "resolved-layout-v3-fnv64:08da20d55ff6820f", + "native_fi_dispatch": true, + "native_plan_audits": true, + "passed": true, + "elapsed_s": 68.96, + "log": "Saved/Linux/l140/determinism/native-owner-fi-clang-w12.log", + "layout": "Saved/Linux/l140/determinism/native-owner-fi-clang-w12.json", + "floating_island_recipe_hash": "0b6a23bc73a3b867", + "passage_clearance_line": "[FloatingIslandPassageClearance] strate=1 tube_samples=5325 island_blocked=0 route_checks=676275 route_blocked=0 arrival_support=5/5 departure_support=5/5 clearance_excess_m=2.824 portal_exclusion_m=13.0 influence_reach_m=1.053 capsule_bound_m=0.880 status=PASS", + "route_audit_line": "[FloatingIslandRouteAudit] points=128 centers=911 capsule_samples=247792 blocked=0 support_samples=6961 unsupported=0 grade_failures=0 status=PASS" + }, + { + "compiler": "gcc", + "workers": 1, + "exit": 0, + "source_stamp": "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da", + "hash_lines": [ + "[PipelineFullDetailHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineWholeViewHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineMaterialHash] tiles=512 material=0xc3cb5fcccf78f4ee", + "[PipelineLODHash] lod=0 tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineLODHash] lod=1 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=2 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=3 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=4 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=5 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=6 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=7 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=8 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineExactQueryHash] probes=9 density=0x74eac0457ddc2026 unclamped=0x74eac0457ddc2026 detail=0xefcba20c0c70c415 collision=0xdd5b083a1fa82cc7 path=direct-Core-queries" + ], + "layout_identity": "resolved-layout-v3-fnv64:08da20d55ff6820f", + "native_fi_dispatch": true, + "native_plan_audits": true, + "passed": true, + "elapsed_s": 100.676, + "log": "Saved/Linux/l140/determinism/native-owner-fi-gcc-w1.log", + "layout": "Saved/Linux/l140/determinism/native-owner-fi-gcc-w1.json", + "floating_island_recipe_hash": "0b6a23bc73a3b867", + "passage_clearance_line": "[FloatingIslandPassageClearance] strate=1 tube_samples=5325 island_blocked=0 route_checks=676275 route_blocked=0 arrival_support=5/5 departure_support=5/5 clearance_excess_m=2.824 portal_exclusion_m=13.0 influence_reach_m=1.053 capsule_bound_m=0.880 status=PASS", + "route_audit_line": "[FloatingIslandRouteAudit] points=128 centers=911 capsule_samples=247792 blocked=0 support_samples=6961 unsupported=0 grade_failures=0 status=PASS" + }, + { + "compiler": "gcc", + "workers": 8, + "exit": 0, + "source_stamp": "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da", + "hash_lines": [ + "[PipelineFullDetailHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineWholeViewHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineMaterialHash] tiles=512 material=0xc3cb5fcccf78f4ee", + "[PipelineLODHash] lod=0 tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineLODHash] lod=1 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=2 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=3 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=4 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=5 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=6 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=7 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=8 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineExactQueryHash] probes=9 density=0x74eac0457ddc2026 unclamped=0x74eac0457ddc2026 detail=0xefcba20c0c70c415 collision=0xdd5b083a1fa82cc7 path=direct-Core-queries" + ], + "layout_identity": "resolved-layout-v3-fnv64:08da20d55ff6820f", + "native_fi_dispatch": true, + "native_plan_audits": true, + "passed": true, + "elapsed_s": 70.398, + "log": "Saved/Linux/l140/determinism/native-owner-fi-gcc-w8.log", + "layout": "Saved/Linux/l140/determinism/native-owner-fi-gcc-w8.json", + "floating_island_recipe_hash": "0b6a23bc73a3b867", + "passage_clearance_line": "[FloatingIslandPassageClearance] strate=1 tube_samples=5325 island_blocked=0 route_checks=676275 route_blocked=0 arrival_support=5/5 departure_support=5/5 clearance_excess_m=2.824 portal_exclusion_m=13.0 influence_reach_m=1.053 capsule_bound_m=0.880 status=PASS", + "route_audit_line": "[FloatingIslandRouteAudit] points=128 centers=911 capsule_samples=247792 blocked=0 support_samples=6961 unsupported=0 grade_failures=0 status=PASS" + }, + { + "compiler": "gcc", + "workers": 12, + "exit": 0, + "source_stamp": "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da", + "hash_lines": [ + "[PipelineFullDetailHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineWholeViewHash] tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineMaterialHash] tiles=512 material=0xc3cb5fcccf78f4ee", + "[PipelineLODHash] lod=0 tiles=512 density=0xe10ac12305f1dbc0 mesh=0x822e56e8f5bfa6f0 payload=0x0be1f08906997251 topology=0x650bbeb9e554d390", + "[PipelineLODHash] lod=1 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=2 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=3 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=4 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=5 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=6 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=7 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineLODHash] lod=8 tiles=0 density=0xcbf29ce484222325 mesh=0xcbf29ce484222325 payload=0xcbf29ce484222325 topology=0xcbf29ce484222325", + "[PipelineExactQueryHash] probes=9 density=0x74eac0457ddc2026 unclamped=0x74eac0457ddc2026 detail=0xefcba20c0c70c415 collision=0xdd5b083a1fa82cc7 path=direct-Core-queries" + ], + "layout_identity": "resolved-layout-v3-fnv64:08da20d55ff6820f", + "native_fi_dispatch": true, + "native_plan_audits": true, + "passed": true, + "elapsed_s": 69.061, + "log": "Saved/Linux/l140/determinism/native-owner-fi-gcc-w12.log", + "layout": "Saved/Linux/l140/determinism/native-owner-fi-gcc-w12.json", + "floating_island_recipe_hash": "0b6a23bc73a3b867", + "passage_clearance_line": "[FloatingIslandPassageClearance] strate=1 tube_samples=5325 island_blocked=0 route_checks=676275 route_blocked=0 arrival_support=5/5 departure_support=5/5 clearance_excess_m=2.824 portal_exclusion_m=13.0 influence_reach_m=1.053 capsule_bound_m=0.880 status=PASS", + "route_audit_line": "[FloatingIslandRouteAudit] points=128 centers=911 capsule_samples=247792 blocked=0 support_samples=6961 unsupported=0 grade_failures=0 status=PASS" + } + ], + "world_assembly_evaluator_version": 239, + "floating_island_geometry_version": 10, + "native_dispatch": "FloatingIslands-native", + "route_and_clearance_baseline": { + "route_audit": "[FloatingIslandRouteAudit] points=128 centers=... status=PASS", + "passage_clearance": "[FloatingIslandPassageClearance] strate=1 tube_samples=5325 island_blocked=0 route_checks=676275 route_blocked=0 arrival_support=5/5 departure_support=5/5 clearance_excess_m=2.824 portal_exclusion_m=13.0 influence_reach_m=1.053 capsule_bound_m=0.880 status=PASS", + "note": "The six identical hash/layout rows are the concrete baselines; route point counts differ by seed only in other sweep cases." + } +} diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-arrival-mouth.jpg b/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-arrival-mouth.jpg new file mode 100644 index 0000000..58f9af4 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-arrival-mouth.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-departure-mouth.jpg b/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-departure-mouth.jpg new file mode 100644 index 0000000..3aa7176 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-departure-mouth.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-island-edge.jpg b/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-island-edge.jpg new file mode 100644 index 0000000..0e8d0f5 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-island-edge.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-walk-between-islands.jpg b/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-walk-between-islands.jpg new file mode 100644 index 0000000..3f2d9c8 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-walk-between-islands.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-wide-silhouette.jpg b/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-wide-silhouette.jpg new file mode 100644 index 0000000..b37c0a5 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-wide-silhouette.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-arrival-mouth.jpg b/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-arrival-mouth.jpg new file mode 100644 index 0000000..ffa177c Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-arrival-mouth.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-departure-mouth.jpg b/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-departure-mouth.jpg new file mode 100644 index 0000000..f92800a Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-departure-mouth.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-island-edge.jpg b/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-island-edge.jpg new file mode 100644 index 0000000..03ebc0b Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-island-edge.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-walk-between-islands.jpg b/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-walk-between-islands.jpg new file mode 100644 index 0000000..a20445b Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-walk-between-islands.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-wide-silhouette.jpg b/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-wide-silhouette.jpg new file mode 100644 index 0000000..a8414e1 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-wide-silhouette.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/captures/manifest.json b/Standalone/Research/WorldAssemblerM3/Linux140/captures/manifest.json new file mode 100644 index 0000000..9cbe0e7 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux140/captures/manifest.json @@ -0,0 +1,229 @@ +{ + "seed": 0, + "source_stamp": "6205e6c180ef32c41792dc3e0d8bf443a96df41974f40334af4bf3a157ab88da", + "before_stamp": "b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1", + "views_per_phase": 5, + "all_quality_pass": true, + "captures": [ + { + "phase": "after", + "view": "arrival-mouth", + "file": "Standalone/Research/WorldAssemblerM3/Linux140/captures/after-arrival-mouth.jpg", + "bytes": 20205, + "camera_m": [ + 23.77218612173332, + 4.6087926326387905, + -735.55 + ], + "target_m": [ + 32.44404347295337, + -1.3114176744441277, + -735.55 + ], + "yaw_deg": 124.32105280346487, + "pitch_deg": 0.0, + "fov_deg": 78.0, + "note": "player eye at the fixed 0-1 arrival mouth", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-arrival-mouth.jpg pixels=921600 mean_luma=111.598 stddev_luma=5.081 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "phase": "after", + "view": "departure-mouth", + "file": "Standalone/Research/WorldAssemblerM3/Linux140/captures/after-departure-mouth.jpg", + "bytes": 23901, + "camera_m": [ + 21.556817528943192, + -14.930993681478485, + -730.751171875 + ], + "target_m": [ + 11.51544953238179, + -11.861640531768845, + -730.751171875 + ], + "yaw_deg": -73.00313688448165, + "pitch_deg": 0.0, + "fov_deg": 78.0, + "note": "player eye approaching the fixed 1-2 departure mouth", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-departure-mouth.jpg pixels=921600 mean_luma=112.186 stddev_luma=5.529 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "phase": "after", + "view": "island-edge", + "file": "Standalone/Research/WorldAssemblerM3/Linux140/captures/after-island-edge.jpg", + "bytes": 19984, + "camera_m": [ + 47.84242754056169, + -19.331304817383565, + -734.4728515625 + ], + "target_m": [ + 54.57926831575571, + -16.75934518129221, + -735.5728515625 + ], + "yaw_deg": 69.10437717375885, + "pitch_deg": -8.673184187101441, + "fov_deg": 78.0, + "note": "player eye beside the route on an island edge", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-island-edge.jpg pixels=921600 mean_luma=114.169 stddev_luma=8.688 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "phase": "after", + "view": "walk-between-islands", + "file": "Standalone/Research/WorldAssemblerM3/Linux140/captures/after-walk-between-islands.jpg", + "bytes": 20752, + "camera_m": [ + 64.3971173756484, + -22.884388870806152, + -733.137890625 + ], + "target_m": [ + 53.12109375, + -24.2802734375, + -734.537109375 + ], + "yaw_deg": -97.05687302971492, + "pitch_deg": -7.020510748634912, + "fov_deg": 78.0, + "note": "player eye looking from one route landing toward the next island", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-walk-between-islands.jpg pixels=921600 mean_luma=111.849 stddev_luma=7.186 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "phase": "after", + "view": "wide-silhouette", + "file": "Standalone/Research/WorldAssemblerM3/Linux140/captures/after-wide-silhouette.jpg", + "bytes": 22894, + "camera_m": [ + 41.099609375, + 8.620008680555555, + -725.6368272569445 + ], + "target_m": [ + 41.099609375, + -14.379991319444445, + -735.6368272569445 + ], + "yaw_deg": 180.0, + "pitch_deg": -23.498565675952097, + "fov_deg": 72.0, + "note": "wide side view showing the island chain silhouette", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux140/captures/after-wide-silhouette.jpg pixels=921600 mean_luma=116.109 stddev_luma=7.003 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "phase": "before", + "view": "arrival-mouth", + "file": "Standalone/Research/WorldAssemblerM3/Linux140/captures/before-arrival-mouth.jpg", + "bytes": 13808, + "camera_m": [ + 23.77218612173332, + 4.6087926326387905, + -735.55 + ], + "target_m": [ + 32.44404347295337, + -1.3114176744441277, + -735.55 + ], + "yaw_deg": 124.32105280346487, + "pitch_deg": 0.0, + "fov_deg": 78.0, + "note": "player eye at the fixed 0-1 arrival mouth", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-arrival-mouth.jpg pixels=921600 mean_luma=132.078 stddev_luma=8.080 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "phase": "before", + "view": "departure-mouth", + "file": "Standalone/Research/WorldAssemblerM3/Linux140/captures/before-departure-mouth.jpg", + "bytes": 17309, + "camera_m": [ + 21.556817528943192, + -14.930993681478485, + -730.751171875 + ], + "target_m": [ + 11.51544953238179, + -11.861640531768845, + -730.751171875 + ], + "yaw_deg": -73.00313688448165, + "pitch_deg": 0.0, + "fov_deg": 78.0, + "note": "player eye approaching the fixed 1-2 departure mouth", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-departure-mouth.jpg pixels=921600 mean_luma=118.383 stddev_luma=9.908 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "phase": "before", + "view": "island-edge", + "file": "Standalone/Research/WorldAssemblerM3/Linux140/captures/before-island-edge.jpg", + "bytes": 15208, + "camera_m": [ + 47.84242754056169, + -19.331304817383565, + -734.4728515625 + ], + "target_m": [ + 54.57926831575571, + -16.75934518129221, + -735.5728515625 + ], + "yaw_deg": 69.10437717375885, + "pitch_deg": -8.673184187101441, + "fov_deg": 78.0, + "note": "player eye beside the route on an island edge", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-island-edge.jpg pixels=921600 mean_luma=109.945 stddev_luma=4.383 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "phase": "before", + "view": "walk-between-islands", + "file": "Standalone/Research/WorldAssemblerM3/Linux140/captures/before-walk-between-islands.jpg", + "bytes": 15126, + "camera_m": [ + 64.3971173756484, + -22.884388870806152, + -733.137890625 + ], + "target_m": [ + 53.12109375, + -24.2802734375, + -734.537109375 + ], + "yaw_deg": -97.05687302971492, + "pitch_deg": -7.020510748634912, + "fov_deg": 78.0, + "note": "player eye looking from one route landing toward the next island", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-walk-between-islands.jpg pixels=921600 mean_luma=110.305 stddev_luma=4.132 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "phase": "before", + "view": "wide-silhouette", + "file": "Standalone/Research/WorldAssemblerM3/Linux140/captures/before-wide-silhouette.jpg", + "bytes": 15268, + "camera_m": [ + 41.099609375, + 8.620008680555555, + -725.6368272569445 + ], + "target_m": [ + 41.099609375, + -14.379991319444445, + -735.6368272569445 + ], + "yaw_deg": 180.0, + "pitch_deg": -23.498565675952097, + "fov_deg": 72.0, + "note": "wide side view showing the island chain silhouette", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux140/captures/before-wide-silhouette.jpg pixels=921600 mean_luma=114.186 stddev_luma=3.531 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + } + ] +} diff --git a/Standalone/Research/WorldAssemblerM3/Linux140/l140-fi-around-tunnels.patch b/Standalone/Research/WorldAssemblerM3/Linux140/l140-fi-around-tunnels.patch new file mode 100644 index 0000000..c4bcb95 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux140/l140-fi-around-tunnels.patch @@ -0,0 +1,1279 @@ +diff --git a/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp b/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp +index 5e66412..8d21685 100644 +--- a/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp ++++ b/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp +@@ -892,6 +892,7 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager, + + bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager, + const FStrateSlot& Slot, VoxelForgeCore::WorldAssemblyManifest& Manifest, ++ const VoxelForgeCore::ResolvedLayoutSnapshotV3& Layout, + double CoreVoxelSizeCm, std::int64_t LayoutChunkWidthSubvoxels, FString& OutError) + { + using namespace VoxelForgeCore; +@@ -903,7 +904,8 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager, + IslandPlan.topChunkZ = Slot.TopChunkZ; + IslandPlan.bottomChunkZ = Slot.BottomChunkZ; + auto& Recipe = IslandPlan.manifest.recipe; +- Recipe.geometryVersion = 8; ++ Recipe.geometryVersion = 10; ++ Recipe.primaryIslandCount = 9; + Recipe.terrainFollowingRoute = static_cast(Passage.Style) == 0; + Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels; + Recipe.seed = static_cast(StrateManager->GetWorldSeed()); +@@ -929,13 +931,24 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager, + / Recipe.ownerToCoreScaleDenominator; + Recipe.spacingSubvoxels = WorldAssemblyFixed(Params.IslandSpacing * OwnerToCoreScale); + Recipe.minimumRadiusSubvoxels = WorldAssemblyFixed(Params.IslandMinRadius * OwnerToCoreScale); +- Recipe.maximumRadiusSubvoxels = WorldAssemblyFixed(Params.IslandMaxRadius * OwnerToCoreScale); ++ Recipe.maximumRadiusSubvoxels = WorldAssemblyFixed(28.0 * OwnerToCoreScale); + Recipe.thicknessRatioQ16 = WorldAssemblyQ16(Params.ThicknessRatio); + Recipe.verticalJitterQ16 = WorldAssemblyQ16(Params.VerticalJitter); + Recipe.topFlattenQ16 = WorldAssemblyQ16(Params.TopFlatten); +- Recipe.roughnessSubvoxels = WorldAssemblyFixed(Params.SurfaceRoughness * OwnerToCoreScale); +- Recipe.blendRadiusSubvoxels = WorldAssemblyFixed(Params.SDFBlendRadius * OwnerToCoreScale); ++ Recipe.roughnessSubvoxels = std::min(WorldAssemblyFixed( ++ Params.SurfaceRoughness * OwnerToCoreScale), WorldAssemblySubvoxelsPerVoxel / 2); ++ Recipe.blendRadiusSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel; ++ Recipe.contourWarpSubvoxels = WorldAssemblySubvoxelsPerVoxel; + Recipe.boundarySealSubvoxels = WorldAssemblyFixed(Params.BoundarySealThickness * OwnerToCoreScale); ++ Recipe.routeConstraints.version = 1; ++ Recipe.routeConstraints.capsuleRadiusSubvoxels = 348; ++ Recipe.routeConstraints.capsuleHeightSubvoxels = 1802; ++ Recipe.routeConstraints.maximumGradeQ16 = 17560; ++ Recipe.routeConstraints.sweepSpacingSubvoxels = 128; ++ Recipe.routeConstraints.supportProbeRadiusSubvoxels = 230; ++ Recipe.routeConstraints.supportSearchDepthSubvoxels = 512; ++ Recipe.routeConstraints.minimumClearanceSubvoxels = 128; ++ Recipe.routeConstraints.minimumSupportProbes = 3; + const auto OwnerVoxelsToCoreQ=[&](double Value) + { + return static_cast(std::llround(Value*Recipe.ownerVoxelSizeMicrons +@@ -944,40 +957,21 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager, + Recipe.anchorIslandOrdinal=Recipe.primaryIslandCount-1; + Recipe.anchorPlacementRadiusSubvoxels=WorldAssemblyMicrometresToSubvoxels( + Slot.PassageDistanceMaximumMicrometres, Recipe.coreVoxelSizeMicrons); +- const std::int64_t spiralRadius=WorldAssemblyMicrometresToSubvoxels( +- Slot.PassageSpiralRadiusMicrometres, Recipe.coreVoxelSizeMicrons); +- // Reserve at least a 1.5 m mid-bore through the anchor root. +- constexpr double AnchorPassageMinimumBoreOwnerVoxels=12.0; +- const std::int64_t OwnerVoxelMicrometres = Recipe.ownerVoxelSizeMicrons; +- const std::int64_t mouthBore=std::max( +- WorldAssemblyMicrometresToSubvoxels(Slot.PassageMouthRadiusMicrometres, +- Recipe.coreVoxelSizeMicrons), +- WorldAssemblyMicrometresToSubvoxels(12 * OwnerVoxelMicrometres, +- Recipe.coreVoxelSizeMicrons)); +- const std::int64_t bore=std::max( +- WorldAssemblyMicrometresToSubvoxels(Slot.PassageMidRadiusMicrometres, +- Recipe.coreVoxelSizeMicrons), +- WorldAssemblyMicrometresToSubvoxels( +- static_cast(AnchorPassageMinimumBoreOwnerVoxels * OwnerVoxelMicrometres), +- Recipe.coreVoxelSizeMicrons)); +- const std::int64_t wallReserve=WorldAssemblyMicrometresToSubvoxels( +- 12 * OwnerVoxelMicrometres, Recipe.coreVoxelSizeMicrons); +- const std::int64_t FourOwnerVoxelsSubvoxels=WorldAssemblyMicrometresToSubvoxels( +- 4 * OwnerVoxelMicrometres, Recipe.coreVoxelSizeMicrons); +- Recipe.anchorMouthOffsetSubvoxels=spiralRadius+mouthBore+FourOwnerVoxelsSubvoxels; +- const std::int64_t excursion=std::max( +- WorldAssemblyMicrometresToSubvoxels(Slot.PassageWanderMicrometres, +- Recipe.coreVoxelSizeMicrons), spiralRadius); +- Recipe.anchorRootRadiusSubvoxels=excursion+bore+wallReserve; +- Recipe.anchorIslandMinimumRadiusSubvoxels=std::max(Recipe.minimumRadiusSubvoxels, +- Recipe.anchorMouthOffsetSubvoxels+mouthBore+FourOwnerVoxelsSubvoxels); ++ Recipe.anchorMouthOffsetSubvoxels = OwnerVoxelsToCoreQ(4.0); ++ Recipe.anchorRootRadiusSubvoxels = std::max(OwnerVoxelsToCoreQ(16.0), ++ Recipe.anchorMouthOffsetSubvoxels + Recipe.routeConstraints.capsuleRadiusSubvoxels ++ + WorldAssemblySubvoxelsPerVoxel); ++ Recipe.anchorIslandMinimumRadiusSubvoxels = std::max(Recipe.maximumRadiusSubvoxels, ++ Recipe.anchorMouthOffsetSubvoxels + Recipe.routeConstraints.capsuleRadiusSubvoxels ++ + WorldAssemblySubvoxelsPerVoxel); + Recipe.anchorRootBottomZSubvoxels=Recipe.layoutMinimumZSubvoxels + +Recipe.boundarySealSubvoxels; + std::string PlanError; +- const std::optional Planned = PlanFloatingIslandCluster(Recipe, PlanError); ++ const std::optional Planned = ++ PlanFloatingIslandClusterAroundPassages(Recipe, Layout, Slot.StrateIndex, PlanError); + if (!Planned) + { +- OutError = FString::Printf(TEXT("FloatingIslands automatic plan for strate %d failed: %s"), ++ OutError = FString::Printf(TEXT("FloatingIslands passage-aware plan for strate %d failed: %s"), + Slot.StrateIndex, UTF8_TO_TCHAR(PlanError.c_str())); + return false; + } +@@ -1671,18 +1665,6 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings, + Generated.plan.strates.push_back(std::move(Stratum)); + ++BuiltSurfaceCount; + } +- // Native floating-island evaluation remains an explicit imported-plan opt-in. +- // The default owner path uses the legacy generator for this slot; emitting an +- // automatic FI plan here silently switches density dispatch and changes the +- // shared passage mouth before the owner has opted into that field. +- else if (Type == ECaveGeneratorType::FloatingIslands && bImportedOverride) +- { +- if (!BuildFloatingIslandStratumPlan(StrateManager, Slot, Generated, +- CoreVoxelSizeCm, LayoutChunkWidthSubvoxels, OutError)) +- { +- return false; +- } +- } + } + if (BuiltSurfaceCount > 1) + { +@@ -2031,6 +2013,98 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings, + { + return false; + } ++ bool bAddedFloatingIslandPlan = false; ++ for (const FStrateSlot& Slot : StrateManager->GetLayout()) ++ { ++ if (Slot.Definition == nullptr ++ || StrateManager->GetGeneratorTypeForChunk(FIntVector(0, 0, Slot.TopChunkZ)) ++ != ECaveGeneratorType::FloatingIslands) ++ { ++ continue; ++ } ++ const auto Existing = std::find_if(Manifest->floatingIslands.begin(), ++ Manifest->floatingIslands.end(), [&Slot](const auto& Plan) ++ { return Plan.strateIndex == Slot.StrateIndex; }); ++ if (Existing != Manifest->floatingIslands.end()) continue; ++ if (!BuildFloatingIslandStratumPlan(StrateManager, Slot, *Manifest, Layout, ++ CoreVoxelSizeCm, LayoutChunkWidthSubvoxels, OutError)) ++ { ++ return false; ++ } ++ bAddedFloatingIslandPlan = true; ++ } ++ if (bAddedFloatingIslandPlan) ++ { ++ if (!Manifest->finalize(CoreError)) ++ { ++ OutError = FString::Printf(TEXT("Core passage-aware FloatingIslands planning failed: %s"), ++ UTF8_TO_TCHAR(CoreError.c_str())); ++ return false; ++ } ++ ManifestHash = FString::Printf(TEXT("%016llx"), ++ static_cast(Manifest->contentHash)); ++ for (const auto& Plan : Manifest->floatingIslands) ++ { ++ auto Stratum = std::find_if(Layout.strates.begin(), Layout.strates.end(), ++ [&Plan](const auto& Item) { return Item.strateIndex == Plan.strateIndex; }); ++ if (Stratum == Layout.strates.end()) ++ { ++ OutError = FString::Printf(TEXT("FloatingIslands strate %d disappeared before anchor export."), ++ Plan.strateIndex); ++ return false; ++ } ++ Stratum->recipeVersion = Plan.manifest.recipe.geometryVersion; ++ std::erase_if(Stratum->anchors, [](const auto& Anchor) ++ { ++ return Anchor.kind == "island_anchor" || Anchor.kind == "passage_anchor" ++ || Anchor.kind == "route_point"; ++ }); ++ const auto& NativePlan = Plan.manifest.plan; ++ const auto& NativeRecipe = Plan.manifest.recipe; ++ for (std::size_t IslandIndex = 0; IslandIndex < NativePlan.islands.size(); ++IslandIndex) ++ { ++ const auto& Island = NativePlan.islands[IslandIndex]; ++ VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor; ++ Anchor.kind = "island_anchor"; ++ Anchor.stableId = "island/" + std::to_string(IslandIndex); ++ Anchor.units = "1/256 Core voxel"; ++ Anchor.values = { Island.centerSubvoxels[0], Island.centerSubvoxels[1], ++ Island.centerSubvoxels[2], Island.radiusSubvoxels, Island.topHalfSubvoxels }; ++ Stratum->anchors.push_back(std::move(Anchor)); ++ if (IslandIndex == NativeRecipe.anchorIslandOrdinal) ++ { ++ VoxelForgeCore::ResolvedLayoutAnchorV2 PassageAnchor; ++ PassageAnchor.kind = "passage_anchor"; ++ PassageAnchor.stableId = "passage-anchor/island/" ++ + std::to_string(IslandIndex); ++ PassageAnchor.units = "1/256 Core voxel"; ++ PassageAnchor.values = { Island.centerSubvoxels[0], Island.centerSubvoxels[1], ++ NativeRecipe.anchorRootRadiusSubvoxels, ++ NativeRecipe.anchorMouthOffsetSubvoxels, ++ NativeRecipe.anchorPlacementRadiusSubvoxels, ++ NativeRecipe.anchorRootBottomZSubvoxels, Island.radiusSubvoxels }; ++ if (!NativePlan.route.empty()) ++ { ++ const auto& Mouth = NativePlan.route.back().positionSubvoxels; ++ PassageAnchor.values.insert(PassageAnchor.values.end(), ++ { Mouth[0], Mouth[1], Mouth[2] }); ++ } ++ Stratum->anchors.push_back(std::move(PassageAnchor)); ++ } ++ } ++ for (std::size_t RouteIndex = 0; RouteIndex < NativePlan.route.size(); ++RouteIndex) ++ { ++ const auto& Point = NativePlan.route[RouteIndex]; ++ VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor; ++ Anchor.kind = "route_point"; ++ Anchor.stableId = "route/" + std::to_string(RouteIndex); ++ Anchor.units = "1/256 Core voxel"; ++ Anchor.values = { Point.positionSubvoxels[0], Point.positionSubvoxels[1], ++ Point.positionSubvoxels[2], Point.radiusSubvoxels }; ++ Stratum->anchors.push_back(std::move(Anchor)); ++ } ++ } ++ } + std::string LayoutError; + if (!Layout.finalize(LayoutError)) + { +diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +index 8c44461..ec7cfe3 100644 +--- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h ++++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +@@ -511,7 +511,7 @@ namespace VoxelForgeCore + // the planned graph route, preserves its walk corridor through shell overlaps, and keeps + // boundary-blended host rock from widening the authored passage wall. + // v237 adds the shared Core cave-graph shaft primitive and basal terminal route. +- inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 237; ++ inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 239; + + enum class FieldChannel : std::uint32_t + { +@@ -1417,6 +1417,19 @@ namespace VoxelForgeCore + bool passed = false; + }; + ++ struct FloatingIslandPassageClearanceAuditResult ++ { ++ std::uint64_t passageSamples = 0; ++ std::uint64_t blockedIslandSamples = 0; ++ std::uint64_t routeSamples = 0; ++ std::uint64_t blockedRouteSamples = 0; ++ std::uint32_t arrivalMouthSupportProbes = 0; ++ std::uint32_t departureMouthSupportProbes = 0; ++ std::int64_t minimumExcessClearanceSubvoxels = 0; ++ double portalExclusionMetres = 0.0; ++ bool passed = false; ++ }; ++ + struct FloatingIslandInfluenceBounds + { + bool finite = false; +@@ -1461,11 +1474,17 @@ namespace VoxelForgeCore + // Planner output is ordinary reviewed data; clients can load it without re-running the planner. + VFCORE_API std::optional PlanFloatingIslandCluster( + const FloatingIslandRecipeV1& recipe, std::string& error); ++ VFCORE_API std::optional PlanFloatingIslandClusterAroundPassages( ++ const FloatingIslandRecipeV1& recipe, const ResolvedLayoutSnapshotV3& layout, ++ std::int32_t strateIndex, std::string& error); + VFCORE_API FloatingIslandBoundsAuditResult AuditFloatingIslandBounds( + const FloatingIslandFieldV1& field, std::uint32_t boxCount = 256, + std::uint64_t seed = 0x49534c414e444231ull); + VFCORE_API FloatingIslandRouteAuditResult AuditFloatingIslandRoute( + const FloatingIslandFieldV1& field, std::int64_t routeOffsetSubvoxels = 0); ++ VFCORE_API FloatingIslandPassageClearanceAuditResult AuditFloatingIslandPassageClearance( ++ const FloatingIslandFieldV1& field, const ResolvedLayoutSnapshotV3& layout, ++ std::int32_t strateIndex, double portalExclusionMetres = 10.0); + VFCORE_API FloatingIslandThicknessAuditResult AuditFloatingIslandThickness( + const FloatingIslandPlanV1& plan, double minimumDepthToDiameterRatio = 0.70); + +diff --git a/Standalone/Core/src/FloatingIslandsInternal.h b/Standalone/Core/src/FloatingIslandsInternal.h +index 33d97d6..58f0959 100644 +--- a/Standalone/Core/src/FloatingIslandsInternal.h ++++ b/Standalone/Core/src/FloatingIslandsInternal.h +@@ -4,6 +4,7 @@ + #include "VoxelForgeCore/CpuCycleCounter.h" + #include "VoxelForgeCore/FloatingIslandInstrumentation.h" + #include "VoxelForgeCore/SharedColumnCache.h" ++#include "VoxelForgeCore/PassageSpline.h" + #include "Json.h" + #include "VoxelForgeCore/VoxelForgeCore.h" + +@@ -285,7 +286,7 @@ namespace floating_island_detail + ? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0; + const std::uint32_t ExpectedDenominator = ScaleGcd > 0 + ? recipe.coreVoxelSizeMicrons / ScaleGcd : 0; +- if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 8 ++ if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 10 + || (recipe.geometryVersion == 1 + && (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0)) + || recipe.unitsPerVoxel != Q +@@ -1988,6 +1989,229 @@ std::string FloatingIslandManifestV1::ToJson() const + return out.str(); + } + ++namespace floating_island_detail ++{ ++ struct FIslandPassageSpan ++ { ++ PassageSpline::FSegment segment; ++ double alongMetres = 0.0; ++ double totalMetres = 0.0; ++ double radiusSubvoxels = 0.0; ++ bool portalAtStart = false; ++ }; ++ ++ inline double SegmentDistanceSquared3D(const PassageSpline::FPoint& p1, ++ const PassageSpline::FPoint& q1, const PassageSpline::FPoint& p2, ++ const PassageSpline::FPoint& q2) ++ { ++ const std::array d1={q1[0]-p1[0],q1[1]-p1[1],q1[2]-p1[2]}; ++ const std::array d2={q2[0]-p2[0],q2[1]-p2[1],q2[2]-p2[2]}; ++ const std::array r={p1[0]-p2[0],p1[1]-p2[1],p1[2]-p2[2]}; ++ const auto dot=[](const auto& a,const auto& b) ++ {return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];}; ++ const double a=dot(d1,d1),e=dot(d2,d2),f=dot(d2,r); ++ double s=0.0,t=0.0; ++ if(a<=1.0e-18&&e<=1.0e-18)return dot(r,r); ++ if(a<=1.0e-18)t=std::clamp(f/e,0.0,1.0); ++ else ++ { ++ const double c=dot(d1,r); ++ if(e<=1.0e-18)s=std::clamp(-c/a,0.0,1.0); ++ else ++ { ++ const double b=dot(d1,d2); ++ const double denominator=a*e-b*b; ++ if(std::abs(denominator)>1.0e-18) ++ s=std::clamp((b*f-c*e)/denominator,0.0,1.0); ++ t=(b*s+f)/e; ++ if(t<0.0){t=0.0;s=std::clamp(-c/a,0.0,1.0);} ++ else if(t>1.0){t=1.0;s=std::clamp((b-c)/a,0.0,1.0);} ++ } ++ } ++ std::array delta{}; ++ for(std::size_t axis=0;axis<3;++axis) ++ delta[axis]=r[axis]+s*d1[axis]-t*d2[axis]; ++ return dot(delta,delta); ++ } ++ ++ inline std::vector BuildIslandPassageSpans( ++ const ResolvedLayoutSnapshotV3& layout,std::int32_t strateIndex, ++ const FloatingIslandRecipeV1& recipe,double portalExclusionMetres) ++ { ++ std::vector spans; ++ const double qPerMetre=static_cast(WorldAssemblySubvoxelsPerVoxel) ++ *1000000.0/std::max(1,recipe.coreVoxelSizeMicrons); ++ for(const auto& passage:layout.passages) ++ { ++ const bool upper=passage.upperStrateIndex==strateIndex; ++ const bool lower=passage.lowerStrateIndex==strateIndex; ++ if((!upper&&!lower)||passage.controlPoints.size()<2)continue; ++ std::vector controls; ++ controls.reserve(passage.controlPoints.size()); ++ for(const auto& point:passage.controlPoints) ++ controls.push_back({static_cast(point.positionSubvoxels[0]), ++ static_cast(point.positionSubvoxels[1]), ++ static_cast(point.positionSubvoxels[2]), ++ static_cast(point.radiusSubvoxels)}); ++ const auto built=PassageSpline::BuildSegments(controls,controls.size()-1, ++ 0.25,qPerMetre,{},0.04); ++ if(built.empty())continue; ++ const double total=built.back().alongMetres+built.back().lengthMetres; ++ const auto& first=controls.front(); ++ const auto& last=controls.back(); ++ const auto distanceSquaredTo=[&](const PassageSpline::FPoint& point, ++ const std::array& mouth) ++ { ++ double result=0.0; ++ for(std::size_t axis=0;axis<3;++axis) ++ {const double delta=point[axis]-mouth[axis];result+=delta*delta;} ++ return result; ++ }; ++ const bool upperAtStart=distanceSquaredTo(first,passage.upperMouthSubvoxels) ++ <=distanceSquaredTo(last,passage.upperMouthSubvoxels); ++ const bool portalAtStart=upper?upperAtStart:!upperAtStart; ++ for(const auto& segment:built) ++ { ++ const double along=segment.alongMetres+0.5*segment.lengthMetres; ++ const double portalDistance=portalAtStart?along:total-along; ++ if(portalDistance<=portalExclusionMetres)continue; ++ const PassageSpline::FPoint midpoint={ ++ 0.5*(segment.a[0]+segment.b[0]), ++ 0.5*(segment.a[1]+segment.b[1]), ++ 0.5*(segment.a[2]+segment.b[2]), ++ 0.5*(segment.a[3]+segment.b[3])}; ++ const double minZ=std::min(segment.a[2],segment.b[2]); ++ const double maxZ=std::max(segment.a[2],segment.b[2]); ++ if(maxZ(recipe.layoutMinimumZSubvoxels) ++ ||minZ>static_cast(recipe.layoutMaximumZSubvoxels))continue; ++ spans.push_back({segment,along,total,midpoint[3],portalAtStart}); ++ } ++ } ++ return spans; ++ } ++ ++ inline std::array EvaluateIslandRouteBezier( ++ const std::array& a,const std::array& b, ++ const std::array& waypoint,double t) ++ { ++ const double u=1.0-t; ++ const std::array result={ ++ u*u*u*a[0]+3.0*u*u*t*waypoint[0]+3.0*u*t*t*waypoint[0]+t*t*t*b[0], ++ u*u*u*a[1]+3.0*u*u*t*waypoint[1]+3.0*u*t*t*waypoint[1]+t*t*t*b[1], ++ std::lerp(a[2],b[2],t),t}; ++ return result; ++ } ++ ++ struct FIslandDetourCandidate ++ { ++ std::array,9> centers{}; ++ double horizontalLengthMetres=0.0; ++ double conservativeClearanceMetres=-std::numeric_limits::infinity(); ++ double waypointAngle=0.0; ++ double waypointRadiusMetres=0.0; ++ }; ++ ++ inline std::vector BuildIslandDetourCandidates( ++ const FloatingIslandRecipeV1& recipe,const ResolvedLayoutPassageV1& arrival, ++ const ResolvedLayoutPassageV1& departure, ++ const std::vector& spans) ++ { ++ std::vector candidates; ++ const double qPerMetre=static_cast(WorldAssemblySubvoxelsPerVoxel) ++ *1000000.0/std::max(1,recipe.coreVoxelSizeMicrons); ++ const std::array a={static_cast(arrival.lowerMouthSubvoxels[0]), ++ static_cast(arrival.lowerMouthSubvoxels[1]), ++ static_cast(arrival.lowerMouthSubvoxels[2])}; ++ const std::array b={static_cast(departure.upperMouthSubvoxels[0]), ++ static_cast(departure.upperMouthSubvoxels[1]), ++ static_cast(departure.upperMouthSubvoxels[2])}; ++ for(int angleIndex=0;angleIndex<16;++angleIndex) ++ for(const double radiusMetres:{60.0,72.0,84.0}) ++ { ++ FIslandDetourCandidate candidate; ++ candidate.waypointAngle=2.0*Pi*angleIndex/16.0; ++ candidate.waypointRadiusMetres=radiusMetres; ++ const std::array waypoint={ ++ static_cast(recipe.layoutOriginXSubvoxels) ++ +std::cos(candidate.waypointAngle)*radiusMetres*qPerMetre, ++ static_cast(recipe.layoutOriginYSubvoxels) ++ +std::sin(candidate.waypointAngle)*radiusMetres*qPerMetre, ++ 0.5*(a[2]+b[2])}; ++ constexpr std::size_t CurveSteps=512; ++ std::array arc{}; ++ std::array,CurveSteps+1> curve{}; ++ auto prior=EvaluateIslandRouteBezier(a,b,waypoint,0.0); ++ curve[0]={prior[0],prior[1],prior[2]}; ++ for(std::size_t n=1;n<=CurveSteps;++n) ++ { ++ const auto value=EvaluateIslandRouteBezier(a,b,waypoint, ++ static_cast(n)/CurveSteps); ++ curve[n]={value[0],value[1],value[2]}; ++ const double dx=(value[0]-prior[0])/qPerMetre; ++ const double dy=(value[1]-prior[1])/qPerMetre; ++ arc[n]=arc[n-1]+std::hypot(dx,dy); ++ prior=value; ++ } ++ candidate.horizontalLengthMetres=arc.back(); ++ if(!(candidate.horizontalLengthMetres>1.0))continue; ++ for(std::size_t island=0;island( ++ CurveSteps,static_cast(upper-arc.begin())); ++ const std::size_t lowerIndex=upperIndex?upperIndex-1:0; ++ const double span=arc[upperIndex]-arc[lowerIndex]; ++ const double t=span>1.0e-9?(target-arc[lowerIndex])/span:0.0; ++ auto point=std::array{ ++ std::lerp(curve[lowerIndex][0],curve[upperIndex][0],t), ++ std::lerp(curve[lowerIndex][1],curve[upperIndex][1],t), ++ std::lerp(a[2],b[2],target/arc.back())}; ++ if(island==0)point=a; ++ if(island+1==candidate.centers.size())point=b; ++ for(std::size_t axis=0;axis<3;++axis) ++ candidate.centers[island][axis]=RoundQ(point[axis]); ++ } ++ double minimumClearance=std::numeric_limits::infinity(); ++ for(std::size_t routeIndex=1;routeIndex(candidate.centers[routeIndex-1][axis]); ++ routeB[axis]=static_cast(candidate.centers[routeIndex][axis]); ++ } ++ for(const auto& span:spans) ++ { ++ const double distance=std::sqrt(SegmentDistanceSquared3D( ++ routeA,routeB,span.segment.a,span.segment.b))/qPerMetre; ++ const double passageRadius=span.radiusSubvoxels/qPerMetre; ++ const double capsuleRadius=0.5*recipe.routeConstraints.capsuleHeightSubvoxels/qPerMetre; ++ const double featureReach=recipe.maximumRadiusSubvoxels/qPerMetre ++ +(recipe.contourWarpSubvoxels+IslandBlendMargin(recipe) ++ +recipe.roughnessSubvoxels)/qPerMetre; ++ minimumClearance=std::min(minimumClearance,distance-passageRadius ++ -1.0533-capsuleRadius-featureReach-0.15); ++ } ++ } ++ candidate.conservativeClearanceMetres=std::isfinite(minimumClearance) ++ ?minimumClearance:1.0e6; ++ candidates.push_back(candidate); ++ } ++ std::stable_sort(candidates.begin(),candidates.end(), ++ [](const auto& left,const auto& right) ++ { ++ const bool leftSafe=left.conservativeClearanceMetres>=0.0; ++ const bool rightSafe=right.conservativeClearanceMetres>=0.0; ++ if(leftSafe!=rightSafe)return leftSafe; ++ if(left.horizontalLengthMetres!=right.horizontalLengthMetres) ++ return left.horizontalLengthMetresright.conservativeClearanceMetres; ++ }); ++ return candidates; ++ } ++} ++ + std::optional PlanFloatingIslandCluster( + const FloatingIslandRecipeV1& inputRecipe, std::string& error) + { +@@ -2264,6 +2488,210 @@ std::optional PlanFloatingIslandCluster( + return manifest; + } + ++std::optional PlanFloatingIslandClusterAroundPassages( ++ const FloatingIslandRecipeV1& inputRecipe,const ResolvedLayoutSnapshotV3& layout, ++ std::int32_t strateIndex,std::string& error) ++{ ++ using namespace floating_island_detail; ++ FloatingIslandRecipeV1 recipe=inputRecipe; ++ if(recipe.routeConstraints.version==0) ++ { ++ recipe.routeConstraints.version=1; ++ recipe.routeConstraints.capsuleRadiusSubvoxels=348; ++ recipe.routeConstraints.capsuleHeightSubvoxels=1802; ++ recipe.routeConstraints.maximumGradeQ16=17560; ++ recipe.routeConstraints.sweepSpacingSubvoxels=128; ++ recipe.routeConstraints.supportProbeRadiusSubvoxels=230; ++ recipe.routeConstraints.supportSearchDepthSubvoxels=512; ++ recipe.routeConstraints.minimumClearanceSubvoxels=128; ++ recipe.routeConstraints.minimumSupportProbes=3; ++ } ++ if(recipe.geometryVersion<10||recipe.primaryIslandCount!=9) ++ {error="corridor-aware FloatingIslands requires geometry version 10 and nine route islands";return std::nullopt;} ++ if(!ValidateRecipe(recipe,error))return std::nullopt; ++ ++ const ResolvedLayoutPassageV1* arrival=nullptr; ++ const ResolvedLayoutPassageV1* departure=nullptr; ++ for(const auto& passage:layout.passages) ++ { ++ if(passage.lowerStrateIndex==strateIndex ++ &&passage.upperStrateIndexstrateIndex)departure=&passage; ++ } ++ if(!arrival||!departure) ++ {error="corridor-aware FloatingIslands needs resolved arrival and departure mouths";return std::nullopt;} ++ ++ constexpr double PortalExclusionMetres=13.0; ++ const auto passageSpans=BuildIslandPassageSpans(layout,strateIndex,recipe, ++ PortalExclusionMetres); ++ if(passageSpans.empty()) ++ {error="corridor-aware FloatingIslands found no resolved passage spans inside its strate";return std::nullopt;} ++ auto candidates=BuildIslandDetourCandidates(recipe,*arrival,*departure,passageSpans); ++ const double qPerMetre=static_cast(WorldAssemblySubvoxelsPerVoxel) ++ *1000000.0/std::max(1,recipe.coreVoxelSizeMicrons); ++ const double capsuleHalf=0.5*recipe.routeConstraints.capsuleHeightSubvoxels; ++ const double routeLift=capsuleHalf+recipe.routeConstraints.minimumClearanceSubvoxels+Q/8.0; ++ std::uint32_t tried=0; ++ std::string lastFailure="no outer detour has conservative tube clearance"; ++ for(const auto& candidate:candidates) ++ { ++ if(candidate.conservativeClearanceMetres<0.0)break; ++ ++tried; ++ FloatingIslandManifestV1 manifest; ++ manifest.recipe=recipe; ++ auto& plan=manifest.plan; ++ plan.islands.reserve(recipe.primaryIslandCount); ++ bool candidateValid=true; ++ for(std::uint32_t ordinal=0;ordinal(ordinal)/(recipe.primaryIslandCount-1); ++ const std::int64_t topZ=RoundQ(std::lerp( ++ static_cast(arrival->lowerMouthSubvoxels[2])-routeLift, ++ static_cast(departure->upperMouthSubvoxels[2])-routeLift,t)); ++ auto island=MakeIsland(recipe,ordinal,center[0],center[1],topZ, ++ 0x100000000ull+ordinal); ++ if(ordinal==recipe.anchorIslandOrdinal) ++ island.radiusSubvoxels=std::max(island.radiusSubvoxels, ++ recipe.anchorIslandMinimumRadiusSubvoxels); ++ island.topHalfSubvoxels=std::max(1,island.radiusSubvoxels/5); ++ island.centerSubvoxels[2]=island.topZSubvoxels-island.topHalfSubvoxels; ++ island.bottomZSubvoxels=IslandBottom(recipe,island.topZSubvoxels, ++ island.radiusSubvoxels); ++ SetIslandBounds(recipe,island); ++ if(island.bottomZSubvoxelsrecipe.layoutMaximumZSubvoxels ++ -recipe.boundarySealSubvoxels) ++ {candidateValid=false;lastFailure="detour island escaped the sealed vertical layout";break;} ++ plan.islands.push_back(std::move(island)); ++ } ++ if(!candidateValid)continue; ++ for(std::uint32_t ordinal=0;ordinal+1(b[0]-a[0]); ++ const double dy=static_cast(b[1]-a[1]); ++ const std::uint32_t pieces=static_cast(std::max(1.0, ++ std::ceil(std::hypot(dx,dy)/routeStep))); ++ for(std::uint32_t piece=segment==0?0:1;piece<=pieces;++piece) ++ { ++ const double t=static_cast(piece)/pieces; ++ const std::int64_t x=RoundQ(static_cast(a[0])+dx*t); ++ const std::int64_t y=RoundQ(static_cast(a[1])+dy*t); ++ const auto surface=FindCapsuleFootingTopZ(recipe,plan,x,y); ++ if(!surface) ++ {candidateValid=false;lastFailure="detour route has no full capsule-width island support";break;} ++ WorldAssemblyRoutePointV1 point; ++ point.stableFeatureId=StableId(recipe.seed,0x500000000ull+routeOrdinal, ++ 0x524f555445ull); ++ point.positionSubvoxels={x,y,*surface+static_cast(std::llround(routeLift))}; ++ point.radiusSubvoxels=4*Q; ++ if(segment==0&&piece==0) ++ point.positionSubvoxels=arrival->lowerMouthSubvoxels; ++ if(segment+2==plan.islands.size()&&piece==pieces) ++ point.positionSubvoxels=departure->upperMouthSubvoxels; ++ if(!plan.route.empty()&&plan.route.back().positionSubvoxels==point.positionSubvoxels) ++ continue; ++ plan.route.push_back(point); ++ ++routeOrdinal; ++ } ++ if(!candidateValid)break; ++ } ++ if(!candidateValid||plan.route.size()<2)continue; ++ ++ const double maximumGrade=Q16ToDouble(recipe.routeConstraints.maximumGradeQ16)*0.95; ++ std::vector baseHeights; ++ baseHeights.reserve(plan.route.size()); ++ for(const auto& point:plan.route)baseHeights.push_back(point.positionSubvoxels[2]); ++ std::vector profile=baseHeights; ++ for(std::size_t i=0;i(baseHeights[i]); ++ for(std::size_t j=0;j(plan.route[i].positionSubvoxels[0] ++ -plan.route[j].positionSubvoxels[0]); ++ const double dy=static_cast(plan.route[i].positionSubvoxels[1] ++ -plan.route[j].positionSubvoxels[1]); ++ adjusted=std::max(adjusted,static_cast(baseHeights[j]) ++ -maximumGrade*std::hypot(dx,dy)); ++ } ++ profile[i]=RoundQ(adjusted); ++ } ++ const auto arrivalZ=arrival->lowerMouthSubvoxels[2]; ++ const auto departureZ=departure->upperMouthSubvoxels[2]; ++ if(profile.front()>arrivalZ+Q/2||profile.back()>departureZ+Q/2) ++ {lastFailure="island support profile cannot meet both fixed mouth elevations";continue;} ++ for(std::size_t i=1;i+1lowerMouthSubvoxels; ++ plan.route.back().positionSubvoxels=departure->upperMouthSubvoxels; ++ ++ if(!manifest.finalize(error)) ++ {lastFailure=error;continue;} ++ const FloatingIslandFieldV1 field(manifest.recipe,manifest.plan); ++ const auto routeAudit=AuditFloatingIslandRoute(field); ++ if(!routeAudit.passed) ++ { ++ lastFailure="detour route sweep failed: blocked=" ++ +std::to_string(routeAudit.blockedCapsuleSamples)+" unsupported=" ++ +std::to_string(routeAudit.unsupportedCenterSamples)+" grade=" ++ +std::to_string(routeAudit.gradeFailures)+" detour_angle_deg=" ++ +std::to_string(candidate.waypointAngle*180.0/Pi)+" detour_radius_m=" ++ +std::to_string(candidate.waypointRadiusMetres)+" first_unsupported_q=(" ++ +std::to_string(routeAudit.firstUnsupportedCenterSubvoxels[0])+","+ ++ std::to_string(routeAudit.firstUnsupportedCenterSubvoxels[1])+","+ ++ std::to_string(routeAudit.firstUnsupportedCenterSubvoxels[2])+") segment=" ++ +std::to_string(routeAudit.firstUnsupportedSegment); ++ continue; ++ } ++ const auto clearance=AuditFloatingIslandPassageClearance(field,layout, ++ strateIndex,PortalExclusionMetres); ++ if(!clearance.passed) ++ { ++ lastFailure="passage clearance failed: island=" ++ +std::to_string(clearance.blockedIslandSamples)+" route=" ++ +std::to_string(clearance.blockedRouteSamples)+" mouth_support=" ++ +std::to_string(clearance.arrivalMouthSupportProbes)+"/" ++ +std::to_string(clearance.departureMouthSupportProbes); ++ continue; ++ } ++ const std::string encoded=manifest.ToJson(); ++ const auto roundTrip=FloatingIslandManifestV1::FromJson(encoded,error); ++ if(!roundTrip||roundTrip->ToJson()!=encoded ++ ||roundTrip->contentHash!=manifest.contentHash) ++ {if(error.empty())error="corridor-aware floating-island plan did not canonical-round-trip";return std::nullopt;} ++ std::printf("[FloatingIslandCorridorPlan] strate=%d detours_tried=%u waypoint_angle_deg=%.1f waypoint_radius_m=%.1f route_m=%.1f conservative_clearance_m=%.2f portal_exclusion_m=%.1f islands=%zu arrival_binding=PASS departure_binding=PASS status=PASS\n", ++ strateIndex,tried,candidate.waypointAngle*180.0/Pi, ++ candidate.waypointRadiusMetres,candidate.horizontalLengthMetres, ++ candidate.conservativeClearanceMetres,PortalExclusionMetres, ++ manifest.plan.islands.size()); ++ error.clear(); ++ return manifest; ++ } ++ const auto bestCandidate=std::max_element(candidates.begin(),candidates.end(), ++ [](const auto& left,const auto& right) ++ {return left.conservativeClearanceMetresconservativeClearanceMetres)) ++ +" last="+lastFailure; ++ return std::nullopt; ++} ++ + FloatingIslandFieldV1::FloatingIslandFieldV1(const FloatingIslandRecipeV1& recipe, + const FloatingIslandPlanV1& plan) noexcept : recipe_(&recipe), plan_(&plan) + {} +@@ -2848,6 +3276,107 @@ FloatingIslandRouteAuditResult AuditFloatingIslandRoute(const FloatingIslandFiel + return result; + } + ++FloatingIslandPassageClearanceAuditResult AuditFloatingIslandPassageClearance( ++ const FloatingIslandFieldV1& field,const ResolvedLayoutSnapshotV3& layout, ++ std::int32_t strateIndex,double portalExclusionMetres) ++{ ++ using namespace floating_island_detail; ++ FloatingIslandPassageClearanceAuditResult result; ++ result.portalExclusionMetres=portalExclusionMetres; ++ if(!field.isValid()||!field.recipe()||!field.plan() ++ ||field.plan()->route.size()<2||!std::isfinite(portalExclusionMetres) ++ ||portalExclusionMetres<0.0)return result; ++ const auto& recipe=*field.recipe(); ++ const double qPerMetre=static_cast(WorldAssemblySubvoxelsPerVoxel) ++ *1000000.0/std::max(1,recipe.coreVoxelSizeMicrons); ++ const std::int64_t influenceReach=RoundQ(1.0533*qPerMetre); ++ const std::int64_t capsuleReach=recipe.routeConstraints.capsuleHeightSubvoxels/2; ++ const std::int64_t sampleGuard=RoundQ(0.15*qPerMetre); ++ const auto spans=BuildIslandPassageSpans(layout,strateIndex,recipe, ++ portalExclusionMetres); ++ bool haveClearance=false; ++ std::int64_t minimumExcess=std::numeric_limits::max(); ++ for(const auto& span:spans) ++ { ++ const PassageSpline::FPoint center={ ++ 0.5*(span.segment.a[0]+span.segment.b[0]), ++ 0.5*(span.segment.a[1]+span.segment.b[1]), ++ 0.5*(span.segment.a[2]+span.segment.b[2]), ++ 0.5*(span.segment.a[3]+span.segment.b[3])}; ++ const std::int64_t density=field.densitySubvoxels(RoundQ(center[0]), ++ RoundQ(center[1]),RoundQ(center[2])); ++ const std::int64_t required=RoundQ(center[3])+influenceReach ++ +capsuleReach+sampleGuard; ++ const std::int64_t excess=density-required; ++ minimumExcess=std::min(minimumExcess,excess); ++ haveClearance=true; ++ ++result.passageSamples; ++ if(excess<0)++result.blockedIslandSamples; ++ ++ for(std::size_t routeSegment=0;routeSegment+1route.size();++routeSegment) ++ { ++ const auto& routeA=field.plan()->route[routeSegment].positionSubvoxels; ++ const auto& routeB=field.plan()->route[routeSegment+1].positionSubvoxels; ++ PassageSpline::FPoint a{},b{}; ++ for(std::size_t axis=0;axis<3;++axis) ++ {a[axis]=static_cast(routeA[axis]);b[axis]=static_cast(routeB[axis]);} ++ const double distance=std::sqrt(SegmentDistanceSquared3D(a,b, ++ span.segment.a,span.segment.b)); ++ const double requiredDistance=center[3]+influenceReach ++ +capsuleReach+sampleGuard; ++ ++result.routeSamples; ++ if(distancestrateIndex) ++ departure=&passage; ++ } ++ const auto mouthSupport=[&](const std::array& mouth) ++ { ++ constexpr std::array,5> probes={{{0.0,0.0}, ++ {1.0,0.0},{-1.0,0.0},{0.0,1.0},{0.0,-1.0}}}; ++ const double radius=static_cast(recipe.routeConstraints.supportProbeRadiusSubvoxels); ++ const std::int64_t foot=mouth[2]-recipe.routeConstraints.capsuleHeightSubvoxels/2; ++ const std::int64_t sampleZ=foot-recipe.routeConstraints.minimumClearanceSubvoxels-Q/2; ++ std::uint32_t supported=0; ++ for(const auto& probe:probes) ++ { ++ const auto x=RoundQ(static_cast(mouth[0])+probe[0]*radius); ++ const auto y=RoundQ(static_cast(mouth[1])+probe[1]*radius); ++ if(field.densitySubvoxels(x,y,sampleZ)<0)++supported; ++ } ++ return supported; ++ }; ++ bool bound=false; ++ if(arrival&&departure) ++ { ++ const auto& route=field.plan()->route; ++ const auto close=[](const auto& a,const auto& b) ++ { ++ for(std::size_t axis=0;axis<3;++axis) ++ if(std::abs(a[axis]-b[axis])>2)return false; ++ return true; ++ }; ++ bound=close(route.front().positionSubvoxels,arrival->lowerMouthSubvoxels) ++ &&close(route.back().positionSubvoxels,departure->upperMouthSubvoxels); ++ result.arrivalMouthSupportProbes=mouthSupport(arrival->lowerMouthSubvoxels); ++ result.departureMouthSupportProbes=mouthSupport(departure->upperMouthSubvoxels); ++ } ++ result.passed=bound&&haveClearance&&result.blockedIslandSamples==0 ++ &&result.blockedRouteSamples==0 ++ &&result.arrivalMouthSupportProbes>=recipe.routeConstraints.minimumSupportProbes ++ &&result.departureMouthSupportProbes>=recipe.routeConstraints.minimumSupportProbes; ++ return result; ++} ++ + FloatingIslandThicknessAuditResult AuditFloatingIslandThickness( + const FloatingIslandPlanV1& plan, double minimumDepthToDiameterRatio) + { +diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h +index 1a57f85..98dabf5 100644 +--- a/Standalone/Core/src/ShapeWorldInternal.h ++++ b/Standalone/Core/src/ShapeWorldInternal.h +@@ -1754,9 +1754,81 @@ public: + ?passageAirDistance:density); + if(nativeTunnelFieldDensity!=std::numeric_limits::lowest()) + return std::max(density,passageAirDistance); ++ const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* landingPlan=nullptr; ++ if(WorldAssembly&&queryStratum&&queryStratum->Resolved ++ &&queryStratum->Resolved->generatorKind=="FloatingIslands") ++ { ++ const auto island=std::find_if(WorldAssembly->floatingIslands.begin(), ++ WorldAssembly->floatingIslands.end(),[&](const auto& candidate) ++ {return candidate.strateIndex==queryStratum->StrateIndex;}); ++ if(island!=WorldAssembly->floatingIslands.end())landingPlan=&*island; ++ } ++ const auto preserveFloatingIslandLandingSupport=[&](float composedDensity) ++ { ++ if(!landingPlan||landingPlan->manifest.recipe.geometryVersion<10) ++ return composedDensity; ++ const auto& constraints=landingPlan->manifest.recipe.routeConstraints; ++ if(constraints.version!=1)return composedDensity; ++ const auto* routeField=std::get_if( ++ &queryStratum->Evaluator); ++ const auto& route=landingPlan->manifest.plan.route; ++ if(!routeField||route.size()<2)return composedDensity; ++ ++ constexpr std::size_t LandingRouteSegments=18; ++ const double supportRadiusMetres=std::min(0.45, ++ constraints.supportProbeRadiusSubvoxels/qPerMetre+0.10); ++ const double queryX=qx,queryY=qy,queryZ=qz; ++ for(std::size_t segment=0;segment+1=LandingRouteSegments ++ &&segment+LandingRouteSegments(b[0]-a[0]); ++ const double dy=static_cast(b[1]-a[1]); ++ const double dz=static_cast(b[2]-a[2]); ++ const double horizontalSquared=dx*dx+dy*dy; ++ if(horizontalSquared<=1.0e-12)continue; ++ const double t=std::clamp(((queryX-a[0])*dx+(queryY-a[1])*dy) ++ /horizontalSquared,0.0,1.0); ++ const double routeX=a[0]+t*dx,routeY=a[1]+t*dy; ++ if(std::hypot(queryX-routeX,queryY-routeY) ++ >supportRadiusMetres*qPerMetre) ++ continue; ++ const double routeCenterZ=a[2]+t*dz; ++ const double footZ=routeCenterZ ++ -0.5*constraints.capsuleHeightSubvoxels; ++ const double shallowDepth=constraints.minimumClearanceSubvoxels+Q/2.0; ++ const double deepDepth=constraints.minimumClearanceSubvoxels ++ +constraints.supportSearchDepthSubvoxels; ++ if(queryZ<=footZ-shallowDepth&&queryZ>=footZ-deepDepth) ++ { ++ const std::int64_t nativeDensity=routeField->densitySubvoxelsAt( ++ static_cast(std::llround(queryX)), ++ static_cast(std::llround(queryY)), ++ static_cast(std::llround(queryZ))); ++ if(nativeDensity<0) ++ return std::min(composedDensity, ++ static_cast(static_cast(nativeDensity)/Q)); ++ } ++ } ++ return composedDensity; ++ }; + if(unionDistance>0.0&&unionShellDistance<0.0&&density>=0.0f) + return static_cast(unionShellDistance/Q); +- return std::max(density,passageAirDistance); ++ float composedDensity=std::max(density,passageAirDistance); ++ if(landingPlan&&landingPlan->manifest.recipe.geometryVersion>=10 ++ &&density>=0.0f&&unionDistance<0.0&&unionShellDistance>0.0) ++ { ++ // In open-air parts of the island stratum, the boolean air union saturates ++ // the passage's positive interior distance to the farther island-air ++ // distance. Keep the nearest positive bore/shell distance so the explicit ++ // passage shell remains measurable without changing any field signs. ++ composedDensity=std::min(composedDensity, ++ std::min(passageAirDistance, ++ static_cast(unionShellDistance/Q))); ++ } ++ return preserveFloatingIslandLandingSupport(composedDensity); + } + + float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const +@@ -7217,9 +7289,11 @@ private: + Density=filteredDensity; + if(routeNativeBlend>0.0&&NativeDensity<0) + { ++ const double routePreservation=Plan->manifest.recipe.geometryVersion>=10 ++ ?routeNativeBlend:routeNativeBlend*0.60; + const double routePreserved=std::lerp(static_cast(Density), + static_cast(std::min(Density,NativeDensity)), +- routeNativeBlend*0.60); ++ routePreservation); + Density=static_cast(std::llround(routePreserved)); + } + // Marching Cubes can retain an isolated solid with no two-metre axis +diff --git a/Standalone/Source/StandaloneViewer.cpp b/Standalone/Source/StandaloneViewer.cpp +index 1c0ed4c..80ad688 100644 +--- a/Standalone/Source/StandaloneViewer.cpp ++++ b/Standalone/Source/StandaloneViewer.cpp +@@ -9208,11 +9208,15 @@ int main(int Argc,char** Argv) + { + const auto PlanStart=std::chrono::steady_clock::now(); + VoxelForgeCore::WorldAssemblyManifest WorldAssembly; ++ VoxelForgeCore::ResolvedLayoutSnapshotV3 ResolvedLayout; + const bool bPlanReady=StandaloneWorldAssemblyPrototype::AttachRound1WorldAssemblyPrototype(*App.World,App.OwnerJsonRoot, + Error,WorldAssemblyManifestOutputPath,&WorldAssembly) + &&(!bFloatingIslandsRecipe + ||StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly( +- *App.World,App.OwnerJsonRoot,WorldAssembly,Error)); ++ *App.World,App.OwnerJsonRoot,WorldAssembly,Error)) ++ &&(!bFloatingIslandsRecipe ++ ||StandaloneWorldAssemblyPrototype::BuildResolvedLayoutSnapshot( ++ *App.World,WorldAssembly,"floating-islands",ResolvedLayout,Error)); + const double PlanMilliseconds=std::chrono::duration( + std::chrono::steady_clock::now()-PlanStart).count(); + std::printf("[WorldAssemblerPlanTiming] total_ms=%.3f status=%s\n", +diff --git a/Standalone/Source/StreamBenchmarkHeadless.cpp b/Standalone/Source/StreamBenchmarkHeadless.cpp +index 1afcff5..f22afe9 100644 +--- a/Standalone/Source/StreamBenchmarkHeadless.cpp ++++ b/Standalone/Source/StreamBenchmarkHeadless.cpp +@@ -1830,31 +1830,11 @@ int main(int argc,char** argv) + else if(!StandaloneWorldAssemblyPrototype::AttachRound1WorldAssemblyPrototype( + *ownerWorld,ownerJsonRoot,error,{},&worldAssembly)) + {std::fprintf(stderr,"World assembler prototype setup failed: %s\n",error.c_str());return 2;} +- if(((!useExportedCorePlan&&options.FloatingIslandsRecipe) +- ||(options.NativeOwnerFloatingIslandsRecipe +- &&worldAssembly.floatingIslands.empty())) ++ if(!useExportedCorePlan&&options.FloatingIslandsRecipe ++ &&!options.NativeOwnerFloatingIslandsRecipe + &&!StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly( + *ownerWorld,ownerJsonRoot,worldAssembly,error)) + {std::fprintf(stderr,"Floating-island assembly setup failed: %s\n",error.c_str());return 2;} +- if(!options.WorldAssemblyManifestOutputPath.empty()) +- { +- const std::string manifestText=worldAssembly.ToJson(); +- const auto& outputPath=options.WorldAssemblyManifestOutputPath; +- std::error_code directoryError; +- if(!outputPath.parent_path().empty()) +- std::filesystem::create_directories(outputPath.parent_path(),directoryError); +- if(directoryError) +- {std::fprintf(stderr,"Could not create the complete world assembly manifest output directory.\n");return 2;} +- std::ofstream output(outputPath,std::ios::binary); +- if(!output) +- {std::fprintf(stderr,"Could not open the complete world assembly manifest output path.\n");return 2;} +- output.write(manifestText.data(),static_cast(manifestText.size())); +- if(!output) +- {std::fprintf(stderr,"Could not write the complete world assembly manifest payload.\n");return 2;} +- std::printf("[WorldAssemblerFullManifest] bytes=%zu surface_strates=%zu floating_islands=%zu path=%s status=PASS\n", +- manifestText.size(),worldAssembly.plan.strates.size(),worldAssembly.floatingIslands.size(), +- outputPath.string().c_str()); +- } + if (options.AuditHeightfieldProfile && !RunHeightfieldProfileAudit(worldAssembly)) + { + std::fprintf(stderr,"Profiled SurfaceWorld interval, classification or route audit failed.\n"); +@@ -1886,6 +1866,31 @@ int main(int argc,char** argv) + &&!StandaloneWorldAssemblyPrototype::ValidateExportedCorePlanMatchesOwner( + *ownerWorld,worldAssembly,resolvedLayout,error)) + {std::fprintf(stderr,"Unreal-resolved Core plan/owner slot validation failed: %s\n",error.c_str());return 2;} ++ if(options.NativeOwnerFloatingIslandsRecipe ++ &&worldAssembly.floatingIslands.empty() ++ &&!StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly( ++ *ownerWorld,ownerJsonRoot,worldAssembly,resolvedLayout,error, ++ true)) ++ {std::fprintf(stderr,"Floating-island assembly setup failed: %s\n",error.c_str());return 2;} ++ if(!options.WorldAssemblyManifestOutputPath.empty()) ++ { ++ const std::string manifestText=worldAssembly.ToJson(); ++ const auto& outputPath=options.WorldAssemblyManifestOutputPath; ++ std::error_code directoryError; ++ if(!outputPath.parent_path().empty()) ++ std::filesystem::create_directories(outputPath.parent_path(),directoryError); ++ if(directoryError) ++ {std::fprintf(stderr,"Could not create the complete world assembly manifest output directory.\n");return 2;} ++ std::ofstream output(outputPath,std::ios::binary); ++ if(!output) ++ {std::fprintf(stderr,"Could not open the complete world assembly manifest output path.\n");return 2;} ++ output.write(manifestText.data(),static_cast(manifestText.size())); ++ if(!output) ++ {std::fprintf(stderr,"Could not write the complete world assembly manifest payload.\n");return 2;} ++ std::printf("[WorldAssemblerFullManifest] bytes=%zu surface_strates=%zu floating_islands=%zu path=%s status=PASS\n", ++ manifestText.size(),worldAssembly.plan.strates.size(),worldAssembly.floatingIslands.size(), ++ outputPath.string().c_str()); ++ } + if(!std::holds_alternative(ownerJsonRoot.Get("motif_family_override").Data)) + { + std::vector requests; +diff --git a/Standalone/Source/WorldAssemblyPrototype.h b/Standalone/Source/WorldAssemblyPrototype.h +index 4e20068..70c4c61 100644 +--- a/Standalone/Source/WorldAssemblyPrototype.h ++++ b/Standalone/Source/WorldAssemblyPrototype.h +@@ -1366,7 +1366,9 @@ namespace StandaloneWorldAssemblyPrototype + } + + inline bool AttachFloatingIslandWorldAssembly(FStandaloneOwnerWorld& owner, +- VFJson::Value& root,VoxelForgeCore::WorldAssemblyManifest& assembly,std::string& error) ++ VFJson::Value& root,VoxelForgeCore::WorldAssemblyManifest& assembly, ++ VoxelForgeCore::ResolvedLayoutSnapshotV3& resolvedLayout, ++ std::string& error,bool planAroundPassages=false) + { + if(!owner.Manager) + {error="floating-island assembly needs the resolved standalone strate manager";return false;} +@@ -1386,7 +1388,8 @@ namespace StandaloneWorldAssemblyPrototype + FIntVector(0,0,islandSlot->TopChunkZ)); + const FStratePassageConfig& passage=islandSlot->Definition->PassageConfig; + recipe.seed=static_cast(owner.Seed); +- recipe.geometryVersion=8; ++ recipe.geometryVersion=planAroundPassages?10:8; ++ if(planAroundPassages)recipe.primaryIslandCount=9; + recipe.terrainFollowingRoute=static_cast(passage.Style)==0; + recipe.ownerVoxelSizeMicrons=owner.OwnerVoxelSizeMicrons; + recipe.coreVoxelSizeMicrons=owner.CoreVoxelSizeMicrons; +@@ -1406,43 +1409,79 @@ namespace StandaloneWorldAssemblyPrototype + recipe.islandDensityQ16=WorldAssemblyQ16(std::clamp(params.IslandDensity,0.0,1.0)); + recipe.spacingSubvoxels=WorldAssemblyFixed(params.IslandSpacing*ownerToCoreScale); + recipe.minimumRadiusSubvoxels=WorldAssemblyFixed(params.IslandMinRadius*ownerToCoreScale); +- recipe.maximumRadiusSubvoxels=WorldAssemblyFixed(params.IslandMaxRadius*ownerToCoreScale); ++ recipe.maximumRadiusSubvoxels=WorldAssemblyFixed((planAroundPassages ++ ?28.0:params.IslandMaxRadius)*ownerToCoreScale); + recipe.thicknessRatioQ16=WorldAssemblyQ16(params.ThicknessRatio); + recipe.verticalJitterQ16=WorldAssemblyQ16(params.VerticalJitter); + recipe.topFlattenQ16=WorldAssemblyQ16(params.TopFlatten); +- recipe.blendRadiusSubvoxels=WorldAssemblyFixed(params.SDFBlendRadius*ownerToCoreScale); ++ recipe.blendRadiusSubvoxels=planAroundPassages ++ ?2*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel ++ :WorldAssemblyFixed(params.SDFBlendRadius*ownerToCoreScale); ++ if(planAroundPassages) ++ recipe.contourWarpSubvoxels=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; + recipe.roughnessSubvoxels=WorldAssemblyFixed(params.SurfaceRoughness*ownerToCoreScale); ++ if(planAroundPassages) ++ recipe.roughnessSubvoxels=std::min(recipe.roughnessSubvoxels, ++ VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel/2); + recipe.boundarySealSubvoxels=WorldAssemblyFixed(params.BoundarySealThickness*ownerToCoreScale); ++ if(planAroundPassages) ++ { ++ recipe.routeConstraints.version=1; ++ recipe.routeConstraints.capsuleRadiusSubvoxels=348; ++ recipe.routeConstraints.capsuleHeightSubvoxels=1802; ++ recipe.routeConstraints.maximumGradeQ16=17560; ++ recipe.routeConstraints.sweepSpacingSubvoxels=128; ++ recipe.routeConstraints.supportProbeRadiusSubvoxels=230; ++ recipe.routeConstraints.supportSearchDepthSubvoxels=512; ++ recipe.routeConstraints.minimumClearanceSubvoxels=128; ++ recipe.routeConstraints.minimumSupportProbes=3; ++ } + const auto ownerVoxelsToCoreQ=[&](double value) + {return static_cast(std::llround(value*owner.OwnerVoxelSizeMicrons + *VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel/owner.CoreVoxelSizeMicrons));}; + recipe.anchorIslandOrdinal=recipe.primaryIslandCount-1; + recipe.anchorPlacementRadiusSubvoxels=ownerVoxelsToCoreQ(passage.DistanceMax); +- const std::int64_t spiralRadius=ownerVoxelsToCoreQ(passage.SpiralRadius); +- const std::int64_t mouthBore=std::max(ownerVoxelsToCoreQ(passage.MouthRadius), +- ownerVoxelsToCoreQ(36.0)); +- const std::int64_t bore=std::max(ownerVoxelsToCoreQ(passage.MidRadius), +- ownerVoxelsToCoreQ(24.0)); +- const std::int64_t wallReserve=ownerVoxelsToCoreQ(20.0); +- // Retain a 4.5-metre mouth for capsule clearance while keeping the long +- // ramp at three metres to leave more rock around the first coil. +- recipe.anchorMouthOffsetSubvoxels=ownerVoxelsToCoreQ(40.0); +- const std::int64_t supportOverhang=ownerVoxelsToCoreQ(16.0); +- const std::int64_t rootTaperReserve=ownerVoxelsToCoreQ(10.0); +- const std::int64_t mouthEnvelope=recipe.anchorMouthOffsetSubvoxels+mouthBore; +- const std::int64_t treadEnvelope=spiralRadius+bore+supportOverhang; +- recipe.anchorRootRadiusSubvoxels=std::max(mouthEnvelope,treadEnvelope) +- +wallReserve+rootTaperReserve; +- recipe.anchorIslandMinimumRadiusSubvoxels=std::max(recipe.minimumRadiusSubvoxels, +- std::max(recipe.anchorMouthOffsetSubvoxels+mouthBore+ownerVoxelsToCoreQ(4.0), +- recipe.anchorRootRadiusSubvoxels+wallReserve)); ++ if(planAroundPassages) ++ { ++ recipe.anchorMouthOffsetSubvoxels=ownerVoxelsToCoreQ(4.0); ++ recipe.anchorRootRadiusSubvoxels=std::max(ownerVoxelsToCoreQ(16.0), ++ recipe.anchorMouthOffsetSubvoxels+recipe.routeConstraints.capsuleRadiusSubvoxels ++ +VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); ++ recipe.anchorIslandMinimumRadiusSubvoxels=std::max(recipe.maximumRadiusSubvoxels, ++ recipe.anchorMouthOffsetSubvoxels+recipe.routeConstraints.capsuleRadiusSubvoxels ++ +VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); ++ } ++ else ++ { ++ const std::int64_t spiralRadius=ownerVoxelsToCoreQ(passage.SpiralRadius); ++ const std::int64_t mouthBore=std::max(ownerVoxelsToCoreQ(passage.MouthRadius), ++ ownerVoxelsToCoreQ(36.0)); ++ const std::int64_t bore=std::max(ownerVoxelsToCoreQ(passage.MidRadius), ++ ownerVoxelsToCoreQ(24.0)); ++ const std::int64_t wallReserve=ownerVoxelsToCoreQ(20.0); ++ recipe.anchorMouthOffsetSubvoxels=ownerVoxelsToCoreQ(40.0); ++ const std::int64_t supportOverhang=ownerVoxelsToCoreQ(16.0); ++ const std::int64_t rootTaperReserve=ownerVoxelsToCoreQ(10.0); ++ const std::int64_t mouthEnvelope=recipe.anchorMouthOffsetSubvoxels+mouthBore; ++ const std::int64_t treadEnvelope=spiralRadius+bore+supportOverhang; ++ recipe.anchorRootRadiusSubvoxels=std::max(mouthEnvelope,treadEnvelope) ++ +wallReserve+rootTaperReserve; ++ recipe.anchorIslandMinimumRadiusSubvoxels=std::max(recipe.minimumRadiusSubvoxels, ++ std::max(recipe.anchorMouthOffsetSubvoxels+mouthBore+ownerVoxelsToCoreQ(4.0), ++ recipe.anchorRootRadiusSubvoxels+wallReserve)); ++ } + recipe.anchorRootBottomZSubvoxels=recipe.layoutMinimumZSubvoxels + +recipe.boundarySealSubvoxels; + +- auto planned=VoxelForgeCore::PlanFloatingIslandCluster(recipe,error); ++ auto planned=planAroundPassages ++ ?VoxelForgeCore::PlanFloatingIslandClusterAroundPassages( ++ recipe,resolvedLayout,islandSlot->StrateIndex,error) ++ :VoxelForgeCore::PlanFloatingIslandCluster(recipe,error); + if(!planned)return false; + const VoxelForgeCore::FloatingIslandFieldV1 routeField( + planned->recipe,planned->plan); ++ const double metresPerSubvoxel=static_cast(owner.CoreVoxelSizeMicrons) ++ /(1000000.0*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); + const auto routeAudit=VoxelForgeCore::AuditFloatingIslandRoute(routeField); + std::printf("[FloatingIslandRouteAudit] points=%zu centers=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu status=%s\n", + planned->plan.route.size(), +@@ -1453,8 +1492,27 @@ namespace StandaloneWorldAssemblyPrototype + static_cast(routeAudit.unsupportedCenterSamples), + static_cast(routeAudit.gradeFailures), + routeAudit.passed?"PASS":"FAIL"); +- const double metresPerSubvoxel=static_cast(owner.CoreVoxelSizeMicrons) +- /(1000000.0*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); ++ VoxelForgeCore::FloatingIslandPassageClearanceAuditResult passageAudit; ++ if(planAroundPassages) ++ { ++ constexpr double PortalExclusionMetres=13.0; ++ passageAudit=VoxelForgeCore::AuditFloatingIslandPassageClearance( ++ routeField,resolvedLayout,islandSlot->StrateIndex,PortalExclusionMetres); ++ std::printf("[FloatingIslandPassageClearance] strate=%d tube_samples=%llu island_blocked=%llu route_checks=%llu route_blocked=%llu arrival_support=%u/5 departure_support=%u/5 clearance_excess_m=%.3f portal_exclusion_m=%.1f influence_reach_m=1.053 capsule_bound_m=%.3f status=%s\n", ++ islandSlot->StrateIndex, ++ static_cast(passageAudit.passageSamples), ++ static_cast(passageAudit.blockedIslandSamples), ++ static_cast(passageAudit.routeSamples), ++ static_cast(passageAudit.blockedRouteSamples), ++ passageAudit.arrivalMouthSupportProbes, ++ passageAudit.departureMouthSupportProbes, ++ passageAudit.minimumExcessClearanceSubvoxels*metresPerSubvoxel, ++ passageAudit.portalExclusionMetres, ++ planned->recipe.routeConstraints.capsuleHeightSubvoxels*metresPerSubvoxel*0.5, ++ passageAudit.passed?"PASS":"FAIL"); ++ if(!routeAudit.passed||!passageAudit.passed) ++ {error="corridor-aware floating-island route or passage audit failed";return false;} ++ } + const double bedrockTopMetres=(minimumZ+recipe.boundarySealSubvoxels)*metresPerSubvoxel; + const double ceilingBottomMetres=(maximumZ-recipe.boundarySealSubvoxels)*metresPerSubvoxel; + for(const auto& island:planned->plan.islands) +@@ -1473,18 +1531,82 @@ namespace StandaloneWorldAssemblyPrototype + assembly.floatingIslands.push_back(std::move(islands)); + if(!assembly.finalize(error))return false; + ++ if(planAroundPassages) ++ { ++ auto resolvedStratum=std::find_if(resolvedLayout.strates.begin(), ++ resolvedLayout.strates.end(),[&](const auto& item) ++ {return item.strateIndex==islandSlot->StrateIndex;}); ++ if(resolvedStratum==resolvedLayout.strates.end()) ++ {error="resolved FloatingIslands stratum disappeared before native anchor attachment";return false;} ++ resolvedStratum->recipeVersion=assembly.floatingIslands.back().manifest.recipe.geometryVersion; ++ resolvedStratum->anchors.erase(std::remove_if(resolvedStratum->anchors.begin(), ++ resolvedStratum->anchors.end(),[](const auto& anchor) ++ {return anchor.kind=="island_anchor"||anchor.kind=="passage_anchor" ++ ||anchor.kind=="route_point";}),resolvedStratum->anchors.end()); ++ const auto& nativePlan=assembly.floatingIslands.back().manifest.plan; ++ const auto& nativeRecipe=assembly.floatingIslands.back().manifest.recipe; ++ for(std::size_t n=0;nanchors.push_back(std::move(anchor)); ++ if(n==nativeRecipe.anchorIslandOrdinal) ++ { ++ VoxelForgeCore::ResolvedLayoutAnchorV2 passageAnchor; ++ passageAnchor.kind="passage_anchor"; ++ passageAnchor.stableId="passage-anchor/island/"+std::to_string(n); ++ passageAnchor.units="1/256 Core voxel"; ++ passageAnchor.values={island.centerSubvoxels[0],island.centerSubvoxels[1], ++ nativeRecipe.anchorRootRadiusSubvoxels, ++ nativeRecipe.anchorMouthOffsetSubvoxels, ++ nativeRecipe.anchorPlacementRadiusSubvoxels, ++ nativeRecipe.anchorRootBottomZSubvoxels,island.radiusSubvoxels}; ++ if(!nativePlan.route.empty()) ++ { ++ const auto& mouth=nativePlan.route.back().positionSubvoxels; ++ passageAnchor.values.insert(passageAnchor.values.end(), ++ {mouth[0],mouth[1],mouth[2]}); ++ } ++ resolvedStratum->anchors.push_back(std::move(passageAnchor)); ++ } ++ } ++ for(std::size_t n=0;nanchors.push_back(std::move(anchor)); ++ } ++ if(!resolvedLayout.finalize(error))return false; ++ } ++ + VFJson::Value assemblyJson; + if(!VFJson::Parser(assembly.ToJson()).Parse(assemblyJson,error))return false; + auto* rootObject=std::get_if(&root.Data); + if(!rootObject){error="owner snapshot root is not a JSON object";return false;} + (*rootObject)["world_assembly"]=std::move(assemblyJson); +- std::printf("[FloatingIslandWorldAssembly] seed=%d strate_index=%d chunks=%d..%d island_count=%zu recipe_hash=%016llx status=PASS\n", ++ std::printf("[FloatingIslandWorldAssembly] seed=%d strate_index=%d chunks=%d..%d island_count=%zu dispatch=FloatingIslands-native recipe_hash=%016llx status=PASS\n", + owner.Seed,islandSlot->StrateIndex,islandSlot->TopChunkZ,islandSlot->BottomChunkZ, + assembly.floatingIslands.back().manifest.plan.islands.size(), + static_cast(assembly.floatingIslands.back().manifest.recipe.contentHash)); + return true; + } + ++ inline bool AttachFloatingIslandWorldAssembly(FStandaloneOwnerWorld& owner, ++ VFJson::Value& root,VoxelForgeCore::WorldAssemblyManifest& assembly, ++ std::string& error) ++ { ++ VoxelForgeCore::ResolvedLayoutSnapshotV3 unusedLayout; ++ return AttachFloatingIslandWorldAssembly(owner,root,assembly,unusedLayout,error,false); ++ } ++ + inline const char* GeneratorKind(ECaveGeneratorType type) + { + switch(type) +@@ -1964,6 +2086,7 @@ namespace StandaloneWorldAssemblyPrototype + :it->manifest.plan.route.size()-1; + std::size_t resolvedRoutePointCount=it->manifest.plan.route.size(); + if(it->manifest.recipe.geometryVersion>=5 ++ &&it->manifest.recipe.geometryVersion<10 + &&!it->manifest.plan.route.empty() + &&it->manifest.recipe.anchorIslandOrdinalmanifest.plan.islands.size()) + {