Implement vanilla hunger, exhaustion, regeneration and starvation ticks

This commit is contained in:
Ludovic Marie committed 2026-10-07 04:07:48 +02:00
1 parent ddb0077c3a
commit 691c2e5b97
7 files changed
+672

No files matched your search

@@ -0,0 +1,243 @@
{
"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": 15211,
"sources_sha256": {
"mods/horizon/src/test/java/dev/horizonlab/parity/VanillaFoodOracle.java": "9219213c9ff960d0eee7ec250ea920aa4db5d0ef508325553c3e4203d32356a1",
"tools/test_food_parity.py": "c81fcc4430787416da5f0747eb657cee83269eb4ec7a5d35330691303c0e96fe",
"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/attributes/entity.rs": "d165e952e0ff2d1a1b037c03df5fbc778b7e263b8f9e402cf9b0fa27c933e0c6",
"crates/horizon-world/src/attributes/mod.rs": "3d05ce2ff10c1b68540e3c87bbd2ac65c56a2e7b7127a7865b60890a2e9a690d",
"crates/horizon-world/src/attributes/operation_map.rs": "fc5bc8921d78ea6721a2eaa915e67483e2a33683927467bdba24f0492356ad97",
"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_entity_push.rs": "12ee91bf604946234b24a0da4333f07d1830ab9743a422d41c8d0afac7d49cfd",
"crates/horizon-world/src/block_landing.rs": "33083fe39266fbefec1b3ea1a19f2afc609791d189da247c895e116ad6eb266a",
"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": "795895284d6e615bcb2082fceacdfe7025c152fb2934bb43f598f17a00ab46f1",
"crates/horizon-world/src/chunk_status.rs": "dbb8583cfa775203c544d24d69e03720385428f15a267a561356c21f5025e9c7",
"crates/horizon-world/src/climbing.rs": "6cd2f919872ff0b726effddaa57eccb805b8eae43f9d01144716010853891879",
"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/edge_backoff.rs": "5259d7aa44e64f88351d1556c44dd0edcad3b400a099c659755792efba7b5773",
"crates/horizon-world/src/entity_collision.rs": "09f2fd7f6cd56fc282f468ecfa765a94506929e9684bd3a5c90123e5b51cd627",
"crates/horizon-world/src/entity_fluid.rs": "02991040ae879a8914f09913128a3506e9e7a1ca33ffc0a22b5729a168346600",
"crates/horizon-world/src/entity_fluid_tick.rs": "4f9e070b88a73c98a4e2e659360ddd6edee636bca29377f1ea9011fd5a556f64",
"crates/horizon-world/src/entity_move.rs": "a40a50d3e841625a75d4cfbb7c2630ab5a62a574456567e7234f401ae8441b32",
"crates/horizon-world/src/entity_obstruction.rs": "a4b28a19bf83b23611c59d94535c488db37e4aee24b22c973c545910894a809f",
"crates/horizon-world/src/fall_damage.rs": "8c28e2933493569fe05dea22332024287d28b0746b514dd465681040b003296b",
"crates/horizon-world/src/fluid.rs": "a0a1f34ba94d8b59dc975becee8952960764d67ae08cf2d6a8bcd40a614b15c4",
"crates/horizon-world/src/fluid_box.rs": "b586af72b1c04a9354fe66205c28dc8b619e205ab9ea19a68232722e4b6e96c1",
"crates/horizon-world/src/fluid_flow.rs": "7c7e486b5c565df3602799648b37b879bcca51414337f607962d6c7951b86d05",
"crates/horizon-world/src/fluid_occupancy.rs": "26037579caeef6c8a5a2bde2c3d142d278558b76d77ebff401cee6e35f2ea961",
"crates/horizon-world/src/fluid_shape.rs": "28bb65a72f5bd074a354ba38a1b1707fcb0947537977a11540bba0adb85995c2",
"crates/horizon-world/src/food.rs": "3cf416fa632a5555a8116bb528a6e80bcb5b6b754b0748b4959ed310102418d3",
"crates/horizon-world/src/free_space.rs": "913536bf4c85ddeeb378d76d1b5110b4175f5688e1a749f32fe6a6d86fbb478e",
"crates/horizon-world/src/ground_support.rs": "3e371d74ecede4d30beef0a7e8a2ee33463584a5b1ed3d852b394efcce1ffbe0",
"crates/horizon-world/src/heightmap.rs": "a5edef082b5af40d814421ea17e129b3b06d70abee6434ed263e4bba58571698",
"crates/horizon-world/src/interaction_shapes.rs": "f321284ddab440a394372b6dedc8b5cc29a653c0745251a9e96c650f21c33dea",
"crates/horizon-world/src/lib.rs": "9cec72b2510d3ce46cf7ff844fbbea55e0fc8dbb1995187ee3a964b621fbcbf5",
"crates/horizon-world/src/liquid_collision.rs": "0f0a83b04e5a5bc9d98ed05fde4d6584ba69642e93522e726e4cfc325d6fcb1d",
"crates/horizon-world/src/living_air.rs": "b2de36bb50bed15e2808fe4a874fcef07f9fea0d2c96385b7b5e8ee9560d4442",
"crates/horizon-world/src/living_fall.rs": "da0f176f4731e1c86e08bfeee8167742fd1b86380ff0e9304c13c1ce3f1c67ab",
"crates/horizon-world/src/living_fluid.rs": "d7cd914cfb2a1f4876ce0ae95e999bf0ff1af732cce3ab4a3aa2d1ab881c7339",
"crates/horizon-world/src/living_landing.rs": "8bca4eb7977abc2b14fb7174be4ea355352106ea1e76ff5f3ad8696969d9eefd",
"crates/horizon-world/src/living_motion.rs": "56f8c84cfbe205a7a69912d5f76b52ef1efca149c1b6801d4684e25fb179aeb9",
"crates/horizon-world/src/main.rs": "fce57418b8c22a6a0a9044baa89ed3d9c243bc2e8413fea3ff0f32985daba021",
"crates/horizon-world/src/movement_emission.rs": "76e7d6f5b19c94676190f54389f9c4ce12cf89945ccfcb9c750de473223e7753",
"crates/horizon-world/src/movement_properties.rs": "6f01ab920b376a13f5ae12c3718f4dd067e1dff2b829585af00a8b2100f3775d",
"crates/horizon-world/src/movement_restitution.rs": "04b4fe0947e7e52576145bd8ed15920278e8a538afcc74d125eaca513dffa0ef",
"crates/horizon-world/src/movement_support.rs": "02d85cf572847f2e60b5a7f9d60b856888a67b3c1f4df03e81998c66fdf874be",
"crates/horizon-world/src/nbt.rs": "60262c3de8c8f68bc2da0370ccb17393d32b85f5f5022c6358599a7d32119ea6",
"crates/horizon-world/src/piston_movement.rs": "7a3fd2549a582b5f3a5c21aa05d67d944d62ca1105856679e100ac5b44e9a52f",
"crates/horizon-world/src/piston_shape.rs": "399c2337103b8e7e0eb5667227c5aa14f9549d6a467b7be7da3d0717f0263500",
"crates/horizon-world/src/player_travel.rs": "6841a677a900495eb6223031ef3ebe8d859bb98908dda0e2209e5f28839dc593",
"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": "97cc950a9a4ba387b78e368f36d595f8664ab7730d132900d220ce23b92826ea",
"crates/horizon-world/src/registry_fall_ray.rs": "52935436132df505691144840dcbd858b15d00d8305997284e3a542ecf6bc0dc",
"crates/horizon-world/src/registry_fluid.rs": "b8959c05850cfeaaad1e4de8ca7c7d0d6ac56da9b2c1de3ebefa5871a11ed35e",
"crates/horizon-world/src/retrogen.rs": "6efd36a6293756688c1990a7965bd65609d7c5e2bf2a5f4f01d54932d2b9386b",
"crates/horizon-world/src/suffocation.rs": "bed219dd5bbab6263b98fbe3d0eb512cb96c52ae847210efe49514d05f22f11e",
"crates/horizon-world/src/swimming.rs": "49d2dae13c109b37ff832cde98a857944ff45247b864a527b9c4f03ecca7a7b7",
"crates/horizon-world/src/tick_data.rs": "f8d04a69ea263f4168bce33f92e5b4ee2fe07d7caf50a1621c6b3ef6eb2479f3",
"crates/horizon-world/src/water_splash.rs": "e0461ae1b2e8c851cca6e3251a188334293292201d7f80dc9e7d60fd1815f4d1",
"crates/horizon-world/src/world_movement.rs": "f8fe9e34f2395d2a04c839d1c8ba78e09ce493c26de6f6e4e237b9c19c061da5",
"crates/horizon-world/src/world_ray.rs": "879bdbbe12f56deb705cc183933431489e840b71eade40c33391eed5949c2e61",
"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/food_fixture.rs": "af283fa5c04667da40a283c3c5eafce781d110abdfe274b6f9b18d5a6783ebf0"
},
"fixtures_sha256": {
".local/rust-food-parity/inputs.txt": "50837fa91cc6a8354a10cd6a02584ca6e41656eea5e0ecbebe407b04fa6dbe48"
},
"rust_output_sha256": "5f63f3351ef5c24dc26b3a499eaba730bc4996311cf24d34dd2707632665d464",
"references": [
{
"side": "client",
"jar_sha256": "4508d006323f24fa02876310c192d739af56516eb259000ac50f0909a68c9a2d",
"output_sha256": "5f63f3351ef5c24dc26b3a499eaba730bc4996311cf24d34dd2707632665d464",
"matched_output_lines": 180878
},
{
"side": "server",
"jar_sha256": "a362163eec5d1612d520772bc16e5b39c09e3b234fdc045f56bf544284ee8ae6",
"output_sha256": "5f63f3351ef5c24dc26b3a499eaba730bc4996311cf24d34dd2707632665d464",
"matched_output_lines": 180878
}
],
"scope": "Actual FoodData eating/properties, exhaustion, hunger predicates and server tick, including query order, mutations/failures, regeneration thresholds, starvation difficulty/health guards, signed-zero/nonfinite values and integer overflow. Player healing/damage and world queries are controlled; full damage/health, consumables, persistence and playable integration remain pending."
}
@@ -0,0 +1,145 @@
use horizon_world::food::{Difficulty, FoodData, FoodWorld};
use serde_json::Value;
use std::{
fs,
io::{self, Write},
path::Path,
};
fn single(v: &Value) -> f32 {
f32::from_bits(u32::from_str_radix(v.as_str().unwrap(), 16).unwrap())
}
fn bits(v: f32) -> u32 {
if v.is_nan() {
f32::NAN.to_bits()
} else {
v.to_bits()
}
}
fn state(v: &Value) -> FoodData {
FoodData {
food: v["food"].as_i64().unwrap() as i32,
saturation: single(&v["saturation"]),
exhaustion: single(&v["exhaustion"]),
timer: v["timer"].as_i64().unwrap() as i32,
}
}
struct World<'a> {
f: &'a Value,
out: &'a mut dyn Write,
calls: i64,
changed: bool,
}
impl World<'_> {
fn query(&mut self, food: &mut FoodData, s: &str) -> io::Result<()> {
writeln!(self.out, "{s}")?;
let n = self.calls;
self.calls += 1;
if n == self.f["fail"].as_i64().unwrap() {
return Err(io::Error::other("injected food callback failure"));
}
if n == self.f["mutate_at"].as_i64().unwrap() {
*food = state(&self.f["after"]);
self.changed = true;
}
Ok(())
}
fn world(&self) -> &Value {
&self.f[if self.changed { "after_world" } else { "world" }]
}
}
impl FoodWorld for World<'_> {
type Error = io::Error;
type Level = ();
fn level(&mut self, f: &mut FoodData) -> io::Result<()> {
self.query(f, "level")
}
fn difficulty(&mut self, _: (), f: &mut FoodData) -> io::Result<Difficulty> {
self.query(f, "difficulty")?;
Ok([
Difficulty::Peaceful,
Difficulty::Easy,
Difficulty::Normal,
Difficulty::Hard,
][self.world()["difficulty"].as_u64().unwrap() as usize])
}
fn natural_regeneration(&mut self, _: (), f: &mut FoodData) -> io::Result<bool> {
self.query(f, "rules")?;
self.query(f, "regeneration")?;
Ok(self.world()["regen"].as_bool().unwrap())
}
fn is_hurt(&mut self, f: &mut FoodData) -> io::Result<bool> {
self.query(f, "is-hurt")?;
Ok(self.world()["hurt"].as_bool().unwrap())
}
fn heal(&mut self, f: &mut FoodData, v: f32) -> io::Result<()> {
self.query(f, &format!("heal {:08x}", bits(v)))
}
fn health(&mut self, f: &mut FoodData) -> io::Result<f32> {
self.query(f, "health")?;
Ok(single(&self.world()["health"]))
}
fn hurt_starvation(&mut self, _: (), f: &mut FoodData, v: f32) -> io::Result<()> {
self.query(f, "damage-sources")?;
self.query(f, "starve-source")?;
self.query(f, &format!("hurt {:08x}", bits(v)))
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let arg = std::env::args().nth(1).unwrap();
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 food = state(&f["initial"]);
let mut w = World {
f: &f,
out: &mut out,
calls: 0,
changed: false,
};
let mut error = false;
for op in f["ops"].as_array().unwrap() {
match op["mode"].as_str().unwrap() {
"eat" => food.eat(
op["nutrition"].as_i64().unwrap() as i32,
single(&op["amount"]),
),
"properties" => food.eat_properties(
op["nutrition"].as_i64().unwrap() as i32,
single(&op["amount"]),
),
"exhaust" => food.add_exhaustion(single(&op["amount"])),
"food" => food.food = op["nutrition"].as_i64().unwrap() as i32,
"saturation" => food.saturation = single(&op["amount"]),
"tick" => {
if food.tick(&mut w).is_err() {
error = true;
break;
}
}
_ => panic!("mode"),
}
writeln!(
w.out,
"state {} {:08x} {:08x} {} {} {}",
food.food,
bits(food.saturation),
bits(food.exhaustion),
food.timer,
food.has_enough_food(),
food.needs_food()
)?;
}
writeln!(w.out, "result {}", if error { "error" } else { "ok" })?;
writeln!(
w.out,
"final {} {:08x} {:08x} {}",
food.food,
bits(food.saturation),
bits(food.exhaustion),
food.timer
)?;
writeln!(w.out, "queries {}", w.calls)?;
}
Ok(())
}
+135
View File
@@ -0,0 +1,135 @@
//! Hunger, saturation, exhaustion and server-side natural regeneration.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Difficulty {
Peaceful,
Easy,
Normal,
Hard,
}
#[derive(Clone, Copy, Debug)]
pub struct FoodData {
pub food: i32,
pub saturation: f32,
pub exhaustion: f32,
pub timer: i32,
}
impl Default for FoodData {
fn default() -> Self {
Self {
food: 20,
saturation: 5.0,
exhaustion: 0.0,
timer: 0,
}
}
}
fn min(a: f32, b: f32) -> f32 {
if a.is_nan() || b.is_nan() {
f32::NAN
} else if a == 0.0 && b == 0.0 {
f32::from_bits(a.to_bits() | b.to_bits())
} else {
a.min(b)
}
}
fn max(a: f32, b: f32) -> f32 {
if a.is_nan() || b.is_nan() {
f32::NAN
} else if a == 0.0 && b == 0.0 {
f32::from_bits(a.to_bits() & b.to_bits())
} else {
a.max(b)
}
}
fn clamp(v: f32, lo: f32, hi: f32) -> f32 {
if v < lo { lo } else { min(v, hi) }
}
impl FoodData {
pub fn eat(&mut self, nutrition: i32, saturation_modifier: f32) {
self.eat_properties(nutrition, nutrition as f32 * saturation_modifier * 2.0);
}
pub fn eat_properties(&mut self, nutrition: i32, saturation: f32) {
self.food = nutrition.wrapping_add(self.food).clamp(0, 20);
self.saturation = clamp(saturation + self.saturation, 0.0, self.food as f32);
}
pub fn add_exhaustion(&mut self, value: f32) {
self.exhaustion = min(self.exhaustion + value, 40.0);
}
pub fn has_enough_food(&self) -> bool {
self.food as f32 > 6.0
}
pub fn needs_food(&self) -> bool {
self.food < 20
}
pub fn tick<W: FoodWorld>(&mut self, world: &mut W) -> Result<(), W::Error> {
let level = world.level(self)?;
let difficulty = world.difficulty(level, self)?;
if self.exhaustion > 4.0 {
self.exhaustion -= 4.0;
if self.saturation > 0.0 {
self.saturation = max(self.saturation - 1.0, 0.0);
} else if difficulty != Difficulty::Peaceful {
self.food = self.food.wrapping_sub(1).max(0);
}
}
let regenerate = world.natural_regeneration(level, self)?;
if regenerate && self.saturation > 0.0 && world.is_hurt(self)? && self.food >= 20 {
self.timer = self.timer.wrapping_add(1);
if self.timer >= 10 {
let amount = min(self.saturation, 6.0);
world.heal(self, amount / 6.0)?;
self.add_exhaustion(amount);
self.timer = 0;
}
} else if regenerate && self.food >= 18 && world.is_hurt(self)? {
self.timer = self.timer.wrapping_add(1);
if self.timer >= 80 {
world.heal(self, 1.0)?;
self.add_exhaustion(6.0);
self.timer = 0;
}
} else if self.food <= 0 {
self.timer = self.timer.wrapping_add(1);
if self.timer >= 80 {
if world.health(self)? > 10.0
|| difficulty == Difficulty::Hard
|| (world.health(self)? > 1.0 && difficulty == Difficulty::Normal)
{
world.hurt_starvation(level, self, 1.0)?;
}
self.timer = 0;
}
} else {
self.timer = 0;
}
Ok(())
}
}
/// World hooks preserve short-circuit query order. Callbacks can mutate food
/// state; errors retain earlier writes and effects. The level is captured once.
pub trait FoodWorld {
type Error;
type Level: Copy;
fn level(&mut self, food: &mut FoodData) -> Result<Self::Level, Self::Error>;
fn difficulty(
&mut self,
level: Self::Level,
food: &mut FoodData,
) -> Result<Difficulty, Self::Error>;
fn natural_regeneration(
&mut self,
level: Self::Level,
food: &mut FoodData,
) -> Result<bool, Self::Error>;
fn is_hurt(&mut self, food: &mut FoodData) -> Result<bool, Self::Error>;
fn heal(&mut self, food: &mut FoodData, amount: f32) -> Result<(), Self::Error>;
fn health(&mut self, food: &mut FoodData) -> Result<f32, Self::Error>;
/// Resolve the player's starvation damage source and dispatch hurtServer on
/// the captured level; the native boolean hurt result is ignored.
fn hurt_starvation(
&mut self,
level: Self::Level,
food: &mut FoodData,
amount: f32,
) -> Result<(), Self::Error>;
}
+1
View File
@@ -31,6 +31,7 @@ pub mod fluid_box;
pub mod fluid_flow;
pub mod fluid_occupancy;
pub mod fluid_shape;
pub mod food;
pub mod free_space;
pub mod ground_support;
pub mod heightmap;
+16
View File
@@ -1133,3 +1133,19 @@ Clippy passes. The exporter asserts that registered default suppliers contain no
modifiers. Set membership is observed in registry order: this does not establish
JVM identity-hash collection order, cross-attribute bulk assignment/serialization,
custom suppliers, equipment integration or a live entity tick.
`food` implements eating, saturation, exhaustion, hunger predicates and the
server hunger tick. It retains the captured level/difficulty, short-circuit
player queries, fast/slow regeneration timers, starvation health/difficulty
guards, wrapping integer arithmetic, Java float edge behavior and partial state
writes when callbacks fail. Heal and starvation requests use explicit world
hooks, which can mutate food state before subsequent tick work.
`python3 tools/test_food_parity.py` compares actual `FoodData` methods. The
[food report](../benchmarks/rust-food-2026-10-07/scoped-parity.json) records
**180,878 matching lines per JAR** across **15,211 fixtures**, including 240-tick
sequences, threshold boundaries, all difficulties, callback mutation/failure,
signed zero and nonfinite arithmetic. Targeted Clippy passes. World rules,
player health/healing and damage delivery are controlled; consumables, saved
hunger data and live survival gameplay integration remain pending.
@@ -0,0 +1,48 @@
package dev.horizonlab.parity;
import com.google.gson.*;import java.io.*;import java.lang.reflect.*;import java.nio.file.*;import java.util.*;
import net.minecraft.world.food.*;import net.minecraft.world.Difficulty;import net.minecraft.server.level.*;
import net.minecraft.world.damagesource.*;import net.minecraft.world.level.gamerules.*;
/** Actual FoodData arithmetic and tick with observable player/world callbacks. */
public final class VanillaFoodOracle {
static final class Stop extends RuntimeException {}
static Object unsafe;static Method allocate;static Field exhaustion,timer;
static <T>T make(Class<T> c)throws Exception{return c.cast(allocate.invoke(unsafe,c));}
static float single(JsonElement v){return Float.intBitsToFloat(Integer.parseUnsignedInt(v.getAsString(),16));}
static String bits(float v){return String.format("%08x",Float.floatToIntBits(v));}
static Field field(String n)throws Exception{var f=FoodData.class.getDeclaredField(n);f.setAccessible(true);return f;}
static void set(FoodData food,JsonObject v){try{food.setFoodLevel(v.get("food").getAsInt());food.setSaturation(single(v.get("saturation")));exhaustion.setFloat(food,single(v.get("exhaustion")));timer.setInt(food,v.get("timer").getAsInt());}catch(Exception e){throw new AssertionError(e);}}
static final class Data {JsonObject f;PrintWriter out;FoodData food;int calls;boolean changed;Level level;Rules rules;Sources sources;
void query(String s){out.println(s);int n=calls++;if(n==f.get("fail").getAsInt())throw new Stop();if(n==f.get("mutate_at").getAsInt()){set(food,f.getAsJsonObject("after"));changed=true;}}
JsonObject state(){return changed?f.getAsJsonObject("after_world"):f.getAsJsonObject("world");}
}
static final class Rules extends GameRules {Data d;Rules(){super(net.minecraft.world.flag.FeatureFlagSet.of());}@SuppressWarnings("unchecked")@Override public <T>T get(GameRule<T> r){if(r!=GameRules.NATURAL_HEALTH_REGENERATION)throw new AssertionError();d.query("regeneration");return (T)Boolean.valueOf(d.state().get("regen").getAsBoolean());}}
static final class Sources extends DamageSources {Data d;DamageSource source;Sources(){super(null);}@Override public DamageSource starve(){d.query("starve-source");return source;}}
static final class Level extends ServerLevel {Data d;Level(){super(null,null,null,null,null,null,false,0L,List.of(),false);}
@Override public Difficulty getDifficulty(){d.query("difficulty");return Difficulty.values()[d.state().get("difficulty").getAsInt()];}
@Override public GameRules getGameRules(){d.query("rules");return d.rules;}
}
static final class Actor extends ServerPlayer {Data d;Actor(){super(null,null,null,null);}
@Override public ServerLevel level(){d.query("level");return d.level;}
@Override public boolean isHurt(){d.query("is-hurt");return d.state().get("hurt").getAsBoolean();}
@Override public float getHealth(){d.query("health");return single(d.state().get("health"));}
@Override public void heal(float v){d.query("heal "+bits(v));}
@Override public DamageSources damageSources(){d.query("damage-sources");return d.sources;}
@Override public boolean hurtServer(ServerLevel l,DamageSource s,float v){if(l!=d.level||s!=d.sources.source)throw new AssertionError();d.query("hurt "+bits(v));return false;}
}
public static void main(String[] args)throws Exception {
net.minecraft.SharedConstants.tryDetectVersion();net.minecraft.server.Bootstrap.bootStrap();var u=Class.forName("sun.misc.Unsafe");var uf=u.getDeclaredField("theUnsafe");uf.setAccessible(true);unsafe=uf.get(null);allocate=u.getMethod("allocateInstance",Class.class);exhaustion=field("exhaustionLevel");timer=field("tickTimer");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.f=f;d.out=out;d.food=new FoodData();set(d.food,f.getAsJsonObject("initial"));d.level=make(Level.class);d.level.d=d;d.rules=make(Rules.class);d.rules.d=d;d.sources=make(Sources.class);d.sources.d=d;d.sources.source=make(DamageSource.class);var actor=make(Actor.class);actor.d=d;
try{for(var v:f.getAsJsonArray("ops")){var op=v.getAsJsonObject();switch(op.get("mode").getAsString()){
case "eat":d.food.eat(op.get("nutrition").getAsInt(),single(op.get("amount")));break;
case "properties":d.food.eat(new FoodProperties(op.get("nutrition").getAsInt(),single(op.get("amount")),false));break;
case "exhaust":d.food.addExhaustion(single(op.get("amount")));break;
case "food":d.food.setFoodLevel(op.get("nutrition").getAsInt());break;
case "saturation":d.food.setSaturation(single(op.get("amount")));break;
case "tick":d.food.tick(actor);break;
default:throw new AssertionError();}
out.println("state "+d.food.getFoodLevel()+" "+bits(d.food.getSaturationLevel())+" "+bits(exhaustion.getFloat(d.food))+" "+timer.getInt(d.food)+" "+d.food.hasEnoughFood()+" "+d.food.needsFood());
}out.println("result ok");}catch(Stop e){out.println("result error");}
out.println("final "+d.food.getFoodLevel()+" "+bits(d.food.getSaturationLevel())+" "+bits(exhaustion.getFloat(d.food))+" "+timer.getInt(d.food));out.println("queries "+d.calls);
}}
}
}
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Compare food arithmetic, exhaustion and server hunger tick 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('>f',v).hex()
def state(food=20,saturation=5,exhaustion=0,timer=0):return dict(food=food,saturation=bits(saturation),exhaustion=bits(exhaustion),timer=timer)
def world(difficulty=2,regen=True,hurt=True,health=10):return dict(difficulty=difficulty,regen=regen,hurt=hurt,health=bits(health))
def op(mode='tick',nutrition=0,amount=0):return dict(mode=mode,nutrition=nutrition,amount=bits(amount))
def row(initial,ops,**kw):
f=dict(initial=initial,ops=ops,world=world(),after=state(0,0,7,79),after_world=world(3,False,False,1),fail=-1,mutate_at=-1);f.update(kw);return f
edges=[-float('inf'),-1e30,-1,-0.,0.,1e-40,.1,1,4,5,6,20,40,1e30,float('inf'),float('nan')]
foods=[-2147483648,-1,0,1,6,17,18,19,20,21,2147483647]
timers=[-2147483648,-1,0,8,9,10,78,79,80,2147483647]
for food in foods:
for nutrition in foods:
for value in edges:
for saturation in [-1.,-0.,0.,5.,float('nan')]:
yield row(state(food,saturation),[op('eat',nutrition,value),op('properties',nutrition,value),op('exhaust',amount=value)])
for difficulty in range(4):
for regen in [False,True]:
for hurt in [False,True]:
for food in [-1,0,17,18,19,20,21]:
for saturation in [0.,.1,5.,6.,float('nan')]:
for timer in [0,9,79]:
yield row(state(food,saturation,4.000001,timer),[op(),op()],world=world(difficulty,regen,hurt,1))
for health in edges:
for difficulty in range(4):yield row(state(0,0,0,79),[op()],world=world(difficulty,False,True,health))
for initial in [state(20,5,0,9),state(18,0,0,79),state(0,0,0,79),state(20,5,5,9)]:
for fail in range(-1,13):
for mutation in range(-1,13):yield row(initial,[op()],fail=fail,mutate_at=mutation)
rng=random.Random(0x464f4f44)
for _ in range(3000):
ops=[op(rng.choice(['tick','tick','tick','eat','properties','exhaust','food','saturation']),rng.choice(foods),rng.choice(edges)) for __ in range(rng.randrange(1,50))]
yield row(state(rng.choice(foods),rng.choice(edges),rng.choice(edges),rng.choice(timers)),ops,world=world(rng.randrange(4),bool(rng.randrange(2)),bool(rng.randrange(2)),rng.choice(edges)),fail=rng.choice([-1,-1,0,3,5,9]),mutate_at=rng.choice([-1,-1,1,4,6,8]))
for initial in [state(20,5),state(18,0),state(0,0)]:yield row(initial,[op() for _ in range(240)])
def main():
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--prepare',action='store_true');args=parser.parse_args()
build=lab.LOCAL/'rust-food-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/VanillaFoodOracle.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/food_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.VanillaFoodOracle',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','food_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'/('food_fixture.exe' if os.name=='nt' else 'food_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:,} food 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 FoodData eating/properties, exhaustion, hunger predicates and server tick, including query order, mutations/failures, regeneration thresholds, starvation difficulty/health guards, signed-zero/nonfinite values and integer overflow. Player healing/damage and world queries are controlled; full damage/health, consumables, persistence and playable integration remain pending.'}
lab.write_json(report_path,report);print(f'Scoped food parity passed: {report_path}')
if __name__=='__main__':main()