Render registry-bound variants and multipart block models
This commit is contained in:
@@ -0,0 +1,661 @@
|
||||
{
|
||||
"adapter": {
|
||||
"backend": "Vulkan",
|
||||
"device": 0,
|
||||
"device_type": "Cpu",
|
||||
"name": "llvmpipe (LLVM 21.1.8, 256 bits)",
|
||||
"vendor": 65541
|
||||
},
|
||||
"animated_sprites": 1,
|
||||
"animation_uploads": 1,
|
||||
"arch": "x86_64",
|
||||
"camera_pitch": -0.3839724354387525,
|
||||
"camera_position": [
|
||||
25.0,
|
||||
18.0,
|
||||
25.0
|
||||
],
|
||||
"camera_yaw": -2.356194490192345,
|
||||
"elapsed_seconds": 0.636712699,
|
||||
"error": null,
|
||||
"merged_quads": 166,
|
||||
"model_gallery": [
|
||||
{
|
||||
"block": "minecraft:oak_stairs",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/oak_stairs_inner",
|
||||
"rotations": [
|
||||
2,
|
||||
3,
|
||||
0
|
||||
],
|
||||
"uv_lock": true
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
-7,
|
||||
4,
|
||||
14
|
||||
],
|
||||
"properties": {
|
||||
"facing": "north",
|
||||
"half": "top",
|
||||
"shape": "inner_left",
|
||||
"waterlogged": "false"
|
||||
},
|
||||
"state_id": 5466
|
||||
},
|
||||
{
|
||||
"block": "minecraft:stone_slab",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/stone_slab_top",
|
||||
"rotations": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
-3,
|
||||
4,
|
||||
14
|
||||
],
|
||||
"properties": {
|
||||
"type": "top",
|
||||
"waterlogged": "false"
|
||||
},
|
||||
"state_id": 15250
|
||||
},
|
||||
{
|
||||
"block": "minecraft:anvil",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/anvil",
|
||||
"rotations": [
|
||||
0,
|
||||
3,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
1,
|
||||
4,
|
||||
14
|
||||
],
|
||||
"properties": {
|
||||
"facing": "east"
|
||||
},
|
||||
"state_id": 12965
|
||||
},
|
||||
{
|
||||
"block": "minecraft:torch",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/torch",
|
||||
"rotations": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
5,
|
||||
4,
|
||||
14
|
||||
],
|
||||
"properties": {},
|
||||
"state_id": 4926
|
||||
},
|
||||
{
|
||||
"block": "minecraft:lantern",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/lantern",
|
||||
"rotations": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
-7,
|
||||
4,
|
||||
18
|
||||
],
|
||||
"properties": {
|
||||
"hanging": "false",
|
||||
"waterlogged": "false"
|
||||
},
|
||||
"state_id": 24197
|
||||
},
|
||||
{
|
||||
"block": "minecraft:oak_fence",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/oak_fence_post",
|
||||
"rotations": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
},
|
||||
{
|
||||
"model": "minecraft:block/oak_fence_side",
|
||||
"rotations": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"uv_lock": true
|
||||
},
|
||||
{
|
||||
"model": "minecraft:block/oak_fence_side",
|
||||
"rotations": [
|
||||
0,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"uv_lock": true
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
-3,
|
||||
4,
|
||||
18
|
||||
],
|
||||
"properties": {
|
||||
"east": "true",
|
||||
"north": "true",
|
||||
"south": "false",
|
||||
"waterlogged": "false",
|
||||
"west": "false"
|
||||
},
|
||||
"state_id": 8642
|
||||
},
|
||||
{
|
||||
"block": "minecraft:oak_fence_gate",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/oak_fence_gate_open",
|
||||
"rotations": [
|
||||
0,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"uv_lock": true
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
1,
|
||||
4,
|
||||
18
|
||||
],
|
||||
"properties": {
|
||||
"facing": "west",
|
||||
"in_wall": "false",
|
||||
"open": "true",
|
||||
"powered": "false"
|
||||
},
|
||||
"state_id": 10401
|
||||
},
|
||||
{
|
||||
"block": "minecraft:iron_bars",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/iron_bars_post_ends",
|
||||
"rotations": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
},
|
||||
{
|
||||
"model": "minecraft:block/iron_bars_side",
|
||||
"rotations": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
},
|
||||
{
|
||||
"model": "minecraft:block/iron_bars_side_alt",
|
||||
"rotations": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
5,
|
||||
4,
|
||||
18
|
||||
],
|
||||
"properties": {
|
||||
"east": "false",
|
||||
"north": "true",
|
||||
"south": "true",
|
||||
"waterlogged": "false",
|
||||
"west": "false"
|
||||
},
|
||||
"state_id": 9711
|
||||
},
|
||||
{
|
||||
"block": "minecraft:cobweb",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/cobweb",
|
||||
"rotations": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
-7,
|
||||
4,
|
||||
22
|
||||
],
|
||||
"properties": {},
|
||||
"state_id": 2362
|
||||
},
|
||||
{
|
||||
"block": "minecraft:grindstone",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/grindstone",
|
||||
"rotations": [
|
||||
1,
|
||||
3,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
-3,
|
||||
4,
|
||||
22
|
||||
],
|
||||
"properties": {
|
||||
"face": "wall",
|
||||
"facing": "west"
|
||||
},
|
||||
"state_id": 24135
|
||||
},
|
||||
{
|
||||
"block": "minecraft:ladder",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/ladder",
|
||||
"rotations": [
|
||||
0,
|
||||
3,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
1,
|
||||
4,
|
||||
22
|
||||
],
|
||||
"properties": {
|
||||
"facing": "west",
|
||||
"waterlogged": "false"
|
||||
},
|
||||
"state_id": 7312
|
||||
},
|
||||
{
|
||||
"block": "minecraft:amethyst_cluster",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/amethyst_cluster",
|
||||
"rotations": [
|
||||
1,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
5,
|
||||
4,
|
||||
22
|
||||
],
|
||||
"properties": {
|
||||
"facing": "east",
|
||||
"waterlogged": "false"
|
||||
},
|
||||
"state_id": 26764
|
||||
},
|
||||
{
|
||||
"block": "minecraft:stone",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/stone",
|
||||
"rotations": [
|
||||
0,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
-7,
|
||||
4,
|
||||
26
|
||||
],
|
||||
"properties": {},
|
||||
"state_id": 1
|
||||
},
|
||||
{
|
||||
"block": "minecraft:stone",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/stone_mirrored",
|
||||
"rotations": [
|
||||
0,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
-3,
|
||||
4,
|
||||
26
|
||||
],
|
||||
"properties": {},
|
||||
"state_id": 1
|
||||
},
|
||||
{
|
||||
"block": "minecraft:stone",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/stone",
|
||||
"rotations": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
1,
|
||||
4,
|
||||
26
|
||||
],
|
||||
"properties": {},
|
||||
"state_id": 1
|
||||
},
|
||||
{
|
||||
"block": "minecraft:stone",
|
||||
"models": [
|
||||
{
|
||||
"model": "minecraft:block/stone",
|
||||
"rotations": [
|
||||
0,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"uv_lock": false
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
5,
|
||||
4,
|
||||
26
|
||||
],
|
||||
"properties": {},
|
||||
"state_id": 1
|
||||
}
|
||||
],
|
||||
"model_quads": 209,
|
||||
"os": "linux",
|
||||
"presented_frames": 30,
|
||||
"project": "Horizon",
|
||||
"resident_chunks": 9,
|
||||
"schema": 1,
|
||||
"scope": "Native cube and cuboid-model renderer with binary cutouts, stepped sprite animation and free camera; no gameplay simulation",
|
||||
"sprite_layers": 18,
|
||||
"window_size": [
|
||||
1280,
|
||||
720
|
||||
],
|
||||
"native_selection": {
|
||||
"matched_placements": 16,
|
||||
"selected_parts": 20,
|
||||
"client_jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d",
|
||||
"output_sha256": "ea3bc37e515550e33301f8c12b50bb44a5496e5e1382c071eae1e43c4ffb3620",
|
||||
"scope": "Actual native registry states, blockstate dispatch, root baking with tagged empty geometry, and legacy collection using state.getSeed(position). Model names and transform matrices match scene selections; no shader/pixel parity claim."
|
||||
},
|
||||
"resource_files": {
|
||||
"files": 5580,
|
||||
"sha256": "873ea291669e5acbc7cb3e156efa0990c3a9b899a784fc63c16be75b187f5391"
|
||||
},
|
||||
"executable_sha256": "a6f08da44b188cedcb9da2557d9a40c7ecc90a74c5efad8541c57330a2e0519b",
|
||||
"resource_manifest_sha256": "94ec11d21355ece462b8fdc72da26d84d8fa686ce3e3d932f43b9f7aad8371ff",
|
||||
"sources": {
|
||||
"Cargo.toml": "3f93319c1eb70dbd7ba25cd74ef343fbf35c52c7c4752f955605c58699cbff9d",
|
||||
"Cargo.lock": "cb1e87d449114a63edd904c0559d631e8fa9feeb27efd6539aacdcfeae178876",
|
||||
"tools/test_native_client.py": "f796ce17f2d93a7a876db531f178fde92a893338b1e056d649945ca63046005a",
|
||||
"crates/horizon-client/Cargo.toml": "d56524457d761c2613c0699b20e21ad6977d93e009d59ef150bbd9fc20d7a025",
|
||||
"crates/horizon-assets/Cargo.toml": "2f2cffacec0b4e22dd7cb032d594289bc51c94ae6cc4e985fee3e310f441d751",
|
||||
"crates/horizon-client/src/assets.rs": "c1f6ed253c2c043050403b8d57a3f112275887d332d47344e1c157b4a0355de0",
|
||||
"crates/horizon-client/src/camera.rs": "f9821c8a51a004dd4ad9c0952e746eb07926f6040cf8660bc26da5a0728ae572",
|
||||
"crates/horizon-client/src/main.rs": "88cf5386ddbf9494d2867ee4c3b3016b7d55ceb6bf7f543362acc2f9e2fe1abd",
|
||||
"crates/horizon-client/src/mesh.rs": "e2fa00435ec8239ba8a3c6539022bb50a10ff9c3850c6faab72f86aabea896ef",
|
||||
"crates/horizon-client/src/models/catalog.rs": "35b854021f0bc941f44f76e99e7fcccb3a135157e7272174e84c3907aadbddf7",
|
||||
"crates/horizon-client/src/models.rs": "63af351c9b48e7a796b2a9591ddb093316f301523b42c6855e5904ddf3ca20d9",
|
||||
"crates/horizon-client/src/render.rs": "0f8929d9061b20adbc5ddd0adcbe9f90ce76227b7bb9bafe3f4474961ee2984c",
|
||||
"crates/horizon-client/src/sprites.rs": "4dd10998581f901bd1c72ed49c27671f362f18285e86e795ddadd11cbc255f69",
|
||||
"crates/horizon-client/src/world.rs": "901aa59c23ea084e926eec014589f4d511c41691a8dc1520534e039c28cfc76c",
|
||||
"crates/horizon-client/src/world.wgsl": "82873c131f46a31aef35106f89a53589f179048362d0f8877eca6d20aa6274a4",
|
||||
"crates/horizon-assets/src/animation.rs": "ebbf7952146a0e66b41396174492f5702627170b03a57e6cd5a45c97a08c975e",
|
||||
"crates/horizon-assets/src/bake/transform.rs": "a95e658fef85b21bcb2f9f7512f98f98d57022d62c1199885b7133beae35a09d",
|
||||
"crates/horizon-assets/src/bake.rs": "f55dda6b6cee5ac6e65a8ee4837fdd2c6674437d59e59b4c45718f6324d4a4d5",
|
||||
"crates/horizon-assets/src/blockstate/bind.rs": "6278962e55c9786bba8184bdf2939af9c6e3b9002b08c3022129964b49a60009",
|
||||
"crates/horizon-assets/src/blockstate/choice.rs": "5a66c8b9833e2f19dd8b0773f3593d97ff5941d3c5b773896b98049efa1395ba",
|
||||
"crates/horizon-assets/src/blockstate.rs": "a4299dfd7adaeb4173982ffaa8e9c9eda2925739ae579a714193c99da2e7ce56",
|
||||
"crates/horizon-assets/src/json.rs": "99fe8af4fbf5d4eed408a92e829ac1a39597a0d92471a6bff73771fad9c830a8",
|
||||
"crates/horizon-assets/src/lib.rs": "e39f21c59a2c3e1c53d977b29bd5e0638e5fc41d86ff0fcc1882a6d90fd961e4",
|
||||
"crates/horizon-assets/src/model.rs": "57ddb3d65e88e7a8aa22c62bc298a2dae7f4e994326a873a31f06c0f36ed051b",
|
||||
"crates/horizon-assets/src/resolve.rs": "3927ac926cdcbd7d334901cc32d0ecc1e498b1bec98f76a46bca6d4964c13541",
|
||||
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaGallerySelectionOracle.java": "a0cf45cb96367404ea60c94a7fe25c142fba138df619bbe4396c633770342adc",
|
||||
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaCuboidModelOracle.java": "7526f20513c549483122ab0d5ad24b9e87f38dc2f6aae55addd701a91ac85e43",
|
||||
"tools/test_model_baking_parity.py": "392ffec44918fb91740ca4a1316d684480d39f14bd34d46175f7f44946d15bd7",
|
||||
"tools/test_rust_parity.py": "29d3db08005873528894b45ca10dcd6278753650473b3eb574e53e06ede40ec6",
|
||||
"tools/lab.py": "d00bf107fa01b6c579734a082c3ca4ade3018f0d6851d46844acf17574f66e64",
|
||||
"tools/prepare_native_client.py": "87d3f99309ebe54c572fb6b2d3b693da172494eef438e9652c2353b6b00db4a6",
|
||||
"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_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": "2995b209aa3dc24cfcbba405cf8b97a398efbc0de97bb04ef2f51abb4cd2b84c",
|
||||
"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"
|
||||
},
|
||||
"visual_inspection": {
|
||||
"platform": "Linux x86_64, headless Wayland, Vulkan llvmpipe LLVM 21.1.8 CPU renderer",
|
||||
"presented_frames": 3600,
|
||||
"error": null,
|
||||
"observed": "Rotated stairs and top slab, anvil orientation, multipart fence connections, open gate, bars, sideways amethyst, wall grindstone and four stone variants inspected in the controlled scene. Physical keyboard movement/rotation exercised. No native pixel comparison or hardware performance claim.",
|
||||
"screenshots": "Private under .local/horizon-client/; no game textures or images in Git."
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ pub struct Assets {
|
||||
pub pixels: Vec<u8>,
|
||||
pub texture_layers: u32,
|
||||
pub model_quads: Vec<crate::models::ModelInstance>,
|
||||
pub model_gallery: Vec<Value>,
|
||||
pub animations: Vec<crate::sprites::SpriteAnimation>,
|
||||
}
|
||||
fn invalid(message: &str) -> io::Error {
|
||||
@@ -181,7 +182,7 @@ impl Assets {
|
||||
let sprite = sprites.insert(root, &format!("minecraft:block/{name}"))?;
|
||||
layers[state.0 as usize] = sprite as i32;
|
||||
}
|
||||
let model_quads = crate::models::gallery(root, &mut sprites)?;
|
||||
let gallery = crate::models::gallery(root, ®istry, &mut sprites)?;
|
||||
Ok(Self {
|
||||
registry,
|
||||
properties,
|
||||
@@ -190,7 +191,8 @@ impl Assets {
|
||||
sprite_size: sprites.size,
|
||||
texture_layers: u32::try_from(sprites.layers.len())?,
|
||||
pixels: sprites.pixels,
|
||||
model_quads,
|
||||
model_quads: gallery.quads,
|
||||
model_gallery: gallery.states,
|
||||
animations: sprites.animations,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ impl App<'_> {
|
||||
"resident_chunks": self.world.chunks.len(),
|
||||
"merged_quads": self.mesh.len(),
|
||||
"model_quads": self.assets.model_quads.len(),
|
||||
"model_gallery": models::GALLERY,
|
||||
"model_gallery": self.assets.model_gallery,
|
||||
"sprite_layers": self.assets.texture_layers,
|
||||
"animated_sprites": self.assets.animations.len(),
|
||||
"animation_uploads": self.renderer.as_ref().map_or(0, |r| r.animation_uploads),
|
||||
|
||||
@@ -1,25 +1,38 @@
|
||||
//! A resource-model inspection gallery. Blockstate dispatch is still separate.
|
||||
//! A blockstate-driven inspection gallery, separate from gameplay simulation.
|
||||
mod catalog;
|
||||
use crate::{Result, sprites::Sprites};
|
||||
use horizon_assets::{
|
||||
bake::bake_model,
|
||||
model::{CuboidModel, Direction},
|
||||
resolve::ModelLibrary,
|
||||
};
|
||||
use std::{collections::BTreeMap, fs, path::Path};
|
||||
use catalog::{StateModels, descriptor};
|
||||
use horizon_assets::model::Direction;
|
||||
use horizon_core::position::BlockPos;
|
||||
use horizon_world::registry::BlockRegistry;
|
||||
use serde_json::{Value, json};
|
||||
use std::path::Path;
|
||||
|
||||
pub const GALLERY: [&str; 12] = [
|
||||
"oak_stairs",
|
||||
"stone_slab",
|
||||
"anvil",
|
||||
"torch",
|
||||
"lantern",
|
||||
"oak_fence_post",
|
||||
"oak_fence_gate",
|
||||
"iron_bars_post",
|
||||
"cobweb",
|
||||
"grindstone",
|
||||
"ladder",
|
||||
"amethyst_cluster",
|
||||
type Placement = (&'static str, &'static [(&'static str, &'static str)]);
|
||||
const GALLERY: [Placement; 16] = [
|
||||
(
|
||||
"oak_stairs",
|
||||
&[
|
||||
("facing", "north"),
|
||||
("half", "top"),
|
||||
("shape", "inner_left"),
|
||||
],
|
||||
),
|
||||
("stone_slab", &[("type", "top")]),
|
||||
("anvil", &[("facing", "east")]),
|
||||
("torch", &[]),
|
||||
("lantern", &[]),
|
||||
("oak_fence", &[("north", "true"), ("east", "true")]),
|
||||
("oak_fence_gate", &[("facing", "west"), ("open", "true")]),
|
||||
("iron_bars", &[("north", "true"), ("south", "true")]),
|
||||
("cobweb", &[]),
|
||||
("grindstone", &[("face", "wall"), ("facing", "west")]),
|
||||
("ladder", &[("facing", "west")]),
|
||||
("amethyst_cluster", &[("facing", "east")]),
|
||||
("stone", &[]),
|
||||
("stone", &[]),
|
||||
("stone", &[]),
|
||||
("stone", &[]),
|
||||
];
|
||||
|
||||
#[repr(C)]
|
||||
@@ -30,55 +43,47 @@ pub struct ModelInstance {
|
||||
pub layer: u32,
|
||||
pub shade: f32,
|
||||
}
|
||||
pub fn gallery(root: &Path, sprites: &mut Sprites) -> Result<Vec<ModelInstance>> {
|
||||
let mut models = BTreeMap::new();
|
||||
let mut pending: Vec<_> = GALLERY
|
||||
.iter()
|
||||
.map(|name| format!("minecraft:block/{name}"))
|
||||
.collect();
|
||||
while let Some(name) = pending.pop() {
|
||||
if models.contains_key(&name) {
|
||||
continue;
|
||||
}
|
||||
let (namespace, path) = name.split_once(':').ok_or("invalid model identifier")?;
|
||||
let text = fs::read_to_string(root.join(format!("assets/{namespace}/models/{path}.json")))?;
|
||||
let model = CuboidModel::from_json(&text)?;
|
||||
if let Some(parent) = &model.parent {
|
||||
pending.push(parent.clone());
|
||||
}
|
||||
models.insert(name, model);
|
||||
}
|
||||
let library = ModelLibrary::new(models)?;
|
||||
let mut instances = Vec::new();
|
||||
for (index, name) in GALLERY.iter().enumerate() {
|
||||
let model = library.resolve(&format!("minecraft:block/{name}"))?;
|
||||
let origin = [
|
||||
(index % 4) as f32 * 4.0 - 7.0,
|
||||
4.0,
|
||||
(index / 4) as f32 * 4.0 + 14.0,
|
||||
];
|
||||
for quad in bake_model(&model)? {
|
||||
if quad.material.force_translucent || quad.tint_index != -1 {
|
||||
return Err(
|
||||
"inspection gallery requires untinted opaque or binary-cutout materials".into(),
|
||||
);
|
||||
}
|
||||
let layer = sprites.insert(root, &quad.material.sprite)?;
|
||||
let shade = match quad.shade_direction_override.unwrap_or(quad.facing) {
|
||||
Direction::Down => 0.5,
|
||||
Direction::Up => 1.0,
|
||||
Direction::North | Direction::South => 0.8,
|
||||
Direction::West | Direction::East => 0.6,
|
||||
};
|
||||
instances.push(ModelInstance {
|
||||
positions: quad
|
||||
.positions
|
||||
.map(|v| std::array::from_fn(|i| v[i] + origin[i])),
|
||||
uv: quad.uv,
|
||||
layer,
|
||||
shade,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(instances)
|
||||
pub struct Gallery {
|
||||
pub quads: Vec<ModelInstance>,
|
||||
pub states: Vec<Value>,
|
||||
}
|
||||
pub fn gallery(root: &Path, registry: &BlockRegistry, sprites: &mut Sprites) -> Result<Gallery> {
|
||||
let models = StateModels::load(root, registry, &GALLERY.map(|(name, _)| name), sprites)?;
|
||||
let mut instances = Vec::new();
|
||||
let mut states = Vec::new();
|
||||
for (index, (name, properties)) in GALLERY.iter().enumerate() {
|
||||
let state = registry.resolve(&descriptor(name, properties))?;
|
||||
let actual = registry
|
||||
.state(state)
|
||||
.ok_or("missing gallery registry state")?;
|
||||
let position = BlockPos::new((index % 4) as i32 * 4 - 7, 4, (index / 4) as i32 * 4 + 14);
|
||||
let origin = [position.x as f32, position.y as f32, position.z as f32];
|
||||
let selected = models.collect(state, position)?;
|
||||
states.push(json!({"block":actual.name,"properties":actual.properties,"state_id":state.0,
|
||||
"position":[position.x,position.y,position.z],
|
||||
"models":selected.iter().map(|(v,_)| json!({"model":v.model,"rotations":v.rotations,"uv_lock":v.uv_lock})).collect::<Vec<_>>()}));
|
||||
for (_, faces) in selected {
|
||||
for face in faces {
|
||||
let quad = &face.quad;
|
||||
let shade = match quad.shade_direction_override.unwrap_or(quad.facing) {
|
||||
Direction::Down => 0.5,
|
||||
Direction::Up => 1.0,
|
||||
Direction::North | Direction::South => 0.8,
|
||||
Direction::West | Direction::East => 0.6,
|
||||
};
|
||||
instances.push(ModelInstance {
|
||||
positions: quad
|
||||
.positions
|
||||
.map(|v| std::array::from_fn(|i| v[i] + origin[i])),
|
||||
uv: quad.uv,
|
||||
layer: face.layer,
|
||||
shade,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Gallery {
|
||||
quads: instances,
|
||||
states,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
//! Bind prepared registry states to model choices; bake each transformed model
|
||||
//! once and share its geometry across positions. Resource failure recovery and
|
||||
//! resource-pack reloads remain separate work.
|
||||
use crate::{Result, sprites::Sprites};
|
||||
use horizon_assets::{
|
||||
bake::{BlockModelTransform, ModelQuad, bake_model_transformed},
|
||||
blockstate::{
|
||||
BlockStateDefinition, Variant, VariantSet,
|
||||
bind::{Properties, PropertySchema, PropertyValues, SelectedModels},
|
||||
choice::CompiledChoices,
|
||||
},
|
||||
model::CuboidModel,
|
||||
resolve::ModelLibrary,
|
||||
};
|
||||
use horizon_core::{
|
||||
position::BlockPos,
|
||||
random::{LegacyRandom, position_seed},
|
||||
};
|
||||
use horizon_world::{
|
||||
chunk::BlockState,
|
||||
registry::{BlockRegistry, BlockStateId},
|
||||
};
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fs,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
pub struct BakedFace {
|
||||
pub quad: ModelQuad,
|
||||
pub layer: u32,
|
||||
}
|
||||
type VariantKey = (String, [u8; 3], bool);
|
||||
pub struct StateModels {
|
||||
choices: Vec<CompiledChoices>,
|
||||
states: BTreeMap<BlockStateId, (usize, SelectedModels)>,
|
||||
baked: BTreeMap<VariantKey, Vec<BakedFace>>,
|
||||
}
|
||||
fn key(variant: &Variant) -> VariantKey {
|
||||
(variant.model.clone(), variant.rotations, variant.uv_lock)
|
||||
}
|
||||
impl StateModels {
|
||||
pub fn load(
|
||||
root: &Path,
|
||||
registry: &BlockRegistry,
|
||||
blocks: &[&str],
|
||||
sprites: &mut Sprites,
|
||||
) -> Result<Self> {
|
||||
let facts: BTreeMap<String, Value> =
|
||||
serde_json::from_reader(fs::File::open(root.join("model-states.json"))?)?;
|
||||
let mut choices = Vec::new();
|
||||
let mut states = BTreeMap::new();
|
||||
let mut variants = BTreeSet::new();
|
||||
for block in blocks.iter().copied().collect::<BTreeSet<_>>() {
|
||||
let name = format!("minecraft:{block}");
|
||||
let facts = facts.get(&name).ok_or("missing block model state schema")?;
|
||||
let schema: PropertySchema = facts["schema"]
|
||||
.as_object()
|
||||
.ok_or("invalid model schema")?
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
let values = if let Some(range) = value.get("integer") {
|
||||
PropertyValues::Integer {
|
||||
minimum: i32::try_from(
|
||||
range[0]
|
||||
.as_i64()
|
||||
.ok_or("invalid integer property minimum")?,
|
||||
)?,
|
||||
maximum: i32::try_from(
|
||||
range[1]
|
||||
.as_i64()
|
||||
.ok_or("invalid integer property maximum")?,
|
||||
)?,
|
||||
}
|
||||
} else {
|
||||
PropertyValues::Named(
|
||||
value["named"]
|
||||
.as_array()
|
||||
.ok_or("missing named property values")?
|
||||
.iter()
|
||||
.map(|v| {
|
||||
v.as_str()
|
||||
.map(str::to_owned)
|
||||
.ok_or("invalid named property value")
|
||||
})
|
||||
.collect::<std::result::Result<_, _>>()?,
|
||||
)
|
||||
};
|
||||
Ok((key.clone(), values))
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
let possible: Vec<Properties> = serde_json::from_value(facts["states"].clone())?;
|
||||
let resource = fs::read_to_string(
|
||||
root.join(format!("assets/minecraft/blockstates/{block}.json")),
|
||||
)?;
|
||||
let definition = BlockStateDefinition::from_json(&resource)?;
|
||||
let bound = definition.bind(&schema, &possible)?;
|
||||
let native_order: Vec<_> = (0..registry.len())
|
||||
.filter_map(|i| {
|
||||
let id = BlockStateId(i as u32);
|
||||
let state = registry.state(id)?;
|
||||
(state.name == name).then_some((id, &state.properties))
|
||||
})
|
||||
.collect();
|
||||
if native_order.len() != possible.len()
|
||||
|| native_order
|
||||
.iter()
|
||||
.zip(&possible)
|
||||
.any(|((_, a), b)| *a != b)
|
||||
{
|
||||
return Err(
|
||||
"model state definitions disagree with the imported block registry".into(),
|
||||
);
|
||||
}
|
||||
for ((id, _), selected) in native_order.into_iter().zip(bound) {
|
||||
if let Some(selected) = selected {
|
||||
states.insert(id, (choices.len(), selected));
|
||||
}
|
||||
}
|
||||
for choice in definition
|
||||
.variants
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|(_, choice)| choice)
|
||||
.chain(
|
||||
definition
|
||||
.multipart
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|selector| &selector.variant),
|
||||
)
|
||||
{
|
||||
match choice {
|
||||
VariantSet::Single(v) => {
|
||||
variants.insert(key(v));
|
||||
}
|
||||
VariantSet::Weighted(v) => variants.extend(v.iter().map(|(v, _)| key(v))),
|
||||
}
|
||||
}
|
||||
choices.push(CompiledChoices::new(&definition)?);
|
||||
}
|
||||
let mut models = BTreeMap::new();
|
||||
let mut pending: Vec<_> = variants.iter().map(|(name, _, _)| name.clone()).collect();
|
||||
while let Some(name) = pending.pop() {
|
||||
if models.contains_key(&name) {
|
||||
continue;
|
||||
}
|
||||
let (namespace, path) = name.split_once(':').ok_or("invalid model identifier")?;
|
||||
let text =
|
||||
fs::read_to_string(root.join(format!("assets/{namespace}/models/{path}.json")))?;
|
||||
let model = CuboidModel::from_json(&text)?;
|
||||
if let Some(parent) = &model.parent {
|
||||
pending.push(parent.clone());
|
||||
}
|
||||
models.insert(name, model);
|
||||
}
|
||||
let library = ModelLibrary::new(models)?;
|
||||
let mut baked = BTreeMap::new();
|
||||
for (name, turns, lock) in variants {
|
||||
let model = library.resolve(&name)?;
|
||||
let faces = bake_model_transformed(&model, BlockModelTransform::new(turns, lock))?.into_iter().map(|quad| {
|
||||
if quad.material.force_translucent || quad.tint_index != -1 {
|
||||
return Err("inspection gallery requires untinted opaque or binary-cutout materials".into());
|
||||
}
|
||||
let layer = sprites.insert(root, &quad.material.sprite)?;
|
||||
Ok(BakedFace { quad, layer })
|
||||
}).collect::<Result<_>>()?;
|
||||
baked.insert((name, turns, lock), faces);
|
||||
}
|
||||
Ok(Self {
|
||||
choices,
|
||||
states,
|
||||
baked,
|
||||
})
|
||||
}
|
||||
pub fn collect(
|
||||
&self,
|
||||
state: BlockStateId,
|
||||
position: BlockPos,
|
||||
) -> Result<Vec<(&Variant, &[BakedFace])>> {
|
||||
let (choice, selected) = self
|
||||
.states
|
||||
.get(&state)
|
||||
.ok_or("unbound gallery block state")?;
|
||||
// These inspection blocks use Block's default position seed. Blocks with
|
||||
// specialized model seeds (e.g. two-block structures) need their callbacks.
|
||||
let mut random = LegacyRandom::new(position_seed(position));
|
||||
let mut variants = Vec::new();
|
||||
self.choices[*choice].collect(selected, &mut random, &mut variants)?;
|
||||
variants
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
Ok((
|
||||
v,
|
||||
self.baked
|
||||
.get(&key(v))
|
||||
.ok_or("unbaked block model")?
|
||||
.as_slice(),
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn descriptor(name: &str, overrides: &[(&str, &str)]) -> BlockState {
|
||||
BlockState {
|
||||
name: format!("minecraft:{name}"),
|
||||
properties: overrides
|
||||
.iter()
|
||||
.map(|(k, v)| ((*k).to_owned(), (*v).to_owned()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
+28
-12
@@ -1,7 +1,7 @@
|
||||
# Horizon native development client
|
||||
|
||||
`horizon-client` opens a native Rust window and renders a controlled block scene
|
||||
stored in Horizon's resident chunks, alongside twelve real resource models. It
|
||||
stored in Horizon's resident chunks, alongside sixteen blockstate-driven model placements. It
|
||||
loads the block registry, models and sprites from pinned Java Edition 26.3
|
||||
resources. A padded chunk mesh
|
||||
culls hidden faces across chunk boundaries and merges adjacent matching faces;
|
||||
@@ -12,14 +12,17 @@ is a free inspection tool.
|
||||
This checkpoint has no player simulation, vanilla world generation, block
|
||||
interaction, inventory, sound, networking or world save/load. It does not meet
|
||||
the [playable-build acceptance criteria](rust-parity.md#completion-criteria-and-playable-builds).
|
||||
Blockstate dispatch, weighted variants, model transforms/UV locking, fractional
|
||||
The model gallery now uses blockstate dispatch, weighted variants, multipart
|
||||
conditions and transformed baking. Chunk-wide model rendering, fractional
|
||||
transparency, tinting, lighting, resource packs, chunk streaming and full visual
|
||||
parity remain unfinished.
|
||||
|
||||
The resource gallery sits beside the pyramid and includes oak stairs, a slab,
|
||||
an anvil, a torch, a lantern, fence parts, iron bars, a cobweb, a grindstone, a
|
||||
ladder and an amethyst cluster. These are explicit model selections for geometry
|
||||
inspection, not simulated block placements. Binary-alpha cutouts are rendered;
|
||||
an anvil, a torch, a lantern, connected fences and iron bars, a cobweb, a wall
|
||||
grindstone, a ladder, a sideways amethyst cluster and four weighted stone
|
||||
placements. Registry state properties select the models: the stairs are an
|
||||
upside-down inner corner, the slab is a top slab and the fence gate is open.
|
||||
These remain static inspection placements without simulation callbacks. Binary-alpha cutouts are rendered;
|
||||
the lantern's three frames advance every eight inspection ticks at 20 ticks/s.
|
||||
The animation metadata codec and frame selection now use the shared
|
||||
`horizon-assets` implementation checked against the pinned client. GPU
|
||||
@@ -31,7 +34,8 @@ Sprites currently require matching square frame dimensions.
|
||||
Use current stable Rust (the locked client dependencies require Rust 1.90 or
|
||||
newer), Python 3.10+ and JDK 25. A native desktop display and compatible graphics
|
||||
driver are required. Resource preparation verifies the pinned game artifacts,
|
||||
exports the registry using the reference server, and extracts selected client
|
||||
exports the registry using the reference server and property schemas using the
|
||||
reference client, and extracts selected client
|
||||
resources into ignored `.local/horizon-client/26.3/`.
|
||||
|
||||
```sh
|
||||
@@ -91,7 +95,7 @@ The [archived decoder result](../benchmarks/rust-resource-number-scanner-2026-10
|
||||
records source, fixture and reference hashes. The oracle uses actual client
|
||||
classes; those rendering APIs are absent from the server. The decoder, parent
|
||||
resolver and baker now feed the development gallery. Blockstate selection, transformed
|
||||
models and UV locking have the separate geometry checks described below. Rendered
|
||||
models and UV locking have the separate checks described below. Rendered
|
||||
appearance still needs its own checks.
|
||||
|
||||
`ModelLibrary` resolves parent geometry, ambient occlusion, GUI lighting and
|
||||
@@ -139,8 +143,7 @@ all 128 rotation/lock combinations on 81 synthetic models, plus further arbitrar
|
||||
element rotations. Positions, UVs and every orientation/inverse-face matrix
|
||||
compare by exact float bits: 19,704 roots and 148,439 quads match, including
|
||||
7,884 bundled model/transform tuples. Quaternion basis rounding and numeric identity
|
||||
rechecking are retained. This remains geometry validation; renderer integration,
|
||||
atlas packing and pixel parity are separate work.
|
||||
rechecking are retained. This remains geometry validation; atlas packing and pixel parity are separate work.
|
||||
|
||||
```sh
|
||||
python3 tools/test_blockstate_transform_parity.py
|
||||
@@ -167,7 +170,8 @@ selector and predicate cases: 3,802 definitions and 687,466 state results match,
|
||||
with 220 expected binding errors. It checks the native replacement-before-abort
|
||||
behavior for overlapping variants, eager multipart validation even when variants
|
||||
cover every state, integer-property aliases, nested conditions and empty part
|
||||
selections. The actual renderer has not yet adopted these selections.
|
||||
selections. The development gallery now uses this binder; general chunk model
|
||||
rendering remains unfinished.
|
||||
|
||||
The Java integer-property parser is shared by `horizon-core`, client binding and
|
||||
world-state decoding. Its [codec regression](../benchmarks/rust-blockstate-model-binding-2026-10-06/shared-integer-codec.json)
|
||||
@@ -186,7 +190,9 @@ uses actual `WeightedVariants.collectParts` and `MultiPartModel.collectParts`
|
||||
with inert observable leaves. All seven distinct bundled weight patterns,
|
||||
synthetic large weights and random multipart graphs produce matching selections
|
||||
and subsequent random values: 6,960 fixtures, 111,360 collections, both legacy
|
||||
and xoroshiro streams. This does not yet bake or render the selected models.
|
||||
and xoroshiro streams. The development gallery now bakes and renders selected
|
||||
models using the legacy stream and the default block position seed. Specialized
|
||||
block seed callbacks remain separate work.
|
||||
|
||||
```sh
|
||||
python3 tools/test_model_choice_parity.py
|
||||
@@ -225,7 +231,7 @@ python3 tools/test_native_client.py --vscreen
|
||||
The mesh tests compare expanded rectangles against independently enumerated
|
||||
exposed voxel faces, including solid neighbors, cavities, material boundaries,
|
||||
negative chunk origins and random surroundings. The startup runner launches a
|
||||
nine-chunk scene, requires 30 presented frames and a clean exit, verifies 145
|
||||
nine-chunk scene, requires 30 presented frames and a clean exit, verifies 209
|
||||
baked gallery faces across 18 sprite layers, and writes GPU,
|
||||
platform, binary and source information to ignored
|
||||
`.local/horizon-client/startup-smoke.json`. It has an external timeout as well as
|
||||
@@ -246,3 +252,13 @@ The model gallery was also inspected at close range on Linux/Vulkan llvmpipe.
|
||||
With the camera stationary, screenshot differences were confined to the lantern
|
||||
as its frames changed. The [model-client startup report](../benchmarks/rust-native-model-client-2026-10-06/startup-smoke.json)
|
||||
records the bounded presentation check and the scope of this visual inspection.
|
||||
|
||||
The [blockstate-client report](../benchmarks/rust-native-blockstate-client-2026-10-06/startup-smoke.json)
|
||||
also checks all 16 gallery placements against actual Java registry binding, root
|
||||
baking and model collection using `state.getSeed(position)`. A tagged empty
|
||||
geometry adapter identifies the 20 selected model parts and their transforms;
|
||||
the separate baking gate covers actual quad geometry. Preparation exports typed
|
||||
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.
|
||||
|
||||
+6
-4
@@ -3,7 +3,7 @@
|
||||
The behavior target is the repository's pinned **Minecraft Java Edition 26.3**.
|
||||
One-to-one vanilla gameplay parity is the acceptance criterion. It has **not**
|
||||
been reached. Horizon now has a [native development client](native-client.md)
|
||||
that renders a controlled block scene and twelve baked resource models with a
|
||||
that renders a controlled block scene and sixteen blockstate-driven model placements with a
|
||||
free camera. It does not run a
|
||||
complete game server or gameplay simulation.
|
||||
The Rust workspace implements core arithmetic/random streams, world data and
|
||||
@@ -24,9 +24,11 @@ binding now also matches native selection across registered and special
|
||||
item-frame states. Weighted selection and multipart random draw consumption
|
||||
also match native traces. Transformed baking now checks every bundled
|
||||
model/rotation/UV-lock tuple and all XYZ quarter-turn combinations against native
|
||||
geometry by exact float bits. Renderer integration remains unfinished. The native renderer now draws an
|
||||
inspection gallery from the decoded/resolved/baked models, including binary
|
||||
cutouts and stepped lantern animation; these are not yet gameplay blocks.
|
||||
geometry by exact float bits. The native renderer now draws an
|
||||
inspection gallery from registry-bound, selected and transformed models, including binary
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package dev.horizonlab.parity;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.mojang.math.Quadrant;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import net.minecraft.client.renderer.block.dispatch.*;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.client.resources.model.geometry.*;
|
||||
import net.minecraft.client.resources.model.sprite.*;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.util.StrictJsonParser;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.Property;
|
||||
import net.minecraft.world.level.levelgen.LegacyRandomSource;
|
||||
import org.joml.Matrix4fc;
|
||||
import static dev.horizonlab.parity.VanillaCuboidModelOracle.map;
|
||||
|
||||
/** Real registry binding, root baking and model collection for the Rust scene.
|
||||
* Empty tagged geometry isolates dispatch; FaceBakery has its separate gate. */
|
||||
public final class VanillaGallerySelectionOracle {
|
||||
private static final Gson JSON=new GsonBuilder().serializeNulls().create();
|
||||
private static Object matrix(Matrix4fc value) {
|
||||
var rows=new long[3][4];
|
||||
for(int r=0;r<3;r++)for(int c=0;c<4;c++)rows[r][c]=Integer.toUnsignedLong(Float.floatToIntBits(value.get(c,r)));
|
||||
return rows;
|
||||
}
|
||||
private static JsonElement tag(String name,ModelState state) {
|
||||
var faces=new ArrayList<Object>();for(var d:Direction.values())faces.add(matrix(state.inverseFaceTransformation(d)));
|
||||
return JSON.toJsonTree(map("model",name,"matrix",matrix(state.transformation().getMatrix()),"inverse_uv",faces));
|
||||
}
|
||||
private static final class Baker implements ModelBaker {
|
||||
final IdentityHashMap<QuadCollection,JsonElement> tags=new IdentityHashMap<>();
|
||||
public ResolvedModel getModel(Identifier id) {
|
||||
return new ResolvedModel() {
|
||||
public String debugName(){return id.toString();}
|
||||
public UnbakedModel wrapped(){throw new AssertionError("unexpected wrapped model access");}
|
||||
public ResolvedModel parent(){return null;}
|
||||
public TextureSlots getTopTextureSlots(){return null;}
|
||||
public boolean getTopAmbientOcclusion(){return true;}
|
||||
public Material.Baked resolveParticleMaterial(TextureSlots slots,ModelBaker baker){return null;}
|
||||
public QuadCollection bakeTopGeometry(TextureSlots slots,ModelBaker baker,ModelState state) {
|
||||
try {
|
||||
var constructor=QuadCollection.class.getDeclaredConstructors()[0];constructor.setAccessible(true);
|
||||
var empty=List.of();var result=(QuadCollection)constructor.newInstance(empty,empty,empty,empty,empty,empty,empty,empty);
|
||||
tags.put(result,tag(id.toString(),state));return result;
|
||||
} catch(ReflectiveOperationException error){throw new AssertionError(error);}
|
||||
}
|
||||
};
|
||||
}
|
||||
public BlockStateModelPart missingBlockModelPart(){throw new AssertionError("unexpected missing model");}
|
||||
public MaterialBaker materials(){throw new AssertionError("unexpected material bake");}
|
||||
public ModelBaker.Interner interner(){throw new AssertionError("unexpected interner");}
|
||||
public <T> T compute(ModelBaker.SharedOperationKey<T> key){return key.compute(this);}
|
||||
}
|
||||
private static <T extends Comparable<T>> BlockState set(BlockState state,Property<T> property,String value) {
|
||||
return state.setValue(property,property.getValue(value).orElseThrow());
|
||||
}
|
||||
public static void main(String[] args) throws Exception {
|
||||
net.minecraft.SharedConstants.tryDetectVersion();net.minecraft.server.Bootstrap.bootStrap();
|
||||
var report=JsonParser.parseString(Files.readString(Path.of(args[0]))).getAsJsonObject();
|
||||
var results=new ArrayList<Object>();int index=0;
|
||||
for(var item:report.getAsJsonArray("model_gallery")) {
|
||||
var row=item.getAsJsonObject();var id=Identifier.parse(row.get("block").getAsString());
|
||||
var block=BuiltInRegistries.BLOCK.getValue(id);var state=block.defaultBlockState();
|
||||
for(var entry:row.getAsJsonObject("properties").entrySet())
|
||||
state=set(state,Objects.requireNonNull(block.getStateDefinition().getProperty(entry.getKey())),entry.getValue().getAsString());
|
||||
if(Block.getId(state)!=row.get("state_id").getAsInt())throw new AssertionError("state ID mismatch");
|
||||
String resource="/assets/"+id.getNamespace()+"/blockstates/"+id.getPath()+".json";
|
||||
String raw;try(var input=VanillaGallerySelectionOracle.class.getResourceAsStream(resource)) {
|
||||
raw=new String(Objects.requireNonNull(input).readAllBytes(),java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
var definition=BlockStateModelDispatcher.CODEC.parse(JsonOps.INSTANCE,StrictJsonParser.parse(raw)).getOrThrow();
|
||||
var root=definition.instantiate(block.getStateDefinition(),id::toString).get(state);
|
||||
var baker=new Baker();var model=Objects.requireNonNull(root).bake(state,baker);
|
||||
var pos=row.getAsJsonArray("position");var position=new BlockPos(pos.get(0).getAsInt(),pos.get(1).getAsInt(),pos.get(2).getAsInt());
|
||||
var parts=new ArrayList<BlockStateModelPart>();model.collectParts(new LegacyRandomSource(state.getSeed(position)),parts);
|
||||
var actual=new ArrayList<JsonElement>();
|
||||
for(var part:parts)actual.add(Objects.requireNonNull(baker.tags.get(((SimpleModelWrapper)part).quads())));
|
||||
var expected=new ArrayList<JsonElement>();
|
||||
for(var selected:row.getAsJsonArray("models")) {
|
||||
var v=selected.getAsJsonObject();var t=v.getAsJsonArray("rotations");
|
||||
var transform=new Variant.SimpleModelState(Quadrant.values()[t.get(0).getAsInt()],Quadrant.values()[t.get(1).getAsInt()],Quadrant.values()[t.get(2).getAsInt()],v.get("uv_lock").getAsBoolean()).asModelState();
|
||||
expected.add(tag(v.get("model").getAsString(),transform));
|
||||
}
|
||||
if(!actual.equals(expected))throw new AssertionError("gallery selection mismatch at "+index+" "+id+": native="+actual+" Rust="+expected);
|
||||
results.add(map("case",index++,"state_id",Block.getId(state),"models",actual));
|
||||
}
|
||||
Files.writeString(Path.of(args[1]),JSON.toJson(results));
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,12 @@ from pathlib import Path
|
||||
import subprocess
|
||||
import zipfile
|
||||
try:
|
||||
from . import lab, test_rust_parity as base, test_worldgen_parity as worldgen
|
||||
from . import lab, test_rust_parity as base, test_worldgen_parity as worldgen, test_model_baking_parity as baking
|
||||
except ImportError:
|
||||
import lab
|
||||
import test_rust_parity as base
|
||||
import test_worldgen_parity as worldgen
|
||||
import test_model_baking_parity as baking
|
||||
|
||||
|
||||
def main():
|
||||
@@ -43,6 +44,23 @@ def main():
|
||||
subprocess.run(['java', '-Xmx512M', '-cp', str(classes) + os.pathsep + dependencies,
|
||||
'dev.horizonlab.parity.VanillaHeightmapOracle', 'export', str(facts)],
|
||||
check=True, stdout=log, stderr=subprocess.STDOUT, timeout=3600)
|
||||
binding = oracle.with_name('VanillaBlockStateBindingOracle.java')
|
||||
helper = oracle.with_name('VanillaCuboidModelOracle.java')
|
||||
binding_sources = base.source_hashes([binding, helper])
|
||||
model_states = root / 'model-states.json'
|
||||
if not (previous.get('model_state_exporters_sha256') == binding_sources
|
||||
and previous.get('client_sha256') == origin['client_sha256']
|
||||
and model_states.exists()
|
||||
and previous.get('model_states_sha256') == lab.digest(model_states, 'sha256')):
|
||||
classes = root / 'client-classes'; classes.mkdir(exist_ok=True)
|
||||
dependencies = os.pathsep.join(map(str, [jars['client'], *baking.client_libraries()]))
|
||||
with (root / 'model-state-export.log').open('w') as log:
|
||||
subprocess.run(['javac', '--release', str(lock['minecraft']['java_major']), '-proc:none',
|
||||
'-cp', dependencies, '-d', str(classes), str(binding), str(helper)],
|
||||
check=True, stdout=log, stderr=subprocess.STDOUT, timeout=3600)
|
||||
subprocess.run(['java', '-Xmx512M', '-cp', str(classes) + os.pathsep + dependencies,
|
||||
'dev.horizonlab.parity.VanillaBlockStateBindingOracle', 'export', str(model_states)],
|
||||
check=True, stdout=log, stderr=subprocess.STDOUT, timeout=3600)
|
||||
tags = worldgen.tag_resources(jars['server'])
|
||||
if tags != worldgen.tag_resources(jars['client']):
|
||||
raise ValueError('Reference block tags disagree')
|
||||
@@ -64,7 +82,9 @@ def main():
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(data)
|
||||
count += 1
|
||||
lab.write_json(manifest, {'schema': 1, **origin, 'data_version': version['world_version'],
|
||||
lab.write_json(manifest, {'schema': 2, **origin,
|
||||
'model_state_exporters_sha256': binding_sources,
|
||||
'model_states_sha256': lab.digest(model_states, 'sha256'), 'data_version': version['world_version'],
|
||||
'facts_sha256': lab.digest(facts, 'sha256'),
|
||||
'block_tags_sha256': lab.digest(root / 'block-tags.json', 'sha256'), 'resource_files': count})
|
||||
print(f'Prepared Horizon native-client resources: {root}')
|
||||
|
||||
@@ -13,13 +13,15 @@ import subprocess
|
||||
import sys
|
||||
|
||||
try:
|
||||
from . import lab
|
||||
from . import lab, test_rust_parity as base, test_model_baking_parity as baking
|
||||
except ImportError:
|
||||
import lab
|
||||
import test_rust_parity as base
|
||||
import test_model_baking_parity as baking
|
||||
|
||||
|
||||
def resource_fingerprint(root):
|
||||
files = [root / 'facts.json', root / 'block-tags.json', root / 'horizon-resources.json',
|
||||
files = [root / 'facts.json', root / 'model-states.json', root / 'block-tags.json', root / 'horizon-resources.json',
|
||||
*sorted(path for path in (root / 'assets').rglob('*') if path.is_file())]
|
||||
digest = hashlib.sha256()
|
||||
for path in files:
|
||||
@@ -44,8 +46,13 @@ def main():
|
||||
resources = resource_fingerprint(root / '26.3')
|
||||
sources = [lab.ROOT / 'Cargo.toml', lab.ROOT / 'Cargo.lock', Path(__file__).resolve(),
|
||||
lab.ROOT / 'crates/horizon-client/Cargo.toml', lab.ROOT / 'crates/horizon-assets/Cargo.toml',
|
||||
*sorted((lab.ROOT / 'crates/horizon-client/src').glob('*')),
|
||||
*sorted((lab.ROOT / 'crates/horizon-assets/src').glob('*.rs'))]
|
||||
*sorted((lab.ROOT / 'crates/horizon-client/src').rglob('*')),
|
||||
*sorted((lab.ROOT / 'crates/horizon-assets/src').rglob('*.rs'))]
|
||||
oracle = lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaGallerySelectionOracle.java'
|
||||
helper = oracle.with_name('VanillaCuboidModelOracle.java')
|
||||
sources += [oracle, helper, lab.ROOT/'tools/test_model_baking_parity.py', lab.ROOT/'tools/test_rust_parity.py', lab.ROOT/'tools/lab.py', lab.ROOT/'tools/prepare_native_client.py']
|
||||
for crate in ['horizon-core', 'horizon-world']:
|
||||
sources += [lab.ROOT/'crates'/crate/'Cargo.toml', *sorted((lab.ROOT/'crates'/crate/'src').rglob('*.rs'))]
|
||||
tested_sources = {
|
||||
str(path.relative_to(lab.ROOT)): lab.digest(path, 'sha256')
|
||||
for path in sources
|
||||
@@ -68,10 +75,29 @@ def main():
|
||||
result = json.loads(report.read_text())
|
||||
if (result.get('error') is not None or result.get('presented_frames') != 30
|
||||
or result.get('resident_chunks') != 9 or result.get('merged_quads', 0) <= 0
|
||||
or result.get('model_quads') != 145 or len(result.get('model_gallery', [])) != 12
|
||||
or result.get('model_quads') != 209 or len(result.get('model_gallery', [])) != 16
|
||||
or result.get('sprite_layers') != 18 or result.get('animated_sprites') != 1
|
||||
or not result.get('adapter') or min(result.get('window_size', [0])) <= 0):
|
||||
raise RuntimeError('Incomplete native client presentation: ' + str(report))
|
||||
lock, jars, _ = base.verify_artifacts()
|
||||
classes = root/'gallery-classes'; classes.mkdir(exist_ok=True)
|
||||
dependencies = os.pathsep.join(map(str, [jars['client'], *baking.client_libraries()]))
|
||||
native = root/'gallery-selection.json'
|
||||
with (root/'gallery-selection.log').open('w') as log:
|
||||
subprocess.run(['javac', '--release', str(lock['minecraft']['java_major']), '-proc:none', '-cp', dependencies,
|
||||
'-d', str(classes), str(oracle), str(helper)], cwd=lab.ROOT, check=True,
|
||||
stdout=log, stderr=subprocess.STDOUT, timeout=3600)
|
||||
subprocess.run(['java', '-Xmx512M', '-cp', str(classes)+os.pathsep+dependencies,
|
||||
'dev.horizonlab.parity.VanillaGallerySelectionOracle', str(report), str(native)],
|
||||
cwd=lab.ROOT, check=True, stdout=log, stderr=subprocess.STDOUT, timeout=3600)
|
||||
selections = json.loads(native.read_text())
|
||||
if len(selections) != len(result['model_gallery']):
|
||||
raise RuntimeError('Incomplete native gallery selection check')
|
||||
result['native_selection'] = {'matched_placements': len(selections),
|
||||
'selected_parts': sum(len(s['models']) for s in selections),
|
||||
'client_jar_sha256': lab.digest(jars['client'], 'sha256'),
|
||||
'output_sha256': lab.digest(native, 'sha256'),
|
||||
'scope': 'Actual native registry states, blockstate dispatch, root baking with tagged empty geometry, and legacy collection using state.getSeed(position). Model names and transform matrices match scene selections; no shader/pixel parity claim.'}
|
||||
if resource_fingerprint(root / '26.3') != resources:
|
||||
raise RuntimeError('Resources changed during native client verification')
|
||||
result['resource_files'] = resources
|
||||
|
||||
Reference in New Issue
Block a user