Files
RustNotMC/tools/test_creative_parity.py
T
SashegDev 874b6f20c2 Клиент: начальный импорт кодовой базы
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
2026-10-29 22:04:28 +03:00

111 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""Compare the creative inventory's tabs and search with the vanilla client's.
The Java oracle (VanillaCreativeSearchOracle) writes the creative-inventory.json
that horizon-prepare exports (VanillaCreativeTabsExporter: every
tab built by vanilla's CreativeModeTabs over the vanilla data pack's
registries), then what the vanilla client shows: every tab's display items
and, for each search query, CreativeModeInventoryScreen.refreshSearchResults'
items, searched through the client's own SessionSearchTrees.
examples/probe/creative_search_fixture loads the export into
horizon_client::creative::Creative. The tabs it shows (id, title, icon,
position, every stack with its components), the search tab's stacks and the
results of every query must agree. The queries are QUERIES below, then every
search stack's item path and tooltip lines, every item tag with and without
"#", and half of each as a prefix.
test_creative_parity.py [--detail N]
"""
import argparse
import json
import os
import subprocess
import sys
try:
from . import interaction_lab as lib, lab, test_rust_parity as base, test_model_baking_parity as baking
except ImportError:
import interaction_lab as lib
import lab
import test_rust_parity as base
import test_model_baking_parity as baking
PARITY = lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity'
# Players' reports (anvil, crafting table, smithing table), other blocks of the
# functional tab, case, spaces, colons and tag searches.
QUERIES = ['', 'anvil', 'Anvil', 'ANVIL', 'crafting', 'crafting table', 'smithing', 'smithing table', 'table', 'chest',
'furnace', 'bed', 'sign', 'banner', 'ominous', 'painting', 'loom', 'grindstone', 'lectern', 'stone', 'oak',
'a', 'e', ' ', ' ', 'xyz', 'potion', 'healing', 'splash', 'sharpness', 'enchanted', 'book', 'mending',
'goat horn', 'ponder', 'firework', 'flight', 'music disc', 'disc', 'spawn egg', 'egg', 'stew', 'arrow',
'tipped', 'light gray', 'red ', ' red', 'minecraft:', 'minecraft:anvil', 'minecraft:table', 'mine:craft',
':anvil', ':', 'anvil:', 'minecraft: anvil', ' minecraft : anvil ', 'diamond_sword', 'diamond sword',
'attack', 'when in main hand', '+', '-3', 'command', 'barrier', 'debug', 'light', 'structure', 'jigsaw',
'#', '#logs', '#minecraft:logs', '#minecraft:', '#:planks', '#wool', '#LOGS', '#swords', '#anvil', '#xyz',
'#minecraft', '#enchantable/', 'é', 'İ', 'Σ']
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--detail', type=int, default=5)
args = parser.parse_args()
lock, jars, _ = base.verify_artifacts()
libraries = baking.client_libraries()
build = lab.LOCAL / 'creative-parity'; build.mkdir(parents=True, exist_ok=True)
queries = build / 'queries.json'; queries.write_text(json.dumps(QUERIES))
classes = build / 'classes'; classes.mkdir(exist_ok=True)
dependencies = os.pathsep.join(map(str, [jars['client'], *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', '-nowarn', '-cp', dependencies, '-d', classes,
*(PARITY / f'{n}.java' for n in ('ParityData', 'VanillaCreativeTabsExporter', 'VanillaCreativeSearchOracle'))],
'compile-java')
run(['java', '-Xmx2G', '-cp', str(classes) + os.pathsep + dependencies, 'dev.horizonlab.parity.VanillaCreativeSearchOracle',
queries, build / 'creative-inventory.json', build / 'vanilla.json'], 'vanilla')
lib.cargo_example('horizon-client', 'creative_search_fixture', lib.RESOURCES, build / 'creative-inventory.json',
build / 'vanilla.json', build / 'rust.json')
vanilla = json.loads((build / 'vanilla.json').read_text())
rust = json.loads((build / 'rust.json').read_text())
bad = 0
shown = [{k: t[k] for k in ('id', 'title', 'icon', 'row', 'column', 'items')} for t in vanilla['tabs'] if t['shown']]
if [t['id'] for t in shown] != [t['id'] for t in rust['tabs']]:
bad += 1
print('TABS vanilla', [t['id'] for t in shown], '\n horizon', [t['id'] for t in rust['tabs']])
for v, r in zip(shown, rust['tabs']):
for k in ('title', 'icon', 'row', 'column'):
if v[k] != r[k]:
bad += 1; print(f'TAB {v["id"]} {k}: vanilla {v[k]!r} | horizon {r[k]!r}')
if v['items'] != r['items']:
bad += 1
i = next((i for i, (a, b) in enumerate(zip(v['items'], r['items'])) if a != b), min(len(v['items']), len(r['items'])))
print(f'TAB {v["id"]}: {len(v["items"])} vanilla stacks, {len(r["items"])} horizon; first difference at {i}:')
print(' vanilla', v['items'][i] if i < len(v['items']) else None)
print(' horizon', r['items'][i] if i < len(r['items']) else None)
print(f' {v["id"]}: {len(v["items"])} stacks')
search = next(t['items'] for t in vanilla['tabs'] if t['type'] == 'SEARCH')
if search != rust['search']:
bad += 1; print(f'SEARCH TAB: {len(search)} vanilla stacks, {len(rust["search"])} horizon')
wrong = [(v, r) for v, r in zip(vanilla['searches'], rust['searches']) if v != r]
for v, r in wrong[:args.detail]:
missing = [search[i] for i in v['results'] if i not in r['results']][:3]
extra = [search[i] for i in r['results'] if i not in v['results']][:3]
print(f'SEARCH {v["query"]!r}: vanilla {len(v["results"])} results, horizon {len(r["results"])}; '
f'missing {missing} extra {extra}')
bad += len(wrong) + abs(len(vanilla['searches']) - len(rust['searches']))
for q in ('anvil', 'crafting', 'smithing'):
hits = next(v['results'] for v in vanilla['searches'] if v['query'] == q)
print(f' search {q!r}: {len(hits)} results, ' + ', '.join(json.loads('"' + search[i].split('id:"')[1].split('"')[0] + '"')
for i in hits[:6]))
print(f'{len(vanilla["searches"]) - len(wrong)}/{len(vanilla["searches"])} searches match; {len(shown)} tabs, '
f'{len(search)} search stacks')
if bad:
sys.exit(1)
print('creative parity ok')
if __name__ == '__main__':
main()