- Export commandlet writes the live Core adapter's world_assembly + resolved layout pair; the headless owner path imports that pair (manifest/layout equal, layout_chunk_width_q validated). owner_world.json re-exported from the owner's staged assets on the Linux host. - Adapter SW route: Core clearance/radius floors, 0.5 m support search, outward-facing tie-break; passage mouth/mid radius floors match Core (36/24 owner voxels). - FI in Unreal now uses the legacy (compiled analytic) generator unless a plan is imported: the auto native-FI plan failed the passage audit on the owner plan (1843 blocked samples, 945 open-air violations). Owner decides whether native FI returns. - In-engine spawn traces per strate (3 each): SW GenerateTile 42.7 worker-s vs 20.1 headless for equal tile counts; LOD0 p95 45-169 ms in UE. - Fast tier green; all three passages pass on the shared plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
124 lines
5.9 KiB
Python
124 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Run bounded Unreal startup traces at each exported owner-plan arrival."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
import time
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[4]
|
|
HOST = Path("/home/jahni/VFHost")
|
|
PLUGIN_SAVED = ROOT / "Saved/Linux/l70"
|
|
HOST_PLUGIN_SAVED = HOST / "Plugins/VoxelForge/Saved/Linux/l70"
|
|
HOST_SAVED = HOST / "Saved/Linux/l70"
|
|
EDITOR = Path("/home/jahni/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd")
|
|
TRACES = PLUGIN_SAVED / "traces"
|
|
SPAWNS_M = {
|
|
0: (8.0, 16.0, -372.2626953125),
|
|
1: (25.8369140625, 3.19921875, -737.0),
|
|
2: (19.166015625, -14.2001953125, -753.3740234375),
|
|
3: (-20.0625, -5.068359375, -900.353515625),
|
|
}
|
|
|
|
|
|
def run(strate: int, trial: int) -> dict:
|
|
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",
|
|
"-DisablePlugins=AndroidFileServer",
|
|
"-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"-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
|
|
event_names: set[str] = set()
|
|
if trace.is_file():
|
|
for line in trace.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
try:
|
|
event_names.add(json.loads(line).get("type", ""))
|
|
except json.JSONDecodeError:
|
|
continue
|
|
row = {
|
|
"label": label, "strate": strate, "trial": trial, "spawn_m": SPAWNS_M[strate],
|
|
"spawn_cm": spawn_cm, "elapsed_s": round(elapsed, 3), "exit_code": result.returncode,
|
|
"trace": str(trace), "stdout": str(stdout), "events": sorted(event_names),
|
|
"has_walkable_gate": "walkable_gate" in event_names,
|
|
"has_lod0_ring": "lod0_ring_ready" in event_names,
|
|
"has_initial_set": "stream_trace_milestone" in event_names,
|
|
"has_summary": "summary" in event_names,
|
|
}
|
|
print(f"[L70EngineTrace] {json.dumps(row, sort_keys=True)}", flush=True)
|
|
return row
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--strates", nargs="+", type=int, choices=range(4), required=True)
|
|
parser.add_argument("--first-trial", type=int, default=1, choices=range(1, 4))
|
|
parser.add_argument("--trials", type=int, default=3)
|
|
args = parser.parse_args()
|
|
if not EDITOR.is_file():
|
|
raise SystemExit(f"missing UE commandlet executable: {EDITOR}")
|
|
HOST_PLUGIN_SAVED.mkdir(parents=True, exist_ok=True)
|
|
if args.trials < args.first_trial or args.trials > 3:
|
|
parser.error("--trials must be 1..3 and >= --first-trial")
|
|
results = [run(strate, trial) for strate in args.strates
|
|
for trial in range(args.first_trial, args.trials + 1)]
|
|
summary = PLUGIN_SAVED / "traces" / ("engine-summary-" + "-".join(map(str, args.strates)) + ".json")
|
|
summary.parent.mkdir(parents=True, exist_ok=True)
|
|
if summary.is_file():
|
|
try:
|
|
previous = json.loads(summary.read_text(encoding="utf-8"))
|
|
merged = {row["label"]: row for row in previous}
|
|
merged.update({row["label"]: row for row in results})
|
|
results = sorted(merged.values(), key=lambda row: (row["strate"], row["trial"]))
|
|
except (OSError, json.JSONDecodeError, KeyError, TypeError):
|
|
pass
|
|
summary.write_text(json.dumps(results, indent=2) + "\n", encoding="utf-8")
|
|
complete = all(row["exit_code"] == 0 and row["has_walkable_gate"] and row["has_lod0_ring"]
|
|
and row["has_initial_set"] and row["has_summary"] for row in results)
|
|
print(f"[L70EngineTraceSet] strata={args.strates} trials_each={args.trials} "
|
|
f"total={len(results)} status={'PASS' if complete else 'INCOMPLETE'} summary={summary}",
|
|
flush=True)
|
|
return 0 if complete else 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|