874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
96 lines
4.5 KiB
Python
Executable File
96 lines
4.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Compare mob default attributes, dimensions (adult and baby) and the entity inventory with vanilla."""
|
|
import argparse
|
|
import math
|
|
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
|
|
|
|
# Entity types that are not mobs (or are players): Horizon models them outside the mob table.
|
|
# Horizon models these differently on purpose: slimes/magma/sulfur cubes are created already sized (size 1,
|
|
# vanilla applies setSize at spawn), and the phantom keeps its move control speed in attrs.speed.
|
|
KNOWN = {('minecraft:slime', k) for k in ('max_health', 'movement_speed', 'attack_damage')} | \
|
|
{('minecraft:magma_cube', k) for k in ('max_health', 'movement_speed', 'attack_damage')} | \
|
|
{('minecraft:sulfur_cube', k) for k in ('max_health', 'movement_speed')} | {('minecraft:phantom', 'movement_speed')}
|
|
|
|
|
|
def parse(path):
|
|
rows = {}
|
|
for line in path.read_text(encoding='utf-8').splitlines():
|
|
id, *fields = line.split('\t')
|
|
row = {'raw': fields}
|
|
for f in fields:
|
|
key, _, rest = f.partition(' ')
|
|
if key == 'attrs':
|
|
row['attrs'] = {k: float(v) for k, v in (p.split('=') for p in rest.split())}
|
|
elif key in ('dim', 'baby'):
|
|
try:
|
|
row[key] = [float(x) for x in rest.split()]
|
|
except ValueError:
|
|
pass
|
|
elif key == 'scale':
|
|
row['scale'] = float(rest)
|
|
rows[id] = row
|
|
return rows
|
|
|
|
|
|
def close(a, b):
|
|
return a == b or abs(a - b) <= 1e-6 * max(1.0, abs(a), abs(b)) or (math.isnan(a) and math.isnan(b))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--prepare', action='store_true')
|
|
args = parser.parse_args()
|
|
build = lab.LOCAL / 'rust-mob-attributes-parity'
|
|
build.mkdir(parents=True, exist_ok=True)
|
|
if args.prepare:
|
|
lab.prepare()
|
|
lock, jars, libraries = base.verify_artifacts()
|
|
oracle = lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaMobAttributesOracle.java'
|
|
classes = build / 'classes'
|
|
classes.mkdir(exist_ok=True)
|
|
deps = os.pathsep.join(map(str, libraries))
|
|
|
|
def run(cmd, name):
|
|
with (build / (name + '.log')).open('w', encoding='utf-8') as log:
|
|
subprocess.run(list(map(str, cmd)), cwd=lab.ROOT, check=True, timeout=3600, stdout=log, stderr=subprocess.STDOUT)
|
|
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none', '-cp', str(jars['server']) + os.pathsep + deps, '-d', classes, oracle], 'compile-java')
|
|
run(['java', '-Xmx1G', '-cp', os.pathsep.join([str(classes), str(jars['server']), deps]), 'dev.horizonlab.parity.VanillaMobAttributesOracle', build / 'vanilla.txt'], 'java')
|
|
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe'], 'compile-rust')
|
|
binary = lab.cargo_target() / 'fast/examples/client_probe'
|
|
resources = lab.RESOURCES
|
|
run([binary, 'mob_attributes_fixture', resources, build / 'rust.txt'], 'rust')
|
|
vanilla, rust = parse(build / 'vanilla.txt'), parse(build / 'rust.txt')
|
|
problems = []
|
|
living = [i for i, r in vanilla.items() if 'attrs' in r]
|
|
missing = [i for i in vanilla if i not in rust]
|
|
(build / 'missing.txt').write_text('\n'.join(missing) + '\n')
|
|
failed = [i for i, r in vanilla.items() if 'raw' in r and r['raw'][:1] == ['FAIL']]
|
|
for id, v in vanilla.items():
|
|
r = rust.get(id)
|
|
if r is None:
|
|
continue
|
|
if 'dim' in v and 'dim' in r and not all(close(a, b) for a, b in zip(v['dim'], r['dim'])):
|
|
problems.append(f'{id}: dim vanilla={v["dim"]} rust={r["dim"]}')
|
|
if 'baby' in v and not all(close(a, b) for a, b in zip(v['baby'], r['baby'])):
|
|
problems.append(f'{id}: baby vanilla={v["baby"]} rust={r["baby"]}')
|
|
for k, x in v.get('attrs', {}).items():
|
|
if (id, k) not in KNOWN and k in r['attrs'] and not close(x, r['attrs'][k]):
|
|
problems.append(f'{id}: {k} vanilla={x} rust={r["attrs"][k]}')
|
|
print(f'vanilla types {len(vanilla)} (living {len(living)}), Horizon mob types {len(rust)}, not in mob table {len(missing)}: {" ".join(m.split(":")[1] for m in missing)}')
|
|
print('oracle could not instantiate:', ' '.join(f.split(':')[1] for f in failed))
|
|
if problems:
|
|
print('\n'.join(problems))
|
|
raise SystemExit(f'{len(problems)} divergences')
|
|
print('Mob attribute/dimension parity passed')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|