From a772a69498675f22745bed9f328d54c7ade16207 Mon Sep 17 00:00:00 2001 From: Ludovic Marie <136255825+ludovic111@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:31:39 +0200 Subject: [PATCH] Match effect eligibility and attribute/passenger notification ordering --- .../scoped-parity.json | 301 +++++++++++ .../examples/effect_notifications_fixture.rs | 506 ++++++++++++++++++ crates/horizon-world/src/active_effects.rs | 3 + .../horizon-world/src/effect_notifications.rs | 246 +++++++++ crates/horizon-world/src/lib.rs | 1 + docs/rust-parity.md | 19 + .../VanillaEffectNotificationsOracle.java | 55 ++ tools/test_effect_notifications_parity.py | 97 ++++ 8 files changed, 1228 insertions(+) create mode 100644 benchmarks/rust-effect-notifications-2026-10-07/scoped-parity.json create mode 100644 crates/horizon-world/examples/effect_notifications_fixture.rs create mode 100644 crates/horizon-world/src/effect_notifications.rs create mode 100644 mods/horizon/src/test/java/dev/horizonlab/parity/VanillaEffectNotificationsOracle.java create mode 100644 tools/test_effect_notifications_parity.py diff --git a/benchmarks/rust-effect-notifications-2026-10-07/scoped-parity.json b/benchmarks/rust-effect-notifications-2026-10-07/scoped-parity.json new file mode 100644 index 0000000000..12c3a466f3 --- /dev/null +++ b/benchmarks/rust-effect-notifications-2026-10-07/scoped-parity.json @@ -0,0 +1,301 @@ +{ + "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": 4476, + "sources_sha256": { + "mods/horizon/src/test/java/dev/horizonlab/parity/VanillaEffectNotificationsOracle.java": "44e9c1e4250a49a7b48c836caea5f9d3b863edf3238420272ee0d8671e1e1673", + "tools/test_effect_notifications_parity.py": "ceea3baa62e5e4a5a73e932cb33d9062467d8ddd57558c989a93aa283530bb58", + "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_effect_tick.rs": "88f19cfc3532c47a58c646bb3e9d99ff239a900752302b6a444dda3013e95cbf", + "crates/horizon-world/src/active_effects.rs": "daaf352a69511a7d69fb329997f783b86585e486e0379c84ab0a55641771646c", + "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/effect_notifications.rs": "ea66bfa010d6d44aee97b9672902f41cd99d0d80239f41c6d4eae5c9a9472d3d", + "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": "f7a851566dbabb96c138c3a364617f3ba84070908772fa44970d8079c2a4e47a", + "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/object_open_set.rs": "d3e0373bf1b652dd054345d5a8e25ec828e06dd7cad65aada35163c95df4e637", + "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/effect_notifications_fixture.rs": "7fe3750a96c45e6023d931d5615fff35f011b6eb5eae0a0075d58393a48f449e" + }, + "fixtures_sha256": { + ".local/rust-effect-notifications-parity/inputs.txt": "e7e3cbd8c5d6adafd87afa28919060f429bdf9ef7bd818a0e1dc7ddeb1c4ee4d", + ".local/rust-effect-notifications-parity/inputs-client.txt": "b655c7578f2f1acc55188de35f00f997d44437d7fd309dd692c7d8a68d03f177", + ".local/rust-effect-notifications-parity/inputs-server.txt": "b655c7578f2f1acc55188de35f00f997d44437d7fd309dd692c7d8a68d03f177" + }, + "references": [ + { + "side": "client", + "jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d", + "output_sha256": "399be32217b9df00f188f9692bc9b5b467e225eefce857a88554cd1b0fe1a9ed", + "rust_output_sha256": "399be32217b9df00f188f9692bc9b5b467e225eefce857a88554cd1b0fe1a9ed", + "matched_output_lines": 1667616 + }, + { + "side": "server", + "jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6", + "output_sha256": "399be32217b9df00f188f9692bc9b5b467e225eefce857a88554cd1b0fe1a9ed", + "rust_output_sha256": "399be32217b9df00f188f9692bc9b5b467e225eefce857a88554cd1b0fe1a9ed", + "matched_output_lines": 1667616 + } + ], + "scope": "Inherited LivingEntity eligibility and add/update/remove notification bodies, private refreshDirtyAttributes and sendEffectToPassengers, composed with native active-effect storage. Actual effect instances, fastutil pending sets, immutable passenger lists and update/remove packet constructors are used. Covers eligibility tag precedence, dirty writes, captured maps/sets/connections, callback mutation, pending iteration/clear prefixes, packet fields and failures. Registered modifier bodies, onAttributeUpdated consequences and packet transport remain controlled; full entity/player lifecycle and gameplay remain pending." +} diff --git a/crates/horizon-world/examples/effect_notifications_fixture.rs b/crates/horizon-world/examples/effect_notifications_fixture.rs new file mode 100644 index 0000000000..248c77a972 --- /dev/null +++ b/crates/horizon-world/examples/effect_notifications_fixture.rs @@ -0,0 +1,506 @@ +use horizon_world::{ + active_effects::{ActiveEffects, EffectHooks}, + effect_notifications::{ + self as notify, EffectPacket, EligibilityWorld, ImmunityTag, NotificationWorld, + RestrictedEffect, + }, + java_hash_map::JavaKey, + object_open_set::{CursorError, ObjectKey, ObjectOpenSet}, + status_effect::{BlendState, Details, EffectInstance}, +}; +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 ObjectKey for Key { + fn object_hash(&self) -> i32 { + self.hash + } +} +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> { + f: Value, + out: &'a mut dyn Write, + keys: Vec, + attribute_keys: Vec, + updates: [ObjectOpenSet; 2], + pool: Vec, + ids: Vec, + calls: i32, + changed: bool, +} +impl World<'_> { + 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, storage: &mut Storage) { + if let Some(dirty) = self.config()["dirty"].as_bool() { + storage.dirty = dirty; + } + let c = self.config().clone(); + 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, s.dirty + ) + .unwrap(); + for (i, updates) in self.updates.iter().enumerate() { + writeln!( + self.out, + "updates {i} {}", + updates + .values() + .map(|v| v.map_or("null".into(), |k| k.id.to_string())) + .collect::>() + .join(",") + ) + .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() { + "pending-add" => { + let key = op["b"].as_i64().unwrap(); + self.updates[a].add((key >= 0).then(|| self.attribute_keys[key as usize].clone())); + } + "pending-remove" => { + let key = op["b"].as_i64().unwrap(); + self.updates[a].remove((key >= 0).then(|| &self.attribute_keys[key as usize])); + } + "pending-clear" => self.updates[a].clear(), + "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(s); + for op in self.f["mutations"].as_array().unwrap().clone() { + self.raw(s, &op); + } + } + Ok(()) + } +} +impl EffectHooks for World<'_> { + type Error = &'static str; + type Source = i32; + type Level = i32; + fn can_be_affected(&mut self, s: &mut Storage, e: Effect) -> Result { + notify::can_be_affected(s, &e, self) + } + fn on_added(&mut self, s: &mut Storage, e: Effect, _: Option) -> Result<(), Self::Error> { + notify::on_added(s, &e, self) + } + fn on_updated( + &mut self, + s: &mut Storage, + e: Effect, + reapply: bool, + _: Option, + ) -> Result<(), Self::Error> { + notify::on_updated(s, &e, reapply, self) + } + fn effect_added(&mut self, s: &mut Storage, e: Key, amp: i32) -> Result<(), Self::Error> { + if e.id < 4 { + Ok(()) + } else { + self.event(s, format!("effect-added {} {amp}", e.id)) + } + } + fn effect_started(&mut self, s: &mut Storage, e: Key, amp: i32) -> Result<(), Self::Error> { + if e.id < 4 { + Ok(()) + } else { + 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> { + notify::on_removed(s, &es, self) + } + 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 EligibilityWorld for World<'_> { + type Error = &'static str; + fn has_immunity_tag(&mut self, s: &mut Storage, tag: ImmunityTag) -> Result { + let id = tag as usize; + self.event(s, format!("tag {id}"))?; + Ok(self.config()["tags"][id].as_bool().unwrap()) + } + fn is_restricted_effect(&self, effect: &Key, kind: RestrictedEffect) -> bool { + effect.id == kind as usize + } +} +impl NotificationWorld for World<'_> { + type Attributes = usize; + type UpdateSet = usize; + type AttributeInstance = Key; + type Attribute = i32; + type Passenger = usize; + type Connection = i32; + fn attributes(&mut self, s: &mut Storage) -> Result { + self.event(s, "attributes".into())?; + Ok(n(self.config(), "attributes") as usize) + } + fn add_modifiers( + &mut self, + s: &mut Storage, + e: Key, + a: usize, + amp: i32, + ) -> Result<(), Self::Error> { + if e.id < 4 { + return Ok(()); + } + self.event(s, format!("add-modifiers {} {a} {amp}", e.id)) + } + fn remove_modifiers(&mut self, s: &mut Storage, e: Key, a: usize) -> Result<(), Self::Error> { + if e.id < 4 { + return Ok(()); + } + self.event(s, format!("remove-modifiers {} {a}", e.id)) + } + fn pending_updates(&mut self, s: &mut Storage, a: usize) -> Result { + self.event(s, format!("pending {a}"))?; + Ok(self.config()["routes"][a].as_u64().unwrap() as usize) + } + fn update_set(&self, s: usize) -> &ObjectOpenSet { + &self.updates[s] + } + fn update_set_mut(&mut self, s: usize) -> &mut ObjectOpenSet { + &mut self.updates[s] + } + fn iterator_error(e: CursorError) -> Self::Error { + match e { + CursorError::MissingWrapped => "null", + CursorError::IndexOutOfBounds => "bounds", + CursorError::NoSuchElement => "empty", + CursorError::NoCurrentEntry => "current", + } + } + fn attribute_of(&mut self, s: &mut Storage, i: Option) -> Result { + let i = i.ok_or("null")?; + self.event(s, format!("attribute {}", i.id))?; + Ok(self.config()["holders"][i.id].as_i64().unwrap() as i32) + } + fn attribute_updated(&mut self, s: &mut Storage, a: i32) -> Result<(), Self::Error> { + self.event(s, format!("attribute-updated {a}")) + } + fn passengers(&self) -> Vec { + self.config()["passengers"] + .as_array() + .unwrap() + .iter() + .map(|p| p.as_u64().unwrap() as usize) + .collect() + } + fn is_server_player(&self, p: usize) -> bool { + p < 2 + } + fn connection(&self, p: usize) -> i32 { + self.config()["connections"][p].as_i64().unwrap() as i32 + } + fn entity_id(&mut self, s: &mut Storage) -> Result { + self.event(s, "entity-id".into())?; + Ok(n(self.config(), "entity")) + } + fn send_packet( + &mut self, + s: &mut Storage, + c: i32, + p: EffectPacket, + ) -> Result<(), Self::Error> { + if c < 0 { + return Err("null"); + } + let message = match p { + EffectPacket::Update { + entity, + effect, + amplifier, + duration, + flags, + } => format!( + "packet {c} update {entity} {} {amplifier} {duration} {flags}", + effect.id + ), + EffectPacket::Remove { entity, effect } => { + format!("packet {c} remove {entity} {}", effect.id) + } + }; + self.event(s, message) + } +} +fn run(f: Value, out: &mut dyn Write) { + 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 attribute_keys = f["attribute_hashes"] + .as_array() + .unwrap() + .iter() + .enumerate() + .map(|(id, h)| Key { + id, + hash: h.as_i64().unwrap() as i32, + identity: 0, + }) + .collect(); + let mut w = World { + attribute_keys, + updates: std::array::from_fn(|_| ObjectOpenSet::default()), + f, + out, + keys, + pool, + ids: Vec::new(), + calls: 0, + changed: false, + }; + let mut s = Storage::default(); + w.reset(&mut s); + 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() { + "eligible" => { + let e = w.pool[a].clone(); + notify::can_be_affected(&mut s, &e, &mut w).map(|v| v.to_string()) + } + "notify-add" => { + let e = w.pool[a].clone(); + notify::on_added(&mut s, &e, &mut w).map(|_| "ok".into()) + } + "notify-update" => { + let e = w.pool[a].clone(); + notify::on_updated(&mut s, &e, op["reapply"].as_bool().unwrap(), &mut w) + .map(|_| "ok".into()) + } + "notify-remove" => { + let es = op["list"] + .as_array() + .unwrap() + .iter() + .map(|i| w.pool[i.as_u64().unwrap() as usize].clone()) + .collect::>(); + notify::on_removed(&mut s, &es, &mut w).map(|_| "ok".into()) + } + "send" => { + let e = w.pool[a].clone(); + notify::send_to_passengers(&mut s, &e, &mut w).map(|_| "ok".into()) + } + "refresh" => notify::refresh_attributes(&mut s, &mut w).map(|_| "ok".into()), + "pending-add" | "pending-remove" | "pending-clear" => { + w.raw(&mut s, &op); + Ok("ok".into()) + } + "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); + } +} +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() { + run(serde_json::from_str(line)?, &mut out); + } + Ok(()) +} diff --git a/crates/horizon-world/src/active_effects.rs b/crates/horizon-world/src/active_effects.rs index 8eb4afae40..94c74ad24a 100644 --- a/crates/horizon-world/src/active_effects.rs +++ b/crates/horizon-world/src/active_effects.rs @@ -5,6 +5,8 @@ use crate::{ }; pub struct ActiveEffects { + /// LivingEntity schedules effect synchronization from construction. + pub dirty: bool, /// Native activeEffects is exposed by getActiveEffectsMap. Direct mutations /// bypass lifecycle notifications, but retain the map's iterator semantics. pub entries: JavaHashMap>, @@ -13,6 +15,7 @@ impl Default for ActiveEffects { fn default() -> Self { Self { entries: JavaHashMap::default(), + dirty: true, } } } diff --git a/crates/horizon-world/src/effect_notifications.rs b/crates/horizon-world/src/effect_notifications.rs new file mode 100644 index 0000000000..cea64b21de --- /dev/null +++ b/crates/horizon-world/src/effect_notifications.rs @@ -0,0 +1,246 @@ +//! Base LivingEntity effect eligibility, attribute refresh and passenger updates. +use crate::{ + active_effects::{ActiveEffects, EffectHooks}, + java_hash_map::JavaKey, + object_open_set::{CursorError, ObjectKey, ObjectOpenSet}, + status_effect::EffectInstance, +}; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ImmunityTag { + Infested, + Oozing, + PoisonAndRegeneration, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RestrictedEffect { + Infested, + Oozing, + Regeneration, + Poison, +} +pub trait EligibilityWorld { + type Error; + fn has_immunity_tag( + &mut self, + storage: &mut ActiveEffects, + tag: ImmunityTag, + ) -> Result; + fn is_restricted_effect(&self, effect: &H, kind: RestrictedEffect) -> bool; +} +pub fn can_be_affected>( + storage: &mut ActiveEffects, + instance: &EffectInstance, + world: &mut W, +) -> Result { + // These branches are exclusive, even when the actor belongs to several tags. + if world.has_immunity_tag(storage, ImmunityTag::Infested)? { + Ok(!world.is_restricted_effect(&instance.read().effect, RestrictedEffect::Infested)) + } else if world.has_immunity_tag(storage, ImmunityTag::Oozing)? { + Ok(!world.is_restricted_effect(&instance.read().effect, RestrictedEffect::Oozing)) + } else if world.has_immunity_tag(storage, ImmunityTag::PoisonAndRegeneration)? { + let data = instance.read(); + Ok( + !world.is_restricted_effect(&data.effect, RestrictedEffect::Regeneration) + && !world.is_restricted_effect(&data.effect, RestrictedEffect::Poison), + ) + } else { + Ok(true) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum EffectPacket { + Update { + entity: i32, + effect: H, + amplifier: i32, + duration: i32, + flags: u8, + }, + Remove { + entity: i32, + effect: H, + }, +} +impl EffectPacket { + pub fn update(entity: i32, instance: &EffectInstance, blend: bool) -> Self { + let data = instance.read(); + Self::Update { + entity, + effect: data.effect.clone(), + amplifier: data.details.amplifier, + duration: data.details.duration, + flags: u8::from(data.details.ambient) + | (u8::from(data.details.visible) << 1) + | (u8::from(data.details.show_icon) << 2) + | (u8::from(blend) << 3), + } + } +} +/// Attribute maps, pending-update sets and connections are stable handles. A +/// callback may switch the actor's current handle without changing a captured one. +pub trait NotificationWorld: EffectHooks { + type Attributes: Copy; + type UpdateSet: Copy; + type AttributeInstance: ObjectKey + Clone; + type Attribute; + type Passenger: Copy; + type Connection: Copy; + fn attributes( + &mut self, + storage: &mut ActiveEffects, + ) -> Result; + fn add_modifiers( + &mut self, + storage: &mut ActiveEffects, + effect: H, + attributes: Self::Attributes, + amplifier: i32, + ) -> Result<(), Self::Error>; + fn remove_modifiers( + &mut self, + storage: &mut ActiveEffects, + effect: H, + attributes: Self::Attributes, + ) -> Result<(), Self::Error>; + fn pending_updates( + &mut self, + storage: &mut ActiveEffects, + attributes: Self::Attributes, + ) -> Result; + fn update_set(&self, set: Self::UpdateSet) -> &ObjectOpenSet; + fn update_set_mut( + &mut self, + set: Self::UpdateSet, + ) -> &mut ObjectOpenSet; + fn iterator_error(error: CursorError) -> Self::Error; + /// None preserves a native null member's failure at getAttribute. + fn attribute_of( + &mut self, + storage: &mut ActiveEffects, + instance: Option, + ) -> Result; + fn attribute_updated( + &mut self, + storage: &mut ActiveEffects, + attribute: Self::Attribute, + ) -> Result<(), Self::Error>; + /// Entity.getPassengers is final and returns its current immutable list. + fn passengers(&self) -> Vec; + fn is_server_player(&self, passenger: Self::Passenger) -> bool; + /// Direct ServerPlayer.connection field access before the virtual getId call. + fn connection(&self, passenger: Self::Passenger) -> Self::Connection; + fn entity_id(&mut self, storage: &mut ActiveEffects) -> Result; + fn send_packet( + &mut self, + storage: &mut ActiveEffects, + connection: Self::Connection, + packet: EffectPacket, + ) -> Result<(), Self::Error>; + fn send_effect_to_passengers( + &mut self, + storage: &mut ActiveEffects, + instance: &EffectInstance, + ) -> Result<(), Self::Error> + where + Self: Sized, + { + send_to_passengers(storage, instance, self) + } +} +pub fn send_to_passengers>( + storage: &mut ActiveEffects, + instance: &EffectInstance, + world: &mut W, +) -> Result<(), W::Error> { + for passenger in world.passengers() { + if world.is_server_player(passenger) { + let connection = world.connection(passenger); + let entity = world.entity_id(storage)?; + let packet = EffectPacket::update(entity, instance, false); + world.send_packet(storage, connection, packet)?; + } + } + Ok(()) +} +pub fn refresh_attributes>( + storage: &mut ActiveEffects, + world: &mut W, +) -> Result<(), W::Error> { + let attributes = world.attributes(storage)?; + let updates = world.pending_updates(storage, attributes)?; + let mut cursor = world.update_set(updates).cursor(); + while cursor.has_next() { + let instance = world + .update_set(updates) + .next(&mut cursor) + .map_err(W::iterator_error)?; + let attribute = world.attribute_of(storage, instance)?; + world.attribute_updated(storage, attribute)?; + } + world.update_set_mut(updates).clear(); + Ok(()) +} +pub fn on_added>( + storage: &mut ActiveEffects, + instance: &EffectInstance, + world: &mut W, +) -> Result<(), W::Error> { + let level = world.level(storage)?; + if !world.is_client(storage, level)? { + storage.dirty = true; + let effect = instance.read().effect.clone(); + let attributes = world.attributes(storage)?; + let amplifier = instance.read().details.amplifier; + world.add_modifiers(storage, effect, attributes, amplifier)?; + world.send_effect_to_passengers(storage, instance)?; + } + Ok(()) +} +pub fn on_updated>( + storage: &mut ActiveEffects, + instance: &EffectInstance, + reapply: bool, + world: &mut W, +) -> Result<(), W::Error> { + let level = world.level(storage)?; + if world.is_client(storage, level)? { + return Ok(()); + } + storage.dirty = true; + if reapply { + let effect = instance.read().effect.clone(); + let attributes = world.attributes(storage)?; + world.remove_modifiers(storage, effect.clone(), attributes)?; + let attributes = world.attributes(storage)?; + let amplifier = instance.read().details.amplifier; + world.add_modifiers(storage, effect, attributes, amplifier)?; + refresh_attributes(storage, world)?; + } + world.send_effect_to_passengers(storage, instance) +} +pub fn on_removed>( + storage: &mut ActiveEffects, + instances: &[EffectInstance], + world: &mut W, +) -> Result<(), W::Error> { + let level = world.level(storage)?; + if world.is_client(storage, level)? { + return Ok(()); + } + storage.dirty = true; + for instance in instances { + let effect = instance.read().effect.clone(); + let attributes = world.attributes(storage)?; + world.remove_modifiers(storage, effect, attributes)?; + for passenger in world.passengers() { + if world.is_server_player(passenger) { + let connection = world.connection(passenger); + let entity = world.entity_id(storage)?; + let effect = instance.read().effect.clone(); + world.send_packet(storage, connection, EffectPacket::Remove { entity, effect })?; + } + } + } + refresh_attributes(storage, world) +} diff --git a/crates/horizon-world/src/lib.rs b/crates/horizon-world/src/lib.rs index 22bdd72a27..a563d8451b 100644 --- a/crates/horizon-world/src/lib.rs +++ b/crates/horizon-world/src/lib.rs @@ -2,6 +2,7 @@ #![forbid(unsafe_code)] pub mod object_open_set; +pub mod effect_notifications; pub mod active_effects; pub mod active_effect_tick; pub mod stored_consume; diff --git a/docs/rust-parity.md b/docs/rust-parity.md index 7584dbf1db..5b6aabd2bd 100644 --- a/docs/rust-parity.md +++ b/docs/rust-parity.md @@ -1059,3 +1059,22 @@ operations and live iteration; alternate constructors, bulk methods, and externa structural mutation followed by iterator removal remain outside its scope. Binding this storage to attribute refresh and the complete entity lifecycle is the next step; this collection comparison does not establish gameplay parity. + +## Effect eligibility and entity notification bodies + +Effect eligibility now follows vanilla's ordered immunity-tag branches. Entity +effect callbacks mark synchronization dirty, retain captured effect/map handles, +apply or remove modifiers, refresh pending attributes and construct passenger +update/removal packets in native order. New effect storage starts dirty, matching +the entity constructor. Attribute refresh reads its captured live fastutil set +and clears that same set only after every callback succeeds. Passenger lists +remain captured while later connection fields and packet data are read at their +native evaluation points. + +The [notification comparison](../benchmarks/rust-effect-notifications-2026-10-07/scoped-parity.json) +matches **1,667,616 lines per reference JAR**, exercising inherited native +eligibility, notification, refresh and passenger methods together with effect +storage. The fixtures use actual status instances, pending sets, immutable +passenger lists and packet constructors. Library and fixture Clippy pass. +Modifier bodies, attribute-update consequences and packet transport are still +controlled; their full bindings and complete gameplay remain unfinished. diff --git a/mods/horizon/src/test/java/dev/horizonlab/parity/VanillaEffectNotificationsOracle.java b/mods/horizon/src/test/java/dev/horizonlab/parity/VanillaEffectNotificationsOracle.java new file mode 100644 index 0000000000..06cc75d005 --- /dev/null +++ b/mods/horizon/src/test/java/dev/horizonlab/parity/VanillaEffectNotificationsOracle.java @@ -0,0 +1,55 @@ +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.world.entity.ai.attributes.*;import net.minecraft.server.level.ServerPlayer;import net.minecraft.server.network.ServerGamePacketListenerImpl;import net.minecraft.network.protocol.Packet;import net.minecraft.network.protocol.game.*;import net.minecraft.tags.*;import com.google.common.collect.ImmutableList;import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +/** Inherited eligibility and effect notification bodies with real packets and pending sets. */ +public final class VanillaEffectNotificationsOracle { + static Object unsafe;static Method allocate;static Field duration,amplifier,ambient,visible,icon,hidden,blend,factor,previous,removed,activeEffects;static Data active;static Field dirty,passengers;static Method refresh;static List> known;static List> attributeHolders; + 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;List> keys=new ArrayList<>();MobEffectInstance[] pool;IdentityHashMap ids=new IdentityHashMap<>();Actor actor;Entity other;EffectLevel[] levels;Attr[] attributeInstances;Attrs[] attributes;List> updates;ServerPlayer[] riders;Connection[] connections;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("dirty").isJsonNull())dirty.setBoolean(actor,c.get("dirty").getAsBoolean());for(int i=0;i<2;i++){int connection=c.getAsJsonArray("connections").get(i).getAsInt();riders[i].connection=connection<0?null:connections[connection];}var ps=new ArrayList();for(var p:c.getAsJsonArray("passengers")){int id=p.getAsInt();ps.add(id<2?riders[id]:other);}passengers.set(actor,ImmutableList.copyOf(ps));removed.set(actor,c.get("removed").getAsBoolean()?Entity.RemovalReason.DISCARDED:null);for(int i=0;iset.add(v==null?"null":Integer.toString(((Attr)v).id)));out.println("updates "+i+" "+set);}var roots=new ArrayList();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;iupdates.get(a).add(b<0?null:attributeInstances[b]);case "pending-remove"->updates.get(a).remove(b<0?null:attributeInstances[b]);case "pending-clear"->updates.get(a).clear();case "put"->map().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 addAttributeModifiers(AttributeMap attributes,int amp){active.event("add-modifiers "+id+" "+((Attrs)attributes).id+" "+amp);} + @Override public void removeAttributeModifiers(AttributeMap attributes){active.event("remove-modifiers "+id+" "+((Attrs)attributes).id);} + @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 boolean is(TagKey> tag){int i=tag==EntityTypeTags.IMMUNE_TO_INFESTED?0:tag==EntityTypeTags.IMMUNE_TO_OOZING?1:tag==EntityTypeTags.IGNORES_POISON_AND_REGEN?2:-1;if(i<0)throw new AssertionError();d.event("tag "+i);return d.config().getAsJsonArray("tags").get(i).getAsBoolean();} + @Override public AttributeMap getAttributes(){d.event("attributes");return d.attributes[n(d.config(),"attributes")];} + @Override protected void onAttributeUpdated(Holder h){d.event("attribute-updated "+attributeHolders.indexOf(h));} + @Override public int getId(){d.event("entity-id");return n(d.config(),"entity");} + void added(MobEffectInstance e){super.onEffectAdded(e,null);}void updated(MobEffectInstance e,boolean apply){super.onEffectUpdated(e,apply,null);}void removed(List es){super.onEffectsRemoved(es);} + @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 boolean isClientSide(){d.event("client "+id);return d.config().get("client").getAsBoolean();}} + static final class Attr extends AttributeInstance {Data d;int id,hash;Attr(){super(null,null);}@Override public int hashCode(){return hash;}@Override public Holder getAttribute(){d.event("attribute "+id);return attributeHolders.get(d.config().getAsJsonArray("holders").get(id).getAsInt());}} + static final class Attrs extends AttributeMap {Data d;int id;Attrs(){super(null);}@Override public Set getAttributesToUpdate(){d.event("pending "+id);return d.updates.get(d.config().getAsJsonArray("routes").get(id).getAsInt());}} + static final class Connection extends ServerGamePacketListenerImpl {Data d;int id;Connection(){super(null,null,null,null);}@Override public void send(Packet packet){String body;if(packet instanceof ClientboundUpdateMobEffectPacket p){int flags=(p.isEffectAmbient()?1:0)|(p.isEffectVisible()?2:0)|(p.effectShowsIcon()?4:0)|(p.shouldBlend()?8:0);body="update "+p.getEntityId()+" "+d.key(p.getEffect())+" "+p.getEffectAmplifier()+" "+p.getEffectDurationTicks()+" "+flags;}else if(packet instanceof ClientboundRemoveMobEffectPacket p){body="remove "+p.entityId()+" "+d.key(p.effect());}else throw new AssertionError();d.event("packet "+id+" "+body);}} + public static void main(String[] args)throws Exception{net.minecraft.SharedConstants.tryDetectVersion();net.minecraft.server.Bootstrap.bootStrap();known=List.of(MobEffects.INFESTED,MobEffects.OOZING,MobEffects.REGENERATION,MobEffects.POISON);attributeHolders=List.of(Attributes.MAX_HEALTH,Attributes.MAX_ABSORPTION,Attributes.SCALE,Attributes.WAYPOINT_TRANSMIT_RANGE);dirty=field(LivingEntity.class,"effectsDirty");passengers=field(Entity.class,"passengers");refresh=LivingEntity.class.getDeclaredMethod("refreshDirtyAttributes");refresh.setAccessible(true);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;var hashes=new JsonArray();var identities=new JsonArray();for(var hash:d.f.getAsJsonArray("hashes")){int id=d.keys.size();var h=id<4?known.get(id):Holder.direct(new Effect(id,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;dirty.setBoolean(d.actor,true);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(),new ObjectOpenHashSet<>());for(int i=0;i<2;i++){d.connections[i]=make(Connection.class);d.connections[i].d=d;d.connections[i].id=i;d.riders[i]=make(ServerPlayer.class);d.attributes[i]=make(Attrs.class);d.attributes[i].d=d;d.attributes[i].id=i;}var ah=d.f.getAsJsonArray("attribute_hashes");d.attributeInstances=new Attr[ah.size()];for(int i=0;iresult=Boolean.toString(d.actor.canBeAffected(d.pool[a])); + case "notify-add"->d.actor.added(d.pool[a]);case "notify-update"->d.actor.updated(d.pool[a],op.get("reapply").getAsBoolean());case "notify-remove"->{var es=new ArrayList();for(var i:op.getAsJsonArray("list"))es.add(d.pool[i.getAsInt()]);d.actor.removed(es);} + case "send"->d.actor.sendEffectToPassengers(d.pool[a]);case "refresh"->{try{refresh.invoke(d.actor);}catch(InvocationTargetException ex){if(ex.getCause() instanceof RuntimeException e)throw e;throw new AssertionError(ex.getCause());}} + case "pending-add","pending-remove","pending-clear"->d.raw(op); + 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";}catch(NullPointerException e){result="null";}catch(IndexOutOfBoundsException e){result="bounds";}catch(NoSuchElementException e){result="empty";}out.println("result "+result);d.all();} + }} + } +} diff --git a/tools/test_effect_notifications_parity.py b/tools/test_effect_notifications_parity.py new file mode 100644 index 0000000000..71514f30c7 --- /dev/null +++ b/tools/test_effect_notifications_parity.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +"""Compare native effect eligibility, notification bodies, pending sets and passenger packets.""" +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(0x4e4f54494659) + durations=[-2147483648,-2,-1,0,1,20,200,2147483647] + amps=[-1,0,1,2,255,256,2147483647] + floats=[0,0x80000000,0x3f000000,0x3f800000,0xbf800000,0x7f800000,0x7fc00000] + def state(**kw): + v=dict(key=4,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(key=i) for i in range(6)],level=0,client=False,removed=False,dirty=False,tags=[False]*3,attributes=0,routes=[0,1],holders=[0,1,2],passengers=[0,2,1,0],connections=[0,1],entity=42);v.update(kw);return v + def op(name,a=4,b=0,**kw): + v=dict(op=name,a=a,b=b,source=-1,partial=0x3f000000,list=[4,5],reapply=True);v.update(kw);return v + def row(**kw): + v=dict(hashes=[0,1,2,3,0,0],attribute_hashes=[0,31,64],initial=config(),after=config(states=[state(key=i,duration=-1,amplifier=9,ambient=True,visible=False,icon=False) for i in range(6)],attributes=1,routes=[1,0],holders=[2,0,1],passengers=[1,0],connections=[1,0],entity=-13,dirty=None),setup=[op('pending-add',0,i) for i in range(3)]+[op('pending-add',1,2)],ops=[op('add'),op('force',5),op('all')],fail=-1,mutate=-1,mutations=[op('put',4,5)]);v.update(kw);return v + for bits in range(8): + for effect in range(6): + for mutate in range(-1,3): + for fail in range(-1,3): + yield row(initial=config(tags=[bool(bits&(1<