From 32c53db88aa53ca4035663398f32b96a181a9caa Mon Sep 17 00:00:00 2001 From: Ludovic Marie <136255825+ludovic111@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:01:50 +0200 Subject: [PATCH] Bind consumable effects to shared entity status storage --- .../scoped-parity.json | 298 +++++++++++++ .../examples/stored_consume_fixture.rs | 407 ++++++++++++++++++ crates/horizon-world/src/lib.rs | 1 + crates/horizon-world/src/stored_consume.rs | 83 ++++ docs/rust-parity.md | 18 + .../parity/VanillaStoredConsumeOracle.java | 57 +++ tools/test_stored_consume_parity.py | 87 ++++ 7 files changed, 951 insertions(+) create mode 100644 benchmarks/rust-stored-consume-2026-10-07/scoped-parity.json create mode 100644 crates/horizon-world/examples/stored_consume_fixture.rs create mode 100644 crates/horizon-world/src/stored_consume.rs create mode 100644 mods/horizon/src/test/java/dev/horizonlab/parity/VanillaStoredConsumeOracle.java create mode 100644 tools/test_stored_consume_parity.py diff --git a/benchmarks/rust-stored-consume-2026-10-07/scoped-parity.json b/benchmarks/rust-stored-consume-2026-10-07/scoped-parity.json new file mode 100644 index 0000000000..64e8337521 --- /dev/null +++ b/benchmarks/rust-stored-consume-2026-10-07/scoped-parity.json @@ -0,0 +1,298 @@ +{ + "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": 3292, + "sources_sha256": { + "mods/horizon/src/test/java/dev/horizonlab/parity/VanillaStoredConsumeOracle.java": "0dc54cee95ed6416c3539c6e046e5efcc74120cc9be1aec5b75605ee7870d5af", + "tools/test_stored_consume_parity.py": "e6c0511067dfae838322d519fdd5ec02537287de94440da980b399d72a0d6e4a", + "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/active_effects.rs": "1788f48f7f88463ced734af0bb02610cd7a991d5a6bdeaa2f9f198a4b7312c2a", + "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": "ba3575cb85866cb940f33a1ac86455d71cf0cc6395b848588a8ee1fe33f5c326", + "crates/horizon-world/src/consume_effect.rs": "03f2aac6879ce133347d42f6f6667d62c06d956ff9faac5dc14182a788b6a236", + "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": "37c921889c4f5d91f7c09ab2ee68d1a5e5a3c4fb1c49b4b189c987b4615fd2d6", + "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": "955502e696d7b0b33bae3ec485c8eb1453a8956116aad68226cf1fc67d60babc", + "crates/horizon-world/src/lib.rs": "cdb393bc95af39318aff30af90ab1aa82fbe989e25d194f79d6a1c2708d3c9de", + "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/status_effect.rs": "588bfcf4d93f50be86cb89545bd5c431edbc71771f704ff55cb52906b1964c37", + "crates/horizon-world/src/stored_consume.rs": "621a52c9fba447c393e561f881a16cf1f97d336e3a0ce493725ef51e2db12ffd", + "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/teleport_consume.rs": "d6a53bbb6ac4dbbd4c0591d0646017898a95beb8ef093dc5d81b9833963be6c3", + "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/stored_consume_fixture.rs": "65e97b29b504ba150314b1ebcdffe4855994e2060b365a75c03bbe60a0e146df" + }, + "fixtures_sha256": { + ".local/rust-stored-consume-parity/inputs.txt": "e137367d3154aafecfd48bf868dd3a84e4427d9196eab6efff14c458a715cdbe", + ".local/rust-stored-consume-parity/inputs-client.txt": "90a64e6b27537f79b5c7be26413fd72507b07cd83f908ddf7266e718ce1a0694", + ".local/rust-stored-consume-parity/inputs-server.txt": "90a64e6b27537f79b5c7be26413fd72507b07cd83f908ddf7266e718ce1a0694" + }, + "references": [ + { + "side": "client", + "jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d", + "output_sha256": "e91e3946682b3410a907226c14ca169ddef0668c8e993f4bfee65bc956d1fc01", + "rust_output_sha256": "e91e3946682b3410a907226c14ca169ddef0668c8e993f4bfee65bc956d1fc01", + "matched_output_lines": 1214124 + }, + { + "side": "server", + "jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6", + "output_sha256": "e91e3946682b3410a907226c14ca169ddef0668c8e993f4bfee65bc956d1fc01", + "rust_output_sha256": "e91e3946682b3410a907226c14ca169ddef0668c8e993f4bfee65bc956d1fc01", + "matched_output_lines": 1214124 + } + ], + "scope": "Concrete apply/remove/clear/sound consume effects composed with inherited LivingEntity effect storage, actual status instances and JDK maps. Both RNG kinds, probability edges, ordinary instance copies, hidden updates, duplicate effects, source identity, map iteration/copy order, callback mutation and failure prefixes are compared. Native holder hashes and identity hashes are captured per reference run. Eligibility, registered effect bodies, attribute/packet notifications and sound delivery remain controlled. Full consumable/player lifecycle and gameplay remain pending." +} diff --git a/crates/horizon-world/examples/stored_consume_fixture.rs b/crates/horizon-world/examples/stored_consume_fixture.rs new file mode 100644 index 0000000000..7fd9abc91e --- /dev/null +++ b/crates/horizon-world/examples/stored_consume_fixture.rs @@ -0,0 +1,407 @@ +use horizon_core::{ + position::BlockPos, + random::{LegacyRandom, RandomSource, XoroshiroRandom}, +}; +use horizon_world::{ + active_effects::{ActiveEffects, EffectHooks}, + consume_effect::ConsumeEffect, + java_hash_map::JavaKey, + status_effect::{BlendState, Details, EffectInstance}, + stored_consume::{ConsumeHooks, StoredConsume}, +}; +use serde_json::Value; +use std::{ + cmp::Ordering, + fs, + io::{self, Write}, + path::Path, +}; +#[derive(Clone)] +struct Key { + id: usize, + hash: i32, + identity: i32, +} +impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} +impl Eq for Key {} +impl JavaKey for Key { + fn java_hash(&self) -> i32 { + self.hash + } + fn tie_break(&self, other: &Self) -> Ordering { + if self.identity <= other.identity { + Ordering::Less + } else { + Ordering::Greater + } + } +} +type Effect = EffectInstance; +type Storage = ActiveEffects; +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: f32) -> u32 { + if v.is_nan() { 0x7fc00000 } else { v.to_bits() } +} +fn details(v: &Value) -> Details { + Details { + duration: n(v, "duration"), + amplifier: n(v, "amplifier"), + ambient: v["ambient"].as_bool().unwrap(), + visible: v["visible"].as_bool().unwrap(), + show_icon: v["icon"].as_bool().unwrap(), + } +} +struct World<'a, R> { + random: [R; 2], + f: Value, + out: &'a mut dyn Write, + keys: Vec, + pool: Vec, + ids: Vec, + calls: i32, + changed: bool, +} +impl World<'_, R> { + fn config(&self) -> &Value { + &self.f[if self.changed { "after" } else { "initial" }] + } + fn id(&mut self, e: &Effect) -> usize { + if let Some(i) = self.ids.iter().position(|v| v.same_reference(e)) { + i + } else { + self.ids.push(e.clone()); + self.ids.len() - 1 + } + } + fn reset(&mut self) { + let c = self.config().clone(); + if c["reseed"].as_bool().unwrap() { + for i in 0..2 { + self.random[i].set_seed(c["seeds"][i].as_i64().unwrap()); + } + } + for (i, v) in c["states"].as_array().unwrap().iter().enumerate() { + let mut e = self.pool[i].write(); + e.details = details(v); + e.blend = BlendState { + factor: fl(v, "factor"), + previous: fl(v, "previous"), + }; + } + } + fn all(&mut self, s: &Storage) { + writeln!(self.out, "state {} {}", self.calls, self.changed).unwrap(); + let mut roots = Vec::new(); + let entries = s + .entries + .entries() + .map(|(k, e)| { + let id = self.id(e); + roots.push(e.clone()); + format!("{}={id}", k.id) + }) + .collect::>() + .join(","); + writeln!(self.out, "map {entries}").unwrap(); + for (i, e) in self.pool.clone().iter().enumerate() { + let id = self.id(e); + writeln!(self.out, "pool {i} {id}").unwrap(); + roots.push(e.clone()); + } + let mut seen = Vec::new(); + let mut i = 0; + while i < roots.len() { + let e = roots[i].clone(); + i += 1; + if seen.iter().any(|v: &Effect| v.same_reference(&e)) { + continue; + } + seen.push(e.clone()); + let id = self.id(&e); + let d = e.read(); + let h = d + .hidden + .as_ref() + .map(|h| { + roots.push(h.clone()); + self.id(h).to_string() + }) + .unwrap_or("none".into()); + writeln!( + self.out, + "node {id} {} {} {} {} {} {} {h} {} {}", + d.effect.id, + d.details.duration, + d.details.amplifier, + d.details.ambient, + d.details.visible, + d.details.show_icon, + bits(d.blend.factor), + bits(d.blend.previous) + ) + .unwrap(); + } + } + fn raw(&mut self, s: &mut Storage, op: &Value) { + let a = n(op, "a") as usize; + let b = n(op, "b") as usize; + match op["op"].as_str().unwrap() { + "put" => { + s.entries.put(self.keys[a].clone(), self.pool[b].clone()); + } + "drop" => { + s.entries.remove(&self.keys[a]); + } + "clear" => s.entries.clear(), + "copy" => self.pool[b] = self.pool[a].copy_instance(), + "alias" => self.pool[b] = self.pool[a].clone(), + _ => panic!("raw op"), + } + } + fn event(&mut self, s: &mut Storage, name: String) -> Result<(), &'static str> { + writeln!(self.out, "event {name}").unwrap(); + let i = self.calls; + self.calls += 1; + self.all(s); + if i == n(&self.f, "fail") { + return Err("callback"); + } + if i == n(&self.f, "mutate") { + self.changed = true; + self.reset(); + for op in self.f["mutations"].as_array().unwrap().clone() { + self.raw(s, &op); + } + } + Ok(()) + } +} +impl EffectHooks for World<'_, R> { + type Error = &'static str; + type Source = i32; + type Level = i32; + fn can_be_affected(&mut self, s: &mut Storage, e: Effect) -> Result { + let i = self.id(&e); + self.event(s, format!("eligible {i}"))?; + Ok(self.config()["allow"].as_bool().unwrap()) + } + fn on_added( + &mut self, + s: &mut Storage, + e: Effect, + source: Option, + ) -> Result<(), Self::Error> { + let i = self.id(&e); + self.event(s, format!("added {i} {}", source.unwrap_or(-1))) + } + fn on_updated( + &mut self, + s: &mut Storage, + e: Effect, + reapply: bool, + source: Option, + ) -> Result<(), Self::Error> { + let i = self.id(&e); + self.event(s, format!("updated {i} {reapply} {}", source.unwrap_or(-1))) + } + fn effect_added(&mut self, s: &mut Storage, e: Key, amp: i32) -> Result<(), Self::Error> { + self.event(s, format!("effect-added {} {amp}", e.id)) + } + fn effect_started(&mut self, s: &mut Storage, e: Key, amp: i32) -> Result<(), Self::Error> { + self.event(s, format!("effect-started {} {amp}", e.id)) + } + fn level(&mut self, s: &mut Storage) -> Result { + self.event(s, "level".into())?; + Ok(n(self.config(), "level")) + } + fn is_client(&mut self, s: &mut Storage, l: i32) -> Result { + self.event(s, format!("client {l}"))?; + Ok(self.config()["client"].as_bool().unwrap()) + } + fn on_removed(&mut self, s: &mut Storage, es: Vec) -> Result<(), Self::Error> { + let ids = es + .iter() + .map(|e| self.id(e).to_string()) + .collect::>() + .join(","); + self.event(s, format!("removed {ids}")) + } + fn get_effect(&mut self, s: &mut Storage, key: Key) -> Result, Self::Error> { + self.event(s, format!("get {}", key.id))?; + Ok(s.get(&key)) + } + fn actor_removed(&self) -> bool { + self.config()["removed"].as_bool().unwrap() + } +} +impl ConsumeHooks> for World<'_, R> { + type Random = usize; + type Rng = R; + type SoundCategory = i32; + fn get_random(&mut self, s: &mut Storage) -> Result { + self.event(s, "random".into())?; + Ok(n(self.config(), "random") as usize) + } + fn random_source(&mut self, r: usize) -> &mut R { + &mut self.random[r] + } + fn effect_set(&self, set: &Vec) -> Vec { + set.clone() + } + fn block_position(&mut self, s: &mut Storage) -> Result { + self.event(s, "position".into())?; + let p = &self.config()["position"]; + Ok(BlockPos::new( + p[0].as_i64().unwrap() as i32, + p[1].as_i64().unwrap() as i32, + p[2].as_i64().unwrap() as i32, + )) + } + fn sound_category(&mut self, s: &mut Storage) -> Result { + self.event(s, "category".into())?; + Ok(n(self.config(), "category")) + } + fn play_sound( + &mut self, + storage: &mut Storage, + level: i32, + p: BlockPos, + sound: u8, + category: i32, + ) -> Result<(), Self::Error> { + self.event( + storage, + format!("sound {level} {} {} {} {sound} {category}", p.x, p.y, p.z), + ) + } +} +fn run(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 keys = f["hashes"] + .as_array() + .unwrap() + .iter() + .enumerate() + .map(|(id, v)| Key { + id, + hash: v.as_i64().unwrap() as i32, + identity: f["identities"][id].as_i64().unwrap() as i32, + }) + .collect::>(); + let mut pool: Vec = Vec::new(); + for v in f["initial"]["states"].as_array().unwrap() { + let h = n(v, "hidden"); + pool.push(Effect::new( + keys[n(v, "key") as usize].clone(), + details(v), + (h >= 0).then(|| pool[h as usize].clone()), + )); + } + let mut w = World { + random, + f, + out, + keys, + pool, + ids: Vec::new(), + calls: 0, + changed: false, + }; + let mut s = Storage::default(); + w.reset(); + for op in w.f["setup"].as_array().unwrap().clone() { + w.raw(&mut s, &op); + } + w.all(&s); + for op in w.f["ops"].as_array().unwrap().clone() { + let a = n(&op, "a") as usize; + let b = n(&op, "b") as usize; + let source = n(&op, "source"); + let source = (source >= 0).then_some(source); + let result: Result = match op["op"].as_str().unwrap() { + "apply-status" | "remove-status" | "clear-status" | "sound" => { + let list = op["list"].as_array().unwrap(); + let effect: ConsumeEffect = match op["op"].as_str().unwrap() { + "apply-status" => ConsumeEffect::ApplyStatus { + effects: list + .iter() + .map(|i| w.pool[i.as_u64().unwrap() as usize].clone()) + .collect(), + probability: fl(&op, "probability"), + }, + "remove-status" => ConsumeEffect::RemoveStatus( + list.iter() + .map(|i| w.keys[i.as_u64().unwrap() as usize].clone()) + .collect(), + ), + "clear-status" => ConsumeEffect::ClearStatus, + _ => ConsumeEffect::PlaySound(n(&op, "sound") as u8), + }; + effect + .apply( + &mut StoredConsume { + effects: &mut s, + world: &mut w, + }, + n(&op, "level"), + ) + .map(|v| v.to_string()) + } + "add" => s + .add(w.pool[a].clone(), source, &mut w) + .map(|v| v.to_string()), + "force" => s + .force_add(w.pool[a].clone(), source, &mut w) + .map(|_| "ok".into()), + "remove" => s.remove(&w.keys[a].clone(), &mut w).map(|v| v.to_string()), + "remove-raw" => Ok(s + .remove_no_update(&w.keys[a]) + .map(|e| w.id(&e).to_string()) + .unwrap_or("none".into())), + "all" => s.remove_all(&mut w).map(|v| v.to_string()), + "has" => Ok(s.has(&w.keys[a]).to_string()), + "get" => { + let key = w.keys[a].clone(); + EffectHooks::get_effect(&mut w, &mut s, key) + .map(|e| e.map(|e| w.id(&e).to_string()).unwrap_or("none".into())) + } + "blend" => s + .blend_factor(w.keys[a].clone(), fl(&op, "partial"), &mut w) + .map(|v| bits(v).to_string()), + "put" | "drop" | "clear" | "copy" | "alias" => { + w.raw(&mut s, &op); + Ok("ok".into()) + } + _ => panic!("operation {b}"), + }; + writeln!(w.out, "result {}", result.unwrap_or_else(str::to_string)).unwrap(); + w.all(&s); + } + 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> { + let arg = std::env::args().nth(1).unwrap(); + let side = std::env::args().nth(2).unwrap(); + let dir = Path::new(&arg); + let mut out = io::BufWriter::new(fs::File::create(dir.join(format!("rust-{side}.txt")))?); + for line in fs::read_to_string(dir.join(format!("inputs-{side}.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(()) +} diff --git a/crates/horizon-world/src/lib.rs b/crates/horizon-world/src/lib.rs index 48a519bd0a..317a05402e 100644 --- a/crates/horizon-world/src/lib.rs +++ b/crates/horizon-world/src/lib.rs @@ -2,6 +2,7 @@ #![forbid(unsafe_code)] pub mod active_effects; +pub mod stored_consume; pub mod attribute_health; pub mod attributes; pub mod block_collision; diff --git a/crates/horizon-world/src/stored_consume.rs b/crates/horizon-world/src/stored_consume.rs new file mode 100644 index 0000000000..19d168a61e --- /dev/null +++ b/crates/horizon-world/src/stored_consume.rs @@ -0,0 +1,83 @@ +//! Consume effects bound to an entity's shared active-effect storage. +use crate::{ + active_effects::{ActiveEffects, EffectHooks}, + consume_effect::EffectWorld, + java_hash_map::JavaKey, + status_effect::EffectInstance, +}; +use horizon_core::{position::BlockPos, random::RandomSource}; + +/// Actor and level services used alongside concrete status storage. Callbacks +/// receive the live storage so reentrant mutations remain visible to the next +/// operation. Random handles identify streams independently of actor changes. +pub trait ConsumeHooks: EffectHooks { + type Random: Copy; + type Rng: RandomSource; + type SoundCategory; + fn get_random(&mut self, storage: &mut ActiveEffects) -> Result; + fn random_source(&mut self, random: Self::Random) -> &mut Self::Rng; + fn effect_set(&self, set: &Set) -> Vec; + fn block_position(&mut self, storage: &mut ActiveEffects) -> Result; + fn sound_category( + &mut self, + storage: &mut ActiveEffects, + ) -> Result; + /// The native call excludes no entity and uses unit volume and pitch. + fn play_sound( + &mut self, + storage: &mut ActiveEffects, + level: Self::Level, + position: BlockPos, + sound: S, + category: Self::SoundCategory, + ) -> Result<(), Self::Error>; +} + +pub struct StoredConsume<'a, H, W> { + pub effects: &'a mut ActiveEffects, + pub world: &'a mut W, +} +impl> EffectWorld + for StoredConsume<'_, H, W> +{ + type Error = W::Error; + type Level = W::Level; + type Random = W::Random; + type Source = W::Rng; + type SoundCategory = W::SoundCategory; + + fn get_random(&mut self) -> Result { + self.world.get_random(self.effects) + } + fn random_source(&mut self, random: Self::Random) -> &mut Self::Source { + self.world.random_source(random) + } + fn add_effect(&mut self, effect: EffectInstance) -> Result { + self.effects.add(effect, None, self.world) + } + fn effect_set(&self, set: &Set) -> Vec { + self.world.effect_set(set) + } + fn remove_effect(&mut self, effect: H) -> Result { + self.effects.remove(&effect, self.world) + } + fn remove_all_effects(&mut self) -> Result { + self.effects.remove_all(self.world) + } + fn block_position(&mut self) -> Result { + self.world.block_position(self.effects) + } + fn sound_category(&mut self) -> Result { + self.world.sound_category(self.effects) + } + fn play_sound( + &mut self, + level: Self::Level, + position: BlockPos, + sound: S, + category: Self::SoundCategory, + ) -> Result<(), Self::Error> { + self.world + .play_sound(self.effects, level, position, sound, category) + } +} diff --git a/docs/rust-parity.md b/docs/rust-parity.md index 3f4b6eba36..18b511f127 100644 --- a/docs/rust-parity.md +++ b/docs/rust-parity.md @@ -996,3 +996,21 @@ World-library and fixture Clippy checks pass. Eligibility policy, attribute and packet notifications, and registered effect behavior remain controlled in these checks. Consumable integration, the entity effect tick loop and complete gameplay remain pending; this is scoped evidence, not full-game parity. + +## Consumable effects bound to entity storage + +`StoredConsume` now binds concrete apply/remove/clear consume effects to the +entity's shared active-effect map. Consumable additions use a null source and +ordinary status-instance copies, then execute the entity's eligibility, update +and notification sequence. Sound and RNG services receive access to live storage +so callback mutations affect subsequent operations. + +The [combined comparison](../benchmarks/rust-stored-consume-2026-10-07/scoped-parity.json) +matches **1,214,124 lines per reference JAR**, executing actual native consume +effects and inherited entity storage together. It covers both random generators, +probability boundaries, duplicate inputs, hidden chains, colliding holder keys, +map-copy removal order, callback mutations and failure prefixes. World-library +and fixture Clippy checks pass. Eligibility policy, registered effect behavior, +attribute/packet notifications and sound delivery remain controlled. Named +holder-set binding, the full player consumption lifecycle and complete gameplay +remain pending. diff --git a/mods/horizon/src/test/java/dev/horizonlab/parity/VanillaStoredConsumeOracle.java b/mods/horizon/src/test/java/dev/horizonlab/parity/VanillaStoredConsumeOracle.java new file mode 100644 index 0000000000..1bbf8f133f --- /dev/null +++ b/mods/horizon/src/test/java/dev/horizonlab/parity/VanillaStoredConsumeOracle.java @@ -0,0 +1,57 @@ +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.core.Holder;import net.minecraft.world.effect.*;import net.minecraft.world.entity.*;import net.minecraft.world.level.Level;import net.minecraft.server.level.ServerLevel; +import net.minecraft.core.BlockPos;import net.minecraft.core.HolderSet;import net.minecraft.world.item.ItemStack;import net.minecraft.world.item.consume_effects.*;import net.minecraft.sounds.*;import net.minecraft.util.RandomSource;import net.minecraft.world.level.levelgen.XoroshiroRandomSource; +/** Actual consume effects composed with inherited entity storage and native instances. */ +public final class VanillaStoredConsumeOracle { + static Object unsafe;static Method allocate;static Field duration,amplifier,ambient,visible,icon,hidden,blend,factor,previous,removed,activeEffects;static Data active; + static Field field(Class c,String n)throws Exception{var f=c.getDeclaredField(n);f.setAccessible(true);return f;} + static T make(Class 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 long bits(float v){return Integer.toUnsignedLong(Float.floatToIntBits(v));} + static final class Stop extends RuntimeException{} + static final class Data {JsonObject f;PrintWriter out;RandomSource[] random;List> keys=new ArrayList<>();MobEffectInstance[] pool;IdentityHashMap ids=new IdentityHashMap<>();Actor actor;Entity other;EffectLevel[] levels;int calls;boolean changed; + JsonObject config(){return f.getAsJsonObject(changed?"after":"initial");} + Map,MobEffectInstance> map(){return actor.getActiveEffectsMap();} + int id(MobEffectInstance e){return ids.computeIfAbsent(e,k->ids.size());} + int key(Holder h){return keys.indexOf(h);} + int source(Entity e){if(e==null)return -1;if(e==actor)return 0;if(e==other)return 1;throw new AssertionError();} + void reset(){try{var c=config();if(c.get("reseed").getAsBoolean())for(int i=0;i<2;i++)random[i].setSeed(c.getAsJsonArray("seeds").get(i).getAsLong());removed.set(actor,c.get("removed").getAsBoolean()?Entity.RemovalReason.DISCARDED:null);for(int i=0;i();var entries=new StringJoiner(",");for(var e:map().entrySet()){entries.add(key(e.getKey())+"="+id(e.getValue()));roots.add(e.getValue());}out.println("map "+entries);for(int i=0;i();for(int i=0;imap().put(keys.get(a),pool[b]);case "drop"->map().remove(keys.get(a));case "clear"->map().clear();case "copy"->pool[b]=new MobEffectInstance(pool[a]);case "alias"->pool[b]=pool[a];default->throw new AssertionError();}} + void event(String name){out.println("event "+name);int i=calls++;all();if(i==n(f,"fail"))throw new Stop();if(i==n(f,"mutate")){changed=true;reset();for(var op:f.getAsJsonArray("mutations"))raw(op.getAsJsonObject());}} + } + static final class Effect extends MobEffect {int id,hash;Effect(int id,int hash){super(MobEffectCategory.BENEFICIAL,0);this.id=id;this.hash=hash;}@Override public int hashCode(){return hash;} + @Override public void onEffectAdded(LivingEntity actor,int amp){if(actor!=active.actor)throw new AssertionError();active.event("effect-added "+id+" "+amp);} + @Override public void onEffectStarted(LivingEntity actor,int amp){if(actor!=active.actor)throw new AssertionError();active.event("effect-started "+id+" "+amp);} + } + static final class Actor extends net.minecraft.world.entity.decoration.ArmorStand {Data d;Actor(){super(null,null);} + @Override public RandomSource getRandom(){d.event("random");return d.random[n(d.config(),"random")];} + @Override public BlockPos blockPosition(){d.event("position");var p=d.config().getAsJsonArray("position");return new BlockPos(p.get(0).getAsInt(),p.get(1).getAsInt(),p.get(2).getAsInt());} + @Override public SoundSource getSoundSource(){d.event("category");return SoundSource.values()[n(d.config(),"category")];} + @Override public boolean canBeAffected(MobEffectInstance e){d.event("eligible "+d.id(e));return d.config().get("allow").getAsBoolean();} + @Override protected void onEffectAdded(MobEffectInstance e,Entity source){d.event("added "+d.id(e)+" "+d.source(source));} + @Override protected void onEffectUpdated(MobEffectInstance e,boolean reapply,Entity source){d.event("updated "+d.id(e)+" "+reapply+" "+d.source(source));} + @Override protected void onEffectsRemoved(Collection values){var ids=new StringJoiner(",");for(var e:values)ids.add(Integer.toString(d.id(e)));d.event("removed "+ids);} + @Override public Level level(){d.event("level");return d.levels[n(d.config(),"level")];} + @Override public MobEffectInstance getEffect(Holder e){d.event("get "+d.key(e));return super.getEffect(e);} + } + static final class EffectLevel extends ServerLevel{Data d;int id;EffectLevel(){super(null,null,null,null,null,null,false,0L,List.of(),false);} + @Override public void playSound(Entity excluded,BlockPos p,SoundEvent sound,SoundSource category,float volume,float pitch){if(excluded!=null||volume!=1||pitch!=1)throw new AssertionError();int s=sound==SoundEvents.GENERIC_EAT.value()?0:sound==SoundEvents.PLAYER_BURP?1:-1;if(s<0)throw new AssertionError();d.event("sound "+id+" "+p.getX()+" "+p.getY()+" "+p.getZ()+" "+s+" "+category.ordinal());} + @Override public boolean isClientSide(){d.event("client "+id);return d.config().get("client").getAsBoolean();}} + public static void main(String[] args)throws Exception{net.minecraft.SharedConstants.tryDetectVersion();net.minecraft.server.Bootstrap.bootStrap();var u=Class.forName("sun.misc.Unsafe");unsafe=field(u,"theUnsafe").get(null);allocate=u.getMethod("allocateInstance",Class.class);activeEffects=field(LivingEntity.class,"activeEffects");removed=field(Entity.class,"removalReason");duration=field(MobEffectInstance.class,"duration");amplifier=field(MobEffectInstance.class,"amplifier");ambient=field(MobEffectInstance.class,"ambient");visible=field(MobEffectInstance.class,"visible");icon=field(MobEffectInstance.class,"showIcon");hidden=field(MobEffectInstance.class,"hiddenEffect");blend=field(MobEffectInstance.class,"blendState");factor=field(blend.getType(),"factor");previous=field(blend.getType(),"factorPreviousFrame");Path dir=Path.of(args[1]); + try(var out=new PrintWriter(Files.newBufferedWriter(dir.resolve(args[0]+".txt")));var enriched=new PrintWriter(Files.newBufferedWriter(dir.resolve("inputs-"+args[0]+".txt")));var lines=Files.lines(dir.resolve("inputs.txt"))){for(var line:(Iterable)lines::iterator){var d=new Data();active=d;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);}var hashes=new JsonArray();var identities=new JsonArray();for(var hash:d.f.getAsJsonArray("hashes")){var h=Holder.direct(new Effect(d.keys.size(),hash.getAsInt()));d.keys.add(h);hashes.add(h.hashCode());identities.add(System.identityHashCode(h));}d.f.add("hashes",hashes);d.f.add("identities",identities);enriched.println(d.f);d.actor=make(Actor.class);d.actor.d=d;d.other=make(net.minecraft.world.entity.decoration.ArmorStand.class);activeEffects.set(d.actor,new HashMap,MobEffectInstance>());d.levels=new EffectLevel[2];for(int i=0;i<2;i++){d.levels[i]=make(EffectLevel.class);d.levels[i].d=d;d.levels[i].id=i;}var states=d.config().getAsJsonArray("states");d.pool=new MobEffectInstance[states.size()];for(int i=0;i{var list=op.getAsJsonArray("list");ConsumeEffect effect;switch(op.get("op").getAsString()){ + case "apply-status"->{var selected=new ArrayList();for(var v:list)selected.add(d.pool[v.getAsInt()]);effect=new ApplyStatusEffectsConsumeEffect(selected,fl(op,"probability"));} + case "remove-status"->{var selected=new ArrayList>();for(var v:list)selected.add(d.keys.get(v.getAsInt()));effect=new RemoveStatusEffectsConsumeEffect(HolderSet.direct(selected));} + case "clear-status"->effect=ClearAllStatusEffectsConsumeEffect.INSTANCE; + default->effect=new PlaySoundConsumeEffect(Holder.direct(n(op,"sound")==0?SoundEvents.GENERIC_EAT.value():SoundEvents.PLAYER_BURP)); + }result=Boolean.toString(effect.apply(d.levels[n(op,"level")],ItemStack.EMPTY,d.actor));} + case "add"->result=Boolean.toString(d.actor.addEffect(d.pool[a],src));case "force"->d.actor.forceAddEffect(d.pool[a],src);case "remove"->result=Boolean.toString(d.actor.removeEffect(d.keys.get(a)));case "remove-raw"->{var e=d.actor.removeEffectNoUpdate(d.keys.get(a));result=e==null?"none":Integer.toString(d.id(e));}case "all"->result=Boolean.toString(d.actor.removeAllEffects());case "has"->result=Boolean.toString(d.actor.hasEffect(d.keys.get(a)));case "get"->{var e=d.actor.getEffect(d.keys.get(a));result=e==null?"none":Integer.toString(d.id(e));}case "blend"->result=Long.toString(bits(d.actor.getEffectBlendFactor(d.keys.get(a),fl(op,"partial"))));case "put","drop","clear","copy","alias"->d.raw(op);default->throw new AssertionError(); + }}catch(Stop e){result="callback";}out.println("result "+result);d.all();} + for(int i=0;i<2;i++)out.println("random "+i+" "+d.random[i].nextLong()+" "+d.random[i].nextLong()); + }} + } +} diff --git a/tools/test_stored_consume_parity.py b/tools/test_stored_consume_parity.py new file mode 100644 index 0000000000..c597ab3af0 --- /dev/null +++ b/tools/test_stored_consume_parity.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Compare concrete consume effects composed with inherited entity status storage.""" +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(): + rng=random.Random(0x53544f524544) + durations=[-2147483648,-2,-1,0,1,20,200,2147483647] + amps=[-1,0,1,2,255,256,2147483647] + floats=[0,0x80000000,0x3f000000,0x3f800000,0xbf800000,0x7f800000,0xff800000,0x7fc00000] + def state(**kw): + v=dict(key=0,duration=20,amplifier=1,ambient=False,visible=True,icon=True,hidden=-1,factor=0x3f000000,previous=0);v.update(kw);return v + def config(**kw): + v=dict(states=[state(),state(duration=200,amplifier=0),state(key=1,hidden=0)],allow=True,level=0,client=False,removed=False,random=0,reseed=False,seeds=[9,-13],position=[1,2,3],category=0);v.update(kw);return v + def op(name,a=0,b=0,**kw): + v=dict(op=name,a=a,b=b,source=-1,partial=0x3f000000,list=[0,1,2,0],probability=0x3f800000,sound=0,level=0);v.update(kw);return v + def row(**kw): + v=dict(xor=False,seeds=[1,-3],hashes=[0,1,2],initial=config(),after=config(states=[state(duration=1,amplifier=9),state(duration=-1,amplifier=2),state(duration=0,amplifier=0)],allow=True,level=1,client=True,removed=True,random=1,reseed=True,position=[-4,5,-6],category=4),setup=[],ops=[op('apply-status'),op('remove-status'),op('clear-status')],fail=-1,mutate=-1,mutations=[op('put',0,2)]);v.update(kw);return v + for xor in [False,True]: + for name in ['apply-status','remove-status','clear-status','sound']: + for setup in [[],[op('put',0,0)],[op('put',0,1),op('put',1,2)]]: + for fail in range(-1,9): + for mutate in range(-1,9): + yield row(xor=xor,setup=setup,ops=[op(name),op('get'),op('has')],fail=fail,mutate=mutate) + for probability in floats+[0x3eaaaaab,0x3f7fffff,1,0xffffffff]: + for ids in [[],[0],[0,0],[0,1,2],[2,0,1,2,0]]: + for allow in [False,True]: + yield row(xor=xor,initial=config(allow=allow),ops=[op('apply-status',list=ids,probability=probability),op('apply-status',list=ids,probability=probability),op('remove-status',list=list(reversed(ids))),op('clear-status')]) + for count in [1,2,3,7,8,9,11,13,24,25,48,49,64]: + for collision in [False,True]: + states=[state(key=i,duration=200,amplifier=i%3) for i in range(count)] + yield row(xor=xor,hashes=[0 if collision else i*31 for i in range(count)],initial=config(states=states),after=config(states=states),setup=[],ops=[op('apply-status',list=list(reversed(range(count)))),op('remove-status',list=list(range(0,count,3))),op('clear-status')],mutations=[]) + for _ in range(600): + count=rng.choice([3,5,8,16]);hashes=[rng.choice([0,0,64,i*31]) for i in range(count)] + def states():return [state(key=rng.randrange(count),duration=rng.choice(durations),amplifier=rng.choice(amps),ambient=bool(rng.randrange(2)),visible=bool(rng.randrange(2)),icon=bool(rng.randrange(2)),hidden=rng.randrange(-1,i),factor=rng.choice(floats),previous=rng.choice(floats)) for i in range(count)] + def cfg():return config(states=states(),allow=bool(rng.randrange(2)),level=rng.randrange(2),client=bool(rng.randrange(2)),removed=bool(rng.randrange(2)),random=rng.randrange(2),reseed=bool(rng.randrange(2)),seeds=[rng.randrange(-2**63,2**63) for _ in range(2)],position=[rng.randrange(-2**31,2**31) for _ in range(3)],category=rng.randrange(8)) + yield row(xor=bool(rng.randrange(2)),seeds=[rng.randrange(-2**63,2**63) for _ in range(2)],hashes=hashes,initial=cfg(),after=cfg(),setup=[op('put',rng.randrange(count),rng.randrange(count)) for _ in range(count)],ops=[op(rng.choice(['apply-status','apply-status','remove-status','clear-status','sound','add','force','remove','remove-raw','all','has','get','blend','put','drop','copy','alias']),rng.randrange(count),rng.randrange(count),source=rng.randrange(-1,2),partial=rng.choice(floats),list=[rng.randrange(count) for _ in range(rng.randrange(7))],probability=rng.choice(floats),level=rng.randrange(2),sound=rng.randrange(2)) for _ in range(20)],fail=rng.randrange(-1,30),mutate=rng.randrange(-1,30),mutations=[op(rng.choice(['put','drop','clear','copy','alias']),rng.randrange(count),rng.randrange(count)) for _ in range(3)]) + + +def main(): + parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--prepare',action='store_true');args=parser.parse_args() + build=lab.LOCAL/'rust-stored-consume-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/VanillaStoredConsumeOracle.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/stored_consume_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.VanillaStoredConsumeOracle',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','stored_consume_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'/('stored_consume_fixture.exe' if os.name=='nt' else 'stored_consume_fixture') + references=[] + for side,jar in jars.items(): + java(side,side,build) + enriched=build/f'inputs-{side}.txt';enriched_hash=base.source_hashes([enriched]) + run([binary,build,side],'rust-'+side) + reference=build/f'{side}.txt';rust_output=build/f'rust-{side}.txt';matched=base.compare(reference,rust_output) + base.ensure_sources_unchanged([enriched],enriched_hash) + tested_fixtures.update(enriched_hash);fixture_paths.append(enriched) + references.append({'side':side,'jar_sha256':lab.digest(jar,'sha256'),'output_sha256':lab.digest(reference,'sha256'),'rust_output_sha256':lab.digest(rust_output,'sha256'),'matched_output_lines':matched}) + print(f'{side}: {matched:,} stored consume 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,'references':references,'scope':'Concrete apply/remove/clear/sound consume effects composed with inherited LivingEntity effect storage, actual status instances and JDK maps. Both RNG kinds, probability edges, ordinary instance copies, hidden updates, duplicate effects, source identity, map iteration/copy order, callback mutation and failure prefixes are compared. Native holder hashes and identity hashes are captured per reference run. Eligibility, registered effect bodies, attribute/packet notifications and sound delivery remain controlled. Full consumable/player lifecycle and gameplay remain pending.'} + lab.write_json(report_path,report);print(f'Scoped stored consume parity passed: {report_path}') + + +if __name__=='__main__':main()