#!/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))