Implement living hurt cooldown and outer damage dispatch

This commit is contained in:
Ludovic Marie
2026-10-07 05:24:37 +02:00
parent f454f2ab46
commit 72aafed363
7 changed files with 887 additions and 0 deletions
@@ -0,0 +1,254 @@
{
"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": 5584,
"sources_sha256": {
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaLivingHurtOracle.java": "b5173b07be60991c6ed9cf75a1056f117459207c57411c00a0cee14fea8a51bf",
"tools/test_living_hurt_parity.py": "2e9770f9e4653feada6a758a40eeaea89f370394106f55199a9541c8c867cec3",
"tools/test_rust_parity.py": "29d3db08005873528894b45ca10dcd6278753650473b3eb574e53e06ede40ec6",
"tools/lab.py": "d00bf107fa01b6c579734a082c3ca4ade3018f0d6851d46844acf17574f66e64",
"Cargo.toml": "3f93319c1eb70dbd7ba25cd74ef343fbf35c52c7c4752f955605c58699cbff9d",
"Cargo.lock": "cb1e87d449114a63edd904c0559d631e8fa9feeb27efd6539aacdcfeae178876",
"crates/horizon-core/Cargo.toml": "e21b502c949da93cf67826592459b687a8cbf737ef92fee514e3ed2cc7cd27ad",
"crates/horizon-world/Cargo.toml": "977338bf3e654193685b015d383493b9f59ff6f5d6210bec212cef8ac4a50bed",
"crates/horizon-core/src/bin/density_parity.rs": "e5b1dd33b7d01306c73cf072629b4fb186e9ab87b672a00108999d1a72263fd0",
"crates/horizon-core/src/bin/float_log_parity.rs": "4a655fcf8822ee7668c4bd126bc84d0b118864a6fc7f5a6aeee667d8bf2590b1",
"crates/horizon-core/src/bin/geometry_parity.rs": "27f6c09fe5018ac3de8e70e0f0bbf78f9abb14f5e4783bcaf46e8ca87b9c6f8a",
"crates/horizon-core/src/bin/noise_parity.rs": "c135bfc708d853e7061e81487d2cfe10de64b2f65677d3b4837cbfe0b8341010",
"crates/horizon-core/src/bin/parity.rs": "f35d928c66f958de39fac5e7e836a657eed95f83599e84899f7ea7a5802ba481",
"crates/horizon-core/src/bin/tick_parity.rs": "a54c164fe4814034d369947a887f5273966f5a878ba849204b80d1c729526a8f",
"crates/horizon-core/src/biome/seed_hash.rs": "5afbb4921c750826b395a08bf06f382fc7f01b5759a93de02a2749c523e2a317",
"crates/horizon-core/src/biome.rs": "908efa8cd4aa0a7adf1cb5e1a222e3dfb640eed5e9172e0fe7f0d1287afae7e8",
"crates/horizon-core/src/carving/ellipsoid.rs": "09232a7588610e8a9176fd9c62d40afb1fb89f2ecab6d555536a1d847b142d86",
"crates/horizon-core/src/carving.rs": "0aa3093caa0437868a8c0ed52a6c6e1993bd51c03583c670a2837e749eae5426",
"crates/horizon-core/src/density/arithmetic.rs": "372faec96c923e2cf4aa6c66c1ac398e676853274557823850dbc8933dd15ab1",
"crates/horizon-core/src/density/beardifier.rs": "bb1052e27dae4a01954ca78d7d5babafef85f3b2c704ccaf1920cb85a098cb50",
"crates/horizon-core/src/density/cache.rs": "38d0b6c9dd04ffef979fdad3f29cfebf98d331c8c9d430be5f39aa34f6c30975",
"crates/horizon-core/src/density/end_islands.rs": "168b9384237bd2e41d0ae5a74e944da4dc74e493bdec31fb73184c9a49e3de55",
"crates/horizon-core/src/density/graph/compiler.rs": "ac67deeb16f16d0859aa20e5a165811a29aca0b910037ee6bf5098211f3a6ede",
"crates/horizon-core/src/density/graph/mod.rs": "b776e7da934cb03051a3053d56d461c71e9978e084922fe4194147aa580c07bf",
"crates/horizon-core/src/density/graph/seeded.rs": "6bcdcaa3dffcfa9df673bbbea35120057a16ba3d2b506c52709c36cf8976b74a",
"crates/horizon-core/src/density/graph/tests.rs": "6bfb8cbff462587dd147b0775ea01d370abbd97544b9bd596c792df1b5f7e634",
"crates/horizon-core/src/density/interpolation.rs": "8c890f52b6467f38a94c654986a6a484b8e0b296ab98c398e60a6ce465b5b010",
"crates/horizon-core/src/density/interval.rs": "b42629163baa6b599a233afe8596d1c370cd81d015cc6fc36a9663aa47b98424",
"crates/horizon-core/src/density/mod.rs": "ca0d77d3282e3746ddcbc92bee97dbf640b9a56214e6ac1537f51f9a5b63d6e7",
"crates/horizon-core/src/density/power.rs": "d5b7270a128d4264268cd772109ad16d71a8ec3ff2d3b63b84cb985296d54a91",
"crates/horizon-core/src/density/rounding.rs": "71840c5206ff8d218ac29bf6f66915d5212764ba270c675711a866ba5b55a7a6",
"crates/horizon-core/src/density/selection.rs": "0dd26af2e85004ec5e053039ad11c384c17c56ace9ab45eb3f5cb3503c0b0330",
"crates/horizon-core/src/density/slice.rs": "5d77605573c0deda47778f1f78f7fa3fbf1944d8f5efbcc65cf589181cadf80b",
"crates/horizon-core/src/density/spline.rs": "6bb2e40f940fc6544d6fb09cb0b8cac722be03bc663dbdfb0f61faae207400bc",
"crates/horizon-core/src/density/surface.rs": "159e0e69cda74f870e80ea64fe523c4397e64af4ccb861bb9fd07b2c3cb91b00",
"crates/horizon-core/src/density/terrain_noise.rs": "8d0059d25eb5d248cb97a4c5a2e670d61528928ea151495c004f25adb723fe3e",
"crates/horizon-core/src/density/tests.rs": "7817796ee40d0ae241bfbebec6bca190e0a8e7d5a1caa8ddf858c3eaa58b8858",
"crates/horizon-core/src/density/world_fields.rs": "c1ce305cb8046b384d90c564cafb24af7365d580389637910f3d8b0cc18eb583",
"crates/horizon-core/src/geometry/border.rs": "1386f8080521231d05ba3e84d94a3e1d5218d35ccb3d5e9fe2bbb05a8739e573",
"crates/horizon-core/src/geometry/mod.rs": "c650386f9860e44a96c55f18c8f1428ca715fcb0bc5c2434db5b1caa43df27fe",
"crates/horizon-core/src/geometry/movement.rs": "bcfb452752940ce53ae6dc267a3c4eb29459046a063c1f884684ab4f4bd0068f",
"crates/horizon-core/src/geometry/shape/create.rs": "189812b91d5e0e3ad963afcd1a6e2831b31b8188df91a904ef27061e644806ad",
"crates/horizon-core/src/geometry/shape/face.rs": "f243bce81384040a95ce7984b02b5699206c596c537b2713097e86005b4d3a5c",
"crates/horizon-core/src/geometry/shape/join.rs": "f1b51887b7bf9482557e3ffa99e136f2e5db07ceeb05fdb116adf45e87129075",
"crates/horizon-core/src/geometry/shape.rs": "e8d88a763de1525f77a30b99591641e8a8b80946263acbde035a9bcc356df960",
"crates/horizon-core/src/geometry/sweep.rs": "abd6009c61555161ef2f18b431b2aa384b126c0871cae8b96a62358498a56a1d",
"crates/horizon-core/src/geometry/traversal.rs": "77a3da570a0b85582b669cf4d841cba4b06327006dda26d4a26df4e8ce05bae4",
"crates/horizon-core/src/geometry/trig.rs": "05cef9341a298a474221a0556a11ddf47650393ea7a97b4b2022a4da6628fe37",
"crates/horizon-core/src/lib.rs": "4255e16837fde42a52ba118f60581d197c571bb530b60d635e506184ebf4db0b",
"crates/horizon-core/src/noise/mod.rs": "1dab5a713abf702402e69ace1bf9e476bba544b0ede5a61c3c3afc60af0a03f3",
"crates/horizon-core/src/noise/normal.rs": "e9e7a46472a54a02c98c46da819842090567153b32fe0c84249009ef19af7aad",
"crates/horizon-core/src/noise/perlin.rs": "e73815447432eab7542f67366cb3f1b368d164e33f6c9cdbd045ac8e4db0932a",
"crates/horizon-core/src/noise/simplex.rs": "6c0dbc15b2b7510195dd28fceca01dcaac1847ccc4a84d299c351589c642fe7a",
"crates/horizon-core/src/noise/stack.rs": "7f6ef0e7d920139ef1a6ad3ec5640f92765715f86dcc5ec29f1a149984f45fa3",
"crates/horizon-core/src/noise/volume.rs": "3f05d2b1780acfdfee60037677d309937a5033b45ef428fc2d540942d4b56b43",
"crates/horizon-core/src/position/iter.rs": "32d2d8e74821cc4e49e3354202d35d3120e4415bfdda9375a73f2137ef651129",
"crates/horizon-core/src/position.rs": "e10101088e6902386b9b6d27773d44312f1ff72bebb19e57967a54945dde9dcd",
"crates/horizon-core/src/random/hash.rs": "0f88a127a1ba2705d3d6c0ebd009324d6ba86ffa2d27edf2d2a84765e690e4a3",
"crates/horizon-core/src/random/positional.rs": "35494745e5d4cf89e282c3ae0ee245e9f2640ddbbc03bc0055df742006a9c862",
"crates/horizon-core/src/random/worldgen.rs": "9f07ff33bcc038d34e27aa5465753bb21df18f2975e43cae201f709bc186945a",
"crates/horizon-core/src/random.rs": "ddb95d3a83e28ad605ff1f49f5df270cc543ca5ef1130e37d989dc74c8d861e6",
"crates/horizon-core/src/storage.rs": "f6c32201c64aefde06e57a2c767be46918df613548a322801546b133799cc33d",
"crates/horizon-core/src/text.rs": "e6553ddb5db5039e8dc4614b6a6dc9f35a26dc3d1673b68a04e72e7983b67abb",
"crates/horizon-core/src/ticks/generation.rs": "be1237e790693f633a61714ddae68c6506a777588a8a3ab5efbe53a35774dbd2",
"crates/horizon-core/src/ticks/heap.rs": "bf28afab8af77c3d243aa96cd8a1fb8d934140a4dbd07dbfdbf4e7908bb4b845",
"crates/horizon-core/src/ticks/index.rs": "1f1a6a568c2a30eafecb020ca4d5f5fc414860de85b8b381fc419a2e3e65c24a",
"crates/horizon-core/src/ticks/mod.rs": "01634630668172516b8b79d0907bad47f6e2c543a6d499c2fa4b201823b375cb",
"crates/horizon-world/src/attribute_health.rs": "6135beb1baf9a55745ab95955bcd05e585e4042a6cffda0a14c13f05593e7186",
"crates/horizon-world/src/attributes/entity.rs": "56fb96b1beb9caaa769369bf7cddf522d2f2e84e241c213b51ae8d39d31a9f13",
"crates/horizon-world/src/attributes/mod.rs": "3d05ce2ff10c1b68540e3c87bbd2ac65c56a2e7b7127a7865b60890a2e9a690d",
"crates/horizon-world/src/attributes/operation_map.rs": "fc5bc8921d78ea6721a2eaa915e67483e2a33683927467bdba24f0492356ad97",
"crates/horizon-world/src/bin/worldgen_parity.rs": "3143ceb9b56eecfd7667e5feb5f9f0cd13cfb8754b38d4ea338dbba97f2819da",
"crates/horizon-world/src/block_collision.rs": "39207247f2f6dd3ddc77640229044246f91b32054563f88a435f1bd92ad2092c",
"crates/horizon-world/src/block_collision_scan.rs": "20802862364c110ff85eeaf48606596a8468b83768dda46b412d337958790b41",
"crates/horizon-world/src/block_entities.rs": "a9deae92e6e5a1fcf4f0476c33ea4d170ddad6fd4b3303e1bb66d933973d5e06",
"crates/horizon-world/src/block_entity_push.rs": "12ee91bf604946234b24a0da4333f07d1830ab9743a422d41c8d0afac7d49cfd",
"crates/horizon-world/src/block_landing.rs": "33083fe39266fbefec1b3ea1a19f2afc609791d189da247c895e116ad6eb266a",
"crates/horizon-world/src/block_placement.rs": "524f4892d50bab77b7d89d19f7b048ffe746996f2ed7f376db2c6ab00ed75c24",
"crates/horizon-world/src/block_properties.rs": "b0bd8a983336c48e488a20caa77dd5c39b1a74bbd14ebc03b3b64556a1c864fc",
"crates/horizon-world/src/block_render.rs": "8df52ee11aef406a1571198a64048062a0ef6864e0058d9293a57f64b73c916a",
"crates/horizon-world/src/block_support.rs": "ade4fbd47284b450dabdd682dc86138b2ac05a0fd379525d7110648908245476",
"crates/horizon-world/src/block_survival.rs": "28ce64375e09819a4f13da9cfbe37c08b5ad0daf01b289c1c360b4567e50e811",
"crates/horizon-world/src/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/compression.rs": "4db73e6ca9a0d552d415ce60e7e88b89c9cf2b7d060a669ff8328f518316e101",
"crates/horizon-world/src/damage_reduction.rs": "f5a599ab11e077f526237018aeda803ea3c420c5b35c78ae9d85e32267e23bf3",
"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/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_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/interaction_shapes.rs": "f321284ddab440a394372b6dedc8b5cc29a653c0745251a9e96c650f21c33dea",
"crates/horizon-world/src/lib.rs": "3caead29ea3c4ce5da28a58f0e9071cbc5e163735bf7039adc8a698a34e6f4fd",
"crates/horizon-world/src/liquid_collision.rs": "0f0a83b04e5a5bc9d98ed05fde4d6584ba69642e93522e726e4cfc325d6fcb1d",
"crates/horizon-world/src/living_air.rs": "b2de36bb50bed15e2808fe4a874fcef07f9fea0d2c96385b7b5e8ee9560d4442",
"crates/horizon-world/src/living_damage.rs": "711e78438ece3e94eb156c0e64cfea06a63f79640a7a81e396ae2c1b277cdce6",
"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/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_food.rs": "f188dc110745bcc7acc15e6c521db943b06b404f69cd251bf74a345516070304",
"crates/horizon-world/src/player_health.rs": "79ecb6b4d7a044dc98e5552c53a5ac2724545b1c1cec4d797eb52576728e3480",
"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_damage.rs": "c4fc29eecf2bf401680d1754bef06797a5e4608ab53c5b52e0dd4996f859dbfe",
"crates/horizon-world/src/suffocation.rs": "bed219dd5bbab6263b98fbe3d0eb512cb96c52ae847210efe49514d05f22f11e",
"crates/horizon-world/src/swimming.rs": "49d2dae13c109b37ff832cde98a857944ff45247b864a527b9c4f03ecca7a7b7",
"crates/horizon-world/src/synced_floats.rs": "f417aab280331ddd6f0ca265a12c0130c78b9924f407c7e07f198140501e6010",
"crates/horizon-world/src/tick_data.rs": "f8d04a69ea263f4168bce33f92e5b4ee2fe07d7caf50a1621c6b3ef6eb2479f3",
"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": "d35ded93fe6e95ea045cadcf4925e656dc34ef832b742179b9ad0cc49f902f31",
"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/living_hurt_fixture.rs": "63ecc1532d32c227855605b9be399bb1c3fc1b3c9d59b2b49a4b2d30955043e0"
},
"fixtures_sha256": {
".local/rust-living-hurt-parity/inputs.txt": "70ab2e85a6c2a52e042896873de6e64b51581a9455a5175dcbb1d942d08038c1"
},
"rust_output_sha256": "0ae19c20fc44ab04a061113a9f45d1f25843f092539f935e49e36d2d366f9921",
"references": [
{
"side": "client",
"jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d",
"output_sha256": "0ae19c20fc44ab04a061113a9f45d1f25843f092539f935e49e36d2d366f9921",
"matched_output_lines": 441413
},
{
"side": "server",
"jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6",
"output_sha256": "0ae19c20fc44ab04a061113a9f45d1f25843f092539f935e49e36d2d366f9921",
"matched_output_lines": 441413
}
],
"scope": "Actual LivingEntity.hurtServer invoked directly on base and ServerPlayer receivers. Controlled blocking, inner damage, sounds, knockback, death, attribution and effect callbacks; native empty death-protection lookup and empty-listener victim criterion. Compares field write ordering, cooldown thresholds, float sanitization, modifiers, repeated hits and injected callback failure. Successful totems, attacker-player criteria, actual blocking reduction, combat/death services and full player outer gates remain pending."
}
@@ -0,0 +1,249 @@
use horizon_world::living_hurt::{HurtState, HurtTag, HurtWorld, hurt_server};
use serde_json::Value;
use std::{
fs,
io::{self, Write},
path::Path,
};
fn single(v: &Value) -> f32 {
f32::from_bits(u32::from_str_radix(v.as_str().unwrap(), 16).unwrap())
}
fn bits(v: f32) -> u32 {
if v.is_nan() {
f32::NAN.to_bits()
} else {
v.to_bits()
}
}
fn state(s: &HurtState<()>) -> String {
format!(
"{} {} {:08x} {} {} {} {}",
s.no_action_time,
s.cooldown,
bits(s.last_hurt),
s.hurt_duration,
s.hurt_time,
s.last_source.is_some(),
s.last_stamp
)
}
struct Env<'a> {
out: &'a mut dyn Write,
op: &'a Value,
calls: i64,
hit: bool,
recorded: f32,
player: bool,
}
impl Env<'_> {
fn flag(&self, n: &str) -> bool {
self.op[n].as_bool().unwrap()
}
fn event(&mut self, s: &str) -> io::Result<()> {
writeln!(self.out, "{s}")?;
let n = self.calls;
self.calls += 1;
if n == self.op["fail"].as_i64().unwrap() {
Err(io::Error::other("injected hurt callback failure"))
} else {
Ok(())
}
}
}
impl HurtWorld for Env<'_> {
type Error = io::Error;
type Source = ();
type UseItem = bool;
type Blocking = ();
type Sound = ();
fn source(&self) {}
fn is_server_player(&self) -> bool {
self.player
}
fn invulnerable(&mut self, _: &mut HurtState<()>) -> io::Result<bool> {
self.event("invulnerable")?;
Ok(self.flag("invulnerable"))
}
fn fire_resistance(&mut self, _: &mut HurtState<()>) -> io::Result<bool> {
self.event("fire-resistance")?;
Ok(self.flag("fire_resistance"))
}
fn sleeping(&mut self, _: &mut HurtState<()>) -> io::Result<bool> {
self.event("sleeping")?;
Ok(self.flag("sleeping"))
}
fn freeze_hurts_extra(&mut self, _: &mut HurtState<()>) -> io::Result<bool> {
self.event("freeze-extra")?;
Ok(self.flag("freeze_extra"))
}
fn stop_sleeping(&mut self, _: &mut HurtState<()>) -> io::Result<()> {
self.event("wake")
}
fn resolve_mob(&mut self, _: &mut HurtState<()>) -> io::Result<()> {
self.event("resolve-mob")
}
fn resolve_player(&mut self, _: &mut HurtState<()>) -> io::Result<()> {
self.event("resolve-player")
}
fn broadcast_damage(&mut self, _: &mut HurtState<()>) -> io::Result<()> {
self.event("broadcast")
}
fn mark_hurt(&mut self, _: &mut HurtState<()>) -> io::Result<()> {
self.event("mark")
}
fn secondary_sound(&mut self, _: &mut HurtState<()>) -> io::Result<()> {
self.event("thorns")
}
fn die(&mut self, _: &mut HurtState<()>) -> io::Result<()> {
self.event("die")
}
fn hurt_sound(&mut self, _: &mut HurtState<()>) -> io::Result<()> {
self.event("hurt-sound")
}
fn dead_or_dying(&mut self, _: &mut HurtState<()>) -> io::Result<bool> {
self.event("dead")?;
Ok(self.flag(if self.hit {
"dead_after"
} else {
"dead_before"
}))
}
fn tag(&mut self, _: &mut HurtState<()>, tag: HurtTag) -> io::Result<bool> {
let n = match tag {
HurtTag::Fire => "fire",
HurtTag::Freezing => "freezing",
HurtTag::DamagesHelmet => "damages_helmet",
HurtTag::BypassesCooldown => "bypasses_cooldown",
HurtTag::NoImpact => "no_impact",
HurtTag::NoKnockback => "no_knockback",
};
self.event(&format!("tag {n}"))?;
Ok(self.flag(n))
}
fn use_item(&mut self, _: &mut HurtState<()>) -> io::Result<bool> {
self.event("use-item")?;
Ok(self.flag("component"))
}
fn apply_blocking(&mut self, _: &mut HurtState<()>, v: f32) -> io::Result<f32> {
self.event(&format!("blocking {:08x}", bits(v)))?;
Ok(single(&self.op["blocked"]))
}
fn helmet_empty(&mut self, _: &mut HurtState<()>) -> io::Result<bool> {
self.event("helmet")?;
Ok(!self.flag("helmet"))
}
fn hurt_helmet(&mut self, _: &mut HurtState<()>, v: f32) -> io::Result<()> {
self.event(&format!("hurt-helmet {:08x}", bits(v)))
}
fn actually_hurt(&mut self, s: &mut HurtState<()>, v: f32) -> io::Result<()> {
self.event(&format!("hurt {:08x} {}", bits(v), state(s)))?;
self.hit = true;
self.recorded = v;
if self.flag("mutate_hit") {
s.cooldown = self.op["hit_cooldown"].as_i64().unwrap() as i32;
s.last_hurt = single(&self.op["hit_last"]);
s.hurt_duration = self.op["hit_duration"].as_i64().unwrap() as i32;
s.hurt_time = self.op["hit_time"].as_i64().unwrap() as i32;
}
Ok(())
}
fn blocking_component(&mut self, v: bool) -> Option<()> {
v.then_some(())
}
fn on_blocked(&mut self, _: &mut HurtState<()>, _: ()) -> io::Result<()> {
Ok(())
}
fn knockback(&mut self, _: &mut HurtState<()>, v: f32, b: bool) -> io::Result<()> {
self.event(&format!("knockback {:08x} {b}", bits(v)))
}
fn death_protection(&mut self, _: &mut HurtState<()>) -> io::Result<bool> {
self.event("tag bypasses_invulnerability")?;
if !self.flag("bypasses_invulnerability") {
self.event("hand 0")?;
self.event("hand 1")?;
}
Ok(false)
}
fn death_sound(&mut self, _: &mut HurtState<()>) -> io::Result<()> {
self.event("death-sound")
}
fn make_sound(&mut self, _: &mut HurtState<()>, _: ()) -> io::Result<()> {
self.event("make-sound")
}
fn game_time(&mut self, _: &mut HurtState<()>) -> io::Result<i64> {
self.event("level")?;
self.event("game-time")?;
Ok(self.op["game_time"].as_i64().unwrap())
}
fn effects_on_hurt(&mut self, _: &mut HurtState<()>, v: f32) -> io::Result<()> {
self.event("effects")?;
for i in 0..self.op["effects"].as_u64().unwrap() {
self.event(&format!("effect {i} {:08x}", bits(v)))?;
}
Ok(())
}
fn player_hurt_criterion(
&mut self,
_: &mut HurtState<()>,
_: f32,
_: f32,
_: bool,
) -> io::Result<()> {
self.event("criterion")
}
fn award_blocked(&mut self, _: &mut HurtState<()>, v: i32) -> io::Result<()> {
self.event(&format!("blocked-stat {v}"))
}
fn attacker_criterion(
&mut self,
_: &mut HurtState<()>,
_: f32,
_: f32,
_: bool,
) -> io::Result<()> {
self.event("attacker")
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let arg = std::env::args().nth(1).unwrap();
let dir = Path::new(&arg);
let mut out = io::BufWriter::new(fs::File::create(dir.join("rust.txt"))?);
for line in fs::read_to_string(dir.join("inputs.txt"))?.lines() {
let f: Value = serde_json::from_str(line)?;
let mut s = HurtState {
no_action_time: f["no_action"].as_i64().unwrap() as i32,
cooldown: f["cooldown"].as_i64().unwrap() as i32,
last_hurt: single(&f["last_hurt"]),
hurt_duration: f["duration"].as_i64().unwrap() as i32,
hurt_time: f["time"].as_i64().unwrap() as i32,
last_source: f["source"].as_bool().unwrap().then_some(()),
last_stamp: f["stamp"].as_i64().unwrap(),
};
let mut recorded = 0.0;
for op in f["ops"].as_array().unwrap() {
let mut e = Env {
out: &mut out,
op,
calls: 0,
hit: false,
recorded,
player: f["player"].as_bool().unwrap(),
};
match hurt_server(&mut s, &mut e, single(&op["damage"])) {
Ok(v) => writeln!(e.out, "bool {v}\nresult ok")?,
Err(_) => writeln!(e.out, "result error")?,
};
recorded = e.recorded;
writeln!(
e.out,
"state {} {:08x} {} {}",
state(&s),
bits(recorded),
e.hit,
e.calls
)?;
}
}
Ok(())
}
+1
View File
@@ -46,6 +46,7 @@ pub mod living_damage;
pub mod living_fall;
pub mod living_fluid;
pub mod living_health;
pub mod living_hurt;
pub mod living_landing;
pub mod living_motion;
pub mod movement_emission;
+201
View File
@@ -0,0 +1,201 @@
//! Outer living damage dispatch, cooldown writes and post-hit service ordering.
use crate::living_damage::{STAT_LIMIT, stat_amount};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HurtTag {
Fire,
Freezing,
DamagesHelmet,
BypassesCooldown,
NoImpact,
NoKnockback,
}
#[derive(Clone, Debug)]
pub struct HurtState<S> {
pub no_action_time: i32,
pub cooldown: i32,
pub last_hurt: f32,
pub hurt_duration: i32,
pub hurt_time: i32,
pub last_source: Option<S>,
pub last_stamp: i64,
}
/// One invocation retains the same incoming source and server level. Services
/// receive live state so reentrant callbacks preserve native write ordering.
pub trait HurtWorld {
type Error;
type Source: Copy;
type UseItem: Copy;
type Blocking: Copy;
type Sound: Copy;
fn source(&self) -> Self::Source;
fn is_server_player(&self) -> bool;
fn invulnerable(&mut self, s: &mut HurtState<Self::Source>) -> Result<bool, Self::Error>;
fn dead_or_dying(&mut self, s: &mut HurtState<Self::Source>) -> Result<bool, Self::Error>;
fn tag(&mut self, s: &mut HurtState<Self::Source>, tag: HurtTag) -> Result<bool, Self::Error>;
fn fire_resistance(&mut self, s: &mut HurtState<Self::Source>) -> Result<bool, Self::Error>;
fn sleeping(&mut self, s: &mut HurtState<Self::Source>) -> Result<bool, Self::Error>;
fn stop_sleeping(&mut self, s: &mut HurtState<Self::Source>) -> Result<(), Self::Error>;
fn use_item(&mut self, s: &mut HurtState<Self::Source>) -> Result<Self::UseItem, Self::Error>;
fn apply_blocking(
&mut self,
s: &mut HurtState<Self::Source>,
damage: f32,
) -> Result<f32, Self::Error>;
fn freeze_hurts_extra(&mut self, s: &mut HurtState<Self::Source>) -> Result<bool, Self::Error>;
fn helmet_empty(&mut self, s: &mut HurtState<Self::Source>) -> Result<bool, Self::Error>;
fn hurt_helmet(
&mut self,
s: &mut HurtState<Self::Source>,
damage: f32,
) -> Result<(), Self::Error>;
fn actually_hurt(
&mut self,
s: &mut HurtState<Self::Source>,
damage: f32,
) -> Result<(), Self::Error>;
fn resolve_mob(&mut self, s: &mut HurtState<Self::Source>) -> Result<(), Self::Error>;
fn resolve_player(&mut self, s: &mut HurtState<Self::Source>) -> Result<(), Self::Error>;
fn blocking_component(&mut self, item: Self::UseItem) -> Option<Self::Blocking>;
fn on_blocked(
&mut self,
s: &mut HurtState<Self::Source>,
block: Self::Blocking,
) -> Result<(), Self::Error>;
fn broadcast_damage(&mut self, s: &mut HurtState<Self::Source>) -> Result<(), Self::Error>;
fn mark_hurt(&mut self, s: &mut HurtState<Self::Source>) -> Result<(), Self::Error>;
fn knockback(
&mut self,
s: &mut HurtState<Self::Source>,
damage: f32,
blocked: bool,
) -> Result<(), Self::Error>;
fn death_protection(&mut self, s: &mut HurtState<Self::Source>) -> Result<bool, Self::Error>;
fn death_sound(&mut self, s: &mut HurtState<Self::Source>) -> Result<Self::Sound, Self::Error>;
fn make_sound(
&mut self,
s: &mut HurtState<Self::Source>,
sound: Self::Sound,
) -> Result<(), Self::Error>;
fn secondary_sound(&mut self, s: &mut HurtState<Self::Source>) -> Result<(), Self::Error>;
fn die(&mut self, s: &mut HurtState<Self::Source>) -> Result<(), Self::Error>;
fn hurt_sound(&mut self, s: &mut HurtState<Self::Source>) -> Result<(), Self::Error>;
fn game_time(&mut self, s: &mut HurtState<Self::Source>) -> Result<i64, Self::Error>;
fn effects_on_hurt(
&mut self,
s: &mut HurtState<Self::Source>,
damage: f32,
) -> Result<(), Self::Error>;
fn player_hurt_criterion(
&mut self,
s: &mut HurtState<Self::Source>,
original: f32,
damage: f32,
blocked: bool,
) -> Result<(), Self::Error>;
fn award_blocked(
&mut self,
s: &mut HurtState<Self::Source>,
amount: i32,
) -> Result<(), Self::Error>;
/// Looks up the attacker, then dispatches its criterion if a server player.
fn attacker_criterion(
&mut self,
s: &mut HurtState<Self::Source>,
original: f32,
damage: f32,
blocked: bool,
) -> Result<(), Self::Error>;
}
pub fn hurt_server<W: HurtWorld>(
s: &mut HurtState<W::Source>,
w: &mut W,
mut damage: f32,
) -> Result<bool, W::Error> {
if w.invulnerable(s)? || w.dead_or_dying(s)? {
return Ok(false);
}
if w.tag(s, HurtTag::Fire)? && w.fire_resistance(s)? {
return Ok(false);
}
if w.sleeping(s)? {
w.stop_sleeping(s)?;
}
s.no_action_time = 0;
if damage < 0.0 {
damage = 0.0;
}
let original = damage;
let item = w.use_item(s)?;
let blocked_amount = w.apply_blocking(s, damage)?;
damage -= blocked_amount;
let blocked = blocked_amount > 0.0;
if w.tag(s, HurtTag::Freezing)? && w.freeze_hurts_extra(s)? {
damage *= 5.0;
}
if w.tag(s, HurtTag::DamagesHelmet)? && !w.helmet_empty(s)? {
w.hurt_helmet(s, damage)?;
damage *= 0.75;
}
if !damage.is_finite() {
damage = f32::MAX;
}
let fresh = if s.cooldown as f32 > 10.0 && !w.tag(s, HurtTag::BypassesCooldown)? {
if damage <= s.last_hurt {
return Ok(false);
}
w.actually_hurt(s, damage - s.last_hurt)?;
s.last_hurt = damage;
false
} else {
s.last_hurt = damage;
s.cooldown = 20;
w.actually_hurt(s, damage)?;
s.hurt_duration = 10;
s.hurt_time = s.hurt_duration;
true
};
w.resolve_mob(s)?;
w.resolve_player(s)?;
if fresh {
let component = w.blocking_component(item);
if let Some(component) = component.filter(|_| blocked) {
w.on_blocked(s, component)?;
} else {
w.broadcast_damage(s)?;
}
if !w.tag(s, HurtTag::NoImpact)? && (!blocked || damage > 0.0) {
w.mark_hurt(s)?;
}
let fully_blocked = blocked && damage <= 0.0;
if !w.tag(s, HurtTag::NoKnockback)? && !fully_blocked {
w.knockback(s, damage, blocked)?;
}
}
if w.dead_or_dying(s)? {
if !w.death_protection(s)? {
if fresh {
let sound = w.death_sound(s)?;
w.make_sound(s, sound)?;
w.secondary_sound(s)?;
}
w.die(s)?;
}
} else if fresh {
w.hurt_sound(s)?;
w.secondary_sound(s)?;
}
let accepted = !blocked || damage > 0.0;
if accepted {
s.last_source = Some(w.source());
s.last_stamp = w.game_time(s)?;
w.effects_on_hurt(s, damage)?;
}
if w.is_server_player() {
w.player_hurt_criterion(s, original, damage, blocked)?;
if blocked_amount > 0.0 && blocked_amount < STAT_LIMIT {
w.award_blocked(s, stat_amount(blocked_amount))?;
}
}
w.attacker_criterion(s, original, damage, blocked)?;
Ok(accepted)
}
+19
View File
@@ -1332,3 +1332,22 @@ resistance, numeric edges, armor attribute mutation, dirty packet drains and
callback failures. Targeted Clippy passes. Empty equipment and controlled
source/effect, durability, combat/stat/event services bound this scope; outer
hurt gates, enchanted equipment and the complete player lifecycle remain pending.
`living_hurt` implements the outer living hurt sequence: early rejection,
blocking/freezing/helmet dispatch, non-finite damage sanitization, repeated-hit
cooldowns, hurt-animation writes, attribution, impact/knockback, death/sound
branches, last-source timestamps, effect notifications and criterion dispatch.
Services receive live hurt state, including the distinct field writes before and
after the inner hit in fresh-hit and cooldown-difference branches.
`python3 tools/test_living_hurt_parity.py` invokes the native LivingEntity method
directly on base and ServerPlayer receivers. The
[living hurt report](../benchmarks/rust-living-hurt-2026-10-07/scoped-parity.json)
records **441,413 matching lines per JAR**, including numeric edges, repeated
hits, callback mutations/failures, empty-hand death-protection lookup, native
soundless blocking components and victim criteria with no listeners. Targeted
Clippy passes. Inner damage, blocking amounts, sounds, knockback, death and
attribution/effect services are controlled. Successful totems, attacker-player
criteria, actual blocking reduction, full service bindings and player-specific
outer gates remain pending.
@@ -0,0 +1,86 @@
package dev.horizonlab.parity;
import com.google.gson.*;import java.io.*;import java.lang.reflect.*;import java.lang.invoke.*;import java.nio.file.*;import java.util.*;
import net.minecraft.core.*;import net.minecraft.core.component.*;import net.minecraft.core.registries.*;import net.minecraft.resources.*;import net.minecraft.tags.*;import net.minecraft.world.InteractionHand;import net.minecraft.world.entity.*;import net.minecraft.world.entity.player.*;import net.minecraft.world.damagesource.*;import net.minecraft.world.effect.*;import net.minecraft.world.item.*;import net.minecraft.world.item.component.*;import net.minecraft.server.level.*;import net.minecraft.server.PlayerAdvancements;import net.minecraft.sounds.*;import net.minecraft.stats.Stats;
/** Invoke the actual LivingEntity outer hurt method, retaining virtual service hooks. */
public final class VanillaLivingHurtOracle {
static final class Stop extends RuntimeException {}
static Object unsafe;static Method allocate;static ItemStack plain,shield;static Field stamp,lastSource,noAction,cooldown,lastHurt,duration,time;
static <T>T make(Class<T> c)throws Exception{return c.cast(allocate.invoke(unsafe,c));}
static Field field(Class<?> c,String n)throws Exception{var f=c.getDeclaredField(n);f.setAccessible(true);return f;}
static float single(JsonElement v){return Float.intBitsToFloat(Integer.parseUnsignedInt(v.getAsString(),16));}
static String bits(float v){return String.format("%08x",Float.floatToIntBits(v));}
static String state(LivingEntity a){try{return noAction.getInt(a)+" "+cooldown.getInt(a)+" "+bits(lastHurt.getFloat(a))+" "+duration.getInt(a)+" "+time.getInt(a)+" "+(lastSource.get(a)!=null)+" "+stamp.getLong(a);}catch(Exception e){throw new AssertionError(e);}}
static final class Data {JsonObject op;PrintWriter out;int calls;boolean hit;float recorded;Server level;PlayerAdvancements advancements;List<MobEffectInstance> effects;
boolean flag(String n){return op.get(n).getAsBoolean();}void event(String s){out.println(s);if(calls++==op.get("fail").getAsInt())throw new Stop();}}
static final class Source extends DamageSource {Data d;Source(){super(null);}
@Override public boolean is(TagKey<DamageType> tag){String n=tag==DamageTypeTags.IS_FIRE?"fire":tag==DamageTypeTags.IS_FREEZING?"freezing":tag==DamageTypeTags.DAMAGES_HELMET?"damages_helmet":tag==DamageTypeTags.BYPASSES_COOLDOWN?"bypasses_cooldown":tag==DamageTypeTags.NO_IMPACT?"no_impact":tag==DamageTypeTags.NO_KNOCKBACK?"no_knockback":tag==DamageTypeTags.BYPASSES_INVULNERABILITY?"bypasses_invulnerability":null;if(n==null)throw new AssertionError();d.event("tag "+n);return d.flag(n);}
@Override public boolean is(ResourceKey<DamageType> k){if(k!=DamageTypes.THORNS)throw new AssertionError();d.event("thorns");return false;}
@Override public Entity getEntity(){d.event("attacker");return null;}}
static final class Server extends ServerLevel {Data d;Server(){super(null,null,null,null,null,null,false,0L,List.of(),false);}@Override public void broadcastDamageEvent(Entity a,DamageSource s){d.event("broadcast");}@Override public long getGameTime(){d.event("game-time");return d.op.get("game_time").getAsLong();}}
static final class Effect extends MobEffectInstance {Data d;int id;Effect(){super(MobEffects.RESISTANCE);}@Override public void onMobHurt(ServerLevel l,LivingEntity a,DamageSource s,float v){d.event("effect "+id+" "+bits(v));}}
static final class Actor extends net.minecraft.world.entity.decoration.ArmorStand {Actor(){super(null,null);}
Data d;
@Override public boolean isInvulnerableTo(ServerLevel l,DamageSource s){d.event("invulnerable");return d.flag("invulnerable");}
@Override public boolean isDeadOrDying(){d.event("dead");return d.flag(d.hit?"dead_after":"dead_before");}
@Override public boolean hasEffect(Holder<MobEffect> e){if(e!=MobEffects.FIRE_RESISTANCE)throw new AssertionError();d.event("fire-resistance");return d.flag("fire_resistance");}
@Override public boolean isSleeping(){d.event("sleeping");return d.flag("sleeping");}
@Override public void stopSleeping(){d.event("wake");}
@Override public ItemStack getUseItem(){d.event("use-item");return d.flag("component")?shield:ItemStack.EMPTY;}
@Override public float applyItemBlocking(ServerLevel l,DamageSource s,float v){d.event("blocking "+bits(v));return single(d.op.get("blocked"));}
@Override public boolean is(TagKey<EntityType<?>> tag){if(tag!=EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)throw new AssertionError();d.event("freeze-extra");return d.flag("freeze_extra");}
@Override public ItemStack getItemBySlot(EquipmentSlot slot){if(slot!=EquipmentSlot.HEAD)throw new AssertionError();d.event("helmet");return d.flag("helmet")?plain:ItemStack.EMPTY;}
@Override protected void hurtHelmet(DamageSource s,float v){d.event("hurt-helmet "+bits(v));}
@Override protected void actuallyHurt(ServerLevel l,DamageSource s,float v){d.event("hurt "+bits(v)+" "+state(this));d.hit=true;d.recorded=v;if(d.flag("mutate_hit")){damageCooldownTime=d.op.get("hit_cooldown").getAsInt();lastHurt=single(d.op.get("hit_last"));hurtDuration=d.op.get("hit_duration").getAsInt();hurtTime=d.op.get("hit_time").getAsInt();}}
@Override protected void resolveMobResponsibleForDamage(DamageSource s){d.event("resolve-mob");}
@Override protected Player resolvePlayerResponsibleForDamage(DamageSource s){d.event("resolve-player");return null;}
@Override protected void markHurt(){d.event("mark");}
@Override public void dealDefaultKnockback(DamageSource s,float v,boolean b){d.event("knockback "+bits(v)+" "+b);}
@Override public ItemStack getItemInHand(InteractionHand h){d.event("hand "+h.ordinal());return ItemStack.EMPTY;}
@Override protected SoundEvent getDeathSound(){d.event("death-sound");return null;}
@Override public void makeSound(SoundEvent e){d.event("make-sound");}
@Override public void die(DamageSource s){d.event("die");}
@Override protected void playHurtSound(DamageSource s){d.event("hurt-sound");}
@Override public ServerLevel level(){d.event("level");return d.level;}
@Override public Collection<MobEffectInstance> getActiveEffects(){d.event("effects");return d.effects;}
}
static final class Human extends ServerPlayer {Human(){super(null,null,null,null);}
Data d;
@Override public boolean isInvulnerableTo(ServerLevel l,DamageSource s){d.event("invulnerable");return d.flag("invulnerable");}
@Override public boolean isDeadOrDying(){d.event("dead");return d.flag(d.hit?"dead_after":"dead_before");}
@Override public boolean hasEffect(Holder<MobEffect> e){if(e!=MobEffects.FIRE_RESISTANCE)throw new AssertionError();d.event("fire-resistance");return d.flag("fire_resistance");}
@Override public boolean isSleeping(){d.event("sleeping");return d.flag("sleeping");}
@Override public void stopSleeping(){d.event("wake");}
@Override public ItemStack getUseItem(){d.event("use-item");return d.flag("component")?shield:ItemStack.EMPTY;}
@Override public float applyItemBlocking(ServerLevel l,DamageSource s,float v){d.event("blocking "+bits(v));return single(d.op.get("blocked"));}
@Override public boolean is(TagKey<EntityType<?>> tag){if(tag!=EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)throw new AssertionError();d.event("freeze-extra");return d.flag("freeze_extra");}
@Override public ItemStack getItemBySlot(EquipmentSlot slot){if(slot!=EquipmentSlot.HEAD)throw new AssertionError();d.event("helmet");return d.flag("helmet")?plain:ItemStack.EMPTY;}
@Override protected void hurtHelmet(DamageSource s,float v){d.event("hurt-helmet "+bits(v));}
@Override protected void actuallyHurt(ServerLevel l,DamageSource s,float v){d.event("hurt "+bits(v)+" "+state(this));d.hit=true;d.recorded=v;if(d.flag("mutate_hit")){damageCooldownTime=d.op.get("hit_cooldown").getAsInt();lastHurt=single(d.op.get("hit_last"));hurtDuration=d.op.get("hit_duration").getAsInt();hurtTime=d.op.get("hit_time").getAsInt();}}
@Override protected void resolveMobResponsibleForDamage(DamageSource s){d.event("resolve-mob");}
@Override protected Player resolvePlayerResponsibleForDamage(DamageSource s){d.event("resolve-player");return null;}
@Override protected void markHurt(){d.event("mark");}
@Override public void dealDefaultKnockback(DamageSource s,float v,boolean b){d.event("knockback "+bits(v)+" "+b);}
@Override public ItemStack getItemInHand(InteractionHand h){d.event("hand "+h.ordinal());return ItemStack.EMPTY;}
@Override protected SoundEvent getDeathSound(){d.event("death-sound");return null;}
@Override public void makeSound(SoundEvent e){d.event("make-sound");}
@Override public void die(DamageSource s){d.event("die");}
@Override protected void playHurtSound(DamageSource s){d.event("hurt-sound");}
@Override public ServerLevel level(){d.event("level");return d.level;}
@Override public Collection<MobEffectInstance> getActiveEffects(){d.event("effects");return d.effects;}
@Override public PlayerAdvancements getAdvancements(){d.event("criterion");return d.advancements;}
@Override public void awardStat(Identifier s,int v){if(s!=Stats.DAMAGE_BLOCKED_BY_SHIELD)throw new AssertionError();d.event("blocked-stat "+v);}
}
public static void main(String[] args)throws Throwable {
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);
noAction=field(LivingEntity.class,"noActionTime");cooldown=field(LivingEntity.class,"damageCooldownTime");lastHurt=field(LivingEntity.class,"lastHurt");duration=field(LivingEntity.class,"hurtDuration");time=field(LivingEntity.class,"hurtTime");stamp=field(LivingEntity.class,"lastDamageStamp");lastSource=field(LivingEntity.class,"lastDamageSource");
var ctor=ItemStack.class.getDeclaredConstructor(Holder.class,int.class,PatchedDataComponentMap.class);ctor.setAccessible(true);plain=(ItemStack)ctor.newInstance(BuiltInRegistries.ITEM.wrapAsHolder(Items.STICK),1,new PatchedDataComponentMap(DataComponentMap.EMPTY));shield=(ItemStack)ctor.newInstance(BuiltInRegistries.ITEM.wrapAsHolder(Items.SHIELD),1,new PatchedDataComponentMap(DataComponentMap.EMPTY));shield.set(DataComponents.BLOCKS_ATTACKS,new BlocksAttacks(0,1,List.of(),new BlocksAttacks.ItemDamageFunction(0,0,0),Optional.empty(),Optional.empty(),Optional.empty()));
var lookup=MethodHandles.privateLookupIn(LivingEntity.class,MethodHandles.lookup());var hurt=lookup.findSpecial(LivingEntity.class,"hurtServer",MethodType.methodType(boolean.class,ServerLevel.class,DamageSource.class,float.class),LivingEntity.class);
Path dir=Path.of(args[1]);try(var out=new PrintWriter(Files.newBufferedWriter(dir.resolve(args[0]+".txt")));var lines=Files.lines(dir.resolve("inputs.txt"))){for(var line:(Iterable<String>)lines::iterator){var f=JsonParser.parseString(line).getAsJsonObject();var d=new Data();d.out=out;d.level=make(Server.class);d.level.d=d;d.advancements=make(PlayerAdvancements.class);field(PlayerAdvancements.class,"activeTriggers").set(d.advancements,new HashMap<>());LivingEntity actor;if(f.get("player").getAsBoolean()){var a=make(Human.class);a.d=d;actor=a;}else{var a=make(Actor.class);a.d=d;actor=a;}var source=make(Source.class);source.d=d;
noAction.setInt(actor,f.get("no_action").getAsInt());cooldown.setInt(actor,f.get("cooldown").getAsInt());lastHurt.setFloat(actor,single(f.get("last_hurt")));duration.setInt(actor,f.get("duration").getAsInt());time.setInt(actor,f.get("time").getAsInt());stamp.setLong(actor,f.get("stamp").getAsLong());if(f.get("source").getAsBoolean())lastSource.set(actor,source);
for(var ov:f.getAsJsonArray("ops")){d.op=ov.getAsJsonObject();d.calls=0;d.hit=false;d.effects=new ArrayList<>();for(int i=0;i<d.op.get("effects").getAsInt();i++){var e=make(Effect.class);e.d=d;e.id=i;d.effects.add(e);}try{boolean result=(boolean)hurt.invokeExact(actor,(ServerLevel)d.level,(DamageSource)source,single(d.op.get("damage")));out.println("bool "+result);out.println("result ok");}catch(Stop e){out.println("result error");}out.println("state "+state(actor)+" "+bits(d.recorded)+" "+d.hit+" "+d.calls);}
}}
}
}
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""Compare registered living hurt and synchronized absorption with vanilla."""
import argparse
import json
import os
from pathlib import Path
import platform
import subprocess
import random
import struct
import math
try:
from . import lab, test_rust_parity as base
except ImportError:
import lab
import test_rust_parity as base
def corpus():
def bits(v):return struct.pack('>f',v).hex()
def op(damage=8,**kw):
o=dict(damage=bits(damage),blocked=bits(0),fail=-1,game_time=123456789,hit_cooldown=3,hit_last=bits(99),hit_duration=2,hit_time=1,effects=0,**{k:False for k in ['invulnerable','dead_before','dead_after','fire','fire_resistance','sleeping','component','freezing','freeze_extra','damages_helmet','helmet','bypasses_cooldown','no_impact','no_knockback','bypasses_invulnerability','mutate_hit']});o.update(kw);return o
def row(ops,**kw):
f=dict(ops=ops,no_action=83,cooldown=0,last_hurt=bits(0),duration=7,time=9,stamp=-3,source=False,player=False);f.update(kw);return f
values=[-float('inf'),-1e30,-1,-0.,0.,1e-40,.1,1,5,10,20,1e30,float('inf'),float('nan')]
for v in values:
for block in values:
for cooldown in [-2147483648,0,10,11,2147483647]:
for player in [False,True]:
yield row([op(v,blocked=bits(block),effects=2),op(v,blocked=bits(block),component=True),op(v,blocked=bits(block),dead_after=True)],cooldown=cooldown,player=player,last_hurt=bits(v))
for fail in range(-1,38):
for cooldown in [0,10,11,20]:
for dead in [False,True]:
for component in [False,True]:
yield row([op(21,fail=fail,sleeping=True,freezing=True,freeze_extra=True,damages_helmet=True,helmet=True,blocked=bits(2),component=component,dead_after=dead,effects=3,mutate_hit=True),op(22)],player=True,cooldown=cooldown)
rng=random.Random(0x4855525453455256)
for _ in range(3000):
ops=[]
for __ in range(rng.randrange(1,20)):
o=op(rng.choice(values),blocked=bits(rng.choice(values)),effects=rng.randrange(4),game_time=rng.choice([-9223372036854775808,-1,0,9223372036854775807]),fail=rng.choice([-1,-1,-1,*range(38)]),hit_last=bits(rng.choice(values)),hit_cooldown=rng.randrange(-30,31))
for k in ['invulnerable','dead_before','dead_after','fire','fire_resistance','sleeping','component','freezing','freeze_extra','damages_helmet','helmet','bypasses_cooldown','no_impact','no_knockback','bypasses_invulnerability','mutate_hit']:o[k]=rng.randrange(4)==0
ops.append(o)
yield row(ops,cooldown=rng.choice([-2147483648,0,10,11,20,2147483647]),last_hurt=bits(rng.choice(values)),source=bool(rng.randrange(2)),player=bool(rng.randrange(2)))
def main():
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--prepare',action='store_true');args=parser.parse_args()
build=lab.LOCAL/'rust-living-hurt-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/VanillaLivingHurtOracle.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/living_hurt_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.VanillaLivingHurtOracle',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','living_hurt_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'/('living_hurt_fixture.exe' if os.name=='nt' else 'living_hurt_fixture')
run([binary,build],'rust');references=[]
for side,jar in jars.items():
java(side,side,build);reference=build/f'{side}.txt';matched=base.compare(reference,build/'rust.txt')
references.append({'side':side,'jar_sha256':lab.digest(jar,'sha256'),'output_sha256':lab.digest(reference,'sha256'),'matched_output_lines':matched})
print(f'{side}: {matched:,} living hurt output lines match Rust',flush=True)
base.ensure_sources_unchanged(sources,tested_sources);base.ensure_sources_unchanged(fixture_paths,tested_fixtures)
report={'schema':1,'minecraft':lock['minecraft']['version'],'manifest_sha1':lock['minecraft']['manifest_sha1'],'platform':platform.platform(),'java':subprocess.check_output(['java','--version'],text=True).splitlines()[0],'rustc':subprocess.check_output(['rustc','--version'],text=True).strip(),'fixtures':len(fixtures),'sources_sha256':tested_sources,'fixtures_sha256':tested_fixtures,'rust_output_sha256':lab.digest(build/'rust.txt','sha256'),'references':references,'scope':'Actual LivingEntity.hurtServer invoked directly on base and ServerPlayer receivers. Controlled blocking, inner damage, sounds, knockback, death, attribution and effect callbacks; native empty death-protection lookup and empty-listener victim criterion. Compares field write ordering, cooldown thresholds, float sanitization, modifiers, repeated hits and injected callback failure. Successful totems, attacker-player criteria, actual blocking reduction, combat/death services and full player outer gates remain pending.'}
lab.write_json(report_path,report);print(f'Scoped living hurt parity passed: {report_path}')
if __name__=='__main__':main()