Match living fall damage decisions and impulse context transitions

This commit is contained in:
Ludovic Marie
2026-10-07 01:07:11 +02:00
parent 38d60eef9e
commit 219f0ee1de
7 changed files with 672 additions and 0 deletions
@@ -0,0 +1,216 @@
{
"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": 11096,
"sources_sha256": {
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaLivingFallOracle.java": "bb29b1f99030f1407d7c25b1553433508a9fbeb5227d236b71ab14ffb8ec7ad9",
"tools/test_living_fall_parity.py": "e18fee2458dcda91c11f1e8226ad078aae1080e1ce3990b29e7e9bf18b52d370",
"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/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_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": "8843e6f48dbbc4245ce4e95564dfba3a4d7a051ba0b1577fb038c07adb0ff0f9",
"crates/horizon-world/src/chunk_status.rs": "dbb8583cfa775203c544d24d69e03720385428f15a267a561356c21f5025e9c7",
"crates/horizon-world/src/collision.rs": "a749794d5c27ccd6c0d964ad02f9dc31d49d9f4fe4b7366cd4bd8155e681b00b",
"crates/horizon-world/src/collision_context.rs": "2176c7034e07f547510a8327cdad9d3b63f26e4754032b76b7d5fe13166c333c",
"crates/horizon-world/src/compression.rs": "4db73e6ca9a0d552d415ce60e7e88b89c9cf2b7d060a669ff8328f518316e101",
"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_obstruction.rs": "a4b28a19bf83b23611c59d94535c488db37e4aee24b22c973c545910894a809f",
"crates/horizon-world/src/fall_damage.rs": "8c28e2933493569fe05dea22332024287d28b0746b514dd465681040b003296b",
"crates/horizon-world/src/fluid.rs": "a0a1f34ba94d8b59dc975becee8952960764d67ae08cf2d6a8bcd40a614b15c4",
"crates/horizon-world/src/ground_support.rs": "3e371d74ecede4d30beef0a7e8a2ee33463584a5b1ed3d852b394efcce1ffbe0",
"crates/horizon-world/src/heightmap.rs": "a5edef082b5af40d814421ea17e129b3b06d70abee6434ed263e4bba58571698",
"crates/horizon-world/src/lib.rs": "3beb1feaf4f2972efb4eb242d6deebe6c3c0a953892b03c8ca4abeea8b5adf59",
"crates/horizon-world/src/liquid_collision.rs": "0f0a83b04e5a5bc9d98ed05fde4d6584ba69642e93522e726e4cfc325d6fcb1d",
"crates/horizon-world/src/living_fall.rs": "da0f176f4731e1c86e08bfeee8167742fd1b86380ff0e9304c13c1ce3f1c67ab",
"crates/horizon-world/src/main.rs": "fce57418b8c22a6a0a9044baa89ed3d9c243bc2e8413fea3ff0f32985daba021",
"crates/horizon-world/src/movement_properties.rs": "9c78bed0d30c8b9cfaa413b9b81b0bf94861605c13b26754e09b24e83d75a227",
"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/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": "a5021a168adcedc37d77f533a8921c02f09c59f46a01737586aaa33c5908b8d0",
"crates/horizon-world/src/retrogen.rs": "6efd36a6293756688c1990a7965bd65609d7c5e2bf2a5f4f01d54932d2b9386b",
"crates/horizon-world/src/suffocation.rs": "bed219dd5bbab6263b98fbe3d0eb512cb96c52ae847210efe49514d05f22f11e",
"crates/horizon-world/src/tick_data.rs": "f8d04a69ea263f4168bce33f92e5b4ee2fe07d7caf50a1621c6b3ef6eb2479f3",
"crates/horizon-world/src/world_movement.rs": "f8fe9e34f2395d2a04c839d1c8ba78e09ce493c26de6f6e4e237b9c19c061da5",
"crates/horizon-world/src/worldgen/anchor.rs": "d156b8a0b147a274cdd143409b441f4f43edecde6e8ee14ef9cf0309f743143a",
"crates/horizon-world/src/worldgen/aquifer/config.rs": "fcd2cc3a520c1527c88f095a3b5d38487e7ebb1ccecc5f757f93d04a069add4f",
"crates/horizon-world/src/worldgen/aquifer/noise_based.rs": "74daaa389b5c3dfdc99214dfff256a9e172f83548d2326cbc941d9637cc53fb7",
"crates/horizon-world/src/worldgen/aquifer.rs": "cb4b66d87de3f9f79ae6ffe4badd6f0f44886cd42ad9de2771b8b56c4f1e28c7",
"crates/horizon-world/src/worldgen/bamboo.rs": "d9b47113c99be34256e58308bc6f40072ff25c579d6bf995182aa862c0006102",
"crates/horizon-world/src/worldgen/biome.rs": "3fe316b875aa32683759c337dc71f624910faf0812a2d6d83b8098d6d0bac7a0",
"crates/horizon-world/src/worldgen/biome_chunk.rs": "8fdfe6ebe3ffeca97611fd5991a5bc6b2783ea1c62f4d8ef3b2dd53c04a5dc1d",
"crates/horizon-world/src/worldgen/biome_predicate.rs": "bfc581bc40077cc74ffda43e990a55fe997adc04c7130f98e039c3305ca79aac",
"crates/horizon-world/src/worldgen/biome_source/codec.rs": "2b9e4f5d679eb444cb73174f511e3b9f32e9eb138248ab12cffc6cfd76adf3a8",
"crates/horizon-world/src/worldgen/biome_source/runtime.rs": "f0b02c1521178e4d0377c6ca62694604b6a773f1f234f070ff0cde4685911d45",
"crates/horizon-world/src/worldgen/biome_source.rs": "e181e1736f3e0b75f7091a24c88de3fd116e01b98424d02382aea7a503d1eb54",
"crates/horizon-world/src/worldgen/biome_temperature.rs": "cde4bbab5e260c4065b113376c423b8511ade93c5aa5fd976e245ee8a58b5ad1",
"crates/horizon-world/src/worldgen/biome_weather.rs": "96b1f9494559ed31d06257b83d604d00cc069ccc8c7b68094117e034b4a71bd7",
"crates/horizon-world/src/worldgen/block_blob.rs": "c608c57b3d77279646ecb89e50677dd8e452b0422ffd357ba755536c5589eb89",
"crates/horizon-world/src/worldgen/block_column.rs": "f72c21a9066ee0aa68c2fb79fba436d05afb14c6ef5a2cb012f1ed35fc17cd22",
"crates/horizon-world/src/worldgen/block_feature.rs": "36b4e46ee7f7f73bcb94d8bce8d99013e4b16234c9a03542eeb3186e88dd1c21",
"crates/horizon-world/src/worldgen/block_pile.rs": "623e369b77e0526ed2756485a6c0bb09683cd28ad8876dea907360ca6665b39d",
"crates/horizon-world/src/worldgen/block_predicate.rs": "074c692367e3f2191a1bb28d87b23402430a843c246bd30a4ea838ee3500280e",
"crates/horizon-world/src/worldgen/blue_ice.rs": "eedd77bdaff808330b864166d303378096daa5c7d235e925770b171c05109247",
"crates/horizon-world/src/worldgen/bonus_chest.rs": "05d0f63fbc7412f13ba1fa37a7238114b8f70bfff318beb2dece590482d49cc0",
"crates/horizon-world/src/worldgen/bulk_section.rs": "eecf61532d7793f853884c5a18a7f436a59835c7b13d3ec9075804511553c9ba",
"crates/horizon-world/src/worldgen/carver/canyon.rs": "509c4d5a9d689849e98e894ab14a9fbf82d10707d38e9cc688ded3c4778df3e2",
"crates/horizon-world/src/worldgen/carver/generation.rs": "a4498d0d1457536de3922a08e9c40ccce8d2ef9cc263801c15a412327513c098",
"crates/horizon-world/src/worldgen/carver.rs": "46b251e28225fe1250de38f49dec5dacda4e2c4ba1e556e21e6d1e14e2651fc3",
"crates/horizon-world/src/worldgen/carving.rs": "4e77d4e5c4a0ca55ca3563b9bdf6f379342e0bdf9d019d60f7bc57ce4afb5fbd",
"crates/horizon-world/src/worldgen/chorus_plant.rs": "29224c164ede919e280a65a68f155c873e2c8fd0aa7d7e244d8fa9e468760250",
"crates/horizon-world/src/worldgen/climate/codec.rs": "d567adeb5d837816fca69102db51d790f71aec1b51c09298230d24612e400a14",
"crates/horizon-world/src/worldgen/climate/index.rs": "c5ce61679a5f0e0f2f0af2a373424effa124d0cce8250609eff38b7034650756",
"crates/horizon-world/src/worldgen/climate.rs": "1b9ad44407f40aee4c1cd5357a5c9c443c1b105728f01cf5c644072f99f8357e",
"crates/horizon-world/src/worldgen/decoration.rs": "75a79b20b21e133816e729f16e758be6165d0db18a64bfbd5fd3618325a2adf3",
"crates/horizon-world/src/worldgen/delta.rs": "c2bf49ddf3231d28c109e68871429bc13d76a7bebe14eb33e84aa856bdd66145",
"crates/horizon-world/src/worldgen/density.rs": "65a98c156a290a6b374ef670d28cd5dfbe0aa93171824d1bbd96422536273f84",
"crates/horizon-world/src/worldgen/disk.rs": "6f8f9e059755af44c9bfa1b91eba54a7d13fad811dce31717c5502d7f6df33d7",
"crates/horizon-world/src/worldgen/end_gateway.rs": "f53fde77d6a36a525cd15d890003b2c129d0689a4cbfa2cf443a0c2e3acae040",
"crates/horizon-world/src/worldgen/end_podium.rs": "8f6d0d6d60ff0fde63de5781e867239db321f82cf12639f0537d410a3731ee1d",
"crates/horizon-world/src/worldgen/feature.rs": "40500af71ee6628dfb27518262d99f08d0383ae6c7b9397452feef0c213eadd9",
"crates/horizon-world/src/worldgen/feature_catalog.rs": "efc7b17b227e71622ef34108be9abd010f5675b68ea0409fcab82581d9aab88a",
"crates/horizon-world/src/worldgen/feature_graph.rs": "da132fca9fdafb0d89a917d5f4b68c60b70fb33cadf948a107da0e35d83dcd91",
"crates/horizon-world/src/worldgen/feature_region.rs": "df0ff142a461cafa9a3b42b949ad3376887f2be8d4ea5cc794a3ed2e977bb499",
"crates/horizon-world/src/worldgen/feature_sort.rs": "3488a093dfe1321aaa29d8a30cfb115d8e666a81bf195514e87b170de626835c",
"crates/horizon-world/src/worldgen/fluid_predicate.rs": "64197c9e63bebf1ce04bc280bfe980bb6c1b9aa887ca5cded904aeb0d9ae6c46",
"crates/horizon-world/src/worldgen/height_provider.rs": "368d4dc119854742a045c4a717679c921456da681fc5f71aa9b37102d6f33b47",
"crates/horizon-world/src/worldgen/material_condition/codec.rs": "09b909bc384472f76d163c8069ec6b507c7d5c6a028a36d5e0e3df20deff7b3b",
"crates/horizon-world/src/worldgen/material_condition/density.rs": "df9d0761e978e5010663ae8b44f2d6c1ecb4907331e333bdb6a2b487c1e107de",
"crates/horizon-world/src/worldgen/material_condition/library.rs": "a55534c5da97575a819af5a7dc0dbec9069993fa8b2619c6c9d5576946e683fa",
"crates/horizon-world/src/worldgen/material_condition/noise.rs": "ffb59cc6be2275d7a5621bf7d768527e74a8326ed7ad78618746711540921675",
"crates/horizon-world/src/worldgen/material_condition/rule.rs": "fed643352efbcb1f68f29b6241896617db889fc7c577a6247564e01922fec1fe",
"crates/horizon-world/src/worldgen/material_condition.rs": "d638dcb8cbcb5da20b3cb29602b0ae6a86c4fdf9d8c0fb725de8ec5fc108717e",
"crates/horizon-world/src/worldgen/material_extensions.rs": "ab806eb679d5116e1aebb64fca70e1499ae71f83c9507a8cb4c3201c2b1bf037",
"crates/horizon-world/src/worldgen/material_stage.rs": "698cbc786eff13c246e2bc2ea062401918cd4265899fbc3d53abc87e45a8e7a1",
"crates/horizon-world/src/worldgen/material_surface.rs": "5d37b90099df3d6da0845dab164e71469fb287019c2ee74bc6714f3b9d6d6d5b",
"crates/horizon-world/src/worldgen/ore_feature.rs": "83b89fd3be691c5412c0215b6abefdcaa44119a05aa41217e061970eb7e12ebd",
"crates/horizon-world/src/worldgen/placement/chain.rs": "9218399b4d3757f7bbc550d7729a7201f32a1c83a7030865e0c12aab8a80d116",
"crates/horizon-world/src/worldgen/placement/context.rs": "a278304795136677ad4b4933ea50c972b9729a1c671c03fa309c193e224b1fd6",
"crates/horizon-world/src/worldgen/placement/layer.rs": "a0f5ca776f33f9d4f72b8fdcd163426152e5e43cb4a7572fc1d4fc57bf965de1",
"crates/horizon-world/src/worldgen/placement.rs": "b0397e5818fae84358546193a95f84bcb0b5bffafce909e6fcb3386179ee8023",
"crates/horizon-world/src/worldgen/replace_blobs.rs": "bf3377d86d2570cd15288ba69dde64a2cf67c5bcbb9a31d0c23421964af74afa",
"crates/horizon-world/src/worldgen/router.rs": "2cee7cf15a0d2ada12be6b39d83e78177fd3033922e4f4cce87a64073cec2847",
"crates/horizon-world/src/worldgen/rule_test.rs": "d1662ec492489ebaa4b87e20f9a8c809dcf3a223a151211bd65ec241a63692fc",
"crates/horizon-world/src/worldgen/settings.rs": "a8fe87752e1982ef11cb67f8d80d8e3428b2c27db178044cb425ba803e689e59",
"crates/horizon-world/src/worldgen/simple_block.rs": "9ba1a88d5db4925f8c37366557887f776c805f9a6444781615b5dbaff52e92a4",
"crates/horizon-world/src/worldgen/snow_freeze.rs": "9040be3d5c7f8f0fd812d2df45e154bf8a9eeef038530b83fa4159d246eac35f",
"crates/horizon-world/src/worldgen/speleothem.rs": "c71651c1c97818a8a85863e14414d6139ea3bdeb897fa422043bcc1499d99aae",
"crates/horizon-world/src/worldgen/spike.rs": "eacf0d9d3d2bc3d4bcf62bc87555f90031138236a5bfef0eb5270204f7c2a4fc",
"crates/horizon-world/src/worldgen/spring.rs": "830849e9b89fcdf09e9b06fbefdcdebe3c8c3f82804f60b8bd5de04ba303cb62",
"crates/horizon-world/src/worldgen/state_provider/catalog.rs": "c8687e15a4887a9200c8d365857d801efaa8f90e669eee3e600a1db4905119eb",
"crates/horizon-world/src/worldgen/state_provider/noise.rs": "7ee369c2a032e5620cb9bdf9ee7adad1f70870fb0a27efb7b7c514d92f83491f",
"crates/horizon-world/src/worldgen/state_provider/properties.rs": "9909139aaca1dc4f743957257fadaa9d423d21fa401fa9abc050505f79365139",
"crates/horizon-world/src/worldgen/state_provider.rs": "be29ada818826a90afe01ab60916360fdfc4cbde5af15c9359cd56b1f1f7b156",
"crates/horizon-world/src/worldgen/tags.rs": "d35ded93fe6e95ea045cadcf4925e656dc34ef832b742179b9ad0cc49f902f31",
"crates/horizon-world/src/worldgen/terrain.rs": "fd1f2e0afc15daf66f484ea40a1f2b7013650bda0941b23c64c81ee9f79cb673",
"crates/horizon-world/src/worldgen/underwater_magma.rs": "7317e40af5ee5a9780f90ce358fa5d3e7f5b3d513b731df6ff97754d9c2a0154",
"crates/horizon-world/src/worldgen/value_provider.rs": "3358b563db8cb0bc8a9cfbbe2b8b3e70941cd2d37a0b519fbb6545f8d0dcbb69",
"crates/horizon-world/src/worldgen/vines.rs": "ee9b79b65012bd86a838241d2ef708680f7fe8eafc8aa656dae9cf2abf3c6418",
"crates/horizon-world/src/worldgen/weighted.rs": "839840aae25302933bbd97a81a2336330e2d03a0ad1684e1e16ac769ed408e6b",
"crates/horizon-world/src/worldgen/write_zone.rs": "8e17a179764d3f68c05882aef35f6810c28a0000f9a83e58bd6030660b76e64e",
"crates/horizon-world/src/worldgen.rs": "e8e9f099ee1fcec8eedce31805d676d3f89c18e84f494a147ffcbfee23d3d54f",
"crates/horizon-world/examples/living_fall_fixture.rs": "1e11e704754fbebea9513f17aa3d781fa4498d89fd186240495d16c08c7fbf9d"
},
"fixtures_sha256": {
".local/rust-living-fall-parity/inputs.txt": "f8502f8d94827af3aa569327bc15fb1c461d29b2a421d6033ceb37600c1d31c0"
},
"rust_output_sha256": "4404a6eb6e61bb51c0b96b092739defc07925149451bf0aa8e054deae1b402b9",
"references": [
{
"side": "client",
"jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d",
"output_sha256": "4404a6eb6e61bb51c0b96b092739defc07925149451bf0aa8e054deae1b402b9",
"matched_output_lines": 153025
},
{
"side": "server",
"jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6",
"output_sha256": "4404a6eb6e61bb51c0b96b092739defc07925149451bf0aa8e054deae1b402b9",
"matched_output_lines": 153025
}
],
"scope": "Actual LivingEntity.calculateFallDamage, causeFallDamage and impulse-context setter/reset/grace methods. Controlled immunity/attribute getters, passenger callback, sounds and server hurt callback expose native query order and failure state. Includes changing immunity, passenger mutations, impulse-height limiting, zero/negative/large grace values, float multiplier widening, floor/saturation, signed zero/nonfinite values and setter sequences. Hurt return values do not change the causeFallDamage result. A null level snapshot exercises the native non-server hurt no-op; actual client entity lifecycle, passenger storage/recursion, health/damage processing, attribute provisioning and tick/save/network integration remain pending."
}
@@ -0,0 +1,197 @@
use horizon_core::geometry::Vec3;
use horizon_world::living_fall::{
ImpulseContext, LivingFallWorld, calculate_fall_damage, cause_living_fall_damage,
};
use serde_json::Value;
use std::{
fs,
io::{self, Write},
path::Path,
};
fn number(v: &Value) -> f64 {
f64::from_bits(u64::from_str_radix(v.as_str().unwrap(), 16).unwrap())
}
fn single(v: &Value) -> f32 {
f32::from_bits(u32::from_str_radix(v.as_str().unwrap(), 16).unwrap())
}
fn bits(v: f64) -> u64 {
if v.is_nan() {
0x7ff8000000000000
} else {
v.to_bits()
}
}
fn vector(v: &Value) -> Option<Vec3> {
if v.is_null() {
None
} else {
Some(Vec3::new(number(&v[0]), number(&v[1]), number(&v[2])))
}
}
struct World<'a> {
f: &'a Value,
out: &'a mut dyn Write,
calls: i64,
immune_calls: usize,
hurt: usize,
impulse: ImpulseContext,
}
impl World<'_> {
fn query(&mut self, s: String) -> io::Result<()> {
writeln!(self.out, "{s}")?;
let call = self.calls;
self.calls += 1;
if call == self.f["fail"].as_i64().unwrap() {
Err(io::Error::other("injected living fall query failure"))
} else {
Ok(())
}
}
fn emit(&mut self) -> io::Result<()> {
let i = self.impulse;
writeln!(
self.out,
"grace {} {} {}",
i.reset_grace_time,
i.is_in_grace_time(),
i.ignores_fall_damage()
)?;
if let Some(p) = i.impact_position {
writeln!(
self.out,
"impact {:016x} {:016x} {:016x}",
bits(p.x),
bits(p.y),
bits(p.z)
)
} else {
writeln!(self.out, "impact none")
}
}
}
impl LivingFallWorld for World<'_> {
type Error = io::Error;
type Sound = bool;
fn impact_position(&self) -> Option<Vec3> {
self.impulse.impact_position
}
fn y(&self) -> f64 {
number(&self.f["y"])
}
fn reset_impulse(&mut self) -> io::Result<()> {
self.query("reset".into())?;
self.impulse.reset();
Ok(())
}
fn try_reset_impulse(&mut self) -> io::Result<()> {
self.query("try-reset".into())?;
if self.impulse.reset_grace_time == 0 {
self.reset_impulse()?;
}
Ok(())
}
fn immune_to_fall_damage(&mut self) -> io::Result<bool> {
self.query("immune".into())?;
let v = self.f["immune"].as_array().unwrap();
let value = v[self.immune_calls.min(v.len() - 1)].as_bool().unwrap();
self.immune_calls += 1;
Ok(value)
}
fn propagate_fall_to_passengers(&mut self, d: f64, m: f32) -> io::Result<()> {
self.query(format!(
"passengers {:016x} {:08x}",
bits(d),
if m.is_nan() { 0x7fc00000 } else { m.to_bits() }
))?;
if self.f["mutate"].as_bool().unwrap() {
self.impulse.reset_grace_time = 17;
self.impulse.impact_position = Some(Vec3::new(17.0, 18.0, 19.0));
}
Ok(())
}
fn safe_fall_distance(&mut self) -> io::Result<f64> {
self.query("safe".into())?;
Ok(number(&self.f["safe"]))
}
fn fall_damage_multiplier(&mut self) -> io::Result<f64> {
self.query("damage-factor".into())?;
Ok(number(&self.f["damage-factor"]))
}
fn fall_sound(&mut self, big: bool) -> io::Result<bool> {
self.query("sounds".into())?;
Ok(big)
}
fn play_fall_sound(&mut self, big: bool) -> io::Result<()> {
self.query(format!("sound {}", if big { "big" } else { "small" }))
}
fn play_block_fall_sound(&mut self) -> io::Result<()> {
self.query("block-sound".into())
}
fn hurt(&mut self, damage: f32) -> io::Result<()> {
if self.f["server"].as_bool().unwrap() {
self.query(format!("hurt {:08x}", damage.to_bits()))?;
self.hurt += 1;
}
Ok(())
}
}
fn run(world: &mut World<'_>) -> io::Result<()> {
let f = world.f;
match f["mode"].as_u64().unwrap() {
0 => {
let value =
calculate_fall_damage(world, number(&f["distance"]), single(&f["multiplier"]))?;
writeln!(world.out, "damage {value}")?;
}
1 => {
let value =
cause_living_fall_damage(world, number(&f["distance"]), single(&f["multiplier"]))?;
writeln!(world.out, "caused {value}")?;
}
2 => {
for op in f["ops"].as_array().unwrap() {
match op["mode"].as_u64().unwrap() {
0 => world.impulse.set_ignore_fall_damage(
op["ignore"].as_bool().unwrap(),
vector(&op["position"]),
),
1 => world
.impulse
.apply_grace_time(op["ticks"].as_i64().unwrap() as i32),
2 => world.try_reset_impulse()?,
3 => world.reset_impulse()?,
_ => unreachable!(),
};
world.emit()?;
}
}
_ => unreachable!(),
};
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let arg = std::env::args().nth(1).expect("private fixture directory");
let dir = Path::new(&arg);
let mut out = io::BufWriter::new(fs::File::create(dir.join("rust.txt"))?);
for line in fs::read_to_string(dir.join("inputs.txt"))?.lines() {
let f: Value = serde_json::from_str(line)?;
let mut world = World {
f: &f,
out: &mut out,
calls: 0,
immune_calls: 0,
hurt: 0,
impulse: ImpulseContext {
reset_grace_time: f["grace"].as_i64().unwrap() as i32,
impact_position: vector(&f["impact"]),
},
};
if run(&mut world).is_err() {
writeln!(world.out, "error")?;
}
world.emit()?;
writeln!(world.out, "hurt-count {}", world.hurt)?;
writeln!(world.out, "queries {}", world.calls)?;
}
Ok(())
}
+1
View File
@@ -23,6 +23,7 @@ pub mod fluid;
pub mod ground_support;
pub mod heightmap;
pub mod liquid_collision;
pub mod living_fall;
pub mod movement_properties;
pub mod movement_restitution;
pub mod movement_support;
+115
View File
@@ -0,0 +1,115 @@
//! Living-entity fall damage calculation, impulse exemption and callback order.
use horizon_core::geometry::Vec3;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ImpulseContext {
pub reset_grace_time: i32,
pub impact_position: Option<Vec3>,
}
impl ImpulseContext {
pub fn set_ignore_fall_damage(&mut self, ignore: bool, position: Option<Vec3>) {
if ignore {
self.apply_grace_time(40);
self.impact_position = position;
} else {
self.reset_grace_time = 0;
}
}
pub fn apply_grace_time(&mut self, ticks: i32) {
self.reset_grace_time = self.reset_grace_time.max(ticks);
}
pub fn reset(&mut self) {
self.reset_grace_time = 0;
self.impact_position = None;
}
pub fn try_reset(&mut self) {
if self.reset_grace_time == 0 {
self.reset();
}
}
pub fn is_in_grace_time(&self) -> bool {
self.reset_grace_time > 0
}
pub fn ignores_fall_damage(&self) -> bool {
self.impact_position.is_some()
}
}
pub trait LivingFallWorld {
type Error;
type Sound: Copy;
fn impact_position(&self) -> Option<Vec3>;
fn y(&self) -> f64;
fn reset_impulse(&mut self) -> Result<(), Self::Error>;
fn try_reset_impulse(&mut self) -> Result<(), Self::Error>;
fn immune_to_fall_damage(&mut self) -> Result<bool, Self::Error>;
fn propagate_fall_to_passengers(
&mut self,
distance: f64,
multiplier: f32,
) -> Result<(), Self::Error>;
fn safe_fall_distance(&mut self) -> Result<f64, Self::Error>;
fn fall_damage_multiplier(&mut self) -> Result<f64, Self::Error>;
/// Select the entity type's small/big fall sound. Big applies above four.
fn fall_sound(&mut self, big: bool) -> Result<Self::Sound, Self::Error>;
fn play_fall_sound(&mut self, sound: Self::Sound) -> Result<(), Self::Error>;
fn play_block_fall_sound(&mut self) -> Result<(), Self::Error>;
/// Dispatch native Entity.hurt semantics (server-only), preserving its source.
fn hurt(&mut self, damage: f32) -> Result<(), Self::Error>;
}
pub fn calculate_fall_damage<W: LivingFallWorld>(
world: &mut W,
distance: f64,
multiplier: f32,
) -> Result<i32, W::Error> {
if world.immune_to_fall_damage()? {
return Ok(0);
}
let power = distance + 1.0e-6 - world.safe_fall_distance()?;
Ok((power * f64::from(multiplier) * world.fall_damage_multiplier()?).floor() as i32)
}
fn java_min(a: f64, b: f64) -> f64 {
if a.is_nan() {
a
} else if b.is_nan() {
b
} else if a == 0.0 && b == 0.0 {
f64::from_bits(a.to_bits() | b.to_bits())
} else {
a.min(b)
}
}
/// Default LivingEntity and Entity fall behavior. Entity-type overrides of the
/// calculation/parent method are a separate dispatch layer. The same damage
/// source must be retained by the provider for riders and the final hurt call.
pub fn cause_living_fall_damage<W: LivingFallWorld>(
world: &mut W,
distance: f64,
multiplier: f32,
) -> Result<bool, W::Error> {
let distance = if let Some(impact) = world.impact_position() {
let distance = java_min(distance, impact.y - world.y());
if distance <= 0.0 {
world.reset_impulse()?;
} else {
world.try_reset_impulse()?;
}
distance
} else {
distance
};
// Base Entity.causeFallDamage returns false, even after notifying riders.
if !world.immune_to_fall_damage()? {
world.propagate_fall_to_passengers(distance, multiplier)?;
}
let damage = calculate_fall_damage(world, distance, multiplier)?;
if damage > 0 {
world.reset_impulse()?;
let sound = world.fall_sound(damage > 4)?;
world.play_fall_sound(sound)?;
world.play_block_fall_sound()?;
world.hurt(damage as f32)?;
Ok(true)
} else {
Ok(false)
}
}
+19
View File
@@ -669,3 +669,22 @@ records **44,566 matching lines per JAR**, including nonfinite arithmetic, float
underflow, callback mutations and failures at every boundary. Workspace Clippy
passes. Living-entity fluid/particle behavior, registered block landing effects,
health/damage and full movement remain pending.
`living_fall` implements default living-entity fall power/damage calculation,
impulse-height exemption and landing damage callback order. It retains separate
immunity checks for passenger propagation and damage calculation, double/float
arithmetic and saturating floor-to-int conversion. Positive damage resets the
impulse context, selects the fall sound, plays block landing sound and dispatches
hurt. A rejected hurt callback does not change the native positive-damage result.
`ImpulseContext` also models setter, grace-extension and reset behavior; disabling
ignore clears grace time while retaining the impact position until a later reset.
`python3 tools/test_living_fall_parity.py` compares actual living methods and
sequential impulse changes. The
[living fall report](../benchmarks/rust-living-fall-2026-10-07/scoped-parity.json)
records **153,025 matching lines per JAR**, including changing immunity, passenger
callback mutations, nonfinite arithmetic, sound threshold selection and failures
after partial state changes. Workspace Clippy passes. Attribute provisioning,
passenger storage/recursion, actual health processing, tick/save/network behavior
and the complete movement lifecycle remain pending.
@@ -0,0 +1,49 @@
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.tags.*;
import net.minecraft.sounds.*;
import net.minecraft.world.entity.*;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.entity.ai.attributes.*;
import net.minecraft.world.damagesource.*;
import net.minecraft.world.phys.Vec3;
/** Actual living fall calculation/cause methods and impulse context transitions. */
public final class VanillaLivingFallOracle {
static final class Stop extends RuntimeException {}
static double number(JsonElement v){return Double.longBitsToDouble(Long.parseUnsignedLong(v.getAsString(),16));}
static float single(JsonElement v){return Float.intBitsToFloat(Integer.parseUnsignedInt(v.getAsString(),16));}
static Vec3 vector(JsonElement v){if(v.isJsonNull())return null;var a=v.getAsJsonArray();return new Vec3(number(a.get(0)),number(a.get(1)),number(a.get(2)));}
static Field grace,impact;
static final class Data {JsonObject f;PrintWriter out;int calls,immuneCalls,hurt;void query(String s){out.println(s);if(calls++==f.get("fail").getAsInt())throw new Stop();}}
static final class Level extends net.minecraft.server.level.ServerLevel {private Level(){super(null,null,null,null,null,null,false,0L,List.of(),false);}}
static final class Actor extends ArmorStand {
Data d;Actor(){super(null,null);}void configure(Level level){setLevel(level);}
@Override public boolean is(TagKey<EntityType<?>> tag){if(tag!=EntityTypeTags.FALL_DAMAGE_IMMUNE)throw new AssertionError(tag);d.query("immune");var values=d.f.getAsJsonArray("immune");return values.get(Math.min(d.immuneCalls++,values.size()-1)).getAsBoolean();}
@Override public double getAttributeValue(Holder<Attribute> attribute){String name=attribute==Attributes.SAFE_FALL_DISTANCE?"safe":attribute==Attributes.FALL_DAMAGE_MULTIPLIER?"damage-factor":null;if(name==null)throw new AssertionError(attribute);d.query(name);return number(d.f.get(name));}
@Override public void resetCurrentImpulseContext(){d.query("reset");super.resetCurrentImpulseContext();}
@Override public void tryResetCurrentImpulseContext(){d.query("try-reset");super.tryResetCurrentImpulseContext();}
@Override protected void propagateFallToPassengers(double distance,float multiplier,DamageSource source){d.query(String.format("passengers %016x %08x",Double.doubleToLongBits(distance),Float.floatToIntBits(multiplier)));if(d.f.get("mutate").getAsBoolean()){try{grace.setInt(this,17);impact.set(this,new Vec3(17,18,19));}catch(ReflectiveOperationException e){throw new AssertionError(e);}}}
@Override public Fallsounds getFallSounds(){d.query("sounds");return new Fallsounds(SoundEvents.GENERIC_SMALL_FALL,SoundEvents.GENERIC_BIG_FALL);}
@Override public void playSound(SoundEvent sound,float volume,float pitch){if(volume!=1||pitch!=1)throw new AssertionError();d.query("sound "+(sound==SoundEvents.GENERIC_BIG_FALL?"big":"small"));}
@Override protected void playBlockFallSound(){d.query("block-sound");}
@Override public boolean hurtServer(net.minecraft.server.level.ServerLevel level,DamageSource source,float damage){d.query(String.format("hurt %08x",Float.floatToIntBits(damage)));d.hurt++;return d.f.get("hurt_result").getAsBoolean();}
int calculate(double distance,float multiplier){return calculateFallDamage(distance,multiplier);}
}
public static void main(String[] args)throws Exception {
net.minecraft.SharedConstants.tryDetectVersion();net.minecraft.server.Bootstrap.bootStrap();var allocator=Class.forName("sun.misc.Unsafe");var field=allocator.getDeclaredField("theUnsafe");field.setAccessible(true);var unsafe=field.get(null);var allocate=allocator.getMethod("allocateInstance",Class.class);
grace=LivingEntity.class.getDeclaredField("currentImpulseContextResetGraceTime");grace.setAccessible(true);impact=LivingEntity.class.getDeclaredField("currentImpulseImpactPos");impact.setAccessible(true);var position=Entity.class.getDeclaredField("position");position.setAccessible(true);Path dir=Path.of(args[1]);
try(var out=new PrintWriter(Files.newBufferedWriter(dir.resolve(args[0]+".txt")));var lines=Files.lines(dir.resolve("inputs.txt"))){
for(var line:(Iterable<String>)lines::iterator){
var f=JsonParser.parseString(line).getAsJsonObject();var d=new Data();d.f=f;d.out=out;var entity=(Actor)allocate.invoke(unsafe,Actor.class);entity.d=d;entity.configure(f.get("server").getAsBoolean()?(Level)allocate.invoke(unsafe,Level.class):null);grace.setInt(entity,f.get("grace").getAsInt());impact.set(entity,vector(f.get("impact")));position.set(entity,new Vec3(0,number(f.get("y")),0));
try{int mode=f.get("mode").getAsInt();if(mode==0)out.println("damage "+entity.calculate(number(f.get("distance")),single(f.get("multiplier"))));else if(mode==1)out.println("caused "+entity.causeFallDamage(number(f.get("distance")),single(f.get("multiplier")),null));else for(var value:f.getAsJsonArray("ops")){var op=value.getAsJsonObject();switch(op.get("mode").getAsInt()){case 0->entity.setIgnoreFallDamageFromCurrentImpulse(op.get("ignore").getAsBoolean(),vector(op.get("position")));case 1->entity.applyPostImpulseGraceTime(op.get("ticks").getAsInt());case 2->entity.tryResetCurrentImpulseContext();case 3->entity.resetCurrentImpulseContext();default->throw new AssertionError();}emit(out,entity);} }
catch(Stop error){out.println("error");}emit(out,entity);out.println("hurt-count "+d.hurt);out.println("queries "+d.calls);
}
}
}
static void emit(PrintWriter out,Actor entity)throws Exception{var p=(Vec3)impact.get(entity);out.println("grace "+grace.getInt(entity)+" "+entity.isInPostImpulseGraceTime()+" "+entity.isIgnoringFallDamageFromCurrentImpulse());if(p==null)out.println("impact none");else out.printf("impact %016x %016x %016x%n",Double.doubleToLongBits(p.x),Double.doubleToLongBits(p.y),Double.doubleToLongBits(p.z));}
}
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""Compare living fall damage calculation, callbacks and impulse context with vanilla."""
import argparse
import json
import os
from pathlib import Path
import platform
import subprocess
import random
import struct
import math
try:
from . import lab, test_rust_parity as base
except ImportError:
import lab
import test_rust_parity as base
def corpus():
def bits(v):return struct.pack('>d',float(v)).hex()
def single(v):
try:return struct.pack('>f',float(v)).hex()
except OverflowError:return struct.pack('>f',math.copysign(float('inf'),v)).hex()
def vector(v):return None if v is None else list(map(bits,v))
def row(mode=1,distance=10,multiplier=1,safe=3,damage=1,immune=(False,False),impact=None,grace=0,y=0,mutate=False,fail=-1,server=True,hurt_result=True,ops=()):return dict(mode=mode,distance=bits(distance),multiplier=single(multiplier),safe=bits(safe),**{'damage-factor':bits(damage)},immune=list(immune),impact=vector(impact),grace=grace,y=bits(y),mutate=mutate,fail=fail,server=server,hurt_result=hurt_result,ops=list(ops))
values=[-float('inf'),-1,-0.0,0,2.999999,3,3.999999,4,7,8,10,1e40,float('inf'),float('nan')]
for mode in [0,1]:
for distance in values:
for multiplier in [-1,-0.0,0,.5,1,2,float('nan')]:
for safe in [0,3,5,float('nan')]:
for immune in [(False,False),(True,True),(False,True),(True,False)]:yield row(mode,distance,multiplier,safe,immune=immune)
for distance in [-0.0,0,4,10,float('nan')]:
for impact in [None,[0,-1,0],[0,-0.0,0],[0,0,0],[0,2,0],[0,10,0],[0,float('nan'),0]]:
for grace in [-1,0,1,40]:
for fail in range(-1,13):yield row(distance=distance,impact=impact,grace=grace,fail=fail,mutate=True)
rng=random.Random(0x4c4956494e474641)
for _ in range(4000):yield row(mode=rng.randrange(2),distance=rng.choice(values),multiplier=rng.choice(values),safe=rng.choice(values),damage=rng.choice(values),immune=(bool(rng.randrange(2)),bool(rng.randrange(2))),impact=rng.choice([None,[0,0,0],[1,5,2],[0,float('inf'),0]]),grace=rng.choice([-1,0,1,40]),y=rng.choice(values),mutate=bool(rng.randrange(2)),fail=rng.choice([-1,-1,0,1,2,4,7,10]),server=bool(rng.randrange(2)),hurt_result=bool(rng.randrange(2)))
for _ in range(2000):
ops=[]
for _ in range(20):ops.append(dict(mode=rng.randrange(4),ignore=bool(rng.randrange(2)),position=vector(rng.choice([None,[0,0,0],[17,-64,-33]])),ticks=rng.choice([-2147483648,-1,0,1,40,100,2147483647])))
yield row(mode=2,impact=rng.choice([None,[1,2,3]]),grace=rng.choice([-1,0,1,40,2147483647]),ops=ops,fail=rng.choice([-1,-1,0,3,7,12]))
def main():
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--prepare',action='store_true');args=parser.parse_args()
build=lab.LOCAL/'rust-living-fall-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/VanillaLivingFallOracle.java'
sources=[oracle,Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/lab.py',lab.ROOT/'Cargo.toml',lab.ROOT/'Cargo.lock',lab.ROOT/'crates/horizon-core/Cargo.toml',lab.ROOT/'crates/horizon-world/Cargo.toml',*sorted((lab.ROOT/'crates/horizon-core/src').rglob('*.rs')),*sorted((lab.ROOT/'crates/horizon-world/src').rglob('*.rs')),lab.ROOT/'crates/horizon-world/examples/living_fall_fixture.rs']
tested_sources=base.source_hashes(sources)
classes=build/'classes';classes.mkdir(exist_ok=True)
def run(command,name):
with (build/(name+'.log')).open('w',encoding='utf-8') as log:base.run(list(map(str,command)),stdout=log,stderr=subprocess.STDOUT)
dependency_path=os.pathsep.join(map(str,libraries))
run(['javac','--release',lock['minecraft']['java_major'],'-proc:none','-cp',str(jars['server'])+os.pathsep+dependency_path,'-d',classes,oracle],'compile-java')
def java(side,mode,path):
run(['java','-Xmx512M','-cp',os.pathsep.join([str(classes),str(jars[side]),dependency_path]),'dev.horizonlab.parity.VanillaLivingFallOracle',mode,path],side+'-'+mode)
fixtures=list(corpus());inputs=build/'inputs.txt';inputs.write_text(''.join(json.dumps(value,separators=(',',':'))+'\n' for value in fixtures),encoding='utf-8')
fixture_paths=[inputs];tested_fixtures=base.source_hashes(fixture_paths)
run(['cargo','build','--release','-p','horizon-world','--example','living_fall_fixture'],'compile-rust')
metadata=json.loads(subprocess.check_output(['cargo','metadata','--no-deps','--format-version','1'],cwd=lab.ROOT,text=True))
binary=Path(metadata['target_directory'])/'release/examples'/('living_fall_fixture.exe' if os.name=='nt' else 'living_fall_fixture')
run([binary,build],'rust');references=[]
for side,jar in jars.items():
java(side,side,build);reference=build/f'{side}.txt';matched=base.compare(reference,build/'rust.txt')
references.append({'side':side,'jar_sha256':lab.digest(jar,'sha256'),'output_sha256':lab.digest(reference,'sha256'),'matched_output_lines':matched})
print(f'{side}: {matched:,} living-fall output lines match Rust',flush=True)
base.ensure_sources_unchanged(sources,tested_sources);base.ensure_sources_unchanged(fixture_paths,tested_fixtures)
report={'schema':1,'minecraft':lock['minecraft']['version'],'manifest_sha1':lock['minecraft']['manifest_sha1'],'platform':platform.platform(),'java':subprocess.check_output(['java','--version'],text=True).splitlines()[0],'rustc':subprocess.check_output(['rustc','--version'],text=True).strip(),'fixtures':len(fixtures),'sources_sha256':tested_sources,'fixtures_sha256':tested_fixtures,'rust_output_sha256':lab.digest(build/'rust.txt','sha256'),'references':references,'scope':'Actual LivingEntity.calculateFallDamage, causeFallDamage and impulse-context setter/reset/grace methods. Controlled immunity/attribute getters, passenger callback, sounds and server hurt callback expose native query order and failure state. Includes changing immunity, passenger mutations, impulse-height limiting, zero/negative/large grace values, float multiplier widening, floor/saturation, signed zero/nonfinite values and setter sequences. Hurt return values do not change the causeFallDamage result. A null level snapshot exercises the native non-server hurt no-op; actual client entity lifecycle, passenger storage/recursion, health/damage processing, attribute provisioning and tick/save/network integration remain pending.'}
lab.write_json(report_path,report);print(f'Scoped living fall parity passed: {report_path}')
if __name__=='__main__':main()