Implement proto-chunk block-entity storage and coordinate resolution
This commit is contained in:
@@ -187,6 +187,10 @@ jobs:
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: stable
|
||||
run: python tools/test_generation_tick_parity.py
|
||||
- name: Compare proto-chunk block-entity storage and coordinates
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: stable
|
||||
run: python tools/test_block_entity_storage_parity.py
|
||||
- name: Compare ordered single-block replacement features
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: stable
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
{
|
||||
"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)",
|
||||
"commands": {
|
||||
"reset": 23,
|
||||
"resolve": 20825,
|
||||
"pending": 2533,
|
||||
"dump": 773,
|
||||
"get": 864,
|
||||
"save": 914,
|
||||
"mutate": 869,
|
||||
"active": 889,
|
||||
"remove": 821
|
||||
},
|
||||
"sources_sha256": {
|
||||
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaBlockEntityStorageOracle.java": "7d9c7b9f9b82e3b543a062424d4b59374fccc2068010d5523c6c0020c22c2e69",
|
||||
"tools/test_block_entity_storage_parity.py": "0a772a8db25e46db18d9a1962209c3199e6daea34140452e64cd73747805f198",
|
||||
"tools/test_rust_parity.py": "29d3db08005873528894b45ca10dcd6278753650473b3eb574e53e06ede40ec6",
|
||||
"tools/lab.py": "d00bf107fa01b6c579734a082c3ca4ade3018f0d6851d46844acf17574f66e64",
|
||||
"Cargo.toml": "dc370d5fdde37238c3e4820046f11c3201020406a4edfa1308257c13edbdcda4",
|
||||
"Cargo.lock": "7307d0c887364db36d327685ecf0671b75305e31e5ba5fbdcdbb15eaf156d20e",
|
||||
"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": "47350edab5791f5074a54563ba98108818c5c6b1c5818a591388eab2ac9f5490",
|
||||
"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/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": "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_support.rs": "ade4fbd47284b450dabdd682dc86138b2ac05a0fd379525d7110648908245476",
|
||||
"crates/horizon-world/src/block_survival.rs": "28ce64375e09819a4f13da9cfbe37c08b5ad0daf01b289c1c360b4567e50e811",
|
||||
"crates/horizon-world/src/chunk.rs": "8843e6f48dbbc4245ce4e95564dfba3a4d7a051ba0b1577fb038c07adb0ff0f9",
|
||||
"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": "4e556a5896b893fbb73bf2e2b8e70601a8374f58b9f4f5f3730e99916a846e3a",
|
||||
"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": "1eae82604d2b3377fd35aaa3a914a67e5af28f93923815a49e895b4d437433cd",
|
||||
"crates/horizon-world/src/registry.rs": "f2e58b2b4d944ac8fc6226bd3352cefc3d2052d8473e02ccf7dc402b176df435",
|
||||
"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": "d6389c8d1adf900eb8f0e6c8833feafe43dc92d74e5c7ffa6f3950975529d624",
|
||||
"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": "7677027594ed3df5e538402d5a3ba9276b67b56490463ca2686bfa418d02bd24",
|
||||
"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": "b8fb9fb80a66a851d916f07f15df1a40b6cc95b49e71aaec4d4b4107f13d9f9f",
|
||||
"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": "1d79a7fdb5dd18b0b59079cb2dc74682ba7af2a650c8efff027cfa530eee7bf9",
|
||||
"crates/horizon-world/src/worldgen.rs": "e8e9f099ee1fcec8eedce31805d676d3f89c18e84f494a147ffcbfee23d3d54f",
|
||||
"crates/horizon-world/examples/block_entity_storage_fixture.rs": "feefacc51bcf2b5af62869130512ac150664470c07cc2c2746d9258a6709b181"
|
||||
},
|
||||
"fixtures_sha256": {
|
||||
".local/rust-block-entity-storage-parity/inputs.txt": "9b9242240b36a6b65fbc0a3c707685946cbbbe16cfcafa59c5b16bd5aa33718f"
|
||||
},
|
||||
"rust_output_sha256": "81304a08cb574ec6b4c46ea76ffd4bdd8bfc331d5b07bd4bc4de3d7564340dc9",
|
||||
"references": [
|
||||
{
|
||||
"side": "client",
|
||||
"jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d",
|
||||
"output_sha256": "81304a08cb574ec6b4c46ea76ffd4bdd8bfc331d5b07bd4bc4de3d7564340dc9",
|
||||
"matched_output_lines": 78251
|
||||
},
|
||||
{
|
||||
"side": "server",
|
||||
"jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6",
|
||||
"output_sha256": "81304a08cb574ec6b4c46ea76ffd4bdd8bfc331d5b07bd4bc4de3d7564340dc9",
|
||||
"matched_output_lines": 78251
|
||||
}
|
||||
],
|
||||
"scope": "Native BlockEntity.getPosFromTag and actual ProtoChunk pending/active insertion, lookup, removal, position union and pending-save fallback. NumericTag coordinate reads cover integer narrowing, float/double floor overflow, infinities/NaNs, missing/nonnumeric fields, wrong-chunk relocation and wrapped chunk coordinates. Active entries replace pending data; later pending records cannot overwrite active entries. In-place pending coordinate mutation retains the original map key. Native chest entities carry loot seeds for active identity and basic saving-metadata comparisons; Rust supplies caller-owned payload serialization. Iteration order is normalized, and pending reads are borrowed/owned values rather than unrestricted Java aliases. Entity creation from NBT, complete entity serializers, active behavior and integration with chunk lifecycle remain pending."
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
use horizon_core::position::{BlockPos, ChunkPos};
|
||||
use horizon_world::{
|
||||
block_entities::{ProtoBlockEntities, position_from_tag},
|
||||
nbt::Tag,
|
||||
};
|
||||
use std::{
|
||||
fs,
|
||||
io::{self, BufRead, Write},
|
||||
str::SplitWhitespace,
|
||||
};
|
||||
struct Input<'a>(SplitWhitespace<'a>);
|
||||
impl Input<'_> {
|
||||
fn int(&mut self) -> i32 {
|
||||
self.0.next().unwrap().parse().unwrap()
|
||||
}
|
||||
fn long(&mut self) -> i64 {
|
||||
self.0.next().unwrap().parse().unwrap()
|
||||
}
|
||||
fn pos(&mut self) -> BlockPos {
|
||||
BlockPos::new(self.int(), self.int(), self.int())
|
||||
}
|
||||
fn tag(&mut self) -> Option<Tag> {
|
||||
let kind = self.0.next().unwrap();
|
||||
let text = self.0.next().unwrap();
|
||||
Some(match kind {
|
||||
"none" => return None,
|
||||
"byte" => Tag::Byte(text.parse().unwrap()),
|
||||
"short" => Tag::Short(text.parse().unwrap()),
|
||||
"int" => Tag::Int(text.parse().unwrap()),
|
||||
"long" => Tag::Long(text.parse().unwrap()),
|
||||
"float" => {
|
||||
let bits = u32::from_str_radix(text, 16).unwrap();
|
||||
Tag::Float(if f32::from_bits(bits) == 0.0 { 0 } else { bits })
|
||||
}
|
||||
"double" => {
|
||||
let bits = u64::from_str_radix(text, 16).unwrap();
|
||||
Tag::Double(if f64::from_bits(bits) == 0.0 { 0 } else { bits })
|
||||
}
|
||||
"string" => Tag::String(text.into()),
|
||||
_ => panic!("unknown atom"),
|
||||
})
|
||||
}
|
||||
}
|
||||
fn xyz(p: BlockPos) -> String {
|
||||
format!("{} {} {}", p.x, p.y, p.z)
|
||||
}
|
||||
fn atom(tag: Option<&Tag>) -> String {
|
||||
match tag {
|
||||
None => "none".into(),
|
||||
Some(Tag::Float(v)) => format!("float:{v:x}"),
|
||||
Some(Tag::Double(v)) => format!("double:{v:x}"),
|
||||
Some(Tag::Byte(v)) => format!("1:{v}"),
|
||||
Some(Tag::Short(v)) => format!("2:{v}"),
|
||||
Some(Tag::Int(v)) => format!("3:{v}"),
|
||||
Some(Tag::Long(v)) => format!("4:{v}"),
|
||||
Some(v) => format!("{}:invalid", v.id()),
|
||||
}
|
||||
}
|
||||
fn pending(tag: Option<&Tag>) -> String {
|
||||
match tag {
|
||||
None => "none".into(),
|
||||
Some(tag) => {
|
||||
let Some(Tag::Long(mark)) = tag.get("mark") else {
|
||||
panic!("missing marker")
|
||||
};
|
||||
format!(
|
||||
"{mark} {} {} {}",
|
||||
atom(tag.get("x")),
|
||||
atom(tag.get("y")),
|
||||
atom(tag.get("z"))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args: Vec<_> = std::env::args().collect();
|
||||
let mut out = io::BufWriter::new(fs::File::create(&args[2])?);
|
||||
let mut position = ChunkPos::default();
|
||||
let mut chunk = ProtoBlockEntities::<i64>::new(position);
|
||||
for (index, line) in io::BufReader::new(fs::File::open(&args[1])?)
|
||||
.lines()
|
||||
.enumerate()
|
||||
{
|
||||
let line = line?;
|
||||
let mut input = Input(line.split_whitespace());
|
||||
let op = input.0.next().unwrap();
|
||||
writeln!(out, "command {index} {op}")?;
|
||||
match op {
|
||||
"reset" => {
|
||||
position = ChunkPos::new(input.int(), input.int());
|
||||
chunk = ProtoBlockEntities::new(position);
|
||||
}
|
||||
"pending" | "resolve" => {
|
||||
let mut entries = std::collections::BTreeMap::new();
|
||||
entries.insert("mark".into(), Tag::Long(input.long()));
|
||||
for name in ["x", "y", "z"] {
|
||||
if let Some(tag) = input.tag() {
|
||||
entries.insert(name.into(), tag);
|
||||
}
|
||||
}
|
||||
let tag = Tag::Compound(entries);
|
||||
writeln!(out, "position {}", xyz(position_from_tag(position, &tag)?))?;
|
||||
if op == "pending" {
|
||||
chunk.set_pending(tag)?;
|
||||
}
|
||||
}
|
||||
"active" => {
|
||||
let p = input.pos();
|
||||
chunk.set_active(p, input.long());
|
||||
}
|
||||
"remove" => {
|
||||
chunk.remove(input.pos());
|
||||
}
|
||||
"mutate" => {
|
||||
let p = input.pos();
|
||||
let x = input.int();
|
||||
if let Some(Tag::Compound(map)) = chunk.pending_mut(p) {
|
||||
map.insert("x".into(), Tag::Int(x));
|
||||
}
|
||||
}
|
||||
"get" => {
|
||||
let p = input.pos();
|
||||
writeln!(
|
||||
out,
|
||||
"active {}",
|
||||
chunk.active(p).map_or("none".into(), ToString::to_string)
|
||||
)?;
|
||||
writeln!(out, "pending {}", pending(chunk.pending(p)))?;
|
||||
}
|
||||
"save" => {
|
||||
let p = input.pos();
|
||||
let saved = chunk.for_saving(p, |mark| {
|
||||
Ok::<_, io::Error>(Tag::compound([("LootTableSeed", Tag::Long(*mark))]))
|
||||
})?;
|
||||
if chunk.active(p).is_some() {
|
||||
let Some(Tag::Long(seed)) = saved.as_ref().unwrap().get("LootTableSeed") else {
|
||||
unreachable!()
|
||||
};
|
||||
writeln!(out, "saved active minecraft:chest {} {seed}", xyz(p))?;
|
||||
} else {
|
||||
writeln!(out, "saved pending {}", pending(saved.as_ref()))?;
|
||||
}
|
||||
}
|
||||
"dump" => {
|
||||
let mut positions: Vec<_> = chunk.positions().into_iter().collect();
|
||||
positions.sort_by_key(|p| (p.x, p.y, p.z));
|
||||
for p in &positions {
|
||||
writeln!(
|
||||
out,
|
||||
"entry {} {} {}",
|
||||
xyz(*p),
|
||||
chunk.active(*p).map_or("none".into(), ToString::to_string),
|
||||
pending(chunk.pending(*p))
|
||||
)?;
|
||||
}
|
||||
writeln!(
|
||||
out,
|
||||
"size {} {}",
|
||||
positions.len(),
|
||||
chunk.pending_entries().len()
|
||||
)?;
|
||||
}
|
||||
_ => panic!("unknown command"),
|
||||
}
|
||||
assert!(input.0.next().is_none());
|
||||
}
|
||||
out.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
//! Proto-chunk block-entity storage. Active payloads are caller-owned runtime
|
||||
//! types; this module does not create entities or execute their behavior.
|
||||
use crate::{compression::invalid, nbt::Tag};
|
||||
use horizon_core::position::{BlockPos, ChunkPos};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
io,
|
||||
};
|
||||
|
||||
/// Resolve native block-entity coordinates, relocating mismatched X/Z into the
|
||||
/// owning chunk while retaining local offsets and the full Y coordinate. The
|
||||
/// supplied tag is not rewritten. NumericTag float/double reads use Mth.floor.
|
||||
pub fn position_from_tag(chunk: ChunkPos, tag: &Tag) -> io::Result<BlockPos> {
|
||||
if !matches!(tag, Tag::Compound(_)) {
|
||||
return Err(invalid("block entity is not a compound"));
|
||||
}
|
||||
let read = |name| match tag.get(name) {
|
||||
Some(Tag::Byte(v)) => i32::from(*v),
|
||||
Some(Tag::Short(v)) => i32::from(*v),
|
||||
Some(Tag::Int(v)) => *v,
|
||||
Some(Tag::Long(v)) => *v as i32,
|
||||
Some(Tag::Float(bits)) => f64::from(f32::from_bits(*bits)).floor() as i32,
|
||||
Some(Tag::Double(bits)) => f64::from_bits(*bits).floor() as i32,
|
||||
_ => 0,
|
||||
};
|
||||
let (mut x, y, mut z) = (read("x"), read("y"), read("z"));
|
||||
if x >> 4 != chunk.x || z >> 4 != chunk.z {
|
||||
x = chunk.x.wrapping_shl(4).wrapping_add(x & 15);
|
||||
z = chunk.z.wrapping_shl(4).wrapping_add(z & 15);
|
||||
}
|
||||
Ok(BlockPos::new(x, y, z))
|
||||
}
|
||||
|
||||
pub struct ProtoBlockEntities<E> {
|
||||
position: ChunkPos,
|
||||
pending: HashMap<BlockPos, Tag>,
|
||||
active: HashMap<BlockPos, E>,
|
||||
}
|
||||
impl<E> ProtoBlockEntities<E> {
|
||||
pub fn new(position: ChunkPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
pending: HashMap::new(),
|
||||
active: HashMap::new(),
|
||||
}
|
||||
}
|
||||
/// Ignore a pending record when an active entity already owns its resolved
|
||||
/// position. Inserting an active entity removes any earlier pending record.
|
||||
pub fn set_pending(&mut self, tag: Tag) -> io::Result<()> {
|
||||
let position = position_from_tag(self.position, &tag)?;
|
||||
if !self.active.contains_key(&position) {
|
||||
self.pending.insert(position, tag);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Position must be the payload's immutable world position, as supplied by
|
||||
/// the runtime's block-entity constructor. No chunk/build-height gate applies.
|
||||
pub fn set_active(&mut self, position: BlockPos, entity: E) -> Option<E> {
|
||||
self.pending.remove(&position);
|
||||
self.active.insert(position, entity)
|
||||
}
|
||||
pub fn active(&self, position: BlockPos) -> Option<&E> {
|
||||
self.active.get(&position)
|
||||
}
|
||||
pub fn active_mut(&mut self, position: BlockPos) -> Option<&mut E> {
|
||||
self.active.get_mut(&position)
|
||||
}
|
||||
pub fn pending(&self, position: BlockPos) -> Option<&Tag> {
|
||||
self.pending.get(&position)
|
||||
}
|
||||
/// Mutating coordinate fields does not re-key an existing pending entry.
|
||||
pub fn pending_mut(&mut self, position: BlockPos) -> Option<&mut Tag> {
|
||||
self.pending.get_mut(&position)
|
||||
}
|
||||
pub fn pending_entries(&self) -> &HashMap<BlockPos, Tag> {
|
||||
&self.pending
|
||||
}
|
||||
pub fn positions(&self) -> HashSet<BlockPos> {
|
||||
self.pending
|
||||
.keys()
|
||||
.chain(self.active.keys())
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
pub fn remove(&mut self, position: BlockPos) -> Option<E> {
|
||||
self.pending.remove(&position);
|
||||
self.active.remove(&position)
|
||||
}
|
||||
/// Serialize an active entity when present, otherwise clone its pending tag.
|
||||
/// The caller supplies runtime serialization; this does not instantiate NBT.
|
||||
pub fn for_saving<T>(
|
||||
&self,
|
||||
position: BlockPos,
|
||||
serialize: impl FnOnce(&E) -> Result<Tag, T>,
|
||||
) -> Result<Option<Tag>, T> {
|
||||
if let Some(entity) = self.active.get(&position) {
|
||||
serialize(entity).map(Some)
|
||||
} else {
|
||||
Ok(self.pending.get(&position).cloned())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod block_collision;
|
||||
pub mod block_entities;
|
||||
pub mod block_placement;
|
||||
pub mod block_properties;
|
||||
pub mod block_support;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use horizon_core::position::{BlockPos, ChunkPos};
|
||||
use horizon_world::{block_entities::ProtoBlockEntities, nbt::Tag};
|
||||
|
||||
#[test]
|
||||
fn invalid_pending_data_is_rejected_without_replacing_active_or_pending_entries() {
|
||||
let mut store = ProtoBlockEntities::new(ChunkPos::new(0, 0));
|
||||
let active = BlockPos::new(0, 64, 0);
|
||||
store.set_active(active, "active");
|
||||
let pending = Tag::compound([("x", Tag::Int(1)), ("y", Tag::Int(64)), ("z", Tag::Int(0))]);
|
||||
store.set_pending(pending.clone()).unwrap();
|
||||
assert!(store.set_pending(Tag::String("invalid".into())).is_err());
|
||||
assert_eq!(store.active(active), Some(&"active"));
|
||||
assert_eq!(store.pending(BlockPos::new(1, 64, 0)), Some(&pending));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_active_serialization_never_falls_back_to_stale_pending_data() {
|
||||
let p = BlockPos::new(-1, 64, -1);
|
||||
let mut store = ProtoBlockEntities::new(ChunkPos::new(-1, -1));
|
||||
store
|
||||
.set_pending(Tag::compound([
|
||||
("x", Tag::Int(15)),
|
||||
("y", Tag::Int(64)),
|
||||
("z", Tag::Int(15)),
|
||||
]))
|
||||
.unwrap();
|
||||
store.set_active(p, 42);
|
||||
assert!(store.pending(p).is_none());
|
||||
let result = store.for_saving(p, |_| Err::<Tag, _>("serialization failed"));
|
||||
assert_eq!(result, Err("serialization failed"));
|
||||
assert_eq!(store.remove(p), Some(42));
|
||||
assert_eq!(
|
||||
store.for_saving(p, |_| Err::<Tag, _>("must not run")),
|
||||
Ok(None)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_save_snapshot_is_independent_and_mutating_coordinates_keeps_the_key() {
|
||||
let mut store = ProtoBlockEntities::<()>::new(ChunkPos::new(0, 0));
|
||||
let p = BlockPos::new(0, 0, 0);
|
||||
store
|
||||
.set_pending(Tag::compound([("payload", Tag::Long(42))]))
|
||||
.unwrap();
|
||||
let snapshot = store.for_saving(p, |_| Err::<Tag, _>(())).unwrap().unwrap();
|
||||
let Some(Tag::Compound(tag)) = store.pending_mut(p) else {
|
||||
panic!("pending")
|
||||
};
|
||||
tag.insert("x".into(), Tag::Int(123));
|
||||
assert_eq!(snapshot.get("x"), None);
|
||||
assert_eq!(store.pending(p).unwrap().get("x"), Some(&Tag::Int(123)));
|
||||
assert!(store.pending(BlockPos::new(123, 0, 0)).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floating_coordinates_floor_before_saturating_to_java_int() {
|
||||
use horizon_world::block_entities::position_from_tag;
|
||||
for (value, expected) in [
|
||||
(Tag::Float(0xcf000001), i32::MIN),
|
||||
(Tag::Double(0xc1e0000000000001), i32::MIN),
|
||||
(Tag::Float(f32::NEG_INFINITY.to_bits()), i32::MIN),
|
||||
(Tag::Double(f64::INFINITY.to_bits()), i32::MAX),
|
||||
(Tag::Double(f64::NAN.to_bits()), 0),
|
||||
(Tag::Float((-1.5_f32).to_bits()), -2),
|
||||
(Tag::Double((-1.5_f64).to_bits()), -2),
|
||||
] {
|
||||
let tag = Tag::compound([("y", value)]);
|
||||
assert_eq!(
|
||||
position_from_tag(ChunkPos::new(0, 0), &tag).unwrap().y,
|
||||
expected
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -813,3 +813,23 @@ Vanilla's actual public/private write-zone methods provide the reference. Worksp
|
||||
tests, all-target Clippy and both focused fixture tests pass. The owning world
|
||||
still supplies generation-step metadata and upgrading-world vertical restrictions;
|
||||
full WorldGenRegion effects remain pending.
|
||||
|
||||
## Proto-chunk block-entity storage
|
||||
|
||||
Rust now stores pending NBT and caller-owned active block-entity payloads by full
|
||||
block position. Active insertion removes pending data, later pending insertion
|
||||
cannot overwrite an active entry, and removal clears both maps. Saving uses the
|
||||
active serializer when present and otherwise returns a pending snapshot. Changing
|
||||
pending coordinate fields retains the existing map key. Coordinate resolution
|
||||
matches NumericTag integer narrowing and floating-point flooring before saturating
|
||||
conversion, then relocates mismatched X/Z into the owning chunk.
|
||||
|
||||
The [block-entity storage comparison](../benchmarks/rust-block-entity-storage-2026-10-06/scoped-parity.json)
|
||||
matches **78,251 lines per reference JAR** across **28,511 commands**, using actual
|
||||
native ProtoChunks and chest entities. Cases cover replacement, removal, position
|
||||
unions, save selection, missing/nonnumeric coordinates, infinities/NaNs, integer
|
||||
extremes and positions that alias when packed. Four focused Rust tests and
|
||||
all-target Clippy pass. Collection order is normalized; Rust exposes borrowed or
|
||||
owned data instead of unrestricted Java aliases. Entity construction from NBT,
|
||||
complete serializers, active entity behavior and resident chunk lifecycle
|
||||
integration remain pending.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package dev.horizonlab.parity;
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.registries.*;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.resources.*;
|
||||
import net.minecraft.world.level.*;
|
||||
import net.minecraft.world.level.biome.*;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.*;
|
||||
import net.minecraft.world.level.chunk.*;
|
||||
import net.minecraft.world.level.storage.loot.LootTable;
|
||||
|
||||
/** Native proto-chunk entity storage and numeric position transport. */
|
||||
public final class VanillaBlockEntityStorageOracle {
|
||||
private static final Comparator<BlockPos> ORDER=Comparator.<BlockPos>comparingInt(BlockPos::getX).thenComparingInt(BlockPos::getY).thenComparingInt(BlockPos::getZ);
|
||||
private static PalettedContainerFactory factory;
|
||||
private static String xyz(BlockPos p){return p.getX()+" "+p.getY()+" "+p.getZ();}
|
||||
private static final class Input {
|
||||
String[] words;int cursor=1;Input(String line){words=line.split(" ");}
|
||||
int integer(){return Integer.parseInt(words[cursor++]);}long number(){return Long.parseLong(words[cursor++]);}
|
||||
BlockPos pos(){return new BlockPos(integer(),integer(),integer());}
|
||||
Tag tag(){String type=words[cursor++],value=words[cursor++];return switch(type){
|
||||
case "none"->null;case "byte"->ByteTag.valueOf(Byte.parseByte(value));case "short"->ShortTag.valueOf(Short.parseShort(value));case "int"->IntTag.valueOf(Integer.parseInt(value));case "long"->LongTag.valueOf(Long.parseLong(value));
|
||||
case "float"->FloatTag.valueOf(Float.intBitsToFloat(Integer.parseUnsignedInt(value,16)));case "double"->DoubleTag.valueOf(Double.longBitsToDouble(Long.parseUnsignedLong(value,16)));case "string"->StringTag.valueOf(value);default->throw new AssertionError(type);
|
||||
};}
|
||||
}
|
||||
private static String atom(Tag tag){
|
||||
if(tag==null)return "none";
|
||||
if(tag instanceof FloatTag value)return "float:"+Integer.toUnsignedString(Float.floatToRawIntBits(value.floatValue()),16);
|
||||
if(tag instanceof DoubleTag value)return "double:"+Long.toUnsignedString(Double.doubleToRawLongBits(value.doubleValue()),16);
|
||||
if(tag instanceof NumericTag value)return tag.getId()+":"+value.longValue();
|
||||
return tag.getId()+":invalid";
|
||||
}
|
||||
private static String pending(CompoundTag tag){return tag==null?"none":tag.getLongOr("mark",0)+" "+atom(tag.get("x"))+" "+atom(tag.get("y"))+" "+atom(tag.get("z"));}
|
||||
public static void main(String[] args)throws Exception {
|
||||
net.minecraft.SharedConstants.tryDetectVersion();net.minecraft.server.Bootstrap.bootStrap();
|
||||
Holder<Biome> biome=Holder.Reference.createStandAlone(new HolderOwner<Biome>(){},Biomes.PLAINS);var ids=new IdMapper<Holder<Biome>>();ids.add(biome);
|
||||
factory=new PalettedContainerFactory(Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY),Blocks.AIR.defaultBlockState(),null,Strategy.createForBiomes(ids),biome,null);
|
||||
var registries=RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY);
|
||||
var loot=ResourceKey.create(Registries.LOOT_TABLE,Identifier.parse("horizon:storage_probe"));
|
||||
ProtoChunk chunk=null;
|
||||
try(var input=Files.newBufferedReader(Path.of(args[0]));var out=new PrintWriter(Files.newBufferedWriter(Path.of(args[1])))){
|
||||
String line;int index=0;
|
||||
while((line=input.readLine())!=null){var in=new Input(line);String op=in.words[0];out.println("command "+index+++" "+op);
|
||||
switch(op){
|
||||
case "reset"->chunk=new ProtoChunk(new ChunkPos(in.integer(),in.integer()),UpgradeData.EMPTY,LevelHeightAccessor.create(-64,384),factory,null);
|
||||
case "pending","resolve"->{var tag=new CompoundTag();tag.putLong("mark",in.number());for(String axis:new String[]{"x","y","z"}){var value=in.tag();if(value!=null)tag.put(axis,value);}out.println("position "+xyz(BlockEntity.getPosFromTag(chunk.getPos(),tag)));if(op.equals("pending"))chunk.setBlockEntityNbt(tag);}
|
||||
case "active"->{var p=in.pos();var entity=new ChestBlockEntity(p,Blocks.CHEST.defaultBlockState());entity.setLootTable(loot,in.number());chunk.setBlockEntity(entity);}
|
||||
case "remove"->chunk.removeBlockEntity(in.pos());
|
||||
case "mutate"->{var p=in.pos();int x=in.integer();var tag=chunk.getBlockEntityNbt(p);if(tag!=null)tag.putInt("x",x);}
|
||||
case "get","save"->{var p=in.pos();if(op.equals("get")){var active=chunk.getBlockEntity(p);out.println("active "+(active==null?"none":((ChestBlockEntity)active).getLootTableSeed()));out.println("pending "+pending(chunk.getBlockEntityNbt(p)));}else{var tag=chunk.getBlockEntityNbtForSaving(p,registries);if(chunk.getBlockEntity(p)!=null)out.println("saved active "+tag.getStringOr("id","")+" "+tag.getIntOr("x",0)+" "+tag.getIntOr("y",0)+" "+tag.getIntOr("z",0)+" "+tag.getLongOr("LootTableSeed",0));else out.println("saved pending "+pending(tag));}}
|
||||
case "dump"->{var positions=new TreeSet<BlockPos>(ORDER);positions.addAll(chunk.getBlockEntitiesPos());for(var p:positions){var entity=chunk.getBlockEntity(p);out.println("entry "+xyz(p)+" "+(entity==null?"none":((ChestBlockEntity)entity).getLootTableSeed())+" "+pending(chunk.getBlockEntityNbt(p)));}out.println("size "+positions.size()+" "+chunk.getBlockEntityNbts().size());}
|
||||
default->throw new AssertionError(op);
|
||||
}
|
||||
if(in.cursor!=in.words.length)throw new AssertionError("extra input");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare native proto-chunk block-entity storage and NBT coordinate coercion."""
|
||||
import argparse
|
||||
from collections import Counter
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import random
|
||||
import subprocess
|
||||
try:
|
||||
from . import lab, test_rust_parity as base
|
||||
except ImportError:
|
||||
import lab, test_rust_parity as base
|
||||
|
||||
|
||||
def corpus():
|
||||
atoms=['none 0','string invalid']
|
||||
for kind,values in [('byte',[-128,-1,0,1,127]),('short',[-32768,-1,0,1,32767]),('int',[-2147483648,-17,-16,-1,0,1,15,16,2147483647]),('long',[-9223372036854775808,-4294967297,-2147483649,-1,0,2147483648,4294967297,9223372036854775807])]:
|
||||
atoms += [f'{kind} {v}' for v in values]
|
||||
atoms += ['float '+bits for bits in ['00000000','80000000','00000001','80000001','3fc00000','bfc00000','4f000000','cf000000','cf000001','7f800000','ff800000','7fc00000','7f800001']]
|
||||
atoms += ['double '+bits for bits in ['0000000000000000','8000000000000000','0000000000000001','8000000000000001','3ff8000000000000','bff8000000000000','41e0000000000000','c1e0000000000000','c1e0000000000001','7ff0000000000000','fff0000000000000','7ff8000000000000','7ff0000000000001']]
|
||||
for cx,cz in [(0,0),(-1,-1),(1,-2),(-134217728,134217727),(-2147483648,2147483647)]:
|
||||
yield f'reset {cx} {cz}'
|
||||
for axis in range(3):
|
||||
for i,atom in enumerate(atoms):
|
||||
xyz=['int -1','int 64','int 31'];xyz[axis]=atom
|
||||
yield 'resolve 0 '+' '.join(xyz)
|
||||
yield f'pending {i} '+' '.join(xyz)
|
||||
yield 'dump'
|
||||
for marker in [-9223372036854775808,-1,0,1,9223372036854775807]:
|
||||
yield 'reset -1 -1'
|
||||
yield f'pending {marker} int 15 int 64 int 15'
|
||||
yield 'get -1 64 -1';yield 'save -1 64 -1'
|
||||
yield 'mutate -1 64 -1 2147483647'
|
||||
yield 'get -1 64 -1';yield 'save -1 64 -1'
|
||||
yield f'active -1 64 -1 {marker}'
|
||||
yield 'pending 99 int 15 int 64 int 15'
|
||||
yield 'get -1 64 -1';yield 'save -1 64 -1';yield 'dump'
|
||||
yield 'remove -1 64 -1';yield 'get -1 64 -1';yield 'save -1 64 -1'
|
||||
rng=random.Random(0x424c4f434b454e54)
|
||||
for trial in range(12):
|
||||
cx,cz=trial%3-1,trial%5-2
|
||||
yield f'reset {cx} {cz}'
|
||||
positions=[(cx*16+x,y,cz*16+z) for x in [0,1,15] for y in [-2147483648,-64,0,64,2147483647] for z in [0,1,15]]
|
||||
positions += [(2147483647,-2147483648,-2147483648),(0,4096,0),(67108864,4096,-67108864)]
|
||||
for _ in range(500):
|
||||
p=rng.choice(positions);xyz=' '.join(map(str,p));op=rng.randrange(7)
|
||||
if op<2:yield f'pending {rng.randrange(-2**63,2**63)} int {p[0]} int {p[1]} int {p[2]}'
|
||||
elif op==2:yield f'active {xyz} {rng.randrange(-2**63,2**63)}'
|
||||
elif op==3:yield f'remove {xyz}'
|
||||
elif op==4:yield f'mutate {xyz} {rng.randrange(-2**31,2**31)}'
|
||||
elif op==5:yield f'get {xyz}'
|
||||
else:yield f'save {xyz}'
|
||||
if rng.randrange(8)==0:yield 'dump'
|
||||
yield 'dump'
|
||||
yield 'reset 0 0'
|
||||
for _ in range(20000):
|
||||
xyz=[rng.choice([f'float {rng.getrandbits(32):08x}',f'double {rng.getrandbits(64):016x}',rng.choice(atoms)]) for _ in range(3)]
|
||||
yield 'resolve 0 '+' '.join(xyz)
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--prepare',action='store_true');args=parser.parse_args()
|
||||
build=lab.LOCAL/'rust-block-entity-storage-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/VanillaBlockEntityStorageOracle.java'
|
||||
sources=[oracle,Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/lab.py',lab.ROOT/'Cargo.toml',lab.ROOT/'Cargo.lock',lab.ROOT/'crates/horizon-core/Cargo.toml',lab.ROOT/'crates/horizon-world/Cargo.toml',*sorted((lab.ROOT/'crates/horizon-core/src').rglob('*.rs')),*sorted((lab.ROOT/'crates/horizon-world/src').rglob('*.rs')),lab.ROOT/'crates/horizon-world/examples/block_entity_storage_fixture.rs']
|
||||
tested_sources=base.source_hashes(sources)
|
||||
commands=list(corpus());inputs=build/'inputs.txt';inputs.write_text('\n'.join(commands)+'\n')
|
||||
tested_fixtures=base.source_hashes([inputs]);classes=build/'classes';classes.mkdir(exist_ok=True)
|
||||
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)
|
||||
dependencies=os.pathsep.join(map(str,libraries))
|
||||
run(['javac','--release',lock['minecraft']['java_major'],'-proc:none','-cp',str(jars['server'])+os.pathsep+dependencies,'-d',classes,oracle],'compile-java')
|
||||
run(['cargo','build','--release','-p','horizon-world','--example','block_entity_storage_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_entity_storage_fixture.exe' if os.name=='nt' else 'block_entity_storage_fixture')
|
||||
run([binary,inputs,build/'rust.txt'],'rust')
|
||||
references=[]
|
||||
for side,jar in jars.items():
|
||||
run(['java','-Xmx512M','-cp',os.pathsep.join([str(classes),str(jar),dependencies]),'dev.horizonlab.parity.VanillaBlockEntityStorageOracle',inputs,build/(side+'.txt')],side)
|
||||
matched=base.compare(build/(side+'.txt'),build/'rust.txt')
|
||||
references.append({'side':side,'jar_sha256':lab.digest(jar,'sha256'),'output_sha256':lab.digest(build/(side+'.txt'),'sha256'),'matched_output_lines':matched})
|
||||
print(f'{side}: {matched:,} block-entity storage lines match Rust',flush=True)
|
||||
base.ensure_sources_unchanged(sources,tested_sources);base.ensure_sources_unchanged([inputs],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(),
|
||||
'commands':dict(Counter(c.split()[0] for c in commands)),'sources_sha256':tested_sources,'fixtures_sha256':tested_fixtures,'rust_output_sha256':lab.digest(build/'rust.txt','sha256'),'references':references,
|
||||
'scope':'Native BlockEntity.getPosFromTag and actual ProtoChunk pending/active insertion, lookup, removal, position union and pending-save fallback. NumericTag coordinate reads cover integer narrowing, float/double floor overflow, infinities/NaNs, missing/nonnumeric fields, wrong-chunk relocation and wrapped chunk coordinates. Active entries replace pending data; later pending records cannot overwrite active entries. In-place pending coordinate mutation retains the original map key. Native chest entities carry loot seeds for active identity and basic saving-metadata comparisons; Rust supplies caller-owned payload serialization. Iteration order is normalized, and pending reads are borrowed/owned values rather than unrestricted Java aliases. Entity creation from NBT, complete entity serializers, active behavior and integration with chunk lifecycle remain pending.'}
|
||||
lab.write_json(report_path,report);print(f'Scoped block-entity storage parity passed: {report_path}')
|
||||
|
||||
if __name__=='__main__':main()
|
||||
Reference in New Issue
Block a user