874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
178 lines
14 KiB
Python
Executable File
178 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Item stacks on the wire: every data component, as vanilla 26.3 encodes it.
|
|
|
|
VanillaItemWireOracle encodes stacks with ItemStack.OPTIONAL_STREAM_CODEC
|
|
(what a server sends in inventories, recipe books and advancements): each
|
|
default component value of every vanilla item, the SNBT fixtures below
|
|
(direct holders, tags and lists, optional fields, every consume effect,
|
|
block predicates with component matchers, the mob variants) and patches
|
|
built in Java (components without a persistent codec, removals). The Rust
|
|
probe decodes each with horizon_protocol::item::Slot and must consume every
|
|
byte and find the same component ids in the same order; a stack whose
|
|
components Horizon only reads past must also encode back to the same bytes.
|
|
Every id in DataComponents must be covered."""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import platform
|
|
import subprocess
|
|
from pathlib import Path
|
|
try:
|
|
from . import lab, test_rust_parity as base
|
|
except ImportError:
|
|
import lab
|
|
import test_rust_parity as base
|
|
|
|
COMPONENT_TYPES = 122
|
|
|
|
FIXTURES = [
|
|
('custom_data', '{custom_data:{a:1,b:"x",c:[1,2]}}'),
|
|
('unbreakable', '{unbreakable:{}}'),
|
|
('custom_name', '{custom_name:{text:"Hi",color:"red",bold:1b}}'),
|
|
('lore', '{lore:["a",{text:"b",italic:0b}]}'),
|
|
('enchantments', '{enchantments:{"minecraft:sharpness":5,"minecraft:unbreaking":3}}'),
|
|
('can_break tag', '{can_break:{blocks:"#minecraft:logs"}}'),
|
|
('can_break list state nbt', '{can_break:[{blocks:["minecraft:stone","minecraft:dirt"],state:{snowy:"true",age:{min:"1",max:"3"},level:{min:"2"}},nbt:"{a:1b}"}]}'),
|
|
('can_place_on components', '{can_place_on:{blocks:"minecraft:chest",components:{"minecraft:custom_name":"x","minecraft:damage":3},predicates:{"minecraft:custom_data":{a:1},"minecraft:damage":{durability:{min:1}}}}}'),
|
|
('can_place_on empty', '{can_place_on:[{}]}'),
|
|
('custom_model_data', '{custom_model_data:{floats:[1.0f,2.5f],flags:[1b,0b],strings:["a"],colors:[16711680]}}'),
|
|
('tooltip_display', '{tooltip_display:{hide_tooltip:1b,hidden_components:["minecraft:enchantments","minecraft:damage"]}}'),
|
|
('repair_cost', '{repair_cost:7}'),
|
|
('intangible_projectile', '{intangible_projectile:{}}'),
|
|
('tooltip_style', '{tooltip_style:"minecraft:fancy"}'),
|
|
('interact_animation', '{interact_animation:{type:"stab",duration:9}}'),
|
|
('dyed_color', '{dyed_color:16711935}'),
|
|
('map_id', '{map_id:42}'),
|
|
('written_book_content', '{written_book_content:{title:{raw:"T",filtered:"F"},author:"me",generation:1,pages:[{raw:"p1",filtered:"q1"},"p2"],resolved:1b}}'),
|
|
('trim registry', '{trim:{material:"minecraft:gold",pattern:"minecraft:coast"}}'),
|
|
('trim direct', '{trim:{material:{palette_id:"minecraft:x",description:"X"},pattern:{asset_id:"minecraft:y",description:"Y",decal:1b}}}'),
|
|
('block_entity_data', '{block_entity_data:{id:"minecraft:chest",Items:[]}}'),
|
|
('lodestone_tracker target', '{lodestone_tracker:{target:{dimension:"minecraft:the_nether",pos:[1,-64,3]},tracked:0b}}'),
|
|
('lodestone_tracker empty', '{lodestone_tracker:{}}'),
|
|
('firework_explosion', '{firework_explosion:{shape:"star",colors:[1,2],fade_colors:[3],has_trail:1b,has_twinkle:0b}}'),
|
|
('profile name', '{profile:{name:"Notch"}}'),
|
|
('profile full', '{profile:{name:"jeb_",id:[I;1,2,3,4],properties:[{name:"textures",value:"abc",signature:"sig"}]}}'),
|
|
('note_block_sound', '{note_block_sound:"minecraft:block.note_block.bell"}'),
|
|
('base_color', '{base_color:"lime"}'),
|
|
('sulfur_cube_content', '{sulfur_cube_content:{id:"minecraft:stone",count:1}}'),
|
|
('lock', '{lock:{items:"minecraft:diamond"}}'),
|
|
('container_loot', '{container_loot:{loot_table:"minecraft:chests/simple_dungeon",seed:5L}}'),
|
|
('waxed', '{waxed:{}}'),
|
|
('banner_patterns', '{banner_patterns:[{pattern:"minecraft:stripe_bottom",color:"red"},{pattern:{asset_id:"minecraft:x",translation_key:"y"},color:"blue"}]}'),
|
|
('pot_decorations', '{pot_decorations:{back:{id:"minecraft:brick"},left:{id:"minecraft:angler_pottery_sherd"},front:{id:"minecraft:skull_pottery_sherd",count:1}}}'),
|
|
('suspicious_stew_effects', '{suspicious_stew_effects:[{id:"minecraft:night_vision",duration:100},{id:"minecraft:poison"}]}'),
|
|
('potion_contents full', '{potion_contents:{potion:"minecraft:swiftness",custom_color:123,custom_effects:[{id:"minecraft:speed",amplifier:1b,duration:200,ambient:1b,show_particles:0b,show_icon:1b,hidden_effect:{id:"minecraft:speed",duration:100,hidden_effect:{id:"minecraft:speed",duration:50}}}],custom_name:"x"}}'),
|
|
('potion_contents bare', '{potion_contents:{}}'),
|
|
('container', '{container:[{slot:0,item:{id:"minecraft:stone",count:3}},{slot:5,item:{id:"minecraft:diamond_sword",components:{"minecraft:damage":5,"minecraft:trim":{material:"minecraft:gold",pattern:"minecraft:coast"}}}}]}'),
|
|
('bundle_contents', '{bundle_contents:[{id:"minecraft:stone",count:2},{id:"minecraft:potion",components:{"minecraft:potion_contents":{potion:"minecraft:water"}}}]}'),
|
|
('charged_projectiles', '{charged_projectiles:[{id:"minecraft:arrow"},{id:"minecraft:firework_rocket",components:{"minecraft:fireworks":{flight_duration:2b,explosions:[{shape:"burst",colors:[5]}]}}}]}'),
|
|
('block_state', '{block_state:{facing:"north",half:"top"}}'),
|
|
('bees', '{bees:[{entity_data:{id:"minecraft:bee",Health:5f},ticks_in_hive:5,min_ticks_in_hive:10}]}'),
|
|
('entity_data', '{entity_data:{id:"minecraft:pig",NoAI:1b}}'),
|
|
('bucket_entity_data', '{bucket_entity_data:{Health:5f}}'),
|
|
('instrument direct', '{instrument:{sound_event:{sound_id:"minecraft:x",range:8f},use_duration:3f,range:64f,description:"Horn"}}'),
|
|
('painting variant', '{"minecraft:painting/variant":"minecraft:kebab"}'),
|
|
('sign_text_front', '{sign_text_front:{messages:["a","b","c","d"],filtered_messages:["e","f","g","h"],color:"red",has_glowing_text:1b}}'),
|
|
('sign_text_back', '{sign_text_back:{messages:["a","","",""]}}'),
|
|
('map_decorations', '{map_decorations:{a:{type:"minecraft:player",x:1.0d,z:2.0d,rotation:0f}}}'),
|
|
('debug_stick_state', '{debug_stick_state:{"minecraft:oak_stairs":"facing"}}'),
|
|
('recipes', '{recipes:["minecraft:stone_bricks"]}'),
|
|
('attribute_modifiers', '{attribute_modifiers:[{type:"minecraft:attack_damage",id:"minecraft:x",amount:1.5d,operation:"add_value",slot:"mainhand",display:{type:"override",value:"hi"}},{type:"minecraft:armor",id:"minecraft:y",amount:2d,operation:"add_multiplied_total",display:{type:"hidden"}}]}'),
|
|
('consumable all effects', '{consumable:{consume_seconds:2f,animation:"drink",sound:{sound_id:"minecraft:x",range:4f},has_consume_particles:0b,on_consume_effects:[{type:"apply_effects",effects:[{id:"minecraft:speed",duration:20}],probability:0.5f},{type:"remove_effects",effects:"minecraft:poison"},{type:"remove_effects",effects:["minecraft:poison","minecraft:wither"]},{type:"clear_all_effects"},{type:"teleport_randomly",diameter:8f},{type:"play_sound",sound:"minecraft:entity.generic.eat"}]}}'),
|
|
('death_protection', '{death_protection:{death_effects:[{type:"clear_all_effects"}]}}'),
|
|
('equippable full', '{equippable:{slot:"head",equip_sound:"minecraft:item.armor.equip_gold",asset_id:"minecraft:gold",camera_overlay:"minecraft:misc/pumpkinblur",allowed_entities:"#minecraft:skeletons",dispensable:0b,swappable:0b,damage_on_hurt:0b,equip_on_interact:1b,can_be_sheared:1b,shearing_sound:{sound_id:"minecraft:y"}}}'),
|
|
('equippable bare', '{equippable:{slot:"chest"}}'),
|
|
('tool rules', '{tool:{rules:[{blocks:"#minecraft:mineable/pickaxe",speed:4f,correct_for_drops:1b},{blocks:["minecraft:stone"]}],default_mining_speed:2f,damage_per_block:2,can_destroy_blocks_in_creative:0b}}'),
|
|
('blocks_attacks full', '{blocks_attacks:{block_delay_seconds:0.5f,disable_cooldown_scale:2f,damage_reductions:[{horizontal_blocking_angle:45f,type:"#minecraft:is_fire",base:1f,factor:0.5f},{base:0f,factor:1f}],item_damage:{threshold:3f,base:1f,factor:2f},bypassed_by:"#minecraft:bypasses_shield",block_sound:"minecraft:item.shield.block",disable_sound:{sound_id:"minecraft:z"}}}'),
|
|
('damage_resistant', '{damage_resistant:{types:"#minecraft:is_fire"}}'),
|
|
('repairable list', '{repairable:{items:["minecraft:diamond","minecraft:emerald"]}}'),
|
|
('use_cooldown group', '{use_cooldown:{seconds:1.5f,cooldown_group:"minecraft:x"}}'),
|
|
('use_remainder', '{use_remainder:{id:"minecraft:bowl",count:2}}'),
|
|
('fireworks', '{fireworks:{flight_duration:3b,explosions:[{shape:"creeper",colors:[1],fade_colors:[2,3],has_trail:1b},{shape:"small_ball"}]}}'),
|
|
('provides_banner_patterns', '{provides_banner_patterns:"#minecraft:pattern_item/globe"}'),
|
|
('wolf variant', '{"minecraft:wolf/variant":"minecraft:ashen","minecraft:wolf/sound_variant":"minecraft:angry","minecraft:wolf/collar":"red"}'),
|
|
('fox salmon parrot', '{"minecraft:fox/variant":"snow","minecraft:salmon/size":"large","minecraft:parrot/variant":"blue"}'),
|
|
('tropical fish', '{"minecraft:tropical_fish/pattern":"flopper","minecraft:tropical_fish/base_color":"white","minecraft:tropical_fish/pattern_color":"black"}'),
|
|
('mooshroom rabbit', '{"minecraft:mooshroom/variant":"brown","minecraft:rabbit/variant":"gold"}'),
|
|
('pig cow chicken', '{"minecraft:pig/variant":"minecraft:cold","minecraft:pig/sound_variant":"minecraft:big","minecraft:cow/variant":"minecraft:warm","minecraft:cow/sound_variant":"minecraft:classic","minecraft:chicken/variant":"minecraft:cold","minecraft:chicken/sound_variant":"minecraft:classic"}'),
|
|
('nautilus frog horse', '{"minecraft:zombie_nautilus/variant":"minecraft:warm","minecraft:frog/variant":"minecraft:cold","minecraft:horse/variant":"chestnut"}'),
|
|
('llama axolotl cat', '{"minecraft:llama/variant":"brown","minecraft:axolotl/variant":"blue","minecraft:cat/variant":"minecraft:tabby","minecraft:cat/sound_variant":"minecraft:classic","minecraft:cat/collar":"blue"}'),
|
|
('sheep shulker villager', '{"minecraft:sheep/color":"pink","minecraft:shulker/color":"purple","minecraft:villager/variant":"minecraft:taiga"}'),
|
|
('trim material direct provider', '{provides_trim_material:{palette_id:"minecraft:x",description:"X"}}'),
|
|
('everything at once', '{damage:3,unbreakable:{},custom_name:"n",enchantments:{"minecraft:mending":1},trim:{material:"minecraft:gold",pattern:"minecraft:coast"},banner_patterns:[{pattern:"minecraft:globe",color:"red"}],pot_decorations:{right:{id:"minecraft:brick"}},lore:["x"],rarity:"epic"}'),
|
|
]
|
|
# Fixtures on another item than a stick (the components need it, or read better there).
|
|
ITEMS = {'profile name': 'player_head', 'profile full': 'player_head', 'trim registry': 'diamond_chestplate',
|
|
'trim direct': 'diamond_chestplate', 'banner_patterns': 'white_banner', 'pot_decorations': 'decorated_pot',
|
|
'everything at once': 'diamond_chestplate'}
|
|
|
|
|
|
def compare(vanilla, rust):
|
|
"""Failures, stacks checked, and the component ids covered."""
|
|
results = {r['label']: r for r in rust}
|
|
failures, covered = [], set()
|
|
for v in vanilla:
|
|
if 'error' in v:
|
|
failures.append(f"oracle {v['label']}: {v['error']}")
|
|
continue
|
|
covered.update(v['added'] + v['removed'])
|
|
r = results.get(v['label'])
|
|
if r is None or 'error' in r:
|
|
failures.append(f"{v['label']}: {r and r['error']}")
|
|
elif r['left'] or r['added'] != v['added'] or r['removed'] != v['removed']:
|
|
failures.append(f"{v['label']}: {r['left']} bytes left, ids {r['added']}/{r['removed']}, vanilla {v['added']}/{v['removed']}")
|
|
elif not r['same_bytes'] and not r['typed']:
|
|
failures.append(f"{v['label']}: read past, but encoded back differently")
|
|
missing = sorted(set(range(COMPONENT_TYPES)) - covered)
|
|
if missing:
|
|
failures.append(f'component ids no stack covers: {missing}')
|
|
return failures, len(vanilla), covered
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--prepare', action='store_true')
|
|
args = parser.parse_args()
|
|
build = lab.LOCAL / 'item-wire-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/VanillaItemWireOracle.java'
|
|
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',
|
|
lab.ROOT / 'mods/horizon/src/test/java', '-cp', str(jars['server']) + os.pathsep + dependency_path,
|
|
'-d', classes, oracle], 'compile-java')
|
|
fixtures = build / 'fixtures.tsv'
|
|
fixtures.write_text(''.join(f'{label}\t{{id:"minecraft:{ITEMS.get(label, "stick")}",count:1,components:{snbt}}}\n'
|
|
for label, snbt in FIXTURES), encoding='utf-8')
|
|
vanilla_path, rust_path = build / 'vanilla.jsonl', build / 'rust.jsonl'
|
|
run(['java', '-Xmx1G', '-cp', os.pathsep.join([str(classes), str(jars['server']), dependency_path]),
|
|
'dev.horizonlab.parity.VanillaItemWireOracle', fixtures, vanilla_path], 'oracle')
|
|
run(['cargo', 'build', '--release', '-p', 'horizon-protocol', '--example', 'item_wire_probe'], 'compile-rust')
|
|
binary = lab.cargo_target() / 'release/examples' / ('item_wire_probe.exe' if os.name == 'nt' else 'item_wire_probe')
|
|
run([binary, vanilla_path, rust_path], 'rust')
|
|
read = lambda path: [json.loads(line) for line in path.read_text(encoding='utf-8').splitlines() if line.strip()]
|
|
failures, stacks, covered = compare(read(vanilla_path), read(rust_path))
|
|
for failure in failures[:40]:
|
|
print(failure)
|
|
if failures:
|
|
raise SystemExit(f'{len(failures)} item wire mismatches (of {stacks} stacks)')
|
|
lab.write_json(report_path, {
|
|
'schema': 1, 'minecraft': lock['minecraft']['version'], 'platform': platform.platform(),
|
|
'stacks': stacks, 'component_types_covered': len(covered),
|
|
'oracle_sha256': lab.digest(vanilla_path, 'sha256'), 'rust_sha256': lab.digest(rust_path, 'sha256')})
|
|
print(f'Item wire parity passed: {stacks} stacks, all {len(covered)} component types: {report_path}')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|