Compose server-player hurt gates with live health and hunger storage

This commit is contained in:
Ludovic Marie committed 2026-10-07 05:36:49 +02:00
1 parent 04e57a5d72
commit 9d65e24286
8 files changed
+1309 -9

No files matched your search

@@ -0,0 +1,264 @@
{
"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": 7465,
"sources_sha256": {
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaServerPlayerCombatOracle.java": "f3a850b7c42ffa5b38a8a526290e4655da2c11220989acffd3a7808325fb8b7e",
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaEntityAttributeOracle.java": "2ad3190a9798ab54bd12e88573028d0ca6a204e2f8a92018aae98b22335213bf",
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaPlayerHealthOracle.java": "5d6dc93c0140e2b2f953fba7290721079d5a607fa81b051e2ac1e4742ab606f2",
"tools/test_server_player_combat_parity.py": "45c75e5f82049081a14af57106e24762ef664124635c8cccc53e66cf08539dde",
"tools/test_rust_parity.py": "29d3db08005873528894b45ca10dcd6278753650473b3eb574e53e06ede40ec6",
"tools/test_player_hurt_parity.py": "3164b8644664fcd46903e801ea70d1bb866e07e7112dc94570161831644bc84d",
"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_immunity.rs": "fd39c41124d694e755cf7faadcf93c636df4042c78ec345092a856b5f952dbcf",
"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": "06065c36a17442ea9e5369fd7eeada9b4c2f823863c0590d917b20693e97fdea",
"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_hurt.rs": "99c9bbd8587ec2dc287806049897b8034d2f5349ad47fc497b70d54148844a36",
"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": "b32663be1f83167735fd50cea9851b0e0e3c147e6a2be2b5ab40bf268ddbc01a",
"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/server_player_combat_fixture.rs": "47ee61368412fd1602ae4eb3441315f57bf846fff1fc1caea384a6db9ec70ddd"
},
"fixtures_sha256": {
".local/rust-server-player-combat-parity/inputs.txt": "f3b1edb606a64067457fdc1e9420f6c893c3928cad7f983cd0f7a92c1cdee541",
".local/rust-server-player-combat-parity/catalog.json": "d1e488e4f432888beb9f643904c8bca242d1e2093f53e23e07da305d70cf7106",
".local/rust-server-player-combat-parity/ids.json": "f7d6037166d966e2714b01cff5626f0cc0a02ce883885ec791c93e977afde0e6",
".local/rust-server-player-combat-parity/slots.json": "29101ebf1ead685029508789eee51c4d4fff65524064d8e32a48efded7036982",
".local/rust-server-player-combat-parity/head.json": "ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d"
},
"rust_output_sha256": "fe33d2fb5cd941b3c57138518bb349f06922ace7367b720bb6ee3ffaff7a0a0b",
"references": [
{
"side": "client",
"jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d",
"output_sha256": "fe33d2fb5cd941b3c57138518bb349f06922ace7367b720bb6ee3ffaff7a0a0b",
"matched_output_lines": 561528
},
{
"side": "server",
"jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6",
"output_sha256": "fe33d2fb5cd941b3c57138518bb349f06922ace7367b720bb6ee3ffaff7a0a0b",
"matched_output_lines": 561528
}
],
"scope": "Actual ServerPlayer outer hurt through Player/LivingEntity gates and actual Player inner damage, native armor/resistance reduction, registered modified attributes, synchronized health/absorption and FoodData exhaustion. Repeated hits, cooldown/state writes, death rejection, callbacks and numeric edges. Invulnerability predicate, blocking amount, durability, effects, PvP permission, attribution, knockback, death services and criterion listeners remain controlled; enchanted items, registered sources and complete entity/game lifecycle remain pending."
}
@@ -0,0 +1,522 @@
use horizon_world::living_hurt::{HurtState, HurtTag, HurtWorld};
use horizon_world::{
attributes::{
Modifier, Operation,
entity::{Catalog, EntityAttributes},
},
combat_rules::ArmorWorld,
damage_reduction::Bypass,
food::FoodData,
living_damage::DamageStat,
player_health::PlayerFloatIds,
server_player_combat::{CombatEnvironment, ServerPlayerCombat},
server_player_damage::ServerDamageEnvironment,
synced_floats::{FloatValue, Observer, SyncedFloats},
};
use horizon_world::{
food::Difficulty,
player_hurt::{CausingEntity, PlayerHurtWorld, ServerPlayerHurtWorld},
};
use serde_json::Value;
use std::sync::Arc;
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,
difficulty_calls: usize,
slots: &'a [Value],
head: u64,
dead: 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")?;
self.dead = true;
Ok(())
}
fn hurt_sound(&mut self, _: &mut HurtState<()>) -> io::Result<()> {
self.event("hurt-sound")
}
fn dead_or_dying(&mut self, _: &mut HurtState<()>) -> io::Result<bool> {
panic!("combat binding supplies stored health/death predicate")
}
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(&format!("slot {}", self.head))?;
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<()> {
panic!("combat binding supplies actual inner damage")
}
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")
}
}
impl PlayerHurtWorld for Env<'_> {
fn ability_invulnerable(&self) -> bool {
self.flag("ability_invulnerable")
}
fn bypasses_invulnerability(&mut self, _: &mut HurtState<()>) -> io::Result<bool> {
self.event("tag bypasses_invulnerability")?;
Ok(self.flag("bypasses_invulnerability"))
}
fn remove_shoulders(&mut self, _: &mut HurtState<()>) -> io::Result<()> {
self.event("shoulders")
}
fn scales_with_difficulty(&mut self, _: &mut HurtState<()>) -> io::Result<bool> {
self.event("scales")?;
Ok(self.flag("scales"))
}
fn difficulty(&mut self, _: &mut HurtState<()>) -> io::Result<Difficulty> {
self.event("difficulty")?;
let v = self.op["difficulties"][self.difficulty_calls]
.as_u64()
.unwrap();
self.difficulty_calls += 1;
Ok([
Difficulty::Peaceful,
Difficulty::Easy,
Difficulty::Normal,
Difficulty::Hard,
][v as usize])
}
}
impl ServerPlayerHurtWorld for Env<'_> {
type Player = ();
type Arrow = ();
fn causing_entity(&mut self, _: &mut HurtState<()>) -> io::Result<CausingEntity<(), ()>> {
self.event("attacker")?;
Ok(match self.op["attacker"].as_u64().unwrap() {
0 | 1 => CausingEntity::Other,
2 => CausingEntity::Player(()),
3 => CausingEntity::Arrow(()),
_ => panic!("attacker"),
})
}
fn arrow_player_owner(&mut self, _: &mut HurtState<()>, _: ()) -> io::Result<Option<()>> {
self.event("owner")?;
Ok((self.op["owner"] == 1).then_some(()))
}
fn can_harm_player(&mut self, _: &mut HurtState<()>, _: ()) -> io::Result<bool> {
self.event("can-harm")?;
Ok(self.flag("can_harm"))
}
}
fn number(v: &Value) -> f64 {
f64::from_bits(u64::from_str_radix(v.as_str().unwrap(), 16).unwrap())
}
fn values(v: Option<&[FloatValue]>) -> String {
match v {
None => "none".into(),
Some([]) => "empty".into(),
Some(v) => v
.iter()
.map(|v| format!("{}:{:08x}", v.id, bits(v.value)))
.collect::<Vec<_>>()
.join(","),
}
}
impl Observer for Env<'_> {
type Error = io::Error;
fn updated(&mut self, data: &mut SyncedFloats, id: u8) -> io::Result<()> {
self.event(&format!(
"updated {id} {:08x} {}",
bits(data.get(id).unwrap()),
data.is_dirty()
))?;
if self.flag("pack_callback") {
writeln!(
self.out,
"callback-pack {}",
values(data.take_dirty().as_deref())
)?;
}
Ok(())
}
}
impl ArmorWorld for Env<'_> {
type Error = io::Error;
type Weapon = ();
type ServerLevel = ();
fn weapon(&mut self) -> io::Result<Option<()>> {
self.event("weapon")?;
Ok(self.flag("weapon").then_some(()))
}
fn server_level(&mut self) -> io::Result<Option<()>> {
self.event("level")?;
Ok(Some(()))
}
fn modify_armor_effectiveness(&mut self, _: (), _: (), v: f32) -> io::Result<f32> {
Ok(v)
}
}
impl ServerDamageEnvironment for Env<'_> {
type Attacker = ();
fn invulnerable(&mut self) -> io::Result<bool> {
self.event("invulnerable")?;
Ok(self.flag("invulnerable"))
}
fn bypasses(&mut self, b: Bypass) -> io::Result<bool> {
let n = match b {
Bypass::Armor => "armor",
Bypass::Effects => "effects",
Bypass::Resistance => "resistance",
Bypass::Enchantments => "enchantments",
};
self.event(&format!("tag bypass_{n}"))?;
Ok(self.flag(&format!("bypass_{n}")))
}
fn hurt_armor(&mut self, a: &mut EntityAttributes, v: f32) -> io::Result<()> {
self.event(&format!("hurt-armor {:08x}", bits(v)))?;
if self.flag("break_armor") {
let id = a.catalog().id("minecraft:armor").unwrap();
a.edit(id, |a, o| {
a.set_base_value(number(&self.op["armor_after"]), o)
})
.unwrap()
.unwrap();
}
Ok(())
}
fn has_resistance(&mut self) -> io::Result<bool> {
self.event("has-resistance")?;
Ok(self.flag("resistance"))
}
fn resistance_amplifier(&mut self) -> io::Result<i32> {
self.event("effect")?;
Ok(self.op["amplifier"].as_i64().unwrap() as i32)
}
fn award_resisted(&mut self, v: i32) -> io::Result<()> {
self.event(&format!("stat resisted {v}"))
}
fn attacker(&mut self) -> io::Result<Option<()>> {
panic!("player damage must not query attacker")
}
fn award_dealt_resisted(&mut self, _: i32) -> io::Result<()> {
panic!("player damage must award own resisted stat")
}
fn damage_protection(&mut self, _: ()) -> io::Result<f32> {
for slot in self.slots {
self.event(&format!("slot {}", slot.as_u64().unwrap()))?;
}
Ok(0.0)
}
fn award_self(&mut self, s: DamageStat, v: i32) -> io::Result<()> {
let n = match s {
DamageStat::Absorbed => "absorbed",
DamageStat::Taken => "taken",
DamageStat::DealtAbsorbed => panic!("attacker stat"),
};
self.event(&format!("stat {n} {v}"))
}
fn award_attacker(&mut self, _: (), _: DamageStat, _: i32) -> io::Result<()> {
panic!("attacker stat")
}
fn source_food_exhaustion(&mut self) -> io::Result<f32> {
self.event("food-exhaustion")?;
Ok(single(&self.op["food_exhaustion"]))
}
fn ability_invulnerable(&self) -> bool {
self.flag("ability_invulnerable")
}
fn level_is_client(&mut self) -> io::Result<bool> {
self.event("level")?;
self.event("client")?;
Ok(self.flag("client"))
}
fn record_damage(&mut self, v: f32) -> io::Result<()> {
self.event("tracker")?;
self.event(&format!("record {:08x}", bits(v)))?;
self.recorded = v;
self.hit = true;
Ok(())
}
fn damage_event(&mut self) -> io::Result<()> {
self.event("damage-event")
}
}
impl CombatEnvironment for Env<'_> {
fn dead_flag(&self) -> bool {
self.dead
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let arg = std::env::args().nth(1).unwrap();
let dir = Path::new(&arg);
let ids: Value = serde_json::from_str(&fs::read_to_string(dir.join("ids.json"))?)?;
let ids = PlayerFloatIds {
health: ids["health"].as_u64().unwrap() as u8,
absorption: ids["absorption"].as_u64().unwrap() as u8,
};
let slots: Value = serde_json::from_str(&fs::read_to_string(dir.join("slots.json"))?)?;
let head: u64 = serde_json::from_str(&fs::read_to_string(dir.join("head.json"))?)?;
let catalog = Arc::new(Catalog::from_json(&serde_json::from_str(
&fs::read_to_string(dir.join("catalog.json"))?,
)?)?);
let keys = [
"minecraft:armor",
"minecraft:armor_toughness",
"minecraft:max_health",
"minecraft:max_absorption",
]
.map(|k| catalog.id(k).unwrap());
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 attributes = catalog.create("minecraft:player").unwrap();
for (i, key) in keys.iter().enumerate() {
let a = &f["attributes"][i];
let m = Modifier::new(
"horizon:test",
number(&a["amount"]),
Operation::ALL[a["operation"].as_u64().unwrap() as usize],
)?;
attributes
.edit(*key, |a, o| {
a.set_base_value(number(&f["attributes"][i]["base"]), o)?;
a.add_transient(m, o)
})
.unwrap()
.unwrap();
}
let mut data = SyncedFloats::new(&[
FloatValue {
id: ids.health,
value: single(&f["health"]),
},
FloatValue {
id: ids.absorption,
value: single(&f["absorption"]),
},
])?;
let mut food = FoodData {
exhaustion: single(&f["exhaustion"]),
..FoodData::default()
};
let mut recorded = 0.0;
let mut dead = f["dead"].as_bool().unwrap();
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(),
};
for op in f["ops"].as_array().unwrap() {
if op["reset_cooldown"].as_bool().unwrap_or(false) {
s.cooldown = 0;
}
let mut e = Env {
out: &mut out,
op,
calls: 0,
hit: false,
recorded,
player: true,
difficulty_calls: 0,
slots: slots.as_array().unwrap(),
head,
dead,
};
let result =
ServerPlayerCombat::new(&mut attributes, &mut data, &mut food, ids, &mut e)?
.hurt(&mut s, single(&op["damage"]));
match result {
Ok(v) => writeln!(e.out, "bool {v}\nresult ok")?,
Err(_) => writeln!(e.out, "result error")?,
};
recorded = e.recorded;
dead = e.dead;
writeln!(
e.out,
"state {} {:08x} {} {}",
state(&s),
bits(recorded),
e.hit,
e.calls
)?;
writeln!(
e.out,
"storage {:08x} {:08x} {:08x} {} {} {} {}",
bits(data.get(ids.health).unwrap()),
bits(data.get(ids.absorption).unwrap()),
bits(food.exhaustion),
data.is_dirty(),
data.item_dirty(ids.health).unwrap(),
data.item_dirty(ids.absorption).unwrap(),
dead
)?;
if e.flag("pack") {
writeln!(e.out, "packed {}", values(data.take_dirty().as_deref()))?;
}
writeln!(e.out, "defaults {}", values(data.non_default().as_deref()))?;
}
}
Ok(())
}
+1
View File
@@ -68,6 +68,7 @@ pub mod registry_collision_scan;
pub mod registry_fall_ray;
pub mod registry_fluid;
pub mod retrogen;
pub mod server_player_combat;
pub mod server_player_damage;
pub mod suffocation;
pub mod swimming;
@@ -0,0 +1,301 @@
//! Server-player outer hurt composed with registered attributes and player storage.
use crate::{
attributes::entity::EntityAttributes,
combat_rules::ArmorWorld,
food::{Difficulty, FoodData},
living_hurt::{HurtState, HurtTag, HurtWorld},
player_health::PlayerFloatIds,
player_hurt::{
CausingEntity, PlayerHurtWorld, ServerPlayerHurtWorld, server_player_hurt_server,
},
server_player_damage::{ServerDamageEnvironment, ServerDamageError, ServerPlayerDamage},
synced_floats::{DataError, SyncedFloats},
};
use std::io;
type Error<E> = <E as ArmorWorld>::Error;
pub trait CombatEnvironment:
ServerDamageEnvironment + ServerPlayerHurtWorld<Error = <Self as ArmorWorld>::Error>
{
/// Direct death flag, read only after health fails the native <= zero test.
fn dead_flag(&self) -> bool;
}
pub struct ServerPlayerCombat<'a, E> {
attributes: &'a mut EntityAttributes,
data: &'a mut SyncedFloats,
food: &'a mut FoodData,
ids: PlayerFloatIds,
environment: &'a mut E,
}
impl<'a, E: CombatEnvironment> ServerPlayerCombat<'a, E> {
pub fn new(
attributes: &'a mut EntityAttributes,
data: &'a mut SyncedFloats,
food: &'a mut FoodData,
ids: PlayerFloatIds,
environment: &'a mut E,
) -> io::Result<Self> {
ServerPlayerDamage::new(attributes, data, food, ids, environment)?;
Ok(Self {
attributes,
data,
food,
ids,
environment,
})
}
pub fn hurt(
&mut self,
state: &mut HurtState<E::Source>,
damage: f32,
) -> Result<bool, ServerDamageError<Error<E>>> {
server_player_hurt_server(state, self, damage)
}
}
impl<E: CombatEnvironment> HurtWorld for ServerPlayerCombat<'_, E> {
type Error = ServerDamageError<Error<E>>;
type Source = E::Source;
type UseItem = E::UseItem;
type Blocking = E::Blocking;
type Sound = E::Sound;
fn source(&self) -> Self::Source {
self.environment.source()
}
fn is_server_player(&self) -> bool {
true
}
fn dead_or_dying(&mut self, _: &mut HurtState<Self::Source>) -> Result<bool, Self::Error> {
let health = self
.data
.get(self.ids.health)
.map_err(|id| ServerDamageError::Data(DataError::UnknownId(id)))?;
Ok(health <= 0.0 || self.environment.dead_flag())
}
fn actually_hurt(
&mut self,
_: &mut HurtState<Self::Source>,
damage: f32,
) -> Result<(), Self::Error> {
ServerPlayerDamage::new(
self.attributes,
self.data,
self.food,
self.ids,
self.environment,
)
.expect("validated immutable player schema")
.apply(damage)
}
fn blocking_component(&mut self, item: Self::UseItem) -> Option<Self::Blocking> {
self.environment.blocking_component(item)
}
fn invulnerable(&mut self, state: &mut HurtState<Self::Source>) -> Result<bool, Self::Error> {
HurtWorld::invulnerable(self.environment, state).map_err(ServerDamageError::Environment)
}
fn tag(
&mut self,
state: &mut HurtState<Self::Source>,
tag: HurtTag,
) -> Result<bool, Self::Error> {
HurtWorld::tag(self.environment, state, tag).map_err(ServerDamageError::Environment)
}
fn fire_resistance(
&mut self,
state: &mut HurtState<Self::Source>,
) -> Result<bool, Self::Error> {
HurtWorld::fire_resistance(self.environment, state).map_err(ServerDamageError::Environment)
}
fn sleeping(&mut self, state: &mut HurtState<Self::Source>) -> Result<bool, Self::Error> {
HurtWorld::sleeping(self.environment, state).map_err(ServerDamageError::Environment)
}
fn stop_sleeping(&mut self, state: &mut HurtState<Self::Source>) -> Result<(), Self::Error> {
HurtWorld::stop_sleeping(self.environment, state).map_err(ServerDamageError::Environment)
}
fn use_item(
&mut self,
state: &mut HurtState<Self::Source>,
) -> Result<Self::UseItem, Self::Error> {
HurtWorld::use_item(self.environment, state).map_err(ServerDamageError::Environment)
}
fn apply_blocking(
&mut self,
state: &mut HurtState<Self::Source>,
damage: f32,
) -> Result<f32, Self::Error> {
HurtWorld::apply_blocking(self.environment, state, damage)
.map_err(ServerDamageError::Environment)
}
fn freeze_hurts_extra(
&mut self,
state: &mut HurtState<Self::Source>,
) -> Result<bool, Self::Error> {
HurtWorld::freeze_hurts_extra(self.environment, state)
.map_err(ServerDamageError::Environment)
}
fn helmet_empty(&mut self, state: &mut HurtState<Self::Source>) -> Result<bool, Self::Error> {
HurtWorld::helmet_empty(self.environment, state).map_err(ServerDamageError::Environment)
}
fn hurt_helmet(
&mut self,
state: &mut HurtState<Self::Source>,
damage: f32,
) -> Result<(), Self::Error> {
HurtWorld::hurt_helmet(self.environment, state, damage)
.map_err(ServerDamageError::Environment)
}
fn resolve_mob(&mut self, state: &mut HurtState<Self::Source>) -> Result<(), Self::Error> {
HurtWorld::resolve_mob(self.environment, state).map_err(ServerDamageError::Environment)
}
fn resolve_player(&mut self, state: &mut HurtState<Self::Source>) -> Result<(), Self::Error> {
HurtWorld::resolve_player(self.environment, state).map_err(ServerDamageError::Environment)
}
fn on_blocked(
&mut self,
state: &mut HurtState<Self::Source>,
block: Self::Blocking,
) -> Result<(), Self::Error> {
HurtWorld::on_blocked(self.environment, state, block)
.map_err(ServerDamageError::Environment)
}
fn broadcast_damage(&mut self, state: &mut HurtState<Self::Source>) -> Result<(), Self::Error> {
HurtWorld::broadcast_damage(self.environment, state).map_err(ServerDamageError::Environment)
}
fn mark_hurt(&mut self, state: &mut HurtState<Self::Source>) -> Result<(), Self::Error> {
HurtWorld::mark_hurt(self.environment, state).map_err(ServerDamageError::Environment)
}
fn knockback(
&mut self,
state: &mut HurtState<Self::Source>,
damage: f32,
blocked: bool,
) -> Result<(), Self::Error> {
HurtWorld::knockback(self.environment, state, damage, blocked)
.map_err(ServerDamageError::Environment)
}
fn death_protection(
&mut self,
state: &mut HurtState<Self::Source>,
) -> Result<bool, Self::Error> {
HurtWorld::death_protection(self.environment, state).map_err(ServerDamageError::Environment)
}
fn death_sound(
&mut self,
state: &mut HurtState<Self::Source>,
) -> Result<Self::Sound, Self::Error> {
HurtWorld::death_sound(self.environment, state).map_err(ServerDamageError::Environment)
}
fn make_sound(
&mut self,
state: &mut HurtState<Self::Source>,
sound: Self::Sound,
) -> Result<(), Self::Error> {
HurtWorld::make_sound(self.environment, state, sound)
.map_err(ServerDamageError::Environment)
}
fn secondary_sound(&mut self, state: &mut HurtState<Self::Source>) -> Result<(), Self::Error> {
HurtWorld::secondary_sound(self.environment, state).map_err(ServerDamageError::Environment)
}
fn die(&mut self, state: &mut HurtState<Self::Source>) -> Result<(), Self::Error> {
HurtWorld::die(self.environment, state).map_err(ServerDamageError::Environment)
}
fn hurt_sound(&mut self, state: &mut HurtState<Self::Source>) -> Result<(), Self::Error> {
HurtWorld::hurt_sound(self.environment, state).map_err(ServerDamageError::Environment)
}
fn game_time(&mut self, state: &mut HurtState<Self::Source>) -> Result<i64, Self::Error> {
HurtWorld::game_time(self.environment, state).map_err(ServerDamageError::Environment)
}
fn effects_on_hurt(
&mut self,
state: &mut HurtState<Self::Source>,
damage: f32,
) -> Result<(), Self::Error> {
HurtWorld::effects_on_hurt(self.environment, state, damage)
.map_err(ServerDamageError::Environment)
}
fn player_hurt_criterion(
&mut self,
state: &mut HurtState<Self::Source>,
original: f32,
damage: f32,
blocked: bool,
) -> Result<(), Self::Error> {
HurtWorld::player_hurt_criterion(self.environment, state, original, damage, blocked)
.map_err(ServerDamageError::Environment)
}
fn award_blocked(
&mut self,
state: &mut HurtState<Self::Source>,
amount: i32,
) -> Result<(), Self::Error> {
HurtWorld::award_blocked(self.environment, state, amount)
.map_err(ServerDamageError::Environment)
}
fn attacker_criterion(
&mut self,
state: &mut HurtState<Self::Source>,
original: f32,
damage: f32,
blocked: bool,
) -> Result<(), Self::Error> {
HurtWorld::attacker_criterion(self.environment, state, original, damage, blocked)
.map_err(ServerDamageError::Environment)
}
}
impl<E: CombatEnvironment> PlayerHurtWorld for ServerPlayerCombat<'_, E> {
fn ability_invulnerable(&self) -> bool {
ServerDamageEnvironment::ability_invulnerable(self.environment)
}
fn bypasses_invulnerability(
&mut self,
state: &mut HurtState<Self::Source>,
) -> Result<bool, Self::Error> {
PlayerHurtWorld::bypasses_invulnerability(self.environment, state)
.map_err(ServerDamageError::Environment)
}
fn remove_shoulders(&mut self, state: &mut HurtState<Self::Source>) -> Result<(), Self::Error> {
PlayerHurtWorld::remove_shoulders(self.environment, state)
.map_err(ServerDamageError::Environment)
}
fn scales_with_difficulty(
&mut self,
state: &mut HurtState<Self::Source>,
) -> Result<bool, Self::Error> {
PlayerHurtWorld::scales_with_difficulty(self.environment, state)
.map_err(ServerDamageError::Environment)
}
fn difficulty(
&mut self,
state: &mut HurtState<Self::Source>,
) -> Result<Difficulty, Self::Error> {
PlayerHurtWorld::difficulty(self.environment, state).map_err(ServerDamageError::Environment)
}
}
impl<E: CombatEnvironment> ServerPlayerHurtWorld for ServerPlayerCombat<'_, E> {
type Player = E::Player;
type Arrow = E::Arrow;
fn causing_entity(
&mut self,
state: &mut HurtState<Self::Source>,
) -> Result<CausingEntity<Self::Player, Self::Arrow>, Self::Error> {
self.environment
.causing_entity(state)
.map_err(ServerDamageError::Environment)
}
fn arrow_player_owner(
&mut self,
state: &mut HurtState<Self::Source>,
arrow: Self::Arrow,
) -> Result<Option<Self::Player>, Self::Error> {
self.environment
.arrow_player_owner(state, arrow)
.map_err(ServerDamageError::Environment)
}
fn can_harm_player(
&mut self,
state: &mut HurtState<Self::Source>,
player: Self::Player,
) -> Result<bool, Self::Error> {
self.environment
.can_harm_player(state, player)
.map_err(ServerDamageError::Environment)
}
}
+19
View File
@@ -1383,3 +1383,22 @@ precedence, virtual flag mutations and callback failures. Targeted Clippy passes
Source/rule queries and virtual flags are controlled; registered source/rule
bindings, nonempty enchantment immunity and full pipeline integration remain
pending.
`server_player_combat` binds server/player/living outer hurt to the checked
registered-attribute, synchronized-health/absorption and hunger inner damage
path. Dead-or-dying reads live health and the direct death flag; the outer
cooldown/animation state is shared across hits. PvP, blocking, effects,
attribution, knockback, death and other world services remain explicit.
`python3 tools/test_server_player_combat_parity.py` compares the actual native
ServerPlayer hurt chain through actual inner reduction and player storage. The
[server-player combat report](../benchmarks/rust-server-player-combat-2026-10-07/scoped-parity.json)
records **561,528 matching lines per JAR**, including repeated hits, death
rejection, modified attributes, cooldown differences, packet drains, numeric
edges and failures after partial writes. Some fixtures explicitly reset cooldown
between hits; they do not claim entity-tick coverage. Targeted Clippy passes.
Invulnerability answers, blocking amounts, armor-durability callbacks, effects,
PvP permission, combat/event/knockback/death services and criterion listeners are
controlled. Enchanted items, registered sources, actual lifecycle services and
playable integration remain pending.
+13 -9
View File
@@ -9,7 +9,10 @@ complete game server or gameplay simulation.
The Rust workspace implements core arithmetic/random streams, world data and
resident sections, scheduled tick ordering, geometry, contained fluid states,
noise/density evaluation, biome and terrain generation kernels, material rules,
and parts of feature placement and decoration. All pinned block-survival
and parts of feature placement and decoration. Movement/fluid interactions,
registered attributes, hunger storage/ticks, synchronized player health and
absorption, damage reduction and outer hurt gates have scoped native comparisons.
These kernels still need complete entity/world lifecycle bindings. All pinned block-survival
families now have native differential coverage. Full simulation callbacks,
complete chunk generation, entity behavior, networking, rendering and playable
integration remain unfinished. The linked subsystem documents and hashed reports
@@ -320,21 +323,22 @@ encoding plus malformed-property fallback. Block gameplay remains pending.
| Registries, block states, items and data components | Block-state IDs, property descriptors and defaults imported and checked; items/components absent | Registry lifecycle, remaining registries, data components and behavior |
| World/chunk storage, NBT and region files | Bounded NBT, four codecs, region export, saved and resident section palettes | Generated-save integration, entity/POI integration, upgrades, live saves and crash recovery |
| Terrain, biomes, structures and lighting | Seeded noise/density graphs, bundled registry and router loading, climate sampling/search and imported-table multi-noise biome resolution, aquifer decisions, noise-stage filling and surface-stage application implemented; complete terrain generation absent | Remaining terrain/biome codecs and world fields, seeded generated-chunk comparisons, biome/feature/structure placement, chunk lifecycle and light propagation |
| Tick scheduling, blocks, fluids and redstone | Scheduled queues, eligibility, callback mutations and saved tick data implemented; block/fluid/redstone behavior absent | Actual block/fluid state transitions, neighbor updates, world integration and end-to-end ordered traces |
| Entities, AI, movement and combat | Bounding-box and voxel collision geometry implemented; live entities absent | Context-dependent collision/physics and tick traces, spawning, pathfinding, damage, equipment and lifecycle |
| Survival, inventory, crafting and progression | Not implemented | Recipes, item transitions, hunger/health, effects, experience and advancement behavior |
| Tick scheduling, blocks, fluids and redstone | Scheduled queues and saved ticks, block survival families, fluid geometry/flow and entity immersion kernels checked; complete live block/fluid/redstone simulation absent | Actual block/fluid state transitions, neighbor updates, world integration and end-to-end ordered traces |
| Entities, AI, movement and combat | Collision, movement/fluid kernels, registered attributes, damage reduction, hurt gates and immunity predicates checked; complete live entity simulation absent | Context-dependent collision/physics and tick traces, spawning, pathfinding, damage, equipment and lifecycle |
| Survival, inventory, crafting and progression | Hunger ticks/storage and synchronized health/absorption implemented and checked; inventory/crafting/progression absent | Recipes, item transitions, hunger/health, effects, experience and advancement behavior |
| Dimensions, portals, weather and game rules | Not implemented | Cross-dimension state, transitions, time/weather and rule-dependent behavior |
| Multiplayer, protocol, authentication and commands | Not implemented | Packet/codec fixtures, vanilla interoperability, reconnects and command behavior |
| Rendering, resources, sound, UI and input | Not implemented | Resource compatibility, render/culling correctness, controls and playable interaction tests |
| Performance and platform support | Not measured for a Rust game | Repeated matched gameplay workloads, frame/tick distributions, memory and physical GPU tests |
| Rendering, resources, sound, UI and input | Native inspection client with selected block models, neighbor visibility, greedy meshing and a free camera; gameplay rendering/audio/UI incomplete | Resource compatibility, render/culling correctness, controls and playable interaction tests |
| Performance and platform support | Registered-attribute allocation reduction measured on Linux; whole-game performance and playable platform packages pending | Repeated matched gameplay workloads, frame/tick distributions, memory and physical GPU tests |
The next simulation milestone is integrating the checked scheduler and
registry-backed sections with actual block behavior and neighbor updates. World
generation now has noise-stage filling, aquifers and surface-stage application;
biome assignment and remaining generation stages need work,
followed by complete seeded-world comparisons. A renderer needs
actual block models, neighboring sections and light data. Simplified geometry
alone cannot establish vanilla behavior. Performance tuning must preserve the
followed by complete seeded-world comparisons. The inspection renderer needs
complete material/lighting support, chunk streaming and a simulated world.
Movement, health and combat kernels need shared live player/entity state and
world services. Scoped comparisons alone cannot establish complete gameplay parity. Performance tuning must preserve the
validated simulation and use measured workloads; changing render distance,
simulation distance or terrain detail invalidates an equal-workload speed claim.
@@ -0,0 +1,107 @@
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.network.syncher.*;import net.minecraft.world.entity.ai.attributes.*;import net.minecraft.world.food.FoodData;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;
/** Native ServerPlayer hurt through actual reduction, attributes, health/absorption and hunger. */
public final class VanillaServerPlayerCombatOracle {
static final class Stop extends RuntimeException {}
static EntityDataAccessor<Float> healthKey,absorptionKey;static Field amplifier;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 double number(JsonElement v){return Double.longBitsToDouble(Long.parseUnsignedLong(v.getAsString(),16));}
static String values(List<SynchedEntityData.DataValue<?>> v){return VanillaPlayerHealthOracle.values(v);}
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;AttributeMap attributes;SynchedEntityData data;Tracker tracker;int calls,difficultyCalls;boolean hit;Abilities abilities;PlainPlayer attacker;Arrow arrow;Actor other;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.BYPASSES_ARMOR?"bypass_armor":tag==DamageTypeTags.BYPASSES_EFFECTS?"bypass_effects":tag==DamageTypeTags.BYPASSES_RESISTANCE?"bypass_resistance":tag==DamageTypeTags.BYPASSES_ENCHANTMENTS?"bypass_enchantments":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 switch(d.op.get("attacker").getAsInt()){case 0->null;case 1->d.other;case 2->d.attacker;case 3->d.arrow;default->throw new AssertionError();};}
@Override public ItemStack getWeaponItem(){d.event("weapon");return d.flag("weapon")?ItemStack.EMPTY:null;}@Override public float getFoodExhaustion(){d.event("food-exhaustion");return single(d.op.get("food_exhaustion"));}
@Override public boolean scalesWithDifficulty(){d.event("scales");return d.flag("scales");}}
static final class PlainPlayer extends Player {PlainPlayer(){super(null,null);}@Override public net.minecraft.world.level.GameType gameMode(){return net.minecraft.world.level.GameType.SURVIVAL;}}
static final class Arrow extends net.minecraft.world.entity.projectile.arrow.Arrow {Data d;Arrow(){super(null,null);}@Override public Entity getOwner(){d.event("owner");return switch(d.op.get("owner").getAsInt()){case 0->null;case 1->d.attacker;case 2->d.other;default->throw new AssertionError();};}}
static final class Tracker extends CombatTracker {Data d;Tracker(){super(null);}@Override public void recordDamage(DamageSource s,float v){d.event("record "+bits(v));d.recorded=v;d.hit=true;}}
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 boolean isClientSide(){d.event("client");return d.flag("client");}@Override public net.minecraft.world.Difficulty getDifficulty(){d.event("difficulty");return net.minecraft.world.Difficulty.values()[d.op.getAsJsonArray("difficulties").get(d.difficultyCalls++).getAsInt()];}@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 hasEffect(Holder<MobEffect> e){if(e==MobEffects.FIRE_RESISTANCE){d.event("fire-resistance");return d.flag("fire_resistance");}if(e!=MobEffects.RESISTANCE)throw new AssertionError();d.event("has-resistance");return d.flag("resistance");}
@Override public MobEffectInstance getEffect(Holder<MobEffect> e){if(e!=MobEffects.RESISTANCE)throw new AssertionError();d.event("effect");var v=new MobEffectInstance(e,100,0);try{amplifier.setInt(v,d.op.get("amplifier").getAsInt());}catch(Exception ex){throw new AssertionError(ex);}return v;}
@Override public double getAttributeValue(Holder<Attribute> a){return d.attributes.getValue(a);}
@Override protected void hurtArmor(DamageSource s,float v){d.event("hurt-armor "+bits(v));if(d.flag("break_armor"))d.attributes.getInstance(Attributes.ARMOR).setBaseValue(number(d.op.get("armor_after")));}
@Override public CombatTracker getCombatTracker(){d.event("tracker");return d.tracker;}
@Override public void gameEvent(Holder<net.minecraft.world.level.gameevent.GameEvent> e){if(e!=net.minecraft.world.level.gameevent.GameEvent.ENTITY_DAMAGE)throw new AssertionError();d.event("damage-event");}
@Override public void onSyncedDataUpdated(EntityDataAccessor<?> a){var k=a.id()==healthKey.id()?healthKey:absorptionKey;d.event("updated "+k.id()+" "+bits(d.data.get(k))+" "+d.data.isDirty());if(d.flag("pack_callback"))d.out.println("callback-pack "+values(d.data.packDirty()));}
@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){d.event("slot "+slot.ordinal());return slot==EquipmentSlot.HEAD&&d.flag("helmet")?plain:ItemStack.EMPTY;}
@Override protected void hurtHelmet(DamageSource s,float v){d.event("hurt-helmet "+bits(v));}
@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");dead=true;}
@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 hasEffect(Holder<MobEffect> e){if(e==MobEffects.FIRE_RESISTANCE){d.event("fire-resistance");return d.flag("fire_resistance");}if(e!=MobEffects.RESISTANCE)throw new AssertionError();d.event("has-resistance");return d.flag("resistance");}
@Override public MobEffectInstance getEffect(Holder<MobEffect> e){if(e!=MobEffects.RESISTANCE)throw new AssertionError();d.event("effect");var v=new MobEffectInstance(e,100,0);try{amplifier.setInt(v,d.op.get("amplifier").getAsInt());}catch(Exception ex){throw new AssertionError(ex);}return v;}
@Override public double getAttributeValue(Holder<Attribute> a){return d.attributes.getValue(a);}
@Override protected void hurtArmor(DamageSource s,float v){d.event("hurt-armor "+bits(v));if(d.flag("break_armor"))d.attributes.getInstance(Attributes.ARMOR).setBaseValue(number(d.op.get("armor_after")));}
@Override public CombatTracker getCombatTracker(){d.event("tracker");return d.tracker;}
@Override public void gameEvent(Holder<net.minecraft.world.level.gameevent.GameEvent> e){if(e!=net.minecraft.world.level.gameevent.GameEvent.ENTITY_DAMAGE)throw new AssertionError();d.event("damage-event");}
@Override public void onSyncedDataUpdated(EntityDataAccessor<?> a){var k=a.id()==healthKey.id()?healthKey:absorptionKey;d.event("updated "+k.id()+" "+bits(d.data.get(k))+" "+d.data.isDirty());if(d.flag("pack_callback"))d.out.println("callback-pack "+values(d.data.packDirty()));}
@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){d.event("slot "+slot.ordinal());return slot==EquipmentSlot.HEAD&&d.flag("helmet")?plain:ItemStack.EMPTY;}
@Override protected void hurtHelmet(DamageSource s,float v){d.event("hurt-helmet "+bits(v));}
@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");dead=true;}
@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 protected void removeEntitiesOnShoulder(){d.event("shoulders");}@Override public boolean canHarmPlayer(Player p){d.event("can-harm");return d.flag("can_harm");}@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){d.event("blocked-stat "+v);return;}String n=s==Stats.DAMAGE_RESISTED?"resisted":s==Stats.DAMAGE_ABSORBED?"absorbed":s==Stats.DAMAGE_TAKEN?"taken":null;if(n==null)throw new AssertionError();d.event("stat "+n+" "+v);}
}
@SuppressWarnings("unchecked") public static void main(String[] args)throws Throwable {
Path dir=Path.of(args[1]);if(args[0].equals("export"))VanillaPlayerHealthOracle.main(args);else{net.minecraft.SharedConstants.tryDetectVersion();net.minecraft.server.Bootstrap.bootStrap();}
healthKey=(EntityDataAccessor<Float>)field(LivingEntity.class,"DATA_HEALTH_ID").get(null);absorptionKey=(EntityDataAccessor<Float>)field(Player.class,"DATA_PLAYER_ABSORPTION_ID").get(null);if(args[0].equals("export")){var slots=new JsonArray();for(var s:EquipmentSlot.VALUES)slots.add(s.ordinal());Files.writeString(dir.resolve("slots.json"),slots.toString());Files.writeString(dir.resolve("head.json"),Integer.toString(EquipmentSlot.HEAD.ordinal()));return;}
var dataCtor=SynchedEntityData.class.getDeclaredConstructor(SyncedDataHolder.class,SynchedEntityData.DataItem[].class);dataCtor.setAccessible(true);amplifier=field(MobEffectInstance.class,"amplifier");var exhaustion=field(FoodData.class,"exhaustionLevel");var keys=List.of(Attributes.ARMOR,Attributes.ARMOR_TOUGHNESS,Attributes.MAX_HEALTH,Attributes.MAX_ABSORPTION);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(Player.class,MethodHandles.lookup());var playerHurt=lookup.findSpecial(Player.class,"hurtServer",MethodType.methodType(boolean.class,ServerLevel.class,DamageSource.class,float.class),Player.class).asType(MethodType.methodType(boolean.class,LivingEntity.class,ServerLevel.class,DamageSource.class,float.class));
var serverLookup=MethodHandles.privateLookupIn(ServerPlayer.class,MethodHandles.lookup());var serverHurt=serverLookup.findSpecial(ServerPlayer.class,"hurtServer",MethodType.methodType(boolean.class,ServerLevel.class,DamageSource.class,float.class),ServerPlayer.class).asType(MethodType.methodType(boolean.class,LivingEntity.class,ServerLevel.class,DamageSource.class,float.class));
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<>());var human=make(Human.class);human.d=d;LivingEntity actor=human;d.abilities=new Abilities();field(Player.class,"abilities").set(actor,d.abilities);d.attacker=make(PlainPlayer.class);d.other=make(Actor.class);d.other.d=d;d.arrow=make(Arrow.class);d.arrow.d=d;var hurt=serverHurt;var source=make(Source.class);source.d=d;
d.attributes=new AttributeMap(VanillaEntityAttributeOracle.supplier(BuiltInRegistries.ENTITY_TYPE.getValue(Identifier.parse("minecraft:player"))));for(int i=0;i<keys.size();i++){var a=f.getAsJsonArray("attributes").get(i).getAsJsonObject();var instance=d.attributes.getInstance(keys.get(i));instance.setBaseValue(number(a.get("base")));instance.addTransientModifier(new AttributeModifier(Identifier.parse("horizon:test"),number(a.get("amount")),AttributeModifier.Operation.values()[a.get("operation").getAsInt()]));}
var items=new SynchedEntityData.DataItem<?>[Math.max(healthKey.id(),absorptionKey.id())+1];for(int i=0;i<items.length;i++)items[i]=new SynchedEntityData.DataItem<Float>(new EntityDataAccessor<Float>(i,EntityDataSerializers.FLOAT),0f);var health=new SynchedEntityData.DataItem<Float>(healthKey,single(f.get("health")));var absorption=new SynchedEntityData.DataItem<Float>(absorptionKey,single(f.get("absorption")));items[healthKey.id()]=health;items[absorptionKey.id()]=absorption;d.data=dataCtor.newInstance(actor,items);field(Entity.class,"entityData").set(actor,d.data);field(LivingEntity.class,"dead").setBoolean(actor,f.get("dead").getAsBoolean());var food=new FoodData();exhaustion.setFloat(food,single(f.get("exhaustion")));field(Player.class,"foodData").set(actor,food);d.tracker=make(Tracker.class);d.tracker.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();if(d.op.has("reset_cooldown")&&d.flag("reset_cooldown"))cooldown.setInt(actor,0);d.calls=0;d.difficultyCalls=0;d.hit=false;d.abilities.invulnerable=d.flag("ability_invulnerable");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);
out.println("storage "+bits(health.getValue())+" "+bits(absorption.getValue())+" "+bits(exhaustion.getFloat(food))+" "+d.data.isDirty()+" "+health.isDirty()+" "+absorption.isDirty()+" "+field(LivingEntity.class,"dead").getBoolean(actor));if(d.flag("pack"))out.println("packed "+values(d.data.packDirty()));out.println("defaults "+values(d.data.getNonDefaultValues()));}
}}
}
}
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Compare composed server-player damage, cooldown, health and hunger 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, test_player_hurt_parity as hurt
except ImportError:
import lab
import test_rust_parity as base
import test_player_hurt_parity as hurt
def corpus(catalog):
import copy
def bits(v):return struct.pack('>f',v).hex()
def double(v):return struct.pack('>d',v).hex()
rng=random.Random(0x434f4d424154)
values=[-float('inf'),-1e30,-1,-0.,0.,1e-40,.1,1,5,10,20,1e30,float('inf'),float('nan')]
def extend(f,randomize=False):
f=copy.deepcopy(f);f.update(health=bits(20),absorption=bits(4),exhaustion=bits(0),dead=False,attributes=[dict(base=double(v),amount=double(0),operation=0) for v in [15,8,20,20]])
if randomize:
for k in ['health','absorption','exhaustion']:f[k]=bits(rng.choice(values))
f['dead']=rng.randrange(8)==0
for a in f['attributes']:a.update(base=double(rng.choice(values)),amount=double(rng.choice(values)),operation=rng.randrange(3))
for o in f['ops']:
o.update(weapon=False,resistance=False,amplifier=0,break_armor=False,armor_after=double(0),food_exhaustion=bits(.1),pack=False,pack_callback=False,client=False,bypass_armor=False,bypass_effects=False,bypass_resistance=False,bypass_enchantments=False)
if randomize:
for k in ['weapon','resistance','break_armor','pack','pack_callback','client','bypass_armor','bypass_effects','bypass_resistance','bypass_enchantments']:o[k]=bool(rng.randrange(2))
o.update(amplifier=rng.choice([-2147483648,-2,-1,0,1,4,5,2147483647]),armor_after=double(rng.choice(values)),food_exhaustion=bits(rng.choice(values)),fail=rng.choice([-1,-1,-1,*range(65)]))
return f
for i,f in enumerate(hurt.corpus()):yield extend(f,i%2==0)
template=extend(next(hurt.corpus()))
clean=copy.deepcopy(template['ops'][0]);clean.update(damage=bits(8),blocked=bits(0),ability_invulnerable=False,scales=False,attacker=0,owner=0,can_harm=True,difficulties=[2,2,2],fail=-1,reset_cooldown=True)
for health in values:
for absorption in values:
f=copy.deepcopy(template);f.update(health=bits(health),absorption=bits(absorption),cooldown=0,last_hurt=bits(0));f['ops']=[dict(clean,damage=bits(v),pack=i%2==0) for i,v in enumerate([1,2,4,8,16,32,64])];yield f
for fail in range(-1,66):
for absorption in [0,4,40]:
f=copy.deepcopy(template);f.update(absorption=bits(absorption),cooldown=0,last_hurt=bits(0));f['ops']=[dict(clean,fail=fail,pack_callback=True,break_armor=True,resistance=True,amplifier=0),dict(clean,damage=bits(25),pack=True)];yield f
def main():
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--prepare',action='store_true');args=parser.parse_args()
build=lab.LOCAL/'rust-server-player-combat-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/VanillaServerPlayerCombatOracle.java'
helper=lab.ROOT/'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaEntityAttributeOracle.java'
health_helper=lab.ROOT/'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaPlayerHealthOracle.java'
sources=[oracle,helper,health_helper,Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/test_player_hurt_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/server_player_combat_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,helper,health_helper],'compile-java')
def java(side,mode,path):
run(['java','-Xmx512M','-cp',os.pathsep.join([str(classes),str(jars[side]),dependency_path]),'dev.horizonlab.parity.VanillaServerPlayerCombatOracle',mode,path],side+'-'+mode)
java('server','export',build);states=json.loads((build/'catalog.json').read_text());fixtures=list(corpus(states));inputs=build/'inputs.txt';inputs.write_text(''.join(json.dumps(value,separators=(',',':'))+'\n' for value in fixtures),encoding='utf-8')
fixture_paths=[inputs,build/'catalog.json',build/'ids.json',build/'slots.json',build/'head.json'];tested_fixtures=base.source_hashes(fixture_paths)
run(['cargo','build','--release','-p','horizon-world','--example','server_player_combat_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'/('server_player_combat_fixture.exe' if os.name=='nt' else 'server_player_combat_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:,} server player combat 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 ServerPlayer outer hurt through Player/LivingEntity gates and actual Player inner damage, native armor/resistance reduction, registered modified attributes, synchronized health/absorption and FoodData exhaustion. Repeated hits, cooldown/state writes, death rejection, callbacks and numeric edges. Invulnerability predicate, blocking amount, durability, effects, PvP permission, attribution, knockback, death services and criterion listeners remain controlled; enchanted items, registered sources and complete entity/game lifecycle remain pending.'}
lab.write_json(report_path,report);print(f'Scoped server player combat parity passed: {report_path}')
if __name__=='__main__':main()