Files

95 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Independent interchange acceptance using nbtlib and official Java26.2 APIs.
Run: artifacts/compat-venv/bin/python scripts/compat_verify.py
Needs nbtlib==2.0.4 and the pinned catalog-cache JAR/JDK25 from catalog generation.
Never starts MinecraftServer or accepts an EULA. Writes only artifacts/.
"""
import argparse
import hashlib
import json
import re
import sqlite3
import subprocess
import tempfile
from pathlib import Path
import nbtlib
ROOT=Path(__file__).resolve().parents[1]
parser=argparse.ArgumentParser()
parser.add_argument("--java-bin",type=Path,default=Path.home()/".local/share/PrismLauncher/java/java-runtime-epsilon/bin")
args=parser.parse_args()
output=Path(tempfile.mkdtemp(prefix="compat-verification-",dir=ROOT/"artifacts"))
def command(argv,**kwargs):
cwd=kwargs.pop("cwd",ROOT)
result=subprocess.run(list(map(str,argv)),cwd=cwd,text=True,capture_output=True,timeout=120,**kwargs)
if result.returncode:
raise RuntimeError(f"Command failed: {argv}\n{result.stdout}\n{result.stderr}")
return result.stdout
command(["cargo","build","-p","shacraft-compat","-p","shacraft-tools"])
compat=ROOT/"target/debug/shacraft-compat";tools=ROOT/"target/debug/shacraft-tools"
def convert(*argv):return json.loads(command([compat,*argv]))
def tool(store,request):
result=json.loads(command([tools,"--data",store,"session"],input=json.dumps(request)+"\n"))
assert result["ok"],result
return result["result"]
source=ROOT/"crates/shacraft-compat/fixtures/java26_2-world"
store=output/"imported"
import_report=convert("import-anvil",source,store)
exact=output/"exact"
convert("export-anvil",store,exact)
for f in source.rglob("*"):
if f.is_file():assert f.read_bytes()==(exact/f.relative_to(source)).read_bytes()
stone=tool(store,{"op":"register","state":"minecraft:stone"})
revision=tool(store,{"op":"revision","world":"main"})
tool(store,{"op":"edit","world":"main","expected_revision":revision,"operation_id":"independent-edit",
"changes":[{"pos":[-1,-1,31],"block":0},{"pos":[512,64,-513],"block":stone}]})
with sqlite3.connect(store/"server.sqlite3") as db:
meta=json.loads(db.execute("select json from metadata where id=1").fetchone()[0]); entity=next(iter(meta["entities"].values()))
entity["position"]=[513.5,65.,-513.5];entity["yaw"]=1.25;entity["properties"]["Invisible"]=True
meta["revision"]+=1;db.execute("update metadata set json=? where id=1",(json.dumps(meta),))
edited=output/"edited";edited_report=convert("export-anvil",store,edited,"--mode","best-effort")
native=output/"native"
tool(native,{"op":"create","name":"own","template":None})
id=tool(native,{"op":"register","state":"minecraft:stone"})
tool(native,{"op":"edit","world":"own","expected_revision":0,"operation_id":"own-build", "changes":[{"pos":[-1,63,0],"block":id},{"pos":[0,64,0],"block":id}]})
new=output/"new-world";new_report=convert("export-anvil",native,new,"--world","own","--mode","best-effort")
schem_store=output/"schem-store";schem_source=ROOT/"crates/shacraft-compat/fixtures/sponge-v3.schem"
convert("import-schem",schem_source,schem_store)
revision=tool(schem_store,{"op":"revision","world":"main"})
tool(schem_store,{"op":"edit","world":"main","expected_revision":revision,"operation_id":"break-chest", "changes":[{"pos":[-17,-1,31],"block":0}]})
schem=output/"schem";convert("export-schem",schem_store,schem,"--mode","best-effort")
a=nbtlib.load(schem_source)["Schematic"];b=nbtlib.load(schem/"world.schem")["Schematic"]
def typed(tag):
if isinstance(tag,nbtlib.Compound):return ("Compound",tuple((k,typed(v)) for k,v in sorted(tag.items())))
if isinstance(tag,nbtlib.List):return ("List",tag.subtype.__name__,tuple(typed(v) for v in tag))
if hasattr(tag,"dtype"):return (type(tag).__name__,tuple(int(v) for v in tag))
return (type(tag).__name__,str(tag))
for key in ["Metadata","Biomes","Entities","Offset","UnknownRoot"]:
assert typed(a[key])==typed(b[key]),key
assert not b["Blocks"]["BlockEntities"]
assert isinstance(b["Metadata"]["Long"],nbtlib.Long)
assert isinstance(b["Metadata"]["EmptyLongList"],nbtlib.List[nbtlib.Long])
cache=ROOT/"artifacts/catalog-cache"
jar=cache/"server.jar"
assert hashlib.sha1(jar.read_bytes()).hexdigest()=="823e2250d24b3ddac457a60c92a6a941943fcd6a"
cp=":".join(map(str,[cache/"versions/26.2/server-26.2.jar",*cache.glob("libraries/**/*.jar")]))
classes=output/"java-classes";classes.mkdir()
command([args.java_bin/"javac","-cp",cp,"-d",classes,ROOT/"scripts/compat_java.java"])
java_results=[]
for label,world in [("exact",exact),("edited",edited),("new",new)]:
text=command([args.java_bin/"java","-Xmx768m","-cp",str(classes)+":"+cp,"compat_java","validate",world],cwd=output)
(output/f"java-{label}.log").write_text(text)
line=next(line[line.index('{'):] for line in text.splitlines() if '"java26_2_verified"' in line)
result=json.loads(line);result["case"]=label;java_results.append(result)
assert [r["nonair_blocks"] for r in java_results]==[3,3,2]
summary={"success":True,"directory":str(output),"java":java_results,"nbtlib_version":nbtlib.__version__,
"source_jar_sha1":hashlib.sha1(jar.read_bytes()).hexdigest(),"source_jar_sha256":hashlib.sha256(jar.read_bytes()).hexdigest(),
"nbtlib_sponge_typed_roundtrip":True,"exact_source_files_identical":True,
"edited_world":edited_report,"native_world":new_report,
"limitations":"Java checks official disk/container/palette/world-metadata codecs, not running server, client renderer, light simulation or EULA acceptance."}
(output/"verification.json").write_text(json.dumps(summary,ensure_ascii=False,indent=2)+"\n")
print(json.dumps({"success":True,"report":str(output/"verification.json"),"java":java_results},indent=2))