parity: Unreal and headless resolve the same four-strate plan (L70, v214)
- 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
This commit is contained in:
1 parent
81e2452a3d
commit
bd68aee1ca
16 files changed
+939
-62
No files matched your search
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Summarize L70 Unreal spawn traces beside exact-plan headless arrivals."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
SAVED = ROOT / "Saved/Linux/l70"
|
||||
TRACES = SAVED / "traces"
|
||||
HEADLESS = SAVED / "headless-arrivals"
|
||||
OUT = SAVED / "arrival-metrics.json"
|
||||
|
||||
|
||||
def parse_trace(strate: int, trial: int) -> dict:
|
||||
path = TRACES / f"strate{strate}-trial{trial}.trace.jsonl"
|
||||
events = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines()
|
||||
if line.strip()]
|
||||
spawn = next(row for row in events if row.get("type") == "trace_spawn_location")
|
||||
ready_gate = next(row for row in events if row.get("type") == "walkable_gate"
|
||||
and row.get("ready") == 1)
|
||||
ring = next(row for row in events if row.get("type") == "lod0_ring_ready")
|
||||
full = next(row for row in events if row.get("type") == "stream_trace_milestone"
|
||||
and row.get("name") == "initial_set_ready")
|
||||
summary = next(row for row in events if row.get("type") == "summary")
|
||||
tiles = [row for row in events if row.get("type") == "tile"]
|
||||
lod0 = summary["tile_percentiles"]["lod0"]["worker_task_s"]
|
||||
return {
|
||||
"label": f"strate{strate}-trial{trial}",
|
||||
"strate": strate,
|
||||
"trial": trial,
|
||||
"walkable_ring_s": ready_gate["t_s"] - spawn["t_s"],
|
||||
"walkable_ready_tiles": ready_gate.get("ready_tiles"),
|
||||
"walkable_required_tiles": ready_gate.get("required_tiles"),
|
||||
"lod0_ring_s": ring["t_s"] - spawn["t_s"],
|
||||
"lod0_tiles": ring.get("lod0_tiles"),
|
||||
"initial_set_s": full["t_s"] - spawn["t_s"],
|
||||
"initial_set_tiles": full.get("loaded"),
|
||||
"worker_seconds": sum(row.get("worker_task_s", 0.0) for row in tiles),
|
||||
"generate_tile_seconds": sum(row.get("core_generate_s", 0.0) for row in tiles),
|
||||
"mesh_copy_seconds": sum(row.get("mesh_copy_s", 0.0) for row in tiles),
|
||||
"rmc_seconds": sum(row.get("rmc_stream_build_s", 0.0) for row in tiles),
|
||||
"lod0_worker_p50_s": lod0["p50"],
|
||||
"lod0_worker_p95_s": lod0["p95"],
|
||||
"tile_events": len(tiles),
|
||||
"summary_tiles": summary["tile_percentiles"]["all"]["count"],
|
||||
}
|
||||
|
||||
|
||||
def parse_headless(strate: int) -> dict:
|
||||
path = HEADLESS / f"strate{strate}.log"
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
milestone = re.search(r"\[PipelineMilestone\] first_tile_ready=([0-9.]+)s", text)
|
||||
stream = re.search(r"\[StreamPipeline\] ([^\n]+)", text)
|
||||
if not milestone or not stream:
|
||||
raise ValueError(f"missing pipeline timings in {path}")
|
||||
fields = dict(re.findall(r"([A-Za-z0-9_]+)=([^ ]+)", stream.group(1)))
|
||||
worker_rows = re.findall(
|
||||
r"\[WorldAssemblerLODWorker\] lod=(\d+) tiles=(\d+) worker_sum_s=([0-9.]+)"
|
||||
r".*?mc_exclusive_ms=([0-9.]+).*?mesh_assembly_ms=([0-9.]+)", text)
|
||||
return {
|
||||
"strate": strate,
|
||||
"first_tile_s": float(milestone.group(1)),
|
||||
"neighborhood_ready_s": float(fields["neighborhood_ready"].rstrip("s")),
|
||||
"full_detail_ready_s": float(fields["full_detail_ready"].rstrip("s")),
|
||||
"whole_view_ready_s": float(fields["whole_view_ready"].rstrip("s")),
|
||||
"neighborhood_tiles": fields["neighborhood_tiles"],
|
||||
"full_detail_tiles": fields["full_detail_tiles"],
|
||||
"whole_tiles": fields["whole_tiles"],
|
||||
"worker_seconds": sum(float(row[2]) for row in worker_rows),
|
||||
"worker_lods": len(worker_rows),
|
||||
"mc_exclusive_seconds": sum(float(row[3]) for row in worker_rows) / 1000.0,
|
||||
"mesh_assembly_seconds": sum(float(row[4]) for row in worker_rows) / 1000.0,
|
||||
}
|
||||
|
||||
|
||||
def median(rows: list[dict], key: str) -> float:
|
||||
return statistics.median(float(row[key]) for row in rows)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
results = []
|
||||
for strate in range(4):
|
||||
trials = [parse_trace(strate, trial) for trial in range(1, 4)]
|
||||
headless = parse_headless(strate)
|
||||
engine = {key: median(trials, key) for key in (
|
||||
"walkable_ring_s", "lod0_ring_s", "initial_set_s", "worker_seconds",
|
||||
"generate_tile_seconds", "mesh_copy_seconds", "rmc_seconds",
|
||||
"lod0_worker_p50_s", "lod0_worker_p95_s")}
|
||||
engine.update({"walkable_ready_tiles": trials[0]["walkable_ready_tiles"],
|
||||
"walkable_required_tiles": trials[0]["walkable_required_tiles"],
|
||||
"lod0_tiles": trials[0]["lod0_tiles"],
|
||||
"initial_set_tiles": trials[0]["initial_set_tiles"],
|
||||
"trace_tile_events": trials[0]["tile_events"],
|
||||
"trace_summary_tiles": trials[0]["summary_tiles"]})
|
||||
row = {"strate": strate, "engine_median": engine, "engine_trials": trials,
|
||||
"headless": headless,
|
||||
"initial_set_time_gap_percent":
|
||||
100.0 * (engine["initial_set_s"] / headless["whole_view_ready_s"] - 1.0),
|
||||
"worker_sum_gap_percent":
|
||||
100.0 * (engine["worker_seconds"] / headless["worker_seconds"] - 1.0)}
|
||||
results.append(row)
|
||||
print(
|
||||
f"strate={strate} walk={engine['walkable_ring_s']:.3f}s "
|
||||
f"lod0={engine['lod0_ring_s']:.3f}s/{engine['lod0_tiles']} tiles "
|
||||
f"full=UE {engine['initial_set_s']:.3f}s/{engine['initial_set_tiles']} "
|
||||
f"vs headless {headless['whole_view_ready_s']:.3f}s/{headless['whole_tiles']} "
|
||||
f"workers=UE {engine['worker_seconds']:.3f}s/{engine['trace_tile_events']} "
|
||||
f"vs headless {headless['worker_seconds']:.3f}s "
|
||||
f"LOD0 p50/p95={engine['lod0_worker_p50_s']:.6f}/{engine['lod0_worker_p95_s']:.6f}s "
|
||||
f"GenerateTile/mesh-copy/RMC={engine['generate_tile_seconds']:.3f}/"
|
||||
f"{engine['mesh_copy_seconds']:.3f}/{engine['rmc_seconds']:.3f}s "
|
||||
f"gaps={row['initial_set_time_gap_percent']:+.1f}% time,"
|
||||
f"{row['worker_sum_gap_percent']:+.1f}% worker")
|
||||
OUT.write_text(json.dumps(results, indent=2) + "\n", encoding="utf-8")
|
||||
print(f"[L70ArrivalMetrics] wrote {OUT}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user