Render registry-bound variants and multipart block models

This commit is contained in:
Ludovic Marie
2026-10-06 21:59:03 +02:00
parent 90a76bc0aa
commit 1a88d6c300
10 changed files with 1138 additions and 97 deletions
@@ -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."
}
}
+4 -2
View File
@@ -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, &registry, &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,
})
}
+1 -1
View File
@@ -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),
+76 -71
View File
@@ -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,
})
}
+214
View File
@@ -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
View File
@@ -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
View File
@@ -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));
}
}
+22 -2
View File
@@ -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}')
+31 -5
View File
@@ -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