Match post-collision restitution and bounce side-effect ordering

This commit is contained in:
Ludovic Marie committed 2026-10-07 00:23:13 +02:00
1 parent 7ac3d31313
commit cebf0c0e06
7 files changed
+646

No files matched your search

@@ -0,0 +1,209 @@
{
"schema": 1,
"minecraft": "26.3",
"manifest_sha1": "4fe1aa1ef8da1cb95c5bad1fb98890ca56dd8ca3",
"platform": "Linux-7.0.0-31-generic-x86_64-with-glibc2.43",
"java": "openjdk 25.0.4.1 2026-08-18",
"rustc": "rustc 1.99.0 (b940084d7 2026-09-28)",
"fixtures": 19784,
"sources_sha256": {
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaRestitutionOracle.java": "7e63393bf0590fc7b1b2ec6c3e4afa8f3a84b555e25744ac94705fcb67be85d0",
"tools/test_restitution_parity.py": "db7090350723ac66ae9ec0bf106dff3e7a8961fcda2bf2132c71eb9e35d7ae7b",
"tools/test_rust_parity.py": "29d3db08005873528894b45ca10dcd6278753650473b3eb574e53e06ede40ec6",
"tools/lab.py": "d00bf107fa01b6c579734a082c3ca4ade3018f0d6851d46844acf17574f66e64",
"Cargo.toml": "3f93319c1eb70dbd7ba25cd74ef343fbf35c52c7c4752f955605c58699cbff9d",
"Cargo.lock": "cb1e87d449114a63edd904c0559d631e8fa9feeb27efd6539aacdcfeae178876",
"crates/horizon-core/Cargo.toml": "e21b502c949da93cf67826592459b687a8cbf737ef92fee514e3ed2cc7cd27ad",
"crates/horizon-world/Cargo.toml": "977338bf3e654193685b015d383493b9f59ff6f5d6210bec212cef8ac4a50bed",
"crates/horizon-core/src/bin/density_parity.rs": "e5b1dd33b7d01306c73cf072629b4fb186e9ab87b672a00108999d1a72263fd0",
"crates/horizon-core/src/bin/float_log_parity.rs": "4a655fcf8822ee7668c4bd126bc84d0b118864a6fc7f5a6aeee667d8bf2590b1",
"crates/horizon-core/src/bin/geometry_parity.rs": "27f6c09fe5018ac3de8e70e0f0bbf78f9abb14f5e4783bcaf46e8ca87b9c6f8a",
"crates/horizon-core/src/bin/noise_parity.rs": "c135bfc708d853e7061e81487d2cfe10de64b2f65677d3b4837cbfe0b8341010",
"crates/horizon-core/src/bin/parity.rs": "f35d928c66f958de39fac5e7e836a657eed95f83599e84899f7ea7a5802ba481",
"crates/horizon-core/src/bin/tick_parity.rs": "a54c164fe4814034d369947a887f5273966f5a878ba849204b80d1c729526a8f",
"crates/horizon-core/src/biome/seed_hash.rs": "5afbb4921c750826b395a08bf06f382fc7f01b5759a93de02a2749c523e2a317",
"crates/horizon-core/src/biome.rs": "908efa8cd4aa0a7adf1cb5e1a222e3dfb640eed5e9172e0fe7f0d1287afae7e8",
"crates/horizon-core/src/carving/ellipsoid.rs": "09232a7588610e8a9176fd9c62d40afb1fb89f2ecab6d555536a1d847b142d86",
"crates/horizon-core/src/carving.rs": "0aa3093caa0437868a8c0ed52a6c6e1993bd51c03583c670a2837e749eae5426",
"crates/horizon-core/src/density/arithmetic.rs": "372faec96c923e2cf4aa6c66c1ac398e676853274557823850dbc8933dd15ab1",
"crates/horizon-core/src/density/beardifier.rs": "bb1052e27dae4a01954ca78d7d5babafef85f3b2c704ccaf1920cb85a098cb50",
"crates/horizon-core/src/density/cache.rs": "38d0b6c9dd04ffef979fdad3f29cfebf98d331c8c9d430be5f39aa34f6c30975",
"crates/horizon-core/src/density/end_islands.rs": "168b9384237bd2e41d0ae5a74e944da4dc74e493bdec31fb73184c9a49e3de55",
"crates/horizon-core/src/density/graph/compiler.rs": "ac67deeb16f16d0859aa20e5a165811a29aca0b910037ee6bf5098211f3a6ede",
"crates/horizon-core/src/density/graph/mod.rs": "b776e7da934cb03051a3053d56d461c71e9978e084922fe4194147aa580c07bf",
"crates/horizon-core/src/density/graph/seeded.rs": "6bcdcaa3dffcfa9df673bbbea35120057a16ba3d2b506c52709c36cf8976b74a",
"crates/horizon-core/src/density/graph/tests.rs": "6bfb8cbff462587dd147b0775ea01d370abbd97544b9bd596c792df1b5f7e634",
"crates/horizon-core/src/density/interpolation.rs": "8c890f52b6467f38a94c654986a6a484b8e0b296ab98c398e60a6ce465b5b010",
"crates/horizon-core/src/density/interval.rs": "b42629163baa6b599a233afe8596d1c370cd81d015cc6fc36a9663aa47b98424",
"crates/horizon-core/src/density/mod.rs": "ca0d77d3282e3746ddcbc92bee97dbf640b9a56214e6ac1537f51f9a5b63d6e7",
"crates/horizon-core/src/density/power.rs": "d5b7270a128d4264268cd772109ad16d71a8ec3ff2d3b63b84cb985296d54a91",
"crates/horizon-core/src/density/rounding.rs": "71840c5206ff8d218ac29bf6f66915d5212764ba270c675711a866ba5b55a7a6",
"crates/horizon-core/src/density/selection.rs": "0dd26af2e85004ec5e053039ad11c384c17c56ace9ab45eb3f5cb3503c0b0330",
"crates/horizon-core/src/density/slice.rs": "5d77605573c0deda47778f1f78f7fa3fbf1944d8f5efbcc65cf589181cadf80b",
"crates/horizon-core/src/density/spline.rs": "6bb2e40f940fc6544d6fb09cb0b8cac722be03bc663dbdfb0f61faae207400bc",
"crates/horizon-core/src/density/surface.rs": "159e0e69cda74f870e80ea64fe523c4397e64af4ccb861bb9fd07b2c3cb91b00",
"crates/horizon-core/src/density/terrain_noise.rs": "8d0059d25eb5d248cb97a4c5a2e670d61528928ea151495c004f25adb723fe3e",
"crates/horizon-core/src/density/tests.rs": "7817796ee40d0ae241bfbebec6bca190e0a8e7d5a1caa8ddf858c3eaa58b8858",
"crates/horizon-core/src/density/world_fields.rs": "c1ce305cb8046b384d90c564cafb24af7365d580389637910f3d8b0cc18eb583",
"crates/horizon-core/src/geometry/border.rs": "1386f8080521231d05ba3e84d94a3e1d5218d35ccb3d5e9fe2bbb05a8739e573",
"crates/horizon-core/src/geometry/mod.rs": "c650386f9860e44a96c55f18c8f1428ca715fcb0bc5c2434db5b1caa43df27fe",
"crates/horizon-core/src/geometry/movement.rs": "bcfb452752940ce53ae6dc267a3c4eb29459046a063c1f884684ab4f4bd0068f",
"crates/horizon-core/src/geometry/shape/create.rs": "189812b91d5e0e3ad963afcd1a6e2831b31b8188df91a904ef27061e644806ad",
"crates/horizon-core/src/geometry/shape/face.rs": "f243bce81384040a95ce7984b02b5699206c596c537b2713097e86005b4d3a5c",
"crates/horizon-core/src/geometry/shape/join.rs": "f1b51887b7bf9482557e3ffa99e136f2e5db07ceeb05fdb116adf45e87129075",
"crates/horizon-core/src/geometry/shape.rs": "e8d88a763de1525f77a30b99591641e8a8b80946263acbde035a9bcc356df960",
"crates/horizon-core/src/geometry/sweep.rs": "abd6009c61555161ef2f18b431b2aa384b126c0871cae8b96a62358498a56a1d",
"crates/horizon-core/src/geometry/traversal.rs": "77a3da570a0b85582b669cf4d841cba4b06327006dda26d4a26df4e8ce05bae4",
"crates/horizon-core/src/geometry/trig.rs": "05cef9341a298a474221a0556a11ddf47650393ea7a97b4b2022a4da6628fe37",
"crates/horizon-core/src/lib.rs": "4255e16837fde42a52ba118f60581d197c571bb530b60d635e506184ebf4db0b",
"crates/horizon-core/src/noise/mod.rs": "1dab5a713abf702402e69ace1bf9e476bba544b0ede5a61c3c3afc60af0a03f3",
"crates/horizon-core/src/noise/normal.rs": "e9e7a46472a54a02c98c46da819842090567153b32fe0c84249009ef19af7aad",
"crates/horizon-core/src/noise/perlin.rs": "e73815447432eab7542f67366cb3f1b368d164e33f6c9cdbd045ac8e4db0932a",
"crates/horizon-core/src/noise/simplex.rs": "6c0dbc15b2b7510195dd28fceca01dcaac1847ccc4a84d299c351589c642fe7a",
"crates/horizon-core/src/noise/stack.rs": "7f6ef0e7d920139ef1a6ad3ec5640f92765715f86dcc5ec29f1a149984f45fa3",
"crates/horizon-core/src/noise/volume.rs": "3f05d2b1780acfdfee60037677d309937a5033b45ef428fc2d540942d4b56b43",
"crates/horizon-core/src/position/iter.rs": "32d2d8e74821cc4e49e3354202d35d3120e4415bfdda9375a73f2137ef651129",
"crates/horizon-core/src/position.rs": "e10101088e6902386b9b6d27773d44312f1ff72bebb19e57967a54945dde9dcd",
"crates/horizon-core/src/random/hash.rs": "0f88a127a1ba2705d3d6c0ebd009324d6ba86ffa2d27edf2d2a84765e690e4a3",
"crates/horizon-core/src/random/positional.rs": "35494745e5d4cf89e282c3ae0ee245e9f2640ddbbc03bc0055df742006a9c862",
"crates/horizon-core/src/random/worldgen.rs": "9f07ff33bcc038d34e27aa5465753bb21df18f2975e43cae201f709bc186945a",
"crates/horizon-core/src/random.rs": "ddb95d3a83e28ad605ff1f49f5df270cc543ca5ef1130e37d989dc74c8d861e6",
"crates/horizon-core/src/storage.rs": "f6c32201c64aefde06e57a2c767be46918df613548a322801546b133799cc33d",
"crates/horizon-core/src/text.rs": "e6553ddb5db5039e8dc4614b6a6dc9f35a26dc3d1673b68a04e72e7983b67abb",
"crates/horizon-core/src/ticks/generation.rs": "be1237e790693f633a61714ddae68c6506a777588a8a3ab5efbe53a35774dbd2",
"crates/horizon-core/src/ticks/heap.rs": "bf28afab8af77c3d243aa96cd8a1fb8d934140a4dbd07dbfdbf4e7908bb4b845",
"crates/horizon-core/src/ticks/index.rs": "1f1a6a568c2a30eafecb020ca4d5f5fc414860de85b8b381fc419a2e3e65c24a",
"crates/horizon-core/src/ticks/mod.rs": "01634630668172516b8b79d0907bad47f6e2c543a6d499c2fa4b201823b375cb",
"crates/horizon-world/src/bin/worldgen_parity.rs": "3143ceb9b56eecfd7667e5feb5f9f0cd13cfb8754b38d4ea338dbba97f2819da",
"crates/horizon-world/src/block_collision.rs": "39207247f2f6dd3ddc77640229044246f91b32054563f88a435f1bd92ad2092c",
"crates/horizon-world/src/block_collision_scan.rs": "20802862364c110ff85eeaf48606596a8468b83768dda46b412d337958790b41",
"crates/horizon-world/src/block_entities.rs": "a9deae92e6e5a1fcf4f0476c33ea4d170ddad6fd4b3303e1bb66d933973d5e06",
"crates/horizon-world/src/block_placement.rs": "524f4892d50bab77b7d89d19f7b048ffe746996f2ed7f376db2c6ab00ed75c24",
"crates/horizon-world/src/block_properties.rs": "b0bd8a983336c48e488a20caa77dd5c39b1a74bbd14ebc03b3b64556a1c864fc",
"crates/horizon-world/src/block_render.rs": "8df52ee11aef406a1571198a64048062a0ef6864e0058d9293a57f64b73c916a",
"crates/horizon-world/src/block_support.rs": "ade4fbd47284b450dabdd682dc86138b2ac05a0fd379525d7110648908245476",
"crates/horizon-world/src/block_survival.rs": "28ce64375e09819a4f13da9cfbe37c08b5ad0daf01b289c1c360b4567e50e811",
"crates/horizon-world/src/chunk.rs": "8843e6f48dbbc4245ce4e95564dfba3a4d7a051ba0b1577fb038c07adb0ff0f9",
"crates/horizon-world/src/chunk_status.rs": "dbb8583cfa775203c544d24d69e03720385428f15a267a561356c21f5025e9c7",
"crates/horizon-world/src/collision.rs": "a749794d5c27ccd6c0d964ad02f9dc31d49d9f4fe4b7366cd4bd8155e681b00b",
"crates/horizon-world/src/collision_context.rs": "2176c7034e07f547510a8327cdad9d3b63f26e4754032b76b7d5fe13166c333c",
"crates/horizon-world/src/compression.rs": "4db73e6ca9a0d552d415ce60e7e88b89c9cf2b7d060a669ff8328f518316e101",
"crates/horizon-world/src/dynamic_support.rs": "5996bc1c436fb6215d8fc3d0bb4de5dd9b966d842d432580d624195771697fb8",
"crates/horizon-world/src/entity_collision.rs": "09f2fd7f6cd56fc282f468ecfa765a94506929e9684bd3a5c90123e5b51cd627",
"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": "f2c399b48e8489cf1d9c659e6855967778e3b2e23b05c94b7e33c3e22c1912ec",
"crates/horizon-world/src/liquid_collision.rs": "0f0a83b04e5a5bc9d98ed05fde4d6584ba69642e93522e726e4cfc325d6fcb1d",
"crates/horizon-world/src/main.rs": "fce57418b8c22a6a0a9044baa89ed3d9c243bc2e8413fea3ff0f32985daba021",
"crates/horizon-world/src/movement_restitution.rs": "04b4fe0947e7e52576145bd8ed15920278e8a538afcc74d125eaca513dffa0ef",
"crates/horizon-world/src/nbt.rs": "60262c3de8c8f68bc2da0370ccb17393d32b85f5f5022c6358599a7d32119ea6",
"crates/horizon-world/src/piston_shape.rs": "399c2337103b8e7e0eb5667227c5aa14f9549d6a467b7be7da3d0717f0263500",
"crates/horizon-world/src/powder_snow_collision.rs": "7289946a21478d11abfd2bdcdddff3d7f98e25bf6a68564fed45c3cc01ac2a36",
"crates/horizon-world/src/region.rs": "72b642e87fed37ed17f54d4c30f96c3b64d26c14bc0589c31ef40ff1616188fc",
"crates/horizon-world/src/registry/json.rs": "7a683afcd1742c5307756927d7f0aa779c7d7992af87d8d371dd98108728e235",
"crates/horizon-world/src/registry.rs": "f2e58b2b4d944ac8fc6226bd3352cefc3d2052d8473e02ccf7dc402b176df435",
"crates/horizon-world/src/registry_collision_scan.rs": "a5021a168adcedc37d77f533a8921c02f09c59f46a01737586aaa33c5908b8d0",
"crates/horizon-world/src/retrogen.rs": "6efd36a6293756688c1990a7965bd65609d7c5e2bf2a5f4f01d54932d2b9386b",
"crates/horizon-world/src/suffocation.rs": "bed219dd5bbab6263b98fbe3d0eb512cb96c52ae847210efe49514d05f22f11e",
"crates/horizon-world/src/tick_data.rs": "f8d04a69ea263f4168bce33f92e5b4ee2fe07d7caf50a1621c6b3ef6eb2479f3",
"crates/horizon-world/src/world_movement.rs": "8ac9e90fe899855036658afd862ff20c4dcd15d1bd3f92a763c4795fecf0549f",
"crates/horizon-world/src/worldgen/anchor.rs": "d156b8a0b147a274cdd143409b441f4f43edecde6e8ee14ef9cf0309f743143a",
"crates/horizon-world/src/worldgen/aquifer/config.rs": "fcd2cc3a520c1527c88f095a3b5d38487e7ebb1ccecc5f757f93d04a069add4f",
"crates/horizon-world/src/worldgen/aquifer/noise_based.rs": "74daaa389b5c3dfdc99214dfff256a9e172f83548d2326cbc941d9637cc53fb7",
"crates/horizon-world/src/worldgen/aquifer.rs": "cb4b66d87de3f9f79ae6ffe4badd6f0f44886cd42ad9de2771b8b56c4f1e28c7",
"crates/horizon-world/src/worldgen/bamboo.rs": "d9b47113c99be34256e58308bc6f40072ff25c579d6bf995182aa862c0006102",
"crates/horizon-world/src/worldgen/biome.rs": "3fe316b875aa32683759c337dc71f624910faf0812a2d6d83b8098d6d0bac7a0",
"crates/horizon-world/src/worldgen/biome_chunk.rs": "8fdfe6ebe3ffeca97611fd5991a5bc6b2783ea1c62f4d8ef3b2dd53c04a5dc1d",
"crates/horizon-world/src/worldgen/biome_predicate.rs": "bfc581bc40077cc74ffda43e990a55fe997adc04c7130f98e039c3305ca79aac",
"crates/horizon-world/src/worldgen/biome_source/codec.rs": "2b9e4f5d679eb444cb73174f511e3b9f32e9eb138248ab12cffc6cfd76adf3a8",
"crates/horizon-world/src/worldgen/biome_source/runtime.rs": "f0b02c1521178e4d0377c6ca62694604b6a773f1f234f070ff0cde4685911d45",
"crates/horizon-world/src/worldgen/biome_source.rs": "e181e1736f3e0b75f7091a24c88de3fd116e01b98424d02382aea7a503d1eb54",
"crates/horizon-world/src/worldgen/biome_temperature.rs": "cde4bbab5e260c4065b113376c423b8511ade93c5aa5fd976e245ee8a58b5ad1",
"crates/horizon-world/src/worldgen/biome_weather.rs": "96b1f9494559ed31d06257b83d604d00cc069ccc8c7b68094117e034b4a71bd7",
"crates/horizon-world/src/worldgen/block_blob.rs": "c608c57b3d77279646ecb89e50677dd8e452b0422ffd357ba755536c5589eb89",
"crates/horizon-world/src/worldgen/block_column.rs": "f72c21a9066ee0aa68c2fb79fba436d05afb14c6ef5a2cb012f1ed35fc17cd22",
"crates/horizon-world/src/worldgen/block_feature.rs": "36b4e46ee7f7f73bcb94d8bce8d99013e4b16234c9a03542eeb3186e88dd1c21",
"crates/horizon-world/src/worldgen/block_pile.rs": "623e369b77e0526ed2756485a6c0bb09683cd28ad8876dea907360ca6665b39d",
"crates/horizon-world/src/worldgen/block_predicate.rs": "074c692367e3f2191a1bb28d87b23402430a843c246bd30a4ea838ee3500280e",
"crates/horizon-world/src/worldgen/blue_ice.rs": "eedd77bdaff808330b864166d303378096daa5c7d235e925770b171c05109247",
"crates/horizon-world/src/worldgen/bonus_chest.rs": "05d0f63fbc7412f13ba1fa37a7238114b8f70bfff318beb2dece590482d49cc0",
"crates/horizon-world/src/worldgen/bulk_section.rs": "eecf61532d7793f853884c5a18a7f436a59835c7b13d3ec9075804511553c9ba",
"crates/horizon-world/src/worldgen/carver/canyon.rs": "509c4d5a9d689849e98e894ab14a9fbf82d10707d38e9cc688ded3c4778df3e2",
"crates/horizon-world/src/worldgen/carver/generation.rs": "a4498d0d1457536de3922a08e9c40ccce8d2ef9cc263801c15a412327513c098",
"crates/horizon-world/src/worldgen/carver.rs": "46b251e28225fe1250de38f49dec5dacda4e2c4ba1e556e21e6d1e14e2651fc3",
"crates/horizon-world/src/worldgen/carving.rs": "4e77d4e5c4a0ca55ca3563b9bdf6f379342e0bdf9d019d60f7bc57ce4afb5fbd",
"crates/horizon-world/src/worldgen/chorus_plant.rs": "29224c164ede919e280a65a68f155c873e2c8fd0aa7d7e244d8fa9e468760250",
"crates/horizon-world/src/worldgen/climate/codec.rs": "d567adeb5d837816fca69102db51d790f71aec1b51c09298230d24612e400a14",
"crates/horizon-world/src/worldgen/climate/index.rs": "c5ce61679a5f0e0f2f0af2a373424effa124d0cce8250609eff38b7034650756",
"crates/horizon-world/src/worldgen/climate.rs": "1b9ad44407f40aee4c1cd5357a5c9c443c1b105728f01cf5c644072f99f8357e",
"crates/horizon-world/src/worldgen/decoration.rs": "75a79b20b21e133816e729f16e758be6165d0db18a64bfbd5fd3618325a2adf3",
"crates/horizon-world/src/worldgen/delta.rs": "c2bf49ddf3231d28c109e68871429bc13d76a7bebe14eb33e84aa856bdd66145",
"crates/horizon-world/src/worldgen/density.rs": "65a98c156a290a6b374ef670d28cd5dfbe0aa93171824d1bbd96422536273f84",
"crates/horizon-world/src/worldgen/disk.rs": "6f8f9e059755af44c9bfa1b91eba54a7d13fad811dce31717c5502d7f6df33d7",
"crates/horizon-world/src/worldgen/end_gateway.rs": "f53fde77d6a36a525cd15d890003b2c129d0689a4cbfa2cf443a0c2e3acae040",
"crates/horizon-world/src/worldgen/end_podium.rs": "8f6d0d6d60ff0fde63de5781e867239db321f82cf12639f0537d410a3731ee1d",
"crates/horizon-world/src/worldgen/feature.rs": "40500af71ee6628dfb27518262d99f08d0383ae6c7b9397452feef0c213eadd9",
"crates/horizon-world/src/worldgen/feature_catalog.rs": "efc7b17b227e71622ef34108be9abd010f5675b68ea0409fcab82581d9aab88a",
"crates/horizon-world/src/worldgen/feature_graph.rs": "da132fca9fdafb0d89a917d5f4b68c60b70fb33cadf948a107da0e35d83dcd91",
"crates/horizon-world/src/worldgen/feature_region.rs": "df0ff142a461cafa9a3b42b949ad3376887f2be8d4ea5cc794a3ed2e977bb499",
"crates/horizon-world/src/worldgen/feature_sort.rs": "3488a093dfe1321aaa29d8a30cfb115d8e666a81bf195514e87b170de626835c",
"crates/horizon-world/src/worldgen/fluid_predicate.rs": "64197c9e63bebf1ce04bc280bfe980bb6c1b9aa887ca5cded904aeb0d9ae6c46",
"crates/horizon-world/src/worldgen/height_provider.rs": "368d4dc119854742a045c4a717679c921456da681fc5f71aa9b37102d6f33b47",
"crates/horizon-world/src/worldgen/material_condition/codec.rs": "09b909bc384472f76d163c8069ec6b507c7d5c6a028a36d5e0e3df20deff7b3b",
"crates/horizon-world/src/worldgen/material_condition/density.rs": "df9d0761e978e5010663ae8b44f2d6c1ecb4907331e333bdb6a2b487c1e107de",
"crates/horizon-world/src/worldgen/material_condition/library.rs": "a55534c5da97575a819af5a7dc0dbec9069993fa8b2619c6c9d5576946e683fa",
"crates/horizon-world/src/worldgen/material_condition/noise.rs": "ffb59cc6be2275d7a5621bf7d768527e74a8326ed7ad78618746711540921675",
"crates/horizon-world/src/worldgen/material_condition/rule.rs": "fed643352efbcb1f68f29b6241896617db889fc7c577a6247564e01922fec1fe",
"crates/horizon-world/src/worldgen/material_condition.rs": "d638dcb8cbcb5da20b3cb29602b0ae6a86c4fdf9d8c0fb725de8ec5fc108717e",
"crates/horizon-world/src/worldgen/material_extensions.rs": "ab806eb679d5116e1aebb64fca70e1499ae71f83c9507a8cb4c3201c2b1bf037",
"crates/horizon-world/src/worldgen/material_stage.rs": "698cbc786eff13c246e2bc2ea062401918cd4265899fbc3d53abc87e45a8e7a1",
"crates/horizon-world/src/worldgen/material_surface.rs": "5d37b90099df3d6da0845dab164e71469fb287019c2ee74bc6714f3b9d6d6d5b",
"crates/horizon-world/src/worldgen/ore_feature.rs": "83b89fd3be691c5412c0215b6abefdcaa44119a05aa41217e061970eb7e12ebd",
"crates/horizon-world/src/worldgen/placement/chain.rs": "9218399b4d3757f7bbc550d7729a7201f32a1c83a7030865e0c12aab8a80d116",
"crates/horizon-world/src/worldgen/placement/context.rs": "a278304795136677ad4b4933ea50c972b9729a1c671c03fa309c193e224b1fd6",
"crates/horizon-world/src/worldgen/placement/layer.rs": "a0f5ca776f33f9d4f72b8fdcd163426152e5e43cb4a7572fc1d4fc57bf965de1",
"crates/horizon-world/src/worldgen/placement.rs": "b0397e5818fae84358546193a95f84bcb0b5bffafce909e6fcb3386179ee8023",
"crates/horizon-world/src/worldgen/replace_blobs.rs": "bf3377d86d2570cd15288ba69dde64a2cf67c5bcbb9a31d0c23421964af74afa",
"crates/horizon-world/src/worldgen/router.rs": "2cee7cf15a0d2ada12be6b39d83e78177fd3033922e4f4cce87a64073cec2847",
"crates/horizon-world/src/worldgen/rule_test.rs": "d1662ec492489ebaa4b87e20f9a8c809dcf3a223a151211bd65ec241a63692fc",
"crates/horizon-world/src/worldgen/settings.rs": "a8fe87752e1982ef11cb67f8d80d8e3428b2c27db178044cb425ba803e689e59",
"crates/horizon-world/src/worldgen/simple_block.rs": "9ba1a88d5db4925f8c37366557887f776c805f9a6444781615b5dbaff52e92a4",
"crates/horizon-world/src/worldgen/snow_freeze.rs": "9040be3d5c7f8f0fd812d2df45e154bf8a9eeef038530b83fa4159d246eac35f",
"crates/horizon-world/src/worldgen/speleothem.rs": "c71651c1c97818a8a85863e14414d6139ea3bdeb897fa422043bcc1499d99aae",
"crates/horizon-world/src/worldgen/spike.rs": "eacf0d9d3d2bc3d4bcf62bc87555f90031138236a5bfef0eb5270204f7c2a4fc",
"crates/horizon-world/src/worldgen/spring.rs": "830849e9b89fcdf09e9b06fbefdcdebe3c8c3f82804f60b8bd5de04ba303cb62",
"crates/horizon-world/src/worldgen/state_provider/catalog.rs": "c8687e15a4887a9200c8d365857d801efaa8f90e669eee3e600a1db4905119eb",
"crates/horizon-world/src/worldgen/state_provider/noise.rs": "7ee369c2a032e5620cb9bdf9ee7adad1f70870fb0a27efb7b7c514d92f83491f",
"crates/horizon-world/src/worldgen/state_provider/properties.rs": "9909139aaca1dc4f743957257fadaa9d423d21fa401fa9abc050505f79365139",
"crates/horizon-world/src/worldgen/state_provider.rs": "be29ada818826a90afe01ab60916360fdfc4cbde5af15c9359cd56b1f1f7b156",
"crates/horizon-world/src/worldgen/tags.rs": "d35ded93fe6e95ea045cadcf4925e656dc34ef832b742179b9ad0cc49f902f31",
"crates/horizon-world/src/worldgen/terrain.rs": "fd1f2e0afc15daf66f484ea40a1f2b7013650bda0941b23c64c81ee9f79cb673",
"crates/horizon-world/src/worldgen/underwater_magma.rs": "7317e40af5ee5a9780f90ce358fa5d3e7f5b3d513b731df6ff97754d9c2a0154",
"crates/horizon-world/src/worldgen/value_provider.rs": "3358b563db8cb0bc8a9cfbbe2b8b3e70941cd2d37a0b519fbb6545f8d0dcbb69",
"crates/horizon-world/src/worldgen/vines.rs": "ee9b79b65012bd86a838241d2ef708680f7fe8eafc8aa656dae9cf2abf3c6418",
"crates/horizon-world/src/worldgen/weighted.rs": "839840aae25302933bbd97a81a2336330e2d03a0ad1684e1e16ac769ed408e6b",
"crates/horizon-world/src/worldgen/write_zone.rs": "8e17a179764d3f68c05882aef35f6810c28a0000f9a83e58bd6030660b76e64e",
"crates/horizon-world/src/worldgen.rs": "e8e9f099ee1fcec8eedce31805d676d3f89c18e84f494a147ffcbfee23d3d54f",
"crates/horizon-world/examples/restitution_fixture.rs": "1c970701dc434155f8c830626ef5ad4cc9ac20330ceb2c91c3e351215b8dedf3"
},
"fixtures_sha256": {
".local/rust-restitution-parity/inputs.txt": "c123b46f42604e661c867d1ecf22f3ce1d84b19cb79dd11869aa7d92e6bba9a7"
},
"rust_output_sha256": "94ab544d2c4c0a2644907521b96b913eeed376863b26912dda56755b48255cb5",
"references": [
{
"side": "client",
"jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d",
"output_sha256": "94ab544d2c4c0a2644907521b96b913eeed376863b26912dda56755b48255cb5",
"matched_output_lines": 177090
},
{
"side": "server",
"jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6",
"output_sha256": "94ab544d2c4c0a2644907521b96b913eeed376863b26912dda56755b48255cb5",
"matched_output_lines": 177090
}
],
"scope": "Actual Entity.restituteMovementAfterCollisions over living/nonliving snapshots and controlled block restitution/tag behavior. Compares horizontal/vertical bounce, effective-gravity threshold, nonliving float scaling, air-drag interpolation, signed zero/nonfinite values and ordered lazy queries. Bounce callbacks can change velocity and fail; final velocity and syncPosition are checked after every injected failure boundary. This does not establish registered block restitution/tag provisioning, actual bounce sound/game-event effects, Entity.move guard conditions, fall damage, grounding or full gameplay."
}
@@ -0,0 +1,162 @@
use horizon_core::{geometry::Vec3, position::BlockPos};
use horizon_world::{
movement_restitution::{RestitutionInput, RestitutionWorld, restitute_movement},
registry::BlockStateId,
};
use serde_json::Value;
use std::{
fs,
io::{self, Write},
path::Path,
};
fn number(v: &Value) -> f64 {
f64::from_bits(u64::from_str_radix(v.as_str().unwrap(), 16).unwrap())
}
fn single(v: &Value) -> f32 {
f32::from_bits(u32::from_str_radix(v.as_str().unwrap(), 16).unwrap())
}
fn vector(v: &Value) -> Vec3 {
Vec3::new(number(&v[0]), number(&v[1]), number(&v[2]))
}
fn bits(v: f64) -> u64 {
if v.is_nan() {
0x7ff8000000000000
} else {
v.to_bits()
}
}
fn vector_text(prefix: &str, v: Vec3) -> String {
format!(
"{prefix} {:016x} {:016x} {:016x}",
bits(v.x),
bits(v.y),
bits(v.z)
)
}
struct World<'a, W> {
f: &'a Value,
out: &'a mut W,
calls: i32,
suppress_calls: usize,
gravity_calls: usize,
velocity: Vec3,
sync: bool,
}
impl<W: Write> World<'_, W> {
fn query(&mut self, text: &str) -> io::Result<()> {
writeln!(self.out, "{text}")?;
let call = self.calls;
self.calls += 1;
if call == self.f["fail"].as_i64().unwrap() as i32 {
Err(io::Error::other("injected restitution failure"))
} else {
Ok(())
}
}
}
impl<W: Write> RestitutionWorld for World<'_, W> {
type Error = io::Error;
fn suppressing_bounce(&mut self) -> io::Result<bool> {
self.query("suppress")?;
let values = self.f["suppress"].as_array().unwrap();
let result = values[self.suppress_calls.min(values.len() - 1)]
.as_bool()
.unwrap();
self.suppress_calls += 1;
Ok(result)
}
fn entity_bounciness(&mut self) -> io::Result<f64> {
self.query("entity-bounce")?;
Ok(number(&self.f["entity"]))
}
fn velocity(&mut self) -> io::Result<Vec3> {
self.query("velocity")?;
Ok(self.velocity)
}
fn effective_gravity(&mut self) -> io::Result<f64> {
self.query("gravity")?;
let values = self.f["gravity"].as_array().unwrap();
let result = number(&values[self.gravity_calls.min(values.len() - 1)]);
self.gravity_calls += 1;
Ok(result)
}
fn block_suppresses_bounce(&mut self, _: BlockStateId) -> io::Result<bool> {
self.query("block-suppresses")?;
Ok(self.f["tag"].as_bool().unwrap())
}
fn block_bounce_restitution(&mut self, _: BlockStateId) -> io::Result<f32> {
self.query("block-bounce")?;
Ok(single(&self.f["block"]))
}
fn air_drag(&mut self) -> io::Result<f32> {
self.query("drag")?;
Ok(single(&self.f["drag"]))
}
fn movement_support_position(&mut self) -> io::Result<BlockPos> {
self.query("support-position")?;
Ok(BlockPos::new(17, -64, -33))
}
fn bounce_on(&mut self, _: BlockStateId, p: BlockPos, fall: f64) -> io::Result<()> {
self.query(&format!(
"bounce-on {} {} {} {:016x}",
p.x,
p.y,
p.z,
bits(fall)
))?;
if self.f["mutate"].as_bool().unwrap() {
self.velocity = Vec3::new(17.0, 18.0, 19.0);
}
Ok(())
}
fn bounce_event(&mut self) -> io::Result<()> {
self.query("bounce-event")
}
fn mark_position_for_sync(&mut self) {
self.sync = true;
}
fn set_velocity(&mut self, v: Vec3) -> io::Result<()> {
self.query(&vector_text("set-velocity", v))?;
self.velocity = v;
Ok(())
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let arg = std::env::args().nth(1).expect("private fixture directory");
let dir = Path::new(&arg);
let mut out = io::BufWriter::new(fs::File::create(dir.join("rust.txt"))?);
for line in fs::read_to_string(dir.join("inputs.txt"))?.lines() {
let f: Value = serde_json::from_str(line)?;
let mut world = World {
f: &f,
out: &mut out,
calls: 0,
suppress_calls: 0,
gravity_calls: 0,
velocity: vector(&f["velocity"]),
sync: f["sync"].as_bool().unwrap(),
};
let result = restitute_movement(
&mut world,
RestitutionInput {
block: BlockStateId(1),
collided_x: f["x"].as_bool().unwrap(),
collided_z: f["z"].as_bool().unwrap(),
vertical_collision: f["vertical"].as_bool().unwrap(),
collision_below: f["below"].as_bool().unwrap(),
living: f["living"].as_bool().unwrap(),
resolved_movement: vector(&f["resolved"]),
fall_distance: number(&f["fall"]),
},
);
writeln!(
world.out,
"result {}",
if result.is_ok() { "ok" } else { "error" }
)?;
writeln!(world.out, "{}", vector_text("final", world.velocity))?;
writeln!(world.out, "sync {}", world.sync)?;
writeln!(world.out, "queries {}", world.calls)?;
}
Ok(())
}
+1
View File
@@ -20,6 +20,7 @@ pub mod entity_obstruction;
pub mod fluid;
pub mod heightmap;
pub mod liquid_collision;
pub mod movement_restitution;
pub mod nbt;
pub mod piston_shape;
pub mod powder_snow_collision;
@@ -0,0 +1,100 @@
//! Native post-collision restitution, including lazy bounce queries and effects.
use crate::registry::BlockStateId;
use horizon_core::{geometry::Vec3, position::BlockPos};
pub struct RestitutionInput {
pub block: BlockStateId,
pub collided_x: bool,
pub collided_z: bool,
pub vertical_collision: bool,
pub collision_below: bool,
pub living: bool,
pub resolved_movement: Vec3,
pub fall_distance: f64,
}
pub trait RestitutionWorld {
type Error;
fn suppressing_bounce(&mut self) -> Result<bool, Self::Error>;
fn entity_bounciness(&mut self) -> Result<f64, Self::Error>;
fn velocity(&mut self) -> Result<Vec3, Self::Error>;
fn effective_gravity(&mut self) -> Result<f64, Self::Error>;
fn block_suppresses_bounce(&mut self, block: BlockStateId) -> Result<bool, Self::Error>;
fn block_bounce_restitution(&mut self, block: BlockStateId) -> Result<f32, Self::Error>;
fn air_drag(&mut self) -> Result<f32, Self::Error>;
fn movement_support_position(&mut self) -> Result<BlockPos, Self::Error>;
fn bounce_on(
&mut self,
block: BlockStateId,
position: BlockPos,
fall_distance: f64,
) -> Result<(), Self::Error>;
fn bounce_event(&mut self) -> Result<(), Self::Error>;
/// Native field write after bounce callbacks and before setting velocity.
fn mark_position_for_sync(&mut self);
fn set_velocity(&mut self, velocity: Vec3) -> Result<(), Self::Error>;
}
fn java_max(a: f64, b: f64) -> f64 {
if a.is_nan() {
a
} else if b.is_nan() {
b
} else if a == 0.0 && b == 0.0 {
f64::from_bits(a.to_bits() & b.to_bits())
} else {
a.max(b)
}
}
/// Provider errors preserve already performed effects; no rollback is attempted.
/// Call only when Entity.move's simulation/collision guards request restitution.
pub fn restitute_movement<W: RestitutionWorld>(
world: &mut W,
input: RestitutionInput,
) -> Result<(), W::Error> {
let mut bounce = if world.suppressing_bounce()? {
0.0
} else {
world.entity_bounciness()?
};
let old = world.velocity()?;
let mut velocity = old;
if input.collided_x {
velocity.x = -old.x * bounce;
}
if input.collided_z {
velocity.z = -old.z * bounce;
}
let mut emit = bounce > 0.0 && (input.collided_x || input.collided_z);
if input.vertical_collision {
if input.collision_below {
if -old.y <= world.effective_gravity()?
|| world.suppressing_bounce()?
|| world.block_suppresses_bounce(input.block)?
{
bounce = 0.0;
} else {
let mut block = world.block_bounce_restitution(input.block)?;
if !input.living {
block *= 0.8f32;
}
bounce = java_max(bounce, f64::from(block));
}
}
let (gravity, drag) = if bounce > 0.0 {
let fraction = input.resolved_movement.y / old.y;
let gravity = fraction * world.effective_gravity()?;
let drag = 1.0 + fraction * (f64::from(world.air_drag()?) - 1.0);
emit = true;
(gravity, drag)
} else {
(0.0, 1.0)
};
velocity.y = (gravity - old.y) * drag * bounce;
}
if emit {
let position = world.movement_support_position()?;
world.bounce_on(input.block, position, input.fall_distance)?;
world.bounce_event()?;
world.mark_position_for_sync();
}
world.set_velocity(velocity)
}
+18
View File
@@ -519,3 +519,21 @@ nonnegative absolute limits/durations. A unit test verifies invalid parameters
leave the existing extent unchanged; workspace Clippy passes. Saved-data codecs,
listeners, dirty-state events, damage, synchronization and live movement binding
remain separate work.
`restitute_movement` now implements the pinned post-collision velocity calculation
with lazy entity/block bounce queries, gravity thresholds, nonliving float
scaling and air-drag interpolation. It preserves horizontal/vertical collision
branches, callback ordering, the position-sync field write and final velocity
assignment. Errors retain already performed effects instead of rolling back the
world or returning a fabricated successful velocity.
`python3 tools/test_restitution_parity.py` executes actual
`Entity.restituteMovementAfterCollisions` on living and nonliving snapshots with
controlled block restitution and suppression tags. The
[restitution report](../benchmarks/rust-restitution-2026-10-07/scoped-parity.json)
records **177,090 matching lines per JAR**. Fixtures cover changing lazy getter
values, signed zero/nonfinite arithmetic, bounce callbacks that alter velocity,
and failures before/after each side effect. Workspace Clippy passes. Registered
block property/tag provisioning, actual bounce sounds/events, `Entity.move`
guards, fall damage, grounding and full player simulation remain pending.
@@ -0,0 +1,78 @@
package dev.horizonlab.parity;
import com.google.gson.*;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.*;
import net.minecraft.core.*;
import net.minecraft.tags.*;
import net.minecraft.world.entity.*;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.level.*;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.*;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.phys.Vec3;
/** Actual Entity restitution with traced provider methods and bounce effects. */
public final class VanillaRestitutionOracle {
static final class Stop extends RuntimeException {}
static double number(JsonElement v){return Double.longBitsToDouble(Long.parseUnsignedLong(v.getAsString(),16));}
static float single(JsonElement v){return Float.intBitsToFloat(Integer.parseUnsignedInt(v.getAsString(),16));}
static Vec3 vector(JsonElement v){var a=v.getAsJsonArray();return new Vec3(number(a.get(0)),number(a.get(1)),number(a.get(2)));}
static String vectorText(String prefix,Vec3 v){return String.format("%s %016x %016x %016x",prefix,Double.doubleToLongBits(v.x),Double.doubleToLongBits(v.y),Double.doubleToLongBits(v.z));}
static final class Data {
JsonObject input;PrintWriter out;int calls,suppressCalls,gravityCalls;Vec3 velocity;
void query(String text){out.println(text);if(calls++==input.get("fail").getAsInt())throw new Stop();}
boolean suppress(){query("suppress");var values=input.getAsJsonArray("suppress");return values.get(Math.min(suppressCalls++,values.size()-1)).getAsBoolean();}
double gravity(){query("gravity");var values=input.getAsJsonArray("gravity");return number(values.get(Math.min(gravityCalls++,values.size()-1)));}
}
static class BounceBlock extends Block {
Data d;BounceBlock(){super(BlockBehaviour.Properties.of());}
@Override public float getBounceRestitution(){d.query("block-bounce");return single(d.input.get("block"));}
@Override public void bounceOn(Level level,BlockState state,BlockPos p,Entity entity,double distance){d.query(String.format("bounce-on %d %d %d %016x",p.getX(),p.getY(),p.getZ(),Double.doubleToLongBits(distance)));if(d.input.get("mutate").getAsBoolean())d.velocity=new Vec3(17,18,19);}
}
static class State extends BlockState {
Data d;BounceBlock block;State(){super(null,null,null);}
@Override public Block getBlock(){return block;}
@Override public boolean is(TagKey<Block> tag){if(tag!=BlockTags.SUPPRESSES_BOUNCE)throw new AssertionError(tag);d.query("block-suppresses");return d.input.get("tag").getAsBoolean();}
}
static class NonLiving extends Marker {
NonLiving(){super(null,null);}
Data d;
@Override public boolean isSuppressingBounce(){return d.suppress();}
@Override protected double getEntityBounciness(){d.query("entity-bounce");return number(d.input.get("entity"));}
@Override public Vec3 getDeltaMovement(){d.query("velocity");return d.velocity;}
@Override protected double getEffectiveGravity(){return d.gravity();}
@Override protected float getAirDrag(){d.query("drag");return single(d.input.get("drag"));}
@Override public BlockPos getBlockPosBelowThatAffectsMyMovement(){d.query("support-position");return new BlockPos(17,-64,-33);}
@Override public void gameEvent(Holder<GameEvent> event){if(event!=GameEvent.BOUNCE)throw new AssertionError(event);d.query("bounce-event");}
@Override public void setDeltaMovement(Vec3 velocity){d.query(vectorText("set-velocity",velocity));d.velocity=velocity;}
}
static class Living extends ArmorStand {
Living(){super(null,null);}
Data d;
@Override public boolean isSuppressingBounce(){return d.suppress();}
@Override protected double getEntityBounciness(){d.query("entity-bounce");return number(d.input.get("entity"));}
@Override public Vec3 getDeltaMovement(){d.query("velocity");return d.velocity;}
@Override protected double getEffectiveGravity(){return d.gravity();}
@Override protected float getAirDrag(){d.query("drag");return single(d.input.get("drag"));}
@Override public BlockPos getBlockPosBelowThatAffectsMyMovement(){d.query("support-position");return new BlockPos(17,-64,-33);}
@Override public void gameEvent(Holder<GameEvent> event){if(event!=GameEvent.BOUNCE)throw new AssertionError(event);d.query("bounce-event");}
@Override public void setDeltaMovement(Vec3 velocity){d.query(vectorText("set-velocity",velocity));d.velocity=velocity;}
}
public static void main(String[] args)throws Exception {
net.minecraft.SharedConstants.tryDetectVersion();net.minecraft.server.Bootstrap.bootStrap();
var allocator=Class.forName("sun.misc.Unsafe");var access=allocator.getDeclaredField("theUnsafe");access.setAccessible(true);var unsafe=access.get(null);var allocate=allocator.getMethod("allocateInstance",Class.class);
var method=Entity.class.getDeclaredMethod("restituteMovementAfterCollisions",BlockState.class,boolean.class,boolean.class,Vec3.class);method.setAccessible(true);Path dir=Path.of(args[1]);
try(var out=new PrintWriter(Files.newBufferedWriter(dir.resolve(args[0]+".txt")));var lines=Files.lines(dir.resolve("inputs.txt"))){
for(var line:(Iterable<String>)lines::iterator){
var f=JsonParser.parseString(line).getAsJsonObject();var d=new Data();d.input=f;d.out=out;d.velocity=vector(f.get("velocity"));Entity entity;
if(f.get("living").getAsBoolean()){var e=(Living)allocate.invoke(unsafe,Living.class);e.d=d;entity=e;}else{var e=(NonLiving)allocate.invoke(unsafe,NonLiving.class);e.d=d;entity=e;}
entity.verticalCollision=f.get("vertical").getAsBoolean();entity.verticalCollisionBelow=f.get("below").getAsBoolean();entity.fallDistance=number(f.get("fall"));entity.syncPosition=f.get("sync").getAsBoolean();
var block=(BounceBlock)allocate.invoke(unsafe,BounceBlock.class);block.d=d;var state=(State)allocate.invoke(unsafe,State.class);state.d=d;state.block=block;
try{method.invoke(entity,state,f.get("x").getAsBoolean(),f.get("z").getAsBoolean(),vector(f.get("resolved")));out.println("result ok");}
catch(InvocationTargetException error){if(error.getCause() instanceof Stop)out.println("result error");else throw error;}
out.println(vectorText("final",d.velocity));out.println("sync "+entity.syncPosition);out.println("queries "+d.calls);
}
}
}
}
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Compare post-collision restitution, bounce callbacks and partial failure state with vanilla."""
import argparse
import json
import os
from pathlib import Path
import platform
import subprocess
import random
import struct
import math
try:
from . import lab, test_rust_parity as base
except ImportError:
import lab
import test_rust_parity as base
def corpus():
def bits(v):return struct.pack('>d',float(v)).hex()
def single(v):return struct.pack('>f',float(v)).hex()
def row(flags=31,entity=0,block=1,vy=-2,gravity=(0.08,0.08),suppress=(False,False),tag=False,drag=0.98,fail=-1,mutate=False,dx=0.3,dz=-0.2,resolved=-0.1):
return dict(x=bool(flags&1),z=bool(flags&2),vertical=bool(flags&4),below=bool(flags&8),living=bool(flags&16),sync=bool(flags&32),
entity=bits(entity),block=single(block),velocity=list(map(bits,[dx,vy,dz])),resolved=list(map(bits,[0,resolved,0])),
gravity=list(map(bits,gravity)),suppress=list(suppress),tag=tag,drag=single(drag),fail=fail,mutate=mutate,fall=bits(3.25))
for flags in range(64):
for entity in [0,-0.0,0.5,1,-1,float('nan'),float('inf')]:
for block in [0,0.1,0.8,1,float('nan')]:
for suppress in [(False,False),(True,),(False,True)]:
for tag in [False,True]:yield row(flags,entity,block,vy=[-2,-0.08,0][flags%3],suppress=suppress,tag=tag)
for vy in [-float('inf'),-2,-math.nextafter(0.08,1),-0.08,-math.nextafter(0.08,0),-0.0,0,0.08,2,float('nan'),float('inf')]:
for gravity in [(0,0),(.08,.08),(.08,.3),(float('nan'),.08)]:
for flags in [4,12,15,31]:
for entity in [0,.5]:
for resolved in [0,-0.1,vy]:yield row(flags,entity,vy=vy,gravity=gravity,resolved=resolved)
for flags in [0,1,3,4,12,15,31,63]:
for fail in range(-1,17):
for mutate in [False,True]:yield row(flags,entity=.5,fail=fail,mutate=mutate)
rng=random.Random(0x5245535449545554)
for index in range(5000):
values=[-2,-.08,-0.0,0,.08,.5,1,2,float('nan'),float('inf'),-float('inf')]
yield row(rng.randrange(64),rng.choice(values),rng.choice(values),rng.choice(values),gravity=(rng.choice(values),rng.choice(values)),
suppress=(bool(rng.randrange(2)),bool(rng.randrange(2))),tag=bool(index%3==0),drag=rng.choice(values),
fail=rng.choice([-1,-1,-1,0,2,5,8,12]),mutate=bool(index%2),dx=rng.choice(values),dz=rng.choice(values),resolved=rng.choice(values))
def main():
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--prepare',action='store_true');args=parser.parse_args()
build=lab.LOCAL/'rust-restitution-parity';build.mkdir(parents=True,exist_ok=True)
report_path=build/'report.json';report_path.unlink(missing_ok=True)
if args.prepare:lab.prepare()
lock,jars,libraries=base.verify_artifacts()
oracle=lab.ROOT/'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaRestitutionOracle.java'
sources=[oracle,Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/lab.py',lab.ROOT/'Cargo.toml',lab.ROOT/'Cargo.lock',lab.ROOT/'crates/horizon-core/Cargo.toml',lab.ROOT/'crates/horizon-world/Cargo.toml',*sorted((lab.ROOT/'crates/horizon-core/src').rglob('*.rs')),*sorted((lab.ROOT/'crates/horizon-world/src').rglob('*.rs')),lab.ROOT/'crates/horizon-world/examples/restitution_fixture.rs']
tested_sources=base.source_hashes(sources)
classes=build/'classes';classes.mkdir(exist_ok=True)
def run(command,name):
with (build/(name+'.log')).open('w',encoding='utf-8') as log:base.run(list(map(str,command)),stdout=log,stderr=subprocess.STDOUT)
dependency_path=os.pathsep.join(map(str,libraries))
run(['javac','--release',lock['minecraft']['java_major'],'-proc:none','-cp',str(jars['server'])+os.pathsep+dependency_path,'-d',classes,oracle],'compile-java')
def java(side,mode,path):
run(['java','-Xmx512M','-cp',os.pathsep.join([str(classes),str(jars[side]),dependency_path]),'dev.horizonlab.parity.VanillaRestitutionOracle',mode,path],side+'-'+mode)
fixtures=list(corpus());inputs=build/'inputs.txt';inputs.write_text(''.join(json.dumps(value,separators=(',',':'))+'\n' for value in fixtures),encoding='utf-8')
fixture_paths=[inputs];tested_fixtures=base.source_hashes(fixture_paths)
run(['cargo','build','--release','-p','horizon-world','--example','restitution_fixture'],'compile-rust')
metadata=json.loads(subprocess.check_output(['cargo','metadata','--no-deps','--format-version','1'],cwd=lab.ROOT,text=True))
binary=Path(metadata['target_directory'])/'release/examples'/('restitution_fixture.exe' if os.name=='nt' else 'restitution_fixture')
run([binary,build],'rust');references=[]
for side,jar in jars.items():
java(side,side,build);reference=build/f'{side}.txt';matched=base.compare(reference,build/'rust.txt')
references.append({'side':side,'jar_sha256':lab.digest(jar,'sha256'),'output_sha256':lab.digest(reference,'sha256'),'matched_output_lines':matched})
print(f'{side}: {matched:,} restitution output lines match Rust',flush=True)
base.ensure_sources_unchanged(sources,tested_sources);base.ensure_sources_unchanged(fixture_paths,tested_fixtures)
report={'schema':1,'minecraft':lock['minecraft']['version'],'manifest_sha1':lock['minecraft']['manifest_sha1'],'platform':platform.platform(),'java':subprocess.check_output(['java','--version'],text=True).splitlines()[0],'rustc':subprocess.check_output(['rustc','--version'],text=True).strip(),'fixtures':len(fixtures),'sources_sha256':tested_sources,'fixtures_sha256':tested_fixtures,'rust_output_sha256':lab.digest(build/'rust.txt','sha256'),'references':references,'scope':'Actual Entity.restituteMovementAfterCollisions over living/nonliving snapshots and controlled block restitution/tag behavior. Compares horizontal/vertical bounce, effective-gravity threshold, nonliving float scaling, air-drag interpolation, signed zero/nonfinite values and ordered lazy queries. Bounce callbacks can change velocity and fail; final velocity and syncPosition are checked after every injected failure boundary. This does not establish registered block restitution/tag provisioning, actual bounce sound/game-event effects, Entity.move guard conditions, fall damage, grounding or full gameplay.'}
lab.write_json(report_path,report);print(f'Scoped restitution parity passed: {report_path}')
if __name__=='__main__':main()