Files
horizon-airdef/tools/interaction_lab.py
T
Ludovic Marie 69c7fd925e Port every crafting recipe type and test recipes against vanilla
The crafting module now matches shapeless recipes with a full bipartite assignment, shrinks shaped patterns like ShapedRecipePattern, keeps result components (suspicious stew effects), and implements the transmute, dye, imbue (tipped arrow), book cloning, firework rocket/star/fade, decorated pot, repair item, map extending, banner duplicate and shield decoration recipes with vanilla's matching and assembly. Crafting remainders now follow Item.craftRemainder only and are placed on the grid by recipe, so cloned books and duplicated banners stay. Stacks gain dyed colour, firework explosion, pot decorations, bundle contents, stew effects and map post-processing components, and their NBT now matches the codecs (defaults omitted, templates without count 1). A banner whose first layer was a rhombus was mistaken for the raid banner; that is fixed.

tools/test_recipe_parity.py drives vanilla's RecipeManager (new VanillaRecipeOracle) and the Rust recipe_fixture over 50k generated crafting, cooking, stonecutting and smithing cases and compares matches, results and remainders as canonical NBT; all match.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-09 14:04:56 +02:00

72 lines
2.9 KiB
Python

"""Shared plumbing for the interaction/inventory differential harnesses
(test_recipe_parity, test_menu_click_parity, test_block_interaction_parity):
compile and run a vanilla Java oracle headlessly, run the matching Rust
example, and report differing lines grouped by a key."""
import json
import os
from pathlib import Path
import subprocess
try:
from . import lab, test_rust_parity as base
except ImportError:
import lab
import test_rust_parity as base
RESOURCES = lab.LOCAL / 'horizon-client' / '26.3'
PARITY = lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity'
def work_dir(name):
path = lab.LOCAL / name
path.mkdir(parents=True, exist_ok=True)
return path
class Vanilla:
"""Compiles oracles against the pinned server jar and runs them."""
def __init__(self, work):
self.lock, self.jars, self.libraries = base.verify_artifacts()
self.work = work
self.classes = work / 'classes'
self.classes.mkdir(exist_ok=True)
self.deps = os.pathsep.join(map(str, [self.jars['server'], *self.libraries]))
def compile(self, *oracles):
sources = [PARITY / (name + '.java') for name in oracles]
subprocess.run(['javac', '--release', str(self.lock['minecraft']['java_major']), '-proc:none',
'-sourcepath', str(lab.ROOT / 'mods/horizon/src/test/java'), '-nowarn',
'-cp', self.deps, '-d', str(self.classes), *map(str, sources)], check=True)
def run(self, oracle, *args, heap='2G'):
subprocess.run(['java', f'-Xmx{heap}', '-cp', str(self.classes) + os.pathsep + self.deps,
'dev.horizonlab.parity.' + oracle, *map(str, args)], check=True)
def cargo_example(package, example, *args, profile=None):
command = ['cargo', 'run', '-q', '-j3']
if profile:
command += ['--profile', profile]
command += ['-p', package, '--example', example, '--', *map(str, args)]
subprocess.run(command, check=True, cwd=lab.ROOT)
def compare(cases, native_path, rust_path, key, describe=None, limit=3):
"""Print differing cases grouped by key(case, native_line); returns the count."""
native = Path(native_path).read_text().split('\n')
rust = Path(rust_path).read_text().split('\n')
if len(native) != len(rust):
print(f'line counts differ: native {len(native)} rust {len(rust)}')
groups = {}
for case, a, b in zip(cases, native, rust):
if a != b:
groups.setdefault(key(case, a), []).append((case, a, b))
describe = describe or (lambda case: case if isinstance(case, str) else json.dumps(case))
total = sum(len(v) for v in groups.values())
for name, entries in sorted(groups.items(), key=lambda e: -len(e[1])):
print(f'{name}: {len(entries)} differing')
for case, a, b in entries[:limit]:
print(f' case {describe(case)}\n native {a}\n rust {b}')
print(f'{len(cases) - total}/{len(cases)} cases match')
return total