From f5ba959cdcc62f01a4bb1fc0bc47d258eb182b22 Mon Sep 17 00:00:00 2001 From: Ludovic Marie <136255825+ludovic111@users.noreply.github.com> Date: Tue, 6 Oct 2026 22:10:44 +0200 Subject: [PATCH] Match native neighboring block-face visibility and suppression --- .../scoped-parity.json | 217 ++++++++++++++++++ .../examples/face_visibility_fixture.rs | 85 +++++++ crates/horizon-world/src/block_render.rs | 205 +++++++++++++++++ crates/horizon-world/src/lib.rs | 1 + docs/face-visibility.md | 41 ++++ docs/native-client.md | 4 + docs/rust-parity.md | 2 + .../parity/VanillaFaceVisibilityOracle.java | 68 ++++++ tools/test_face_visibility_parity.py | 94 ++++++++ 9 files changed, 717 insertions(+) create mode 100644 benchmarks/rust-face-visibility-2026-10-06/scoped-parity.json create mode 100644 crates/horizon-world/examples/face_visibility_fixture.rs create mode 100644 crates/horizon-world/src/block_render.rs create mode 100644 docs/face-visibility.md create mode 100644 mods/horizon/src/test/java/dev/horizonlab/parity/VanillaFaceVisibilityOracle.java create mode 100644 tools/test_face_visibility_parity.py diff --git a/benchmarks/rust-face-visibility-2026-10-06/scoped-parity.json b/benchmarks/rust-face-visibility-2026-10-06/scoped-parity.json new file mode 100644 index 0000000000..bedf33f290 --- /dev/null +++ b/benchmarks/rust-face-visibility-2026-10-06/scoped-parity.json @@ -0,0 +1,217 @@ +{ + "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, + "block_definitions": 1286, + "behavior_kinds": [ + "bars", + "leaves", + "liquid", + "mangrove_roots", + "never", + "same_block" + ], + "pairs": 1441816, + "direction_and_leaf_setting_cases": 17301792, + "sources_sha256": { + "mods/horizon/src/test/java/dev/horizonlab/parity/VanillaFaceVisibilityOracle.java": "00f5337317bba7a491bb62bbf07b32637657055c6f3076066bb01edf134169a1", + "mods/horizon/src/test/java/dev/horizonlab/parity/VanillaCollisionOracle.java": "60ff9ef7c1d5e23c080f9aec95ab2e7d3b7c5b5bcd67b9efa9518776c400629a", + "mods/horizon/src/test/java/dev/horizonlab/parity/VanillaHeightmapOracle.java": "145c943637ed177cc4ef8a85f2484da8afd164868e84a8463882feec1653a860", + "tools/test_face_visibility_parity.py": "3f9bc4ebd8af5e5c600a0bd507210aa757c1e5faca8536f43fffead45fc1e02d", + "tools/test_rust_parity.py": "29d3db08005873528894b45ca10dcd6278753650473b3eb574e53e06ede40ec6", + "tools/test_worldgen_parity.py": "b40972ab2ad222aa6d77537cc36b434a217baa1f1b56bbaad945d6e12fe776af", + "tools/lab.py": "d00bf107fa01b6c579734a082c3ca4ade3018f0d6851d46844acf17574f66e64", + "Cargo.toml": "3f93319c1eb70dbd7ba25cd74ef343fbf35c52c7c4752f955605c58699cbff9d", + "Cargo.lock": "cb1e87d449114a63edd904c0559d631e8fa9feeb27efd6539aacdcfeae178876", + "crates/horizon-world/examples/face_visibility_fixture.rs": "e3e58c9d092fcf11c1e50d971e20f8be3bd3d9d462d48e26f9a550fdb2937218", + "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/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/Cargo.toml": "977338bf3e654193685b015d383493b9f59ff6f5d6210bec212cef8ac4a50bed", + "crates/horizon-world/src/bin/worldgen_parity.rs": "3143ceb9b56eecfd7667e5feb5f9f0cd13cfb8754b38d4ea338dbba97f2819da", + "crates/horizon-world/src/block_collision.rs": "cec2af81d212d5eed7a2196e4cf0be2daf4cecf53248f78cc68f2190e4d83f9a", + "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": "05313e0c1f3089524fe029f9aac3e439cd47f1c9584c8c7e154c9adcfa88c335", + "crates/horizon-world/src/compression.rs": "4db73e6ca9a0d552d415ce60e7e88b89c9cf2b7d060a669ff8328f518316e101", + "crates/horizon-world/src/dynamic_support.rs": "e799b31e5120cfdb04e0b342f2097d610ed98ef8c4be031620ee00368d82df59", + "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": "9f254668a237fc2d28a599b0ee6f1eac57cdafa2fd1af67e6a2303ee98fdd5ed", + "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/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" + }, + "fixtures_sha256": { + ".local/rust-face-visibility-parity/pairs.txt": "555ad20f071cfbe3cdcb9533a6ffedd3e44ede8f29e91f6566e8f7256b1ea01c", + ".local/rust-face-visibility-parity/block-registry.nbt": "5042073c80753f9548cb8fdb5b1000f0e5a1220127a0cc3c6a75737e180bf9bb", + ".local/rust-face-visibility-parity/occlusion.nbt": "a3ae54c4316613140fb93fabec61e32a11d46185da7abc4e6f453bb900f260a8", + ".local/rust-face-visibility-parity/definitions.json": "7084a7304cb44fdfeea3eb9f8a228d5a4af8a4029ad58f0831a044720eea27f7", + ".local/rust-face-visibility-parity/tags.json": "5e1301e5a9abf7e4de93af5dae3a157e852dbdb4ea345c2eb626305a605d4f98" + }, + "rust_output_sha256": "e537bccb19f4e91569e5389c9578ced9f41fe3f258b8e1881a38f9f69a04e2a6", + "references": [ + { + "side": "client", + "jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d", + "output_sha256": "e537bccb19f4e91569e5389c9578ced9f41fe3f258b8e1881a38f9f69a04e2a6", + "matched_output_lines": 2883632 + }, + { + "side": "server", + "jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6", + "output_sha256": "e537bccb19f4e91569e5389c9578ced9f41fe3f258b8e1881a38f9f69a04e2a6", + "matched_output_lines": 2883632 + } + ], + "scope": "Actual BlockState.skipRendering and Block.shouldRenderFace for all registered states against representative and seeded random neighbors, exhaustive same-block pairs for override families and every cross-material bars/panes connection state pair. All six directions and both cutout-leaf settings; pinned block tags bound on both Java sides. Rust imports raw occlusion grids, class-rule assignments, definition canOcclude flags and tag membership, then computes face geometry, canonical shortcuts, skip rules and ONLY_FIRST subtraction. Includes waterlogged fluid neighbors, glass identity, vertical roots, bars tag/property checks and partial occlusion. Does not establish chunk mesh integration, lighting, translucent sorting, fluid surfaces, GPU pixels or gameplay parity." +} diff --git a/crates/horizon-world/examples/face_visibility_fixture.rs b/crates/horizon-world/examples/face_visibility_fixture.rs new file mode 100644 index 0000000000..b2ef8716c9 --- /dev/null +++ b/crates/horizon-world/examples/face_visibility_fixture.rs @@ -0,0 +1,85 @@ +use horizon_core::geometry::Direction; +use horizon_world::{ + block_render::{FaceVisibility, RenderDefinition, SkipRendering}, + collision::CachedCollisionRegistry, + nbt::{self, Limits, Tag}, + registry::{BlockRegistry, BlockStateId}, +}; +use serde_json::Value; +use std::{ + collections::BTreeMap, + fs, + io::{BufRead, BufReader, BufWriter, Write}, + path::Path, +}; +fn main() -> Result<(), Box> { + let path = std::env::args().nth(1).ok_or("private fixture directory")?; + let dir = Path::new(&path); + let read = |name: &str| -> Result> { + Ok( + nbt::read_named(&fs::read(dir.join(name))?, Limits::default())? + .0 + .tag, + ) + }; + let registry = BlockRegistry::from_nbt(&read("block-registry.nbt")?)?; + let occlusion = CachedCollisionRegistry::from_nbt(&read("occlusion.nbt")?, ®istry)?; + let definitions: BTreeMap = + serde_json::from_reader(fs::File::open(dir.join("definitions.json"))?)?; + let definitions = definitions + .into_iter() + .map(|(name, v)| { + let skip = match v["rule"].as_str().ok_or("missing rule")? { + "never" => SkipRendering::Never, + "same_block" => SkipRendering::SameBlock, + "bars" => SkipRendering::Bars, + "leaves" => SkipRendering::Leaves, + "liquid" => SkipRendering::Liquid, + "mangrove_roots" => SkipRendering::MangroveRoots, + _ => return Err("unrecognized rendering rule"), + }; + Ok(( + name, + RenderDefinition { + skip, + can_occlude: v["can_occlude"].as_bool().ok_or("missing occlusion flag")?, + bars_tag: v["bars_tag"].as_bool().ok_or("missing bars tag")?, + }, + )) + }) + .collect::>()?; + let mut visibility = FaceVisibility::new(&occlusion, &definitions)?; + for invalid in [registry.len() as u32, u32::MAX] { + assert!( + visibility + .should_render_face(BlockStateId(invalid), BlockStateId(0), Direction::Up, false) + .is_err() + ); + assert!( + visibility + .skip_rendering(BlockStateId(0), BlockStateId(invalid), Direction::Up, false) + .is_err() + ); + } + let mut output = BufWriter::new(fs::File::create(dir.join("rust.txt"))?); + for line in BufReader::new(fs::File::open(dir.join("pairs.txt"))?).lines() { + let line = line?; + let (a, b) = line.split_once(' ').ok_or("invalid pair")?; + let a = BlockStateId(a.parse()?); + let b = BlockStateId(b.parse()?); + for cutout in [false, true] { + let mut skip = 0u8; + let mut visible = 0u8; + for direction in Direction::ALL { + if visibility.skip_rendering(a, b, direction, cutout)? { + skip |= 1 << direction as u8; + } + if visibility.should_render_face(a, b, direction, cutout)? { + visible |= 1 << direction as u8; + } + } + writeln!(output, "{} {} {cutout} {skip} {visible}", a.0, b.0)?; + } + } + Ok(()) +} diff --git a/crates/horizon-world/src/block_render.rs b/crates/horizon-world/src/block_render.rs new file mode 100644 index 0000000000..129614cf4a --- /dev/null +++ b/crates/horizon-world/src/block_render.rs @@ -0,0 +1,205 @@ +//! Neighbor face visibility over immutable block definitions and occlusion grids. +//! This decides whether a culled model face is emitted; it does not shade it or +//! implement translucent sorting, fluid meshing, or block updates. +use crate::{ + collision::CachedCollisionRegistry, + compression::invalid, + fluid::{FluidKind, FluidState}, + registry::BlockStateId, +}; +use horizon_core::geometry::{Direction, ShapeOp, VoxelShape}; +use std::{ + collections::{BTreeMap, HashMap}, + io, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SkipRendering { + Never, + SameBlock, + Bars, + Leaves, + Liquid, + MangroveRoots, +} +#[derive(Clone, Copy, Debug)] +pub struct RenderDefinition { + pub skip: SkipRendering, + pub can_occlude: bool, + pub bars_tag: bool, +} +#[derive(Clone, Copy)] +struct State { + block: BlockStateId, + faces: [u32; 6], + definition: RenderDefinition, + connections: u8, + connection_properties: u8, + fluid: FluidKind, +} +/// Prepared face geometry and a bounded cache shared across state pairs. Geometry +/// is deduplicated by the imported immutable shape, not copied for every state. +pub struct FaceVisibility { + states: Vec, + faces: Vec, + cache: HashMap<(u32, u32), bool>, +} +impl FaceVisibility { + pub fn new( + occlusion: &CachedCollisionRegistry<'_>, + definitions: &BTreeMap, + ) -> io::Result { + let registry = occlusion.registry(); + let mut states = Vec::with_capacity(registry.len()); + // IDs 0 and 1 represent the canonical empty/full face singletons used + // by BlockState.initCache; other empty-like shapes retain their identity. + let mut faces = vec![VoxelShape::empty(), VoxelShape::block()]; + let mut shape_faces = HashMap::new(); + for id in 0..registry.len() { + let id = BlockStateId(id as u32); + let state = registry + .state(id) + .ok_or_else(|| invalid("unknown render state"))?; + let definition = *definitions + .get(&state.name) + .ok_or_else(|| invalid("missing render definition"))?; + let shape = occlusion + .cached_shape(id)? + .ok_or_else(|| invalid("missing occlusion geometry"))?; + let face_ids = if !definition.can_occlude || shape.is_empty() { + [0; 6] + } else if let Some(ids) = shape_faces.get(&std::ptr::from_ref(shape)) { + *ids + } else { + let mut ids = [1; 6]; + if shape + .join_is_not_empty(&faces[1], ShapeOp::XOR) + .map_err(|e| invalid(e.to_string()))? + { + for direction in Direction::ALL { + let face = shape + .face_shape(direction) + .map_err(|e| invalid(e.to_string()))?; + ids[direction as usize] = if face.same_identity(&faces[0]) { + 0 + } else if face.same_identity(&faces[1]) { + 1 + } else { + let id = faces.len() as u32; + faces.push(face); + id + }; + } + } + shape_faces.insert(std::ptr::from_ref(shape), ids); + ids + }; + let mut connections = 0; + let mut connection_properties = 0; + for (name, direction) in [ + ("north", Direction::North), + ("south", Direction::South), + ("west", Direction::West), + ("east", Direction::East), + ] { + if let Some(value @ ("false" | "true")) = + state.properties.get(name).map(String::as_str) + { + connection_properties |= 1 << direction as u8; + if value == "true" { + connections |= 1 << direction as u8; + } + } + } + if definition.skip == SkipRendering::Bars && connection_properties != 0b111100 { + return Err(invalid("bars rendering requires four boolean connections")); + } + states.push(State { + block: registry + .default_state(&state.name) + .ok_or_else(|| invalid("missing render block identity"))?, + faces: face_ids, + definition, + connections, + connection_properties, + fluid: FluidState::from_block(state)?.kind(), + }); + } + Ok(Self { + states, + faces, + cache: HashMap::new(), + }) + } + fn state(&self, id: BlockStateId) -> io::Result { + self.states + .get(id.0 as usize) + .copied() + .ok_or_else(|| invalid("unknown render state ID")) + } + fn skips(a: State, b: State, direction: Direction, cutout_leaves: bool) -> bool { + let vertical = matches!(direction, Direction::Down | Direction::Up); + let bit = 1 << direction as u8; + let opposite = 1 << direction.opposite() as u8; + match a.definition.skip { + SkipRendering::Never => false, + SkipRendering::SameBlock => a.block == b.block, + SkipRendering::Leaves => !cutout_leaves && b.definition.skip == SkipRendering::Leaves, + SkipRendering::Liquid => a.fluid.same_family(b.fluid), + SkipRendering::MangroveRoots => a.block == b.block && vertical, + SkipRendering::Bars => { + (a.block == b.block + || (a.definition.bars_tag + && b.definition.bars_tag + && b.connection_properties & opposite != 0)) + && (vertical || (a.connections & bit != 0 && b.connections & opposite != 0)) + } + } + } + pub fn skip_rendering( + &self, + state: BlockStateId, + neighbor: BlockStateId, + direction: Direction, + cutout_leaves: bool, + ) -> io::Result { + Ok(Self::skips( + self.state(state)?, + self.state(neighbor)?, + direction, + cutout_leaves, + )) + } + pub fn should_render_face( + &mut self, + state: BlockStateId, + neighbor: BlockStateId, + direction: Direction, + cutout_leaves: bool, + ) -> io::Result { + let a = self.state(state)?; + let b = self.state(neighbor)?; + let neighbor_face = b.faces[direction.opposite() as usize]; + if neighbor_face == 1 || Self::skips(a, b, direction, cutout_leaves) { + return Ok(false); + } + let own_face = a.faces[direction as usize]; + if neighbor_face == 0 || own_face == 0 { + return Ok(true); + } + let key = (own_face, neighbor_face); + if let Some(value) = self.cache.get(&key) { + return Ok(*value); + } + let visible = self.faces[own_face as usize] + .join_is_not_empty(&self.faces[neighbor_face as usize], ShapeOp::ONLY_FIRST) + .map_err(|e| invalid(e.to_string()))?; + // Cache policy affects speed only. Keep bounded memory while retaining + // more shared face pairs than the native 256-entry per-thread cache. + if self.cache.len() == 4096 { + self.cache.clear(); + } + self.cache.insert(key, visible); + Ok(visible) + } +} diff --git a/crates/horizon-world/src/lib.rs b/crates/horizon-world/src/lib.rs index 04667b337e..1ac0957e8c 100644 --- a/crates/horizon-world/src/lib.rs +++ b/crates/horizon-world/src/lib.rs @@ -5,6 +5,7 @@ pub mod block_collision; pub mod block_entities; pub mod block_placement; pub mod block_properties; +pub mod block_render; pub mod block_support; pub mod block_survival; pub mod chunk; diff --git a/docs/face-visibility.md b/docs/face-visibility.md new file mode 100644 index 0000000000..f4112eb701 --- /dev/null +++ b/docs/face-visibility.md @@ -0,0 +1,41 @@ +# Neighboring model-face visibility + +`horizon-world::block_render::FaceVisibility` decides whether to emit a model +quad with a culling direction beside a neighboring block. It prepares immutable +face geometry from the pinned raw occlusion shapes, shares faces across states, +and caches shape-pair subtraction with bounded memory. It is ready for chunk +meshing; it does not yet replace the development scene's cube-only culling. + +The decision preserves the native order: a canonical full neighbor face hides +the quad, block-specific suppression can hide it, canonical empty faces bypass +the shape comparison, and partial faces use `ONLY_FIRST` subtraction. Full +occlusion is computed from geometry, independently of collision or support. + +Block-specific suppression covers every method owner in the pinned registry: +ordinary blocks, same-block glass/ice and powder snow, bars/panes, leaves, +liquids, and mangrove roots. Bars respect the loaded bars tag, facing connections, +and the neighbor's boolean property availability. Leaf suppression is controlled +by the cutout-leaves setting. Liquids compare fluid families, including contained +water in waterlogged neighbors. Mangrove roots suppress vertical internal faces. + +The [native comparison](../benchmarks/rust-face-visibility-2026-10-06/scoped-parity.json) +calls actual `BlockState.skipRendering` and `Block.shouldRenderFace` with pinned +tags bound in both reference JARs. It covers all 35,723 states and 1,286 block +definitions: 1,441,816 ordered state pairs, six directions and two leaf settings +(17,301,792 direction/setting cases). Every overriding family has exhaustive +same-block state pairs; bars and panes also have exhaustive cross-material +connection pairs. Representative and seeded random neighbors exercise partial +occlusion across the rest of the registry. Both references match 2,883,632 output +lines, each containing suppression and visibility masks for all six directions. + +```sh +python3 tools/test_face_visibility_parity.py +cargo clippy --workspace --all-targets --locked -- -D warnings +``` + +The private imports contain raw shape grids, definition flags, class-rule +assignments and tag membership. Rust computes the behavior being compared. +Unknown state IDs produce errors. Native fixture files, reference JARs and traces +stay under ignored `.local/`; only hashed reports are committed. Lighting, +translucent sorting, fluid surfaces, GPU pixels and gameplay remain outside this +comparison. Native macOS and Windows runs are still required. diff --git a/docs/native-client.md b/docs/native-client.md index 2259d861cb..de20fac54b 100644 --- a/docs/native-client.md +++ b/docs/native-client.md @@ -262,3 +262,7 @@ property domains and canonical state order, which the Rust loader checks against its imported registry before binding. Each distinct transformed model is baked once during gallery preparation and reused across positions. The selected state IDs, properties, positions and models are recorded in the startup report. + +[Neighbor-face visibility](face-visibility.md) now has native client/server +coverage across all registered states and suppression-rule families. Integrating +that result with resident chunk model meshing is the next rendering step. diff --git a/docs/rust-parity.md b/docs/rust-parity.md index e6f6e7e36d..ef31caf53d 100644 --- a/docs/rust-parity.md +++ b/docs/rust-parity.md @@ -29,6 +29,8 @@ inspection gallery from registry-bound, selected and transformed models, includi cutouts, multipart connections, weighted stone variants and stepped lantern animation. All 16 scene selections match native model collection. These are not yet gameplay blocks; general chunk model rendering remains unfinished. +[Neighbor face visibility](face-visibility.md) now matches both reference JARs +across 1,441,816 state pairs, including all pinned suppression-rule families. Animation metadata, frame sequences and tick-state selection now have native differential coverage, including all bundled animated texture metadata. GPU interpolation and game-loop synchronization remain unfinished. diff --git a/mods/horizon/src/test/java/dev/horizonlab/parity/VanillaFaceVisibilityOracle.java b/mods/horizon/src/test/java/dev/horizonlab/parity/VanillaFaceVisibilityOracle.java new file mode 100644 index 0000000000..c88bcc7ed2 --- /dev/null +++ b/mods/horizon/src/test/java/dev/horizonlab/parity/VanillaFaceVisibilityOracle.java @@ -0,0 +1,68 @@ +package dev.horizonlab.parity; + +import com.google.gson.*; +import java.io.*; +import java.nio.file.*; +import java.util.*; +import net.minecraft.core.Direction; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.*; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.state.BlockState; + +/** Imports definition kinds and raw geometry; compares actual skipRendering and + * Block.shouldRenderFace on the unmodified native state registry. */ +public final class VanillaFaceVisibilityOracle { + private static final Gson JSON=new GsonBuilder().serializeNulls().create(); + private static String rule(Block block) throws Exception { + for(Class type=block.getClass();type!=null;type=type.getSuperclass()) { + try { + type.getDeclaredMethod("skipRendering",BlockState.class,BlockState.class,Direction.class); + return switch(type.getSimpleName()) { + case "BlockBehaviour" -> "never"; + case "HalfTransparentBlock", "PowderSnowBlock" -> "same_block"; + case "IronBarsBlock" -> "bars"; + case "LeavesBlock" -> "leaves"; + case "LiquidBlock" -> "liquid"; + case "MangroveRootsBlock" -> "mangrove_roots"; + default -> throw new AssertionError("Unhandled skip-rendering owner "+type); + }; + } catch(NoSuchMethodException ignored) {} + } + throw new AssertionError("Missing skip-rendering method"); + } + public static void main(String[] args)throws Exception { + net.minecraft.SharedConstants.tryDetectVersion();net.minecraft.server.Bootstrap.bootStrap(); + var directory=Path.of(args[1]); + VanillaHeightmapOracle.bindTags(JsonParser.parseString(Files.readString(directory.resolve("tags.json"))).getAsJsonObject()); + if(args[0].equals("export")) { + VanillaCollisionOracle.main(new String[]{"generate",args[1]}); + try(var output=new DataOutputStream(Files.newOutputStream(directory.resolve("occlusion.nbt")))) { + NbtIo.write(VanillaCollisionOracle.occlusionDefinitions(),output); + } + var definitions=new TreeMap(); + for(var block:BuiltInRegistries.BLOCK) { + var state=block.defaultBlockState();var ids=new ArrayList(); + for(var value:block.getStateDefinition().getPossibleStates())ids.add(Block.getId(value)); + definitions.put(BuiltInRegistries.BLOCK.getKey(block).toString(),Map.of("rule",rule(block), + "can_occlude",state.canOcclude(),"bars_tag",state.is(BlockTags.BARS),"default",Block.getId(state),"states",ids)); + } + Files.writeString(directory.resolve("definitions.json"),JSON.toJson(definitions));return; + } + try(var inputs=Files.newBufferedReader(directory.resolve("pairs.txt"));var output=Files.newBufferedWriter(directory.resolve(args[0]+".txt"))) { + for(String line;(line=inputs.readLine())!=null;) { + var values=line.split(" ");int a=Integer.parseInt(values[0]),b=Integer.parseInt(values[1]); + var state=Block.stateById(a);var neighbor=Block.stateById(b); + for(boolean cutout:new boolean[]{false,true}) { + LeavesBlock.setCutoutLeaves(cutout);int skip=0,visible=0; + for(var direction:Direction.values()) { + if(state.skipRendering(neighbor,direction))skip|=1<