Files
horizon-airdef/tools/test_collision_parity.py
T
Ludovic Marie cb19660fbe Build Horizon Rust parity foundations and differential validation
Add version-bound core, world data, geometry, generation and feature implementations with native client/server comparison tools and scoped evidence reports. Apply Horizon project branding.

Workspace tests, Clippy and 147 Python tests passed. Full 51-family survival and expanded provider comparisons are still running; complete gameplay parity and a playable Rust client remain unfinished. Private game artifacts and the isolated simple-block placement work are excluded.
2026-10-06 11:24:51 +02:00

79 lines
4.9 KiB
Python

#!/usr/bin/env python3
"""Validate registry-backed cached collisions against the pinned vanilla binaries."""
import argparse
import json
import os
from pathlib import Path
import platform
import subprocess
import tempfile
try:
from . import lab, test_rust_parity
except ImportError:
import lab
import test_rust_parity
def counts(output):
marker = "HORIZON_COLLISION_RESULT "
found = [json.loads(line.split(marker, 1)[1]) for line in output.splitlines() if marker in line]
if len(found) != 1:
raise AssertionError(f"Missing collision counts: {found}")
result = found[0]
if result["registry_states"] != 35723 or result["cached_states"] + result["uncached_states"] != result["registry_states"] or result["queries"] != result["cached_states"] * 6 or result["unique_shapes"] <= 0:
raise AssertionError(f"Incomplete collision coverage: {result}")
return result
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--prepare", action="store_true")
args = parser.parse_args()
directory = lab.LOCAL / "rust-collision-parity"
directory.mkdir(parents=True, exist_ok=True)
report_path = directory / "report.json"
report_path.unlink(missing_ok=True)
if args.prepare:
lab.prepare()
lock, jars, libraries = test_rust_parity.verify_artifacts()
base = Path(tempfile.mkdtemp(prefix="run-", dir=directory))
oracle = lab.ROOT / "mods/horizon/src/test/java/dev/horizonlab/parity/VanillaCollisionOracle.java"
sources = [oracle, Path(__file__).resolve(), lab.ROOT / "tools/test_rust_parity.py", lab.ROOT / "Cargo.toml", lab.ROOT / "Cargo.lock", lab.ROOT / "crates/horizon-core/Cargo.toml", lab.ROOT / "crates/horizon-world/Cargo.toml", *sorted((lab.ROOT / "crates/horizon-core/src").rglob("*.rs")), *sorted((lab.ROOT / "crates/horizon-world/src").rglob("*.rs")), lab.ROOT / "crates/horizon-world/examples/collision_fixture.rs"]
tested_sources = test_rust_parity.source_hashes(sources)
classes = base / "classes"
classes.mkdir()
dependency_path = os.pathsep.join(map(str, libraries))
test_rust_parity.run(["javac", "--release", "25", "-proc:none", "-cp", os.pathsep.join([str(jars["server"]), dependency_path]), "-d", str(classes), str(oracle)])
def java(side, mode):
result = subprocess.run(["java", "-Xmx1G", "-cp", os.pathsep.join([str(classes), str(jars[side]), dependency_path]), "dev.horizonlab.parity.VanillaCollisionOracle", mode, str(base)], cwd=lab.ROOT, capture_output=True, text=True, timeout=300)
(base / f"{side}-{mode}.log").write_text(result.stdout + result.stderr, encoding="utf-8")
result.check_returncode()
return result.stdout
expected = counts(java("server", "generate"))
test_rust_parity.run(["cargo", "build", "--release", "-p", "horizon-world", "--example", "collision_fixture"])
metadata = json.loads(subprocess.check_output(["cargo", "metadata", "--no-deps", "--format-version", "1"], cwd=lab.ROOT, text=True))
binary = Path(metadata["target_directory"]) / "release/examples" / ("collision_fixture.exe" if os.name == "nt" else "collision_fixture")
actual = counts(subprocess.check_output([str(binary), str(base)], cwd=lab.ROOT, text=True))
if actual != expected:
raise AssertionError(f"Collision coverage differs: vanilla={expected}, Rust={actual}")
references = []
for side, jar in jars.items():
java(side, side)
reference = base / f"vanilla-{side}.txt"
matched = test_rust_parity.compare(reference, base / "rust.txt")
references.append({"side": side, "jar_sha256": lab.digest(jar, "sha256"), "output_sha256": lab.digest(reference, "sha256"), "matched_output_lines": matched, **expected})
print(f"{side}: {matched:,} cached collision output lines match", flush=True)
test_rust_parity.ensure_sources_unchanged(sources, tested_sources)
report = {"schema": 1, "minecraft": lock["minecraft"]["version"], "manifest_sha1": lock["minecraft"]["manifest_sha1"], "platform": platform.platform(),
"java": subprocess.check_output(["java", "--version"], text=True).splitlines()[0], "rustc": subprocess.check_output(["rustc", "--version"], text=True).strip(),
"inputs_sha256": lab.digest(base / "inputs.txt", "sha256"), "rust_output_sha256": lab.digest(base / "rust.txt", "sha256"), "sources_sha256": tested_sources, "references": references,
"scope": "Pinned no-context cached collision branch only. Registry identity validation, resident section lookup, grids, discrete extrema including empty shapes, greedy boxes, bounds, translated collide, ray hits, closest points and moved cell lookup. Uncached states and entity-context behavior remain unsupported."}
lab.write_json(report_path, report)
lab.write_json(base / "report.json", report)
print(f"Cached collision comparison passed: {report_path}")
if __name__ == "__main__":
main()