Bind player absorption and health to shared synchronized storage

This commit is contained in:
Ludovic Marie committed 2026-10-07 05:05:28 +02:00
1 parent 5b6ce7faa9
commit 4f595f34a5
7 files changed
+782

No files matched your search

@@ -0,0 +1,255 @@
{
"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": 4258,
"sources_sha256": {
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaPlayerHealthOracle.java": "5d6dc93c0140e2b2f953fba7290721079d5a607fa81b051e2ac1e4742ab606f2",
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaEntityAttributeOracle.java": "2ad3190a9798ab54bd12e88573028d0ca6a204e2f8a92018aae98b22335213bf",
"tools/test_player_health_parity.py": "7a094194489f5e952c23737da9b352cf17d4b5b08c05d4c8dc8f50a98f194beb",
"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": "d66b993f1b13d15977a792e04a0668ca9cc3920d49d2c7737efaffb0be03621f",
"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_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/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/player_health_fixture.rs": "7650495fbe91f9796a5b1be2115a583e4d55dba42d73ede292018711e3b9213f"
},
"fixtures_sha256": {
".local/rust-player-health-parity/inputs.txt": "ae26fb76cf1034d72f15095389903074ed1c435e83291ec9cb41023663481ec0",
".local/rust-player-health-parity/catalog.json": "d1e488e4f432888beb9f643904c8bca242d1e2093f53e23e07da305d70cf7106",
".local/rust-player-health-parity/ids.json": "f7d6037166d966e2714b01cff5626f0cc0a02ce883885ec791c93e977afde0e6"
},
"rust_output_sha256": "546505ec6df593bc2e15ac2f28b3ee41ee0f723a537a827f982e6e34a6ad2ca0",
"references": [
{
"side": "client",
"jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d",
"output_sha256": "546505ec6df593bc2e15ac2f28b3ee41ee0f723a537a827f982e6e34a6ad2ca0",
"matched_output_lines": 243146
},
{
"side": "server",
"jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6",
"output_sha256": "546505ec6df593bc2e15ac2f28b3ee41ee0f723a537a827f982e6e34a6ad2ca0",
"matched_output_lines": 243146
}
],
"scope": "Actual Player health/absorption methods using registered player AttributeMap and one SynchedEntityData table. Exports actual health/absorption field IDs; compares modified attribute limits, healing, player absorption getter/setter, dirty/default extraction, received updates, callback packet drains and failures. Observer side effects are controlled; custom virtual data replacement, other synchronized serializers, wire codecs and full player damage/tick integration remain pending."
}
@@ -0,0 +1,242 @@
use horizon_world::{
attributes::{
Modifier, Operation,
entity::{Catalog, EntityAttributes},
},
player_health::{PlayerAttributeHealth, PlayerFloatIds},
synced_floats::{FloatValue, Observer, SyncedFloats},
};
use serde_json::Value;
use std::{
fs,
io::{self, Write},
path::Path,
sync::Arc,
};
fn single(v: &Value) -> f32 {
f32::from_bits(u32::from_str_radix(v.as_str().unwrap(), 16).unwrap())
}
fn number(v: &Value) -> f64 {
f64::from_bits(u64::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 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(","),
}
}
struct Callback<'a> {
out: &'a mut dyn Write,
op: &'a Value,
calls: i64,
}
impl Callback<'_> {
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 player health callback failure"))
} else {
Ok(())
}
}
}
impl Observer for Callback<'_> {
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.op["pack_callback"].as_bool().unwrap() {
writeln!(
self.out,
"callback-pack {}",
values(data.take_dirty().as_deref())
)?;
}
Ok(())
}
fn assigned(&mut self, _: &mut SyncedFloats, v: &[FloatValue]) -> io::Result<()> {
self.event(&format!("assigned {}", values(Some(v))))
}
}
struct State {
attributes: EntityAttributes,
data: SyncedFloats,
ids: PlayerFloatIds,
dead: bool,
}
impl State {
fn bind<'a, 'b>(
&'a mut self,
cb: &'a mut Callback<'b>,
) -> PlayerAttributeHealth<'a, Callback<'b>> {
PlayerAttributeHealth::new(
&mut self.attributes,
&mut self.data,
self.ids,
self.dead,
cb,
)
.unwrap()
}
}
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 catalog = Arc::new(Catalog::from_json(&serde_json::from_str(
&fs::read_to_string(dir.join("catalog.json"))?,
)?)?);
let keys = [
catalog.id("minecraft:max_health").unwrap(),
catalog.id("minecraft:max_absorption").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 state = State {
attributes: catalog.create("minecraft:player").unwrap(),
data: SyncedFloats::new(&[
FloatValue {
id: ids.health,
value: single(&f["health"]),
},
FloatValue {
id: ids.absorption,
value: single(&f["absorption"]),
},
])?,
ids,
dead: f["dead"].as_bool().unwrap(),
};
for op in f["ops"].as_array().unwrap() {
let mut cb = Callback {
out: &mut out,
op,
calls: 0,
};
let key = keys[op["attribute"].as_u64().unwrap() as usize];
let v = single(&op["value"]);
let result = match op["mode"].as_str().unwrap() {
"set" => state.bind(&mut cb).set_health(v).map_err(|_| ()),
"heal" => state.bind(&mut cb).heal(v).map_err(|_| ()),
"absorption" => state.bind(&mut cb).set_absorption(v).map_err(|_| ()),
"get-absorption" => state
.bind(&mut cb)
.absorption()
.map_err(|_| ())
.and_then(|v| writeln!(cb.out, "float {:08x}", bits(v)).map_err(|_| ())),
"hurt" => state
.bind(&mut cb)
.is_hurt()
.map_err(|_| ())
.and_then(|v| writeln!(cb.out, "bool {v}").map_err(|_| ())),
"dead" => state
.bind(&mut cb)
.is_dead_or_dying()
.map_err(|_| ())
.and_then(|v| writeln!(cb.out, "bool {v}").map_err(|_| ())),
"base" => {
state
.attributes
.edit(key, |a, o| a.set_base_value(number(&op["amount"]), o))
.unwrap()
.unwrap();
Ok(())
}
"add" => {
let m = Modifier::new(
op["id"].as_str().unwrap(),
number(&op["amount"]),
Operation::ALL[op["operation"].as_u64().unwrap() as usize],
)?;
state
.attributes
.edit(key, |a, o| a.add_or_update_transient(m, o))
.unwrap()
.unwrap();
Ok(())
}
"remove" => {
state
.attributes
.edit(key, |a, o| a.remove(op["id"].as_str().unwrap(), o))
.unwrap()
.unwrap();
Ok(())
}
"reset" => {
state.attributes.reset_base(key);
Ok(())
}
"pack" => writeln!(
cb.out,
"packed {}",
values(state.data.take_dirty().as_deref())
)
.map_err(|_| ()),
"defaults" => writeln!(
cb.out,
"defaults {}",
values(state.data.non_default().as_deref())
)
.map_err(|_| ()),
"assign" => {
let values = op["values"]
.as_array()
.unwrap()
.iter()
.map(|v| FloatValue {
id: if v["field"] == 0 {
ids.health
} else {
ids.absorption
},
value: single(&v["value"]),
})
.collect::<Vec<_>>();
state.data.assign(&values, &mut cb).map_err(|_| ())
}
_ => panic!("mode"),
};
writeln!(
cb.out,
"result {}",
if result.is_ok() { "ok" } else { "error" }
)?;
writeln!(
cb.out,
"state {:08x} {:08x} {} {} {} {} {} {}",
bits(state.data.get(ids.health).unwrap()),
bits(state.data.get(ids.absorption).unwrap()),
state.data.is_dirty(),
state.data.item_dirty(ids.health).unwrap(),
state.data.item_dirty(ids.absorption).unwrap(),
state.data.is_default(ids.health).unwrap(),
state.data.is_default(ids.absorption).unwrap(),
cb.calls
)?;
}
}
Ok(())
}
+1
View File
@@ -56,6 +56,7 @@ pub mod nbt;
pub mod piston_movement;
pub mod piston_shape;
pub mod player_food;
pub mod player_health;
pub mod player_travel;
pub mod powder_snow_collision;
pub mod region;
+155
View File
@@ -0,0 +1,155 @@
//! Player absorption uses virtual synchronized-data access, unlike base living
//! absorption field storage. Health keeps its direct synchronized-data access.
use crate::{
attributes::entity::{AttributeId, EntityAttributes, LookupError},
compression::invalid,
living_health::{self, HealthWorld},
synced_floats::{DataError, Observer, SyncedFloats},
};
use std::{fmt, io};
pub trait PlayerHealthWorld: HealthWorld {
fn entity_data(&mut self) -> Result<Self::Data, Self::Error>;
fn read_absorption(&mut self, data: Self::Data) -> Result<f32, Self::Error>;
fn write_absorption(&mut self, data: Self::Data, value: f32) -> Result<(), Self::Error>;
}
pub fn absorption<W: PlayerHealthWorld>(world: &mut W) -> Result<f32, W::Error> {
let data = world.entity_data()?;
world.read_absorption(data)
}
pub fn internal_set_absorption<W: PlayerHealthWorld>(
world: &mut W,
value: f32,
) -> Result<(), W::Error> {
let data = world.entity_data()?;
world.write_absorption(data, value)
}
#[derive(Clone, Copy, Debug)]
pub struct PlayerFloatIds {
pub health: u8,
pub absorption: u8,
}
#[derive(Debug)]
pub enum PlayerHealthError<E> {
Attribute(LookupError),
Data(DataError<E>),
}
impl<E: fmt::Display> fmt::Display for PlayerHealthError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Attribute(e) => write!(f, "player health attribute lookup: {e:?}"),
Self::Data(e) => e.fmt(f),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for PlayerHealthError<E> {}
/// Ordinary registered-player state, borrowing its actual float table. IDs come
/// from the pinned entity-data schema; construction does not invent spawn state.
pub struct PlayerAttributeHealth<'a, O> {
attributes: &'a mut EntityAttributes,
data: &'a mut SyncedFloats,
ids: PlayerFloatIds,
max_health: AttributeId,
max_absorption: AttributeId,
dead: bool,
observer: &'a mut O,
}
impl<'a, O: Observer> PlayerAttributeHealth<'a, O> {
pub fn new(
attributes: &'a mut EntityAttributes,
data: &'a mut SyncedFloats,
ids: PlayerFloatIds,
dead: bool,
observer: &'a mut O,
) -> io::Result<Self> {
if ids.health == ids.absorption
|| data.get(ids.health).is_err()
|| data.get(ids.absorption).is_err()
{
return Err(invalid("missing or aliased player float fields"));
}
let max_health = attributes
.catalog()
.id("minecraft:max_health")
.ok_or_else(|| invalid("missing MAX_HEALTH definition"))?;
let max_absorption = attributes
.catalog()
.id("minecraft:max_absorption")
.ok_or_else(|| invalid("missing MAX_ABSORPTION definition"))?;
Ok(Self {
attributes,
data,
ids,
max_health,
max_absorption,
dead,
observer,
})
}
pub fn heal(&mut self, amount: f32) -> Result<(), PlayerHealthError<O::Error>> {
living_health::heal(self, amount)
}
pub fn set_health(&mut self, value: f32) -> Result<(), PlayerHealthError<O::Error>> {
living_health::set_health(self, value)
}
pub fn set_absorption(&mut self, value: f32) -> Result<(), PlayerHealthError<O::Error>> {
living_health::set_absorption(self, value)
}
pub fn absorption(&mut self) -> Result<f32, PlayerHealthError<O::Error>> {
absorption(self)
}
pub fn is_hurt(&mut self) -> Result<bool, PlayerHealthError<O::Error>> {
living_health::is_hurt(self)
}
pub fn is_dead_or_dying(&mut self) -> Result<bool, PlayerHealthError<O::Error>> {
living_health::is_dead_or_dying(self)
}
}
impl<O: Observer> HealthWorld for PlayerAttributeHealth<'_, O> {
type Error = PlayerHealthError<O::Error>;
type Data = ();
fn health(&mut self) -> Result<f32, Self::Error> {
self.data
.get(self.ids.health)
.map_err(|id| PlayerHealthError::Data(DataError::UnknownId(id)))
}
fn health_data(&self) {}
fn max_health_attribute(&mut self) -> Result<f64, Self::Error> {
self.attributes
.value(self.max_health)
.map_err(PlayerHealthError::Attribute)
}
fn max_absorption_attribute(&mut self) -> Result<f64, Self::Error> {
self.attributes
.value(self.max_absorption)
.map_err(PlayerHealthError::Attribute)
}
fn store_health(&mut self, _: (), value: f32) -> Result<(), Self::Error> {
self.data
.set(self.ids.health, value, false, self.observer)
.map_err(PlayerHealthError::Data)
}
fn store_absorption(&mut self, value: f32) -> Result<(), Self::Error> {
internal_set_absorption(self, value)
}
fn dead_flag(&self) -> bool {
self.dead
}
fn dispatch_set_health(&mut self, value: f32) -> Result<(), Self::Error> {
living_health::set_health(self, value)
}
}
impl<O: Observer> PlayerHealthWorld for PlayerAttributeHealth<'_, O> {
fn entity_data(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn read_absorption(&mut self, _: ()) -> Result<f32, Self::Error> {
self.data
.get(self.ids.absorption)
.map_err(|id| PlayerHealthError::Data(DataError::UnknownId(id)))
}
fn write_absorption(&mut self, _: (), value: f32) -> Result<(), Self::Error> {
self.data
.set(self.ids.absorption, value, false, self.observer)
.map_err(PlayerHealthError::Data)
}
}
+16
View File
@@ -1299,3 +1299,19 @@ NaN/signed-zero equality. Native gaps are untouched default float items; observe
keys are explicit configured IDs. Targeted Clippy passes. Other serializers,
serializer-mismatch errors, wire codecs and player absorption binding remain
pending.
`player_health` binds ordinary registered player attributes to health and
absorption in one synchronized float table. The player absorption getter and
internal setter use virtual data access; health retains its direct data access.
The final absorption setter still applies the registered maximum before writing.
`python3 tools/test_player_health_parity.py` compares actual Player methods,
registered player attributes and `SynchedEntityData`, exporting the actual pinned
health/absorption field IDs. The
[player health report](../benchmarks/rust-player-health-2026-10-07/scoped-parity.json)
records **243,146 matching lines per JAR**, covering modified limits, healing,
absorption reads/writes, combined dirty/default lists, received assignments,
callback packet drains and failures. Targeted Clippy passes. Observer effects
are controlled; custom virtual data replacement, other synchronized serializers,
wire codecs and full player damage/tick integration remain pending.
@@ -0,0 +1,39 @@
package dev.horizonlab.parity;
import com.google.gson.*;import java.io.*;import java.lang.reflect.*;import java.nio.file.*;import java.util.*;
import net.minecraft.core.*;import net.minecraft.core.registries.*;import net.minecraft.resources.Identifier;import net.minecraft.world.entity.*;import net.minecraft.world.entity.player.Player;import net.minecraft.world.entity.ai.attributes.*;import net.minecraft.network.syncher.*;
/** Actual player health/absorption on one native synchronized table. */
public final class VanillaPlayerHealthOracle {
static final class Stop extends RuntimeException {}
static EntityDataAccessor<Float> healthKey,absorptionKey;
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 bits(float v){return String.format("%08x",Float.floatToIntBits(v));}
static String values(List<SynchedEntityData.DataValue<?>> values){if(values==null)return "none";var s=new StringJoiner(",");for(var v:values)s.add(v.id()+":"+bits((Float)v.value()));return s.length()==0?"empty":s.toString();}
static Field field(Class<?> c,String n)throws Exception{var f=c.getDeclaredField(n);f.setAccessible(true);return f;}
static final class Data {JsonObject op;PrintWriter out;AttributeMap attributes;SynchedEntityData data;int calls;
void event(String s){out.println(s);if(calls++==op.get("fail").getAsInt())throw new Stop();}}
static final class Actor extends Player {Data d;Actor(){super(null,null);}@Override public net.minecraft.world.level.GameType gameMode(){return net.minecraft.world.level.GameType.SURVIVAL;}
@Override public double getAttributeValue(Holder<Attribute> a){return d.attributes.getValue(a);}
@Override public void onSyncedDataUpdated(EntityDataAccessor<?> a){var key=a.id()==healthKey.id()?healthKey:absorptionKey;d.event("updated "+key.id()+" "+bits(d.data.get(key))+" "+d.data.isDirty());if(d.op.get("pack_callback").getAsBoolean())d.out.println("callback-pack "+values(d.data.packDirty()));}
@Override public void onSyncedDataUpdated(List<SynchedEntityData.DataValue<?>> v){d.event("assigned "+values(v));}
}
@SuppressWarnings("unchecked") public static void main(String[] args)throws Exception {
Path dir=Path.of(args[1]);if(args[0].equals("export"))VanillaEntityAttributeOracle.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 ids=new JsonObject();ids.addProperty("health",healthKey.id());ids.addProperty("absorption",absorptionKey.id());Files.writeString(dir.resolve("ids.json"),ids.toString());return;}
var u=Class.forName("sun.misc.Unsafe");var uf=u.getDeclaredField("theUnsafe");uf.setAccessible(true);var unsafe=uf.get(null);var allocate=u.getMethod("allocateInstance",Class.class);var ctor=SynchedEntityData.class.getDeclaredConstructor(SyncedDataHolder.class,SynchedEntityData.DataItem[].class);ctor.setAccessible(true);var storage=field(Entity.class,"entityData");var dead=field(LivingEntity.class,"dead");var keys=List.of(Attributes.MAX_HEALTH,Attributes.MAX_ABSORPTION);
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.attributes=new AttributeMap(VanillaEntityAttributeOracle.supplier(BuiltInRegistries.ENTITY_TYPE.getValue(Identifier.parse("minecraft:player"))));var actor=(Actor)allocate.invoke(unsafe,Actor.class);actor.d=d;
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=ctor.newInstance(actor,items);storage.set(actor,d.data);dead.setBoolean(actor,f.get("dead").getAsBoolean());
for(var ov:f.getAsJsonArray("ops")){var op=ov.getAsJsonObject();d.op=op;d.calls=0;var key=keys.get(op.get("attribute").getAsInt());float v=single(op.get("value"));try{switch(op.get("mode").getAsString()){
case "set":actor.setHealth(v);break;case "heal":actor.heal(v);break;case "absorption":actor.setAbsorptionAmount(v);break;case "get-absorption":out.println("float "+bits(actor.getAbsorptionAmount()));break;
case "hurt":out.println("bool "+actor.isHurt());break;case "dead":out.println("bool "+actor.isDeadOrDying());break;
case "base":d.attributes.getInstance(key).setBaseValue(number(op.get("amount")));break;
case "add":d.attributes.getInstance(key).addOrUpdateTransientModifier(new AttributeModifier(Identifier.parse(op.get("id").getAsString()),number(op.get("amount")),AttributeModifier.Operation.values()[op.get("operation").getAsInt()]));break;
case "remove":d.attributes.getInstance(key).removeModifier(Identifier.parse(op.get("id").getAsString()));break;case "reset":d.attributes.resetBaseValue(key);break;
case "pack":out.println("packed "+values(d.data.packDirty()));break;case "defaults":out.println("defaults "+values(d.data.getNonDefaultValues()));break;
case "assign":{var data=new ArrayList<SynchedEntityData.DataValue<?>>();for(var x:op.getAsJsonArray("values")){var e=x.getAsJsonObject();data.add(new SynchedEntityData.DataValue<Float>(e.get("field").getAsInt()==0?healthKey.id():absorptionKey.id(),EntityDataSerializers.FLOAT,single(e.get("value"))));}d.data.assignValues(data);break;}
default:throw new AssertionError();}out.println("result ok");}catch(Stop e){out.println("result error");}
out.println("state "+bits(health.getValue())+" "+bits(absorption.getValue())+" "+d.data.isDirty()+" "+health.isDirty()+" "+absorption.isDirty()+" "+health.isSetToDefault()+" "+absorption.isSetToDefault()+" "+d.calls);
}
}}
}
}
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""Compare registered player health 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(catalog):
def bits(v):return struct.pack('>f',v).hex()
def double(v):return struct.pack('>d',v).hex()
def op(mode,value=0,**kw):
o=dict(mode=mode,value=bits(value),amount=double(0),attribute=0,id='horizon:a',operation=0,values=[],fail=-1,pack_callback=False);o.update(kw);return o
def row(health,absorption,ops,dead=False):return dict(health=bits(health),absorption=bits(absorption),ops=ops,dead=dead)
values=[-float('inf'),-1e30,-1,-0.,0.,1e-40,.1,1,5,10,20,1e30,float('inf'),float('nan')]
for start in values:
for maximum in values:
for value in values:
yield row(start,start,[op('base',amount=double(maximum)),op('base',attribute=1,amount=double(maximum)),op('set',value),op('absorption',value),op('get-absorption'),op('hurt'),op('dead'),op('pack'),op('heal',value),op('defaults'),op('pack')])
for fail in range(-1,6):
for pack in [False,True]:
ops=[op('base',attribute=1,amount=double(20)),op('set',9),op('absorption',5),op('pack'),op('set',1),op('assign',values=[dict(field=0,value=bits(20)),dict(field=1,value=bits(7)),dict(field=0,value=bits(10))]),op('pack')]
yield row(1,0,[dict(o,fail=fail,pack_callback=pack) for o in ops])
rng=random.Random(0x504c415945524850)
for _ in range(1500):
ops=[]
for __ in range(rng.randrange(5,70)):
ops.append(op(rng.choice(['set','heal','absorption','get-absorption','hurt','dead','base','add','remove','reset','pack','defaults','assign']),rng.choice(values),attribute=rng.randrange(2),amount=double(rng.choice(values)),operation=rng.randrange(3),id=rng.choice(['horizon:a','horizon:b']),values=[dict(field=rng.randrange(2),value=bits(rng.choice(values))) for ___ in range(rng.randrange(5))],fail=rng.choice([-1,-1,-1,0,1,2]),pack_callback=bool(rng.randrange(2))))
yield row(rng.choice(values),rng.choice(values),ops,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-player-health-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/VanillaPlayerHealthOracle.java'
helper=lab.ROOT/'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaEntityAttributeOracle.java'
sources=[oracle,helper,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/player_health_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],'compile-java')
def java(side,mode,path):
run(['java','-Xmx512M','-cp',os.pathsep.join([str(classes),str(jars[side]),dependency_path]),'dev.horizonlab.parity.VanillaPlayerHealthOracle',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'];tested_fixtures=base.source_hashes(fixture_paths)
run(['cargo','build','--release','-p','horizon-world','--example','player_health_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'/('player_health_fixture.exe' if os.name=='nt' else 'player_health_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:,} player health 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 Player health/absorption methods using registered player AttributeMap and one SynchedEntityData table. Exports actual health/absorption field IDs; compares modified attribute limits, healing, player absorption getter/setter, dirty/default extraction, received updates, callback packet drains and failures. Observer side effects are controlled; custom virtual data replacement, other synchronized serializers, wire codecs and full player damage/tick integration remain pending.'}
lab.write_json(report_path,report);print(f'Scoped player health parity passed: {report_path}')
if __name__=='__main__':main()