captures: readable player-view captures with a coverage gate (L69); R36 native-generator survey

- Capture exports now cover a 90-degree player frustum (radius 3, LOD 0-8, overlap patches);
  a 17x9 ray coverage probe aborts before rendering if tiles are missing (truncated-mesh
  control: 480 uncovered samples, rejected). Clear colour is sky blue, so un-meshed space can't
  pass as dark rock. 16 views of the owner's four-strate plan.
- Explore commandlet: finite XYZ spawn override for traces (Source/, Linux-compiled).
- In-engine four-strate trace NOT obtained: the engine-generated plan fails passage validation
  at a solid centerline voxel (~(-43,-34.5,-448.5) m) while the headless owner_world.json
  plan passes. Engine and headless plans diverge.
- R36 (read-only): order TN -> VS -> FI for native migration; native FI is reachable from the
  adapter but not selected in the owner run, cause unknown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-27 04:00:49 +01:00
1 parent bd7bba994c
commit 81e2452a3d
24 files changed
+1011 -5

No files matched your search

@@ -17982,6 +17982,25 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
OutError = TEXT("Could not create the BuildHost stream-trace pawn.");
return false;
}
FVector StartupTraceSpawnCm = FVector::ZeroVector;
FString StartupTraceSpawnText;
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StartupTraceSpawnCm="),
StartupTraceSpawnText, false))
{
TArray<FString> Components;
StartupTraceSpawnText.ParseIntoArray(Components, TEXT(","), true);
if (Components.Num() != 3
|| !LexTryParseString(StartupTraceSpawnCm.X, *Components[0])
|| !LexTryParseString(StartupTraceSpawnCm.Y, *Components[1])
|| !LexTryParseString(StartupTraceSpawnCm.Z, *Components[2])
|| !FMath::IsFinite(StartupTraceSpawnCm.X)
|| !FMath::IsFinite(StartupTraceSpawnCm.Y)
|| !FMath::IsFinite(StartupTraceSpawnCm.Z))
{
OutError = TEXT("voxel.StartupTraceSpawnCm must be finite X,Y,Z centimetres.");
return false;
}
}
USceneComponent* PawnRoot = NewObject<USceneComponent>(PlayerPawn, NAME_None, RF_Transient);
if (PawnRoot == nullptr)
{
@@ -17990,7 +18009,7 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
}
PlayerPawn->SetRootComponent(PawnRoot);
PawnRoot->RegisterComponent();
PlayerPawn->SetActorLocation(FVector::ZeroVector, false, nullptr, ETeleportType::TeleportPhysics);
PlayerPawn->SetActorLocation(StartupTraceSpawnCm, false, nullptr, ETeleportType::TeleportPhysics);
PlayerController->Possess(PlayerPawn);
if (TestWorld->GetFirstPlayerController() != PlayerController
|| PlayerController->GetPawn() != PlayerPawn)
@@ -18029,6 +18048,11 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
return false;
}
Actor->SetActorTickEnabled(true);
VoxelForgeStartupTrace::RecordEvent(TEXT("trace_spawn_location"), FString::Printf(
TEXT("\"location_cm\":[%s,%s,%s]"),
*FString::SanitizeFloat(StartupTraceSpawnCm.X),
*FString::SanitizeFloat(StartupTraceSpawnCm.Y),
*FString::SanitizeFloat(StartupTraceSpawnCm.Z)));
auto DesiredTilesLoaded = [](const AVoxelWorld* World)
{
@@ -0,0 +1,725 @@
#!/usr/bin/env python3
"""Render arrival, departure, and mid-tunnel views for the four-strate owner plan."""
import json
import importlib.util
import argparse
import math
import os
import re
import struct
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l69"
BIN = OUT / "bin"
CAPTURES = Path(__file__).resolve().parent / "captures"
HEADLESS = OUT / "bin/VoxelForgeCoreHeadless-l69"
RENDERER = BIN / "VoxelForgeOffscreenCapture-l69"
PNG_DIR = OUT / "capture-png"
RECIPE_INPUTS = (
("passage-0-1", ROOT / "Standalone/data/owner_world.json",
"surface-world", "SurfaceWorld", "upper"),
("passage-1-2", ROOT / "Standalone/data/owner_world.json",
"surface-world", "FloatingIslands", "upper"),
("passage-2-3", ROOT / "Standalone/data/owner_world.json",
"surface-world", "TunnelNetwork", "upper"),
)
SUBVOXELS_PER_VOXEL = 256.0
RING_AUDIT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
spec = importlib.util.spec_from_file_location("ring_profile_l32", RING_AUDIT)
ring_profile = importlib.util.module_from_spec(spec)
spec.loader.exec_module(ring_profile)
for directory in (OUT, BIN, PNG_DIR, CAPTURES):
directory.mkdir(parents=True, exist_ok=True)
ENV = os.environ.copy()
ENV["LP_NUM_THREADS"] = "12"
ENV["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
PARSER = argparse.ArgumentParser(description=__doc__)
PARSER.add_argument("--scene", choices=[value[0] for value in RECIPE_INPUTS])
PARSER.add_argument("--view", choices=("arrival-mouth", "departure-mouth", "mid-tunnel"))
OPTIONS = PARSER.parse_args()
def run(name, command):
log = OUT / f"capture-{name}.log"
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", *map(str, command)]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, env=ENV, stdout=stream,
stderr=subprocess.STDOUT, timeout=1740, check=False)
elapsed = time.monotonic() - started
print(f"[L69CaptureRun] step={name} elapsed={elapsed:.1f}s exit={result.returncode} log={log}",
flush=True)
if result.returncode != 0:
raise SystemExit(f"{name} failed; see {log}")
return log
def add(a, b):
return tuple(x + y for x, y in zip(a, b))
def sub(a, b):
return tuple(x - y for x, y in zip(a, b))
def mul(a, scale):
return tuple(x * scale for x in a)
def norm(a):
length = math.sqrt(sum(x * x for x in a))
if length <= 1.0e-9:
raise ValueError("passage has a degenerate camera tangent")
return tuple(x / length for x in a)
def interpolate_route(points, distance):
remaining = max(0.0, distance)
for a, b in zip(points, points[1:]):
delta = sub(b[0], a[0])
length = math.sqrt(sum(x * x for x in delta))
if length <= 1.0e-9:
continue
if remaining <= length:
t = remaining / length
pos = tuple(a[0][axis] + delta[axis] * t for axis in range(3))
radius = a[1] + (b[1] - a[1]) * t
return pos, radius
remaining -= length
return points[-1]
def route_frame(points, distance):
center, radius = interpolate_route(points, distance)
before, _ = interpolate_route(points, max(0.0, distance - 0.25))
after, _ = interpolate_route(points, distance + 0.25)
tangent = norm(sub(after, before))
horizontal = norm((tangent[0], tangent[1], 0.0))
side = (-horizontal[1], horizontal[0], 0.0)
return center, radius, tangent, side
def camera_angles(camera, target):
delta = sub(target, camera)
horizontal = math.hypot(delta[0], delta[1])
yaw = math.degrees(math.atan2(delta[0], delta[1]))
pitch = math.degrees(math.atan2(delta[2], horizontal))
return yaw, pitch
def fmt(values):
return ",".join(f"{value:.6f}" for value in values)
def load_reference_cameras():
log = ROOT / "Saved/Linux/l34/run-capture-standard-set-final-radius3.log"
pattern = re.compile(
r"^\[L34CaptureCamera\] view=(\S+).*?camera_m=\(([^)]*)\) "
r"target_m=\(([^)]*)\) clip=(\S+)", re.M)
values = {}
for name, camera, target, clip in pattern.findall(
log.read_text(encoding="utf-8", errors="replace")):
parse = lambda value: tuple(float(part) for part in value.split(","))
values[name] = (parse(camera), parse(target), None if clip == "none" else parse(clip))
return values
REFERENCE_CAMERAS = {}
def terrain_surface_near(mesh, x, y, expected_height):
bounds = (x - 2.0, x + 2.0, y - 2.0, y + 2.0)
_, triangles, _ = ring_profile.read_scene(
str(mesh), None, -1.0e9, 1.0e9, bounds)
buckets = ring_profile.build_buckets(triangles, True, bounds, cell_size=1.0)
hits = []
for dy in range(-4, 5):
for dx in range(-4, 5):
sample_x, sample_y = x + dx * 0.5, y + dy * 0.5
hit = ring_profile.sample_surface(buckets, sample_x, sample_y, True, 1.0,
preferred_height=expected_height)
if hit is not None:
hits.append((math.hypot(dx * 0.5, dy * 0.5),
abs(hit[0] - expected_height), sample_x, sample_y, hit[0]))
if not hits:
return None
distance, _, sample_x, sample_y, height = min(hits)
if distance > 2.0 or abs(height - expected_height) > 8.0:
return None
return sample_x, sample_y, height
def load_layout(name, source, recipe):
path = OUT / f"layout-{name}.json"
run(f"layout-{name}", [HEADLESS, source, "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=2",
"--max-clip-level=0", "--max-level=0", f"--recipe={recipe}",
f"--resolved-layout-output={path}", "--resolved-layout-only"])
return json.loads(path.read_text(encoding="utf-8"))
def q_to_meters(layout):
return layout["core_voxel_size_um"] / (SUBVOXELS_PER_VOXEL * 1_000_000.0)
def q_position(value, scale):
return tuple(float(component) * scale for component in value)
def q_route(points, scale):
return [(q_position(point["position_q"], scale), point["radius_q"] * scale)
for point in points]
def vector_length(value):
return math.sqrt(sum(component * component for component in value))
def unit(value):
length = vector_length(value)
if length <= 1.0e-9:
raise RuntimeError("capture camera direction is degenerate")
return tuple(component / length for component in value)
def add_scaled(point, direction, scale):
return tuple(point[axis] + direction[axis] * scale for axis in range(3))
def make_player_views(layout):
scale = q_to_meters(layout)
passages = {(int(item["upper_strate_index"]), int(item["lower_strate_index"])): item
for item in layout["passages"]}
p01, p12, p23 = passages[(0, 1)], passages[(1, 2)], passages[(2, 3)]
strate0 = next(item for item in layout["strates"] if int(item["strate_index"]) == 0)
surface_anchors = [item["values"] for item in strate0.get("anchors", [])
if item.get("kind") == "route_point"]
if len(surface_anchors) < 2:
raise RuntimeError("strate 0 has no resolved surface arrival route")
surface_route = [(q_position(value[:3], scale), value[3] * scale)
for value in surface_anchors]
route_specs = [
(0, surface_route, True, surface_route[-1][0], p01, "upper", False),
(1, q_route(p01["control_points"], scale), True,
q_position(p01["lower_mouth_q"], scale), p12, "upper", True),
(2, q_route(p12["control_points"], scale), True,
q_position(p12["lower_mouth_q"], scale), p23, "upper", False),
(3, q_route(p23["control_points"], scale), True,
q_position(p23["lower_mouth_q"], scale), p23, "lower", True),
]
views = {}
for strate, source_route, reverse_route, spawn, departure, departure_end, reverse_departure in route_specs:
route = list(reversed(source_route)) if reverse_route else list(source_route)
if vector_length(sub(route[0][0], spawn)) > 5.0:
raise RuntimeError(f"strate {strate} spawn is {vector_length(sub(route[0][0], spawn)):.2f} m from its arrival route")
path_length = sum(vector_length(sub(b[0], a[0])) for a, b in zip(route, route[1:]))
arrival_target_pos, arrival_target_radius = interpolate_route(route, min(20.0, path_length))
arrival_camera = (spawn[0], spawn[1], spawn[2] + 1.6)
arrival_target = (arrival_target_pos[0], arrival_target_pos[1],
arrival_target_pos[2] - arrival_target_radius + 1.8)
ayaw, apitch = camera_angles(arrival_camera, arrival_target)
views[(strate, "arrival")] = {
"camera": arrival_camera, "target": arrival_target, "yaw": ayaw,
"pitch": apitch, "fov_deg": 90.0,
"description": "player eye at the strate arrival, facing into its route"}
route_distance = min(10.0, path_length * 0.2)
route_center, route_radius, _, _ = route_frame(route, route_distance)
route_camera = (route_center[0], route_center[1],
route_center[2] - route_radius + 1.6)
route_target_pos, route_target_radius = interpolate_route(
route, min(path_length, route_distance + 28.0))
route_target = (route_target_pos[0], route_target_pos[1],
route_target_pos[2] - route_target_radius + 1.8)
ryaw, rpitch = camera_angles(route_camera, route_target)
views[(strate, "route")] = {
"camera": route_camera, "target": route_target, "yaw": ryaw,
"pitch": rpitch, "fov_deg": 90.0,
"description": "player-height view along the passage or surface route"}
mouth_path = q_route(departure["control_points"], scale)
if departure_end == "lower":
mouth_path.reverse()
mouth, mouth_radius, inward, _ = route_frame(mouth_path, 0.0)
departure_distance = min(7.4, sum(vector_length(sub(b[0], a[0]))
for a, b in zip(mouth_path, mouth_path[1:])))
inside, inside_radius, _, _ = route_frame(mouth_path, departure_distance)
departure_camera = (inside[0], inside[1], inside[2] - inside_radius + 1.6)
outward = mul(inward, -1.0)
departure_target = (mouth[0] + outward[0] * 12.0,
mouth[1] + outward[1] * 12.0,
mouth[2] - mouth_radius + 1.8)
dyaw, dpitch = camera_angles(departure_camera, departure_target)
views[(strate, "departure-mouth")] = {
"camera": departure_camera, "target": departure_target,
"yaw": dyaw, "pitch": dpitch, "fov_deg": 90.0,
"description": f"inside the {departure_end} mouth looking out"}
overview_camera = (spawn[0] - 12.0, spawn[1] - 16.0, spawn[2] + 70.0)
overview_target = (spawn[0], spawn[1], spawn[2] - 4.0)
oyaw, opitch = camera_angles(overview_camera, overview_target)
views[(strate, "overview")] = {
"camera": overview_camera, "target": overview_target,
"yaw": oyaw, "pitch": opitch, "fov_deg": 90.0,
"description": "overview from 70 m above the arrival"}
return views
def scene_records(path, include_offsets=False):
records = []
keys = set()
with path.open("rb") as stream:
header = stream.read(60)
if len(header) != 60 or header[:7] != b"VFMSH00":
raise RuntimeError(f"{path} has an invalid mesh header")
version, voxel_um, count = struct.unpack_from("<III", header, 8)
if version not in (1, 2) or voxel_um == 0:
raise RuntimeError(f"{path} has an unsupported mesh header")
for _ in range(count):
start = stream.tell()
key_data = stream.read(16)
count_data = stream.read(20)
if len(key_data) != 16 or len(count_data) != 20:
raise RuntimeError(f"{path} has a truncated tile header")
key = struct.unpack("<iiii", key_data)
_, _, vertices, indices, masks = struct.unpack("<IIIII", count_data)
payload_bytes = vertices * 24 + indices * 4 + masks
if version == 2:
payload_bytes += vertices * 10
stream.seek(payload_bytes, os.SEEK_CUR)
end = stream.tell()
if end > path.stat().st_size:
raise RuntimeError(f"{path} ends inside a tile record")
keys.add(key)
if include_offsets:
records.append((key, start, end))
if stream.read(1):
raise RuntimeError(f"{path} has trailing data after {count} tiles")
return header, voxel_um, keys, records
def tile_keys_at(point, keys, voxel_um):
voxel_m = voxel_um / 1_000_000.0
found = set()
for level in range(9):
extent = 32.0 * (1 << level) * voxel_m
key = (math.floor(point[0] / extent), math.floor(point[1] / extent),
math.floor(point[2] / extent), level)
if key in keys:
found.add(key)
return found
def frame_basis(yaw, pitch):
yaw, pitch = math.radians(yaw), math.radians(pitch)
forward = (math.sin(yaw) * math.cos(pitch),
math.cos(yaw) * math.cos(pitch), math.sin(pitch))
right = (math.cos(yaw), -math.sin(yaw), 0.0)
up = (-math.sin(yaw) * math.sin(pitch),
-math.cos(yaw) * math.sin(pitch), math.cos(pitch))
return unit(forward), unit(right), unit(up)
def coverage_audit(camera, yaw, pitch, fov, keys, voxel_um):
forward, right, up = frame_basis(yaw, pitch)
tan_h = math.tan(math.radians(fov) * 0.5)
tan_v = tan_h * 900.0 / 1600.0
uncovered = []
missing_points = []
rays = 0
distance_samples = tuple(range(1, 200, 4))
for row in range(9):
sy = (row / 8.0) * 2.0 - 1.0
for column in range(17):
sx = (column / 16.0) * 2.0 - 1.0
ray = unit(tuple(forward[axis] + right[axis] * sx * tan_h
+ up[axis] * sy * tan_v for axis in range(3)))
rays += 1
for distance in distance_samples:
point = add_scaled(camera, ray, distance)
if not tile_keys_at(point, keys, voxel_um):
uncovered.append((row, column, distance))
missing_points.append(point)
return {"rays": rays, "samples_per_ray": len(distance_samples),
"missing_samples": len(uncovered), "first_missing": uncovered[:8],
"missing_points": missing_points}
def merge_scene_exports(destination, sources):
unique = {}
reference_header = None
reference_version = None
reference_voxel_um = None
for source in sources:
header, voxel_um, _, records = scene_records(source, include_offsets=True)
version = struct.unpack_from("<I", header, 8)[0]
if reference_header is None:
reference_header, reference_version, reference_voxel_um = header, version, voxel_um
elif version != reference_version or voxel_um != reference_voxel_um:
raise RuntimeError("mesh export fragments use incompatible voxel formats")
for record in records:
unique.setdefault(record[0], (source, record[1], record[2]))
merged_header = bytearray(reference_header)
struct.pack_into("<I", merged_header, 16, len(unique))
temporary = destination.with_suffix(destination.suffix + ".merge-tmp")
with temporary.open("wb") as output_stream:
output_stream.write(merged_header)
for source, start, end in unique.values():
with source.open("rb") as input_stream:
input_stream.seek(start)
remaining = end - start
while remaining:
block = input_stream.read(min(1024 * 1024, remaining))
if not block:
raise RuntimeError(f"{source} changed while merging mesh exports")
output_stream.write(block)
remaining -= len(block)
os.replace(temporary, destination)
return len(unique)
def write_truncated_scene(source, destination, header, keys, records, remove_keys):
retained = [record for record in records if record[0] not in remove_keys]
new_header = bytearray(header)
struct.pack_into("<I", new_header, 16, len(retained))
with source.open("rb") as input_stream, destination.open("wb") as output_stream:
output_stream.write(new_header)
for _, start, end in retained:
input_stream.seek(start)
remaining = end - start
while remaining:
block = input_stream.read(min(1024 * 1024, remaining))
if not block:
raise RuntimeError("source mesh changed while writing the negative control")
output_stream.write(block)
remaining -= len(block)
return {key for key in keys if key not in remove_keys}
def run_negative_control(mesh, stem, view, header, voxel_um, keys, records):
camera = view["camera"]
forward, right, up = frame_basis(view["yaw"], view["pitch"])
remove = set()
for distance in range(89, 110, 4):
center = add_scaled(camera, forward, distance)
for side in (-8.0, 0.0, 8.0):
for vertical in (-8.0, 0.0, 8.0):
point = add_scaled(add_scaled(center, right, side), up, vertical)
remove.update(tile_keys_at(point, keys, voxel_um))
if not remove:
raise RuntimeError("negative-control ray did not intersect any exported tiles")
truncated = OUT / "controls" / f"{stem}.truncated.vfmesh"
truncated.parent.mkdir(parents=True, exist_ok=True)
remaining = write_truncated_scene(mesh, truncated, header, keys, records, remove)
audit = coverage_audit(camera, view["yaw"], view["pitch"], view["fov_deg"],
remaining, voxel_um)
if audit["missing_samples"] == 0:
raise RuntimeError("negative control unexpectedly passed frustum coverage")
print(f"[L69NegativeControl] file={truncated.relative_to(ROOT)} removed_tiles={len(remove)} "
f"missing_samples={audit['missing_samples']} flagged_before_render=1 status=PASS",
flush=True)
def export_capture(strate, label, view, negative_control=False):
stem = f"strate{strate}-{label}"
camera = view["camera"]
yaw, pitch = view["yaw"], view["pitch"]
mesh = OUT / "meshes" / f"{stem}.vfmesh"
png = PNG_DIR / f"{stem}.png"
jpg = CAPTURES / f"{stem}.jpg"
mesh.parent.mkdir(parents=True, exist_ok=True)
source = ROOT / "Standalone/data/owner_world.json"
parts = [mesh]
def export_part(part_name, export_camera):
part = mesh if part_name.endswith("-base") else OUT / "mesh-patches" / f"{part_name}.vfmesh"
part.parent.mkdir(parents=True, exist_ok=True)
run(f"{part_name}-export", [HEADLESS, source, "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", "--max-level=8",
"--render-distance-chunks=0", "--mesh-coverage", "--capture-skip-walkability",
"--recipe=surface-world",
f"--camera={fmt((*export_camera, yaw, pitch))}", f"--mesh-export={part}"])
return part
export_part(f"{stem}-base", camera)
header, voxel_um, keys, records = scene_records(mesh, include_offsets=negative_control)
audit = coverage_audit(camera, yaw, pitch, view["fov_deg"], keys, voxel_um)
patch_index = 0
while audit["missing_samples"] and patch_index < 3:
patch_index += 1
points = audit["missing_points"]
patch_camera = tuple(sum(point[axis] for point in points) / len(points)
for axis in range(3))
patch_name = f"strate{strate}-{label}-patch{patch_index}"
part = export_part(patch_name, patch_camera)
parts.append(part)
merged_tiles = merge_scene_exports(mesh, parts)
print(f"[L69CoveragePatch] view={stem} patch={patch_index} "
f"camera_m=({fmt(patch_camera)}) merged_tiles={merged_tiles}", flush=True)
header, voxel_um, keys, records = scene_records(mesh, include_offsets=negative_control)
audit = coverage_audit(camera, yaw, pitch, view["fov_deg"], keys, voxel_um)
print(f"[L69Coverage] view={stem} tiles={len(keys)} rays={audit['rays']} "
f"samples_per_ray={audit['samples_per_ray']} distance_m=200 "
f"missing_samples={audit['missing_samples']} "
f"status={'PASS' if audit['missing_samples'] == 0 else 'FAIL'}", flush=True)
if audit["missing_samples"]:
raise SystemExit(f"{stem} lacks tile coverage within the 200 m camera frustum: "
f"{audit['first_missing']}")
if negative_control:
run_negative_control(mesh, stem, view, header, voxel_um, keys, records)
print(f"[L69CaptureCamera] view={stem} camera_m=({fmt(camera)}) "
f"target_m=({fmt(view['target'])}) yaw={yaw:.3f} pitch={pitch:.3f} "
f"fov_deg={view['fov_deg']:.1f} description={view['description']}", flush=True)
render = [RENDERER, mesh, png, "--size=1600x900",
f"--fov-deg={view['fov_deg']:.1f}", f"--camera-m={fmt(camera)}",
f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}",
"--tile-skirts=off", "--shade=readable", "--headlamp=on",
"--height-tint=on", "--tile-edges=off"]
run(f"{stem}-render", render)
run(f"{stem}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error",
"-y", "-i", png, "-frames:v", "1", "-q:v", "3", jpg])
size = jpg.stat().st_size if jpg.exists() else 0
if not 0 < size <= 300_000:
raise SystemExit(f"{jpg.name} is missing or exceeds 300 KB (bytes={size})")
print(f"[L69CaptureJPEG] file={jpg.relative_to(ROOT)} bytes={size} status=PASS",
flush=True)
def main():
if not HEADLESS.is_file():
raise SystemExit(f"missing current L68 headless binary: {HEADLESS}")
if not RENDERER.is_file():
raise SystemExit(f"missing L69 offscreen renderer: {RENDERER}")
layout = load_layout("owner-four-strate", ROOT / "Standalone/data/owner_world.json",
"surface-world")
views = make_player_views(layout)
print(f"[L69CapturePlan] strates=4 views={len(views)} frustum_m=200 "
f"clip_radius=3 max_clip_level=8 max_tile_level=8 patches=up-to-3", flush=True)
first = True
for strate in range(4):
for label in ("arrival", "route", "departure-mouth", "overview"):
export_capture(strate, label, views[(strate, label)], negative_control=first)
first = False
print(f"[L69CaptureSet] files={len(list(CAPTURES.glob('*.jpg')))} "
f"directory={CAPTURES.relative_to(ROOT)} status=PASS", flush=True)
if __name__ == "__main__":
main()
def select_passage(layout, mouth_generator, preferred_side):
strata = {int(value["strate_index"]): value for value in layout["strates"]}
preferred_candidates = []
fallback_candidates = []
for passage in layout["passages"]:
upper = strata[int(passage["upper_strate_index"])]
lower = strata[int(passage["lower_strate_index"])]
sides = []
if upper["generator_kind"] == mouth_generator:
sides.append("upper")
if lower["generator_kind"] == mouth_generator:
sides.append("lower")
if preferred_side in sides:
preferred_candidates.append((passage, preferred_side == "lower"))
elif sides:
fallback_candidates.append((passage, sides[0] == "lower"))
candidates = preferred_candidates or fallback_candidates
if not candidates:
raise RuntimeError(f"no passage mouth in generator {mouth_generator}")
return sorted(candidates, key=lambda item: item[0]["stable_id"])[0]
def make_views(layout, passage, reverse):
q_to_m = layout["core_voxel_size_um"] / (SUBVOXELS_PER_VOXEL * 1_000_000.0)
controls = [
(tuple(value * q_to_m for value in point["position_q"]),
point["radius_q"] * q_to_m)
for point in passage["control_points"]
]
lower_mouth = tuple(value * q_to_m for value in passage["lower_mouth_q"])
lower_index = min(range(len(controls)), key=lambda i: sum(
(controls[i][0][axis] - lower_mouth[axis]) ** 2 for axis in range(3)))
path = controls[:lower_index + 1]
if len(path) < 2:
raise RuntimeError(f"{passage['stable_id']} has no mouth-to-mouth route")
if reverse:
path.reverse()
lengths = [math.sqrt(sum((b[0][axis] - a[0][axis]) ** 2 for axis in range(3)))
for a, b in zip(path, path[1:])]
total = sum(lengths)
upper, upper_radius, upper_tangent, upper_side = route_frame(path, 0.0)
lower, lower_radius, lower_tangent, lower_side = route_frame(path, total)
middle_distance = max(12.0, min(total - 12.0, total * 0.4)) if total > 24.0 else total * 0.4
if (int(passage["upper_strate_index"],) == 1
and int(passage["lower_strate_index"]) == 2 and not reverse):
# The old 7 m authored-radius beat sat near the middle of this audit window.
middle_distance = min(total - 12.0, 90.8)
entry_distance = min(6.0, total * 0.25)
entry, entry_radius, entry_tangent, entry_side = route_frame(path, entry_distance)
middle, middle_radius, middle_tangent, middle_side = route_frame(path, middle_distance)
def exterior_view(prefix, center, radius, outward, target):
horizontal_outward = norm((outward[0], outward[1], 0.0))
eye_height = center[2] - radius + 1.6
eye = (center[0] + horizontal_outward[0] * 5.5,
center[1] + horizontal_outward[1] * 5.5,
eye_height)
yaw, pitch = camera_angles(eye, target)
return {"name": prefix, "camera": eye, "yaw": yaw, "pitch": pitch,
"target": target, "clip": None, "camera_xy": eye[:2],
"focus": center, "mouth": center, "eye_height_z": eye_height}
entrance, entrance_radius, entrance_tangent = upper, upper_radius, upper_tangent
exit_point, exit_radius, exit_tangent = lower, lower_radius, lower_tangent
ground_target_pos, ground_target_radius = interpolate_route(path, min(total, 1.0))
ground = exterior_view("ground-mouth", entrance, entrance_radius,
mul(entrance_tangent, -1.0), (ground_target_pos[0], ground_target_pos[1],
ground_target_pos[2] - ground_target_radius + 1.0))
ground["focus"] = entrance
ground["outside_camera"] = True
def cutaway_view(prefix, center, radius, tangent, side, distance, target_distance):
camera = (center[0] - side[0] * 7.0, center[1] - side[1] * 7.0,
center[2] - radius + 1.6)
target_pos, target_radius = interpolate_route(path, target_distance)
target = (target_pos[0], target_pos[1],
target_pos[2] - target_radius + 1.6)
yaw, pitch = camera_angles(camera, target)
plane = (side[0], side[1], 0.0,
-(side[0] * center[0] + side[1] * center[1]))
return {"name": prefix, "camera": camera, "yaw": yaw, "pitch": pitch,
"target": target, "clip": plane, "camera_xy": camera[:2],
"focus": center, "mouth": center}
def inside_mouth_view(prefix, at_start):
endpoint_distance = 0.0 if at_start else total
mouth, _, tangent, mouth_side = route_frame(path, endpoint_distance)
camera_distance = min(total, 7.4) if at_start else max(0.0, total - 5.5)
center, _, _, side = route_frame(path, camera_distance)
camera = (center[0] + side[0] * 2.5,
center[1] + side[1] * 2.5, center[2])
outward = mul(tangent, -1.0) if at_start else tangent
target = (mouth[0] + outward[0] * 8.0 + mouth_side[0] * 2.5,
mouth[1] + outward[1] * 8.0 + mouth_side[1] * 2.5,
mouth[2])
yaw, pitch = camera_angles(camera, target)
return {"name": prefix, "camera": camera, "yaw": yaw, "pitch": pitch,
"target": target, "clip": None, "camera_xy": camera[:2],
"focus": mouth, "mouth": mouth, "outside_camera": False,
"camera_location": "passage-air", "focus_radius": 30.0,
"eye_height_z": camera[2], "fov_deg": 80.0}
# Place each interface view at its true route endpoint. The endpoint camera
# is a side cutaway so the solid terrain in front of an underground mouth
# cannot hide the floor-to-tunnel connection.
arrival_distance = 0.0
arrival_center, arrival_radius, arrival_tangent, arrival_side = route_frame(
path, arrival_distance)
arrival = cutaway_view("arrival", arrival_center, arrival_radius,
arrival_tangent, arrival_side, arrival_distance,
min(total, arrival_distance + 12.0))
arrival["camera_location"] = "passage-air"
arrival["focus_radius"] = 20.0
arrival["yaw"], arrival["pitch"] = camera_angles(
arrival["camera"], arrival["target"])
entry_camera_distance = min(8.0, total * 0.1)
entry_center, entry_radius, entry_tangent, entry_side = route_frame(
path, entry_camera_distance)
# Player-height view from just inside the passage, aimed through its actual
# interface mouth so the floor, bore, and opening share one frame.
entry_camera = (entry_center[0], entry_center[1],
entry_center[2] - entry_radius + 1.6)
entry_target_pos, entry_target_radius = interpolate_route(
path, min(total, entry_distance + 14.0))
entry_target = (entry_target_pos[0], entry_target_pos[1],
entry_camera[2] - 2.5)
entry_yaw, entry_pitch = camera_angles(entry_camera, entry_target)
entry_view = {"name": "rock-entry", "camera": entry_camera,
"yaw": entry_yaw, "pitch": entry_pitch, "target": entry_target,
"clip": None, "camera_xy": entry_camera[:2],
"focus": entry_center, "mouth": entrance, "outside_camera": False,
"camera_location": "passage-air", "focus_radius": 20.0,
"eye_height_z": entry_camera[2]}
middle_view = cutaway_view("mid-tunnel", middle, middle_radius, middle_tangent,
middle_side, middle_distance, min(total, middle_distance + 2.0))
ground["focus_radius"] = 20.0
middle_view["focus_radius"] = 20.0
middle_camera_center, _ = interpolate_route(path, middle_distance)
middle_view["camera"] = (middle_camera_center[0] + middle_side[0] * 0.75,
middle_camera_center[1] + middle_side[1] * 0.75,
middle[2] - middle_radius + 1.6)
middle_target_pos, middle_target_radius = interpolate_route(
path, min(total, middle_distance + 14.0))
middle_view["target"] = (middle_target_pos[0] + middle_side[0] * 2.35,
middle_target_pos[1] + middle_side[1] * 2.35,
middle_target_pos[2] - middle_target_radius + 1.8)
# Keep the camera inside the bore, close to one wall, and look down the
# route so the old beat location is visible at player height.
middle_view["clip"] = None
middle_view["camera_location"] = "passage-air"
middle_view["yaw"], middle_view["pitch"] = camera_angles(
middle_view["camera"], middle_view["target"])
return (ground, entry_view, middle_view, arrival,
inside_mouth_view("inside-mouth-start", True),
inside_mouth_view("inside-mouth-end", False)), total, lower_index
def export_capture(scene_name, source, recipe, passage, endpoint, view):
stem = f"{passage['stable_id'].replace('/', '-')}-{endpoint}"
view["name"] = stem
mesh = OUT / f"{stem}.vfmesh"
png = PNG_DIR / f"{stem}.png"
jpg = CAPTURES / f"{stem}.jpg"
yaw, pitch = view["yaw"], view["pitch"]
camera = view["camera"]
export_camera = view.get("focus", camera)
run(f"{stem}-export", [HEADLESS, source, "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=0", "--max-level=0",
"--render-distance-chunks=0", f"--recipe={recipe}",
f"--camera={fmt((*export_camera, yaw, pitch))}", f"--mesh-export={mesh}"])
render_clip = view["clip"]
surface = None
if view.get("outside_camera", False):
surface = terrain_surface_near(mesh, *view["camera_xy"], view["target"][2])
if surface is None:
camera = (view["camera_xy"][0], view["camera_xy"][1], view["eye_height_z"])
else:
camera = (surface[0], surface[1], surface[2] + 1.6)
elif render_clip is not None:
camera = view["camera"]
yaw, pitch = camera_angles(camera, view["target"])
fov_deg = view.get("fov_deg", 80.0)
print(f"[L68CaptureCamera] view={stem} camera_m=({fmt(camera)}) "
f"target_m=({fmt(view['target'])}) "
f"clip={fmt(render_clip) if render_clip is not None else 'none'} fov_deg={fov_deg:.1f}",
flush=True)
render = [RENDERER, mesh, png, "--size=1600x900", f"--fov-deg={fov_deg:.1f}",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}",
"--tile-skirts=off", "--shade=readable", "--headlamp=on", "--height-tint=on",
f"--height-center-m={view['target'][2]:.6f}", "--height-range-m=8", "--tile-edges=off"]
if view.get("focus_radius", 24.0) is not None:
render.append(f"--focus-region={fmt((*view['focus'], view.get('focus_radius', 24.0)))}")
if render_clip is not None:
render.append(f"--clip-plane={fmt(render_clip)}")
run(f"{stem}-render", render)
run(f"{stem}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error",
"-y", "-i", png, "-frames:v", "1", "-q:v", "4", jpg])
size = jpg.stat().st_size if jpg.exists() else 0
print(f"[L68CaptureJPEG] file={jpg.relative_to(ROOT)} bytes={size} "
f"status={'PASS' if 0 < size <= 300_000 else 'FAIL'}", flush=True)
if not 0 < size <= 300_000:
raise SystemExit(f"{jpg.name} is missing or exceeds 300 KB")
@@ -0,0 +1,54 @@
# Linux69 — owner four-strata captures and spawn trace
## Captures
The L68 exports were limited to a small clip radius and LOD0 (max-clip-level=0, max-level=0). The camera frustum extended beyond those tiles, while the renderer cleared empty pixels to RGB (0.035, 0.055, 0.075). Unmeshed space therefore looked nearly black.
L69 exports use a 90° player-view frustum, radius 3 and LODs through 8. A coverage probe samples a 17×9 ray grid at 50 points per ray, every 4 m from 1 m through 197 m. Nine views needed one overlapping mesh patch. All 16 ended with zero uncovered probe samples. The renderer clear color is now sky blue (RGB (0.30, 0.48, 0.62)). A tile coverage failure aborts before rendering, so a missing export cannot silently become a black capture. This is a sampled coverage check, not a per-pixel proof.
The negative control removes 8 tiles from the strate 0 arrival mesh. It reports 480 uncovered samples and fails before rendering. Control mesh: Saved/Linux/l69/controls/strate0-arrival.truncated.vfmesh.
The coverage probe only answers whether mesh tiles cover its sampled frustum points. No real hole was confirmed in the captures. The engine trace blocker below is a solid passage sample. Blue pixels indicate an unoccluded ray through covered tiles; this round did not add a separate classifier for open sky versus a terrain breach.
All JPEGs are below 300 KB; combined size is 447,288 bytes.
| Strate | View | File | Bytes | What it shows |
|---:|---|---|---:|---|
| 0 | Arrival | [strate0-arrival.jpg](captures/strate0-arrival.jpg) | 40,064 | Player-eye view at the surface arrival, facing into the route. |
| 0 | Route | [strate0-route.jpg](captures/strate0-route.jpg) | 38,567 | Player-height view along the surface route toward the passage. |
| 0 | Departure mouth | [strate0-departure-mouth.jpg](captures/strate0-departure-mouth.jpg) | 35,055 | Inside the upper mouth looking outward. |
| 0 | Overview | [strate0-overview.jpg](captures/strate0-overview.jpg) | 23,395 | Elevated view, 70 m above the arrival, looking down at the route. |
| 1 | Arrival | [strate1-arrival.jpg](captures/strate1-arrival.jpg) | 24,524 | Player-eye view at the strate 1 arrival, facing into its route. |
| 1 | Route | [strate1-route.jpg](captures/strate1-route.jpg) | 29,602 | Player-height view along the passage. |
| 1 | Departure mouth | [strate1-departure-mouth.jpg](captures/strate1-departure-mouth.jpg) | 26,090 | Inside the upper mouth looking outward. |
| 1 | Overview | [strate1-overview.jpg](captures/strate1-overview.jpg) | 25,960 | Elevated view, 70 m above the arrival, looking down at the route. |
| 2 | Arrival | [strate2-arrival.jpg](captures/strate2-arrival.jpg) | 20,339 | Player-eye view at the strate 2 arrival, facing into its route. |
| 2 | Route | [strate2-route.jpg](captures/strate2-route.jpg) | 20,244 | Player-height view along the passage. |
| 2 | Departure mouth | [strate2-departure-mouth.jpg](captures/strate2-departure-mouth.jpg) | 33,125 | Inside the upper mouth looking outward. |
| 2 | Overview | [strate2-overview.jpg](captures/strate2-overview.jpg) | 25,668 | Elevated view, 70 m above the arrival, looking down at the route. |
| 3 | Arrival | [strate3-arrival.jpg](captures/strate3-arrival.jpg) | 24,307 | Player-eye view at the strate 3 arrival, facing into its route. |
| 3 | Route | [strate3-route.jpg](captures/strate3-route.jpg) | 23,966 | Player-height view along the passage. |
| 3 | Departure mouth | [strate3-departure-mouth.jpg](captures/strate3-departure-mouth.jpg) | 35,034 | Inside the lower mouth looking outward. |
| 3 | Overview | [strate3-overview.jpg](captures/strate3-overview.jpg) | 21,348 | Elevated view, 70 m above the arrival, looking down at the route. |
## In-engine spawn trace
The host rebuilt successfully on Linux. The trace harness was prepared for 3 rotated/interleaved trials per strate, spawning at each arrival. No strate 0 attempt produced a valid Core trace; the other strata were not run. Engine metrics are therefore unavailable, including walkable ring, LOD0 ring, full set, worker-seconds, LOD0 p50/p95, and generateTile / mesh-copy / RMC timings.
| Strate | Arrival spawn (m) | Headless whole-view median | Valid engine trials | Walkable ring | LOD0 ring | Full set | Worker-s | LOD0 p50 / p95 | generateTile / mesh copy / RMC |
|---:|---|---:|---:|---:|---:|---:|---:|---|---|
| 0 | (0, 0, -374.939) | 1.074 s | 0/3 (attempts blocked) | N/A | N/A | N/A | N/A | N/A | N/A |
| 1 | (25.837, 3.199, -737.000) | 1.503 s | 0/3 (not run) | N/A | N/A | N/A | N/A | N/A | N/A |
| 2 | (19.166, -14.200, -753.374) | 0.577 s | 0/3 (not run) | N/A | N/A | N/A | N/A | N/A | N/A |
| 3 | (-20.063, -5.068, -900.354) | 0.210 s | 0/3 (not run) | N/A | N/A | N/A | N/A | N/A | N/A |
No engine/headless percentage comparison is available, so no >30% gap can be assessed. The default engine-generated four-strate plan failed passage validation at the solid Core voxel (-172, -138, -1794) (25 cm per voxel; approximately (-43.0, -34.5, -448.5) m). This is a blocked centerline point, not a hole. The saved owner manifest lacked layout_chunk_width_q; regenerating and combining its current SurfaceWorld plan with the saved FloatingIslands plan got past parsing, but UE rejected the island plan bounds. The imported slot is strate 1 chunks [-79, -16], while the live slot resolves to [-93, -64]. A floating-islands-only headless export also failed because it could not resolve a SurfaceWorld strate. I stopped without bypassing the production checks.
## Failed runs and build notes
- Radius 4 exports, with max clip level 8 and then 7, exceeded the 160-million-node shape safety bound. Radius 3 plus overlapping patches covered the views.
- The UE host toolchain failed under the 6 GiB address-space limit during CoreCLR startup, so host build and UE runs used the requested 6 GiB RSS limit. The host build passed after using the local VFHost/PluginDeps plugin mirror; the final incremental build compiled the spawn-coordinate parser.
- The first UE trace launch exposed comma parsing in the new spawn argument. The parser was corrected and the host rebuilt. One RSS-capped launch exited when AndroidFileServer failed to map; disabling that unrelated plugin let the commandlet run. The generated-settings plan hit the solid-centerline failure above. Manifest attempts then encountered the stale schema and stale island bounds; none reached valid spawn tracing.
- No attempted run exceeded 30 minutes. Logs are under Saved/Linux/l69.
No world recipe, geometry, or peer-determinism inputs changed. The only Source/ edit is Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp, adding a finite XYZ spawn override for the trace; it compiled in the Linux host build. Standalone/Source/StreamBenchmarkHeadless.cpp also writes the canonical manifest after plan assembly for trace setup; its Linux rebuild passed. Other capture-only edits are in Standalone/Source/LinuxOffscreenCapture.cpp and Standalone/Source/StreamBenchmarkPipeline.h.
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Run three interleaved UE startup traces at each owner-plan arrival."""
import subprocess
import time
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
HOST = Path("/home/jahni/VFHost")
PLUGIN_SAVED = ROOT / "Saved/Linux/l69"
HOST_PLUGIN_SAVED = HOST / "Plugins/VoxelForge/Saved/Linux/l69"
OWNER_WORLD = ROOT / "Standalone/data/owner_world.json"
OWNER_MANIFEST = HOST_PLUGIN_SAVED / "owner_world_manifest.json"
EDITOR = Path("/home/jahni/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd")
TRACES = PLUGIN_SAVED / "traces"
HOST_SAVED = HOST / "Saved/Linux/l69"
SPAWNS_M = {
0: (0.0, 0.0, -374.939453),
1: (25.836914, 3.199219, -737.0),
2: (19.166016, -14.200195, -753.374023),
3: (-20.0625, -5.068359, -900.353516),
}
ORDER = ((0, 1), (1, 1), (2, 1), (3, 1),
(1, 2), (2, 2), (3, 2), (0, 2),
(2, 3), (3, 3), (0, 3), (1, 3))
def run(strate, trial):
label = f"strate{strate}-trial{trial}"
trace = TRACES / f"{label}.trace.jsonl"
abslog = TRACES / f"{label}.log"
stdout = TRACES / f"{label}.stdout.log"
output = HOST_PLUGIN_SAVED / "output" / label
userdir = HOST_SAVED / label / "User"
zendata = HOST_SAVED / label / "Zen"
ddc = HOST_SAVED / "DDC"
for directory in (output, userdir, zendata, ddc, TRACES):
directory.mkdir(parents=True, exist_ok=True)
spawn_cm = ",".join(f"{value * 100.0:.3f}" for value in SPAWNS_M[strate])
command = [
str(EDITOR), str(HOST / "VFHost.uproject"), "-nullrhi", "-unattended",
"-nop4", "-nosplash", "-NoSound", "-corelimit=12", "-NoZenAutoLaunch",
"-notraceserver", "-nocrashreports", "-ddc=NoZenLocalFallback",
f"-userdir={userdir}", f"-ZenDataPath={zendata}", f"-LocalDataCachePath={ddc}",
"-run=VoxelForgeExplore", "-seed=0", "-archetype=TunnelNetwork", "-slot=0",
"-modes=export", "-gameconfig=1", "-coreworldtest=1", "-ownerfourstrateplan=1",
"-corevoxelsizecm=25", "-coreislandtest=0", "-corefallbacktest=0",
"-coreofftest=0", "-coreplantest=0",
"-settingsref=/Game/VoxelForge/DA_Settings.DA_Settings", "-corehybridmesher=0",
f"-coreworldassembly={OWNER_MANIFEST}",
f"-out={output}",
"-voxel.StreamingCoarseFirst=0", "-voxel.StreamingDynamicNearReserve=1",
"-voxel.StreamingLegacyPendingResultCap=0", "-voxel.CoreRegionConcurrentAdmission=1",
"-voxel.StreamingResultBacklogCap=256", "-voxel.StreamingReadyQueueDepth=8",
"-voxel.StreamingBackgroundWorkerCap=1", "-voxel.CoreColumnCacheDirectMapped=0",
"-voxel.CoreMCGradientMemoization=1", "-voxel.StreamingCollisionLookahead=1",
"-coreworldstreamtrace=1", "-voxel.TestMove=0",
"-voxel.TestMoveSpeedCmPerSecond=0", f"-voxel.StartupTraceSpawnCm={spawn_cm}",
f"-voxel.StartupTraceFile={trace}", "-voxel.StartupTraceUntilEndPlay=1",
"-voxel.StartupTraceMaxTiles=16384", "-voxel.CollisionGate=0",
f"-abslog={abslog}",
]
wrapped = ["taskset", "-c", "0-11", "prlimit", "--rss=6442450944", "--",
"timeout", "1740s", *command]
started = time.monotonic()
with stdout.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1740, check=False)
elapsed = time.monotonic() - started
print(f"[L69EngineTrace] label={label} spawn_cm={spawn_cm} elapsed_s={elapsed:.2f} "
f"exit={result.returncode} trace={trace} stdout={stdout}", flush=True)
if result.returncode != 0:
raise SystemExit(f"{label} failed; inspect {stdout} and {abslog}")
if not trace.is_file() or trace.stat().st_size == 0:
raise SystemExit(f"{label} exited successfully but produced no startup trace")
def main():
if not EDITOR.is_file():
raise SystemExit(f"missing UE commandlet executable: {EDITOR}")
HOST_PLUGIN_SAVED.mkdir(parents=True, exist_ok=True)
owner = json.loads(OWNER_WORLD.read_text(encoding="utf-8"))
assembly = owner.get("settings_asset", {}).get("WorldAssemblyManifestJson")
if not isinstance(assembly, str) or not assembly:
raise SystemExit(f"{OWNER_WORLD} has no serialized world-assembly manifest")
OWNER_MANIFEST.write_text(assembly + "\n", encoding="utf-8")
print(f"[L69EngineTracePlan] source={OWNER_WORLD} manifest={OWNER_MANIFEST} "
f"bytes={OWNER_MANIFEST.stat().st_size}", flush=True)
for strate, trial in ORDER:
run(strate, trial)
print("[L69EngineTraceSet] strata=4 trials_each=3 total=12 status=PASS", flush=True)
if __name__ == "__main__":
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -0,0 +1,77 @@
## Ranked recommendation
**Keep the order TN → VS → FI.** The order is unchanged; I’ve corrected the explanation for FI and tightened the first-round gate.
| Rank | Generator | Recommendation |
|---|---|---|
| **1. TN** | Best first native-family step for caves and liminal variety. **[CODE]** A native cave graph, field evaluator and adapter recipe exist. **[ASSET]** The owner’s TN asset does not select that recipe or provide its graph. **[INFERENCE]** The parameter-to-graph work is a substantial migration, and a speed gain is unproven: the owner’s legacy TN is 0.577 s; the native cave fixture was 0.706 s. |
| **2. VS** | Strong visual payoff, but the existing native lattice is not yet a convincing shaft generator. **[CODE]** The adapter accepts `NativeLattice` for `VerticalShafts` and evaluates explicit room/tunnel graphs. **[REPORT]** The fixture looked like a corkscrew tunnel and called for an open shaft core; it took 1.694 s. This makes VS the highest geometry and gameplay risk. |
| **3. FI** | Keep last in a performance-led sequence, **not** because native FI is fixture-only. **[CODE]** Its native field, adapter planner and runtime dispatch path exist. **[REPORT]** The current owner run still reports compiled analytic FI at 1.503 s, and other FI fixtures are slower. **[INFERENCE]** It is the most mature native implementation, but its speed payoff is unclear. |
**What changed in this re-audit:** the FI adapter can auto-plan for an owner world without an imported assembly; an imported assembly must contain a matching plan for every FI stratum or validation fails. I cannot identify from the inspected code why the reported owner run still dispatches compiled analytic FI, so I withdraw the earlier suggestion that an imported override explains it. I also cannot attribute the owner’s FI time to the native mouth filter: that code runs only on the native path, while L67 reports the owner path as compiled analytic. Finally, changing TN’s field could disturb a passage landing; the first-round brief now makes the L68 route audits a promotion gate.
Labels below distinguish **[CODE]** source inspection, **[ASSET]** checked-in owner export, **[REPORT]** recorded run evidence, and **[INFERENCE]** an estimate or conclusion rather than a verified implementation fact.
## Evidence
### The native paths are reachable from the runtime adapter
**[CODE] They are not reachable only through a fixture flag.** The production route in source is:
1. `AVoxelWorld::InitializeVoxelForgeCoreWorld` calls `SerializeResolvedWorld`, parses the returned snapshot and constructs the Core `World`. [VoxelWorld.cpp:7591–7623](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:7591) [VoxelWorld.cpp:7661–7664](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:7661)
2. The Unreal adapter attaches a validated assembly. On the generated-manifest branch it calls `BuildFloatingIslandStratumPlan` for FI slots; it also writes resolved layout entries, configures each `CoreGeometry` recipe, plans passages and resolves authored room/tunnel records. [VoxelForgeCoreAdapter.cpp:1552–1664](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1552) [VoxelForgeCoreAdapter.cpp:1836–1850](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1836) [VoxelForgeCoreAdapter.cpp:1980–2000](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1980)
3. Core’s regular tile-region build passes the manifest and resolved layout to `FStandaloneShapeWorld::Build`; that build sets `bUseNativeManifestFields=true` and binds the resolved fields. This is the normal build path, separate from `bNativeOnly`, the audit mode. [VoxelForgeCore.cpp:1667–1673](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:1667) [ShapeWorldInternal.h:2332–2352](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2332)
4. `DensityAt` tries native FI, then the bound native cave/surface field; `DensityDispatchPathAt` probes those same evaluators and reports the selected path. Native cave composition applies the resolved graph and shared composition. [ShapeWorldInternal.h:3068–3099](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:3068) [ShapeWorldInternal.h:3123–3140](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:3123) [ShapeWorldInternal.h:1892–1920](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1892)
**[REPORT] Reachable does not mean active in the owner run.** L67 says the owner’s FI, TN and VS paths use the compiled analytic base evaluator. [L67 report:5](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux67/REPORT.md:5) **[ASSET]** The checked-in export selects `CoreGeometry.GeneratorRecipe=None`, so TN and VS are not opted into their graph recipes. [owner_world.json:1](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/data/owner_world.json:1) FI is different: its planner is automatic on one adapter branch, but the L67 dispatch evidence says the current run did not select it. The precise runtime reason remains unresolved.
There is one boundary to this finding: **[CODE]** the adapter has a CoreGeometry-only layout path when there are no SurfaceWorld or FI strata, but native density evaluation requires a `WorldAssembly` as well as a resolved layout. That may leave a caves-only world unable to use this path without an assembly. The owner’s four-strata world includes SurfaceWorld, so this caveat does not by itself explain its present dispatch. [VoxelForgeCoreAdapter.cpp:1602–1606](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1602) [ShapeWorldInternal.h:6570–6583](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:6570)
### Exists / missing, checked against code
| Generator | Exists: what I verified | Missing: what I verified or inferred |
|---|---|---|
| **FI** | **[CODE]** `FloatingIslandFieldV1` evaluates island plans and provides feature bounds and intervals. The Unreal adapter maps island settings and creates plans; `ShapeWorldInternal.h` binds and dispatches the field when its plan is present. [FloatingIslandsInternal.h:2286–2315](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FloatingIslandsInternal.h:2286) [FloatingIslandsInternal.h:2492–2540](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FloatingIslandsInternal.h:2492) [VoxelForgeCoreAdapter.cpp:857–950](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:857) | **[CODE + REPORT]** No island-field algorithm port is missing. What is missing from the current owner run is successful native selection; the cause is not established. **[INFERENCE]** Likely work is path instrumentation and, only if it exposes a defect, a small adapter/runtime or verification change—not a new field family. |
| **TN** | **[CODE]** `NativeCaves` selects `Core.Caves.PrimitiveField` v1. The Unreal adapter converts authored rooms and tunnels; the Core resolver and shape evaluator resolve and compose that graph. [VoxelForgeCoreAdapter.cpp:145–158](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:145) [VoxelForgeCoreAdapter.cpp:1288–1414](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1288) [ShapeWorldInternal.h:7381–7430](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:7381) | **[CODE]** The inspected adapter resolves authored Core records; it does not translate the owner’s 82-field `FStrateGenerationParams` into them. **[ASSET]** The owner recipe is None and its graph arrays are empty. A parameter compiler and explicit opt-in are therefore missing. **[INFERENCE]** Likely touch points are `VoxelStrateTypes.h`, `VoxelForgeCoreAdapter.cpp`, Core graph/schema or resolver code if needed, `ShapeWorldInternal.h` if the field needs more semantics, plus owner verification. |
| **VS** | **[CODE]** `NativeLattice` is accepted for `VerticalShafts`, and uses resolved rooms and explicit tunnel paths. **[REPORT]** A native lattice fixture proves reachability and interval checks. [VoxelForgeCoreAdapter.cpp:159–171](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:159) [L58 report:9–13](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux58/REPORT.md:9) | **[CODE SURVEY + REPORT]** The inspected recipe/planner is generic lattice graph support, not a shaft-specific planner for `FVerticalShaftParams`; the fixture report says its result is not an open vertical shaft. A shaft plan/open core and safe landings remain. The claim that there is no other dedicated planner is limited to the inspected adapter/Core route; the report independently confirms the visual gap. |
**[INFERENCE] File counts from my prior answer were estimates, not counted changes.** For FI, the relevant implementation is already in `FloatingIslandsInternal.h`, `ShapeWorldInternal.h` and `VoxelForgeCoreAdapter.cpp`; first locate the selection mismatch before estimating edits. For TN, those same adapter/Core layers plus `VoxelStrateTypes.h` are the likely work area. For VS, expect those layers plus shaft-specific geometry and proof. Exact touched-file counts depend on whether existing records can represent the required parameter semantics.
### FI cost: traced paths and falsifier
There are three paths to keep separate:
- **[REPORT] Current owner path:** L67 reports `compiled-analytic/FloatingIslands`, not native FI. In `ShapeWorldInternal.h`, that means `DensityAt` falls through the native probes to `AnalyticDensityAt`; its general field path uses `EffectiveRockDistance`, the local detail pass, and shared composition. [ShapeWorldInternal.h:3068–3099](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:3068) [ShapeWorldInternal.h:5343–5422](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:5343) **I have not proved that the L55/L60 FI timings isolate this exact evaluator.**
- **[CODE] Unreal legacy generator path:** `BuildFloatingIslandStack` starts from void, adds `FIslandBlobSource`, roughness, fill and structural posts. `FIslandBlobSource` evaluates nearby hash-placed islands and contour warp. [VoxelGenerator.cpp:2630–2636](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelGenerator.cpp:2630) [VoxelDensityOpStack.cpp:2871–2935](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelDensityOpStack.cpp:2871) [VoxelDensityOpStack.cpp:9504–9533](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelDensityOpStack.cpp:9504)
- **[CODE] Native FI path:** when selected, Core samples `FloatingIslandFieldV1`; near a mouth it may add a 27-tap smoothing filter and six neighbor probes. [ShapeWorldInternal.h:6592–6608](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:6592) [ShapeWorldInternal.h:6737–6800](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:6737)
**[REPORT]** L55 attributes substantial worker work to grid and skirts: 36.414 worker-seconds of grid work and 2.787 of skirts across its 2,370-tile FI drain. These are worker-seconds for that fixture, not the current owner’s 1.503 s wall time. [L55 report:15–30](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux55/REPORT.md:15) R32’s optimistic calculation says removing all measured FI grid work from the L60 setup still leaves about 1.59 s; again, that is not the current owner path. [R32 report:41–45](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research32/REPORT.md:41)
So the defensible conclusion is **native FI is not yet demonstrated to be a speed fix**. I would revise that if a paired owner benchmark held camera, tile counts and clip settings fixed, logged `DensityDispatchPathAt`, and attributed time to field sampling, grid classification, meshing/normals and skirts. If native dispatch cut the same owner cold view to about 1 s without changing passage audits, that would disprove the current caution.
### Passage and landing risk
**Yes, a TN field migration could break a contract that L66–L68 just made pass.** The shared composer consumes the generator field before applying the boundary bands and passage carves; native graph caves also skip the legacy floor-cap stage. Changing the underlying field can therefore change support near a landing even if the passage plan and composer code stay untouched. [ShapeWorldInternal.h:1892–1920](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1892)
L68’s three interfaces had 0 blocked, unsupported and grade failures, with wall ripple ≤3.5 cm and floor ripple ≤2.5 cm. [L68 report:11–21](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux68/REPORT.md:11) Keeping the passage configuration fixed is necessary, but it does not prove the changed generator field preserves those results.
## First-round brief for the implementer
**Goal:** Add an explicitly opt-in native TN path for the owner’s current TN stratum. Compile its existing generation settings into a versioned native cave plan; keep legacy TN as the default until the owner plan passes all gates below. Do not change the shared passage planner or composer in this round.
**Invariants:**
- Account for every non-default owner TN setting in a field-by-field mapping. If the native graph cannot represent one, extend the recipe or reject native opt-in and leave legacy active. Do not silently drop it.
- Hold the owner’s seed, strate bounds, resolved passage configuration, passage route and shared composition code fixed.
- Keep FI and VS dispatch unchanged.
- Do not promote TN if its changed field alters a landing or route contract.
**Evidence required before enabling it on the owner asset:**
- `DensityDispatchPathAt` reports `Caves-native` throughout the TN stratum; record the resolved manifest/layout identity and graph plan used.
- Repeat the **full L68 passage audit across all three interfaces**, not just TN→VS: zero blocked/unsupported/grade failures and the same wall/floor ripple limits. Compare route and landing geometry with the fixed L68 plan.
- Recheck reachability, headroom and grade, plus interval boxes against brute-force samples; false all-air/all-solid controls must still be rejected.
- Verify deterministic hashes at W1/W8/W12 on both compilers.
- Run five interleaved cold owner whole-view repetitions with stable tile counts; target a median at or below 1.0 s and report moving p50/p95 against the legacy baseline.
Until those checks pass, native TN should remain an opt-in canary and the current owner recipe should remain on legacy dispatch.
+3 -1
View File
@@ -794,7 +794,9 @@ void main()
glEnableVertexAttribArray(5);glVertexAttribPointer(5,4,GL_FLOAT,GL_FALSE,sizeof(FVertex),
reinterpret_cast<void*>(offsetof(FVertex,Material)));
glViewport(0,0,Width,Height);glDisable(GL_CULL_FACE);glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);glClearColor(0.035f,0.055f,0.075f,1.0f);
// A covered empty ray represents open air. Keep it visibly blue so it cannot
// be mistaken for the old near-black result from a scene that lacked tiles.
glDepthFunc(GL_LEQUAL);glClearColor(0.30f,0.48f,0.62f,1.0f);
glClearDepthf(1.0f);glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glUseProgram(Program);
glUniform1f(glGetUniformLocation(Program,"uTanHalfHorizontal"),static_cast<float>(TanHalfHorizontal));
+27 -2
View File
@@ -82,6 +82,7 @@ namespace
bool HasCamera=false;
bool HasCameraStep=false;
bool AutoSurfaceCamera=false;
bool CaptureSkipWalkability=false;
FVector CameraMeters=FVector::ZeroVector;
FVector CameraStepMeters=FVector::ZeroVector;
float CameraYawDegrees=0.0f;
@@ -1410,6 +1411,7 @@ namespace
" --region-crossing (compile entering Core regions at the camera step)\n"
" --column-cache=shared|thread-local (default: shared)\n"
" --mesh-export=FILE (write the exact generated tile meshes for offline rendering)\n"
" --capture-skip-walkability (mesh export only; defer the owner-wide passage gate)\n"
" --world-assembler-manifest-output=FILE (write the canonical Core recipe and plan)\n"
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
" --seed=N --recipe=surface-world|floating-islands|caves|lattice\n"
@@ -1589,6 +1591,7 @@ namespace
if(options.MeshExportPath.empty())
{std::fprintf(stderr,"--mesh-export needs a file path\n");return false;}
}
else if(arg=="--capture-skip-walkability")options.CaptureSkipWalkability=true;
else if(arg.starts_with("--workers="))
{if(!readInt("--workers=",options.WorkerCount))return false;}
else if(arg.starts_with("--clip-radius="))
@@ -1626,6 +1629,8 @@ namespace
{std::fprintf(stderr,"--audit-ore-vein-yield requires --ore-veins-demo\n");return false;}
if(options.LatticeRecipe&&options.LatticeStrateIndex<0)
{std::fprintf(stderr,"--recipe=lattice requires an explicit --lattice-strate=N selection\n");return false;}
if(options.CaptureSkipWalkability&&options.MeshExportPath.empty())
{std::fprintf(stderr,"--capture-skip-walkability requires --mesh-export=FILE\n");return false;}
if((options.AuditNativeLattice||options.LatticeOrphanControl)&&!options.LatticeRecipe)
{std::fprintf(stderr,"native lattice audits require --recipe=lattice\n");return false;}
if(options.TlsProbeRequested)return true;
@@ -1716,12 +1721,31 @@ int main(int argc,char** argv)
const auto planStart=Clock::now();
VoxelForgeCore::WorldAssemblyManifest worldAssembly;
if(!StandaloneWorldAssemblyPrototype::AttachRound1WorldAssemblyPrototype(
*ownerWorld,ownerJsonRoot,error,options.WorldAssemblyManifestOutputPath.string(),&worldAssembly))
*ownerWorld,ownerJsonRoot,error,{},&worldAssembly))
{std::fprintf(stderr,"World assembler prototype setup failed: %s\n",error.c_str());return 2;}
if(options.FloatingIslandsRecipe
&&!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<std::streamsize>(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");
@@ -2159,5 +2183,6 @@ int main(int argc,char** argv)
options.ExactMidpointDensityMemo,options.ActiveLaneBatching,
options.ProofBlockCells,options.HasSignEdgeRepairOverride
?options.ParallelSignEdgeRepair:options.FloatingIslandsRecipe,
options.AuditNativeLattice,options.LatticeOrphanControl);
options.AuditNativeLattice,options.LatticeOrphanControl,
options.CaptureSkipWalkability);
}
+4 -1
View File
@@ -1872,7 +1872,7 @@ namespace StandaloneStreamBenchmark
bool bExactMidpointDensityMemo=false,
bool bActiveLaneBatching=false,int32 ProofBlockCells=8,
bool bParallelSignEdgeRepair=false,bool bAuditNativeLattice=false,
bool bLatticeOrphanControl=false)
bool bLatticeOrphanControl=false,bool bCaptureSkipWalkability=false)
{
using Clock=std::chrono::steady_clock;
FStandaloneOwnerWorld& World=*OwnerWorld;
@@ -2029,6 +2029,9 @@ namespace StandaloneStreamBenchmark
ShapeRequest.detail=DetailVariant;ShapeRequest.includeFarWorld=bIncludeFarWorld;
ShapeRequest.roundTunnels=bRoundTunnels;
ShapeRequest.enhancedTunnelWalls=bEnhancedTunnelWalls;
ShapeRequest.validatePassageRoutes=!bCaptureSkipWalkability;
if(bCaptureSkipWalkability)
std::printf("[CaptureWalkabilityGate] validation=deferred geometry_input=unchanged mode=mesh-export\n");
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache);