874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
193 lines
9.2 KiB
Python
193 lines
9.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare moving-piston push geometry and predicates with pinned vanilla.
|
|
|
|
`PistonMovingBlockEntity.moveCollidedEntities` decides how far an entity is
|
|
pushed; the movement it describes is `Entity.move(MoverType.PISTON, ...)`, whose
|
|
collision and per-tick limit the game owns. This harness covers the deciding
|
|
half: the movement area, the offset block box, `getMovement` on every face, the
|
|
per-box loop, `moveStuckEntities`' box and `matchesStickyCritera`, and
|
|
`fixEntityWithinPistonBase`.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import platform
|
|
import random
|
|
import struct
|
|
import subprocess
|
|
try:
|
|
from . import lab, test_rust_parity as base
|
|
except ImportError:
|
|
import lab
|
|
import test_rust_parity as base
|
|
|
|
|
|
def corpus():
|
|
def f32(v): return struct.pack('>f', float(v)).hex()
|
|
def f64(v): return struct.pack('>d', float(v)).hex()
|
|
def box(values): return [f64(v) for v in values]
|
|
step = [1.0 / 3.0, 0.25, 0.5, 0.75, 1.0]
|
|
unit = [0.0, 0.0, 0.0, 1.0, 1.0, 1.0]
|
|
offsets = [[0, 0, 0], [-17, -64, 30000000], [2147483647, -2147483648, -2147483648]]
|
|
faces = [0, 1, 2, 3, 4, 5]
|
|
# Progress pairs around the 0.25/0.5/1.0 thresholds the head and the
|
|
# extended-progress offset change at.
|
|
progress = [0.0, 0.25, 0.49999997, 0.5, 0.75, 0.99999994, 1.0]
|
|
entities = [
|
|
[0.9, 0.0, 0.2, 1.5, 1.8, 0.8],
|
|
[1.4, 0.0, 0.2, 2.0, 1.8, 0.8],
|
|
[0.75, 0.0, 0.2, 1.35, 1.8, 0.8],
|
|
[3.0, 0.0, 0.2, 3.6, 1.8, 0.8],
|
|
[0.49, 0.0, 0.49, 0.51, 1.8, 0.51],
|
|
[0.0, 1.0, 0.0, 1.0, 2.8, 1.0],
|
|
]
|
|
for index, position in enumerate(offsets):
|
|
for direction in faces:
|
|
for extending in (False, True):
|
|
for source in (False, True):
|
|
for current in progress:
|
|
advanced = min(current + 0.5, 1.0)
|
|
for entity in entities:
|
|
yield {
|
|
'pos': position,
|
|
'direction': direction,
|
|
'extending': extending,
|
|
'source': source,
|
|
'progress': f32(current),
|
|
'new_progress': f32(advanced),
|
|
'block_box': box(unit),
|
|
'movement_box': box(unit),
|
|
'entity': box(entity),
|
|
'boxes': [box(unit)],
|
|
'sticky_top': f64(0.0),
|
|
'reaction': 0,
|
|
'on_ground': True,
|
|
'supported': False,
|
|
'entity_x': f64(entity[0]),
|
|
'entity_z': f64(entity[2]),
|
|
}
|
|
# Split boxes, a retracting honey drag with every push reaction, and the
|
|
# predicate's supported/on-ground corners.
|
|
for index in range(4000):
|
|
rng = random.Random(0x50555348 + index)
|
|
current = rng.choice(progress)
|
|
advanced = min(current + 0.5, 1.0)
|
|
count = rng.randint(1, 4)
|
|
boxes = []
|
|
for _ in range(count):
|
|
low = [rng.choice([0.0, 0.25, 0.5]), rng.choice([0.0, 0.5]), rng.choice([0.0, 0.5])]
|
|
high = [min(v + rng.choice([0.25, 0.5, 1.0]), 1.0) for v in low]
|
|
boxes.append(box(low + high))
|
|
yield {
|
|
'pos': rng.choice(offsets),
|
|
'direction': rng.randrange(6),
|
|
'extending': rng.random() < 0.5,
|
|
'source': rng.random() < 0.5,
|
|
'progress': f32(current),
|
|
'new_progress': f32(advanced),
|
|
'block_box': boxes[0],
|
|
'movement_box': boxes[rng.randrange(len(boxes))],
|
|
'entity': box([
|
|
rng.choice([0.0, 0.49, 0.75, 0.9, 1.4]),
|
|
rng.choice([0.0, 0.5]),
|
|
rng.choice([0.0, 0.2, 0.49]),
|
|
rng.choice([0.51, 1.35, 1.5, 2.0]),
|
|
rng.choice([1.8, 2.8]),
|
|
rng.choice([0.51, 0.8, 1.0]),
|
|
]),
|
|
'boxes': boxes,
|
|
'sticky_top': f64(rng.choice([0.0, 0.5, 0.9375])),
|
|
'reaction': rng.randrange(5),
|
|
'on_ground': rng.random() < 0.5,
|
|
'supported': rng.random() < 0.5,
|
|
'entity_x': f64(rng.choice([0.0, 0.5, 1.0, 2.0])),
|
|
'entity_z': f64(rng.choice([0.0, 0.5, 1.0, 2.0])),
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--prepare', action='store_true')
|
|
args = parser.parse_args()
|
|
build = lab.LOCAL / 'rust-piston-push-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/VanillaPistonPushOracle.java'
|
|
helpers = [oracle.with_name(name + '.java')
|
|
for name in ['VanillaCollisionOracle', 'VanillaBlockStateCodecOracle', 'VanillaFaceSupportOracle']]
|
|
sources = [oracle, *helpers, 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', lab.ROOT / 'crates/horizon-world/Cargo.toml',
|
|
*sorted((lab.ROOT / 'crates/horizon-core/src').rglob('*.rs')),
|
|
*sorted((lab.ROOT / 'crates/horizon-world/src').rglob('*.rs')),
|
|
lab.ROOT / 'crates/horizon-world/examples/probe/piston_push_fixture.rs']
|
|
tested_sources = base.source_hashes(sources)
|
|
classes = build / 'classes'
|
|
classes.mkdir(exist_ok=True)
|
|
|
|
def run(command, name):
|
|
with (build / (name + '.log')).open('w', encoding='utf-8') as log:
|
|
base.run(list(map(str, command)), stdout=log, stderr=subprocess.STDOUT)
|
|
|
|
dependency_path = os.pathsep.join(map(str, libraries))
|
|
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none',
|
|
'-sourcepath', str(lab.ROOT / 'mods/horizon/src/test/java'),
|
|
'-cp', str(jars['server']) + os.pathsep + dependency_path,
|
|
'-d', classes, oracle, *helpers], 'compile-java')
|
|
|
|
def java(side, mode, path):
|
|
run(['java', '-Xmx512M', '-cp', os.pathsep.join([str(classes), str(jars[side]), dependency_path]),
|
|
'dev.horizonlab.parity.VanillaPistonPushOracle', mode, path], side + '-' + mode)
|
|
|
|
fixtures = list(corpus())
|
|
inputs = build / 'inputs.txt'
|
|
inputs.write_text(''.join(json.dumps(value, separators=(',', ':')) + '\n' for value in fixtures), encoding='utf-8')
|
|
fixture_paths = [inputs]
|
|
tested_fixtures = base.source_hashes(fixture_paths)
|
|
run(['cargo', 'build', '--release', '-p', 'horizon-world', '--example', 'world_probe'], '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' / ('world_probe.exe' if os.name == 'nt' else 'world_probe')
|
|
run([binary, 'piston_push_fixture', build], 'rust')
|
|
references = []
|
|
for side, jar in jars.items():
|
|
java(side, side, build)
|
|
reference = build / f'{side}.txt'
|
|
matched = base.compare(reference, build / 'rust.txt')
|
|
references.append({'side': side, 'jar_sha256': lab.digest(jar, 'sha256'),
|
|
'output_sha256': lab.digest(reference, 'sha256'), 'matched_output_lines': matched})
|
|
print(f'{side}: {matched:,} piston-push output lines match Rust', flush=True)
|
|
base.ensure_sources_unchanged(sources, tested_sources)
|
|
base.ensure_sources_unchanged(fixture_paths, 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(),
|
|
'fixtures': len(fixtures),
|
|
'sources_sha256': tested_sources,
|
|
'fixtures_sha256': tested_fixtures,
|
|
'rust_output_sha256': lab.digest(build / 'rust.txt', 'sha256'),
|
|
'references': references,
|
|
'scope': 'Actual PistonMovingBlockEntity push geometry and predicates: getExtendedProgress, '
|
|
'moveByPositionAndProgress, PistonMath.getMovementArea, getMovement on every face, the '
|
|
'moveCollidedEntities loop over split shapes with its delta cap and PUSH_OFFSET, the '
|
|
'getEntities search box, and moveStuckEntities\' box with matchesStickyCritera for every push '
|
|
'reaction. fixEntityWithinPistonBase is covered by unit tests only: it calls '
|
|
'Entity.move(MoverType.PISTON, ...) and so needs a live level. Entity selection order, that '
|
|
'collision pass, the per-tick 0.51 limit, block behaviour of the moved state and full gameplay '
|
|
'remain elsewhere.',
|
|
}
|
|
lab.write_json(report_path, report)
|
|
print(f'Scoped piston-push parity passed: {report_path}')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main() |