70583cbf2a
The new particle types now come from the events that spawn them in vanilla. - Level events 3003 (wax on), 3004 (wax off), 3005 (scrape): ParticleUtils.spawnParticlesOnBlockFaces, three to five particles on each face, from the client level's random. They are sent by honeycomb on a block (HoneycombItem.useOn), axe scraping and wax removal (BlockTransformer's TransformParticle.send), dispensed honeycomb, and copper golems being waxed or scraped. - Level event 3002: a struck lightning rod sends sparks along its axis (ParticleUtils.spawnParticlesAlongAxis); without an axis they cover the block's faces. - Eating: LivingEntity.spawnItemParticles, five crumbs every four ticks and sixteen when consumed (Consumable.onConsume), placed in front of the eyes and thrown along the look, as item particles in full colour. They were greyed terrain crumbs above the eyes. They now follow Consumable.shouldEmitParticlesAndSounds with the remaining ticks before the countdown, as LivingEntity does: six times over 32 ticks, not seven, each one a tick earlier. - Snowballs (Snowball.handleEntityEvent(3)): eight item crumbs at rest. Eggs (ThrownEgg): eight scattered by up to 0.04 a tick. - Slime, magma cube and sulfur cube landings (AbstractCubeMob.tick): size × 16 item_slime, flame or sulfur_cube_goo particles around the body, from draws the mob's random also makes on the server. New harness tools/test_emitter_particles_parity.py: the real ParticleUtils emitters and LivingEntity.spawnItemParticles. Part of #63 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
54 lines
2.5 KiB
Python
54 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare particle emitters with the real vanilla ones: ParticleUtils
|
|
spawnParticlesOnBlockFaces and spawnParticlesAlongAxis as LevelEventHandler
|
|
calls them for level events 3002 (electric sparks), 3003 (wax on), 3004 (wax
|
|
off) and 3005 (scrape), and LivingEntity.spawnItemParticles (eating, drinking):
|
|
particle, count, positions, motion and every random draw."""
|
|
import json
|
|
import random
|
|
|
|
try:
|
|
from . import lab, oracle_harness as harness
|
|
except ImportError:
|
|
import lab, oracle_harness as harness
|
|
|
|
|
|
def cases(rng):
|
|
for _ in range(600):
|
|
pos = [rng.randrange(-30000, 30000), rng.randrange(-64, 320), rng.randrange(-30000, 30000)]
|
|
yield dict(kind='block', pos=pos, axis=rng.choice([-1, -1, 0, 1, 2]), seed=rng.randrange(-2**63, 2**63))
|
|
angles = [0.0, 90.0, -90.0, 45.0, -45.0, 180.0, -180.0, 12.5, 359.75, -721.5]
|
|
for _ in range(900):
|
|
# Dyadic values: serde_json's decimal parsing may round others a unit off.
|
|
pos = [rng.randrange(-2**20, 2**20) / 64 for _ in range(3)]
|
|
yield dict(kind='item_use', pos=pos, pitch=rng.choice(angles + [rng.randrange(-5760, 5760) / 64]),
|
|
yaw=rng.choice(angles + [rng.randrange(-23040, 23040) / 64]),
|
|
eye=rng.choice([1.62, 1.27, 0.4, 1.74]), count=rng.choice([5, 16, 1, 0]),
|
|
item=rng.choice(['minecraft:apple', 'minecraft:cooked_beef', 'minecraft:potion', 'minecraft:honey_bottle']),
|
|
seed=rng.randrange(-2**63, 2**63))
|
|
|
|
|
|
def make_cases(build):
|
|
path = build / 'cases.jsonl'
|
|
count = 0
|
|
with path.open('w') as out:
|
|
for case in cases(random.Random(0x454d4954)):
|
|
out.write(json.dumps(case, separators=(',', ':')) + '\n')
|
|
count += 1
|
|
return path, count
|
|
|
|
|
|
def main():
|
|
scope = ('The real ParticleUtils.spawnParticlesOnBlockFaces (UniformInt 3..5 per face) and spawnParticlesAlongAxis '
|
|
'(radius 0.125, UniformInt 10..19) on a stand-in level with a seeded random, as LevelEventHandler runs '
|
|
'them for level events 3002-3005, and the real LivingEntity.spawnItemParticles on an entity given a '
|
|
'position, rotation, eye height and seeded random. Which code sends those events, and the particles '
|
|
'once spawned, are covered elsewhere.')
|
|
harness.differential(
|
|
'rust-emitter-particles-parity', 'VanillaEmitterParticlesOracle', 'emitter_particles_probe', make_cases, scope,
|
|
sources=[lab.ROOT / 'crates/horizon-client/src/particles.rs'])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|