Compose consumable timing and sounds with native eating particles

This commit is contained in:
Ludovic Marie
2026-10-07 09:53:21 +02:00
parent faaa25cd1b
commit 9c14d22bc9
7 changed files with 765 additions and 0 deletions
@@ -0,0 +1,290 @@
{
"schema": 1,
"minecraft": "26.3",
"manifest_sha1": "4fe1aa1ef8da1cb95c5bad1fb98890ca56dd8ca3",
"platform": "Linux-7.0.0-31-generic-x86_64-with-glibc2.43",
"java": "openjdk 25.0.4.1 2026-08-18",
"rustc": "rustc 1.99.0 (b940084d7 2026-09-28)",
"fixtures": 6792,
"sources_sha256": {
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaConsumableEmissionOracle.java": "b2062b2996ed9436a02e320346a088a0a975863f9bf4a219a78432af3fedc6b3",
"tools/test_consumable_emission_parity.py": "52437b37cf4ca6736c9b8960ef5ee00b09074e8a6161639a90bf0c9a366ae61b",
"tools/test_rust_parity.py": "29d3db08005873528894b45ca10dcd6278753650473b3eb574e53e06ede40ec6",
"tools/lab.py": "d00bf107fa01b6c579734a082c3ca4ade3018f0d6851d46844acf17574f66e64",
"Cargo.toml": "3f93319c1eb70dbd7ba25cd74ef343fbf35c52c7c4752f955605c58699cbff9d",
"Cargo.lock": "6884c7209f007ab9ee742b9c3c73828cfa1c6753d35b5fe25a87cab5955b33f8",
"crates/horizon-core/Cargo.toml": "e21b502c949da93cf67826592459b687a8cbf737ef92fee514e3ed2cc7cd27ad",
"crates/horizon-world/Cargo.toml": "18bbad377a8047e4d4ecd0fc87d83d46d1c0f1ce2b8c47bbe64633e7b4420b57",
"crates/horizon-core/src/bin/density_parity.rs": "e5b1dd33b7d01306c73cf072629b4fb186e9ab87b672a00108999d1a72263fd0",
"crates/horizon-core/src/bin/float_log_parity.rs": "4a655fcf8822ee7668c4bd126bc84d0b118864a6fc7f5a6aeee667d8bf2590b1",
"crates/horizon-core/src/bin/geometry_parity.rs": "27f6c09fe5018ac3de8e70e0f0bbf78f9abb14f5e4783bcaf46e8ca87b9c6f8a",
"crates/horizon-core/src/bin/noise_parity.rs": "c135bfc708d853e7061e81487d2cfe10de64b2f65677d3b4837cbfe0b8341010",
"crates/horizon-core/src/bin/parity.rs": "f35d928c66f958de39fac5e7e836a657eed95f83599e84899f7ea7a5802ba481",
"crates/horizon-core/src/bin/tick_parity.rs": "a54c164fe4814034d369947a887f5273966f5a878ba849204b80d1c729526a8f",
"crates/horizon-core/src/biome/seed_hash.rs": "5afbb4921c750826b395a08bf06f382fc7f01b5759a93de02a2749c523e2a317",
"crates/horizon-core/src/biome.rs": "908efa8cd4aa0a7adf1cb5e1a222e3dfb640eed5e9172e0fe7f0d1287afae7e8",
"crates/horizon-core/src/carving/ellipsoid.rs": "09232a7588610e8a9176fd9c62d40afb1fb89f2ecab6d555536a1d847b142d86",
"crates/horizon-core/src/carving.rs": "0aa3093caa0437868a8c0ed52a6c6e1993bd51c03583c670a2837e749eae5426",
"crates/horizon-core/src/density/arithmetic.rs": "372faec96c923e2cf4aa6c66c1ac398e676853274557823850dbc8933dd15ab1",
"crates/horizon-core/src/density/beardifier.rs": "bb1052e27dae4a01954ca78d7d5babafef85f3b2c704ccaf1920cb85a098cb50",
"crates/horizon-core/src/density/cache.rs": "38d0b6c9dd04ffef979fdad3f29cfebf98d331c8c9d430be5f39aa34f6c30975",
"crates/horizon-core/src/density/end_islands.rs": "168b9384237bd2e41d0ae5a74e944da4dc74e493bdec31fb73184c9a49e3de55",
"crates/horizon-core/src/density/graph/compiler.rs": "ac67deeb16f16d0859aa20e5a165811a29aca0b910037ee6bf5098211f3a6ede",
"crates/horizon-core/src/density/graph/mod.rs": "b776e7da934cb03051a3053d56d461c71e9978e084922fe4194147aa580c07bf",
"crates/horizon-core/src/density/graph/seeded.rs": "6bcdcaa3dffcfa9df673bbbea35120057a16ba3d2b506c52709c36cf8976b74a",
"crates/horizon-core/src/density/graph/tests.rs": "6bfb8cbff462587dd147b0775ea01d370abbd97544b9bd596c792df1b5f7e634",
"crates/horizon-core/src/density/interpolation.rs": "8c890f52b6467f38a94c654986a6a484b8e0b296ab98c398e60a6ce465b5b010",
"crates/horizon-core/src/density/interval.rs": "b42629163baa6b599a233afe8596d1c370cd81d015cc6fc36a9663aa47b98424",
"crates/horizon-core/src/density/mod.rs": "ca0d77d3282e3746ddcbc92bee97dbf640b9a56214e6ac1537f51f9a5b63d6e7",
"crates/horizon-core/src/density/power.rs": "d5b7270a128d4264268cd772109ad16d71a8ec3ff2d3b63b84cb985296d54a91",
"crates/horizon-core/src/density/rounding.rs": "71840c5206ff8d218ac29bf6f66915d5212764ba270c675711a866ba5b55a7a6",
"crates/horizon-core/src/density/selection.rs": "0dd26af2e85004ec5e053039ad11c384c17c56ace9ab45eb3f5cb3503c0b0330",
"crates/horizon-core/src/density/slice.rs": "5d77605573c0deda47778f1f78f7fa3fbf1944d8f5efbcc65cf589181cadf80b",
"crates/horizon-core/src/density/spline.rs": "6bb2e40f940fc6544d6fb09cb0b8cac722be03bc663dbdfb0f61faae207400bc",
"crates/horizon-core/src/density/surface.rs": "159e0e69cda74f870e80ea64fe523c4397e64af4ccb861bb9fd07b2c3cb91b00",
"crates/horizon-core/src/density/terrain_noise.rs": "8d0059d25eb5d248cb97a4c5a2e670d61528928ea151495c004f25adb723fe3e",
"crates/horizon-core/src/density/tests.rs": "7817796ee40d0ae241bfbebec6bca190e0a8e7d5a1caa8ddf858c3eaa58b8858",
"crates/horizon-core/src/density/world_fields.rs": "c1ce305cb8046b384d90c564cafb24af7365d580389637910f3d8b0cc18eb583",
"crates/horizon-core/src/geometry/border.rs": "1386f8080521231d05ba3e84d94a3e1d5218d35ccb3d5e9fe2bbb05a8739e573",
"crates/horizon-core/src/geometry/mod.rs": "c650386f9860e44a96c55f18c8f1428ca715fcb0bc5c2434db5b1caa43df27fe",
"crates/horizon-core/src/geometry/movement.rs": "bcfb452752940ce53ae6dc267a3c4eb29459046a063c1f884684ab4f4bd0068f",
"crates/horizon-core/src/geometry/shape/create.rs": "189812b91d5e0e3ad963afcd1a6e2831b31b8188df91a904ef27061e644806ad",
"crates/horizon-core/src/geometry/shape/face.rs": "f243bce81384040a95ce7984b02b5699206c596c537b2713097e86005b4d3a5c",
"crates/horizon-core/src/geometry/shape/join.rs": "f1b51887b7bf9482557e3ffa99e136f2e5db07ceeb05fdb116adf45e87129075",
"crates/horizon-core/src/geometry/shape.rs": "e8d88a763de1525f77a30b99591641e8a8b80946263acbde035a9bcc356df960",
"crates/horizon-core/src/geometry/sweep.rs": "abd6009c61555161ef2f18b431b2aa384b126c0871cae8b96a62358498a56a1d",
"crates/horizon-core/src/geometry/traversal.rs": "77a3da570a0b85582b669cf4d841cba4b06327006dda26d4a26df4e8ce05bae4",
"crates/horizon-core/src/geometry/trig.rs": "05cef9341a298a474221a0556a11ddf47650393ea7a97b4b2022a4da6628fe37",
"crates/horizon-core/src/lib.rs": "4255e16837fde42a52ba118f60581d197c571bb530b60d635e506184ebf4db0b",
"crates/horizon-core/src/noise/mod.rs": "1dab5a713abf702402e69ace1bf9e476bba544b0ede5a61c3c3afc60af0a03f3",
"crates/horizon-core/src/noise/normal.rs": "e9e7a46472a54a02c98c46da819842090567153b32fe0c84249009ef19af7aad",
"crates/horizon-core/src/noise/perlin.rs": "e73815447432eab7542f67366cb3f1b368d164e33f6c9cdbd045ac8e4db0932a",
"crates/horizon-core/src/noise/simplex.rs": "6c0dbc15b2b7510195dd28fceca01dcaac1847ccc4a84d299c351589c642fe7a",
"crates/horizon-core/src/noise/stack.rs": "7f6ef0e7d920139ef1a6ad3ec5640f92765715f86dcc5ec29f1a149984f45fa3",
"crates/horizon-core/src/noise/volume.rs": "3f05d2b1780acfdfee60037677d309937a5033b45ef428fc2d540942d4b56b43",
"crates/horizon-core/src/position/iter.rs": "32d2d8e74821cc4e49e3354202d35d3120e4415bfdda9375a73f2137ef651129",
"crates/horizon-core/src/position.rs": "e10101088e6902386b9b6d27773d44312f1ff72bebb19e57967a54945dde9dcd",
"crates/horizon-core/src/random/hash.rs": "0f88a127a1ba2705d3d6c0ebd009324d6ba86ffa2d27edf2d2a84765e690e4a3",
"crates/horizon-core/src/random/positional.rs": "35494745e5d4cf89e282c3ae0ee245e9f2640ddbbc03bc0055df742006a9c862",
"crates/horizon-core/src/random/worldgen.rs": "9f07ff33bcc038d34e27aa5465753bb21df18f2975e43cae201f709bc186945a",
"crates/horizon-core/src/random.rs": "ddb95d3a83e28ad605ff1f49f5df270cc543ca5ef1130e37d989dc74c8d861e6",
"crates/horizon-core/src/storage.rs": "f6c32201c64aefde06e57a2c767be46918df613548a322801546b133799cc33d",
"crates/horizon-core/src/text.rs": "e6553ddb5db5039e8dc4614b6a6dc9f35a26dc3d1673b68a04e72e7983b67abb",
"crates/horizon-core/src/ticks/generation.rs": "be1237e790693f633a61714ddae68c6506a777588a8a3ab5efbe53a35774dbd2",
"crates/horizon-core/src/ticks/heap.rs": "bf28afab8af77c3d243aa96cd8a1fb8d934140a4dbd07dbfdbf4e7908bb4b845",
"crates/horizon-core/src/ticks/index.rs": "1f1a6a568c2a30eafecb020ca4d5f5fc414860de85b8b381fc419a2e3e65c24a",
"crates/horizon-core/src/ticks/mod.rs": "01634630668172516b8b79d0907bad47f6e2c543a6d499c2fa4b201823b375cb",
"crates/horizon-world/src/attribute_health.rs": "6135beb1baf9a55745ab95955bcd05e585e4042a6cffda0a14c13f05593e7186",
"crates/horizon-world/src/attributes/entity.rs": "56fb96b1beb9caaa769369bf7cddf522d2f2e84e241c213b51ae8d39d31a9f13",
"crates/horizon-world/src/attributes/mod.rs": "3d05ce2ff10c1b68540e3c87bbd2ac65c56a2e7b7127a7865b60890a2e9a690d",
"crates/horizon-world/src/attributes/operation_map.rs": "fc5bc8921d78ea6721a2eaa915e67483e2a33683927467bdba24f0492356ad97",
"crates/horizon-world/src/bin/worldgen_parity.rs": "3143ceb9b56eecfd7667e5feb5f9f0cd13cfb8754b38d4ea338dbba97f2819da",
"crates/horizon-world/src/block_collision.rs": "39207247f2f6dd3ddc77640229044246f91b32054563f88a435f1bd92ad2092c",
"crates/horizon-world/src/block_collision_scan.rs": "20802862364c110ff85eeaf48606596a8468b83768dda46b412d337958790b41",
"crates/horizon-world/src/block_entities.rs": "a9deae92e6e5a1fcf4f0476c33ea4d170ddad6fd4b3303e1bb66d933973d5e06",
"crates/horizon-world/src/block_entity_push.rs": "12ee91bf604946234b24a0da4333f07d1830ab9743a422d41c8d0afac7d49cfd",
"crates/horizon-world/src/block_landing.rs": "33083fe39266fbefec1b3ea1a19f2afc609791d189da247c895e116ad6eb266a",
"crates/horizon-world/src/block_placement.rs": "524f4892d50bab77b7d89d19f7b048ffe746996f2ed7f376db2c6ab00ed75c24",
"crates/horizon-world/src/block_properties.rs": "b0bd8a983336c48e488a20caa77dd5c39b1a74bbd14ebc03b3b64556a1c864fc",
"crates/horizon-world/src/block_render.rs": "8df52ee11aef406a1571198a64048062a0ef6864e0058d9293a57f64b73c916a",
"crates/horizon-world/src/block_support.rs": "ade4fbd47284b450dabdd682dc86138b2ac05a0fd379525d7110648908245476",
"crates/horizon-world/src/block_survival.rs": "28ce64375e09819a4f13da9cfbe37c08b5ad0daf01b289c1c360b4567e50e811",
"crates/horizon-world/src/blocking_item_damage.rs": "afea3cb5761e8b82ee4d9bb4ef3c6fd10182a324dbaec99e6c9ebfac255dda09",
"crates/horizon-world/src/blocking_rules.rs": "83c5e2105cec56a90182ce582e1c67ade2b189be6de1a34d08035000cc07c492",
"crates/horizon-world/src/bundle_weight.rs": "a0e7b0587ab5d46cd55588f25d55a088b7a4725099c13ba6df146957be36014c",
"crates/horizon-world/src/chunk.rs": "795895284d6e615bcb2082fceacdfe7025c152fb2934bb43f598f17a00ab46f1",
"crates/horizon-world/src/chunk_status.rs": "dbb8583cfa775203c544d24d69e03720385428f15a267a561356c21f5025e9c7",
"crates/horizon-world/src/climbing.rs": "6cd2f919872ff0b726effddaa57eccb805b8eae43f9d01144716010853891879",
"crates/horizon-world/src/collision.rs": "a749794d5c27ccd6c0d964ad02f9dc31d49d9f4fe4b7366cd4bd8155e681b00b",
"crates/horizon-world/src/collision_context.rs": "2176c7034e07f547510a8327cdad9d3b63f26e4754032b76b7d5fe13166c333c",
"crates/horizon-world/src/combat_rules.rs": "01131f70d1acdd96a910e7f96deaa12e51a1e3bb9208085f04e3ec7b657d3cd4",
"crates/horizon-world/src/component_durability.rs": "37a46e184f95bc5027e2b9af72ff52f729ad03601d08e1827f181fca383a35af",
"crates/horizon-world/src/component_patch.rs": "f6f8992f241dfb25e012a06a7ba3fecab8d958686dddd096595a4806e56f0ab3",
"crates/horizon-world/src/compression.rs": "4db73e6ca9a0d552d415ce60e7e88b89c9cf2b7d060a669ff8328f518316e101",
"crates/horizon-world/src/consumable.rs": "11eade3c3a9d8a2cc4549511dbf8d7697b105d221c83827d7ba5cc51d8f702c7",
"crates/horizon-world/src/damage_immunity.rs": "fd39c41124d694e755cf7faadcf93c636df4042c78ec345092a856b5f952dbcf",
"crates/horizon-world/src/damage_reduction.rs": "f5a599ab11e077f526237018aeda803ea3c420c5b35c78ae9d85e32267e23bf3",
"crates/horizon-world/src/damage_source.rs": "24945e397a926d34695652980923609e4723e02fe6b6e56d4aaf673e45d759f7",
"crates/horizon-world/src/dynamic_support.rs": "5996bc1c436fb6215d8fc3d0bb4de5dd9b966d842d432580d624195771697fb8",
"crates/horizon-world/src/edge_backoff.rs": "5259d7aa44e64f88351d1556c44dd0edcad3b400a099c659755792efba7b5773",
"crates/horizon-world/src/entity_collision.rs": "09f2fd7f6cd56fc282f468ecfa765a94506929e9684bd3a5c90123e5b51cd627",
"crates/horizon-world/src/entity_fluid.rs": "02991040ae879a8914f09913128a3506e9e7a1ca33ffc0a22b5729a168346600",
"crates/horizon-world/src/entity_fluid_tick.rs": "4f9e070b88a73c98a4e2e659360ddd6edee636bca29377f1ea9011fd5a556f64",
"crates/horizon-world/src/entity_move.rs": "a40a50d3e841625a75d4cfbb7c2630ab5a62a574456567e7234f401ae8441b32",
"crates/horizon-world/src/entity_obstruction.rs": "a4b28a19bf83b23611c59d94535c488db37e4aee24b22c973c545910894a809f",
"crates/horizon-world/src/extra_items.rs": "6eba0a0c795d5fd841d31471708e78324b4852adebb2e6ed700dcc0caab668cd",
"crates/horizon-world/src/fall_damage.rs": "8c28e2933493569fe05dea22332024287d28b0746b514dd465681040b003296b",
"crates/horizon-world/src/fluid.rs": "a0a1f34ba94d8b59dc975becee8952960764d67ae08cf2d6a8bcd40a614b15c4",
"crates/horizon-world/src/fluid_box.rs": "b586af72b1c04a9354fe66205c28dc8b619e205ab9ea19a68232722e4b6e96c1",
"crates/horizon-world/src/fluid_flow.rs": "7c7e486b5c565df3602799648b37b879bcca51414337f607962d6c7951b86d05",
"crates/horizon-world/src/fluid_occupancy.rs": "26037579caeef6c8a5a2bde2c3d142d278558b76d77ebff401cee6e35f2ea961",
"crates/horizon-world/src/fluid_shape.rs": "28bb65a72f5bd074a354ba38a1b1707fcb0947537977a11540bba0adb85995c2",
"crates/horizon-world/src/food.rs": "3cf416fa632a5555a8116bb528a6e80bcb5b6b754b0748b4959ed310102418d3",
"crates/horizon-world/src/food_properties.rs": "0926fa3f3bc3d0608c45d02f564355825ca3c3526a82bf9c1375c7a191fa5e12",
"crates/horizon-world/src/food_storage.rs": "58c433801a285efcc973638c11d694a41f85dc96703a3a7ce8d3673f3eb31d52",
"crates/horizon-world/src/free_space.rs": "913536bf4c85ddeeb378d76d1b5110b4175f5688e1a749f32fe6a6d86fbb478e",
"crates/horizon-world/src/ground_support.rs": "3e371d74ecede4d30beef0a7e8a2ee33463584a5b1ed3d852b394efcce1ffbe0",
"crates/horizon-world/src/heightmap.rs": "a5edef082b5af40d814421ea17e129b3b06d70abee6434ed263e4bba58571698",
"crates/horizon-world/src/identifier.rs": "a3ad79b1a4ea171726f07b1fc6e2b78f224157e8f0d53dd2ce96e93ccd2d38dd",
"crates/horizon-world/src/interaction_shapes.rs": "f321284ddab440a394372b6dedc8b5cc29a653c0745251a9e96c650f21c33dea",
"crates/horizon-world/src/inventory_pickup.rs": "ea830c7916e7f2d95c6db7ef48be1e876e6ebafcb438e47fc72f9a5bd7c37d07",
"crates/horizon-world/src/inventory_tick.rs": "293a3d57a1689337cfd58e5a73dcdbed4cb360f94d98ff5458f0df92de387365",
"crates/horizon-world/src/inventory_use.rs": "6a6c2ecc73c83dda4cd798c0304df12ace6096eb110d899f4f0c7ffd852bf001",
"crates/horizon-world/src/item_after_use.rs": "2b6598ce5b7b304dea2c8057b4a1eb6530264ac4d0546c3da0c76deed9609da5",
"crates/horizon-world/src/item_blocking.rs": "f6eee0d6d177cc3148960122327ba1b459a2f0e85241c1d9a3e40f0b8c169296",
"crates/horizon-world/src/item_cooldowns.rs": "7b625868509ab3c51e72db0ac714bb0e7ec5e2610d37271005a1a737f4d14431",
"crates/horizon-world/src/item_durability.rs": "9fdf835e5c7bd0e1dd63ef2ba0076b770cdd4d6d14cdcd11de27690f0702f94e",
"crates/horizon-world/src/item_particles.rs": "861b14f65adb9b14f8158fe9f0b7128beac3df8107b1337c31b8b9b49434ba3f",
"crates/horizon-world/src/item_stack_refs.rs": "18d3ca85cac0daf59200857513761359e5690f719cb3f7f64ae552f0c4169ea3",
"crates/horizon-world/src/item_stack_storage.rs": "b4dcb83dcaa346848c5484161feb3e2cf08e83f7793d7ed83be25b14b9d12884",
"crates/horizon-world/src/item_template.rs": "dc80025215d1867e656924ea2a9d273aacbcaea21293d986ba42d0004ee93b69",
"crates/horizon-world/src/item_use.rs": "b1c79428986efbf9dd5ede703a39eb387fb6dcc7d9950f0e49243a353a5b8070",
"crates/horizon-world/src/item_use_catalog.rs": "a9b53d2e816583239a75c3c61e78dacc5e3b27dee595be34b1c7fa169f17a571",
"crates/horizon-world/src/item_use_lifecycle.rs": "4639b70440cf811e79de09c48a3e035143fcb6be8a64c2f61b10ae55a2f133b7",
"crates/horizon-world/src/item_use_start.rs": "a84092dfb8b8f0a89d0928bb2f8ac252bd9bfd3f8d74fce1a7f7a5b3c3f4f50b",
"crates/horizon-world/src/java_hash_map.rs": "6bd97d87f0e3eaad440162145a1515b9a0b7cfb85cb0ea337874ed9745f8dd45",
"crates/horizon-world/src/lib.rs": "60efbce26b1d4ffeed8dd76dfd29db953602d505f0f8ffec7d87f9136bfd7689",
"crates/horizon-world/src/liquid_collision.rs": "0f0a83b04e5a5bc9d98ed05fde4d6584ba69642e93522e726e4cfc325d6fcb1d",
"crates/horizon-world/src/living_air.rs": "b2de36bb50bed15e2808fe4a874fcef07f9fea0d2c96385b7b5e8ee9560d4442",
"crates/horizon-world/src/living_damage.rs": "2b448209629b9bf2e678d667ad0d6c7fa22146892dd106b169d96b31b0df12bb",
"crates/horizon-world/src/living_fall.rs": "da0f176f4731e1c86e08bfeee8167742fd1b86380ff0e9304c13c1ce3f1c67ab",
"crates/horizon-world/src/living_fluid.rs": "d7cd914cfb2a1f4876ce0ae95e999bf0ff1af732cce3ab4a3aa2d1ab881c7339",
"crates/horizon-world/src/living_health.rs": "221659f85552424e937d503bba6d8003006b106ced042b32d3a6f6cc23e12ed0",
"crates/horizon-world/src/living_hurt.rs": "844239ad7d15c39629bb3cfc06ebae6d1cff20b7627468f26e0ee8dff3abdcb8",
"crates/horizon-world/src/living_landing.rs": "8bca4eb7977abc2b14fb7174be4ea355352106ea1e76ff5f3ad8696969d9eefd",
"crates/horizon-world/src/living_motion.rs": "56f8c84cfbe205a7a69912d5f76b52ef1efca149c1b6801d4684e25fb179aeb9",
"crates/horizon-world/src/living_use_flags.rs": "826f4fdeba7e208368cbbf910fa408db0afbc6442d842d178a70fae53e0d0ea6",
"crates/horizon-world/src/main.rs": "fce57418b8c22a6a0a9044baa89ed3d9c243bc2e8413fea3ff0f32985daba021",
"crates/horizon-world/src/movement_emission.rs": "76e7d6f5b19c94676190f54389f9c4ce12cf89945ccfcb9c750de473223e7753",
"crates/horizon-world/src/movement_properties.rs": "6f01ab920b376a13f5ae12c3718f4dd067e1dff2b829585af00a8b2100f3775d",
"crates/horizon-world/src/movement_restitution.rs": "04b4fe0947e7e52576145bd8ed15920278e8a538afcc74d125eaca513dffa0ef",
"crates/horizon-world/src/movement_support.rs": "02d85cf572847f2e60b5a7f9d60b856888a67b3c1f4df03e81998c66fdf874be",
"crates/horizon-world/src/nbt.rs": "60262c3de8c8f68bc2da0370ccb17393d32b85f5f5022c6358599a7d32119ea6",
"crates/horizon-world/src/piston_movement.rs": "7a3fd2549a582b5f3a5c21aa05d67d944d62ca1105856679e100ac5b44e9a52f",
"crates/horizon-world/src/piston_shape.rs": "399c2337103b8e7e0eb5667227c5aa14f9549d6a467b7be7da3d0717f0263500",
"crates/horizon-world/src/player_equipment.rs": "1fa8c53e67624c4641246249d79a800a28a4858c62482bd880b4e3b866b936ef",
"crates/horizon-world/src/player_food.rs": "f188dc110745bcc7acc15e6c521db943b06b404f69cd251bf74a345516070304",
"crates/horizon-world/src/player_health.rs": "79ecb6b4d7a044dc98e5552c53a5ac2724545b1c1cec4d797eb52576728e3480",
"crates/horizon-world/src/player_hurt.rs": "99c9bbd8587ec2dc287806049897b8034d2f5349ad47fc497b70d54148844a36",
"crates/horizon-world/src/player_inventory.rs": "f4f67d8312793f25930626c4ac06db688fcf2e2292d6675071d105fb1708d952",
"crates/horizon-world/src/player_travel.rs": "6841a677a900495eb6223031ef3ebe8d859bb98908dda0e2209e5f28839dc593",
"crates/horizon-world/src/powder_snow_collision.rs": "7289946a21478d11abfd2bdcdddff3d7f98e25bf6a68564fed45c3cc01ac2a36",
"crates/horizon-world/src/region.rs": "72b642e87fed37ed17f54d4c30f96c3b64d26c14bc0589c31ef40ff1616188fc",
"crates/horizon-world/src/registry/json.rs": "7a683afcd1742c5307756927d7f0aa779c7d7992af87d8d371dd98108728e235",
"crates/horizon-world/src/registry.rs": "f2e58b2b4d944ac8fc6226bd3352cefc3d2052d8473e02ccf7dc402b176df435",
"crates/horizon-world/src/registry_collision_scan.rs": "97cc950a9a4ba387b78e368f36d595f8664ab7730d132900d220ce23b92826ea",
"crates/horizon-world/src/registry_fall_ray.rs": "52935436132df505691144840dcbd858b15d00d8305997284e3a542ecf6bc0dc",
"crates/horizon-world/src/registry_fluid.rs": "b8959c05850cfeaaad1e4de8ca7c7d0d6ac56da9b2c1de3ebefa5871a11ed35e",
"crates/horizon-world/src/retrogen.rs": "6efd36a6293756688c1990a7965bd65609d7c5e2bf2a5f4f01d54932d2b9386b",
"crates/horizon-world/src/server_player_combat.rs": "a9743971581af86d926eeba42004d932caf83382c470dd98bf613cb9c417db58",
"crates/horizon-world/src/server_player_damage.rs": "828a4649fec3e9bda2dfe20aced8c95922c697321de2be4127bd4e5712ecbb7c",
"crates/horizon-world/src/simple_container.rs": "0fedbc7e974b1db79b88bd6aff977654189a5d553cf42852fe8bf0885cf90b30",
"crates/horizon-world/src/suffocation.rs": "bed219dd5bbab6263b98fbe3d0eb512cb96c52ae847210efe49514d05f22f11e",
"crates/horizon-world/src/swimming.rs": "49d2dae13c109b37ff832cde98a857944ff45247b864a527b9c4f03ecca7a7b7",
"crates/horizon-world/src/synced_floats.rs": "0152f2752bafaf68769d56dbc88494a976496a680c6b113d6b48a446158ba072",
"crates/horizon-world/src/synced_values.rs": "adffa15026497d6671ffd12e5e17df9329c93f0e022700cdfebb80ab494382ae",
"crates/horizon-world/src/tick_data.rs": "f8d04a69ea263f4168bce33f92e5b4ee2fe07d7caf50a1621c6b3ef6eb2479f3",
"crates/horizon-world/src/use_remainder.rs": "f555335afeca7970888131a6da2f5a614a73253b631185d1ba804593a349535e",
"crates/horizon-world/src/view_direction.rs": "e18120ae26dc5cf702b29fc4c8487088bab133c8ce5abce01226833fafb0d5a4",
"crates/horizon-world/src/water_splash.rs": "e0461ae1b2e8c851cca6e3251a188334293292201d7f80dc9e7d60fd1815f4d1",
"crates/horizon-world/src/world_movement.rs": "f8fe9e34f2395d2a04c839d1c8ba78e09ce493c26de6f6e4e237b9c19c061da5",
"crates/horizon-world/src/world_ray.rs": "879bdbbe12f56deb705cc183933431489e840b71eade40c33391eed5949c2e61",
"crates/horizon-world/src/worldgen/anchor.rs": "d156b8a0b147a274cdd143409b441f4f43edecde6e8ee14ef9cf0309f743143a",
"crates/horizon-world/src/worldgen/aquifer/config.rs": "fcd2cc3a520c1527c88f095a3b5d38487e7ebb1ccecc5f757f93d04a069add4f",
"crates/horizon-world/src/worldgen/aquifer/noise_based.rs": "74daaa389b5c3dfdc99214dfff256a9e172f83548d2326cbc941d9637cc53fb7",
"crates/horizon-world/src/worldgen/aquifer.rs": "cb4b66d87de3f9f79ae6ffe4badd6f0f44886cd42ad9de2771b8b56c4f1e28c7",
"crates/horizon-world/src/worldgen/bamboo.rs": "d9b47113c99be34256e58308bc6f40072ff25c579d6bf995182aa862c0006102",
"crates/horizon-world/src/worldgen/biome.rs": "3fe316b875aa32683759c337dc71f624910faf0812a2d6d83b8098d6d0bac7a0",
"crates/horizon-world/src/worldgen/biome_chunk.rs": "8fdfe6ebe3ffeca97611fd5991a5bc6b2783ea1c62f4d8ef3b2dd53c04a5dc1d",
"crates/horizon-world/src/worldgen/biome_predicate.rs": "bfc581bc40077cc74ffda43e990a55fe997adc04c7130f98e039c3305ca79aac",
"crates/horizon-world/src/worldgen/biome_source/codec.rs": "2b9e4f5d679eb444cb73174f511e3b9f32e9eb138248ab12cffc6cfd76adf3a8",
"crates/horizon-world/src/worldgen/biome_source/runtime.rs": "f0b02c1521178e4d0377c6ca62694604b6a773f1f234f070ff0cde4685911d45",
"crates/horizon-world/src/worldgen/biome_source.rs": "e181e1736f3e0b75f7091a24c88de3fd116e01b98424d02382aea7a503d1eb54",
"crates/horizon-world/src/worldgen/biome_temperature.rs": "cde4bbab5e260c4065b113376c423b8511ade93c5aa5fd976e245ee8a58b5ad1",
"crates/horizon-world/src/worldgen/biome_weather.rs": "96b1f9494559ed31d06257b83d604d00cc069ccc8c7b68094117e034b4a71bd7",
"crates/horizon-world/src/worldgen/block_blob.rs": "c608c57b3d77279646ecb89e50677dd8e452b0422ffd357ba755536c5589eb89",
"crates/horizon-world/src/worldgen/block_column.rs": "f72c21a9066ee0aa68c2fb79fba436d05afb14c6ef5a2cb012f1ed35fc17cd22",
"crates/horizon-world/src/worldgen/block_feature.rs": "36b4e46ee7f7f73bcb94d8bce8d99013e4b16234c9a03542eeb3186e88dd1c21",
"crates/horizon-world/src/worldgen/block_pile.rs": "623e369b77e0526ed2756485a6c0bb09683cd28ad8876dea907360ca6665b39d",
"crates/horizon-world/src/worldgen/block_predicate.rs": "074c692367e3f2191a1bb28d87b23402430a843c246bd30a4ea838ee3500280e",
"crates/horizon-world/src/worldgen/blue_ice.rs": "eedd77bdaff808330b864166d303378096daa5c7d235e925770b171c05109247",
"crates/horizon-world/src/worldgen/bonus_chest.rs": "05d0f63fbc7412f13ba1fa37a7238114b8f70bfff318beb2dece590482d49cc0",
"crates/horizon-world/src/worldgen/bulk_section.rs": "eecf61532d7793f853884c5a18a7f436a59835c7b13d3ec9075804511553c9ba",
"crates/horizon-world/src/worldgen/carver/canyon.rs": "509c4d5a9d689849e98e894ab14a9fbf82d10707d38e9cc688ded3c4778df3e2",
"crates/horizon-world/src/worldgen/carver/generation.rs": "a4498d0d1457536de3922a08e9c40ccce8d2ef9cc263801c15a412327513c098",
"crates/horizon-world/src/worldgen/carver.rs": "46b251e28225fe1250de38f49dec5dacda4e2c4ba1e556e21e6d1e14e2651fc3",
"crates/horizon-world/src/worldgen/carving.rs": "4e77d4e5c4a0ca55ca3563b9bdf6f379342e0bdf9d019d60f7bc57ce4afb5fbd",
"crates/horizon-world/src/worldgen/chorus_plant.rs": "29224c164ede919e280a65a68f155c873e2c8fd0aa7d7e244d8fa9e468760250",
"crates/horizon-world/src/worldgen/climate/codec.rs": "d567adeb5d837816fca69102db51d790f71aec1b51c09298230d24612e400a14",
"crates/horizon-world/src/worldgen/climate/index.rs": "c5ce61679a5f0e0f2f0af2a373424effa124d0cce8250609eff38b7034650756",
"crates/horizon-world/src/worldgen/climate.rs": "1b9ad44407f40aee4c1cd5357a5c9c443c1b105728f01cf5c644072f99f8357e",
"crates/horizon-world/src/worldgen/decoration.rs": "75a79b20b21e133816e729f16e758be6165d0db18a64bfbd5fd3618325a2adf3",
"crates/horizon-world/src/worldgen/delta.rs": "c2bf49ddf3231d28c109e68871429bc13d76a7bebe14eb33e84aa856bdd66145",
"crates/horizon-world/src/worldgen/density.rs": "65a98c156a290a6b374ef670d28cd5dfbe0aa93171824d1bbd96422536273f84",
"crates/horizon-world/src/worldgen/disk.rs": "6f8f9e059755af44c9bfa1b91eba54a7d13fad811dce31717c5502d7f6df33d7",
"crates/horizon-world/src/worldgen/end_gateway.rs": "f53fde77d6a36a525cd15d890003b2c129d0689a4cbfa2cf443a0c2e3acae040",
"crates/horizon-world/src/worldgen/end_podium.rs": "8f6d0d6d60ff0fde63de5781e867239db321f82cf12639f0537d410a3731ee1d",
"crates/horizon-world/src/worldgen/feature.rs": "40500af71ee6628dfb27518262d99f08d0383ae6c7b9397452feef0c213eadd9",
"crates/horizon-world/src/worldgen/feature_catalog.rs": "efc7b17b227e71622ef34108be9abd010f5675b68ea0409fcab82581d9aab88a",
"crates/horizon-world/src/worldgen/feature_graph.rs": "da132fca9fdafb0d89a917d5f4b68c60b70fb33cadf948a107da0e35d83dcd91",
"crates/horizon-world/src/worldgen/feature_region.rs": "df0ff142a461cafa9a3b42b949ad3376887f2be8d4ea5cc794a3ed2e977bb499",
"crates/horizon-world/src/worldgen/feature_sort.rs": "3488a093dfe1321aaa29d8a30cfb115d8e666a81bf195514e87b170de626835c",
"crates/horizon-world/src/worldgen/fluid_predicate.rs": "64197c9e63bebf1ce04bc280bfe980bb6c1b9aa887ca5cded904aeb0d9ae6c46",
"crates/horizon-world/src/worldgen/height_provider.rs": "368d4dc119854742a045c4a717679c921456da681fc5f71aa9b37102d6f33b47",
"crates/horizon-world/src/worldgen/material_condition/codec.rs": "09b909bc384472f76d163c8069ec6b507c7d5c6a028a36d5e0e3df20deff7b3b",
"crates/horizon-world/src/worldgen/material_condition/density.rs": "df9d0761e978e5010663ae8b44f2d6c1ecb4907331e333bdb6a2b487c1e107de",
"crates/horizon-world/src/worldgen/material_condition/library.rs": "a55534c5da97575a819af5a7dc0dbec9069993fa8b2619c6c9d5576946e683fa",
"crates/horizon-world/src/worldgen/material_condition/noise.rs": "ffb59cc6be2275d7a5621bf7d768527e74a8326ed7ad78618746711540921675",
"crates/horizon-world/src/worldgen/material_condition/rule.rs": "fed643352efbcb1f68f29b6241896617db889fc7c577a6247564e01922fec1fe",
"crates/horizon-world/src/worldgen/material_condition.rs": "d638dcb8cbcb5da20b3cb29602b0ae6a86c4fdf9d8c0fb725de8ec5fc108717e",
"crates/horizon-world/src/worldgen/material_extensions.rs": "ab806eb679d5116e1aebb64fca70e1499ae71f83c9507a8cb4c3201c2b1bf037",
"crates/horizon-world/src/worldgen/material_stage.rs": "698cbc786eff13c246e2bc2ea062401918cd4265899fbc3d53abc87e45a8e7a1",
"crates/horizon-world/src/worldgen/material_surface.rs": "5d37b90099df3d6da0845dab164e71469fb287019c2ee74bc6714f3b9d6d6d5b",
"crates/horizon-world/src/worldgen/ore_feature.rs": "83b89fd3be691c5412c0215b6abefdcaa44119a05aa41217e061970eb7e12ebd",
"crates/horizon-world/src/worldgen/placement/chain.rs": "9218399b4d3757f7bbc550d7729a7201f32a1c83a7030865e0c12aab8a80d116",
"crates/horizon-world/src/worldgen/placement/context.rs": "a278304795136677ad4b4933ea50c972b9729a1c671c03fa309c193e224b1fd6",
"crates/horizon-world/src/worldgen/placement/layer.rs": "a0f5ca776f33f9d4f72b8fdcd163426152e5e43cb4a7572fc1d4fc57bf965de1",
"crates/horizon-world/src/worldgen/placement.rs": "b0397e5818fae84358546193a95f84bcb0b5bffafce909e6fcb3386179ee8023",
"crates/horizon-world/src/worldgen/replace_blobs.rs": "bf3377d86d2570cd15288ba69dde64a2cf67c5bcbb9a31d0c23421964af74afa",
"crates/horizon-world/src/worldgen/router.rs": "2cee7cf15a0d2ada12be6b39d83e78177fd3033922e4f4cce87a64073cec2847",
"crates/horizon-world/src/worldgen/rule_test.rs": "d1662ec492489ebaa4b87e20f9a8c809dcf3a223a151211bd65ec241a63692fc",
"crates/horizon-world/src/worldgen/settings.rs": "a8fe87752e1982ef11cb67f8d80d8e3428b2c27db178044cb425ba803e689e59",
"crates/horizon-world/src/worldgen/simple_block.rs": "9ba1a88d5db4925f8c37366557887f776c805f9a6444781615b5dbaff52e92a4",
"crates/horizon-world/src/worldgen/snow_freeze.rs": "9040be3d5c7f8f0fd812d2df45e154bf8a9eeef038530b83fa4159d246eac35f",
"crates/horizon-world/src/worldgen/speleothem.rs": "c71651c1c97818a8a85863e14414d6139ea3bdeb897fa422043bcc1499d99aae",
"crates/horizon-world/src/worldgen/spike.rs": "eacf0d9d3d2bc3d4bcf62bc87555f90031138236a5bfef0eb5270204f7c2a4fc",
"crates/horizon-world/src/worldgen/spring.rs": "830849e9b89fcdf09e9b06fbefdcdebe3c8c3f82804f60b8bd5de04ba303cb62",
"crates/horizon-world/src/worldgen/state_provider/catalog.rs": "c8687e15a4887a9200c8d365857d801efaa8f90e669eee3e600a1db4905119eb",
"crates/horizon-world/src/worldgen/state_provider/noise.rs": "7ee369c2a032e5620cb9bdf9ee7adad1f70870fb0a27efb7b7c514d92f83491f",
"crates/horizon-world/src/worldgen/state_provider/properties.rs": "9909139aaca1dc4f743957257fadaa9d423d21fa401fa9abc050505f79365139",
"crates/horizon-world/src/worldgen/state_provider.rs": "be29ada818826a90afe01ab60916360fdfc4cbde5af15c9359cd56b1f1f7b156",
"crates/horizon-world/src/worldgen/tags.rs": "74e4110fb19c4c4047192fd65c38015b8afc7f9fc50404d75380b5063808148c",
"crates/horizon-world/src/worldgen/terrain.rs": "fd1f2e0afc15daf66f484ea40a1f2b7013650bda0941b23c64c81ee9f79cb673",
"crates/horizon-world/src/worldgen/underwater_magma.rs": "7317e40af5ee5a9780f90ce358fa5d3e7f5b3d513b731df6ff97754d9c2a0154",
"crates/horizon-world/src/worldgen/value_provider.rs": "3358b563db8cb0bc8a9cfbbe2b8b3e70941cd2d37a0b519fbb6545f8d0dcbb69",
"crates/horizon-world/src/worldgen/vines.rs": "ee9b79b65012bd86a838241d2ef708680f7fe8eafc8aa656dae9cf2abf3c6418",
"crates/horizon-world/src/worldgen/weighted.rs": "839840aae25302933bbd97a81a2336330e2d03a0ad1684e1e16ac769ed408e6b",
"crates/horizon-world/src/worldgen/write_zone.rs": "8e17a179764d3f68c05882aef35f6810c28a0000f9a83e58bd6030660b76e64e",
"crates/horizon-world/src/worldgen.rs": "e8e9f099ee1fcec8eedce31805d676d3f89c18e84f494a147ffcbfee23d3d54f",
"crates/horizon-world/examples/consumable_emission_fixture.rs": "e2ff60d3000e223146d5627d1f597dc51e94063ad5c2cd3fe56bd6dd534d61f1"
},
"fixtures_sha256": {
".local/rust-consumable-emission-parity/inputs.txt": "878816271f14f745794e99d1e770b3a12ab4fe8eab342ff576c6485f06918d78"
},
"rust_output_sha256": "223552ebdae6a7d52fc00511b65857bc31672c841c4dbf61a9061077a770a435",
"references": [
{
"side": "client",
"jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d",
"output_sha256": "223552ebdae6a7d52fc00511b65857bc31672c841c4dbf61a9061077a770a435",
"matched_output_lines": 124732
},
{
"side": "server",
"jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6",
"output_sha256": "223552ebdae6a7d52fc00511b65857bc31672c841c4dbf61a9061077a770a435",
"matched_output_lines": 124732
}
],
"scope": "Actual Consumable timing and emitParticlesAndSounds composed with inherited LivingEntity.spawnItemParticles, real templates and both native RNGs. Compares all animations, unconditional random draws, supplied versus direct random sources, sound overrides after particle callbacks, retained sound parameters, float timing edges and mutation/failure prefixes. Audio playback, rendering, consumable completion/effects and full gameplay remain pending."
}
@@ -0,0 +1,253 @@
use horizon_core::{
geometry::Vec3,
random::{LegacyRandom, RandomSource, XoroshiroRandom},
};
use horizon_world::consumable::{Consumable, EmissionWorld, UseAnimation};
use horizon_world::{
component_patch::Prototype,
item_particles::{self, ItemParticleWorld, ParticleError},
item_stack_refs::{ItemStacks, StackRef},
item_template::ItemTemplate,
};
use serde_json::Value;
use std::{
fs,
io::{self, Write},
path::Path,
};
type Stack = StackRef<u8, u8, i32>;
type Template = ItemTemplate<u8, u8, i32>;
fn n(v: &Value, k: &str) -> i32 {
v[k].as_i64().unwrap() as i32
}
fn fl(v: &Value, k: &str) -> f32 {
f32::from_bits(v[k].as_u64().unwrap() as u32)
}
fn bits(v: f64) -> u64 {
if v.is_nan() {
0x7ff8000000000000
} else {
v.to_bits()
}
}
fn opt(v: Option<&i32>) -> String {
v.map_or("none".into(), i32::to_string)
}
struct World<'a, R> {
f: Value,
out: &'a mut dyn Write,
stack: Stack,
random: [R; 2],
position: Vec3,
changed: bool,
calls: i32,
first: Option<Template>,
}
impl<R: RandomSource> World<'_, R> {
fn config(&self) -> &Value {
&self.f[if self.changed { "after" } else { "initial" }]
}
fn reset(&mut self) {
let c = self.config().clone();
let p = &c["position"];
self.position = Vec3::new(
p[0].as_f64().unwrap(),
p[1].as_f64().unwrap(),
p[2].as_f64().unwrap(),
);
let mut s = self.stack.write();
s.count = n(&c, "count");
s.components
.set(0, c["damage"].as_i64().map(|v| v as i32))
.unwrap();
if c["reseed"].as_bool().unwrap() {
for i in 0..2 {
self.random[i].set_seed(c["seeds"][i].as_i64().unwrap());
}
}
}
fn all(&mut self) {
let s = self.stack.read();
writeln!(
self.out,
"state {} {} {} {} {} {} {}",
self.calls,
self.changed,
n(self.config(), "random"),
n(self.config(), "level"),
s.item(),
s.count(),
opt(s.component(&0))
)
.unwrap();
}
fn event(&mut self, name: String) -> Result<(), &'static str> {
writeln!(self.out, "event {name}").unwrap();
let i = self.calls;
self.calls += 1;
self.all();
if i == n(&self.f, "fail") {
return Err("callback");
}
if i == n(&self.f, "mutate") {
self.changed = true;
self.reset();
}
Ok(())
}
}
impl<R: RandomSource> ItemParticleWorld<u8, u8, i32> for World<'_, R> {
type Error = &'static str;
type Level = i32;
type Source = R;
fn random(&mut self) -> &mut R {
let i = n(self.config(), "random") as usize;
&mut self.random[i]
}
fn pitch(&mut self) -> Result<f32, Self::Error> {
self.event("pitch".into())?;
Ok(fl(self.config(), "pitch"))
}
fn yaw(&mut self) -> Result<f32, Self::Error> {
self.event("yaw".into())?;
Ok(fl(self.config(), "yaw"))
}
fn position(&self) -> Vec3 {
self.position
}
fn eye_y(&mut self) -> Result<f64, Self::Error> {
self.event("eye".into())?;
Ok(self.position.y + f64::from(fl(self.config(), "eye")))
}
fn level(&mut self) -> Result<i32, Self::Error> {
self.event("level".into())?;
Ok(n(self.config(), "level"))
}
fn add_particle(
&mut self,
level: i32,
t: &Template,
p: Vec3,
v: Vec3,
) -> Result<(), Self::Error> {
let same = self
.first
.as_ref()
.is_none_or(|first| first.same_reference(t));
if self.first.is_none() {
self.first = Some(t.clone());
}
let damage = opt(t.component(&Prototype::new([]), &0));
self.event(format!(
"particle {level} {} {} {damage} {same} {} {} {} {} {} {}",
t.item(),
t.count(),
bits(p.x),
bits(p.y),
bits(p.z),
bits(v.x),
bits(v.y),
bits(v.z)
))
}
}
impl<R: RandomSource> EmissionWorld for World<'_, R> {
type Error = &'static str;
type Stack = Stack;
type Sound = u8;
type Random = usize;
type Source = R;
fn random_source(&mut self, r: usize) -> &mut R {
&mut self.random[r]
}
fn spawn_item_particles(&mut self, s: Stack, count: i32) -> Result<(), Self::Error> {
assert!(s.same_reference(&self.stack));
self.event(format!("spawn {count}"))?;
item_particles::spawn(self, &s, 0, count).map_err(|e| match e {
ParticleError::World(e) => e,
ParticleError::Template(_) => "template",
})
}
fn overrides_consume_sound(&self) -> bool {
self.f["override"].as_bool().unwrap()
}
fn consume_sound(&mut self, s: Stack) -> Result<u8, Self::Error> {
assert!(s.same_reference(&self.stack));
self.event("override".into())?;
Ok(n(self.config(), "sound") as u8)
}
fn play_sound(&mut self, s: u8, volume: f32, pitch: f32) -> Result<(), Self::Error> {
self.event(format!(
"sound {s} {} {}",
volume.to_bits(),
pitch.to_bits()
))
}
}
fn run<R: RandomSource>(f: Value, out: &mut dyn Write, make: fn(i64) -> R) {
let random = std::array::from_fn(|i| make(f["seeds"][i].as_i64().unwrap()));
let stack = ItemStacks::new(0).create(n(&f, "item") as u8, 1, Prototype::new([]));
let count = n(&f, "particles");
let mut w = World {
f,
out,
stack: stack.clone(),
random,
position: Vec3::ZERO,
changed: false,
calls: 0,
first: None,
};
w.reset();
w.all();
let animation = [
UseAnimation::None,
UseAnimation::Eat,
UseAnimation::Drink,
UseAnimation::Block,
UseAnimation::Bow,
UseAnimation::Trident,
UseAnimation::Crossbow,
UseAnimation::Spyglass,
UseAnimation::TootHorn,
UseAnimation::Brush,
UseAnimation::Bundle,
UseAnimation::Spear,
][n(&w.f, "animation") as usize];
let component = Consumable::<u8> {
seconds: fl(&w.f, "seconds"),
animation,
sound: n(&w.f, "sound") as u8,
particles: w.f["has_particles"].as_bool().unwrap(),
effects: vec![],
};
let remaining = n(&w.f, "remaining");
writeln!(
w.out,
"timing {} {}",
component.ticks(),
component.should_emit(remaining)
)
.unwrap();
let random = n(&w.f, "emit_random") as usize;
let result = component.emit(&mut w, random, stack, count);
writeln!(w.out, "result {}", result.err().unwrap_or("ok")).unwrap();
w.all();
for (i, r) in w.random.iter_mut().enumerate() {
writeln!(w.out, "random {i} {} {}", r.next_long(), r.next_long()).unwrap();
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let arg = std::env::args().nth(1).unwrap();
let dir = Path::new(&arg);
let mut out = io::BufWriter::new(fs::File::create(dir.join("rust.txt"))?);
for line in fs::read_to_string(dir.join("inputs.txt"))?.lines() {
let f: Value = serde_json::from_str(line)?;
if f["xor"].as_bool().unwrap() {
run(f, &mut out, XoroshiroRandom::new);
} else {
run(f, &mut out, LegacyRandom::new);
}
}
Ok(())
}
+96
View File
@@ -0,0 +1,96 @@
//! Consumable component timing and ordered particle/sound emission.
use horizon_core::random::RandomSource;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UseAnimation {
None,
Eat,
Drink,
Block,
Bow,
Trident,
Crossbow,
Spyglass,
TootHorn,
Brush,
Bundle,
Spear,
}
#[derive(Clone, Debug)]
pub struct Consumable<S, E = ()> {
pub seconds: f32,
pub animation: UseAnimation,
pub sound: S,
pub particles: bool,
pub effects: Vec<E>,
}
impl<S: PartialEq, E: PartialEq> PartialEq for Consumable<S, E> {
fn eq(&self, other: &Self) -> bool {
let bits = |v: f32| if v.is_nan() { 0x7fc00000 } else { v.to_bits() };
bits(self.seconds) == bits(other.seconds)
&& self.animation == other.animation
&& self.sound == other.sound
&& self.particles == other.particles
&& self.effects == other.effects
}
}
pub trait EmissionWorld {
type Error;
type Stack: Clone;
type Sound: Clone;
type Random: Copy;
type Source: RandomSource;
/// This is the supplied random source, which may differ from the actor's
/// direct field used inside spawn_item_particles.
fn random_source(&mut self, random: Self::Random) -> &mut Self::Source;
fn spawn_item_particles(&mut self, stack: Self::Stack, count: i32) -> Result<(), Self::Error>;
/// Native class/interface membership, not a virtual actor call.
fn overrides_consume_sound(&self) -> bool;
fn consume_sound(&mut self, stack: Self::Stack) -> Result<Self::Sound, Self::Error>;
fn play_sound(
&mut self,
sound: Self::Sound,
volume: f32,
pitch: f32,
) -> Result<(), Self::Error>;
}
impl<S, E> Consumable<S, E> {
pub fn ticks(&self) -> i32 {
(self.seconds * 20.0) as i32
}
pub fn should_emit(&self, remaining: i32) -> bool {
let elapsed = self.ticks().wrapping_sub(remaining);
let threshold = (self.ticks() as f32 * 0.21875) as i32;
elapsed > threshold && remaining % 4 == 0
}
}
impl<S: Clone, E> Consumable<S, E> {
pub fn emit<W: EmissionWorld<Sound = S>>(
&self,
world: &mut W,
random: W::Random,
stack: W::Stack,
count: i32,
) -> Result<(), W::Error> {
let source = world.random_source(random);
let eat_volume = if source.next_boolean() { 0.5 } else { 1.0 };
let eat_pitch = source.triangle_float(1.0, 0.2);
let drink_pitch = 0.9f32 + source.next_float() * (1.0f32 - 0.9f32);
let (volume, pitch) = if self.animation == UseAnimation::Drink {
(0.5, drink_pitch)
} else {
(eat_volume, eat_pitch)
};
if self.particles {
world.spawn_item_particles(stack.clone(), count)?;
}
let sound = if world.overrides_consume_sound() {
world.consume_sound(stack)?
} else {
self.sound.clone()
};
world.play_sound(sound, volume, pitch)
}
}
+1
View File
@@ -25,6 +25,7 @@ pub mod combat_rules;
pub mod component_durability;
pub mod component_patch;
pub mod compression;
pub mod consumable;
pub mod damage_immunity;
pub mod damage_reduction;
pub mod damage_source;
+16
View File
@@ -1969,3 +1969,19 @@ cases, random-source replacement and callback mutation/failure prefixes.
Targeted Clippy passes. The fixture uses scalar damage component patches and
controlled particle delivery; renderer enqueue, full consumable dispatch and
complete gameplay remain pending.
`consumable` supplies consumption timing and particle/sound dispatch. Both
eating and drinking pitch draws occur for every animation before particle
callbacks. Sound overrides run afterward; volume and pitch retain their
previously drawn values. Particle emission may use a different actor random
source from the one supplied for sound selection.
`python3 tools/test_consumable_emission_parity.py` composes actual Consumable
emission with inherited LivingEntity particles and real item templates. The
[consumable emission report](../benchmarks/rust-consumable-emission-2026-10-07/scoped-parity.json)
records **124,732 matching lines per JAR**, covering all animations, timing
overflow and float edges, random-source aliases, optional particles, sound
overrides and callback mutation/failure prefixes. Targeted Clippy passes. Sound
playback, rendering, consumable completion/effects and full gameplay remain
pending.
@@ -0,0 +1,38 @@
package dev.horizonlab.parity;
import com.google.gson.*;import java.io.*;import java.lang.reflect.*;import java.nio.file.*;import java.util.*;
import net.minecraft.world.item.component.Consumable;import net.minecraft.sounds.*;import net.minecraft.core.Holder;
import net.minecraft.world.entity.*;import net.minecraft.world.level.Level;import net.minecraft.server.level.ServerLevel;import net.minecraft.world.item.*;import net.minecraft.util.RandomSource;import net.minecraft.world.level.levelgen.XoroshiroRandomSource;import net.minecraft.world.phys.Vec3;import net.minecraft.core.particles.*;import net.minecraft.core.component.DataComponents;
/** Actual consumable emission composed with inherited item particles. */
public final class VanillaConsumableEmissionOracle {
static Object unsafe;static Method allocate;static Field position,randomField;
static Field field(Class<?> c,String n)throws Exception{var f=c.getDeclaredField(n);f.setAccessible(true);return f;}
static <T>T make(Class<T> c)throws Exception{return c.cast(allocate.invoke(unsafe,c));}
static int n(JsonObject v,String k){return v.get(k).getAsInt();}
static float fl(JsonObject v,String k){return Float.intBitsToFloat((int)v.get(k).getAsLong());}
static String bits(double v){return Long.toUnsignedString(Double.doubleToLongBits(v));}
static int kind(Item item){return item==Items.AIR?0:item==Items.STONE?1:2;}
static String opt(Integer value){return value==null?"none":value.toString();}
static final class Stop extends RuntimeException{}
static final class Data{JsonObject f;PrintWriter out;RandomSource[] random;Actor actor;ParticleLevel[] levels;ItemStack stack;ItemParticleOption first;boolean changed;int calls;
JsonObject config(){return f.getAsJsonObject(changed?"after":"initial");}
void reset(){try{var c=config();var p=c.getAsJsonArray("position");position.set(actor,new Vec3(p.get(0).getAsDouble(),p.get(1).getAsDouble(),p.get(2).getAsDouble()));randomField.set(actor,random[n(c,"random")]);stack.setCount(n(c,"count"));if(c.get("damage").isJsonNull())stack.remove(DataComponents.DAMAGE);else stack.set(DataComponents.DAMAGE,n(c,"damage"));if(c.get("reseed").getAsBoolean())for(int i=0;i<2;i++)random[i].setSeed(c.getAsJsonArray("seeds").get(i).getAsLong());}catch(Exception e){throw new AssertionError(e);}}
void all(){out.println("state "+calls+" "+changed+" "+n(config(),"random")+" "+n(config(),"level")+" "+kind(stack.getItem())+" "+stack.getCount()+" "+opt(stack.get(DataComponents.DAMAGE)));}
void event(String name){out.println("event "+name);int index=calls++;all();if(index==n(f,"fail"))throw new Stop();if(index==n(f,"mutate")){changed=true;reset();}}
}
static class Actor extends net.minecraft.world.entity.decoration.ArmorStand{Data d;Actor(){super(null,null);}
@Override public void spawnItemParticles(ItemStack s,int count){if(s!=d.stack)throw new AssertionError();d.event("spawn "+count);super.spawnItemParticles(s,count);}
@Override public void playSound(SoundEvent s,float volume,float pitch){d.event("sound "+(s==SoundEvents.GENERIC_EAT.value()?0:s==SoundEvents.PLAYER_BURP?1:-1)+" "+Integer.toUnsignedString(Float.floatToIntBits(volume))+" "+Integer.toUnsignedString(Float.floatToIntBits(pitch)));}
@Override public RandomSource getRandom(){throw new AssertionError("virtual random getter must not be used");}
@Override public float getXRot(){d.event("pitch");return fl(d.config(),"pitch");}
@Override public float getYRot(){d.event("yaw");return fl(d.config(),"yaw");}
@Override public double getEyeY(){d.event("eye");return getY()+(double)fl(d.config(),"eye");}
@Override public Level level(){d.event("level");return d.levels[n(d.config(),"level")];}
}
static final class OverrideActor extends Actor implements Consumable.OverrideConsumeSound {public SoundEvent getConsumeSound(ItemStack s){if(s!=d.stack)throw new AssertionError();d.event("override");return n(d.config(),"sound")==0?SoundEvents.GENERIC_EAT.value():SoundEvents.PLAYER_BURP;}}
static final class ParticleLevel extends ServerLevel{Data d;int id;ParticleLevel(){super(null,null,null,null,null,null,false,0L,List.of(),false);}
@Override public void addParticle(ParticleOptions options,double x,double y,double z,double vx,double vy,double vz){var p=(ItemParticleOption)options;if(p.getType()!=ParticleTypes.ITEM)throw new AssertionError();boolean same=d.first==null||d.first==p;if(d.first==null)d.first=p;var t=p.getItem();d.event("particle "+id+" "+kind(t.item().value())+" "+t.count()+" "+opt(t.get(DataComponents.DAMAGE))+" "+same+" "+bits(x)+" "+bits(y)+" "+bits(z)+" "+bits(vx)+" "+bits(vy)+" "+bits(vz));}
}
public static void main(String[] args)throws Exception{net.minecraft.SharedConstants.tryDetectVersion();net.minecraft.server.Bootstrap.bootStrap();for(var item:new Item[]{Items.AIR,Items.STONE,Items.BREAD})item.builtInRegistryHolder().bindComponents(net.minecraft.core.component.DataComponentMap.EMPTY);var u=Class.forName("sun.misc.Unsafe");unsafe=field(u,"theUnsafe").get(null);allocate=u.getMethod("allocateInstance",Class.class);position=field(Entity.class,"position");randomField=field(Entity.class,"random");Path dir=Path.of(args[1]);
try(var out=new PrintWriter(Files.newBufferedWriter(dir.resolve(args[0]+".txt")));var lines=Files.lines(dir.resolve("inputs.txt"))){for(var line:(Iterable<String>)lines::iterator){var d=new Data();d.f=JsonParser.parseString(line).getAsJsonObject();d.out=out;d.random=new RandomSource[2];for(int i=0;i<2;i++){long seed=d.f.getAsJsonArray("seeds").get(i).getAsLong();d.random[i]=d.f.get("xor").getAsBoolean()?new XoroshiroRandomSource(seed):RandomSource.create(seed);}d.actor=d.f.get("override").getAsBoolean()?make(OverrideActor.class):make(Actor.class);d.actor.d=d;d.levels=new ParticleLevel[2];for(int i=0;i<2;i++){d.levels[i]=make(ParticleLevel.class);d.levels[i].d=d;d.levels[i].id=i;}d.stack=new ItemStack(new Item[]{Items.AIR,Items.STONE,Items.BREAD}[n(d.f,"item")]);d.reset();d.all();var component=new Consumable(fl(d.f,"seconds"),ItemUseAnimation.values()[n(d.f,"animation")],Holder.direct(n(d.f,"sound")==0?SoundEvents.GENERIC_EAT.value():SoundEvents.PLAYER_BURP),d.f.get("has_particles").getAsBoolean(),List.of());out.println("timing "+component.consumeTicks()+" "+component.shouldEmitParticlesAndSounds(n(d.f,"remaining")));try{component.emitParticlesAndSounds(d.random[n(d.f,"emit_random")],d.actor,d.stack,n(d.f,"particles"));out.println("result ok");}catch(Stop e){out.println("result callback");}d.all();for(int i=0;i<2;i++)out.println("random "+i+" "+d.random[i].nextLong()+" "+d.random[i].nextLong());}
}}
}
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Compare consumable timing, sound selection and concrete eating particles."""
import argparse
import json
import os
from pathlib import Path
import platform
import subprocess
import random
try:
from . import lab, test_rust_parity as base
except ImportError:
import lab
import test_rust_parity as base
def corpus():
floats=[0,0x80000000,1,0x3d000000,0x3d4ccccd,0x3f000000,0x3f800000,0x3fcccccd,0xbf800000,0x42b40000,0xc2b40000,0x7f7fffff,0x7f800000,0xff800000,0x7fc00000]
seeds=[0,1,-1,-9223372036854775808,9223372036854775807,123456789]
def config(**kw):
v=dict(position=[1.25,64.5,-2.75],pitch=0,yaw=0,eye=0x3fcf5c29,count=3,damage=7,random=0,level=0,sound=0,reseed=False,seeds=[99,101]);v.update(kw);return v
def row(**kw):
v=dict(initial=config(),after=config(position=[-3.5,71.25,9.5],pitch=0x42b40000,yaw=0xc2b40000,count=1,damage=99,random=1,level=1,sound=1,reseed=True),seeds=[0,1],item=2,xor=False,particles=3,fail=-1,mutate=-1,animation=1,seconds=0x3fcccccd,remaining=24,sound=0,has_particles=True,emit_random=0,override=True);v.update(kw);return v
for xor in [False,True]:
for seed in seeds:
for animation in [1,2]:
for fail in range(-1,12):
for mutate in range(-1,12):yield row(xor=xor,seeds=[seed,~seed],animation=animation,fail=fail,mutate=mutate)
for animation in range(12):
for override in [False,True]:
for particles in [False,True]:
for emit_random in [0,1]:yield row(animation=animation,override=override,has_particles=particles,emit_random=emit_random)
for seconds in floats:
for remaining in [-2147483648,-2147483647,-33,-32,-1,0,1,4,7,8,20,24,25,28,32,2147483647]:yield row(seconds=seconds,remaining=remaining,has_particles=False)
rng=random.Random(0x434f4e53554d45)
def random_config():return config(position=[rng.choice([-0.0,0.0,1.25,-15.5,29999999.5]) for _ in range(3)],pitch=rng.choice(floats),yaw=rng.choice(floats),eye=rng.choice(floats),count=rng.choice([-1,0,1,16,2147483647]),damage=rng.choice([None,-1,0,99,2147483647]),random=rng.randrange(2),level=rng.randrange(2),sound=rng.randrange(2),reseed=bool(rng.randrange(2)),seeds=[rng.randrange(-2**63,2**63) for _ in range(2)])
for _ in range(2400):yield row(initial=random_config(),after=random_config(),seeds=[rng.randrange(-2**63,2**63) for _ in range(2)],item=rng.randrange(3),xor=bool(rng.randrange(2)),particles=rng.choice([-1,0,1,3,5,16]),fail=rng.randrange(-1,35),mutate=rng.randrange(-1,35),animation=rng.randrange(12),seconds=rng.randrange(2**32),remaining=rng.randrange(-2**31,2**31),sound=rng.randrange(2),has_particles=bool(rng.randrange(2)),emit_random=rng.randrange(2),override=bool(rng.randrange(2)))
def main():
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--prepare',action='store_true');args=parser.parse_args()
build=lab.LOCAL/'rust-consumable-emission-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/VanillaConsumableEmissionOracle.java'
sources=[oracle,Path(__file__),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/consumable_emission_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','-cp',str(jars['server'])+os.pathsep+dependency_path,'-d',classes,oracle],'compile-java')
def java(side,mode,path):
run(['java','-Xmx512M','-cp',os.pathsep.join([str(classes),str(jars[side]),dependency_path]),'dev.horizonlab.parity.VanillaConsumableEmissionOracle',mode,path],side+'-'+mode)
fixtures=list(corpus());inputs=build/'inputs.txt';inputs.write_text(''.join(json.dumps(value,separators=(',',':'))+'\n' for value in fixtures),encoding='utf-8')
fixture_paths=[inputs];tested_fixtures=base.source_hashes(fixture_paths)
run(['cargo','build','--release','-p','horizon-world','--example','consumable_emission_fixture'],'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'/('consumable_emission_fixture.exe' if os.name=='nt' else 'consumable_emission_fixture')
run([binary,build],'rust');references=[]
for side,jar in jars.items():
java(side,side,build);reference=build/f'{side}.txt';matched=base.compare(reference,build/'rust.txt')
references.append({'side':side,'jar_sha256':lab.digest(jar,'sha256'),'output_sha256':lab.digest(reference,'sha256'),'matched_output_lines':matched})
print(f'{side}: {matched:,} consumable emission output lines match Rust',flush=True)
base.ensure_sources_unchanged(sources,tested_sources);base.ensure_sources_unchanged(fixture_paths,tested_fixtures)
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(),'fixtures':len(fixtures),'sources_sha256':tested_sources,'fixtures_sha256':tested_fixtures,'rust_output_sha256':lab.digest(build/'rust.txt','sha256'),'references':references,'scope':'Actual Consumable timing and emitParticlesAndSounds composed with inherited LivingEntity.spawnItemParticles, real templates and both native RNGs. Compares all animations, unconditional random draws, supplied versus direct random sources, sound overrides after particle callbacks, retained sound parameters, float timing edges and mutation/failure prefixes. Audio playback, rendering, consumable completion/effects and full gameplay remain pending.'}
lab.write_json(report_path,report);print(f'Scoped consumable emission parity passed: {report_path}')
if __name__=='__main__':main()