874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
98 lines
4.7 KiB
Python
Executable File
98 lines
4.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Compare Horizon's environment attributes with the vanilla 26.3 attribute system.
|
|
|
|
The Java oracle builds vanilla's `EnvironmentAttributeSystem` from the data pack
|
|
(dimension attributes, one biome's attributes, the dimension timelines and the
|
|
weather layers) and prints every visual/gameplay attribute for a grid of
|
|
dimensions, world times, rain/thunder levels and biomes.
|
|
`examples/probe/environment_fixture` samples the same cases through Horizon's
|
|
`Environment::sample` + `apply_weather`; every attribute Horizon computes must
|
|
agree with vanilla.
|
|
|
|
test_environment_render_parity.py [--tolerance EPS]
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
|
|
try:
|
|
from . import lab, test_rust_parity as base, test_model_baking_parity as baking
|
|
except ImportError:
|
|
import lab, test_rust_parity as base, test_model_baking_parity as baking
|
|
|
|
|
|
def cases(resources):
|
|
biomes = sorted(p.stem for p in (resources / 'data/minecraft/worldgen/biome').glob('*.json'))
|
|
out = []
|
|
for dim in ['overworld', 'the_nether', 'the_end']:
|
|
for ticks in list(range(0, 48000, 250)) + [23999, 24001, 1234567]:
|
|
for rain, thunder in [(0, 0), (0.5, 0), (1, 0), (1, 1), (0.7, 0.3)] if dim == 'overworld' else [(0, 0)]:
|
|
out.append(f'{dim} {ticks} {rain} {thunder} plains')
|
|
own = {'the_nether': {'nether_wastes', 'soul_sand_valley', 'crimson_forest', 'warped_forest', 'basalt_deltas'},
|
|
'the_end': {'the_end', 'small_end_islands', 'end_midlands', 'end_highlands', 'end_barrens'}}
|
|
for dim in ['overworld', 'the_nether', 'the_end']:
|
|
for biome in biomes:
|
|
if dim in own and biome not in own[dim]:
|
|
continue
|
|
if dim == 'overworld' and any(biome in b for b in own.values()):
|
|
continue
|
|
for ticks in (0, 6000, 12500, 18000):
|
|
for rain, thunder in [(0, 0), (1, 0.4)] if dim == 'overworld' else [(0, 0)]:
|
|
out.append(f'{dim} {ticks} {rain} {thunder} {biome}')
|
|
return out
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--tolerance', type=float, default=2e-5)
|
|
args = parser.parse_args()
|
|
lock, jars, libraries = base.verify_artifacts()
|
|
build = lab.LOCAL / 'rust-environment-parity'; build.mkdir(parents=True, exist_ok=True)
|
|
resources = lab.RESOURCES
|
|
inputs = build / 'inputs.txt'; inputs.write_text('\n'.join(cases(resources)) + '\n')
|
|
classes = build / 'classes'; classes.mkdir(exist_ok=True)
|
|
oracle = lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaEnvironmentOracle.java'
|
|
deps = os.pathsep.join(map(str, libraries))
|
|
|
|
def run(command, name):
|
|
with (build / (name + '.log')).open('w') as log:
|
|
result = subprocess.run(list(map(str, command)), cwd=lab.ROOT, timeout=3600, stdout=log, stderr=subprocess.STDOUT)
|
|
if result.returncode:
|
|
print((build / (name + '.log')).read_text()[-4000:]); sys.exit(f'{name} failed')
|
|
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none', '-sourcepath', lab.ROOT / 'mods/horizon/src/test/java',
|
|
'-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.VanillaEnvironmentOracle', inputs, build / 'vanilla.jsonl'], 'vanilla')
|
|
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe', '--',
|
|
'environment_fixture', resources, inputs, build / 'rust.jsonl'], 'rust')
|
|
vanilla = [json.loads(l) for l in (build / 'vanilla.jsonl').read_text().splitlines()]
|
|
rust = [json.loads(l) for l in (build / 'rust.jsonl').read_text().splitlines()]
|
|
assert len(vanilla) == len(rust), (len(vanilla), len(rust))
|
|
bad = {}
|
|
uncovered = set()
|
|
for v, r in zip(vanilla, rust):
|
|
for key, expected in v.items():
|
|
if key == 'case':
|
|
continue
|
|
if key not in r:
|
|
uncovered.add(key); continue
|
|
got = r[key]
|
|
pairs = zip(expected, got) if isinstance(expected, list) else [(expected, got)]
|
|
if any(abs(a - b) > args.tolerance * (1 + abs(a)) for a, b in pairs):
|
|
bad.setdefault(key, []).append((v['case'], expected, got))
|
|
print(f'{len(vanilla)} cases compared')
|
|
for key, items in sorted(bad.items()):
|
|
print(f'MISMATCH {key}: {len(items)} cases, e.g. {items[0]}')
|
|
if uncovered:
|
|
print('attributes Horizon does not model:', ', '.join(sorted(uncovered)))
|
|
if bad:
|
|
sys.exit(f'{len(bad)} attributes differ from vanilla')
|
|
print('environment parity ok')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|