Match lazy block collision scanning and provider query order

This commit is contained in:
Ludovic Marie
2026-10-06 23:24:57 +02:00
parent 75e835558c
commit 99f310c4e3
8 changed files with 967 additions and 0 deletions
@@ -0,0 +1,204 @@
{
"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": 4176,
"sources_sha256": {
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaBlockCollisionScanOracle.java": "ee34da34195df5a0cdd4a24a4faf7154200b674d6523eba4700381439d556b2d",
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaFaceSupportOracle.java": "9ce939a435842867786fed648951bbe77f6ab843f6df09af15f3deb1948c4877",
"tools/test_block_collision_scan_parity.py": "5289ba6c555367d4dbfe7cbf8e577705c16a8426dfcc431c787aeed3e513970d",
"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/mod.rs": "dc92f73885dd0a7760743bf74f1ab8afa839d61cef2dfef890f8a23b67b4742e",
"crates/horizon-core/src/geometry/movement.rs": "930e34abef6f6eeec1ee7a6c920b5ba01bfe37b90678884490f4be53241f6b96",
"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": "5d0f5ded49d34bb999d73a4e1b89a318fa66c0246e9904d3cbf810d3aa370312",
"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/entity_obstruction.rs": "a4b28a19bf83b23611c59d94535c488db37e4aee24b22c973c545910894a809f",
"crates/horizon-world/src/fluid.rs": "a0a1f34ba94d8b59dc975becee8952960764d67ae08cf2d6a8bcd40a614b15c4",
"crates/horizon-world/src/heightmap.rs": "a5edef082b5af40d814421ea17e129b3b06d70abee6434ed263e4bba58571698",
"crates/horizon-world/src/lib.rs": "3f574287b721b86f39d976d98a69b632b87658cd38f4a7dc0e6c1b6623d5a6a3",
"crates/horizon-world/src/liquid_collision.rs": "0f0a83b04e5a5bc9d98ed05fde4d6584ba69642e93522e726e4cfc325d6fcb1d",
"crates/horizon-world/src/main.rs": "fce57418b8c22a6a0a9044baa89ed3d9c243bc2e8413fea3ff0f32985daba021",
"crates/horizon-world/src/nbt.rs": "60262c3de8c8f68bc2da0370ccb17393d32b85f5f5022c6358599a7d32119ea6",
"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/retrogen.rs": "6efd36a6293756688c1990a7965bd65609d7c5e2bf2a5f4f01d54932d2b9386b",
"crates/horizon-world/src/tick_data.rs": "f8d04a69ea263f4168bce33f92e5b4ee2fe07d7caf50a1621c6b3ef6eb2479f3",
"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/block_collision_scan_fixture.rs": "509420fc9601ece4ad17d4eec67a2d2ad4de0c653c55cbf01ba746a354575f17"
},
"fixtures_sha256": {
".local/rust-block-collision-scan-parity/inputs.txt": "d87e1a985430aab7a9fd77e6f3a70e4810d19241c74a9fe8c324348d5c0737dc"
},
"rust_output_sha256": "f975369ad065b5417dbeacbfa554fc3f27ddaa4f2e634e57f5380e070b355d72",
"references": [
{
"side": "client",
"jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d",
"output_sha256": "f975369ad065b5417dbeacbfa554fc3f27ddaa4f2e634e57f5380e070b355d72",
"matched_output_lines": 517820
},
{
"side": "server",
"jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6",
"output_sha256": "f975369ad065b5417dbeacbfa554fc3f27ddaa4f2e634e57f5380e070b355d72",
"matched_output_lines": 517820
}
],
"scope": "Actual BlockCollisions cursor and selection algorithm over controlled block metadata, chunk providers and context shapes. Compares ordered chunk/state/suffocation/shape calls, missing-chunk retries, cached chunk reuse, face/edge/corner filtering, canonical full-block versus equal noncanonical shape behavior, exact emitted shape data, early consumer termination and injected provider failures. Finite bounded scans include thin/zero-size boxes, chunk crossings and wrapped integer positions. Registry-specific metadata/shape dispatch, live chunk loading and entity movement are separate integration work; unsupported cursor volumes and caller budgets use typed Rust errors."
}
@@ -0,0 +1,239 @@
use horizon_core::{
geometry::{Aabb, Axis, ShapeOp, Vec3, VoxelShape},
position::{BlockPos, ChunkPos},
};
use horizon_world::{
block_collision_scan::{BlockCollisionScan, BlockCollisionWorld, CollisionBlock},
collision_context::CollisionContext,
registry::BlockStateId,
};
use serde_json::Value;
use std::{
cell::RefCell,
fs,
io::{self, Write},
path::Path,
rc::Rc,
};
fn number(v: &Value) -> f64 {
f64::from_bits(u64::from_str_radix(v.as_str().unwrap(), 16).unwrap())
}
fn values(
out: &mut impl Write,
label: &str,
values: impl IntoIterator<Item = f64>,
) -> io::Result<()> {
write!(out, "{label}")?;
for v in values {
let bits = if v.is_nan() {
0x7ff8_0000_0000_0000
} else {
v.to_bits()
};
write!(out, " {bits:016x}")?;
}
writeln!(out)
}
fn box_values(out: &mut impl Write, label: &str, b: Aabb) -> io::Result<()> {
values(
out,
label,
[
b.min(Axis::X),
b.min(Axis::Y),
b.min(Axis::Z),
b.max(Axis::X),
b.max(Axis::Y),
b.max(Axis::Z),
],
)
}
fn emit(out: &mut impl Write, shape: &VoxelShape) -> io::Result<()> {
writeln!(
out,
"shape {} {} {}",
shape.is_empty(),
shape.boxes().len(),
shape.is_cube()
)?;
for (axis, name) in Axis::ALL.into_iter().zip(["x", "y", "z"]) {
values(out, name, shape.coordinates(axis).iter().copied())?;
}
let axes = shape.cube_coordinate_axes();
writeln!(
out,
"axes {}",
u8::from(axes[0]) + 2 * u8::from(axes[1]) + 4 * u8::from(axes[2])
)?;
values(
out,
"extrema",
Axis::ALL
.map(|a| shape.min(a))
.into_iter()
.chain(Axis::ALL.map(|a| shape.max(a))),
)?;
match shape.bounds() {
Some(b) => box_values(out, "bounds", b)?,
None => writeln!(out, "bounds none")?,
}
for b in shape.boxes() {
box_values(out, "box", *b)?;
}
Ok(())
}
fn shape(index: u64) -> VoxelShape {
let cuboid = |min, max| VoxelShape::from_box(Aabb::new(min, max)).unwrap();
match index {
0 => VoxelShape::empty(),
1 => VoxelShape::block(),
2 => VoxelShape::block().moved(Vec3::ZERO),
3 => cuboid(Vec3::ZERO, Vec3::new(1.0, 0.5, 1.0)),
4 => cuboid(Vec3::new(-0.25, -0.5, -0.25), Vec3::new(1.25, 1.5, 1.25)),
5 => cuboid(Vec3::ZERO, Vec3::new(1e-8, 1.0, 1.0)),
6 => cuboid(Vec3::ZERO, Vec3::new(0.25, 1.0, 0.25))
.join(
&cuboid(Vec3::new(0.75, 0.0, 0.75), Vec3::new(1.0, 1.0, 1.0)),
ShapeOp::OR,
)
.unwrap(),
_ => unreachable!(),
}
}
struct Entry {
block: CollisionBlock,
shape: VoxelShape,
suffocating: bool,
}
struct World<W> {
out: Rc<RefCell<W>>,
calls: i32,
fail: i32,
missing: i32,
seed: i32,
palette: Vec<Entry>,
}
impl<W: Write> World<W> {
fn query(&mut self, value: &str) -> io::Result<()> {
writeln!(self.out.borrow_mut(), "{value}")?;
let index = self.calls;
self.calls += 1;
if index == self.fail {
return Err(io::Error::other("injected collision scan failure"));
}
Ok(())
}
}
impl<W: Write> BlockCollisionWorld for World<W> {
type Error = io::Error;
type Chunk = ChunkPos;
fn collision_chunk(&mut self, p: ChunkPos) -> io::Result<Option<ChunkPos>> {
self.query(&format!("chunk {} {}", p.x, p.z))?;
Ok(
if self.missing == 1 || self.missing == 2 && (p.x ^ p.z) & 1 != 0 {
None
} else {
Some(p)
},
)
}
fn collision_block(&mut self, chunk: ChunkPos, p: BlockPos) -> io::Result<CollisionBlock> {
assert_eq!(chunk, p.chunk());
self.query(&format!("block {} {} {}", p.x, p.y, p.z))?;
let hash =
p.x.wrapping_mul(3)
.wrapping_add(p.y.wrapping_mul(5))
.wrapping_add(p.z.wrapping_mul(7))
.wrapping_add(self.seed)
& i32::MAX;
Ok(self.palette[hash as usize % self.palette.len()].block)
}
fn is_suffocating(
&mut self,
chunk: ChunkPos,
block: CollisionBlock,
p: BlockPos,
) -> io::Result<bool> {
assert_eq!(chunk, p.chunk());
self.query(&format!(
"suffocating {} {} {} {}",
p.x, p.y, p.z, block.state.0
))?;
Ok(self.palette[block.state.0 as usize].suffocating)
}
fn context_shape(
&mut self,
block: CollisionBlock,
p: BlockPos,
_: &CollisionContext<'_>,
) -> io::Result<VoxelShape> {
self.query(&format!("shape {} {} {} {}", p.x, p.y, p.z, block.state.0))?;
Ok(self.palette[block.state.0 as usize].shape.clone())
}
}
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 out = Rc::new(RefCell::new(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 palette = f["palette"]
.as_array()
.unwrap()
.iter()
.enumerate()
.map(|(i, v)| Entry {
block: CollisionBlock {
state: BlockStateId(i as u32),
large_collision_shape: v[1].as_bool().unwrap(),
moving_piston: v[2].as_bool().unwrap(),
},
shape: shape(v[0].as_u64().unwrap()),
suffocating: v[3].as_bool().unwrap(),
})
.collect();
let mut world = World {
out: out.clone(),
calls: 0,
fail: f["fail"].as_i64().unwrap() as i32,
missing: f["missing"].as_i64().unwrap() as i32,
seed: f["seed"].as_i64().unwrap() as i32,
palette,
};
let v = &f["box"];
let bounds = Aabb::new(
Vec3::new(number(&v[0]), number(&v[1]), number(&v[2])),
Vec3::new(number(&v[3]), number(&v[4]), number(&v[5])),
);
let mut hits = 0;
{
let mut scan = BlockCollisionScan::new(
&mut world,
CollisionContext::empty(false),
bounds,
f["suffocating"].as_bool().unwrap(),
1_000_000,
)?;
for _ in 0..f["limit"].as_u64().unwrap() {
match scan.next() {
None => break,
Some(Ok(hit)) => {
let p = hit.position;
writeln!(out.borrow_mut(), "hit {} {} {}", p.x, p.y, p.z)?;
emit(&mut *out.borrow_mut(), &hit.shape)?;
hits += 1;
}
Some(Err(_)) => {
writeln!(out.borrow_mut(), "error")?;
break;
}
}
}
}
writeln!(out.borrow_mut(), "done {hits} {}", world.calls)?;
}
Ok(())
}
@@ -0,0 +1,237 @@
//! Lazy block collision selection in vanilla cursor/query order. Providers own
//! chunk availability, state metadata, suffocation predicates and context dispatch.
use crate::{collision_context::CollisionContext, registry::BlockStateId};
use horizon_core::{
geometry::{Aabb, ShapeError, ShapeOp, Vec3, VoxelShape},
position::{BlockPos, ChunkPos},
};
#[derive(Clone, Copy, Debug)]
pub struct CollisionBlock {
pub state: BlockStateId,
pub large_collision_shape: bool,
pub moving_piston: bool,
}
pub trait BlockCollisionWorld {
type Error;
type Chunk: Copy;
/// None is a missing collision chunk; native iteration retries missing chunks.
fn collision_chunk(&mut self, position: ChunkPos) -> Result<Option<Self::Chunk>, Self::Error>;
fn collision_block(
&mut self,
chunk: Self::Chunk,
position: BlockPos,
) -> Result<CollisionBlock, Self::Error>;
fn is_suffocating(
&mut self,
chunk: Self::Chunk,
block: CollisionBlock,
position: BlockPos,
) -> Result<bool, Self::Error>;
/// Query against the global collision world, not the cached chunk provider.
/// Shapes must preserve canonical block identity (including imported shapes).
fn context_shape(
&mut self,
block: CollisionBlock,
position: BlockPos,
context: &CollisionContext<'_>,
) -> Result<VoxelShape, Self::Error>;
}
#[derive(Debug)]
pub enum CollisionScanError<E> {
InvalidBounds,
TooLarge,
VisitBudget,
World(E),
Shape(ShapeError),
}
impl<E: std::fmt::Display> std::fmt::Display for CollisionScanError<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidBounds => f.write_str("collision scan bounds must be finite"),
Self::TooLarge => {
f.write_str("collision scan exceeds the positive Java-int cursor domain")
}
Self::VisitBudget => f.write_str("collision scan candidate budget exhausted"),
Self::World(error) => error.fmt(f),
Self::Shape(error) => error.fmt(f),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for CollisionScanError<E> {}
#[derive(Debug)]
pub struct BlockCollisionHit {
pub position: BlockPos,
pub shape: VoxelShape,
}
pub struct BlockCollisionScan<'w, 'c, W: BlockCollisionWorld> {
world: &'w mut W,
context: CollisionContext<'c>,
bounds: Aabb,
entity_shape: VoxelShape,
only_suffocating: bool,
start: BlockPos,
sizes: [usize; 3],
index: usize,
count: usize,
budget: usize,
chunk: Option<(ChunkPos, W::Chunk)>,
failed: bool,
}
impl<'w, 'c, W: BlockCollisionWorld> BlockCollisionScan<'w, 'c, W> {
/// Finite bounds and a positive-Java-int candidate volume are supported.
/// Budget counts every cursor cell, including skipped corners. After an error
/// iteration terminates; already performed queries are not rolled back.
pub fn new(
world: &'w mut W,
context: CollisionContext<'c>,
bounds: Aabb,
only_suffocating: bool,
candidate_budget: usize,
) -> Result<Self, CollisionScanError<W::Error>> {
let min = bounds.min_position();
let max = bounds.max_position();
if !min.is_finite() || !max.is_finite() {
return Err(CollisionScanError::InvalidBounds);
}
let start = BlockPos::containing(Vec3::new(min.x - 1.0e-7, min.y - 1.0e-7, min.z - 1.0e-7))
.offset(-1, -1, -1);
let end = BlockPos::containing(Vec3::new(max.x + 1.0e-7, max.y + 1.0e-7, max.z + 1.0e-7))
.offset(1, 1, 1);
let sizes = [
end.x.wrapping_sub(start.x).wrapping_add(1),
end.y.wrapping_sub(start.y).wrapping_add(1),
end.z.wrapping_sub(start.z).wrapping_add(1),
];
if sizes.iter().any(|size| *size <= 0) {
return Err(CollisionScanError::TooLarge);
}
let count = sizes
.into_iter()
.try_fold(1u64, |n, size| {
n.checked_mul(size as u64).filter(|n| *n <= i32::MAX as u64)
})
.ok_or(CollisionScanError::TooLarge)? as usize;
Ok(Self {
world,
context,
bounds,
entity_shape: VoxelShape::from_box(bounds).map_err(CollisionScanError::Shape)?,
only_suffocating,
start,
sizes: sizes.map(|s| s as usize),
index: 0,
count,
budget: candidate_budget,
chunk: None,
failed: false,
})
}
fn find_next(&mut self) -> Result<Option<BlockCollisionHit>, CollisionScanError<W::Error>> {
while self.index < self.count {
if self.index >= self.budget {
return Err(CollisionScanError::VisitBudget);
}
let [width, height, _] = self.sizes;
let relative = [
self.index % width,
self.index / width % height,
self.index / width / height,
];
self.index += 1;
let kind = relative
.into_iter()
.zip(self.sizes)
.filter(|(p, size)| *p == 0 || *p == size - 1)
.count();
if kind == 3 {
continue;
}
let p = self
.start
.offset(relative[0] as i32, relative[1] as i32, relative[2] as i32);
let chunk_position = p.chunk();
let chunk = match self.chunk {
Some((at, chunk)) if at == chunk_position => Some(chunk),
_ => {
let chunk = self
.world
.collision_chunk(chunk_position)
.map_err(CollisionScanError::World)?;
self.chunk = chunk.map(|chunk| (chunk_position, chunk));
chunk
}
};
let Some(chunk) = chunk else {
continue;
};
let block = self
.world
.collision_block(chunk, p)
.map_err(CollisionScanError::World)?;
if self.only_suffocating
&& !self
.world
.is_suffocating(chunk, block, p)
.map_err(CollisionScanError::World)?
{
continue;
}
if kind == 1 && !block.large_collision_shape || kind == 2 && !block.moving_piston {
continue;
}
let shape = self
.world
.context_shape(block, p, &self.context)
.map_err(CollisionScanError::World)?;
let offset = Vec3::new(f64::from(p.x), f64::from(p.y), f64::from(p.z));
let moved = if shape.same_identity(&VoxelShape::block()) {
// Native adds one in floating point, including at i32::MAX.
let unit = Aabb::new(
offset,
Vec3::new(offset.x + 1.0, offset.y + 1.0, offset.z + 1.0),
);
if !self.bounds.intersects(unit) {
continue;
}
shape.moved(offset)
} else {
let moved = shape.moved(offset);
if moved.is_empty()
|| !moved
.join_is_not_empty(&self.entity_shape, ShapeOp::AND)
.map_err(CollisionScanError::Shape)?
{
continue;
}
moved
};
return Ok(Some(BlockCollisionHit {
position: p,
shape: moved,
}));
}
Ok(None)
}
}
impl<W: BlockCollisionWorld> Iterator for BlockCollisionScan<'_, '_, W> {
type Item = Result<BlockCollisionHit, CollisionScanError<W::Error>>;
fn next(&mut self) -> Option<Self::Item> {
if self.failed {
return None;
}
match self.find_next() {
Ok(hit) => hit.map(Ok),
Err(error) => {
self.failed = true;
Some(Err(error))
}
}
}
}
impl<W: BlockCollisionWorld> std::iter::FusedIterator for BlockCollisionScan<'_, '_, W> {}
+1
View File
@@ -2,6 +2,7 @@
#![forbid(unsafe_code)]
pub mod block_collision;
pub mod block_collision_scan;
pub mod block_entities;
pub mod block_placement;
pub mod block_properties;
@@ -0,0 +1,108 @@
use horizon_core::{
geometry::{Aabb, Vec3, VoxelShape},
position::{BlockPos, ChunkPos},
};
use horizon_world::{
block_collision_scan::{
BlockCollisionScan, BlockCollisionWorld, CollisionBlock, CollisionScanError,
},
collision_context::CollisionContext,
};
use std::io;
struct FailedChunk {
calls: usize,
}
impl BlockCollisionWorld for FailedChunk {
type Error = io::Error;
type Chunk = ();
fn collision_chunk(&mut self, _: ChunkPos) -> io::Result<Option<()>> {
self.calls += 1;
Err(io::Error::other("missing test provider"))
}
fn collision_block(&mut self, _: (), _: BlockPos) -> io::Result<CollisionBlock> {
unreachable!()
}
fn is_suffocating(&mut self, _: (), _: CollisionBlock, _: BlockPos) -> io::Result<bool> {
unreachable!()
}
fn context_shape(
&mut self,
_: CollisionBlock,
_: BlockPos,
_: &CollisionContext<'_>,
) -> io::Result<VoxelShape> {
unreachable!()
}
}
fn bounds() -> Aabb {
Aabb::new(Vec3::ZERO, Vec3::new(1.0, 1.0, 1.0))
}
#[test]
fn budgets_include_skipped_corners_and_errors_fuse_iteration() {
for budget in [0, 1] {
let mut world = FailedChunk { calls: 0 };
let mut scan = BlockCollisionScan::new(
&mut world,
CollisionContext::empty(false),
bounds(),
false,
budget,
)
.unwrap();
assert!(matches!(
scan.next(),
Some(Err(CollisionScanError::VisitBudget))
));
assert!(scan.next().is_none());
drop(scan);
assert_eq!(world.calls, 0);
}
let mut world = FailedChunk { calls: 0 };
let mut scan = BlockCollisionScan::new(
&mut world,
CollisionContext::empty(false),
bounds(),
false,
1000,
)
.unwrap();
assert!(matches!(
scan.next(),
Some(Err(CollisionScanError::World(_)))
));
assert!(scan.next().is_none());
drop(scan);
assert_eq!(world.calls, 1);
}
#[test]
fn invalid_or_excessive_domains_fail_before_world_queries() {
let mut world = FailedChunk { calls: 0 };
for coordinate in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
let bounds = Aabb::new(Vec3::ZERO, Vec3::new(coordinate, 1.0, 1.0));
assert!(matches!(
BlockCollisionScan::new(
&mut world,
CollisionContext::empty(false),
bounds,
false,
1000
),
Err(CollisionScanError::InvalidBounds)
));
}
let bounds = Aabb::new(Vec3::ZERO, Vec3::new(100000.0, 100000.0, 100000.0));
assert!(matches!(
BlockCollisionScan::new(
&mut world,
CollisionContext::empty(false),
bounds,
false,
1000
),
Err(CollisionScanError::TooLarge)
));
assert_eq!(world.calls, 0);
}
+21
View File
@@ -392,3 +392,24 @@ with **2,391,334 lines per JAR** in its
[regression report](../benchmarks/rust-canonical-collision-2026-10-06/contextual-regression.json).
Five import tests cover legacy data, equal-but-distinct grids, canonical restoration
and malformed identity metadata. Workspace Clippy passes without warnings.
`BlockCollisionScan` now implements lazy cursor selection around entity bounds.
It retains the native epsilon-expanded cursor, X/Y/Z visit order, skipped corners,
large-shape face filtering, moving-piston edge filtering and suffocation-before-
shape query order. Only successful chunk lookups are reused; absent chunks are
queried again. Shape selection uses the global collision world, while suffocation
uses the selected chunk. Canonical full blocks use native AABB intersection;
other shapes use moved voxel intersection. Returned positions are independent
values. Providers supply current block metadata and contextual shape selection.
`python3 tools/test_block_collision_scan_parity.py` compares **4,176 fixtures**
and **517,820 matching lines per JAR** in the
[collision-scan report](../benchmarks/rust-block-collision-scan-2026-10-06/scoped-parity.json).
The native iterator runs over controlled metadata, shapes and chunk availability;
traces cover early termination, missing chunks, query failures, thin/zero-width
bounds, integer-coordinate wrapping and equal shapes with different canonical
identities. Two Rust API tests cover finite/bounded input requirements, cursor
visit budgets and termination after errors. Unsupported cursor volumes return
errors; Java's overflowing iteration counts are outside this bounded API.
Registry/world integration and live movement still need implementation.
@@ -0,0 +1,73 @@
package dev.horizonlab.parity;
import com.google.gson.*;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.*;
import net.minecraft.core.BlockPos;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.*;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.*;
import net.minecraft.world.phys.shapes.*;
/** Runs native BlockCollisions with controlled block metadata and traced providers. */
public final class VanillaBlockCollisionScanOracle {
static final class Stop extends RuntimeException {}
static final class Trace {
PrintWriter out;int calls,fail;CollisionGetter global;
void query(String value){out.println(value);if(calls++==fail)throw new Stop();}
}
public static class State extends BlockState {
int id;boolean large,moving,suffocating;VoxelShape shape;Trace trace;BlockGetter readFrom;
public State(){super(null,null,null);}
@Override public Block getBlock(){return moving?Blocks.MOVING_PISTON:Blocks.STONE;}
@Override public boolean hasLargeCollisionShape(){return large;}
@Override public boolean isSuffocating(BlockGetter world,BlockPos p){if(world!=readFrom||world==trace.global)throw new AssertionError("wrong suffocation world");trace.query("suffocating "+p.getX()+" "+p.getY()+" "+p.getZ()+" "+id);return suffocating;}
}
static VoxelShape shape(int index){return switch(index){
case 0->Shapes.empty();case 1->Shapes.block();case 2->Shapes.block().move(0,0,0);
case 3->Shapes.box(0,0,0,1,0.5,1);case 4->Shapes.box(-0.25,-0.5,-0.25,1.25,1.5,1.25);
case 5->Shapes.box(0,0,0,1e-8,1,1);
case 6->Shapes.or(Shapes.box(0,0,0,0.25,1,0.25),Shapes.box(0.75,0,0.75,1,1,1));
default->throw new AssertionError(index);
};}
static double number(JsonElement value){return Double.longBitsToDouble(Long.parseUnsignedLong(value.getAsString(),16));}
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 access=allocator.getDeclaredField("theUnsafe");access.setAccessible(true);var unsafe=access.get(null);var allocate=allocator.getMethod("allocateInstance",Class.class);
var emit=VanillaFaceSupportOracle.class.getDeclaredMethod("emit",PrintWriter.class,VoxelShape.class);emit.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 trace=new Trace();trace.out=out;trace.fail=f.get("fail").getAsInt();
var palette=f.getAsJsonArray("palette");var states=new State[palette.size()];
for(int i=0;i<states.length;i++){var v=palette.get(i).getAsJsonArray();var state=(State)allocate.invoke(unsafe,State.class);state.id=i;state.shape=shape(v.get(0).getAsInt());state.large=v.get(1).getAsBoolean();state.moving=v.get(2).getAsBoolean();state.suffocating=v.get(3).getAsBoolean();state.trace=trace;states[i]=state;}
int seed=f.get("seed").getAsInt(),missing=f.get("missing").getAsInt();
var world=(CollisionGetter)Proxy.newProxyInstance(CollisionGetter.class.getClassLoader(),new Class<?>[]{CollisionGetter.class},(proxy,method,values)->{
if(!method.getName().equals("getChunkForCollisions"))throw new AssertionError(method);
int cx=(int)values[0],cz=(int)values[1];trace.query("chunk "+cx+" "+cz);
if(missing==1||missing==2&&((cx^cz)&1)!=0)return null;
return Proxy.newProxyInstance(BlockGetter.class.getClassLoader(),new Class<?>[]{BlockGetter.class},(chunk,m,a)->{
if(!m.getName().equals("getBlockState"))throw new AssertionError(m);
var p=(BlockPos)a[0];trace.query("block "+p.getX()+" "+p.getY()+" "+p.getZ());
var state=states[((p.getX()*3+p.getY()*5+p.getZ()*7+seed)&Integer.MAX_VALUE)%states.length];state.readFrom=(BlockGetter)chunk;return state;
});
});trace.global=world;
var context=new CollisionContext(){
public boolean isDescending(){throw new AssertionError();}
public boolean isAbove(VoxelShape s,BlockPos p,boolean d){throw new AssertionError();}
public boolean isHoldingItem(Item i){throw new AssertionError();}
public boolean alwaysCollideWithFluid(){throw new AssertionError();}
public boolean canStandOnFluid(FluidState a,FluidState b){throw new AssertionError();}
public VoxelShape getCollisionShape(BlockState state,CollisionGetter collision,BlockPos p){if(collision!=world)throw new AssertionError("wrong collision world");var s=(State)state;trace.query("shape "+p.getX()+" "+p.getY()+" "+p.getZ()+" "+s.id);return s.shape;}
};
var v=f.getAsJsonArray("box");var box=new AABB(number(v.get(0)),number(v.get(1)),number(v.get(2)),number(v.get(3)),number(v.get(4)),number(v.get(5)));
var scan=new BlockCollisions<VoxelShape>(world,context,box,f.get("suffocating").getAsBoolean(),(p,s)->{out.println("hit "+p.getX()+" "+p.getY()+" "+p.getZ());return s;});
int hits=0,limit=f.get("limit").getAsInt();
try{while(hits<limit&&scan.hasNext()){emit.invoke(null,out,scan.next());hits++;}}catch(Stop error){out.println("error");}
out.println("done "+hits+" "+trace.calls);
}
}
}
}
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Compare lazy block collision scanning and query ordering with pinned vanilla."""
import argparse
import json
import os
from pathlib import Path
import platform
import subprocess
import random
import struct
try:
from . import lab, test_rust_parity as base
except ImportError:
import lab
import test_rust_parity as base
def corpus():
def box(values):return [struct.pack('>d',float(v)).hex() for v in values]
boxes=[
[0.2,0.2,0.2,0.8,0.8,0.8],[-0.3,0,0.3,0.3,1.8,0.9],[0,0,0,1,1,1],
[0.5,0.2,0.2,0.5,0.8,0.8],[0.5,0.2,0.2,0.5+1e-8,0.8,0.8],
[0.5,0.2,0.2,0.5+2e-7,0.8,0.8],[1-1e-8,0.2,0.2,1+1e-8,0.8,0.8],
[15.8,63.2,-0.3,16.2,64.8,0.3],[-16.2,-64.3,-16.2,-15.8,-62.5,-15.8],
[0.5,0.5,0.5,0.5,0.5,0.5],[1e-7,1e-7,1e-7,1-1e-7,1-1e-7,1-1e-7],
[2,2,2,-1,-1,-1],[-20.2,0.2,0.2,20.2,0.8,0.8],
[2147483647.2,0.2,0.2,2147483647.8,0.8,0.8],
[-2147483648.8,-0.5,-0.5,-2147483648.2,0.5,0.5],
[0.2,2147483647.2,0.2,0.8,2147483647.8,0.8],
[-0.0,-0.0,-0.0,1e-8,1e-8,1e-8],[-1e-8,-1e-8,-1e-8,0,0,0],
]
palettes=[[shape,large,moving,suffocating] for shape in range(7) for large in [False,True] for moving in [False,True] for suffocating in [False,True]]
def row(bounds,palette,suffocating=False,missing=0,fail=-1,limit=100000,seed=0):
return {'box':box(bounds),'palette':palette,'suffocating':suffocating,'missing':missing,'fail':fail,'limit':limit,'seed':seed}
for entry in palettes:
for bounds in boxes:
for suffocating in [False,True]:yield row(bounds,[entry],suffocating)
for fail in [0,1,2,5,15,50]:
for suffocating in [False,True]:yield row(boxes[7],[entry],suffocating,fail=fail)
for missing in [1,2]:
for bounds in boxes:
for suffocating in [False,True]:
for limit in [0,1,4,100000]:yield row(bounds,palettes,suffocating,missing,limit=limit)
rng=random.Random(0x434f4c4c4953494f4e)
for index in range(1200):
low=[rng.choice([-16,-1,0,15,16])+rng.random() for _ in range(3)]
high=[v+rng.choice([0,1e-8,2e-7,0.25,0.6,1.8,2.5]) for v in low]
palette=rng.sample(palettes,rng.randrange(1,13))
yield row([*low,*high],palette,bool(index%2),rng.randrange(3),rng.choice([-1,-1,-1,0,3,10,50]),rng.choice([0,1,3,100000]),rng.randrange(-2147483648,2147483648))
def main():
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--prepare',action='store_true');args=parser.parse_args()
build=lab.LOCAL/'rust-block-collision-scan-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/VanillaBlockCollisionScanOracle.java'
helpers=[oracle.with_name('VanillaFaceSupportOracle.java')]
sources=[oracle,*helpers,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/block_collision_scan_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,*helpers],'compile-java')
def java(side,mode,path):
run(['java','-Xmx512M','-cp',os.pathsep.join([str(classes),str(jars[side]),dependency_path]),'dev.horizonlab.parity.VanillaBlockCollisionScanOracle',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','block_collision_scan_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'/('block_collision_scan_fixture.exe' if os.name=='nt' else 'block_collision_scan_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:,} block-collision-scan 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 BlockCollisions cursor and selection algorithm over controlled block metadata, chunk providers and context shapes. Compares ordered chunk/state/suffocation/shape calls, missing-chunk retries, cached chunk reuse, face/edge/corner filtering, canonical full-block versus equal noncanonical shape behavior, exact emitted shape data, early consumer termination and injected provider failures. Finite bounded scans include thin/zero-size boxes, chunk crossings and wrapped integer positions. Registry-specific metadata/shape dispatch, live chunk loading and entity movement are separate integration work; unsupported cursor volumes and caller budgets use typed Rust errors.'}
lab.write_json(report_path,report);print(f'Scoped block collision scan parity passed: {report_path}')
if __name__=='__main__':main()