Files
VoxelForge/Standalone/Research/WorldAssemblerM3/Linux70/CompareResolvedPlansL70.py
T
Fr0zkaandClaude Opus 5.5 bd68aee1ca 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
2026-10-27 06:22:48 +01:00

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())