874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
97 lines
7.2 KiB
Python
97 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare blockstate-transformed cuboid baking against the pinned Java client."""
|
|
import itertools
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import platform
|
|
import subprocess
|
|
import zipfile
|
|
try:
|
|
from . import lab, test_rust_parity as base, test_model_baking_parity as baking, test_cuboid_model_parity as decoding
|
|
except ImportError:
|
|
import lab, test_rust_parity as base, test_model_baking_parity as baking, test_cuboid_model_parity as decoding
|
|
|
|
|
|
def corpus(archive):
|
|
models = {'minecraft:' + name.removeprefix('assets/minecraft/models/').removesuffix('.json'): archive.read(name).decode()
|
|
for name in sorted(archive.namelist()) if name.startswith('assets/minecraft/models/block/') and name.endswith('.json')}
|
|
groups = {}
|
|
definitions = 0
|
|
for name in sorted(archive.namelist()):
|
|
if not name.startswith('assets/minecraft/blockstates/') or not name.endswith('.json'):
|
|
continue
|
|
definitions += 1
|
|
root = json.loads(archive.read(name))
|
|
for choice in [*root.get('variants', {}).values(), *(s['apply'] for s in root.get('multipart', []))]:
|
|
for variant in choice if isinstance(choice, list) else [choice]:
|
|
state = tuple(variant.get(axis, 0) % 360 // 90 for axis in ['x', 'y', 'z']) + (variant.get('uvlock', False),)
|
|
model = variant['model']
|
|
if ':' not in model:
|
|
model = 'minecraft:' + model
|
|
groups.setdefault(state, set()).add(model)
|
|
rows = [{'models': models, 'roots': sorted(roots), 'transform': state} for state, roots in sorted(groups.items())]
|
|
resource_pairs = sum(len(roots) for roots in groups.values())
|
|
# All 128 transform/lock combinations on asymmetric, reversed and degenerate
|
|
# geometry, explicit/default UVs, signed zero and representative element rotations.
|
|
synthetic = list(baking.corpus())[-1]['models']
|
|
names = list(synthetic)
|
|
selected = names[:12] + names[12:248:9] + names[248:504:13] + names[504::53] + names[-4:]
|
|
focused = {name: synthetic[name] for name in dict.fromkeys(selected)}
|
|
for turns in itertools.product(range(4), repeat=3):
|
|
for lock in [False, True]:
|
|
rows.append({'models': focused, 'roots': list(focused), 'transform': [*turns, lock]})
|
|
# Cover every remaining arbitrary-angle element fixture under a deterministic
|
|
# transform; do not multiply the whole large corpus by 128 unnecessarily.
|
|
for state in itertools.product(range(4), range(4), [False, True]):
|
|
x, y, lock = state
|
|
subset = {name: raw for i, (name, raw) in enumerate(synthetic.items()) if i % 32 == x*8+y*2+lock}
|
|
rows.append({'models': subset, 'roots': list(subset), 'transform': [x, y, (x+y)%4, lock]})
|
|
return rows, definitions, resource_pairs, len(focused)
|
|
|
|
|
|
def main():
|
|
lock, jars, _ = base.verify_artifacts()
|
|
build = lab.LOCAL / 'rust-blockstate-transform-parity'; build.mkdir(parents=True, exist_ok=True)
|
|
report_path = build / 'report.json'; report_path.unlink(missing_ok=True)
|
|
oracle = lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaModelBakingOracle.java'
|
|
helper = oracle.with_name('VanillaCuboidModelOracle.java'); crate = lab.ROOT / 'crates/horizon-assets'
|
|
sources = [oracle, helper, Path(__file__), lab.ROOT/'tools/test_model_baking_parity.py',
|
|
lab.ROOT/'tools/test_cuboid_model_parity.py', lab.ROOT/'tools/test_model_resolution_parity.py',
|
|
lab.ROOT/'tools/lab.py', lab.ROOT/'tools/test_rust_parity.py', lab.ROOT/'Cargo.toml',
|
|
lab.ROOT/'Cargo.lock', crate/'Cargo.toml', *sorted(crate.rglob('*.rs'))]
|
|
tested = base.source_hashes(sources)
|
|
with zipfile.ZipFile(jars['client']) as archive:
|
|
rows, definitions, resource_pairs, focused = corpus(archive)
|
|
inputs = build/'inputs.jsonl'; inputs.write_text(''.join(json.dumps(row)+'\n' for row in rows))
|
|
fixtures = base.source_hashes([inputs]); classes = build/'classes'; classes.mkdir(exist_ok=True)
|
|
def run(command, name):
|
|
with (build/(name+'.log')).open('w') as log:
|
|
subprocess.run(list(map(str, command)), cwd=lab.ROOT, check=True, timeout=3600, stdout=log, stderr=subprocess.STDOUT)
|
|
dependencies = os.pathsep.join(map(str, [jars['client'], *baking.client_libraries()]))
|
|
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none','-sourcepath',str(lab.ROOT/'mods/horizon/src/test/java'), '-cp', dependencies, '-d', classes, oracle, helper], 'compile-java')
|
|
run(['cargo', 'build', '--locked', '-p', 'horizon-assets', '--example', 'model_baking_fixture'], 'compile-rust')
|
|
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--locked', '--no-deps', '--format-version', '1'], cwd=lab.ROOT))
|
|
binary = Path(metadata['target_directory'])/'debug/examples'/('model_baking_fixture.exe' if os.name == 'nt' else 'model_baking_fixture')
|
|
run([binary, inputs, build/'rust.jsonl'], 'rust')
|
|
run(['java', '-Xmx768M', '-cp', str(classes)+os.pathsep+dependencies, 'dev.horizonlab.parity.VanillaModelBakingOracle', inputs, build/'client.jsonl'], 'client')
|
|
rejected = decoding.canonicalize(build/'client.jsonl'); decoding.canonicalize(build/'rust.jsonl')
|
|
matched = base.compare(build/'client.jsonl', build/'rust.jsonl')
|
|
base.ensure_sources_unchanged(sources, tested); base.ensure_sources_unchanged([inputs], fixtures)
|
|
quads = sum(len(json.loads(line).get('baked', {}).get('quads', [])) for line in (build/'client.jsonl').open())
|
|
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(), 'resource_definitions': definitions,
|
|
'resource_model_transform_pairs': resource_pairs, 'synthetic_models_per_all_128_transforms': focused,
|
|
'graph_fixtures': len(rows), 'rejected_roots': rejected, 'baked_quads': quads,
|
|
'sources_sha256': tested, 'fixtures_sha256': fixtures, 'rust_output_sha256': lab.digest(build/'rust.jsonl', 'sha256'),
|
|
'references': [{'side': 'client', 'jar_sha256': lab.digest(jars['client'], 'sha256'),
|
|
'output_sha256': lab.digest(build/'client.jsonl', 'sha256'), 'matched_output_lines': matched}],
|
|
'scope': 'Actual FaceBakery with Variant.SimpleModelState and unit-atlas sprite, default JOML math. Every distinct model/XYZ-rotation/UV-lock tuple in bundled blockstates; all 64 quarter-turn combinations with and without UV lock on synthetic geometry. Exact float bits for positions, UVs, element matrices, orientation and all six inverse UV matrices; native Direction.rotate for culling. Includes identity UV lock, degenerate/reversed geometry, element rotations, signed zero and float overflow. Does not establish atlas packing, shader pixels, lighting, render classification, renderer integration or gameplay parity.'}
|
|
lab.write_json(report_path, report)
|
|
print(f'Native transformed baking: {matched:,} roots / {quads:,} quads match ({rejected:,} rejected); {report_path}', flush=True)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|