6396e8c161
tools/parity.py traces each harness (its Python, Java oracle sources and the Rust sources of the probes it runs, from Cargo's dep-info) and reruns it only when one of those, the vanilla artifacts or the toolchains change. Harnesses run in parallel within the available memory, and their probes build without LTO and with 16 codegen units into .local/parity-target: a one-line change rebuilds every horizon-world probe in 3 seconds instead of 122. The full suite takes about 20 minutes instead of an hour. tools/gate.py is the check a branch passes before integration, and tools/supervise.py manages the per-area worktrees: status, new worktrees, saving unfinished work, and integrating a branch only when the gate passes. Harnesses find probes through lab.cargo_target() instead of a hard-coded target/, the save round-trip harness builds the binaries it launches, and the mob AI harness now fails on divergences like every other harness (--report-only keeps the old behaviour). Debug builds keep line tables only, so worktrees stay small. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
114 lines
4.8 KiB
Python
114 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare structure placement (terrain + structure pieces, no placed features)
|
|
between vanilla and Rust for target chunks.
|
|
|
|
usage: compare_structure_blocks.py RESOURCES SEED DIMENSION CX,CZ...
|
|
Runs VanillaStructurePlacementOracle and the structure_blocks example and
|
|
reports, per chunk, the blocks and block entities that differ.
|
|
"""
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
try:
|
|
from . import lab, test_rust_parity as base
|
|
except ImportError:
|
|
import lab
|
|
import test_rust_parity as base
|
|
|
|
|
|
def build_dir():
|
|
return Path(os.environ['HORIZON_ORACLE_BUILD']) if os.environ.get('HORIZON_ORACLE_BUILD') else lab.LOCAL/'structure-placement-oracle'
|
|
|
|
|
|
def run_oracle(out, args):
|
|
lock, jars, libraries = base.verify_artifacts()
|
|
source = lab.ROOT/'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaStructurePlacementOracle.java'
|
|
classes = build_dir()/'classes'
|
|
classes.mkdir(parents=True, exist_ok=True)
|
|
dependencies = os.pathsep.join(map(str, [jars['server'], *libraries]))
|
|
compiled = subprocess.run(['javac', '--release', str(lock['minecraft']['java_major']), '-proc:none', '-cp', dependencies,
|
|
'-d', str(classes), str(source)], capture_output=True, text=True)
|
|
if compiled.returncode:
|
|
raise SystemExit('javac failed:\n' + compiled.stderr[-3000:])
|
|
result = subprocess.run(['java', '-Xmx4G', '-cp', str(classes)+os.pathsep+dependencies,
|
|
'dev.horizonlab.parity.VanillaStructurePlacementOracle', str(out), *args],
|
|
capture_output=True, text=True)
|
|
if result.returncode:
|
|
lines = [line for line in (result.stdout + result.stderr).splitlines() if 'Exception' in line or 'Error' in line or '\tat ' in line]
|
|
raise SystemExit('oracle failed: ' + ' | '.join(lines[:8]))
|
|
|
|
|
|
def parse(lines):
|
|
chunks, current = {}, None
|
|
for line in lines:
|
|
t = line.split()
|
|
if not t:
|
|
continue
|
|
if t[0] == 'chunk':
|
|
current = (int(t[1]), int(t[2]))
|
|
chunks[current] = {'rows': {}, 'be': []}
|
|
elif t[0] == 'y' and current:
|
|
chunks[current]['rows'][int(t[1])] = t[2:]
|
|
elif t[0] == 'be' and current:
|
|
# Position, loot table and seed; the block entity type follows
|
|
# from the (separately compared) block state.
|
|
chunks[current]['be'].append(' '.join(t[1:4] + t[5:]))
|
|
return chunks
|
|
|
|
|
|
def state_names(resources):
|
|
names = {}
|
|
report = lab.LOCAL/'datagen/out/reports/blocks.json'
|
|
if report.exists():
|
|
for block, info in json.loads(report.read_text()).items():
|
|
for state in info['states']:
|
|
props = ','.join(f'{k}={v}' for k, v in sorted(state.get('properties', {}).items()))
|
|
names[str(state['id'])] = block + (f'[{props}]' if props else '')
|
|
return names
|
|
|
|
|
|
def main():
|
|
resources, seed, dimension, targets = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4:]
|
|
out = build_dir()/'vanilla.txt'
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
run_oracle(out, [seed, dimension, *targets])
|
|
vanilla = parse(out.read_text().splitlines())
|
|
subprocess.run(['cargo', 'build', '--profile', 'fast', '-q', '-p', 'horizon-world', '--example', 'structure_blocks'],
|
|
check=True, cwd=lab.ROOT)
|
|
rust_out = subprocess.run([str(lab.cargo_target()/'fast/examples/structure_blocks'), resources, seed, dimension, *targets],
|
|
check=True, capture_output=True, text=True).stdout
|
|
(build_dir()/'rust.txt').write_text(rust_out)
|
|
rust = parse(rust_out.splitlines())
|
|
names = state_names(resources)
|
|
failed = 0
|
|
for key in sorted(vanilla):
|
|
v, r = vanilla[key], rust.get(key)
|
|
if r is None:
|
|
print(key, 'missing from Rust')
|
|
failed += 1
|
|
continue
|
|
diffs = []
|
|
for y, row in v['rows'].items():
|
|
other = r['rows'].get(y, [])
|
|
for i, (a, b) in enumerate(zip(row, other)):
|
|
if a != b:
|
|
diffs.append((key[0] * 16 + i % 16, y, key[1] * 16 + i // 16, a, b))
|
|
total = sum(len(row) for row in v['rows'].values())
|
|
be_v, be_r = set(v['be']), set(r['be'])
|
|
status = 'match' if not diffs and be_v == be_r else 'DIFF'
|
|
print(f'chunk {key}: {status}; {total - len(diffs)} of {total} blocks equal; block entities {len(be_v & be_r)} of {len(be_v)} equal')
|
|
for x, y, z, a, b in diffs[:12]:
|
|
print(f' {x} {y} {z} vanilla {names.get(a, a)} rust {names.get(b, b)}')
|
|
for e in sorted(be_v - be_r)[:6]:
|
|
print(' only vanilla be', e)
|
|
for e in sorted(be_r - be_v)[:6]:
|
|
print(' only rust be', e)
|
|
failed += status != 'match'
|
|
return 1 if failed else 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|