Match registered block landing and bounce reactions

This commit is contained in:
Ludovic Marie committed 2026-10-07 01:37:33 +02:00
1 parent 2e3866837c
commit 6cea2835e0
8 files changed
+1320

No files matched your search

@@ -0,0 +1,228 @@
{
"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)",
"registry_states": 35723,
"registry_blocks": 1286,
"fixtures": 712544,
"sources_sha256": {
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaBlockLandingOracle.java": "e397acdbaf2702f358c90ba53a804afa26d7f29a278884c0e779b18f1ae40e63",
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaMovementRegistryOracle.java": "49fcc1f4348a8e279dcb916ae149de8c97f15df54ff1a675826b7c6c3dda06d9",
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaHeightmapOracle.java": "145c943637ed177cc4ef8a85f2484da8afd164868e84a8463882feec1653a860",
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaCollisionOracle.java": "f2b4f440a5bbce4224a4b3cee494b0f7ddcafbf72db12592c94ef9fb94fbc8a6",
"tools/test_block_landing_parity.py": "6785ecf77217ec5698db4ee532624401955eabeaa8b545175b4ccca7250f1f52",
"tools/test_rust_parity.py": "29d3db08005873528894b45ca10dcd6278753650473b3eb574e53e06ede40ec6",
"tools/lab.py": "d00bf107fa01b6c579734a082c3ca4ade3018f0d6851d46844acf17574f66e64",
"Cargo.toml": "3f93319c1eb70dbd7ba25cd74ef343fbf35c52c7c4752f955605c58699cbff9d",
"Cargo.lock": "cb1e87d449114a63edd904c0559d631e8fa9feeb27efd6539aacdcfeae178876",
"crates/horizon-world/examples/block_landing_fixture.rs": "7bbcacc9b67321d1d12684c488c73f4935e3a22f4c8e5141f93673d5918b4efb",
"crates/horizon-core/Cargo.toml": "e21b502c949da93cf67826592459b687a8cbf737ef92fee514e3ed2cc7cd27ad",
"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/Cargo.toml": "977338bf3e654193685b015d383493b9f59ff6f5d6210bec212cef8ac4a50bed",
"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": "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": "525f4245d9125f7007a3c1d63ab1ac327aac6e6e4134fb25f56a996bf8859c41",
"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": "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/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"
},
"fixtures_sha256": {
".local/rust-block-landing-parity/inputs.txt": "44b0aec7dc5b05175f1562b0afa6b64c67cf0fc5370c8006341cb9f45da54119",
".local/rust-block-landing-parity/landing-states.json": "f4a8503a3d5a141b158971fb9bc37bcc90086401b3f5b6b37f2352fa9beb41a4",
".local/rust-block-landing-parity/landing-definitions.json": "03f7a3aaa6e52c0fabb845fb7a43054538771d14e47e5d8f9a88bb907b7780a9",
".local/rust-block-landing-parity/movement-properties.nbt": "4b5646cbb675025efc9ac6e24b024ac2edddfa6725eb8e158357beda622a3888",
".local/rust-block-landing-parity/block-registry.nbt": "5042073c80753f9548cb8fdb5b1000f0e5a1220127a0cc3c6a75737e180bf9bb",
".local/rust-block-landing-parity/cached-collision.nbt": "4a274cd720a1bda585b02f50f16e90246576679e1e173c214548c5cbf607ab2d"
},
"rust_output_sha256": "dca0477ebdf14f4da98f71d1c77a0df01db9c04dd8a381d9b2426de6a4659feb",
"references": [
{
"side": "client",
"jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d",
"output_sha256": "dca0477ebdf14f4da98f71d1c77a0df01db9c04dd8a381d9b2426de6a4659feb",
"matched_output_lines": 3524267
},
{
"side": "server",
"jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6",
"output_sha256": "dca0477ebdf14f4da98f71d1c77a0df01db9c04dd8a381d9b2426de6a4659feb",
"matched_output_lines": 3524267
}
],
"scope": "Actual registered Block.fallOn and bounceOn across all states and eight entity class families, plus client/server distinctions, damage return, bounce suppression, protection/rules, random boundaries, NaN/infinity and failures at each observable callback. Farmland replacement uses native registered collision shapes and ordered occupant pushes; turtle egg changes preserve hatch and compare write flags, event order and effects after failed writes. Entity damage, randomness, selected entities, sounds, world writes and events are controlled providers. Live health, entity storage, network, audio and full movement lifecycle integration remain separate work."
}
@@ -0,0 +1,326 @@
use horizon_core::{
geometry::{Aabb, Axis, Vec3, VoxelShape},
position::BlockPos,
};
use horizon_world::{
block_entity_push::BlockPushWorld,
block_landing::*,
collision::CachedCollisionRegistry,
entity_obstruction::EntityIdentity,
movement_properties::MovementProperties,
nbt::{self, Limits, Tag},
registry::{BlockRegistry, BlockStateId},
};
use serde_json::Value;
use std::{
collections::BTreeMap,
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 fbits(v: f32) -> u32 {
if v.is_nan() { 0x7fc00000 } else { v.to_bits() }
}
fn pos(p: BlockPos) -> String {
format!("{} {} {}", p.x, p.y, p.z)
}
fn bbox(v: &Value) -> Aabb {
Aabb::new(
Vec3::new(number(&v[0]), number(&v[1]), number(&v[2])),
Vec3::new(number(&v[3]), number(&v[4]), number(&v[5])),
)
}
fn bounds(label: &str, b: Aabb) -> String {
format!(
"{label} {:016x} {:016x} {:016x} {:016x} {:016x} {:016x}",
bits(b.min(Axis::X)),
bits(b.min(Axis::Y)),
bits(b.min(Axis::Z)),
bits(b.max(Axis::X)),
bits(b.max(Axis::Y)),
bits(b.max(Axis::Z))
)
}
struct World<'a, 'r> {
f: &'a Value,
out: &'a mut dyn Write,
calls: i64,
state: BlockStateId,
air: BlockStateId,
collision: &'a CachedCollisionRegistry<'r>,
entities: Vec<Aabb>,
}
impl World<'_, '_> {
fn flag(&self, k: &str) -> bool {
self.f[k].as_bool().unwrap()
}
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 landing failure"))
} else {
Ok(())
}
}
}
impl BlockPushWorld for World<'_, '_> {
type Error = io::Error;
fn collision_shape(&mut self, s: BlockStateId, _: BlockPos) -> io::Result<VoxelShape> {
Ok(self
.collision
.cached_shape(s)?
.expect("registered farmland/base shapes cached")
.clone())
}
fn entities(&mut self, b: Aabb) -> io::Result<Vec<EntityIdentity>> {
self.query(bounds("entities", b))?;
Ok((0..self.entities.len())
.map(|i| EntityIdentity(i as u64))
.collect())
}
fn entity_bounds(&self, e: EntityIdentity) -> Aabb {
self.entities[e.0 as usize]
}
fn teleport_relative(&mut self, e: EntityIdentity, v: Vec3) -> io::Result<()> {
self.query(format!(
"teleport {} {:016x} {:016x} {:016x}",
e.0,
bits(v.x),
bits(v.y),
bits(v.z)
))?;
let i = e.0 as usize;
self.entities[i] = self.entities[i].move_by(v);
if self.flag("mutate") && i + 1 < self.entities.len() {
self.entities[i + 1] = self.entities[i + 1].move_by(Vec3::new(0.0, 0.125, 0.0));
}
Ok(())
}
}
impl BlockLandingWorld for World<'_, '_> {
type DamageSource = &'static str;
type Sound = &'static str;
fn is_server_level(&self) -> bool {
self.flag("server")
}
fn is_client_side(&mut self) -> io::Result<bool> {
self.query("client".into())?;
Ok(self.flag("client"))
}
fn random_float(&mut self) -> io::Result<f32> {
self.query("random".into())?;
self.query("float".into())?;
Ok(single(&self.f["random_float"]))
}
fn random_int(&mut self, b: i32) -> io::Result<i32> {
self.query("random".into())?;
self.query(format!("int {b}"))?;
Ok(self.f["random_int"].as_i64().unwrap() as i32)
}
fn under_spawn_protection(&mut self, p: BlockPos) -> io::Result<bool> {
self.query("server".into())?;
self.query(format!("protection {}", pos(p)))?;
Ok(self.flag("protected"))
}
fn mob_griefing(&mut self) -> io::Result<bool> {
self.query("rules".into())?;
self.query("mob-griefing".into())?;
Ok(self.flag("griefing"))
}
fn entity_dimensions(&self) -> (f32, f32) {
(single(&self.f["width"]), single(&self.f["height"]))
}
fn suppressing_bounce(&mut self) -> io::Result<bool> {
self.query("suppress".into())?;
Ok(self.flag("suppress"))
}
fn damage_source(
&mut self,
o: DamageSourceOwner,
k: LandingDamage,
) -> io::Result<Self::DamageSource> {
self.query(
match o {
DamageSourceOwner::Entity => "entity-sources",
DamageSourceOwner::Level => "level-sources",
}
.into(),
)?;
let k = match k {
LandingDamage::Fall => "fall",
LandingDamage::Stalagmite => "stalagmite",
};
self.query(format!("{k}-source"))?;
Ok(k)
}
fn cause_fall_damage(&mut self, d: f64, m: f32, s: Self::DamageSource) -> io::Result<bool> {
self.query(format!("damage {:016x} {:08x} {s}", bits(d), fbits(m)))?;
Ok(self.flag("damage"))
}
fn known_sound(&self, s: LandingSound) -> Self::Sound {
match s {
LandingSound::HoneySlide => "honey-slide",
LandingSound::HoneyFall => "honey-fall",
LandingSound::TurtleEggBreak => "egg-break",
LandingSound::ShelfMushroomBounce => "mushroom",
}
}
fn living_fall_sound(&mut self, big: bool) -> io::Result<Self::Sound> {
self.query("fall-sounds".into())?;
Ok(if big { "big" } else { "small" })
}
fn entity_sound(&mut self, s: Self::Sound, v: f32, p: f32) -> io::Result<()> {
self.query(format!(
"entity-sound {s} {:08x} {:08x}",
fbits(v),
fbits(p)
))
}
fn block_sound(&mut self, p: BlockPos, s: Self::Sound, v: f32, pitch: f32) -> io::Result<()> {
self.query(format!(
"block-sound {} {s} {:08x} {:08x}",
pos(p),
fbits(v),
fbits(pitch)
))
}
fn broadcast_entity_event(&mut self, e: u8) -> io::Result<()> {
self.query(format!("broadcast {e}"))
}
fn destroy_block(&mut self, p: BlockPos, drops: bool) -> io::Result<bool> {
self.query(format!("destroy {} {drops}", pos(p)))?;
if self.flag("write") {
self.state = self.air;
}
Ok(self.flag("write"))
}
fn set_block(&mut self, p: BlockPos, s: BlockStateId, flags: u32) -> io::Result<bool> {
self.query(format!("set {} {} {flags}", pos(p), s.0))?;
if self.flag("write") {
self.state = s;
}
Ok(self.flag("write"))
}
fn block_event(
&mut self,
e: LandingEvent,
p: BlockPos,
s: BlockStateId,
entity: bool,
) -> io::Result<()> {
self.query(format!(
"event {} {} {} {entity}",
match e {
LandingEvent::BlockChange => "change",
LandingEvent::BlockDestroy => "destroy",
},
pos(p),
s.0
))
}
fn level_event(&mut self, e: i32, p: BlockPos, d: i32) -> io::Result<()> {
self.query(format!("level-event {e} {} {d}", pos(p)))
}
}
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"))?);
let read = |name: &str| -> Result<Tag, Box<dyn std::error::Error>> {
Ok(
nbt::read_named(&fs::read(dir.join(name))?, Limits::default())?
.0
.tag,
)
};
let registry = BlockRegistry::from_nbt(&read("block-registry.nbt")?)?;
let data = read("movement-properties.nbt")?;
let properties = MovementProperties::from_nbt(&data, &registry)?;
let collision = CachedCollisionRegistry::from_nbt(&read("cached-collision.nbt")?, &registry)?;
let raw: BTreeMap<String, Value> =
serde_json::from_str(&fs::read_to_string(dir.join("landing-definitions.json"))?)?;
let mut definitions = BTreeMap::new();
for (name, d) in raw {
let fall = match d["fall"].as_str().unwrap() {
"Block" => FallDefinition::Default,
"HayBlock" => FallDefinition::Hay,
"HoneyBlock" => FallDefinition::Honey {
volume: single(&d["volume"]),
pitch: single(&d["pitch"]),
},
"SlimeBlock" => FallDefinition::Slime,
"PowderSnowBlock" => FallDefinition::PowderSnow,
"PointedDripstoneBlock" => FallDefinition::PointedDripstone,
"FarmlandBlock" => FallDefinition::Farmland {
base_block: d["base"].as_str().unwrap().into(),
},
"TurtleEggBlock" => FallDefinition::TurtleEgg,
_ => panic!("unknown landing family"),
};
definitions.insert(
name,
LandingDefinition {
fall,
shelf_mushroom_bounce: d["bounce"].as_bool().unwrap(),
},
);
}
let landing = BlockLandingRegistry::new(&properties, &definitions)?;
let kinds = [
LandingEntityKind::Other,
LandingEntityKind::Living,
LandingEntityKind::Player,
LandingEntityKind::Zombie,
LandingEntityKind::Turtle,
LandingEntityKind::Bat,
LandingEntityKind::Item,
LandingEntityKind::PrimedTnt,
];
for line in fs::read_to_string(dir.join("inputs.txt"))?.lines() {
let f: Value = serde_json::from_str(line)?;
let s = BlockStateId(f["state"].as_u64().unwrap() as u32);
let p = &f["position"];
let p = BlockPos::new(
p[0].as_i64().unwrap() as i32,
p[1].as_i64().unwrap() as i32,
p[2].as_i64().unwrap() as i32,
);
let kind = kinds[f["kind"].as_u64().unwrap() as usize];
let mut world = World {
f: &f,
out: &mut out,
calls: 0,
state: s,
air: registry.default_state("minecraft:air").unwrap(),
collision: &collision,
entities: f["entities"].as_array().unwrap().iter().map(bbox).collect(),
};
let result = if world.flag("bounce") {
landing.bounce_on(&mut world, s, p, kind)
} else {
landing.fall_on(&mut world, s, p, kind, number(&f["distance"]))
};
writeln!(world.out, "{}", if result.is_ok() { "ok" } else { "error" })?;
writeln!(world.out, "state {}", world.state.0)?;
for (i, b) in world.entities.iter().enumerate() {
writeln!(world.out, "{}", bounds(&format!("final {i}"), *b))?;
}
writeln!(world.out, "queries {}", world.calls)?;
}
Ok(())
}
+437
View File
@@ -0,0 +1,437 @@
//! Registered block landing/bounce reactions and their ordered world effects.
use crate::{
block_entity_push::{BlockPushError, BlockPushWorld, push_entities_up},
compression::invalid,
movement_properties::MovementProperties,
registry::BlockStateId,
};
use horizon_core::position::BlockPos;
use std::{collections::BTreeMap, fmt, io};
#[derive(Clone, Debug)]
pub enum FallDefinition {
Default,
Hay,
Honey { volume: f32, pitch: f32 },
Slime,
PowderSnow,
PointedDripstone,
Farmland { base_block: String },
TurtleEgg,
}
#[derive(Clone, Debug)]
pub struct LandingDefinition {
pub fall: FallDefinition,
pub shelf_mushroom_bounce: bool,
}
#[derive(Clone, Copy, Debug)]
enum Rule {
Default,
Hay,
Honey { volume: f32, pitch: f32 },
Slime,
PowderSnow,
DripstoneTip,
Farmland { base: BlockStateId },
TurtleEgg { remaining: Option<BlockStateId> },
}
#[derive(Clone, Copy, Debug)]
struct State {
rule: Rule,
reduction: f32,
shelf_mushroom_bounce: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LandingEntityKind {
Other,
Living,
Player,
Zombie,
Turtle,
Bat,
Item,
PrimedTnt,
}
impl LandingEntityKind {
fn living(self) -> bool {
matches!(
self,
Self::Living | Self::Player | Self::Zombie | Self::Turtle | Self::Bat
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LandingSound {
HoneySlide,
HoneyFall,
TurtleEggBreak,
ShelfMushroomBounce,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LandingDamage {
Fall,
Stalagmite,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DamageSourceOwner {
Entity,
Level,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LandingEvent {
BlockChange,
BlockDestroy,
}
pub trait BlockLandingWorld: BlockPushWorld {
type DamageSource: Copy;
type Sound: Copy;
fn is_server_level(&self) -> bool;
fn is_client_side(&mut self) -> Result<bool, Self::Error>;
fn random_float(&mut self) -> Result<f32, Self::Error>;
fn random_int(&mut self, bound: i32) -> Result<i32, Self::Error>;
fn under_spawn_protection(&mut self, position: BlockPos) -> Result<bool, Self::Error>;
fn mob_griefing(&mut self) -> Result<bool, Self::Error>;
fn entity_dimensions(&self) -> (f32, f32);
fn suppressing_bounce(&mut self) -> Result<bool, Self::Error>;
fn damage_source(
&mut self,
owner: DamageSourceOwner,
kind: LandingDamage,
) -> Result<Self::DamageSource, Self::Error>;
fn cause_fall_damage(
&mut self,
distance: f64,
multiplier: f32,
source: Self::DamageSource,
) -> Result<bool, Self::Error>;
fn known_sound(&self, sound: LandingSound) -> Self::Sound;
fn living_fall_sound(&mut self, big: bool) -> Result<Self::Sound, Self::Error>;
fn entity_sound(
&mut self,
sound: Self::Sound,
volume: f32,
pitch: f32,
) -> Result<(), Self::Error>;
/// Sound source BLOCKS, with no excluded/source entity.
fn block_sound(
&mut self,
position: BlockPos,
sound: Self::Sound,
volume: f32,
pitch: f32,
) -> Result<(), Self::Error>;
fn broadcast_entity_event(&mut self, event: u8) -> Result<(), Self::Error>;
fn destroy_block(&mut self, position: BlockPos, drops: bool) -> Result<bool, Self::Error>;
fn set_block(
&mut self,
position: BlockPos,
state: BlockStateId,
flags: u32,
) -> Result<bool, Self::Error>;
fn block_event(
&mut self,
event: LandingEvent,
position: BlockPos,
state: BlockStateId,
with_entity: bool,
) -> Result<(), Self::Error>;
fn level_event(&mut self, event: i32, position: BlockPos, data: i32)
-> Result<(), Self::Error>;
}
#[derive(Debug)]
pub enum LandingError<E> {
Data(io::Error),
World(E),
Push(BlockPushError<E>),
}
impl<E: fmt::Display> fmt::Display for LandingError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Data(e) => e.fmt(f),
Self::World(e) => e.fmt(f),
Self::Push(e) => e.fmt(f),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for LandingError<E> {}
pub struct BlockLandingRegistry {
states: Vec<State>,
}
impl BlockLandingRegistry {
pub fn new(
properties: &MovementProperties<'_>,
definitions: &BTreeMap<String, LandingDefinition>,
) -> io::Result<Self> {
let registry = properties.registry();
if definitions
.keys()
.any(|name| registry.default_state(name).is_none())
{
return Err(invalid("unknown landing definition"));
}
let mut states = Vec::with_capacity(registry.len());
for index in 0..registry.len() {
let id = BlockStateId(index as u32);
let state = registry.state(id).unwrap();
let definition = definitions
.get(&state.name)
.ok_or_else(|| invalid("missing landing definition"))?;
let rule = match &definition.fall {
FallDefinition::Default => Rule::Default,
FallDefinition::Hay => Rule::Hay,
FallDefinition::Honey { volume, pitch } => Rule::Honey {
volume: *volume,
pitch: *pitch,
},
FallDefinition::Slime => Rule::Slime,
FallDefinition::PowderSnow => Rule::PowderSnow,
FallDefinition::PointedDripstone => {
let direction = state
.properties
.get("vertical_direction")
.map(String::as_str);
let thickness = state.properties.get("thickness").map(String::as_str);
if !matches!(direction, Some("up" | "down"))
|| !matches!(
thickness,
Some("tip" | "tip_merge" | "frustum" | "middle" | "base")
)
{
return Err(invalid("invalid landing dripstone state"));
}
if direction == Some("up") && thickness == Some("tip") {
Rule::DripstoneTip
} else {
Rule::Default
}
}
FallDefinition::Farmland { base_block } => Rule::Farmland {
base: registry
.default_state(base_block)
.ok_or_else(|| invalid("unknown farmland base block"))?,
},
FallDefinition::TurtleEgg => {
let eggs = state
.properties
.get("eggs")
.and_then(|s| s.parse::<i32>().ok())
.filter(|v| (1..=4).contains(v))
.ok_or_else(|| invalid("invalid landing egg count"))?;
let remaining = if eggs == 1 {
None
} else {
let mut next = state.clone();
next.properties
.insert("eggs".into(), (eggs - 1).to_string());
Some(
registry
.id(&next)
.ok_or_else(|| invalid("missing decremented egg state"))?,
)
};
Rule::TurtleEgg { remaining }
}
};
states.push(State {
rule,
reduction: properties.coefficients(id)?.fall_distance_reduction,
shelf_mushroom_bounce: definition.shelf_mushroom_bounce,
});
}
Ok(Self { states })
}
fn state<E>(&self, id: BlockStateId) -> Result<State, LandingError<E>> {
self.states
.get(id.0 as usize)
.copied()
.ok_or_else(|| LandingError::Data(invalid("landing state outside registry")))
}
pub fn bounce_on<W: BlockLandingWorld>(
&self,
world: &mut W,
state: BlockStateId,
position: BlockPos,
entity: LandingEntityKind,
) -> Result<(), LandingError<W::Error>> {
if self.state(state)?.shelf_mushroom_bounce
&& !matches!(
entity,
LandingEntityKind::Item | LandingEntityKind::PrimedTnt
)
{
let sound = world.known_sound(LandingSound::ShelfMushroomBounce);
world
.block_sound(position, sound, 1.0, 1.0)
.map_err(LandingError::World)?;
}
Ok(())
}
pub fn fall_on<W: BlockLandingWorld>(
&self,
world: &mut W,
state: BlockStateId,
position: BlockPos,
entity: LandingEntityKind,
distance: f64,
) -> Result<(), LandingError<W::Error>> {
let data = self.state(state)?;
match data.rule {
Rule::Default => {}
Rule::Hay => {
cause(
world,
DamageSourceOwner::Level,
LandingDamage::Fall,
distance,
0.2,
)?;
return Ok(());
}
Rule::Honey { volume, pitch } => {
let sound = world.known_sound(LandingSound::HoneySlide);
world
.entity_sound(sound, 1.0, 1.0)
.map_err(LandingError::World)?;
if !world.is_client_side().map_err(LandingError::World)? {
world
.broadcast_entity_event(54)
.map_err(LandingError::World)?;
}
if cause(
world,
DamageSourceOwner::Level,
LandingDamage::Fall,
distance,
0.2,
)? {
let sound = world.known_sound(LandingSound::HoneyFall);
world
.entity_sound(sound, volume * 0.5, pitch * 0.75)
.map_err(LandingError::World)?;
}
return Ok(());
}
Rule::Slime => {
if !world.suppressing_bounce().map_err(LandingError::World)? {
cause(
world,
DamageSourceOwner::Level,
LandingDamage::Fall,
distance,
0.0,
)?;
}
return Ok(());
}
Rule::PowderSnow => {
if (distance >= 4.0 || distance.is_nan()) && entity.living() {
let sound = world
.living_fall_sound(distance >= 7.0 || distance.is_nan())
.map_err(LandingError::World)?;
world
.entity_sound(sound, 1.0, 1.0)
.map_err(LandingError::World)?;
}
return Ok(());
}
Rule::DripstoneTip => {
cause(
world,
DamageSourceOwner::Level,
LandingDamage::Stalagmite,
distance + 2.5,
2.0,
)?;
return Ok(());
}
Rule::Farmland { base } => {
if world.is_server_level()
&& f64::from(world.random_float().map_err(LandingError::World)?)
< distance - 0.5
&& entity.living()
{
let allowed = if entity == LandingEntityKind::Player {
!world
.under_spawn_protection(position)
.map_err(LandingError::World)?
} else {
world.mob_griefing().map_err(LandingError::World)?
};
if allowed {
let (width, height) = world.entity_dimensions();
if width * width * height > 0.512f32 {
let next = push_entities_up(world, state, base, position)
.map_err(LandingError::Push)?;
world
.set_block(position, next, 3)
.map_err(LandingError::World)?;
world
.block_event(LandingEvent::BlockChange, position, next, true)
.map_err(LandingError::World)?;
}
}
}
}
Rule::TurtleEgg { remaining } => {
if !matches!(
entity,
LandingEntityKind::Zombie | LandingEntityKind::Turtle | LandingEntityKind::Bat
) && world.is_server_level()
{
let allowed = if entity == LandingEntityKind::Player {
!world
.under_spawn_protection(position)
.map_err(LandingError::World)?
} else {
entity.living() && world.mob_griefing().map_err(LandingError::World)?
};
if allowed && world.random_int(3).map_err(LandingError::World)? == 0 {
let sound = world.known_sound(LandingSound::TurtleEggBreak);
let pitch =
0.9f32 + world.random_float().map_err(LandingError::World)? * 0.2f32;
world
.block_sound(position, sound, 0.7, pitch)
.map_err(LandingError::World)?;
if let Some(next) = remaining {
world
.set_block(position, next, 2)
.map_err(LandingError::World)?;
world
.block_event(LandingEvent::BlockDestroy, position, state, false)
.map_err(LandingError::World)?;
world
.level_event(2001, position, state.0 as i32)
.map_err(LandingError::World)?;
} else {
world
.destroy_block(position, false)
.map_err(LandingError::World)?;
}
}
}
}
}
cause(
world,
DamageSourceOwner::Entity,
LandingDamage::Fall,
distance * f64::from(1.0f32 - data.reduction),
1.0,
)?;
Ok(())
}
}
fn cause<W: BlockLandingWorld>(
world: &mut W,
owner: DamageSourceOwner,
kind: LandingDamage,
distance: f64,
multiplier: f32,
) -> Result<bool, LandingError<W::Error>> {
let source = world
.damage_source(owner, kind)
.map_err(LandingError::World)?;
world
.cause_fall_damage(distance, multiplier, source)
.map_err(LandingError::World)
}
+1
View File
@@ -5,6 +5,7 @@ pub mod block_collision;
pub mod block_collision_scan;
pub mod block_entities;
pub mod block_entity_push;
pub mod block_landing;
pub mod block_placement;
pub mod block_properties;
pub mod block_render;
@@ -43,6 +43,17 @@ pub struct MovementProperties<'r> {
definitions: BTreeMap<String, MovementCoefficients>,
}
impl<'r> MovementProperties<'r> {
pub fn registry(&self) -> &'r BlockRegistry {
self.registry
}
pub fn coefficients(&self, state: BlockStateId) -> io::Result<MovementCoefficients> {
let state = self
.registry
.state(state)
.ok_or_else(|| invalid("movement state outside registry"))?;
Ok(self.definitions[&state.name])
}
pub fn from_nbt(tag: &Tag, registry: &'r BlockRegistry) -> io::Result<Self> {
if tag.get("DataVersion") != Some(&Tag::Int(DATA_VERSION)) {
return Err(invalid("movement properties DataVersion mismatch"));
+17
View File
@@ -704,3 +704,20 @@ partial shapes, large coordinates, nonfinite entity boxes, mutations of later
entities and failures after partial effects. Workspace Clippy passes. Registered
replacement dispatch, actual entity selection and teleport/network semantics
remain separate integration work.
`block_landing` binds audited native landing/bounce families to every registered
state. It implements fall-distance reduction, hay/honey damage multipliers,
slime suppression, powder-snow fall sounds, upward dripstone tips, farmland
replacement with occupant pushes, turtle-egg destruction/decrement and shelf
mushroom bounce sounds. Random draws, server-class checks, client flags, spawn
protection, mob griefing, sounds, writes and events retain native order.
`python3 tools/test_block_landing_parity.py` invokes registered native methods for
all **35,723 states / 1,286 blocks** with eight entity class families. The
[registered landing report](../benchmarks/rust-block-landing-2026-10-07/scoped-parity.json)
records **3,524,267 matching lines per JAR**, including float/double boundaries,
NaN/infinity, failed writes, callback failures, egg hatch preservation and
farmland collision pushes. Targeted Clippy passes. Actual health, live entity
selection/storage, teleport/network behavior, audio playback and full movement
lifecycle integration remain separate work.
@@ -0,0 +1,218 @@
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.sounds.*;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.*;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.damagesource.*;
import net.minecraft.world.level.*;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.*;
import net.minecraft.world.level.gamerules.*;
import net.minecraft.world.level.gameevent.*;
import net.minecraft.world.phys.*;
/** Runs registered native landing/bounce methods with controlled observable providers. */
public final class VanillaBlockLandingOracle {
static final class Stop extends RuntimeException {}
static Object unsafe;static Method allocate;
static <T>T make(Class<T> type){try{return type.cast(allocate.invoke(unsafe,type));}catch(Exception e){throw new AssertionError(e);}}
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 String pos(BlockPos p){return p.getX()+" "+p.getY()+" "+p.getZ();}
static String bounds(String label,AABB b){return String.format("%s %016x %016x %016x %016x %016x %016x",label,Double.doubleToLongBits(b.minX),Double.doubleToLongBits(b.minY),Double.doubleToLongBits(b.minZ),Double.doubleToLongBits(b.maxX),Double.doubleToLongBits(b.maxY),Double.doubleToLongBits(b.maxZ));}
static AABB box(JsonElement v){var a=v.getAsJsonArray();return new AABB(number(a.get(0)),number(a.get(1)),number(a.get(2)),number(a.get(3)),number(a.get(4)),number(a.get(5)));}
static String sound(SoundEvent s){if(s==SoundEvents.HONEY_BLOCK_SLIDE)return "honey-slide";if(s==SoundEvents.HONEY_BLOCK_FALL)return "honey-fall";if(s==SoundEvents.TURTLE_EGG_BREAK)return "egg-break";if(s==SoundEvents.SHELF_MUSHROOM_BOUNCE)return "mushroom";if(s==SoundEvents.GENERIC_SMALL_FALL)return "small";if(s==SoundEvents.GENERIC_BIG_FALL)return "big";throw new AssertionError(s);}
static final class Data {
JsonObject f;PrintWriter out;int calls;BlockState state;Sources sources;Rules rules;Server server;RandomSource random;List<Occupant> occupants;
boolean flag(String name){return f.get(name).getAsBoolean();}
void query(String s){out.println(s);if(calls++==f.get("fail").getAsInt())throw new Stop();}
boolean damage(double distance,float multiplier,DamageSource source){query(String.format("damage %016x %08x %s",Double.doubleToLongBits(distance),Float.floatToIntBits(multiplier),source==sources.fall?"fall":source==sources.stalagmite?"stalagmite":"unknown"));return flag("damage");}
void entitySound(SoundEvent s,float volume,float pitch){query(String.format("entity-sound %s %08x %08x",sound(s),Float.floatToIntBits(volume),Float.floatToIntBits(pitch)));}
boolean set(BlockPos p,BlockState next,int flags){query("set "+pos(p)+" "+Block.getId(next)+" "+flags);if(flag("write"))state=next;return flag("write");}
boolean destroy(BlockPos p,boolean drops){query("destroy "+pos(p)+" "+drops);if(flag("write"))state=Blocks.AIR.defaultBlockState();return flag("write");}
void event(Holder<GameEvent> event,BlockPos p,GameEvent.Context c){query("event "+(event==GameEvent.BLOCK_CHANGE?"change":event==GameEvent.BLOCK_DESTROY?"destroy":"unknown")+" "+pos(p)+" "+Block.getId(c.affectedState())+" "+(c.sourceEntity()!=null));}
void blockSound(Entity entity,BlockPos p,SoundEvent sound,SoundSource source,float volume,float pitch){if(entity!=null||source!=SoundSource.BLOCKS)throw new AssertionError();query(String.format("block-sound %s %s %08x %08x",pos(p),sound(sound),Float.floatToIntBits(volume),Float.floatToIntBits(pitch)));}
List<Entity> entities(Entity excluded,AABB bounds){if(excluded!=null)throw new AssertionError();query(bounds("entities",bounds));return new ArrayList<Entity>(occupants);}
}
static final class Sources extends DamageSources {
Data d;DamageSource fall,stalagmite;Sources(){super(null);}
@Override public DamageSource fall(){d.query("fall-source");return fall;}
@Override public DamageSource stalagmite(){d.query("stalagmite-source");return stalagmite;}
}
static final class Rules extends GameRules {
Data d;Rules(){super(net.minecraft.world.flag.FeatureFlagSet.of());}
@SuppressWarnings("unchecked") @Override public <T>T get(GameRule<T> rule){if(rule!=GameRules.MOB_GRIEFING)throw new AssertionError();d.query("mob-griefing");return (T)Boolean.valueOf(d.flag("griefing"));}
}
static final class Server extends net.minecraft.server.MinecraftServer {
@Override public int getMaxPlayers(){throw new UnsupportedOperationException();}
Data d;Server(){super(null,null,null,null,Optional.empty(),null,null,null,null,false,null);}
@Override public boolean isUnderSpawnProtection(net.minecraft.server.level.ServerLevel level,BlockPos p,Player player){d.query("protection "+pos(p));return d.flag("protected");}
@Override public boolean initServer(){throw new UnsupportedOperationException();}
@Override public net.minecraft.server.permissions.LevelBasedPermissionSet operatorUserPermissions(){throw new UnsupportedOperationException();}
@Override public net.minecraft.server.permissions.PermissionSet getFunctionCompilationPermissions(){throw new UnsupportedOperationException();}
@Override public boolean shouldRconBroadcast(){throw new UnsupportedOperationException();}
@Override public net.minecraft.util.debugchart.SampleLogger getTickTimeLogger(){throw new UnsupportedOperationException();}
@Override public boolean isTickTimeLoggingEnabled(){throw new UnsupportedOperationException();}
@Override public net.minecraft.SystemReport fillServerSystemReport(net.minecraft.SystemReport p0){throw new UnsupportedOperationException();}
@Override public boolean isDedicatedServer(){throw new UnsupportedOperationException();}
@Override public int getRateLimitPacketsPerSecond(){throw new UnsupportedOperationException();}
@Override public int getCommandSpamThresholdSeconds(){throw new UnsupportedOperationException();}
@Override public int getChatSpamThresholdSeconds(){throw new UnsupportedOperationException();}
@Override public boolean useNativeTransport(){throw new UnsupportedOperationException();}
@Override public boolean isPublished(){throw new UnsupportedOperationException();}
@Override public boolean shouldInformAdmins(){throw new UnsupportedOperationException();}
@Override public boolean isSingleplayerOwner(net.minecraft.server.players.NameAndId p0){throw new UnsupportedOperationException();}
}
static final class ServerWorld extends net.minecraft.server.level.ServerLevel {
Data d;ServerWorld(){super(null,null,null,null,null,null,false,0L,List.of(),false);}
@Override public GameRules getGameRules(){d.query("rules");return d.rules;}
@Override public boolean isClientSide(){d.query("client");return d.flag("client");}
@Override public RandomSource getRandom(){d.query("random");return d.random;}
@Override public DamageSources damageSources(){d.query("level-sources");return d.sources;}
@Override public net.minecraft.server.MinecraftServer getServer(){d.query("server");return d.server;}
@Override public void playSound(Entity e,BlockPos p,SoundEvent s,SoundSource source,float volume,float pitch){d.blockSound(e,p,s,source,volume,pitch);}
@Override public void broadcastEntityEvent(Entity e,byte event){d.query("broadcast "+event);}
@Override public boolean setBlock(BlockPos p,BlockState state,int flags){return d.set(p,state,flags);}
@Override public boolean destroyBlock(BlockPos p,boolean drops){return d.destroy(p,drops);}
@Override public void gameEvent(Holder<GameEvent> e,BlockPos p,GameEvent.Context c){d.event(e,p,c);}
@Override public void levelEvent(int event,BlockPos p,int data){d.query("level-event "+event+" "+pos(p)+" "+data);}
@Override public List<Entity> getEntities(Entity excluded,AABB bounds){return d.entities(excluded,bounds);}
}
static final class ClientWorld extends Level {
Data d;ClientWorld(){super(null,null,null,null,false,false,0L,0);}
@Override public boolean isClientSide(){d.query("client");return d.flag("client");}
@Override public RandomSource getRandom(){d.query("random");return d.random;}
@Override public DamageSources damageSources(){d.query("level-sources");return d.sources;}
@Override public net.minecraft.server.MinecraftServer getServer(){d.query("server");return d.server;}
@Override public void playSound(Entity e,BlockPos p,SoundEvent s,SoundSource source,float volume,float pitch){d.blockSound(e,p,s,source,volume,pitch);}
@Override public void broadcastEntityEvent(Entity e,byte event){d.query("broadcast "+event);}
@Override public boolean setBlock(BlockPos p,BlockState state,int flags){return d.set(p,state,flags);}
@Override public boolean destroyBlock(BlockPos p,boolean drops){return d.destroy(p,drops);}
@Override public void gameEvent(Holder<GameEvent> e,BlockPos p,GameEvent.Context c){d.event(e,p,c);}
@Override public void levelEvent(int event,BlockPos p,int data){d.query("level-event "+event+" "+pos(p)+" "+data);}
@Override public List<Entity> getEntities(Entity excluded,AABB bounds){return d.entities(excluded,bounds);}
@Override public net.minecraft.world.clock.ClockManager clockManager(){throw new UnsupportedOperationException();}
@Override public void destroyBlockProgress(int p0,net.minecraft.core.BlockPos p1,int p2){throw new UnsupportedOperationException();}
@Override public java.util.Collection dragonParts(){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.flag.FeatureFlagSet enabledFeatures(){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.attribute.EnvironmentAttributeSystem environmentAttributes(){throw new UnsupportedOperationException();}
@Override public void explode(net.minecraft.world.entity.Entity p0,net.minecraft.world.damagesource.DamageSource p1,net.minecraft.world.level.ExplosionDamageCalculator p2,double p3,double p4,double p5,float p6,boolean p7,net.minecraft.world.level.Level.ExplosionInteraction p8,net.minecraft.core.particles.ParticleOptions p9,net.minecraft.core.particles.ParticleOptions p10,net.minecraft.util.random.WeightedList p11,net.minecraft.core.Holder p12){throw new UnsupportedOperationException();}
@Override public void gameEvent(net.minecraft.core.Holder p0,net.minecraft.world.phys.Vec3 p1,net.minecraft.world.level.gameevent.GameEvent.Context p2){throw new UnsupportedOperationException();}
@Override public java.lang.String gatherChunkSourceStats(){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.ticks.LevelTickAccess getBlockTicks(){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.level.chunk.ChunkSource getChunkSource(){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.level.entity.LevelEntityGetter getEntities(){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.entity.Entity getEntity(int p0){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.ticks.LevelTickAccess getFluidTicks(){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.level.saveddata.maps.MapItemSavedData getMapData(net.minecraft.world.level.saveddata.maps.MapId p0){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.level.storage.LevelData.RespawnData getRespawnData(){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.scores.Scoreboard getScoreboard(){throw new UnsupportedOperationException();}
@Override public int getSeaLevel(){throw new UnsupportedOperationException();}
@Override public net.minecraft.core.Holder getUncachedNoiseBiome(int p0,int p1,int p2){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.level.border.WorldBorder getWorldBorder(){throw new UnsupportedOperationException();}
@Override public void levelEvent(net.minecraft.world.entity.Entity p0,int p1,net.minecraft.core.BlockPos p2,int p3){throw new UnsupportedOperationException();}
@Override public void playSeededSound(net.minecraft.world.entity.Entity p0,net.minecraft.world.entity.Entity p1,net.minecraft.core.Holder p2,net.minecraft.sounds.SoundSource p3,float p4,float p5,long p6){throw new UnsupportedOperationException();}
@Override public void playSeededSound(net.minecraft.world.entity.Entity p0,double p1,double p2,double p3,net.minecraft.core.Holder p4,net.minecraft.sounds.SoundSource p5,float p6,float p7,long p8){throw new UnsupportedOperationException();}
@Override public java.util.List players(){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.item.crafting.RecipeAccess recipeAccess(){throw new UnsupportedOperationException();}
@Override public void sendBlockUpdated(net.minecraft.core.BlockPos p0,net.minecraft.world.level.block.state.BlockState p1,net.minecraft.world.level.block.state.BlockState p2,int p3){throw new UnsupportedOperationException();}
@Override public void setRespawnData(net.minecraft.world.level.storage.LevelData.RespawnData p0){throw new UnsupportedOperationException();}
@Override public net.minecraft.world.TickRateManager tickRateManager(){throw new UnsupportedOperationException();}
}
static final class Other extends Marker {
Data d;Other(){super(null,null);}
@Override public boolean causeFallDamage(double distance,float multiplier,DamageSource source){return d.damage(distance,multiplier,source);}
@Override public boolean isSuppressingBounce(){d.query("suppress");return d.flag("suppress");}
@Override public DamageSources damageSources(){d.query("entity-sources");return d.sources;}
@Override public void playSound(SoundEvent s,float volume,float pitch){d.entitySound(s,volume,pitch);}
}
static final class Living extends net.minecraft.world.entity.decoration.ArmorStand {
Data d;Living(){super(null,null);}
@Override public boolean causeFallDamage(double distance,float multiplier,DamageSource source){return d.damage(distance,multiplier,source);}
@Override public boolean isSuppressingBounce(){d.query("suppress");return d.flag("suppress");}
@Override public DamageSources damageSources(){d.query("entity-sources");return d.sources;}
@Override public void playSound(SoundEvent s,float volume,float pitch){d.entitySound(s,volume,pitch);}
@Override public Fallsounds getFallSounds(){d.query("fall-sounds");return new Fallsounds(SoundEvents.GENERIC_SMALL_FALL,SoundEvents.GENERIC_BIG_FALL);}
}
static final class Human extends Player {
Data d;Human(){super(null,null);}
@Override public boolean causeFallDamage(double distance,float multiplier,DamageSource source){return d.damage(distance,multiplier,source);}
@Override public boolean isSuppressingBounce(){d.query("suppress");return d.flag("suppress");}
@Override public DamageSources damageSources(){d.query("entity-sources");return d.sources;}
@Override public void playSound(SoundEvent s,float volume,float pitch){d.entitySound(s,volume,pitch);}
@Override public Fallsounds getFallSounds(){d.query("fall-sounds");return new Fallsounds(SoundEvents.GENERIC_SMALL_FALL,SoundEvents.GENERIC_BIG_FALL);}
@Override public GameType gameMode(){return GameType.SURVIVAL;}
}
static final class Undead extends net.minecraft.world.entity.monster.zombie.Zombie {
Data d;Undead(){super(null,null);}
@Override public boolean causeFallDamage(double distance,float multiplier,DamageSource source){return d.damage(distance,multiplier,source);}
@Override public boolean isSuppressingBounce(){d.query("suppress");return d.flag("suppress");}
@Override public DamageSources damageSources(){d.query("entity-sources");return d.sources;}
@Override public void playSound(SoundEvent s,float volume,float pitch){d.entitySound(s,volume,pitch);}
@Override public Fallsounds getFallSounds(){d.query("fall-sounds");return new Fallsounds(SoundEvents.GENERIC_SMALL_FALL,SoundEvents.GENERIC_BIG_FALL);}
}
static final class Reptile extends net.minecraft.world.entity.animal.turtle.Turtle {
Data d;Reptile(){super(null,null);}
@Override public boolean causeFallDamage(double distance,float multiplier,DamageSource source){return d.damage(distance,multiplier,source);}
@Override public boolean isSuppressingBounce(){d.query("suppress");return d.flag("suppress");}
@Override public DamageSources damageSources(){d.query("entity-sources");return d.sources;}
@Override public void playSound(SoundEvent s,float volume,float pitch){d.entitySound(s,volume,pitch);}
@Override public Fallsounds getFallSounds(){d.query("fall-sounds");return new Fallsounds(SoundEvents.GENERIC_SMALL_FALL,SoundEvents.GENERIC_BIG_FALL);}
}
static final class Flying extends net.minecraft.world.entity.ambient.Bat {
Data d;Flying(){super(null,null);}
@Override public boolean causeFallDamage(double distance,float multiplier,DamageSource source){return d.damage(distance,multiplier,source);}
@Override public boolean isSuppressingBounce(){d.query("suppress");return d.flag("suppress");}
@Override public DamageSources damageSources(){d.query("entity-sources");return d.sources;}
@Override public void playSound(SoundEvent s,float volume,float pitch){d.entitySound(s,volume,pitch);}
@Override public Fallsounds getFallSounds(){d.query("fall-sounds");return new Fallsounds(SoundEvents.GENERIC_SMALL_FALL,SoundEvents.GENERIC_BIG_FALL);}
}
static final class Item extends net.minecraft.world.entity.item.ItemEntity {
Data d;Item(){super(null,null);}
@Override public boolean causeFallDamage(double distance,float multiplier,DamageSource source){return d.damage(distance,multiplier,source);}
@Override public boolean isSuppressingBounce(){d.query("suppress");return d.flag("suppress");}
@Override public DamageSources damageSources(){d.query("entity-sources");return d.sources;}
@Override public void playSound(SoundEvent s,float volume,float pitch){d.entitySound(s,volume,pitch);}
}
static final class Tnt extends net.minecraft.world.entity.item.PrimedTnt {
Data d;Tnt(){super(null,null);}
@Override public boolean causeFallDamage(double distance,float multiplier,DamageSource source){return d.damage(distance,multiplier,source);}
@Override public boolean isSuppressingBounce(){d.query("suppress");return d.flag("suppress");}
@Override public DamageSources damageSources(){d.query("entity-sources");return d.sources;}
@Override public void playSound(SoundEvent s,float volume,float pitch){d.entitySound(s,volume,pitch);}
}
static final class Occupant extends Marker {
Data d;int id;Occupant(){super(null,null);}
@Override public void teleportRelative(double x,double y,double z){d.query(String.format("teleport %d %016x %016x %016x",id,Double.doubleToLongBits(x),Double.doubleToLongBits(y),Double.doubleToLongBits(z)));setBoundingBox(getBoundingBox().move(x,y,z));if(d.flag("mutate")&&id+1<d.occupants.size()){var next=d.occupants.get(id+1);next.setBoundingBox(next.getBoundingBox().move(0,.125,0));}}
}
static void export(Path dir)throws Exception {
VanillaCollisionOracle.main(new String[]{"generate",dir.toString()});VanillaMovementRegistryOracle.export(dir);
var definitions=new JsonObject();var metadata=new JsonArray();var base=FarmlandBlock.class.getDeclaredField("baseBlock");base.setAccessible(true);var sound=BlockBehaviour.class.getDeclaredField("soundType");sound.setAccessible(true);
var owners=Set.of(Block.class,HayBlock.class,HoneyBlock.class,SlimeBlock.class,PowderSnowBlock.class,PointedDripstoneBlock.class,FarmlandBlock.class,TurtleEggBlock.class);
for(var block:BuiltInRegistries.BLOCK){var owner=block.getClass().getMethod("fallOn",Level.class,BlockState.class,BlockPos.class,Entity.class,double.class).getDeclaringClass();if(!owners.contains(owner))throw new AssertionError(owner);var bounce=block.getClass().getMethod("bounceOn",Level.class,BlockState.class,BlockPos.class,Entity.class,double.class).getDeclaringClass();if(bounce!=Block.class&&bounce!=ShelfMushroomBlock.class)throw new AssertionError(bounce);var entry=new JsonObject();entry.addProperty("fall",owner.getSimpleName());entry.addProperty("bounce",bounce==ShelfMushroomBlock.class);if(owner==FarmlandBlock.class)entry.addProperty("base",BuiltInRegistries.BLOCK.getKey((Block)base.get(block)).toString());if(owner==HoneyBlock.class){var type=(SoundType)sound.get(block);entry.addProperty("volume",String.format("%08x",Float.floatToIntBits(type.getVolume())));entry.addProperty("pitch",String.format("%08x",Float.floatToIntBits(type.getPitch())));if(type.getFallSound()!=SoundEvents.HONEY_BLOCK_FALL)throw new AssertionError();}definitions.add(BuiltInRegistries.BLOCK.getKey(block).toString(),entry);}
for(var state:Block.BLOCK_STATE_REGISTRY){var row=new JsonObject();row.addProperty("id",Block.getId(state));row.addProperty("name",BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString());metadata.add(row);}
Files.writeString(dir.resolve("landing-definitions.json"),definitions.toString());Files.writeString(dir.resolve("landing-states.json"),metadata.toString());
}
public static void main(String[] args)throws Exception {
net.minecraft.SharedConstants.tryDetectVersion();net.minecraft.server.Bootstrap.bootStrap();Path dir=Path.of(args[1]);if(args[0].equals("export")){export(dir);return;}
var allocator=Class.forName("sun.misc.Unsafe");var field=allocator.getDeclaredField("theUnsafe");field.setAccessible(true);unsafe=field.get(null);allocate=allocator.getMethod("allocateInstance",Class.class);var dimensions=Entity.class.getDeclaredField("dimensions");dimensions.setAccessible(true);
var kinds=new Class<?>[]{Other.class,Living.class,Human.class,Undead.class,Reptile.class,Flying.class,Item.class,Tnt.class};
try(var out=new PrintWriter(Files.newBufferedWriter(dir.resolve(args[0]+".txt")));var lines=Files.lines(dir.resolve("inputs.txt"))){for(var line:(Iterable<String>)lines::iterator){
var f=JsonParser.parseString(line).getAsJsonObject();var d=new Data();d.f=f;d.out=out;d.state=Block.stateById(f.get("state").getAsInt());d.sources=make(Sources.class);d.sources.d=d;d.sources.fall=make(DamageSource.class);d.sources.stalagmite=make(DamageSource.class);d.rules=make(Rules.class);d.rules.d=d;d.server=make(Server.class);d.server.d=d;
d.random=(RandomSource)Proxy.newProxyInstance(RandomSource.class.getClassLoader(),new Class<?>[]{RandomSource.class},(proxy,method,values)->{if(method.getName().equals("nextFloat")){d.query("float");return single(f.get("random_float"));}if(method.getName().equals("nextInt")&&values.length==1){d.query("int "+values[0]);return f.get("random_int").getAsInt();}throw new AssertionError(method);});
Level world;if(d.flag("server")){var w=make(ServerWorld.class);w.d=d;world=w;}else{var w=make(ClientWorld.class);w.d=d;world=w;}
var kind=kinds[f.get("kind").getAsInt()];var entity=(Entity)make(kind);var dataField=kind.getDeclaredField("d");dataField.set(entity,d);dimensions.set(entity,EntityDimensions.scalable(single(f.get("width")),single(f.get("height"))));d.occupants=new ArrayList<>();for(var box:f.getAsJsonArray("entities")){var e=make(Occupant.class);e.d=d;e.id=d.occupants.size();e.setBoundingBox(box(box));d.occupants.add(e);}
var p=f.getAsJsonArray("position");var position=new BlockPos(p.get(0).getAsInt(),p.get(1).getAsInt(),p.get(2).getAsInt());var state=d.state;
try{if(d.flag("bounce"))state.getBlock().bounceOn(world,state,position,entity,number(f.get("distance")));else state.getBlock().fallOn(world,state,position,entity,number(f.get("distance")));out.println("ok");}catch(Stop e){out.println("error");}
out.println("state "+Block.getId(d.state));for(var e:d.occupants)out.println(bounds("final "+e.id,e.getBoundingBox()));out.println("queries "+d.calls);
}}
}
}
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Compare every registered block landing/bounce family with pinned vanilla."""
import json
import math
import os
from pathlib import Path
import platform
import random
import struct
import subprocess
try:
from . import lab, test_rust_parity as base
except ImportError:
import lab
import test_rust_parity as base
def corpus(states, definitions):
def bits(v):return struct.pack('>d',float(v)).hex()
def single(v):return struct.pack('>f',float(v)).hex()
def row(state,kind=2,**kw):
f=dict(state=state,kind=kind,bounce=False,server=True,client=False,distance=bits(8),random_float=single(.25),random_int=0,width=single(.6),height=single(1.8),damage=True,suppress=False,protected=False,griefing=True,write=True,position=[17,-64,-33],entities=[],mutate=False,fail=-1)
f.update(kw);return f
for state in states:
for kind in range(8):
for bounce in [False,True]:yield row(state['id'],kind,bounce=bounce)
special=[s for s in states if definitions[s['name']]['fall']!='Block' or definitions[s['name']]['bounce']]
distances=[-math.inf,-1.,-0.,0.,.5,math.nextafter(.75,0),.75,math.nextafter(.75,1),1.,math.nextafter(4.,0),4.,math.nextafter(4.,5),math.nextafter(7.,0),7.,math.nextafter(7.,8),1e40,math.inf,math.nan]
for state in special:
for kind in range(8):
for distance in distances:
for server,client in [(True,False),(True,True),(False,True),(False,False)]:yield row(state['id'],kind,distance=bits(distance),server=server,client=client)
for flag in ['damage','suppress','protected','griefing','write']:
for value in [False,True]:
for fail in range(-1,18):yield row(state['id'],kind,**{flag:value,'fail':fail})
for bounce in [False,True]:
for fail in range(-1,4):yield row(state['id'],kind,bounce=bounce,fail=fail)
rng=random.Random(0x4c414e44494e47)
for _ in range(18000):
state=rng.choice(special);p=rng.choice([[0,0,0],[17,-64,-33],[2147483647,0,-2147483648]])
entities=[]
if definitions[state['name']]['fall']=='FarmlandBlock':
for _ in range(rng.randrange(5)):
y=rng.choice([0,.5,.9375,1,1.1]);low=[p[0]+.2,p[1]+y,p[2]+.2];entities.append([*low,low[0]+.6,low[1]+1.8,low[2]+.6])
yield row(state['id'],rng.randrange(8),server=bool(rng.randrange(2)),client=bool(rng.randrange(2)),distance=bits(rng.choice(distances+[rng.uniform(-1,12)])),random_float=single(rng.choice([0.,.25,.5,.99999994,math.nan])),random_int=rng.randrange(3),width=single(rng.choice([0.,.6,.8,1.,2.,math.nan,math.inf])),height=single(rng.choice([0.,.8,1.,1.8,math.nan,math.inf])),damage=bool(rng.randrange(2)),suppress=bool(rng.randrange(2)),protected=bool(rng.randrange(2)),griefing=bool(rng.randrange(2)),write=bool(rng.randrange(2)),position=p,entities=[[bits(v) for v in b] for b in entities],mutate=bool(rng.randrange(2)),fail=rng.choice([-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12]))
# Complete successful farmland pushes, all mutation/failure cut points and volume threshold.
farms=[s['id'] for s in states if definitions[s['name']]['fall']=='FarmlandBlock']
for state in farms:
for width,height in [(.8,.8),(.8,math.nextafter(.8,1)),(.6,1.8),(1.,.512),(1.,.5120001),(1.,.5119999),(-.8,1.),(math.inf,0.),(math.nan,1.)]:
for kind in [1,2]:
for fail in range(-1,18):
for mutate in [False,True]:yield row(state,kind,width=single(width),height=single(height),entities=[[bits(v) for v in b] for b in [[17.2,-63.0625,-32.8,17.8,-61.2,-32.2],[17.2,-63.5,-32.8,17.8,-61.7,-32.2]]],mutate=mutate,fail=fail)
def main():
lock,jars,libraries=base.verify_artifacts();build=lab.LOCAL/'rust-block-landing-parity';build.mkdir(exist_ok=True);report_path=build/'report.json';report_path.unlink(missing_ok=True)
root=lab.ROOT/'mods/horizon/src/test/java/dev/horizonlab/parity'
oracles=[root/(n+'.java') for n in ['VanillaBlockLandingOracle','VanillaMovementRegistryOracle','VanillaHeightmapOracle','VanillaCollisionOracle']]
sources=[*oracles,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-world/examples/block_landing_fixture.rs']
for crate in ['horizon-core','horizon-world']:sources += [lab.ROOT/'crates'/crate/'Cargo.toml',*sorted((lab.ROOT/'crates'/crate/'src').rglob('*.rs'))]
tested=base.source_hashes(sources);classes=build/'classes';classes.mkdir(exist_ok=True);dependencies=os.pathsep.join(map(str,libraries))
def run(command,name):
with (build/(name+'.log')).open('w') as log:subprocess.run(list(map(str,command)),cwd=lab.ROOT,check=True,timeout=3600,stdout=log,stderr=subprocess.STDOUT)
run(['javac','--release',lock['minecraft']['java_major'],'-proc:none','-cp',str(jars['server'])+os.pathsep+dependencies,'-d',classes,*oracles],'compile-java')
def java(side,mode):run(['java','-Xmx768M','-cp',os.pathsep.join([str(classes),str(jars[side]),dependencies]),'dev.horizonlab.parity.VanillaBlockLandingOracle',mode,build],side+'-'+mode)
java('server','export');states=json.loads((build/'landing-states.json').read_text());definitions=json.loads((build/'landing-definitions.json').read_text())
if len(states)!=35723 or len(definitions)!=1286:raise AssertionError('Incomplete pinned registry')
count=0
with (build/'inputs.txt').open('w') as output:
for f in corpus(states,definitions):output.write(json.dumps(f,separators=(',',':'))+'\n');count+=1
fixture_paths=[build/n for n in ['inputs.txt','landing-states.json','landing-definitions.json','movement-properties.nbt','block-registry.nbt','cached-collision.nbt']];fixtures=base.source_hashes(fixture_paths)
run(['cargo','build','--locked','--release','-p','horizon-world','--example','block_landing_fixture'],'compile-rust')
metadata=json.loads(subprocess.check_output(['cargo','metadata','--locked','--no-deps','--format-version','1'],cwd=lab.ROOT));binary=Path(metadata['target_directory'])/'release/examples'/('block_landing_fixture.exe' if os.name=='nt' else 'block_landing_fixture')
run([binary,build],'rust');references=[]
for side,jar in jars.items():
java(side,side);matched=base.compare(build/(side+'.txt'),build/'rust.txt');references.append(dict(side=side,jar_sha256=lab.digest(jar,'sha256'),output_sha256=lab.digest(build/(side+'.txt'),'sha256'),matched_output_lines=matched));print(f'{side}: {matched:,} registered block landing lines match',flush=True)
base.ensure_sources_unchanged(sources,tested);base.ensure_sources_unchanged(fixture_paths,fixtures)
report=dict(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(),registry_states=len(states),registry_blocks=len(definitions),fixtures=count,sources_sha256=tested,fixtures_sha256=fixtures,rust_output_sha256=lab.digest(build/'rust.txt','sha256'),references=references,scope='Actual registered Block.fallOn and bounceOn across all states and eight entity class families, plus client/server distinctions, damage return, bounce suppression, protection/rules, random boundaries, NaN/infinity and failures at each observable callback. Farmland replacement uses native registered collision shapes and ordered occupant pushes; turtle egg changes preserve hatch and compare write flags, event order and effects after failed writes. Entity damage, randomness, selected entities, sounds, world writes and events are controlled providers. Live health, entity storage, network, audio and full movement lifecycle integration remain separate work.')
lab.write_json(report_path,report);print(f'Scoped registered landing parity passed: {report_path}',flush=True)
if __name__=='__main__':main()