874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
125 lines
8.1 KiB
Python
125 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare proto-chunk ticks and world-generation tick routing with vanilla."""
|
|
import argparse
|
|
from collections import Counter
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import platform
|
|
import random
|
|
import subprocess
|
|
try:
|
|
from . import lab, test_rust_parity as base
|
|
except ImportError:
|
|
import lab, test_rust_parity as base
|
|
|
|
|
|
def corpus():
|
|
extremes = [-9223372036854775808, -2147483648, -1, 0, 1, 2147483647, 9223372036854775807]
|
|
for time in extremes:
|
|
yield 'reset'
|
|
yield 'load -1 -1 4 0 -1 64 -1 -2147483648 -100 0 -1 64 -1 2147483647 100 1 -1 64 -1 -1 0 2 -2 65 -2 12 3'
|
|
yield 'query 0 -1 64 -1'
|
|
yield 'snapshot -1 -1'
|
|
yield f'schedule 0 -1 64 -1 {time} -3 {time}'
|
|
yield f'schedule 3 -3 66 -3 {time} 2 {time}'
|
|
yield f'dump -1 -1 {time}'
|
|
yield 'snapshot_dump'
|
|
yield f'promote -1 -1 {time}'
|
|
yield f'dump -1 -1 {time}'
|
|
yield f'schedule 4 -4 67 -4 {time} -2 {time}'
|
|
yield f'dump -1 -1 {time}'
|
|
yield 'snapshot_dump'
|
|
for kind in ['load', 'level']:
|
|
yield 'reset'
|
|
# Deliberately load a position outside the owning container; routing is
|
|
# determined by each request's position, not by entries in saved lists.
|
|
yield f'{kind} 0 0 2 0 16 64 0 7 0 0 16 64 0 8 -3'
|
|
yield 'query 0 16 64 0'
|
|
yield 'dump 0 0 0'
|
|
yield 'dump 1 0 0'
|
|
yield 'fail 1'
|
|
yield 'query 0 0 64 0'
|
|
yield 'schedule 1 0 64 0 0 0 0'
|
|
yield 'will 1 0 64 0'
|
|
yield 'count'
|
|
yield 'fail 0'
|
|
yield 'query 1 0 64 0'
|
|
yield 'unpack 0 0 9223372036854775807'
|
|
yield 'dump 0 0 -9223372036854775808'
|
|
positions = [[-2147483648, -2147483648, 2147483647], [2147483647, 2147483647, -2147483648],
|
|
[0, 0, 0], [67108864, 4096, -67108864], [-1, -1, -1], [15, 319, 15], [16, -64, 16]]
|
|
for priority in [-2147483648, -4, -3, -2, -1, 0, 1, 2, 3, 4, 2147483647]:
|
|
yield 'reset'
|
|
for i, p in enumerate(positions):
|
|
xyz = ' '.join(map(str, p))
|
|
yield f'schedule {i} {xyz} -9223372036854775808 {priority} 9223372036854775807'
|
|
yield f'schedule {i} {xyz} 9223372036854775807 {-priority if priority != -2147483648 else 0} -9223372036854775808'
|
|
yield f'query {i} {xyz}'
|
|
yield f'will {i} {xyz}'
|
|
yield f'dump {p[0] >> 4} {p[2] >> 4} 12345'
|
|
rng = random.Random(0x484f52495a4f4e)
|
|
for trial in range(12):
|
|
yield 'reset'
|
|
for cx in range(-2, 3):
|
|
command = 'load' if (cx + trial) % 2 == 0 else 'level'
|
|
rows = []
|
|
for i in range(24):
|
|
row = [rng.randrange(8), cx*16+rng.randrange(4), rng.randrange(-3, 4), rng.randrange(4), rng.choice([-2147483648, -1, 0, 1, 2147483647]), rng.randrange(-8, 9)]
|
|
rows.append(' '.join(map(str, row)))
|
|
if i % 3 == 0: rows.append(rows[-1])
|
|
yield f'{command} {cx} 0 {len(rows)} ' + ' '.join(rows)
|
|
for step in range(700):
|
|
cx = rng.randrange(-2, 3); p = [cx*16+rng.randrange(4), rng.randrange(-3, 4), rng.randrange(4)]
|
|
xyz = ' '.join(map(str, p)); typ = rng.randrange(8)
|
|
if step % 71 == 0:
|
|
yield 'fail 1'; yield f'query {typ} {xyz}'; yield f'will {typ} {xyz}'; yield 'count'; yield 'fail 0'
|
|
if step % 79 == 0: yield f'promote {cx} 0 {rng.choice(extremes)}'
|
|
if step % 83 == 0: yield f'snapshot {cx} 0'
|
|
if step % 89 == 0: yield 'snapshot_dump'
|
|
if step % 101 == 0: yield f'remove {cx} 0'
|
|
yield f'schedule {typ} {xyz} {rng.choice(extremes)} {rng.randrange(-8, 9)} {rng.choice(extremes)}'
|
|
yield f'query {typ} {xyz}'
|
|
if step % 23 == 0: yield f'dump {cx} 0 {rng.choice(extremes)}'
|
|
for cx in range(-2, 3): yield f'dump {cx} 0 {rng.choice(extremes)}'
|
|
yield 'snapshot_dump'
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__); parser.add_argument('--prepare', action='store_true'); args = parser.parse_args()
|
|
build = lab.LOCAL / 'rust-generation-tick-parity'; build.mkdir(parents=True, exist_ok=True)
|
|
report_path = build / 'report.json'; report_path.unlink(missing_ok=True)
|
|
if args.prepare: lab.prepare()
|
|
lock, jars, libraries = base.verify_artifacts()
|
|
oracle = lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaGenerationTickOracle.java'
|
|
sources = [oracle, Path(__file__), lab.ROOT / 'tools/test_rust_parity.py', lab.ROOT / 'tools/lab.py', lab.ROOT / 'Cargo.toml', lab.ROOT / 'Cargo.lock', lab.ROOT / 'crates/horizon-core/Cargo.toml',
|
|
*sorted((lab.ROOT / 'crates/horizon-core/src').rglob('*.rs')), lab.ROOT / 'crates/horizon-core/examples/generation_tick_fixture.rs']
|
|
tested_sources = base.source_hashes(sources)
|
|
commands = list(corpus()); inputs = build / 'inputs.txt'; inputs.write_text('\n'.join(commands) + '\n')
|
|
tested_fixtures = base.source_hashes([inputs]); classes = build / 'classes'; classes.mkdir(exist_ok=True)
|
|
dependencies = os.pathsep.join(map(str, libraries))
|
|
def run(command, name, **kwargs):
|
|
with (build / (name + '.log')).open('w') as log:
|
|
subprocess.run(list(map(str, command)), cwd=lab.ROOT, check=True, timeout=3600, stderr=log, **({'stdout': log} | kwargs))
|
|
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none','-sourcepath',str(lab.ROOT/'mods/horizon/src/test/java'), '-cp', str(jars['server']) + os.pathsep + dependencies, '-d', classes, oracle], 'compile-java')
|
|
run(['cargo', 'build', '--release', '-p', 'horizon-core', '--example', 'generation_tick_fixture'], 'compile-rust')
|
|
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--no-deps', '--format-version', '1'], cwd=lab.ROOT, text=True))
|
|
binary = Path(metadata['target_directory']) / 'release/examples' / ('generation_tick_fixture.exe' if os.name == 'nt' else 'generation_tick_fixture')
|
|
with inputs.open('rb') as source, (build / 'rust.txt').open('wb') as output: run([binary], 'rust', stdin=source, stdout=output)
|
|
references = []
|
|
for side, jar in jars.items():
|
|
run(['java', '-Xmx512M', '-cp', os.pathsep.join([str(classes), str(jar), dependencies]), 'dev.horizonlab.parity.VanillaGenerationTickOracle', inputs, build / (side + '.txt')], side)
|
|
matched = base.compare(build / (side + '.txt'), build / 'rust.txt')
|
|
references.append({'side': side, 'jar_sha256': lab.digest(jar, 'sha256'), 'output_sha256': lab.digest(build / (side + '.txt'), 'sha256'), 'matched_output_lines': matched})
|
|
print(f'{side}: {matched:,} generation-tick lines match Rust', flush=True)
|
|
base.ensure_sources_unchanged(sources, tested_sources); base.ensure_sources_unchanged([inputs], tested_fixtures)
|
|
report = {'schema': 1, 'minecraft': lock['minecraft']['version'], 'manifest_sha1': lock['minecraft']['manifest_sha1'], 'platform': platform.platform(),
|
|
'java': subprocess.check_output(['java', '--version'], text=True).splitlines()[0], 'rustc': subprocess.check_output(['rustc', '--version'], text=True).strip(),
|
|
'commands': dict(Counter(command.split()[0] for command in commands)), 'sources_sha256': tested_sources, 'fixtures_sha256': tested_fixtures,
|
|
'rust_output_sha256': lab.digest(build / 'rust.txt', 'sha256'), 'references': references,
|
|
'scope': 'Native ProtoChunkTicks load/schedule deduplication, insertion order, zero delay for scheduled requests, preserved saved delays/priorities, time-independent packing and independent scheduledTicks snapshots. Native WorldGenTickAccess lookup ordering, routing to mixed proto/level containers, lookup failures, false willTickThisTick and zero count without lookups. Proto-to-level promotion and unpacked save behavior, position/type identities, priorities, duplicates, unloaded containers and integer/time extremes are compared. Rust exposes pack as a borrowed immutable slice; mutation of the native returned list is outside the API. Actual block/fluid execution and feature-world scheduling integration remain pending.'}
|
|
lab.write_json(report_path, report); print(f'Scoped generation-tick parity passed: {report_path}')
|
|
|
|
|
|
if __name__ == '__main__': main()
|