874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
167 lines
9.5 KiB
Python
167 lines
9.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare registered live attribute maps, queues and effect modifiers with vanilla."""
|
|
import argparse
|
|
import json
|
|
import math
|
|
import os
|
|
from pathlib import Path
|
|
import platform
|
|
import random
|
|
import struct
|
|
import subprocess
|
|
|
|
try:
|
|
from . import lab, test_rust_parity as base, test_entity_attribute_parity as entity_base
|
|
except ImportError:
|
|
import lab
|
|
import test_rust_parity as base
|
|
import test_entity_attribute_parity as entity_base
|
|
|
|
|
|
def corpus(catalog):
|
|
yield from entity_base.corpus(catalog)
|
|
|
|
def bits(value):
|
|
return struct.pack('>d', float(value)).hex()
|
|
|
|
def op(mode, attribute=0, **kwargs):
|
|
result = dict(mode=mode, attribute=attribute, id='horizon:a', amount=bits(.125),
|
|
operation=0, base=bits(7), amplifier=0)
|
|
result.update(kwargs)
|
|
return result
|
|
|
|
count = len(catalog['attributes'])
|
|
rng = random.Random(0x4c49564541545452)
|
|
values = [-math.inf, -1e30, -1, -0., 0., 1e-40, .1, 1, 2, 20, 1e16, math.inf, math.nan]
|
|
amplifiers = [-2**31, -1, 0, 1, 255, 2**31-1]
|
|
for entity in catalog['entities']:
|
|
for reverse in [False, True]:
|
|
order = list(range(count))
|
|
if reverse:
|
|
order.reverse()
|
|
ops = [op(mode, -1) for mode in ['has', 'has_modifier', 'value', 'base', 'modifier', 'reset', 'ensure', 'set']]
|
|
for i in order:
|
|
ops.extend(op(mode, i) for mode in ['has', 'value', 'base', 'reset'])
|
|
# The effect loop creates instances in its own native map order.
|
|
for i in [*order, -1]:
|
|
ops.append(op('define', i, operation=i % 3, amount=bits(values[i % len(values)])))
|
|
for amplifier in amplifiers:
|
|
ops.extend([op('clear_updates'), op('clear_sync'), op('effect-add', amplifier=amplifier)])
|
|
for i in order:
|
|
ops.extend(op(mode, i) for mode in ['value', 'modifier', 'has_modifier'])
|
|
ops.extend([op('effect-remove'), op('effect-remove')])
|
|
for i in order:
|
|
ops.extend(op(mode, i) for mode in ['ack_update', 'set', 'ack_sync', 'reset', 'value'])
|
|
ops.extend([op('clear_updates'), op('clear_sync')])
|
|
yield dict(entity=entity, ops=ops)
|
|
|
|
for index, attribute in enumerate(catalog['attributes']):
|
|
entity = next(name for name, defaults in catalog['entities'].items() if attribute['id'] in defaults)
|
|
edges = values.copy()
|
|
for field in ['min', 'max', 'default']:
|
|
if field in attribute:
|
|
value = struct.unpack('>d', bytes.fromhex(attribute[field]))[0]
|
|
edges.extend([math.nextafter(value, -math.inf), value, math.nextafter(value, math.inf)])
|
|
for value in edges:
|
|
ops = [op('set', index, base=bits(value)), op('base', index), op('value', index)]
|
|
for operation in range(3):
|
|
ops.extend([op('define', index, operation=operation, amount=bits(value)),
|
|
op('effect-add', amplifier=255), op('value', index),
|
|
op('effect-remove'), op('value', index)])
|
|
ops.extend([op('reset', index), op('value', index)])
|
|
yield dict(entity=entity, ops=ops)
|
|
|
|
for _ in range(200):
|
|
entity = rng.choice(list(catalog['entities']))
|
|
ops = []
|
|
for _ in range(100):
|
|
ops.append(op(rng.choice(['define', 'effect-add', 'effect-remove', 'ensure', 'set', 'add',
|
|
'permanent', 'update', 'remove', 'clear', 'value', 'base', 'modifier',
|
|
'has_modifier', 'reset', 'ack_update', 'ack_sync', 'clear_updates', 'clear_sync']),
|
|
rng.randrange(-1, count), operation=rng.randrange(3),
|
|
id=rng.choice(['horizon:a', 'horizon:b', 'horizon:c']),
|
|
amount=bits(rng.choice(values)), base=bits(rng.choice(values)),
|
|
amplifier=rng.choice(amplifiers)))
|
|
yield dict(entity=entity, ops=ops)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--prepare', action='store_true')
|
|
args = parser.parse_args()
|
|
build = lab.LOCAL / 'rust-live-attributes-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/VanillaLiveAttributesOracle.java'
|
|
helper = oracle.with_name('VanillaEntityAttributeOracle.java')
|
|
sources = [oracle, helper, Path(__file__), lab.ROOT / 'tools/test_entity_attribute_parity.py',
|
|
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/live_attributes_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, helper], 'compile-java')
|
|
|
|
def java(side, mode):
|
|
run(['java', '-Xmx512M', '-cp', os.pathsep.join([str(classes), str(jars[side]), dependency_path]),
|
|
'dev.horizonlab.parity.VanillaLiveAttributesOracle', mode, build], mode)
|
|
|
|
for side in jars:
|
|
java(side, 'export-' + side)
|
|
catalog = json.loads((build / 'catalog-server.json').read_text())
|
|
if catalog != json.loads((build / 'catalog-client.json').read_text()):
|
|
raise AssertionError('Client/server attribute catalogs differ')
|
|
lab.write_json(build / 'catalog.json', catalog)
|
|
fixtures = list(corpus(catalog))
|
|
inputs = build / 'inputs.txt'
|
|
inputs.write_text(''.join(json.dumps(row, separators=(',', ':')) + '\n' for row in fixtures), encoding='utf-8')
|
|
fixture_paths = [inputs, build / 'catalog.json', build / 'catalog-client.json', build / 'catalog-server.json']
|
|
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')
|
|
references = []
|
|
for side, jar in jars.items():
|
|
java(side, side)
|
|
enriched = build / f'inputs-{side}.txt'
|
|
fixture_paths.append(enriched)
|
|
tested_fixtures.update(base.source_hashes([enriched]))
|
|
run([binary, 'live_attributes_fixture', build, side], 'rust-' + side)
|
|
reference = build / f'{side}.txt'
|
|
candidate = build / f'rust-{side}.txt'
|
|
matched = base.compare(reference, candidate)
|
|
references.append(dict(side=side, jar_sha256=lab.digest(jar, 'sha256'),
|
|
output_sha256=lab.digest(reference, 'sha256'), matched_output_lines=matched,
|
|
rust_output_sha256=lab.digest(candidate, 'sha256')))
|
|
print(f'{side}: {matched:,} live attribute output lines match Rust', flush=True)
|
|
base.ensure_sources_unchanged(sources, tested_sources)
|
|
base.ensure_sources_unchanged(fixture_paths, tested_fixtures)
|
|
report = dict(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),
|
|
attributes=len(catalog['attributes']), entity_suppliers=len(catalog['entities']),
|
|
fastutil_sha256={str(p.relative_to(lab.ROOT)): lab.digest(p, 'sha256') for p in libraries if 'fastutil' in str(p)},
|
|
sources_sha256=tested_sources, fixtures_sha256=tested_fixtures, references=references,
|
|
scope='Actual registered AttributeMap, immutable DefaultAttributes suppliers, ranged definitions, lazy instances, query/reset methods, modifier reference identities/caches/permanent state, MobEffect modifier loops and native-order update/sync queues. Includes the complete previous entity-attribute corpus, all registered attributes/entity suppliers, null lookups, range boundaries, nonfinite arithmetic, wrapping amplifiers and independent/bulk queue acknowledgements. Replays each reference JVM holder/instance hash sequence. Mutable custom supplier prototypes, internal supplier caches, full entity health/dimensions/gameplay, wire/save codecs and playable platform builds remain outside this gate.')
|
|
lab.write_json(report_path, report)
|
|
print(f'Scoped live attribute parity passed: {report_path}')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|