- 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
156 lines
6.8 KiB
Python
156 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Normalize and diff the Unreal export, headless import, and pre-export fallback."""
|
|
from __future__ import annotations
|
|
|
|
import difflib
|
|
import json
|
|
import hashlib
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[4]
|
|
OUT = ROOT / "Saved/Linux/l70"
|
|
REPORT_DIR = Path(__file__).resolve().parent
|
|
OWNER = ROOT / "Standalone/data/owner_world.json"
|
|
|
|
|
|
def load(path: Path):
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def normalized_layout(path: Path) -> dict:
|
|
value = load(path)
|
|
# Headless annotates imported entries with the owner's requested passage style.
|
|
# That annotation is not part of the serialized Unreal resolved layout.
|
|
value.pop("resolved_strate_passage_styles", None)
|
|
return value
|
|
|
|
|
|
def canonical(manifest_path: Path, layout_path: Path, *, include_owner_inputs: bool) -> dict:
|
|
manifest = load(manifest_path)
|
|
layout = normalized_layout(layout_path)
|
|
result = {
|
|
"manifest": manifest,
|
|
"layout": layout,
|
|
"native_floating_island_plans": [
|
|
{k: v for k, v in plan.items() if k in ("strate_index", "top_chunk_z", "bottom_chunk_z")}
|
|
for plan in manifest.get("floating_islands", [])
|
|
],
|
|
"generator_recipes": [
|
|
{
|
|
"strate_index": s.get("strate_index"),
|
|
"generator_kind": s.get("generator_kind"),
|
|
"recipe_id": s.get("recipe_id"),
|
|
"recipe_version": s.get("recipe_version"),
|
|
"query_dispatch_path": s.get("query_dispatch_path"),
|
|
}
|
|
for s in layout.get("strates", [])
|
|
],
|
|
}
|
|
if include_owner_inputs:
|
|
owner = load(OWNER)
|
|
result["owner_slots"] = [
|
|
{
|
|
"index": slot.get("index"),
|
|
"top_chunk_z": slot.get("top_chunk_z"),
|
|
"bottom_chunk_z": slot.get("bottom_chunk_z"),
|
|
"height_chunks": slot.get("height_chunks"),
|
|
"generator_recipe_setting": slot.get("asset", {}).get("CoreGeometry", {}).get("GeneratorRecipe"),
|
|
"passage_config": slot.get("passage_config"),
|
|
}
|
|
for slot in owner.get("strates", [])
|
|
]
|
|
result["owner_passages"] = owner.get("passages", [])
|
|
result["effective_layout_width_q"] = manifest.get("recipe", {}).get("layout_chunk_width_q")
|
|
stale = owner.get("settings_asset", {}).get("WorldAssemblyManifestJson", "")
|
|
result["inactive_settings_manifest"] = {
|
|
"sha256": hashlib.sha256(stale.encode("utf-8")).hexdigest() if stale else None,
|
|
"active": owner.get("settings_asset", {}).get("bUseWorldAssemblyManifestOverride", False),
|
|
}
|
|
return result
|
|
|
|
|
|
def json_text(value) -> str:
|
|
return json.dumps(value, indent=2, sort_keys=True, ensure_ascii=False) + "\n"
|
|
|
|
|
|
def write(name: str, value) -> Path:
|
|
path = OUT / name
|
|
path.write_text(json_text(value), encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def main() -> int:
|
|
unreal = canonical(OUT / "manifest-unreal-strict-core.json",
|
|
OUT / "layout-unreal-strict-core.json", include_owner_inputs=True)
|
|
headless = canonical(OUT / "manifest-headless-imported-final.json",
|
|
OUT / "layout-headless-imported-final.json", include_owner_inputs=True)
|
|
unreal_path = write("resolved-plan-unreal-canonical.json", unreal)
|
|
headless_path = write("resolved-plan-headless-canonical.json", headless)
|
|
exact = unreal == headless
|
|
|
|
old = canonical(OUT / "manifest-headless-fallback-current.json",
|
|
OUT / "layout-headless-fallback-current.json", include_owner_inputs=False)
|
|
old_path = write("resolved-plan-headless-fallback-canonical.json", old)
|
|
compared_keys = ("manifest", "layout", "native_floating_island_plans", "generator_recipes")
|
|
unreal_core = {key: unreal[key] for key in compared_keys}
|
|
old_core = {key: old[key] for key in compared_keys}
|
|
old_diffs = list(difflib.unified_diff(
|
|
json_text(unreal_core).splitlines(), json_text(old_core).splitlines(),
|
|
fromfile="unreal-current", tofile="headless-pre-export-fallback", lineterm=""))
|
|
diff_path = OUT / "resolved-plan-diff.txt"
|
|
diff_path.write_text("\n".join(old_diffs) + "\n", encoding="utf-8")
|
|
|
|
# Report compact high-value field deltas for the previous independently planned pair.
|
|
old_layout = old["layout"]
|
|
new_layout = unreal["layout"]
|
|
old_slots = [(s.get("strate_index"), s.get("generator_kind"),
|
|
s.get("interior_bottom_um"), s.get("interior_top_um"))
|
|
for s in old_layout.get("strates", [])]
|
|
new_slots = [(s.get("strate_index"), s.get("generator_kind"),
|
|
s.get("interior_bottom_um"), s.get("interior_top_um"))
|
|
for s in new_layout.get("strates", [])]
|
|
route_rows = []
|
|
for label, layout in (("fallback", old_layout), ("unreal", new_layout)):
|
|
passages = []
|
|
for passage in layout.get("passages", []):
|
|
points = passage.get("control_points", [])
|
|
passages.append({
|
|
"id": passage.get("stable_id"),
|
|
"upper": passage.get("upper_strate_index"),
|
|
"lower": passage.get("lower_strate_index"),
|
|
"style": passage.get("style"),
|
|
"control_point_count": len(points),
|
|
"upper_mouth_q": passage.get("upper_mouth_q"),
|
|
"lower_mouth_q": passage.get("lower_mouth_q"),
|
|
})
|
|
route_rows.append((label, passages))
|
|
lines = [
|
|
f"final_unreal_equals_headless={exact}",
|
|
f"final_manifest_equal={unreal['manifest'] == headless['manifest']}",
|
|
f"final_layout_equal={unreal['layout'] == headless['layout']}",
|
|
f"final_stable_identity={new_layout.get('stable_identity')}",
|
|
f"layout_width_q={unreal['effective_layout_width_q']}",
|
|
f"native_fi_plans={unreal['native_floating_island_plans']}",
|
|
f"owner_slots_current={[(s.get('index'), s.get('top_chunk_z'), s.get('bottom_chunk_z')) for s in unreal['owner_slots']]}",
|
|
f"pre_export_stable_identity={old_layout.get('stable_identity')}",
|
|
f"pre_export_manifest_hash={old['manifest'].get('content_hash')}",
|
|
f"unreal_manifest_hash={unreal['manifest'].get('content_hash')}",
|
|
f"pre_export_slots={old_slots}",
|
|
f"unreal_slots={new_slots}",
|
|
f"routes={json.dumps(route_rows, sort_keys=True)}",
|
|
f"pre_export_diff_lines={len(old_diffs)}",
|
|
f"canonical_unreal={unreal_path}",
|
|
f"canonical_headless={headless_path}",
|
|
f"canonical_fallback={old_path}",
|
|
f"diff={diff_path}",
|
|
]
|
|
result = REPORT_DIR / "resolved-plan-comparison.txt"
|
|
result.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
print("\n".join(lines), flush=True)
|
|
return 0 if exact else 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|